| #!/usr/bin/python |
| """ |
| Step file creator/editor. |
| |
| @copyright: Red Hat Inc 2009 |
| @author: mgoldish@redhat.com (Michael Goldish) |
| @version: "20090401" |
| """ |
| |
| import pygtk, gtk, os, glob, shutil, sys, logging |
| import common, ppm_utils |
| pygtk.require('2.0') |
| |
| |
| # General utilities |
| |
| def corner_and_size_clipped(startpoint, endpoint, limits): |
| c0 = startpoint[:] |
| c1 = endpoint[:] |
| if c0[0] < 0: |
| c0[0] = 0 |
| if c0[1] < 0: |
| c0[1] = 0 |
| if c1[0] < 0: |
| c1[0] = 0 |
| if c1[1] < 0: |
| c1[1] = 0 |
| if c0[0] > limits[0] - 1: |
| c0[0] = limits[0] - 1 |
| if c0[1] > limits[1] - 1: |
| c0[1] = limits[1] - 1 |
| if c1[0] > limits[0] - 1: |
| c1[0] = limits[0] - 1 |
| if c1[1] > limits[1] - 1: |
| c1[1] = limits[1] - 1 |
| return ([min(c0[0], c1[0]), |
| min(c0[1], c1[1])], |
| [abs(c1[0] - c0[0]) + 1, |
| abs(c1[1] - c0[1]) + 1]) |
| |
| |
| def key_event_to_qemu_string(event): |
| keymap = gtk.gdk.keymap_get_default() |
| keyvals = keymap.get_entries_for_keycode(event.hardware_keycode) |
| keyval = keyvals[0][0] |
| keyname = gtk.gdk.keyval_name(keyval) |
| |
| dict = { "Return": "ret", |
| "Tab": "tab", |
| "space": "spc", |
| "Left": "left", |
| "Right": "right", |
| "Up": "up", |
| "Down": "down", |
| "F1": "f1", |
| "F2": "f2", |
| "F3": "f3", |
| "F4": "f4", |
| "F5": "f5", |
| "F6": "f6", |
| "F7": "f7", |
| "F8": "f8", |
| "F9": "f9", |
| "F10": "f10", |
| "F11": "f11", |
| "F12": "f12", |
| "Escape": "esc", |
| "minus": "minus", |
| "equal": "equal", |
| "BackSpace": "backspace", |
| "comma": "comma", |
| "period": "dot", |
| "slash": "slash", |
| "Insert": "insert", |
| "Delete": "delete", |
| "Home": "home", |
| "End": "end", |
| "Page_Up": "pgup", |
| "Page_Down": "pgdn", |
| "Menu": "menu", |
| "semicolon": "0x27", |
| "backslash": "0x2b", |
| "apostrophe": "0x28", |
| "grave": "0x29", |
| "less": "0x2b", |
| "bracketleft": "0x1a", |
| "bracketright": "0x1b", |
| "Super_L": "0xdc", |
| "Super_R": "0xdb", |
| } |
| |
| if ord('a') <= keyval <= ord('z') or ord('0') <= keyval <= ord('9'): |
| str = keyname |
| elif keyname in dict.keys(): |
| str = dict[keyname] |
| else: |
| return "" |
| |
| if event.state & gtk.gdk.CONTROL_MASK: |
| str = "ctrl-" + str |
| if event.state & gtk.gdk.MOD1_MASK: |
| str = "alt-" + str |
| if event.state & gtk.gdk.SHIFT_MASK: |
| str = "shift-" + str |
| |
| return str |
| |
| |
| class StepMakerWindow: |
| |
| # Constructor |
| |
| def __init__(self): |
| # Window |
| self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
| self.window.set_title("Step Maker Window") |
| self.window.connect("delete-event", self.delete_event) |
| self.window.connect("destroy", self.destroy) |
| self.window.set_default_size(600, 800) |
| |
| # Main box (inside a frame which is inside a VBox) |
| self.menu_vbox = gtk.VBox() |
| self.window.add(self.menu_vbox) |
| self.menu_vbox.show() |
| |
| frame = gtk.Frame() |
| frame.set_border_width(10) |
| frame.set_shadow_type(gtk.SHADOW_NONE) |
| self.menu_vbox.pack_end(frame) |
| frame.show() |
| |
| self.main_vbox = gtk.VBox(spacing=10) |
| frame.add(self.main_vbox) |
| self.main_vbox.show() |
| |
| # EventBox |
| self.scrolledwindow = gtk.ScrolledWindow() |
| self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, |
| gtk.POLICY_AUTOMATIC) |
| self.scrolledwindow.set_shadow_type(gtk.SHADOW_NONE) |
| self.main_vbox.pack_start(self.scrolledwindow) |
| self.scrolledwindow.show() |
| |
| table = gtk.Table(1, 1) |
| self.scrolledwindow.add_with_viewport(table) |
| table.show() |
| table.realize() |
| |
| self.event_box = gtk.EventBox() |
| table.attach(self.event_box, 0, 1, 0, 1, gtk.EXPAND, gtk.EXPAND) |
| self.event_box.show() |
| self.event_box.realize() |
| |
| # Image |
| self.image = gtk.Image() |
| self.event_box.add(self.image) |
| self.image.show() |
| |
| # Data VBox |
| self.data_vbox = gtk.VBox(spacing=10) |
| self.main_vbox.pack_start(self.data_vbox, expand=False) |
| self.data_vbox.show() |
| |
| # User VBox |
| self.user_vbox = gtk.VBox(spacing=10) |
| self.main_vbox.pack_start(self.user_vbox, expand=False) |
| self.user_vbox.show() |
| |
| # Screendump ID HBox |
| box = gtk.HBox(spacing=10) |
| self.data_vbox.pack_start(box) |
| box.show() |
| |
| label = gtk.Label("Screendump ID:") |
| box.pack_start(label, False) |
| label.show() |
| |
| self.entry_screendump = gtk.Entry() |
| self.entry_screendump.set_editable(False) |
| box.pack_start(self.entry_screendump) |
| self.entry_screendump.show() |
| |
| label = gtk.Label("Time:") |
| box.pack_start(label, False) |
| label.show() |
| |
| self.entry_time = gtk.Entry() |
| self.entry_time.set_editable(False) |
| self.entry_time.set_width_chars(10) |
| box.pack_start(self.entry_time, False) |
| self.entry_time.show() |
| |
| # Comment HBox |
| box = gtk.HBox(spacing=10) |
| self.data_vbox.pack_start(box) |
| box.show() |
| |
| label = gtk.Label("Comment:") |
| box.pack_start(label, False) |
| label.show() |
| |
| self.entry_comment = gtk.Entry() |
| box.pack_start(self.entry_comment) |
| self.entry_comment.show() |
| |
| # Sleep HBox |
| box = gtk.HBox(spacing=10) |
| self.data_vbox.pack_start(box) |
| box.show() |
| |
| self.check_sleep = gtk.CheckButton("Sleep:") |
| self.check_sleep.connect("toggled", self.event_check_sleep_toggled) |
| box.pack_start(self.check_sleep, False) |
| self.check_sleep.show() |
| |
| self.spin_sleep = gtk.SpinButton(gtk.Adjustment(0, 0, 50000, 1, 10, 0), |
| climb_rate=0.0) |
| box.pack_start(self.spin_sleep, False) |
| self.spin_sleep.show() |
| |
| # Barrier HBox |
| box = gtk.HBox(spacing=10) |
| self.data_vbox.pack_start(box) |
| box.show() |
| |
| self.check_barrier = gtk.CheckButton("Barrier:") |
| self.check_barrier.connect("toggled", self.event_check_barrier_toggled) |
| box.pack_start(self.check_barrier, False) |
| self.check_barrier.show() |
| |
| vbox = gtk.VBox() |
| box.pack_start(vbox) |
| vbox.show() |
| |
| self.label_barrier_region = gtk.Label("Region:") |
| self.label_barrier_region.set_alignment(0, 0.5) |
| vbox.pack_start(self.label_barrier_region) |
| self.label_barrier_region.show() |
| |
| self.label_barrier_md5sum = gtk.Label("MD5:") |
| self.label_barrier_md5sum.set_alignment(0, 0.5) |
| vbox.pack_start(self.label_barrier_md5sum) |
| self.label_barrier_md5sum.show() |
| |
| self.label_barrier_timeout = gtk.Label("Timeout:") |
| box.pack_start(self.label_barrier_timeout, False) |
| self.label_barrier_timeout.show() |
| |
| self.spin_barrier_timeout = gtk.SpinButton(gtk.Adjustment(0, 0, 50000, |
| 1, 10, 0), |
| climb_rate=0.0) |
| box.pack_start(self.spin_barrier_timeout, False) |
| self.spin_barrier_timeout.show() |
| |
| self.check_barrier_optional = gtk.CheckButton("Optional") |
| box.pack_start(self.check_barrier_optional, False) |
| self.check_barrier_optional.show() |
| |
| # Keystrokes HBox |
| box = gtk.HBox(spacing=10) |
| self.data_vbox.pack_start(box) |
| box.show() |
| |
| label = gtk.Label("Keystrokes:") |
| box.pack_start(label, False) |
| label.show() |
| |
| frame = gtk.Frame() |
| frame.set_shadow_type(gtk.SHADOW_IN) |
| box.pack_start(frame) |
| frame.show() |
| |
| self.text_buffer = gtk.TextBuffer() |
| self.entry_keys = gtk.TextView(self.text_buffer) |
| self.entry_keys.set_wrap_mode(gtk.WRAP_WORD) |
| self.entry_keys.connect("key-press-event", self.event_key_press) |
| frame.add(self.entry_keys) |
| self.entry_keys.show() |
| |
| self.check_manual = gtk.CheckButton("Manual") |
| self.check_manual.connect("toggled", self.event_manual_toggled) |
| box.pack_start(self.check_manual, False) |
| self.check_manual.show() |
| |
| button = gtk.Button("Clear") |
| button.connect("clicked", self.event_clear_clicked) |
| box.pack_start(button, False) |
| button.show() |
| |
| # Mouse click HBox |
| box = gtk.HBox(spacing=10) |
| self.data_vbox.pack_start(box) |
| box.show() |
| |
| label = gtk.Label("Mouse action:") |
| box.pack_start(label, False) |
| label.show() |
| |
| self.button_capture = gtk.Button("Capture") |
| box.pack_start(self.button_capture, False) |
| self.button_capture.show() |
| |
| self.check_mousemove = gtk.CheckButton("Move: ...") |
| box.pack_start(self.check_mousemove, False) |
| self.check_mousemove.show() |
| |
| self.check_mouseclick = gtk.CheckButton("Click: ...") |
| box.pack_start(self.check_mouseclick, False) |
| self.check_mouseclick.show() |
| |
| self.spin_sensitivity = gtk.SpinButton(gtk.Adjustment(1, 1, 100, 1, 10, |
| 0), |
| climb_rate=0.0) |
| box.pack_end(self.spin_sensitivity, False) |
| self.spin_sensitivity.show() |
| |
| label = gtk.Label("Sensitivity:") |
| box.pack_end(label, False) |
| label.show() |
| |
| self.spin_latency = gtk.SpinButton(gtk.Adjustment(10, 1, 500, 1, 10, 0), |
| climb_rate=0.0) |
| box.pack_end(self.spin_latency, False) |
| self.spin_latency.show() |
| |
| label = gtk.Label("Latency:") |
| box.pack_end(label, False) |
| label.show() |
| |
| self.handler_event_box_press = None |
| self.handler_event_box_release = None |
| self.handler_event_box_scroll = None |
| self.handler_event_box_motion = None |
| self.handler_event_box_expose = None |
| |
| self.window.realize() |
| self.window.show() |
| |
| self.clear_state() |
| |
| # Utilities |
| |
| def message(self, text, title): |
| dlg = gtk.MessageDialog(self.window, |
| gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, |
| gtk.MESSAGE_INFO, |
| gtk.BUTTONS_CLOSE, |
| title) |
| dlg.set_title(title) |
| dlg.format_secondary_text(text) |
| response = dlg.run() |
| dlg.destroy() |
| |
| |
| def question_yes_no(self, text, title): |
| dlg = gtk.MessageDialog(self.window, |
| gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, |
| gtk.MESSAGE_QUESTION, |
| gtk.BUTTONS_YES_NO, |
| title) |
| dlg.set_title(title) |
| dlg.format_secondary_text(text) |
| response = dlg.run() |
| dlg.destroy() |
| if response == gtk.RESPONSE_YES: |
| return True |
| return False |
| |
| |
| def inputdialog(self, text, title, default_response=""): |
| # Define a little helper function |
| def inputdialog_entry_activated(entry): |
| dlg.response(gtk.RESPONSE_OK) |
| |
| # Create the dialog |
| dlg = gtk.MessageDialog(self.window, |
| gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, |
| gtk.MESSAGE_QUESTION, |
| gtk.BUTTONS_OK_CANCEL, |
| title) |
| dlg.set_title(title) |
| dlg.format_secondary_text(text) |
| |
| # Create an entry widget |
| entry = gtk.Entry() |
| entry.set_text(default_response) |
| entry.connect("activate", inputdialog_entry_activated) |
| dlg.vbox.pack_start(entry) |
| entry.show() |
| |
| # Run the dialog |
| response = dlg.run() |
| dlg.destroy() |
| if response == gtk.RESPONSE_OK: |
| return entry.get_text() |
| return None |
| |
| |
| def filedialog(self, title=None, default_filename=None): |
| chooser = gtk.FileChooserDialog(title=title, parent=self.window, |
| action=gtk.FILE_CHOOSER_ACTION_OPEN, |
| buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, |
| gtk.RESPONSE_OK)) |
| chooser.resize(700, 500) |
| if default_filename: |
| chooser.set_filename(os.path.abspath(default_filename)) |
| filename = None |
| response = chooser.run() |
| if response == gtk.RESPONSE_OK: |
| filename = chooser.get_filename() |
| chooser.destroy() |
| return filename |
| |
| |
| def redirect_event_box_input(self, press=None, release=None, scroll=None, |
| motion=None, expose=None): |
| if self.handler_event_box_press != None: \ |
| self.event_box.disconnect(self.handler_event_box_press) |
| if self.handler_event_box_release != None: \ |
| self.event_box.disconnect(self.handler_event_box_release) |
| if self.handler_event_box_scroll != None: \ |
| self.event_box.disconnect(self.handler_event_box_scroll) |
| if self.handler_event_box_motion != None: \ |
| self.event_box.disconnect(self.handler_event_box_motion) |
| if self.handler_event_box_expose != None: \ |
| self.event_box.disconnect(self.handler_event_box_expose) |
| self.handler_event_box_press = None |
| self.handler_event_box_release = None |
| self.handler_event_box_scroll = None |
| self.handler_event_box_motion = None |
| self.handler_event_box_expose = None |
| if press != None: self.handler_event_box_press = \ |
| self.event_box.connect("button-press-event", press) |
| if release != None: self.handler_event_box_release = \ |
| self.event_box.connect("button-release-event", release) |
| if scroll != None: self.handler_event_box_scroll = \ |
| self.event_box.connect("scroll-event", scroll) |
| if motion != None: self.handler_event_box_motion = \ |
| self.event_box.connect("motion-notify-event", motion) |
| if expose != None: self.handler_event_box_expose = \ |
| self.event_box.connect_after("expose-event", expose) |
| |
| |
| def get_keys(self): |
| return self.text_buffer.get_text( |
| self.text_buffer.get_start_iter(), |
| self.text_buffer.get_end_iter()) |
| |
| |
| def add_key(self, key): |
| text = self.get_keys() |
| if len(text) > 0 and text[-1] != ' ': |
| text += " " |
| text += key |
| self.text_buffer.set_text(text) |
| |
| |
| def clear_keys(self): |
| self.text_buffer.set_text("") |
| |
| |
| def update_barrier_info(self): |
| if self.barrier_selected: |
| self.label_barrier_region.set_text("Selected region: Corner: " + \ |
| str(tuple(self.barrier_corner)) + \ |
| " Size: " + \ |
| str(tuple(self.barrier_size))) |
| else: |
| self.label_barrier_region.set_text("No region selected.") |
| self.label_barrier_md5sum.set_text("MD5: " + self.barrier_md5sum) |
| |
| |
| def update_mouse_click_info(self): |
| if self.mouse_click_captured: |
| self.check_mousemove.set_label("Move: " + \ |
| str(tuple(self.mouse_click_coords))) |
| self.check_mouseclick.set_label("Click: button %d" % |
| self.mouse_click_button) |
| else: |
| self.check_mousemove.set_label("Move: ...") |
| self.check_mouseclick.set_label("Click: ...") |
| |
| |
| def clear_state(self, clear_screendump=True): |
| # Recording time |
| self.entry_time.set_text("unknown") |
| if clear_screendump: |
| # Screendump |
| self.clear_image() |
| # Screendump ID |
| self.entry_screendump.set_text("") |
| # Comment |
| self.entry_comment.set_text("") |
| # Sleep |
| self.check_sleep.set_active(True) |
| self.check_sleep.set_active(False) |
| self.spin_sleep.set_value(10) |
| # Barrier |
| self.clear_barrier_state() |
| # Keystrokes |
| self.check_manual.set_active(False) |
| self.clear_keys() |
| # Mouse actions |
| self.check_mousemove.set_sensitive(False) |
| self.check_mouseclick.set_sensitive(False) |
| self.check_mousemove.set_active(False) |
| self.check_mouseclick.set_active(False) |
| self.mouse_click_captured = False |
| self.mouse_click_coords = [0, 0] |
| self.mouse_click_button = 0 |
| self.update_mouse_click_info() |
| |
| |
| def clear_barrier_state(self): |
| self.check_barrier.set_active(True) |
| self.check_barrier.set_active(False) |
| self.check_barrier_optional.set_active(False) |
| self.spin_barrier_timeout.set_value(10) |
| self.barrier_selection_started = False |
| self.barrier_selected = False |
| self.barrier_corner0 = [0, 0] |
| self.barrier_corner1 = [0, 0] |
| self.barrier_corner = [0, 0] |
| self.barrier_size = [0, 0] |
| self.barrier_md5sum = "" |
| self.update_barrier_info() |
| |
| |
| def set_image(self, w, h, data): |
| (self.image_width, self.image_height, self.image_data) = (w, h, data) |
| self.image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_data( |
| data, gtk.gdk.COLORSPACE_RGB, False, 8, |
| w, h, w*3)) |
| hscrollbar = self.scrolledwindow.get_hscrollbar() |
| hscrollbar.set_range(0, w) |
| vscrollbar = self.scrolledwindow.get_vscrollbar() |
| vscrollbar.set_range(0, h) |
| |
| |
| def set_image_from_file(self, filename): |
| if not ppm_utils.image_verify_ppm_file(filename): |
| logging.warning("set_image_from_file: Warning: received invalid" |
| "screendump file") |
| return self.clear_image() |
| (w, h, data) = ppm_utils.image_read_from_ppm_file(filename) |
| self.set_image(w, h, data) |
| |
| |
| def clear_image(self): |
| self.image.clear() |
| self.image_width = 0 |
| self.image_height = 0 |
| self.image_data = "" |
| |
| |
| def update_screendump_id(self, data_dir): |
| if not self.image_data: |
| return |
| # Find a proper ID for the screendump |
| scrdump_md5sum = ppm_utils.image_md5sum(self.image_width, |
| self.image_height, |
| self.image_data) |
| scrdump_id = ppm_utils.find_id_for_screendump(scrdump_md5sum, data_dir) |
| if not scrdump_id: |
| # Not found; generate one |
| scrdump_id = ppm_utils.generate_id_for_screendump(scrdump_md5sum, |
| data_dir) |
| self.entry_screendump.set_text(scrdump_id) |
| |
| |
| def get_step_lines(self, data_dir=None): |
| if self.check_barrier.get_active() and not self.barrier_selected: |
| self.message("No barrier region selected.", "Error") |
| return |
| |
| str = "step" |
| |
| # Add step recording time |
| if self.entry_time.get_text(): |
| str += " " + self.entry_time.get_text() |
| |
| str += "\n" |
| |
| # Add screendump line |
| if self.image_data: |
| str += "screendump %s\n" % self.entry_screendump.get_text() |
| |
| # Add comment |
| if self.entry_comment.get_text(): |
| str += "# %s\n" % self.entry_comment.get_text() |
| |
| # Add sleep line |
| if self.check_sleep.get_active(): |
| str += "sleep %d\n" % self.spin_sleep.get_value() |
| |
| # Add barrier_2 line |
| if self.check_barrier.get_active(): |
| str += "barrier_2 %d %d %d %d %s %d" % ( |
| self.barrier_size[0], self.barrier_size[1], |
| self.barrier_corner[0], self.barrier_corner[1], |
| self.barrier_md5sum, self.spin_barrier_timeout.get_value()) |
| if self.check_barrier_optional.get_active(): |
| str += " optional" |
| str += "\n" |
| |
| # Add "Sending keys" comment |
| keys_to_send = self.get_keys().split() |
| if keys_to_send: |
| str += "# Sending keys: %s\n" % self.get_keys() |
| |
| # Add key and var lines |
| for key in keys_to_send: |
| if key.startswith("$"): |
| varname = key[1:] |
| str += "var %s\n" % varname |
| else: |
| str += "key %s\n" % key |
| |
| # Add mousemove line |
| if self.check_mousemove.get_active(): |
| str += "mousemove %d %d\n" % (self.mouse_click_coords[0], |
| self.mouse_click_coords[1]) |
| |
| # Add mouseclick line |
| if self.check_mouseclick.get_active(): |
| dict = { 1 : 1, |
| 2 : 2, |
| 3 : 4 } |
| str += "mouseclick %d\n" % dict[self.mouse_click_button] |
| |
| # Write screendump and cropped screendump image files |
| if data_dir and self.image_data: |
| # Create the data dir if it doesn't exist |
| if not os.path.exists(data_dir): |
| os.makedirs(data_dir) |
| # Get the full screendump filename |
| scrdump_filename = os.path.join(data_dir, |
| self.entry_screendump.get_text()) |
| # Write screendump file if it doesn't exist |
| if not os.path.exists(scrdump_filename): |
| try: |
| ppm_utils.image_write_to_ppm_file(scrdump_filename, |
| self.image_width, |
| self.image_height, |
| self.image_data) |
| except IOError: |
| self.message("Could not write screendump file.", "Error") |
| |
| #if self.check_barrier.get_active(): |
| # # Crop image to get the cropped screendump |
| # (cw, ch, cdata) = ppm_utils.image_crop( |
| # self.image_width, self.image_height, self.image_data, |
| # self.barrier_corner[0], self.barrier_corner[1], |
| # self.barrier_size[0], self.barrier_size[1]) |
| # cropped_scrdump_md5sum = ppm_utils.image_md5sum(cw, ch, cdata) |
| # cropped_scrdump_filename = \ |
| # ppm_utils.get_cropped_screendump_filename(scrdump_filename, |
| # cropped_scrdump_md5sum) |
| # # Write cropped screendump file |
| # try: |
| # ppm_utils.image_write_to_ppm_file(cropped_scrdump_filename, |
| # cw, ch, cdata) |
| # except IOError: |
| # self.message("Could not write cropped screendump file.", |
| # "Error") |
| |
| return str |
| |
| def set_state_from_step_lines(self, str, data_dir, warn=True): |
| self.clear_state() |
| |
| for line in str.splitlines(): |
| words = line.split() |
| if not words: |
| continue |
| |
| if line.startswith("#") \ |
| and not self.entry_comment.get_text() \ |
| and not line.startswith("# Sending keys:") \ |
| and not line.startswith("# ----"): |
| self.entry_comment.set_text(line.strip("#").strip()) |
| |
| elif words[0] == "step": |
| if len(words) >= 2: |
| self.entry_time.set_text(words[1]) |
| |
| elif words[0] == "screendump": |
| self.entry_screendump.set_text(words[1]) |
| self.set_image_from_file(os.path.join(data_dir, words[1])) |
| |
| elif words[0] == "sleep": |
| self.spin_sleep.set_value(int(words[1])) |
| self.check_sleep.set_active(True) |
| |
| elif words[0] == "key": |
| self.add_key(words[1]) |
| |
| elif words[0] == "var": |
| self.add_key("$%s" % words[1]) |
| |
| elif words[0] == "mousemove": |
| self.mouse_click_captured = True |
| self.mouse_click_coords = [int(words[1]), int(words[2])] |
| self.update_mouse_click_info() |
| |
| elif words[0] == "mouseclick": |
| self.mouse_click_captured = True |
| self.mouse_click_button = int(words[1]) |
| self.update_mouse_click_info() |
| |
| elif words[0] == "barrier_2": |
| # Get region corner and size from step lines |
| self.barrier_corner = [int(words[3]), int(words[4])] |
| self.barrier_size = [int(words[1]), int(words[2])] |
| # Get corner0 and corner1 from step lines |
| self.barrier_corner0 = self.barrier_corner |
| self.barrier_corner1 = [self.barrier_corner[0] + |
| self.barrier_size[0] - 1, |
| self.barrier_corner[1] + |
| self.barrier_size[1] - 1] |
| # Get the md5sum |
| self.barrier_md5sum = words[5] |
| # Pretend the user selected the region with the mouse |
| self.barrier_selection_started = True |
| self.barrier_selected = True |
| # Update label widgets according to region information |
| self.update_barrier_info() |
| # Check the barrier checkbutton |
| self.check_barrier.set_active(True) |
| # Set timeout value |
| self.spin_barrier_timeout.set_value(int(words[6])) |
| # Set 'optional' checkbutton state |
| self.check_barrier_optional.set_active(words[-1] == "optional") |
| # Update the image widget |
| self.event_box.queue_draw() |
| |
| if warn: |
| # See if the computed md5sum matches the one recorded in |
| # the file |
| computed_md5sum = ppm_utils.get_region_md5sum( |
| self.image_width, self.image_height, |
| self.image_data, self.barrier_corner[0], |
| self.barrier_corner[1], self.barrier_size[0], |
| self.barrier_size[1]) |
| if computed_md5sum != self.barrier_md5sum: |
| self.message("Computed MD5 sum (%s) differs from MD5" |
| " sum recorded in steps file (%s)" % |
| (computed_md5sum, self.barrier_md5sum), |
| "Warning") |
| |
| # Events |
| |
| def delete_event(self, widget, event): |
| pass |
| |
| def destroy(self, widget): |
| gtk.main_quit() |
| |
| def event_check_barrier_toggled(self, widget): |
| if self.check_barrier.get_active(): |
| self.redirect_event_box_input( |
| self.event_button_press, |
| self.event_button_release, |
| None, |
| None, |
| self.event_expose) |
| self.event_box.queue_draw() |
| self.event_box.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.CROSSHAIR)) |
| self.label_barrier_region.set_sensitive(True) |
| self.label_barrier_md5sum.set_sensitive(True) |
| self.label_barrier_timeout.set_sensitive(True) |
| self.spin_barrier_timeout.set_sensitive(True) |
| self.check_barrier_optional.set_sensitive(True) |
| else: |
| self.redirect_event_box_input() |
| self.event_box.queue_draw() |
| self.event_box.window.set_cursor(None) |
| self.label_barrier_region.set_sensitive(False) |
| self.label_barrier_md5sum.set_sensitive(False) |
| self.label_barrier_timeout.set_sensitive(False) |
| self.spin_barrier_timeout.set_sensitive(False) |
| self.check_barrier_optional.set_sensitive(False) |
| |
| def event_check_sleep_toggled(self, widget): |
| if self.check_sleep.get_active(): |
| self.spin_sleep.set_sensitive(True) |
| else: |
| self.spin_sleep.set_sensitive(False) |
| |
| def event_manual_toggled(self, widget): |
| self.entry_keys.grab_focus() |
| |
| def event_clear_clicked(self, widget): |
| self.clear_keys() |
| self.entry_keys.grab_focus() |
| |
| def event_expose(self, widget, event): |
| if not self.barrier_selection_started: |
| return |
| (corner, size) = corner_and_size_clipped(self.barrier_corner0, |
| self.barrier_corner1, |
| self.event_box.size_request()) |
| gc = self.event_box.window.new_gc(line_style=gtk.gdk.LINE_DOUBLE_DASH, |
| line_width=1) |
| gc.set_foreground(gc.get_colormap().alloc_color("red")) |
| gc.set_background(gc.get_colormap().alloc_color("dark red")) |
| gc.set_dashes(0, (4, 4)) |
| self.event_box.window.draw_rectangle( |
| gc, False, |
| corner[0], corner[1], |
| size[0]-1, size[1]-1) |
| |
| def event_drag_motion(self, widget, event): |
| old_corner1 = self.barrier_corner1 |
| self.barrier_corner1 = [int(event.x), int(event.y)] |
| (corner, size) = corner_and_size_clipped(self.barrier_corner0, |
| self.barrier_corner1, |
| self.event_box.size_request()) |
| (old_corner, old_size) = corner_and_size_clipped(self.barrier_corner0, |
| old_corner1, |
| self.event_box.size_request()) |
| corner0 = [min(corner[0], old_corner[0]), min(corner[1], old_corner[1])] |
| corner1 = [max(corner[0] + size[0], old_corner[0] + old_size[0]), |
| max(corner[1] + size[1], old_corner[1] + old_size[1])] |
| size = [corner1[0] - corner0[0] + 1, |
| corner1[1] - corner0[1] + 1] |
| self.event_box.queue_draw_area(corner0[0], corner0[1], size[0], size[1]) |
| |
| def event_button_press(self, widget, event): |
| (corner, size) = corner_and_size_clipped(self.barrier_corner0, |
| self.barrier_corner1, |
| self.event_box.size_request()) |
| self.event_box.queue_draw_area(corner[0], corner[1], size[0], size[1]) |
| self.barrier_corner0 = [int(event.x), int(event.y)] |
| self.barrier_corner1 = [int(event.x), int(event.y)] |
| self.redirect_event_box_input( |
| self.event_button_press, |
| self.event_button_release, |
| None, |
| self.event_drag_motion, |
| self.event_expose) |
| self.barrier_selection_started = True |
| |
| def event_button_release(self, widget, event): |
| self.redirect_event_box_input( |
| self.event_button_press, |
| self.event_button_release, |
| None, |
| None, |
| self.event_expose) |
| (self.barrier_corner, self.barrier_size) = \ |
| corner_and_size_clipped(self.barrier_corner0, self.barrier_corner1, |
| self.event_box.size_request()) |
| self.barrier_md5sum = ppm_utils.get_region_md5sum( |
| self.image_width, self.image_height, self.image_data, |
| self.barrier_corner[0], self.barrier_corner[1], |
| self.barrier_size[0], self.barrier_size[1]) |
| self.barrier_selected = True |
| self.update_barrier_info() |
| |
| def event_key_press(self, widget, event): |
| if self.check_manual.get_active(): |
| return False |
| str = key_event_to_qemu_string(event) |
| self.add_key(str) |
| return True |
| |
| |
| class StepEditor(StepMakerWindow): |
| ui = '''<ui> |
| <menubar name="MenuBar"> |
| <menu action="File"> |
| <menuitem action="Open"/> |
| <separator/> |
| <menuitem action="Quit"/> |
| </menu> |
| <menu action="Edit"> |
| <menuitem action="CopyStep"/> |
| <menuitem action="DeleteStep"/> |
| </menu> |
| <menu action="Insert"> |
| <menuitem action="InsertNewBefore"/> |
| <menuitem action="InsertNewAfter"/> |
| <separator/> |
| <menuitem action="InsertStepsBefore"/> |
| <menuitem action="InsertStepsAfter"/> |
| </menu> |
| <menu action="Tools"> |
| <menuitem action="CleanUp"/> |
| </menu> |
| </menubar> |
| </ui>''' |
| |
| # Constructor |
| |
| def __init__(self, filename=None): |
| StepMakerWindow.__init__(self) |
| |
| self.steps_filename = None |
| self.steps = [] |
| |
| # Create a UIManager instance |
| uimanager = gtk.UIManager() |
| |
| # Add the accelerator group to the toplevel window |
| accelgroup = uimanager.get_accel_group() |
| self.window.add_accel_group(accelgroup) |
| |
| # Create an ActionGroup |
| actiongroup = gtk.ActionGroup('StepEditor') |
| |
| # Create actions |
| actiongroup.add_actions([ |
| ('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit the Program', |
| self.quit), |
| ('Open', gtk.STOCK_OPEN, '_Open', None, 'Open steps file', |
| self.open_steps_file), |
| ('CopyStep', gtk.STOCK_COPY, '_Copy current step...', "", |
| 'Copy current step to user specified position', self.copy_step), |
| ('DeleteStep', gtk.STOCK_DELETE, '_Delete current step', "", |
| 'Delete current step', self.event_remove_clicked), |
| ('InsertNewBefore', gtk.STOCK_ADD, '_New step before current', "", |
| 'Insert new step before current step', self.insert_before), |
| ('InsertNewAfter', gtk.STOCK_ADD, 'N_ew step after current', "", |
| 'Insert new step after current step', self.insert_after), |
| ('InsertStepsBefore', gtk.STOCK_ADD, '_Steps before current...', |
| "", 'Insert steps (from file) before current step', |
| self.insert_steps_before), |
| ('InsertStepsAfter', gtk.STOCK_ADD, 'Steps _after current...', "", |
| 'Insert steps (from file) after current step', |
| self.insert_steps_after), |
| ('CleanUp', gtk.STOCK_DELETE, '_Clean up data directory', "", |
| 'Move unused PPM files to a backup directory', self.cleanup), |
| ('File', None, '_File'), |
| ('Edit', None, '_Edit'), |
| ('Insert', None, '_Insert'), |
| ('Tools', None, '_Tools') |
| ]) |
| |
| def create_shortcut(name, callback, keyname): |
| # Create an action |
| action = gtk.Action(name, None, None, None) |
| # Connect a callback to the action |
| action.connect("activate", callback) |
| actiongroup.add_action_with_accel(action, keyname) |
| # Have the action use accelgroup |
| action.set_accel_group(accelgroup) |
| # Connect the accelerator to the action |
| action.connect_accelerator() |
| |
| create_shortcut("Next", self.event_next_clicked, "Page_Down") |
| create_shortcut("Previous", self.event_prev_clicked, "Page_Up") |
| |
| # Add the actiongroup to the uimanager |
| uimanager.insert_action_group(actiongroup, 0) |
| |
| # Add a UI description |
| uimanager.add_ui_from_string(self.ui) |
| |
| # Create a MenuBar |
| menubar = uimanager.get_widget('/MenuBar') |
| self.menu_vbox.pack_start(menubar, False) |
| |
| # Remember the Edit menu bar for future reference |
| self.menu_edit = uimanager.get_widget('/MenuBar/Edit') |
| self.menu_edit.set_sensitive(False) |
| |
| # Remember the Insert menu bar for future reference |
| self.menu_insert = uimanager.get_widget('/MenuBar/Insert') |
| self.menu_insert.set_sensitive(False) |
| |
| # Remember the Tools menu bar for future reference |
| self.menu_tools = uimanager.get_widget('/MenuBar/Tools') |
| self.menu_tools.set_sensitive(False) |
| |
| # Next/Previous HBox |
| hbox = gtk.HBox(spacing=10) |
| self.user_vbox.pack_start(hbox) |
| hbox.show() |
| |
| self.button_first = gtk.Button(stock=gtk.STOCK_GOTO_FIRST) |
| self.button_first.connect("clicked", self.event_first_clicked) |
| hbox.pack_start(self.button_first) |
| self.button_first.show() |
| |
| #self.button_prev = gtk.Button("<< Previous") |
| self.button_prev = gtk.Button(stock=gtk.STOCK_GO_BACK) |
| self.button_prev.connect("clicked", self.event_prev_clicked) |
| hbox.pack_start(self.button_prev) |
| self.button_prev.show() |
| |
| self.label_step = gtk.Label("Step:") |
| hbox.pack_start(self.label_step, False) |
| self.label_step.show() |
| |
| self.entry_step_num = gtk.Entry() |
| self.entry_step_num.connect("activate", self.event_entry_step_activated) |
| self.entry_step_num.set_width_chars(3) |
| hbox.pack_start(self.entry_step_num, False) |
| self.entry_step_num.show() |
| |
| #self.button_next = gtk.Button("Next >>") |
| self.button_next = gtk.Button(stock=gtk.STOCK_GO_FORWARD) |
| self.button_next.connect("clicked", self.event_next_clicked) |
| hbox.pack_start(self.button_next) |
| self.button_next.show() |
| |
| self.button_last = gtk.Button(stock=gtk.STOCK_GOTO_LAST) |
| self.button_last.connect("clicked", self.event_last_clicked) |
| hbox.pack_start(self.button_last) |
| self.button_last.show() |
| |
| # Save HBox |
| hbox = gtk.HBox(spacing=10) |
| self.user_vbox.pack_start(hbox) |
| hbox.show() |
| |
| self.button_save = gtk.Button("_Save current step") |
| self.button_save.connect("clicked", self.event_save_clicked) |
| hbox.pack_start(self.button_save) |
| self.button_save.show() |
| |
| self.button_remove = gtk.Button("_Delete current step") |
| self.button_remove.connect("clicked", self.event_remove_clicked) |
| hbox.pack_start(self.button_remove) |
| self.button_remove.show() |
| |
| self.button_replace = gtk.Button("_Replace screendump") |
| self.button_replace.connect("clicked", self.event_replace_clicked) |
| hbox.pack_start(self.button_replace) |
| self.button_replace.show() |
| |
| # Disable unused widgets |
| self.button_capture.set_sensitive(False) |
| self.spin_latency.set_sensitive(False) |
| self.spin_sensitivity.set_sensitive(False) |
| |
| # Disable main vbox because no steps file is loaded |
| self.main_vbox.set_sensitive(False) |
| |
| # Set title |
| self.window.set_title("Step Editor") |
| |
| # Events |
| |
| def delete_event(self, widget, event): |
| # Make sure the step is saved (if the user wants it to be) |
| self.verify_save() |
| |
| def event_first_clicked(self, widget): |
| if not self.steps: |
| return |
| # Make sure the step is saved (if the user wants it to be) |
| self.verify_save() |
| # Go to first step |
| self.set_step(0) |
| |
| def event_last_clicked(self, widget): |
| if not self.steps: |
| return |
| # Make sure the step is saved (if the user wants it to be) |
| self.verify_save() |
| # Go to last step |
| self.set_step(len(self.steps) - 1) |
| |
| def event_prev_clicked(self, widget): |
| if not self.steps: |
| return |
| # Make sure the step is saved (if the user wants it to be) |
| self.verify_save() |
| # Go to previous step |
| index = self.current_step_index - 1 |
| if self.steps: |
| index = index % len(self.steps) |
| self.set_step(index) |
| |
| def event_next_clicked(self, widget): |
| if not self.steps: |
| return |
| # Make sure the step is saved (if the user wants it to be) |
| self.verify_save() |
| # Go to next step |
| index = self.current_step_index + 1 |
| if self.steps: |
| index = index % len(self.steps) |
| self.set_step(index) |
| |
| def event_entry_step_activated(self, widget): |
| if not self.steps: |
| return |
| step_index = self.entry_step_num.get_text() |
| if not step_index.isdigit(): |
| return |
| step_index = int(step_index) - 1 |
| if step_index == self.current_step_index: |
| return |
| self.verify_save() |
| self.set_step(step_index) |
| |
| def event_save_clicked(self, widget): |
| if not self.steps: |
| return |
| self.save_step() |
| |
| def event_remove_clicked(self, widget): |
| if not self.steps: |
| return |
| if not self.question_yes_no("This will modify the steps file." |
| " Are you sure?", "Remove step?"): |
| return |
| # Remove step |
| del self.steps[self.current_step_index] |
| # Write changes to file |
| self.write_steps_file(self.steps_filename) |
| # Move to previous step |
| self.set_step(self.current_step_index) |
| |
| def event_replace_clicked(self, widget): |
| if not self.steps: |
| return |
| # Let the user choose a screendump file |
| current_filename = os.path.join(self.steps_data_dir, |
| self.entry_screendump.get_text()) |
| filename = self.filedialog("Choose PPM image file", |
| default_filename=current_filename) |
| if not filename: |
| return |
| if not ppm_utils.image_verify_ppm_file(filename): |
| self.message("Not a valid PPM image file.", "Error") |
| return |
| self.clear_image() |
| self.clear_barrier_state() |
| self.set_image_from_file(filename) |
| self.update_screendump_id(self.steps_data_dir) |
| |
| # Menu actions |
| |
| def open_steps_file(self, action): |
| # Make sure the step is saved (if the user wants it to be) |
| self.verify_save() |
| # Let the user choose a steps file |
| current_filename = self.steps_filename |
| filename = self.filedialog("Open steps file", |
| default_filename=current_filename) |
| if not filename: |
| return |
| self.set_steps_file(filename) |
| |
| def quit(self, action): |
| # Make sure the step is saved (if the user wants it to be) |
| self.verify_save() |
| # Quit |
| gtk.main_quit() |
| |
| def copy_step(self, action): |
| if not self.steps: |
| return |
| self.verify_save() |
| self.set_step(self.current_step_index) |
| # Get the desired position |
| step_index = self.inputdialog("Copy step to position:", |
| "Copy step", |
| str(self.current_step_index + 2)) |
| if not step_index: |
| return |
| step_index = int(step_index) - 1 |
| # Get the lines of the current step |
| step = self.steps[self.current_step_index] |
| # Insert new step at position step_index |
| self.steps.insert(step_index, step) |
| # Go to new step |
| self.set_step(step_index) |
| # Write changes to disk |
| self.write_steps_file(self.steps_filename) |
| |
| def insert_before(self, action): |
| if not self.steps_filename: |
| return |
| if not self.question_yes_no("This will modify the steps file." |
| " Are you sure?", "Insert new step?"): |
| return |
| self.verify_save() |
| step_index = self.current_step_index |
| # Get the lines of a blank step |
| self.clear_state() |
| step = self.get_step_lines() |
| # Insert new step at position step_index |
| self.steps.insert(step_index, step) |
| # Go to new step |
| self.set_step(step_index) |
| # Write changes to disk |
| self.write_steps_file(self.steps_filename) |
| |
| def insert_after(self, action): |
| if not self.steps_filename: |
| return |
| if not self.question_yes_no("This will modify the steps file." |
| " Are you sure?", "Insert new step?"): |
| return |
| self.verify_save() |
| step_index = self.current_step_index + 1 |
| # Get the lines of a blank step |
| self.clear_state() |
| step = self.get_step_lines() |
| # Insert new step at position step_index |
| self.steps.insert(step_index, step) |
| # Go to new step |
| self.set_step(step_index) |
| # Write changes to disk |
| self.write_steps_file(self.steps_filename) |
| |
| def insert_steps(self, filename, index): |
| # Read the steps file |
| (steps, header) = self.read_steps_file(filename) |
| |
| data_dir = ppm_utils.get_data_dir(filename) |
| for step in steps: |
| self.set_state_from_step_lines(step, data_dir, warn=False) |
| step = self.get_step_lines(self.steps_data_dir) |
| |
| # Insert steps into self.steps |
| self.steps[index:index] = steps |
| # Write changes to disk |
| self.write_steps_file(self.steps_filename) |
| |
| def insert_steps_before(self, action): |
| if not self.steps_filename: |
| return |
| # Let the user choose a steps file |
| current_filename = self.steps_filename |
| filename = self.filedialog("Choose steps file", |
| default_filename=current_filename) |
| if not filename: |
| return |
| self.verify_save() |
| |
| step_index = self.current_step_index |
| # Insert steps at position step_index |
| self.insert_steps(filename, step_index) |
| # Go to new steps |
| self.set_step(step_index) |
| |
| def insert_steps_after(self, action): |
| if not self.steps_filename: |
| return |
| # Let the user choose a steps file |
| current_filename = self.steps_filename |
| filename = self.filedialog("Choose steps file", |
| default_filename=current_filename) |
| if not filename: |
| return |
| self.verify_save() |
| |
| step_index = self.current_step_index + 1 |
| # Insert new steps at position step_index |
| self.insert_steps(filename, step_index) |
| # Go to new steps |
| self.set_step(step_index) |
| |
| def cleanup(self, action): |
| if not self.steps_filename: |
| return |
| if not self.question_yes_no("All unused PPM files will be moved to a" |
| " backup directory. Are you sure?", |
| "Clean up data directory?"): |
| return |
| # Remember the current step index |
| current_step_index = self.current_step_index |
| # Get the backup dir |
| backup_dir = os.path.join(self.steps_data_dir, "backup") |
| # Create it if it doesn't exist |
| if not os.path.exists(backup_dir): |
| os.makedirs(backup_dir) |
| # Move all files to the backup dir |
| for filename in glob.glob(os.path.join(self.steps_data_dir, |
| "*.[Pp][Pp][Mm]")): |
| shutil.move(filename, backup_dir) |
| # Get the used files back |
| for step in self.steps: |
| self.set_state_from_step_lines(step, backup_dir, warn=False) |
| self.get_step_lines(self.steps_data_dir) |
| # Remove the used files from the backup dir |
| used_files = os.listdir(self.steps_data_dir) |
| for filename in os.listdir(backup_dir): |
| if filename in used_files: |
| os.unlink(os.path.join(backup_dir, filename)) |
| # Restore step index |
| self.set_step(current_step_index) |
| # Inform the user |
| self.message("All unused PPM files may be found at %s." % |
| os.path.abspath(backup_dir), |
| "Clean up data directory") |
| |
| # Methods |
| |
| def read_steps_file(self, filename): |
| steps = [] |
| header = "" |
| |
| file = open(filename, "r") |
| for line in file.readlines(): |
| words = line.split() |
| if not words: |
| continue |
| if line.startswith("# ----"): |
| continue |
| if words[0] == "step": |
| steps.append("") |
| if steps: |
| steps[-1] += line |
| else: |
| header += line |
| file.close() |
| |
| return (steps, header) |
| |
| def set_steps_file(self, filename): |
| try: |
| (self.steps, self.header) = self.read_steps_file(filename) |
| except (TypeError, IOError): |
| self.message("Cannot read file %s." % filename, "Error") |
| return |
| |
| self.steps_filename = filename |
| self.steps_data_dir = ppm_utils.get_data_dir(filename) |
| # Go to step 0 |
| self.set_step(0) |
| |
| def set_step(self, index): |
| # Limit index to legal boundaries |
| if index < 0: |
| index = 0 |
| if index > len(self.steps) - 1: |
| index = len(self.steps) - 1 |
| |
| # Enable the menus |
| self.menu_edit.set_sensitive(True) |
| self.menu_insert.set_sensitive(True) |
| self.menu_tools.set_sensitive(True) |
| |
| # If no steps exist... |
| if self.steps == []: |
| self.current_step_index = index |
| self.current_step = None |
| # Set window title |
| self.window.set_title("Step Editor -- %s" % |
| os.path.basename(self.steps_filename)) |
| # Set step entry widget text |
| self.entry_step_num.set_text("") |
| # Clear the state of all widgets |
| self.clear_state() |
| # Disable the main vbox |
| self.main_vbox.set_sensitive(False) |
| return |
| |
| self.current_step_index = index |
| self.current_step = self.steps[index] |
| # Set window title |
| self.window.set_title("Step Editor -- %s -- step %d" % |
| (os.path.basename(self.steps_filename), |
| index + 1)) |
| # Set step entry widget text |
| self.entry_step_num.set_text(str(self.current_step_index + 1)) |
| # Load the state from the step lines |
| self.set_state_from_step_lines(self.current_step, self.steps_data_dir) |
| # Enable the main vbox |
| self.main_vbox.set_sensitive(True) |
| # Make sure the step lines in self.current_step are identical to the |
| # output of self.get_step_lines |
| self.current_step = self.get_step_lines() |
| |
| def verify_save(self): |
| if not self.steps: |
| return |
| # See if the user changed anything |
| if self.get_step_lines() != self.current_step: |
| if self.question_yes_no("Step contents have been modified." |
| " Save step?", "Save changes?"): |
| self.save_step() |
| |
| def save_step(self): |
| lines = self.get_step_lines(self.steps_data_dir) |
| if lines != None: |
| self.steps[self.current_step_index] = lines |
| self.current_step = lines |
| self.write_steps_file(self.steps_filename) |
| |
| def write_steps_file(self, filename): |
| file = open(filename, "w") |
| file.write(self.header) |
| for step in self.steps: |
| file.write("# " + "-" * 32 + "\n") |
| file.write(step) |
| file.close() |
| |
| |
| if __name__ == "__main__": |
| se = StepEditor() |
| if len(sys.argv) > 1: |
| se.set_steps_file(sys.argv[1]) |
| gtk.main() |