#!/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()