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