lmr | c6bd3a6 | 2009-06-10 19:38:06 +0000 | [diff] [blame] | 1 | #!/usr/bin/python |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 2 | """ |
| 3 | Step file creator/editor. |
| 4 | |
| 5 | @copyright: Red Hat Inc 2009 |
| 6 | @author: mgoldish@redhat.com (Michael Goldish) |
| 7 | @version: "20090401" |
| 8 | """ |
| 9 | |
lmr | b635b86 | 2009-09-10 14:53:21 +0000 | [diff] [blame^] | 10 | import pygtk, gtk, os, glob, shutil, sys, logging |
| 11 | import common, ppm_utils |
| 12 | pygtk.require('2.0') |
| 13 | |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 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: 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 | |
| 34 | def 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 | |
| 98 | class 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 | |
| 858 | class StepEditor(StepMakerWindow): |
| 859 | ui = '''<ui> |
| 860 | <menubar name="MenuBar"> |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 861 | <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> |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 880 | </menubar> |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 881 | </ui>''' |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 882 | |
| 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 |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 899 | actiongroup = gtk.ActionGroup('StepEditor') |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 900 | |
| 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), |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 907 | ('CopyStep', gtk.STOCK_COPY, '_Copy current step...', "", |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 908 | 'Copy current step to user specified position', self.copy_step), |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 909 | ('DeleteStep', gtk.STOCK_DELETE, '_Delete current step', "", |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 910 | 'Delete current step', self.event_remove_clicked), |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 911 | ('InsertNewBefore', gtk.STOCK_ADD, '_New step before current', "", |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 912 | 'Insert new step before current step', self.insert_before), |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 913 | ('InsertNewAfter', gtk.STOCK_ADD, 'N_ew step after current', "", |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 914 | 'Insert new step after current step', self.insert_after), |
| 915 | ('InsertStepsBefore', gtk.STOCK_ADD, '_Steps before current...', |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 916 | "", 'Insert steps (from file) before current step', |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 917 | self.insert_steps_before), |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 918 | ('InsertStepsAfter', gtk.STOCK_ADD, 'Steps _after current...', "", |
| 919 | 'Insert steps (from file) after current step', |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 920 | self.insert_steps_after), |
lmr | 1b99b40 | 2009-06-08 14:21:15 +0000 | [diff] [blame] | 921 | ('CleanUp', gtk.STOCK_DELETE, '_Clean up data directory', "", |
| 922 | 'Move unused PPM files to a backup directory', self.cleanup), |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 923 | ('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") |
lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 942 | |
| 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 | |
| 1386 | if __name__ == "__main__": |
| 1387 | se = StepEditor() |
| 1388 | if len(sys.argv) > 1: |
| 1389 | se.set_steps_file(sys.argv[1]) |
| 1390 | gtk.main() |