Home | History | Annotate | Download | only in virt
      1 #!/usr/bin/python
      2 """
      3 Step file creator/editor.
      4 
      5 @copyright: Red Hat Inc 2009
      6 @author: mgoldish (at] redhat.com (Michael Goldish)
      7 @version: "20090401"
      8 """
      9 
     10 import pygtk, gtk, os, glob, shutil, sys, logging
     11 import common, ppm_utils
     12 pygtk.require('2.0')
     13 
     14 
     15 # General utilities
     16 
     17 def corner_and_size_clipped(startpoint, endpoint, limits):
     18     c0 = startpoint[:]
     19     c1 = endpoint[:]
     20     if c0[0] < 0:
     21         c0[0] = 0
     22     if c0[1] < 0:
     23         c0[1] = 0
     24     if c1[0] < 0:
     25         c1[0] = 0
     26     if c1[1] < 0:
     27         c1[1] = 0
     28     if c0[0] > limits[0] - 1:
     29         c0[0] = limits[0] - 1
     30     if c0[1] > limits[1] - 1:
     31         c0[1] = limits[1] - 1
     32     if c1[0] > limits[0] - 1:
     33         c1[0] = limits[0] - 1
     34     if c1[1] > limits[1] - 1:
     35         c1[1] = limits[1] - 1
     36     return ([min(c0[0], c1[0]),
     37              min(c0[1], c1[1])],
     38             [abs(c1[0] - c0[0]) + 1,
     39              abs(c1[1] - c0[1]) + 1])
     40 
     41 
     42 def key_event_to_qemu_string(event):
     43     keymap = gtk.gdk.keymap_get_default()
     44     keyvals = keymap.get_entries_for_keycode(event.hardware_keycode)
     45     keyval = keyvals[0][0]
     46     keyname = gtk.gdk.keyval_name(keyval)
     47 
     48     dict = { "Return": "ret",
     49              "Tab": "tab",
     50              "space": "spc",
     51              "Left": "left",
     52              "Right": "right",
     53              "Up": "up",
     54              "Down": "down",
     55              "F1": "f1",
     56              "F2": "f2",
     57              "F3": "f3",
     58              "F4": "f4",
     59              "F5": "f5",
     60              "F6": "f6",
     61              "F7": "f7",
     62              "F8": "f8",
     63              "F9": "f9",
     64              "F10": "f10",
     65              "F11": "f11",
     66              "F12": "f12",
     67              "Escape": "esc",
     68              "minus": "minus",
     69              "equal": "equal",
     70              "BackSpace": "backspace",
     71              "comma": "comma",
     72              "period": "dot",
     73              "slash": "slash",
     74              "Insert": "insert",
     75              "Delete": "delete",
     76              "Home": "home",
     77              "End": "end",
     78              "Page_Up": "pgup",
     79              "Page_Down": "pgdn",
     80              "Menu": "menu",
     81              "semicolon": "0x27",
     82              "backslash": "0x2b",
     83              "apostrophe": "0x28",
     84              "grave": "0x29",
     85              "less": "0x2b",
     86              "bracketleft": "0x1a",
     87              "bracketright": "0x1b",
     88              "Super_L": "0xdc",
     89              "Super_R": "0xdb",
     90              }
     91 
     92     if ord('a') <= keyval <= ord('z') or ord('0') <= keyval <= ord('9'):
     93         str = keyname
     94     elif keyname in dict.keys():
     95         str = dict[keyname]
     96     else:
     97         return ""
     98 
     99     if event.state & gtk.gdk.CONTROL_MASK:
    100         str = "ctrl-" + str
    101     if event.state & gtk.gdk.MOD1_MASK:
    102         str = "alt-" + str
    103     if event.state & gtk.gdk.SHIFT_MASK:
    104         str = "shift-" + str
    105 
    106     return str
    107 
    108 
    109 class StepMakerWindow:
    110 
    111     # Constructor
    112 
    113     def __init__(self):
    114         # Window
    115         self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    116         self.window.set_title("Step Maker Window")
    117         self.window.connect("delete-event", self.delete_event)
    118         self.window.connect("destroy", self.destroy)
    119         self.window.set_default_size(600, 800)
    120 
    121         # Main box (inside a frame which is inside a VBox)
    122         self.menu_vbox = gtk.VBox()
    123         self.window.add(self.menu_vbox)
    124         self.menu_vbox.show()
    125 
    126         frame = gtk.Frame()
    127         frame.set_border_width(10)
    128         frame.set_shadow_type(gtk.SHADOW_NONE)
    129         self.menu_vbox.pack_end(frame)
    130         frame.show()
    131 
    132         self.main_vbox = gtk.VBox(spacing=10)
    133         frame.add(self.main_vbox)
    134         self.main_vbox.show()
    135 
    136         # EventBox
    137         self.scrolledwindow = gtk.ScrolledWindow()
    138         self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC,
    139                                        gtk.POLICY_AUTOMATIC)
    140         self.scrolledwindow.set_shadow_type(gtk.SHADOW_NONE)
    141         self.main_vbox.pack_start(self.scrolledwindow)
    142         self.scrolledwindow.show()
    143 
    144         table = gtk.Table(1, 1)
    145         self.scrolledwindow.add_with_viewport(table)
    146         table.show()
    147         table.realize()
    148 
    149         self.event_box = gtk.EventBox()
    150         table.attach(self.event_box, 0, 1, 0, 1, gtk.EXPAND, gtk.EXPAND)
    151         self.event_box.show()
    152         self.event_box.realize()
    153 
    154         # Image
    155         self.image = gtk.Image()
    156         self.event_box.add(self.image)
    157         self.image.show()
    158 
    159         # Data VBox
    160         self.data_vbox = gtk.VBox(spacing=10)
    161         self.main_vbox.pack_start(self.data_vbox, expand=False)
    162         self.data_vbox.show()
    163 
    164         # User VBox
    165         self.user_vbox = gtk.VBox(spacing=10)
    166         self.main_vbox.pack_start(self.user_vbox, expand=False)
    167         self.user_vbox.show()
    168 
    169         # Screendump ID HBox
    170         box = gtk.HBox(spacing=10)
    171         self.data_vbox.pack_start(box)
    172         box.show()
    173 
    174         label = gtk.Label("Screendump ID:")
    175         box.pack_start(label, False)
    176         label.show()
    177 
    178         self.entry_screendump = gtk.Entry()
    179         self.entry_screendump.set_editable(False)
    180         box.pack_start(self.entry_screendump)
    181         self.entry_screendump.show()
    182 
    183         label = gtk.Label("Time:")
    184         box.pack_start(label, False)
    185         label.show()
    186 
    187         self.entry_time = gtk.Entry()
    188         self.entry_time.set_editable(False)
    189         self.entry_time.set_width_chars(10)
    190         box.pack_start(self.entry_time, False)
    191         self.entry_time.show()
    192 
    193         # Comment HBox
    194         box = gtk.HBox(spacing=10)
    195         self.data_vbox.pack_start(box)
    196         box.show()
    197 
    198         label = gtk.Label("Comment:")
    199         box.pack_start(label, False)
    200         label.show()
    201 
    202         self.entry_comment = gtk.Entry()
    203         box.pack_start(self.entry_comment)
    204         self.entry_comment.show()
    205 
    206         # Sleep HBox
    207         box = gtk.HBox(spacing=10)
    208         self.data_vbox.pack_start(box)
    209         box.show()
    210 
    211         self.check_sleep = gtk.CheckButton("Sleep:")
    212         self.check_sleep.connect("toggled", self.event_check_sleep_toggled)
    213         box.pack_start(self.check_sleep, False)
    214         self.check_sleep.show()
    215 
    216         self.spin_sleep = gtk.SpinButton(gtk.Adjustment(0, 0, 50000, 1, 10, 0),
    217                                          climb_rate=0.0)
    218         box.pack_start(self.spin_sleep, False)
    219         self.spin_sleep.show()
    220 
    221         # Barrier HBox
    222         box = gtk.HBox(spacing=10)
    223         self.data_vbox.pack_start(box)
    224         box.show()
    225 
    226         self.check_barrier = gtk.CheckButton("Barrier:")
    227         self.check_barrier.connect("toggled", self.event_check_barrier_toggled)
    228         box.pack_start(self.check_barrier, False)
    229         self.check_barrier.show()
    230 
    231         vbox = gtk.VBox()
    232         box.pack_start(vbox)
    233         vbox.show()
    234 
    235         self.label_barrier_region = gtk.Label("Region:")
    236         self.label_barrier_region.set_alignment(0, 0.5)
    237         vbox.pack_start(self.label_barrier_region)
    238         self.label_barrier_region.show()
    239 
    240         self.label_barrier_md5sum = gtk.Label("MD5:")
    241         self.label_barrier_md5sum.set_alignment(0, 0.5)
    242         vbox.pack_start(self.label_barrier_md5sum)
    243         self.label_barrier_md5sum.show()
    244 
    245         self.label_barrier_timeout = gtk.Label("Timeout:")
    246         box.pack_start(self.label_barrier_timeout, False)
    247         self.label_barrier_timeout.show()
    248 
    249         self.spin_barrier_timeout = gtk.SpinButton(gtk.Adjustment(0, 0, 50000,
    250                                                                   1, 10, 0),
    251                                                                  climb_rate=0.0)
    252         box.pack_start(self.spin_barrier_timeout, False)
    253         self.spin_barrier_timeout.show()
    254 
    255         self.check_barrier_optional = gtk.CheckButton("Optional")
    256         box.pack_start(self.check_barrier_optional, False)
    257         self.check_barrier_optional.show()
    258 
    259         # Keystrokes HBox
    260         box = gtk.HBox(spacing=10)
    261         self.data_vbox.pack_start(box)
    262         box.show()
    263 
    264         label = gtk.Label("Keystrokes:")
    265         box.pack_start(label, False)
    266         label.show()
    267 
    268         frame = gtk.Frame()
    269         frame.set_shadow_type(gtk.SHADOW_IN)
    270         box.pack_start(frame)
    271         frame.show()
    272 
    273         self.text_buffer = gtk.TextBuffer()
    274         self.entry_keys = gtk.TextView(self.text_buffer)
    275         self.entry_keys.set_wrap_mode(gtk.WRAP_WORD)
    276         self.entry_keys.connect("key-press-event", self.event_key_press)
    277         frame.add(self.entry_keys)
    278         self.entry_keys.show()
    279 
    280         self.check_manual = gtk.CheckButton("Manual")
    281         self.check_manual.connect("toggled", self.event_manual_toggled)
    282         box.pack_start(self.check_manual, False)
    283         self.check_manual.show()
    284 
    285         button = gtk.Button("Clear")
    286         button.connect("clicked", self.event_clear_clicked)
    287         box.pack_start(button, False)
    288         button.show()
    289 
    290         # Mouse click HBox
    291         box = gtk.HBox(spacing=10)
    292         self.data_vbox.pack_start(box)
    293         box.show()
    294 
    295         label = gtk.Label("Mouse action:")
    296         box.pack_start(label, False)
    297         label.show()
    298 
    299         self.button_capture = gtk.Button("Capture")
    300         box.pack_start(self.button_capture, False)
    301         self.button_capture.show()
    302 
    303         self.check_mousemove = gtk.CheckButton("Move: ...")
    304         box.pack_start(self.check_mousemove, False)
    305         self.check_mousemove.show()
    306 
    307         self.check_mouseclick = gtk.CheckButton("Click: ...")
    308         box.pack_start(self.check_mouseclick, False)
    309         self.check_mouseclick.show()
    310 
    311         self.spin_sensitivity = gtk.SpinButton(gtk.Adjustment(1, 1, 100, 1, 10,
    312                                                               0),
    313                                                               climb_rate=0.0)
    314         box.pack_end(self.spin_sensitivity, False)
    315         self.spin_sensitivity.show()
    316 
    317         label = gtk.Label("Sensitivity:")
    318         box.pack_end(label, False)
    319         label.show()
    320 
    321         self.spin_latency = gtk.SpinButton(gtk.Adjustment(10, 1, 500, 1, 10, 0),
    322                                            climb_rate=0.0)
    323         box.pack_end(self.spin_latency, False)
    324         self.spin_latency.show()
    325 
    326         label = gtk.Label("Latency:")
    327         box.pack_end(label, False)
    328         label.show()
    329 
    330         self.handler_event_box_press = None
    331         self.handler_event_box_release = None
    332         self.handler_event_box_scroll = None
    333         self.handler_event_box_motion = None
    334         self.handler_event_box_expose = None
    335 
    336         self.window.realize()
    337         self.window.show()
    338 
    339         self.clear_state()
    340 
    341     # Utilities
    342 
    343     def message(self, text, title):
    344         dlg = gtk.MessageDialog(self.window,
    345                 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
    346                 gtk.MESSAGE_INFO,
    347                 gtk.BUTTONS_CLOSE,
    348                 title)
    349         dlg.set_title(title)
    350         dlg.format_secondary_text(text)
    351         response = dlg.run()
    352         dlg.destroy()
    353 
    354 
    355     def question_yes_no(self, text, title):
    356         dlg = gtk.MessageDialog(self.window,
    357                 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
    358                 gtk.MESSAGE_QUESTION,
    359                 gtk.BUTTONS_YES_NO,
    360                 title)
    361         dlg.set_title(title)
    362         dlg.format_secondary_text(text)
    363         response = dlg.run()
    364         dlg.destroy()
    365         if response == gtk.RESPONSE_YES:
    366             return True
    367         return False
    368 
    369 
    370     def inputdialog(self, text, title, default_response=""):
    371         # Define a little helper function
    372         def inputdialog_entry_activated(entry):
    373             dlg.response(gtk.RESPONSE_OK)
    374 
    375         # Create the dialog
    376         dlg = gtk.MessageDialog(self.window,
    377                 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
    378                 gtk.MESSAGE_QUESTION,
    379                 gtk.BUTTONS_OK_CANCEL,
    380                 title)
    381         dlg.set_title(title)
    382         dlg.format_secondary_text(text)
    383 
    384         # Create an entry widget
    385         entry = gtk.Entry()
    386         entry.set_text(default_response)
    387         entry.connect("activate", inputdialog_entry_activated)
    388         dlg.vbox.pack_start(entry)
    389         entry.show()
    390 
    391         # Run the dialog
    392         response = dlg.run()
    393         dlg.destroy()
    394         if response == gtk.RESPONSE_OK:
    395             return entry.get_text()
    396         return None
    397 
    398 
    399     def filedialog(self, title=None, default_filename=None):
    400         chooser = gtk.FileChooserDialog(title=title, parent=self.window,
    401                                         action=gtk.FILE_CHOOSER_ACTION_OPEN,
    402                 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
    403                          gtk.RESPONSE_OK))
    404         chooser.resize(700, 500)
    405         if default_filename:
    406             chooser.set_filename(os.path.abspath(default_filename))
    407         filename = None
    408         response = chooser.run()
    409         if response == gtk.RESPONSE_OK:
    410             filename = chooser.get_filename()
    411         chooser.destroy()
    412         return filename
    413 
    414 
    415     def redirect_event_box_input(self, press=None, release=None, scroll=None,
    416                                  motion=None, expose=None):
    417         if self.handler_event_box_press != None: \
    418         self.event_box.disconnect(self.handler_event_box_press)
    419         if self.handler_event_box_release != None: \
    420         self.event_box.disconnect(self.handler_event_box_release)
    421         if self.handler_event_box_scroll != None: \
    422         self.event_box.disconnect(self.handler_event_box_scroll)
    423         if self.handler_event_box_motion != None: \
    424         self.event_box.disconnect(self.handler_event_box_motion)
    425         if self.handler_event_box_expose != None: \
    426         self.event_box.disconnect(self.handler_event_box_expose)
    427         self.handler_event_box_press = None
    428         self.handler_event_box_release = None
    429         self.handler_event_box_scroll = None
    430         self.handler_event_box_motion = None
    431         self.handler_event_box_expose = None
    432         if press != None: self.handler_event_box_press = \
    433         self.event_box.connect("button-press-event", press)
    434         if release != None: self.handler_event_box_release = \
    435         self.event_box.connect("button-release-event", release)
    436         if scroll != None: self.handler_event_box_scroll = \
    437         self.event_box.connect("scroll-event", scroll)
    438         if motion != None: self.handler_event_box_motion = \
    439         self.event_box.connect("motion-notify-event", motion)
    440         if expose != None: self.handler_event_box_expose = \
    441         self.event_box.connect_after("expose-event", expose)
    442 
    443 
    444     def get_keys(self):
    445         return self.text_buffer.get_text(
    446                 self.text_buffer.get_start_iter(),
    447                 self.text_buffer.get_end_iter())
    448 
    449 
    450     def add_key(self, key):
    451         text = self.get_keys()
    452         if len(text) > 0 and text[-1] != ' ':
    453             text += " "
    454         text += key
    455         self.text_buffer.set_text(text)
    456 
    457 
    458     def clear_keys(self):
    459         self.text_buffer.set_text("")
    460 
    461 
    462     def update_barrier_info(self):
    463         if self.barrier_selected:
    464             self.label_barrier_region.set_text("Selected region: Corner: " + \
    465                                             str(tuple(self.barrier_corner)) + \
    466                                             " Size: " + \
    467                                             str(tuple(self.barrier_size)))
    468         else:
    469             self.label_barrier_region.set_text("No region selected.")
    470         self.label_barrier_md5sum.set_text("MD5: " + self.barrier_md5sum)
    471 
    472 
    473     def update_mouse_click_info(self):
    474         if self.mouse_click_captured:
    475             self.check_mousemove.set_label("Move: " + \
    476                                            str(tuple(self.mouse_click_coords)))
    477             self.check_mouseclick.set_label("Click: button %d" %
    478                                             self.mouse_click_button)
    479         else:
    480             self.check_mousemove.set_label("Move: ...")
    481             self.check_mouseclick.set_label("Click: ...")
    482 
    483 
    484     def clear_state(self, clear_screendump=True):
    485         # Recording time
    486         self.entry_time.set_text("unknown")
    487         if clear_screendump:
    488             # Screendump
    489             self.clear_image()
    490         # Screendump ID
    491         self.entry_screendump.set_text("")
    492         # Comment
    493         self.entry_comment.set_text("")
    494         # Sleep
    495         self.check_sleep.set_active(True)
    496         self.check_sleep.set_active(False)
    497         self.spin_sleep.set_value(10)
    498         # Barrier
    499         self.clear_barrier_state()
    500         # Keystrokes
    501         self.check_manual.set_active(False)
    502         self.clear_keys()
    503         # Mouse actions
    504         self.check_mousemove.set_sensitive(False)
    505         self.check_mouseclick.set_sensitive(False)
    506         self.check_mousemove.set_active(False)
    507         self.check_mouseclick.set_active(False)
    508         self.mouse_click_captured = False
    509         self.mouse_click_coords = [0, 0]
    510         self.mouse_click_button = 0
    511         self.update_mouse_click_info()
    512 
    513 
    514     def clear_barrier_state(self):
    515         self.check_barrier.set_active(True)
    516         self.check_barrier.set_active(False)
    517         self.check_barrier_optional.set_active(False)
    518         self.spin_barrier_timeout.set_value(10)
    519         self.barrier_selection_started = False
    520         self.barrier_selected = False
    521         self.barrier_corner0 = [0, 0]
    522         self.barrier_corner1 = [0, 0]
    523         self.barrier_corner = [0, 0]
    524         self.barrier_size = [0, 0]
    525         self.barrier_md5sum = ""
    526         self.update_barrier_info()
    527 
    528 
    529     def set_image(self, w, h, data):
    530         (self.image_width, self.image_height, self.image_data) = (w, h, data)
    531         self.image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_data(
    532             data, gtk.gdk.COLORSPACE_RGB, False, 8,
    533             w, h, w*3))
    534         hscrollbar = self.scrolledwindow.get_hscrollbar()
    535         hscrollbar.set_range(0, w)
    536         vscrollbar = self.scrolledwindow.get_vscrollbar()
    537         vscrollbar.set_range(0, h)
    538 
    539 
    540     def set_image_from_file(self, filename):
    541         if not ppm_utils.image_verify_ppm_file(filename):
    542             logging.warning("set_image_from_file: Warning: received invalid"
    543                             "screendump file")
    544             return self.clear_image()
    545         (w, h, data) = ppm_utils.image_read_from_ppm_file(filename)
    546         self.set_image(w, h, data)
    547 
    548 
    549     def clear_image(self):
    550         self.image.clear()
    551         self.image_width = 0
    552         self.image_height = 0
    553         self.image_data = ""
    554 
    555 
    556     def update_screendump_id(self, data_dir):
    557         if not self.image_data:
    558             return
    559         # Find a proper ID for the screendump
    560         scrdump_md5sum = ppm_utils.image_md5sum(self.image_width,
    561                                                 self.image_height,
    562                                                 self.image_data)
    563         scrdump_id = ppm_utils.find_id_for_screendump(scrdump_md5sum, data_dir)
    564         if not scrdump_id:
    565             # Not found; generate one
    566             scrdump_id = ppm_utils.generate_id_for_screendump(scrdump_md5sum,
    567                                                               data_dir)
    568         self.entry_screendump.set_text(scrdump_id)
    569 
    570 
    571     def get_step_lines(self, data_dir=None):
    572         if self.check_barrier.get_active() and not self.barrier_selected:
    573             self.message("No barrier region selected.", "Error")
    574             return
    575 
    576         str = "step"
    577 
    578         # Add step recording time
    579         if self.entry_time.get_text():
    580             str += " " + self.entry_time.get_text()
    581 
    582         str += "\n"
    583 
    584         # Add screendump line
    585         if self.image_data:
    586             str += "screendump %s\n" % self.entry_screendump.get_text()
    587 
    588         # Add comment
    589         if self.entry_comment.get_text():
    590             str += "# %s\n" % self.entry_comment.get_text()
    591 
    592         # Add sleep line
    593         if self.check_sleep.get_active():
    594             str += "sleep %d\n" % self.spin_sleep.get_value()
    595 
    596         # Add barrier_2 line
    597         if self.check_barrier.get_active():
    598             str += "barrier_2 %d %d %d %d %s %d" % (
    599                     self.barrier_size[0], self.barrier_size[1],
    600                     self.barrier_corner[0], self.barrier_corner[1],
    601                     self.barrier_md5sum, self.spin_barrier_timeout.get_value())
    602             if self.check_barrier_optional.get_active():
    603                 str += " optional"
    604             str += "\n"
    605 
    606         # Add "Sending keys" comment
    607         keys_to_send = self.get_keys().split()
    608         if keys_to_send:
    609             str += "# Sending keys: %s\n" % self.get_keys()
    610 
    611         # Add key and var lines
    612         for key in keys_to_send:
    613             if key.startswith("$"):
    614                 varname = key[1:]
    615                 str += "var %s\n" % varname
    616             else:
    617                 str += "key %s\n" % key
    618 
    619         # Add mousemove line
    620         if self.check_mousemove.get_active():
    621             str += "mousemove %d %d\n" % (self.mouse_click_coords[0],
    622                                           self.mouse_click_coords[1])
    623 
    624         # Add mouseclick line
    625         if self.check_mouseclick.get_active():
    626             dict = { 1 : 1,
    627                      2 : 2,
    628                      3 : 4 }
    629             str += "mouseclick %d\n" % dict[self.mouse_click_button]
    630 
    631         # Write screendump and cropped screendump image files
    632         if data_dir and self.image_data:
    633             # Create the data dir if it doesn't exist
    634             if not os.path.exists(data_dir):
    635                 os.makedirs(data_dir)
    636             # Get the full screendump filename
    637             scrdump_filename = os.path.join(data_dir,
    638                                             self.entry_screendump.get_text())
    639             # Write screendump file if it doesn't exist
    640             if not os.path.exists(scrdump_filename):
    641                 try:
    642                     ppm_utils.image_write_to_ppm_file(scrdump_filename,
    643                                                       self.image_width,
    644                                                       self.image_height,
    645                                                       self.image_data)
    646                 except IOError:
    647                     self.message("Could not write screendump file.", "Error")
    648 
    649             #if self.check_barrier.get_active():
    650             #    # Crop image to get the cropped screendump
    651             #    (cw, ch, cdata) = ppm_utils.image_crop(
    652             #            self.image_width, self.image_height, self.image_data,
    653             #            self.barrier_corner[0], self.barrier_corner[1],
    654             #            self.barrier_size[0], self.barrier_size[1])
    655             #    cropped_scrdump_md5sum = ppm_utils.image_md5sum(cw, ch, cdata)
    656             #    cropped_scrdump_filename = \
    657             #    ppm_utils.get_cropped_screendump_filename(scrdump_filename,
    658             #                                            cropped_scrdump_md5sum)
    659             #    # Write cropped screendump file
    660             #    try:
    661             #        ppm_utils.image_write_to_ppm_file(cropped_scrdump_filename,
    662             #                                          cw, ch, cdata)
    663             #    except IOError:
    664             #        self.message("Could not write cropped screendump file.",
    665             #                     "Error")
    666 
    667         return str
    668 
    669     def set_state_from_step_lines(self, str, data_dir, warn=True):
    670         self.clear_state()
    671 
    672         for line in str.splitlines():
    673             words = line.split()
    674             if not words:
    675                 continue
    676 
    677             if line.startswith("#") \
    678                     and not self.entry_comment.get_text() \
    679                     and not line.startswith("# Sending keys:") \
    680                     and not line.startswith("# ----"):
    681                 self.entry_comment.set_text(line.strip("#").strip())
    682 
    683             elif words[0] == "step":
    684                 if len(words) >= 2:
    685                     self.entry_time.set_text(words[1])
    686 
    687             elif words[0] == "screendump":
    688                 self.entry_screendump.set_text(words[1])
    689                 self.set_image_from_file(os.path.join(data_dir, words[1]))
    690 
    691             elif words[0] == "sleep":
    692                 self.spin_sleep.set_value(int(words[1]))
    693                 self.check_sleep.set_active(True)
    694 
    695             elif words[0] == "key":
    696                 self.add_key(words[1])
    697 
    698             elif words[0] == "var":
    699                 self.add_key("$%s" % words[1])
    700 
    701             elif words[0] == "mousemove":
    702                 self.mouse_click_captured = True
    703                 self.mouse_click_coords = [int(words[1]), int(words[2])]
    704                 self.update_mouse_click_info()
    705 
    706             elif words[0] == "mouseclick":
    707                 self.mouse_click_captured = True
    708                 self.mouse_click_button = int(words[1])
    709                 self.update_mouse_click_info()
    710 
    711             elif words[0] == "barrier_2":
    712                 # Get region corner and size from step lines
    713                 self.barrier_corner = [int(words[3]), int(words[4])]
    714                 self.barrier_size = [int(words[1]), int(words[2])]
    715                 # Get corner0 and corner1 from step lines
    716                 self.barrier_corner0 = self.barrier_corner
    717                 self.barrier_corner1 = [self.barrier_corner[0] +
    718                                         self.barrier_size[0] - 1,
    719                                         self.barrier_corner[1] +
    720                                         self.barrier_size[1] - 1]
    721                 # Get the md5sum
    722                 self.barrier_md5sum = words[5]
    723                 # Pretend the user selected the region with the mouse
    724                 self.barrier_selection_started = True
    725                 self.barrier_selected = True
    726                 # Update label widgets according to region information
    727                 self.update_barrier_info()
    728                 # Check the barrier checkbutton
    729                 self.check_barrier.set_active(True)
    730                 # Set timeout value
    731                 self.spin_barrier_timeout.set_value(int(words[6]))
    732                 # Set 'optional' checkbutton state
    733                 self.check_barrier_optional.set_active(words[-1] == "optional")
    734                 # Update the image widget
    735                 self.event_box.queue_draw()
    736 
    737                 if warn:
    738                     # See if the computed md5sum matches the one recorded in
    739                     # the file
    740                     computed_md5sum = ppm_utils.get_region_md5sum(
    741                             self.image_width, self.image_height,
    742                             self.image_data, self.barrier_corner[0],
    743                             self.barrier_corner[1], self.barrier_size[0],
    744                             self.barrier_size[1])
    745                     if computed_md5sum != self.barrier_md5sum:
    746                         self.message("Computed MD5 sum (%s) differs from MD5"
    747                                      " sum recorded in steps file (%s)" %
    748                                      (computed_md5sum, self.barrier_md5sum),
    749                                      "Warning")
    750 
    751     # Events
    752 
    753     def delete_event(self, widget, event):
    754         pass
    755 
    756     def destroy(self, widget):
    757         gtk.main_quit()
    758 
    759     def event_check_barrier_toggled(self, widget):
    760         if self.check_barrier.get_active():
    761             self.redirect_event_box_input(
    762                     self.event_button_press,
    763                     self.event_button_release,
    764                     None,
    765                     None,
    766                     self.event_expose)
    767             self.event_box.queue_draw()
    768             self.event_box.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.CROSSHAIR))
    769             self.label_barrier_region.set_sensitive(True)
    770             self.label_barrier_md5sum.set_sensitive(True)
    771             self.label_barrier_timeout.set_sensitive(True)
    772             self.spin_barrier_timeout.set_sensitive(True)
    773             self.check_barrier_optional.set_sensitive(True)
    774         else:
    775             self.redirect_event_box_input()
    776             self.event_box.queue_draw()
    777             self.event_box.window.set_cursor(None)
    778             self.label_barrier_region.set_sensitive(False)
    779             self.label_barrier_md5sum.set_sensitive(False)
    780             self.label_barrier_timeout.set_sensitive(False)
    781             self.spin_barrier_timeout.set_sensitive(False)
    782             self.check_barrier_optional.set_sensitive(False)
    783 
    784     def event_check_sleep_toggled(self, widget):
    785         if self.check_sleep.get_active():
    786             self.spin_sleep.set_sensitive(True)
    787         else:
    788             self.spin_sleep.set_sensitive(False)
    789 
    790     def event_manual_toggled(self, widget):
    791         self.entry_keys.grab_focus()
    792 
    793     def event_clear_clicked(self, widget):
    794         self.clear_keys()
    795         self.entry_keys.grab_focus()
    796 
    797     def event_expose(self, widget, event):
    798         if not self.barrier_selection_started:
    799             return
    800         (corner, size) = corner_and_size_clipped(self.barrier_corner0,
    801                                                  self.barrier_corner1,
    802                                                  self.event_box.size_request())
    803         gc = self.event_box.window.new_gc(line_style=gtk.gdk.LINE_DOUBLE_DASH,
    804                                           line_width=1)
    805         gc.set_foreground(gc.get_colormap().alloc_color("red"))
    806         gc.set_background(gc.get_colormap().alloc_color("dark red"))
    807         gc.set_dashes(0, (4, 4))
    808         self.event_box.window.draw_rectangle(
    809                 gc, False,
    810                 corner[0], corner[1],
    811                 size[0]-1, size[1]-1)
    812 
    813     def event_drag_motion(self, widget, event):
    814         old_corner1 = self.barrier_corner1
    815         self.barrier_corner1 = [int(event.x), int(event.y)]
    816         (corner, size) = corner_and_size_clipped(self.barrier_corner0,
    817                                                  self.barrier_corner1,
    818                                                  self.event_box.size_request())
    819         (old_corner, old_size) = corner_and_size_clipped(self.barrier_corner0,
    820                                                          old_corner1,
    821                                                   self.event_box.size_request())
    822         corner0 = [min(corner[0], old_corner[0]), min(corner[1], old_corner[1])]
    823         corner1 = [max(corner[0] + size[0], old_corner[0] + old_size[0]),
    824                    max(corner[1] + size[1], old_corner[1] + old_size[1])]
    825         size = [corner1[0] - corner0[0] + 1,
    826                 corner1[1] - corner0[1] + 1]
    827         self.event_box.queue_draw_area(corner0[0], corner0[1], size[0], size[1])
    828 
    829     def event_button_press(self, widget, event):
    830         (corner, size) = corner_and_size_clipped(self.barrier_corner0,
    831                                                  self.barrier_corner1,
    832                                                  self.event_box.size_request())
    833         self.event_box.queue_draw_area(corner[0], corner[1], size[0], size[1])
    834         self.barrier_corner0 = [int(event.x), int(event.y)]
    835         self.barrier_corner1 = [int(event.x), int(event.y)]
    836         self.redirect_event_box_input(
    837                 self.event_button_press,
    838                 self.event_button_release,
    839                 None,
    840                 self.event_drag_motion,
    841                 self.event_expose)
    842         self.barrier_selection_started = True
    843 
    844     def event_button_release(self, widget, event):
    845         self.redirect_event_box_input(
    846                 self.event_button_press,
    847                 self.event_button_release,
    848                 None,
    849                 None,
    850                 self.event_expose)
    851         (self.barrier_corner, self.barrier_size) = \
    852         corner_and_size_clipped(self.barrier_corner0, self.barrier_corner1,
    853                                 self.event_box.size_request())
    854         self.barrier_md5sum = ppm_utils.get_region_md5sum(
    855                 self.image_width, self.image_height, self.image_data,
    856                 self.barrier_corner[0], self.barrier_corner[1],
    857                 self.barrier_size[0], self.barrier_size[1])
    858         self.barrier_selected = True
    859         self.update_barrier_info()
    860 
    861     def event_key_press(self, widget, event):
    862         if self.check_manual.get_active():
    863             return False
    864         str = key_event_to_qemu_string(event)
    865         self.add_key(str)
    866         return True
    867 
    868 
    869 class StepEditor(StepMakerWindow):
    870     ui = '''<ui>
    871     <menubar name="MenuBar">
    872         <menu action="File">
    873             <menuitem action="Open"/>
    874             <separator/>
    875             <menuitem action="Quit"/>
    876         </menu>
    877         <menu action="Edit">
    878             <menuitem action="CopyStep"/>
    879             <menuitem action="DeleteStep"/>
    880         </menu>
    881         <menu action="Insert">
    882             <menuitem action="InsertNewBefore"/>
    883             <menuitem action="InsertNewAfter"/>
    884             <separator/>
    885             <menuitem action="InsertStepsBefore"/>
    886             <menuitem action="InsertStepsAfter"/>
    887         </menu>
    888         <menu action="Tools">
    889             <menuitem action="CleanUp"/>
    890         </menu>
    891     </menubar>
    892 </ui>'''
    893 
    894     # Constructor
    895 
    896     def __init__(self, filename=None):
    897         StepMakerWindow.__init__(self)
    898 
    899         self.steps_filename = None
    900         self.steps = []
    901 
    902         # Create a UIManager instance
    903         uimanager = gtk.UIManager()
    904 
    905         # Add the accelerator group to the toplevel window
    906         accelgroup = uimanager.get_accel_group()
    907         self.window.add_accel_group(accelgroup)
    908 
    909         # Create an ActionGroup
    910         actiongroup = gtk.ActionGroup('StepEditor')
    911 
    912         # Create actions
    913         actiongroup.add_actions([
    914             ('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit the Program',
    915              self.quit),
    916             ('Open', gtk.STOCK_OPEN, '_Open', None, 'Open steps file',
    917              self.open_steps_file),
    918             ('CopyStep', gtk.STOCK_COPY, '_Copy current step...', "",
    919              'Copy current step to user specified position', self.copy_step),
    920             ('DeleteStep', gtk.STOCK_DELETE, '_Delete current step', "",
    921              'Delete current step', self.event_remove_clicked),
    922             ('InsertNewBefore', gtk.STOCK_ADD, '_New step before current', "",
    923              'Insert new step before current step', self.insert_before),
    924             ('InsertNewAfter', gtk.STOCK_ADD, 'N_ew step after current', "",
    925              'Insert new step after current step', self.insert_after),
    926             ('InsertStepsBefore', gtk.STOCK_ADD, '_Steps before current...',
    927              "", 'Insert steps (from file) before current step',
    928              self.insert_steps_before),
    929             ('InsertStepsAfter', gtk.STOCK_ADD, 'Steps _after current...', "",
    930              'Insert steps (from file) after current step',
    931              self.insert_steps_after),
    932             ('CleanUp', gtk.STOCK_DELETE, '_Clean up data directory', "",
    933              'Move unused PPM files to a backup directory', self.cleanup),
    934             ('File', None, '_File'),
    935             ('Edit', None, '_Edit'),
    936             ('Insert', None, '_Insert'),
    937             ('Tools', None, '_Tools')
    938             ])
    939 
    940         def create_shortcut(name, callback, keyname):
    941             # Create an action
    942             action = gtk.Action(name, None, None, None)
    943             # Connect a callback to the action
    944             action.connect("activate", callback)
    945             actiongroup.add_action_with_accel(action, keyname)
    946             # Have the action use accelgroup
    947             action.set_accel_group(accelgroup)
    948             # Connect the accelerator to the action
    949             action.connect_accelerator()
    950 
    951         create_shortcut("Next", self.event_next_clicked, "Page_Down")
    952         create_shortcut("Previous", self.event_prev_clicked, "Page_Up")
    953 
    954         # Add the actiongroup to the uimanager
    955         uimanager.insert_action_group(actiongroup, 0)
    956 
    957         # Add a UI description
    958         uimanager.add_ui_from_string(self.ui)
    959 
    960         # Create a MenuBar
    961         menubar = uimanager.get_widget('/MenuBar')
    962         self.menu_vbox.pack_start(menubar, False)
    963 
    964         # Remember the Edit menu bar for future reference
    965         self.menu_edit = uimanager.get_widget('/MenuBar/Edit')
    966         self.menu_edit.set_sensitive(False)
    967 
    968         # Remember the Insert menu bar for future reference
    969         self.menu_insert = uimanager.get_widget('/MenuBar/Insert')
    970         self.menu_insert.set_sensitive(False)
    971 
    972         # Remember the Tools menu bar for future reference
    973         self.menu_tools = uimanager.get_widget('/MenuBar/Tools')
    974         self.menu_tools.set_sensitive(False)
    975 
    976         # Next/Previous HBox
    977         hbox = gtk.HBox(spacing=10)
    978         self.user_vbox.pack_start(hbox)
    979         hbox.show()
    980 
    981         self.button_first = gtk.Button(stock=gtk.STOCK_GOTO_FIRST)
    982         self.button_first.connect("clicked", self.event_first_clicked)
    983         hbox.pack_start(self.button_first)
    984         self.button_first.show()
    985 
    986         #self.button_prev = gtk.Button("<< Previous")
    987         self.button_prev = gtk.Button(stock=gtk.STOCK_GO_BACK)
    988         self.button_prev.connect("clicked", self.event_prev_clicked)
    989         hbox.pack_start(self.button_prev)
    990         self.button_prev.show()
    991 
    992         self.label_step = gtk.Label("Step:")
    993         hbox.pack_start(self.label_step, False)
    994         self.label_step.show()
    995 
    996         self.entry_step_num = gtk.Entry()
    997         self.entry_step_num.connect("activate", self.event_entry_step_activated)
    998         self.entry_step_num.set_width_chars(3)
    999         hbox.pack_start(self.entry_step_num, False)
   1000         self.entry_step_num.show()
   1001 
   1002         #self.button_next = gtk.Button("Next >>")
   1003         self.button_next = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
   1004         self.button_next.connect("clicked", self.event_next_clicked)
   1005         hbox.pack_start(self.button_next)
   1006         self.button_next.show()
   1007 
   1008         self.button_last = gtk.Button(stock=gtk.STOCK_GOTO_LAST)
   1009         self.button_last.connect("clicked", self.event_last_clicked)
   1010         hbox.pack_start(self.button_last)
   1011         self.button_last.show()
   1012 
   1013         # Save HBox
   1014         hbox = gtk.HBox(spacing=10)
   1015         self.user_vbox.pack_start(hbox)
   1016         hbox.show()
   1017 
   1018         self.button_save = gtk.Button("_Save current step")
   1019         self.button_save.connect("clicked", self.event_save_clicked)
   1020         hbox.pack_start(self.button_save)
   1021         self.button_save.show()
   1022 
   1023         self.button_remove = gtk.Button("_Delete current step")
   1024         self.button_remove.connect("clicked", self.event_remove_clicked)
   1025         hbox.pack_start(self.button_remove)
   1026         self.button_remove.show()
   1027 
   1028         self.button_replace = gtk.Button("_Replace screendump")
   1029         self.button_replace.connect("clicked", self.event_replace_clicked)
   1030         hbox.pack_start(self.button_replace)
   1031         self.button_replace.show()
   1032 
   1033         # Disable unused widgets
   1034         self.button_capture.set_sensitive(False)
   1035         self.spin_latency.set_sensitive(False)
   1036         self.spin_sensitivity.set_sensitive(False)
   1037 
   1038         # Disable main vbox because no steps file is loaded
   1039         self.main_vbox.set_sensitive(False)
   1040 
   1041         # Set title
   1042         self.window.set_title("Step Editor")
   1043 
   1044     # Events
   1045 
   1046     def delete_event(self, widget, event):
   1047         # Make sure the step is saved (if the user wants it to be)
   1048         self.verify_save()
   1049 
   1050     def event_first_clicked(self, widget):
   1051         if not self.steps:
   1052             return
   1053         # Make sure the step is saved (if the user wants it to be)
   1054         self.verify_save()
   1055         # Go to first step
   1056         self.set_step(0)
   1057 
   1058     def event_last_clicked(self, widget):
   1059         if not self.steps:
   1060             return
   1061         # Make sure the step is saved (if the user wants it to be)
   1062         self.verify_save()
   1063         # Go to last step
   1064         self.set_step(len(self.steps) - 1)
   1065 
   1066     def event_prev_clicked(self, widget):
   1067         if not self.steps:
   1068             return
   1069         # Make sure the step is saved (if the user wants it to be)
   1070         self.verify_save()
   1071         # Go to previous step
   1072         index = self.current_step_index - 1
   1073         if self.steps:
   1074             index = index % len(self.steps)
   1075         self.set_step(index)
   1076 
   1077     def event_next_clicked(self, widget):
   1078         if not self.steps:
   1079             return
   1080         # Make sure the step is saved (if the user wants it to be)
   1081         self.verify_save()
   1082         # Go to next step
   1083         index = self.current_step_index + 1
   1084         if self.steps:
   1085             index = index % len(self.steps)
   1086         self.set_step(index)
   1087 
   1088     def event_entry_step_activated(self, widget):
   1089         if not self.steps:
   1090             return
   1091         step_index = self.entry_step_num.get_text()
   1092         if not step_index.isdigit():
   1093             return
   1094         step_index = int(step_index) - 1
   1095         if step_index == self.current_step_index:
   1096             return
   1097         self.verify_save()
   1098         self.set_step(step_index)
   1099 
   1100     def event_save_clicked(self, widget):
   1101         if not self.steps:
   1102             return
   1103         self.save_step()
   1104 
   1105     def event_remove_clicked(self, widget):
   1106         if not self.steps:
   1107             return
   1108         if not self.question_yes_no("This will modify the steps file."
   1109                                     " Are you sure?", "Remove step?"):
   1110             return
   1111         # Remove step
   1112         del self.steps[self.current_step_index]
   1113         # Write changes to file
   1114         self.write_steps_file(self.steps_filename)
   1115         # Move to previous step
   1116         self.set_step(self.current_step_index)
   1117 
   1118     def event_replace_clicked(self, widget):
   1119         if not self.steps:
   1120             return
   1121         # Let the user choose a screendump file
   1122         current_filename = os.path.join(self.steps_data_dir,
   1123                                         self.entry_screendump.get_text())
   1124         filename = self.filedialog("Choose PPM image file",
   1125                                    default_filename=current_filename)
   1126         if not filename:
   1127             return
   1128         if not ppm_utils.image_verify_ppm_file(filename):
   1129             self.message("Not a valid PPM image file.", "Error")
   1130             return
   1131         self.clear_image()
   1132         self.clear_barrier_state()
   1133         self.set_image_from_file(filename)
   1134         self.update_screendump_id(self.steps_data_dir)
   1135 
   1136     # Menu actions
   1137 
   1138     def open_steps_file(self, action):
   1139         # Make sure the step is saved (if the user wants it to be)
   1140         self.verify_save()
   1141         # Let the user choose a steps file
   1142         current_filename = self.steps_filename
   1143         filename = self.filedialog("Open steps file",
   1144                                    default_filename=current_filename)
   1145         if not filename:
   1146             return
   1147         self.set_steps_file(filename)
   1148 
   1149     def quit(self, action):
   1150         # Make sure the step is saved (if the user wants it to be)
   1151         self.verify_save()
   1152         # Quit
   1153         gtk.main_quit()
   1154 
   1155     def copy_step(self, action):
   1156         if not self.steps:
   1157             return
   1158         self.verify_save()
   1159         self.set_step(self.current_step_index)
   1160         # Get the desired position
   1161         step_index = self.inputdialog("Copy step to position:",
   1162                                       "Copy step",
   1163                                       str(self.current_step_index + 2))
   1164         if not step_index:
   1165             return
   1166         step_index = int(step_index) - 1
   1167         # Get the lines of the current step
   1168         step = self.steps[self.current_step_index]
   1169         # Insert new step at position step_index
   1170         self.steps.insert(step_index, step)
   1171         # Go to new step
   1172         self.set_step(step_index)
   1173         # Write changes to disk
   1174         self.write_steps_file(self.steps_filename)
   1175 
   1176     def insert_before(self, action):
   1177         if not self.steps_filename:
   1178             return
   1179         if not self.question_yes_no("This will modify the steps file."
   1180                                     " Are you sure?", "Insert new step?"):
   1181             return
   1182         self.verify_save()
   1183         step_index = self.current_step_index
   1184         # Get the lines of a blank step
   1185         self.clear_state()
   1186         step = self.get_step_lines()
   1187         # Insert new step at position step_index
   1188         self.steps.insert(step_index, step)
   1189         # Go to new step
   1190         self.set_step(step_index)
   1191         # Write changes to disk
   1192         self.write_steps_file(self.steps_filename)
   1193 
   1194     def insert_after(self, action):
   1195         if not self.steps_filename:
   1196             return
   1197         if not self.question_yes_no("This will modify the steps file."
   1198                                     " Are you sure?", "Insert new step?"):
   1199             return
   1200         self.verify_save()
   1201         step_index = self.current_step_index + 1
   1202         # Get the lines of a blank step
   1203         self.clear_state()
   1204         step = self.get_step_lines()
   1205         # Insert new step at position step_index
   1206         self.steps.insert(step_index, step)
   1207         # Go to new step
   1208         self.set_step(step_index)
   1209         # Write changes to disk
   1210         self.write_steps_file(self.steps_filename)
   1211 
   1212     def insert_steps(self, filename, index):
   1213         # Read the steps file
   1214         (steps, header) = self.read_steps_file(filename)
   1215 
   1216         data_dir = ppm_utils.get_data_dir(filename)
   1217         for step in steps:
   1218             self.set_state_from_step_lines(step, data_dir, warn=False)
   1219             step = self.get_step_lines(self.steps_data_dir)
   1220 
   1221         # Insert steps into self.steps
   1222         self.steps[index:index] = steps
   1223         # Write changes to disk
   1224         self.write_steps_file(self.steps_filename)
   1225 
   1226     def insert_steps_before(self, action):
   1227         if not self.steps_filename:
   1228             return
   1229         # Let the user choose a steps file
   1230         current_filename = self.steps_filename
   1231         filename = self.filedialog("Choose steps file",
   1232                                    default_filename=current_filename)
   1233         if not filename:
   1234             return
   1235         self.verify_save()
   1236 
   1237         step_index = self.current_step_index
   1238         # Insert steps at position step_index
   1239         self.insert_steps(filename, step_index)
   1240         # Go to new steps
   1241         self.set_step(step_index)
   1242 
   1243     def insert_steps_after(self, action):
   1244         if not self.steps_filename:
   1245             return
   1246         # Let the user choose a steps file
   1247         current_filename = self.steps_filename
   1248         filename = self.filedialog("Choose steps file",
   1249                                    default_filename=current_filename)
   1250         if not filename:
   1251             return
   1252         self.verify_save()
   1253 
   1254         step_index = self.current_step_index + 1
   1255         # Insert new steps at position step_index
   1256         self.insert_steps(filename, step_index)
   1257         # Go to new steps
   1258         self.set_step(step_index)
   1259 
   1260     def cleanup(self, action):
   1261         if not self.steps_filename:
   1262             return
   1263         if not self.question_yes_no("All unused PPM files will be moved to a"
   1264                                     " backup directory. Are you sure?",
   1265                                     "Clean up data directory?"):
   1266             return
   1267         # Remember the current step index
   1268         current_step_index = self.current_step_index
   1269         # Get the backup dir
   1270         backup_dir = os.path.join(self.steps_data_dir, "backup")
   1271         # Create it if it doesn't exist
   1272         if not os.path.exists(backup_dir):
   1273             os.makedirs(backup_dir)
   1274         # Move all files to the backup dir
   1275         for filename in glob.glob(os.path.join(self.steps_data_dir,
   1276                                                "*.[Pp][Pp][Mm]")):
   1277             shutil.move(filename, backup_dir)
   1278         # Get the used files back
   1279         for step in self.steps:
   1280             self.set_state_from_step_lines(step, backup_dir, warn=False)
   1281             self.get_step_lines(self.steps_data_dir)
   1282         # Remove the used files from the backup dir
   1283         used_files = os.listdir(self.steps_data_dir)
   1284         for filename in os.listdir(backup_dir):
   1285             if filename in used_files:
   1286                 os.unlink(os.path.join(backup_dir, filename))
   1287         # Restore step index
   1288         self.set_step(current_step_index)
   1289         # Inform the user
   1290         self.message("All unused PPM files may be found at %s." %
   1291                      os.path.abspath(backup_dir),
   1292                      "Clean up data directory")
   1293 
   1294     # Methods
   1295 
   1296     def read_steps_file(self, filename):
   1297         steps = []
   1298         header = ""
   1299 
   1300         file = open(filename, "r")
   1301         for line in file.readlines():
   1302             words = line.split()
   1303             if not words:
   1304                 continue
   1305             if line.startswith("# ----"):
   1306                 continue
   1307             if words[0] == "step":
   1308                 steps.append("")
   1309             if steps:
   1310                 steps[-1] += line
   1311             else:
   1312                 header += line
   1313         file.close()
   1314 
   1315         return (steps, header)
   1316 
   1317     def set_steps_file(self, filename):
   1318         try:
   1319             (self.steps, self.header) = self.read_steps_file(filename)
   1320         except (TypeError, IOError):
   1321             self.message("Cannot read file %s." % filename, "Error")
   1322             return
   1323 
   1324         self.steps_filename = filename
   1325         self.steps_data_dir = ppm_utils.get_data_dir(filename)
   1326         # Go to step 0
   1327         self.set_step(0)
   1328 
   1329     def set_step(self, index):
   1330         # Limit index to legal boundaries
   1331         if index < 0:
   1332             index = 0
   1333         if index > len(self.steps) - 1:
   1334             index = len(self.steps) - 1
   1335 
   1336         # Enable the menus
   1337         self.menu_edit.set_sensitive(True)
   1338         self.menu_insert.set_sensitive(True)
   1339         self.menu_tools.set_sensitive(True)
   1340 
   1341         # If no steps exist...
   1342         if self.steps == []:
   1343             self.current_step_index = index
   1344             self.current_step = None
   1345             # Set window title
   1346             self.window.set_title("Step Editor -- %s" %
   1347                                   os.path.basename(self.steps_filename))
   1348             # Set step entry widget text
   1349             self.entry_step_num.set_text("")
   1350             # Clear the state of all widgets
   1351             self.clear_state()
   1352             # Disable the main vbox
   1353             self.main_vbox.set_sensitive(False)
   1354             return
   1355 
   1356         self.current_step_index = index
   1357         self.current_step = self.steps[index]
   1358         # Set window title
   1359         self.window.set_title("Step Editor -- %s -- step %d" %
   1360                               (os.path.basename(self.steps_filename),
   1361                                index + 1))
   1362         # Set step entry widget text
   1363         self.entry_step_num.set_text(str(self.current_step_index + 1))
   1364         # Load the state from the step lines
   1365         self.set_state_from_step_lines(self.current_step, self.steps_data_dir)
   1366         # Enable the main vbox
   1367         self.main_vbox.set_sensitive(True)
   1368         # Make sure the step lines in self.current_step are identical to the
   1369         # output of self.get_step_lines
   1370         self.current_step = self.get_step_lines()
   1371 
   1372     def verify_save(self):
   1373         if not self.steps:
   1374             return
   1375         # See if the user changed anything
   1376         if self.get_step_lines() != self.current_step:
   1377             if self.question_yes_no("Step contents have been modified."
   1378                                     " Save step?", "Save changes?"):
   1379                 self.save_step()
   1380 
   1381     def save_step(self):
   1382         lines = self.get_step_lines(self.steps_data_dir)
   1383         if lines != None:
   1384             self.steps[self.current_step_index] = lines
   1385             self.current_step = lines
   1386             self.write_steps_file(self.steps_filename)
   1387 
   1388     def write_steps_file(self, filename):
   1389         file = open(filename, "w")
   1390         file.write(self.header)
   1391         for step in self.steps:
   1392             file.write("# " + "-" * 32 + "\n")
   1393             file.write(step)
   1394         file.close()
   1395 
   1396 
   1397 if __name__ == "__main__":
   1398     se = StepEditor()
   1399     if len(sys.argv) > 1:
   1400         se.set_steps_file(sys.argv[1])
   1401     gtk.main()
   1402