|
|
1.1 ! root 1: #!/usr/bin/env python ! 2: # ! 3: # A PyGtk UI for Hatari that can embed the Hatari emulator window. ! 4: # ! 5: # Requires PyGtk (python-gtk2) package and its dependencies to be present. ! 6: # ! 7: # Copyright (C) 2008-2010 by Eero Tamminen <eerot at berlios> ! 8: # ! 9: # This program is free software; you can redistribute it and/or modify ! 10: # it under the terms of the GNU General Public License as published by ! 11: # the Free Software Foundation; either version 2 of the License, or ! 12: # (at your option) any later version. ! 13: # ! 14: # This program is distributed in the hope that it will be useful, ! 15: # but WITHOUT ANY WARRANTY; without even the implied warranty of ! 16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! 17: # GNU General Public License for more details. ! 18: ! 19: import os ! 20: import sys ! 21: import getopt ! 22: ! 23: # use correct version of pygtk/gtk ! 24: import pygtk ! 25: pygtk.require('2.0') ! 26: import gtk ! 27: import gobject ! 28: ! 29: from debugui import HatariDebugUI ! 30: from hatari import Hatari, HatariConfigMapping ! 31: from uihelpers import UInfo, UIHelp, create_button, create_toolbutton, \ ! 32: create_toggle, HatariTextInsert, get_open_filename, get_save_filename ! 33: from dialogs import AboutDialog, TodoDialog, NoteDialog, ErrorDialog, \ ! 34: InputDialog, KillDialog, QuitSaveDialog, ResetDialog, TraceDialog, \ ! 35: FloppyDialog, HardDiskDialog, DisplayDialog, JoystickDialog, \ ! 36: MachineDialog, PeripheralDialog, PathDialog, SoundDialog ! 37: ! 38: ! 39: # helper functions to match callback args ! 40: def window_hide_cb(window, arg): ! 41: window.hide() ! 42: return True ! 43: ! 44: ! 45: # --------------------------------------------------------------- ! 46: # Class with Hatari and configuration instances which methods are ! 47: # called to change those (with additional dialogs or directly). ! 48: # Owns the application window and socket widget embedding Hatari. ! 49: class UICallbacks: ! 50: tmpconfpath = os.path.expanduser("~/.hatari/.tmp.cfg") ! 51: def __init__(self): ! 52: # Hatari and configuration ! 53: self.hatari = Hatari() ! 54: error = self.hatari.is_compatible() ! 55: if error: ! 56: ErrorDialog(None).run(error) ! 57: sys.exit(-1) ! 58: ! 59: self.config = HatariConfigMapping(self.hatari) ! 60: try: ! 61: self.config.validate() ! 62: except (KeyError, AttributeError): ! 63: NoteDialog(None).run("Loading Hatari configuration failed!\nRetrying after saving Hatari configuration.") ! 64: self.hatari.save_config() ! 65: self.config = HatariConfigMapping(self.hatari) ! 66: self.config.validate() ! 67: ! 68: # windows are created when needed ! 69: self.mainwin = None ! 70: self.hatariwin = None ! 71: self.debugui = None ! 72: self.panels = {} ! 73: ! 74: # dialogs are created when needed ! 75: self.aboutdialog = None ! 76: self.inputdialog = None ! 77: self.tracedialog = None ! 78: self.resetdialog = None ! 79: self.quitdialog = None ! 80: self.killdialog = None ! 81: ! 82: self.floppydialog = None ! 83: self.harddiskdialog = None ! 84: self.displaydialog = None ! 85: self.joystickdialog = None ! 86: self.machinedialog = None ! 87: self.peripheraldialog = None ! 88: self.sounddialog = None ! 89: self.pathdialog = None ! 90: ! 91: # used by run() ! 92: self.memstate = None ! 93: self.floppy = None ! 94: self.io_id = None ! 95: ! 96: # TODO: Hatari UI own configuration settings save/load ! 97: self.tracepoints = None ! 98: ! 99: def _reset_config_dialogs(self): ! 100: self.floppydialog = None ! 101: self.harddiskdialog = None ! 102: self.displaydialog = None ! 103: self.joystickdialog = None ! 104: self.machinedialog = None ! 105: self.peripheraldialog = None ! 106: self.sounddialog = None ! 107: self.pathdialog = None ! 108: ! 109: # ---------- create UI ---------------- ! 110: def create_ui(self, accelgroup, menu, toolbars, fullscreen, embed): ! 111: "create_ui(menu, toolbars, fullscreen, embed)" ! 112: # add horizontal elements ! 113: hbox = gtk.HBox() ! 114: if toolbars["left"]: ! 115: hbox.pack_start(toolbars["left"], False, True) ! 116: if embed: ! 117: self._add_uisocket(hbox) ! 118: if toolbars["right"]: ! 119: hbox.pack_start(toolbars["right"], False, True) ! 120: # add vertical elements ! 121: vbox = gtk.VBox() ! 122: if menu: ! 123: vbox.add(menu) ! 124: if toolbars["top"]: ! 125: vbox.pack_start(toolbars["top"], False, True) ! 126: vbox.add(hbox) ! 127: if toolbars["bottom"]: ! 128: vbox.pack_start(toolbars["bottom"], False, True) ! 129: # put them to main window ! 130: mainwin = gtk.Window(gtk.WINDOW_TOPLEVEL) ! 131: mainwin.set_title("%s %s" % (UInfo.name, UInfo.version)) ! 132: mainwin.set_icon_from_file(UInfo.icon) ! 133: if accelgroup: ! 134: mainwin.add_accel_group(accelgroup) ! 135: if fullscreen: ! 136: mainwin.fullscreen() ! 137: mainwin.add(vbox) ! 138: mainwin.show_all() ! 139: # for run and quit callbacks ! 140: self.killdialog = KillDialog(mainwin) ! 141: mainwin.connect("delete_event", self.quit) ! 142: self.mainwin = mainwin ! 143: ! 144: def _add_uisocket(self, box): ! 145: # add Hatari parent container to given box ! 146: socket = gtk.Socket() ! 147: # without this, closing Hatari would remove the socket widget ! 148: socket.connect("plug-removed", lambda obj: True) ! 149: socket.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("black")) ! 150: socket.set_events(gtk.gdk.ALL_EVENTS_MASK) ! 151: socket.set_flags(gtk.CAN_FOCUS) ! 152: # set initial embedded hatari size ! 153: width, height = self.config.get_window_size() ! 154: socket.set_size_request(width, height) ! 155: # no resizing for the Hatari window ! 156: box.pack_start(socket, False, False) ! 157: self.hatariwin = socket ! 158: ! 159: # ------- run callback ----------- ! 160: def _socket_cb(self, fd, event): ! 161: if event != gobject.IO_IN: ! 162: # hatari process died, make sure Hatari instance notices ! 163: self.hatari.kill() ! 164: return False ! 165: width, height = self.hatari.get_embed_info() ! 166: print "New size = %d x %d" % (width, height) ! 167: oldwidth, oldheight = self.hatariwin.get_size_request() ! 168: self.hatariwin.set_size_request(width, height) ! 169: if width < oldwidth or height < oldheight: ! 170: # force also mainwin smaller (it automatically grows) ! 171: self.mainwin.resize(width, height) ! 172: return True ! 173: ! 174: def run(self, widget = None): ! 175: if not self.killdialog.run(self.hatari): ! 176: return ! 177: if self.io_id: ! 178: gobject.source_remove(self.io_id) ! 179: args = ["--configfile"] ! 180: # whether to use Hatari config or unsaved Hatari UI config? ! 181: if self.config.is_changed(): ! 182: args += [self.config.save_tmp(self.tmpconfpath)] ! 183: else: ! 184: args += [self.config.get_path()] ! 185: if self.memstate: ! 186: args += self.memstate ! 187: # only way to change boot order is to specify disk on command line ! 188: if self.floppy: ! 189: args += self.floppy ! 190: if self.hatariwin: ! 191: size = self.hatariwin.window.get_size() ! 192: self.hatari.run(args, self.hatariwin.window) ! 193: # get notifications of Hatari window size changes ! 194: self.hatari.enable_embed_info() ! 195: socket = self.hatari.get_control_socket().fileno() ! 196: events = gobject.IO_IN | gobject.IO_HUP | gobject.IO_ERR ! 197: self.io_id = gobject.io_add_watch(socket, events, self._socket_cb) ! 198: # all keyboard events should go to Hatari window ! 199: self.hatariwin.grab_focus() ! 200: else: ! 201: self.hatari.run(args) ! 202: ! 203: def set_floppy(self, floppy): ! 204: self.floppy = floppy ! 205: ! 206: # ------- quit callback ----------- ! 207: def quit(self, widget, arg = None): ! 208: # due to Gtk API, needs to return True when *not* quitting ! 209: if not self.killdialog.run(self.hatari): ! 210: return True ! 211: if self.io_id: ! 212: gobject.source_remove(self.io_id) ! 213: if self.config.is_changed(): ! 214: if not self.quitdialog: ! 215: self.quitdialog = QuitSaveDialog(self.mainwin) ! 216: if not self.quitdialog.run(self.config): ! 217: return True ! 218: gtk.main_quit() ! 219: if os.path.exists(self.tmpconfpath): ! 220: os.unlink(self.tmpconfpath) ! 221: # continue to mainwin destroy if called by delete_event ! 222: return False ! 223: ! 224: # ------- pause callback ----------- ! 225: def pause(self, widget): ! 226: if widget.get_active(): ! 227: self.hatari.pause() ! 228: else: ! 229: self.hatari.unpause() ! 230: ! 231: # dialogs ! 232: # ------- reset callback ----------- ! 233: def reset(self, widget): ! 234: if not self.resetdialog: ! 235: self.resetdialog = ResetDialog(self.mainwin) ! 236: self.resetdialog.run(self.hatari) ! 237: ! 238: # ------- about callback ----------- ! 239: def about(self, widget): ! 240: if not self.aboutdialog: ! 241: self.aboutdialog = AboutDialog(self.mainwin) ! 242: self.aboutdialog.run() ! 243: ! 244: # ------- input callback ----------- ! 245: def inputs(self, widget): ! 246: if not self.inputdialog: ! 247: self.inputdialog = InputDialog(self.mainwin) ! 248: self.inputdialog.run(self.hatari) ! 249: ! 250: # ------- floppydisk callback ----------- ! 251: def floppydisk(self, widget): ! 252: if not self.floppydialog: ! 253: self.floppydialog = FloppyDialog(self.mainwin) ! 254: self.floppydialog.run(self.config) ! 255: ! 256: # ------- harddisk callback ----------- ! 257: def harddisk(self, widget): ! 258: if not self.harddiskdialog: ! 259: self.harddiskdialog = HardDiskDialog(self.mainwin) ! 260: self.harddiskdialog.run(self.config) ! 261: ! 262: # ------- display callback ----------- ! 263: def display(self, widget): ! 264: if not self.displaydialog: ! 265: self.displaydialog = DisplayDialog(self.mainwin) ! 266: self.displaydialog.run(self.config) ! 267: ! 268: # ------- joystick callback ----------- ! 269: def joystick(self, widget): ! 270: if not self.joystickdialog: ! 271: self.joystickdialog = JoystickDialog(self.mainwin) ! 272: self.joystickdialog.run(self.config) ! 273: ! 274: # ------- machine callback ----------- ! 275: def machine(self, widget): ! 276: if not self.machinedialog: ! 277: self.machinedialog = MachineDialog(self.mainwin) ! 278: if self.machinedialog.run(self.config): ! 279: self.hatari.trigger_shortcut("coldreset") ! 280: ! 281: # ------- peripheral callback ----------- ! 282: def peripheral(self, widget): ! 283: if not self.peripheraldialog: ! 284: self.peripheraldialog = PeripheralDialog(self.mainwin) ! 285: self.peripheraldialog.run(self.config) ! 286: ! 287: # ------- sound callback ----------- ! 288: def sound(self, widget): ! 289: if not self.sounddialog: ! 290: self.sounddialog = SoundDialog(self.mainwin) ! 291: self.sounddialog.run(self.config) ! 292: ! 293: # ------- path callback ----------- ! 294: def path(self, widget): ! 295: if not self.pathdialog: ! 296: self.pathdialog = PathDialog(self.mainwin) ! 297: self.pathdialog.run(self.config) ! 298: ! 299: # ------- debug callback ----------- ! 300: def debugger(self, widget): ! 301: if not self.debugui: ! 302: self.debugui = HatariDebugUI(self.hatari) ! 303: self.debugui.show() ! 304: ! 305: # ------- trace callback ----------- ! 306: def trace(self, widget): ! 307: if not self.tracedialog: ! 308: self.tracedialog = TraceDialog(self.mainwin) ! 309: self.tracepoints = self.tracedialog.run(self.hatari, self.tracepoints) ! 310: ! 311: # ------ snapshot load/save callbacks --------- ! 312: def load(self, widget): ! 313: path = os.path.expanduser("~/.hatari/hatari.sav") ! 314: filename = get_open_filename("Select snapshot", self.mainwin, path) ! 315: if filename: ! 316: self.memstate = ["--memstate", filename] ! 317: self.run() ! 318: return True ! 319: return False ! 320: ! 321: def save(self, widget): ! 322: self.hatari.trigger_shortcut("savemem") ! 323: ! 324: # ------ config load/save callbacks --------- ! 325: def config_load(self, widget): ! 326: path = self.config.get_path() ! 327: filename = get_open_filename("Select configuration file", self.mainwin, path) ! 328: if filename: ! 329: self.hatari.change_option("--configfile %s" % filename) ! 330: self.config.load(filename) ! 331: self._reset_config_dialogs() ! 332: return True ! 333: return False ! 334: ! 335: def config_save(self, widget): ! 336: path = self.config.get_path() ! 337: filename = get_save_filename("Save configuration as...", self.mainwin, path) ! 338: if filename: ! 339: self.config.save_as(filename) ! 340: return True ! 341: return False ! 342: ! 343: # ------- fast forward callback ----------- ! 344: def set_fastforward(self, widget): ! 345: self.config.set_fastforward(widget.get_active()) ! 346: ! 347: def get_fastforward(self): ! 348: return self.config.get_fastforward() ! 349: ! 350: # ------- fullscreen callback ----------- ! 351: def set_fullscreen(self, widget): ! 352: # if user can select this, Hatari isn't in fullscreen ! 353: self.hatari.change_option("--fullscreen") ! 354: ! 355: # ------- screenshot callback ----------- ! 356: def screenshot(self, widget): ! 357: self.hatari.trigger_shortcut("screenshot") ! 358: ! 359: # ------- record callbacks ----------- ! 360: def recanim(self, widget): ! 361: self.hatari.trigger_shortcut("recanim") ! 362: ! 363: def recsound(self, widget): ! 364: self.hatari.trigger_shortcut("recsound") ! 365: ! 366: # ------- insert key special callback ----------- ! 367: def keypress(self, widget, code): ! 368: self.hatari.insert_event("keypress %s" % code) ! 369: self.hatari.insert_event("keyrelease %s" % code) ! 370: ! 371: def textinsert(self, widget, text): ! 372: HatariTextInsert(self.hatari, text) ! 373: ! 374: # ------- panel callback ----------- ! 375: def panel(self, action, box): ! 376: title = action.get_name() ! 377: if title not in self.panels: ! 378: window = gtk.Window(gtk.WINDOW_TOPLEVEL) ! 379: window.set_transient_for(self.mainwin) ! 380: window.set_icon_from_file(UInfo.icon) ! 381: window.set_title(title) ! 382: window.add(box) ! 383: window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) ! 384: window.connect("delete_event", window_hide_cb) ! 385: self.panels[title] = window ! 386: else: ! 387: window = self.panels[title] ! 388: window.show_all() ! 389: window.deiconify() ! 390: ! 391: ! 392: # --------------------------------------------------------------- ! 393: # class for creating menus, toolbars and panels ! 394: # and managing actions bound to them ! 395: class UIActions: ! 396: def __init__(self): ! 397: cb = self.callbacks = UICallbacks() ! 398: ! 399: self.help = UIHelp() ! 400: ! 401: self.actions = gtk.ActionGroup("All") ! 402: ! 403: # name, icon ID, label, accel, tooltip, callback ! 404: self.actions.add_toggle_actions(( ! 405: # TODO: how to know when these are changed from inside Hatari? ! 406: ("recanim", gtk.STOCK_MEDIA_RECORD, "Record animation", "<Ctrl>A", "Record animation", cb.recanim), ! 407: ("recsound", gtk.STOCK_MEDIA_RECORD, "Record sound", "<Ctrl>W", "Record YM/Wav", cb.recsound), ! 408: ("pause", gtk.STOCK_MEDIA_PAUSE, "Pause", "<Ctrl>P", "Pause Hatari to save battery", cb.pause), ! 409: ("forward", gtk.STOCK_MEDIA_FORWARD, "Forward", "<Ctrl>F", "Whether to fast forward Hatari (needs fast machine)", cb.set_fastforward, cb.get_fastforward()) ! 410: )) ! 411: ! 412: # name, icon ID, label, accel, tooltip, callback ! 413: self.actions.add_actions(( ! 414: ("load", gtk.STOCK_OPEN, "Load snapshot...", "<Ctrl>L", "Load emulation snapshot", cb.load), ! 415: ("save", gtk.STOCK_SAVE, "Save snapshot", "<Ctrl>S", "Save emulation snapshot", cb.save), ! 416: ("shot", gtk.STOCK_MEDIA_RECORD, "Grab screenshot", "<Ctrl>G", "Grab a screenshot", cb.screenshot), ! 417: ("quit", gtk.STOCK_QUIT, "Quit", "<Ctrl>Q", "Quit Hatari UI", cb.quit), ! 418: ! 419: ("run", gtk.STOCK_MEDIA_PLAY, "Run", "<Ctrl>R", "(Re-)run Hatari", cb.run), ! 420: ("full", gtk.STOCK_FULLSCREEN, "Fullscreen", "<Ctrl>U", "Toggle whether Hatari is fullscreen", cb.set_fullscreen), ! 421: ("input", gtk.STOCK_SPELL_CHECK, "Inputs...", "<Ctrl>N", "Simulate text input and mouse clicks", cb.inputs), ! 422: ("reset", gtk.STOCK_REFRESH, "Reset...", "<Ctrl>E", "Warm or cold reset Hatari", cb.reset), ! 423: ! 424: ("display", gtk.STOCK_PREFERENCES, "Display...", "<Ctrl>Y", "Display settings", cb.display), ! 425: ("floppy", gtk.STOCK_FLOPPY, "Floppies...", "<Ctrl>D", "Floppy images", cb.floppydisk), ! 426: ("harddisk", gtk.STOCK_HARDDISK, "Hard disks...", "<Ctrl>H", "Hard disk images and directories", cb.harddisk), ! 427: ("joystick", gtk.STOCK_CONNECT, "Joysticks...", "<Ctrl>J", "Joystick settings", cb.joystick), ! 428: ("machine", gtk.STOCK_HARDDISK, "Machine...", "<Ctrl>M", "Hatari st/e/tt/falcon configuration", cb.machine), ! 429: ("device", gtk.STOCK_PRINT, "Peripherals...", "<Ctrl>V", "Toggle Midi, Printer, RS232 peripherals", cb.peripheral), ! 430: ("sound", gtk.STOCK_PROPERTIES, "Sound...", "<Ctrl>O", "Sound settings", cb.sound), ! 431: ! 432: ("path", gtk.STOCK_DIRECTORY, "Paths...", None, "Device & save file paths", cb.path), ! 433: ("lconfig", gtk.STOCK_OPEN, "Load config...", "<Ctrl>C", "Load configuration", self.config_load), ! 434: ("sconfig", gtk.STOCK_SAVE_AS, "Save config as...", None, "Save configuration", cb.config_save), ! 435: ! 436: ("debug", gtk.STOCK_FIND, "Debugger...", "<Ctrl>B", "Activate Hatari debugger", cb.debugger), ! 437: ("trace", gtk.STOCK_EXECUTE, "Trace settings...", "<Ctrl>T", "Hatari tracing setup", cb.trace), ! 438: ! 439: ("manual", None, "Hatari manual", None, None, self.help.view_hatari_manual), ! 440: ("compatibility", None, "Hatari compatibility list", None, None, self.help.view_hatari_compatibility), ! 441: ("release", None, "Hatari release notes", None, None, self.help.view_hatari_releasenotes), ! 442: ("todo", None, "Hatari TODO", None, None, self.help.view_hatari_todo), ! 443: ("bugs", None, "Report a bug", None, None, self.help.view_hatari_bugs), ! 444: ("mails", None, "Hatari mailing lists", None, None, self.help.view_hatari_mails), ! 445: ("changes", None, "Latest Hatari changes", None, None, self.help.view_hatari_repository), ! 446: ("authors", None, "Hatari authors", None, None, self.help.view_hatari_authors), ! 447: ("hatari", None, "Hatari home page", None, None, self.help.view_hatari_page), ! 448: ("hatariui", None, "Hatari UI home page", None, None, self.help.view_hatariui_page), ! 449: ("about", gtk.STOCK_INFO, "Hatari UI info", "<Ctrl>I", "Hatari UI information", cb.about) ! 450: )) ! 451: self.action_names = [x.get_name() for x in self.actions.list_actions()] ! 452: ! 453: # no actions set yet to panels or toolbars ! 454: self.toolbars = {} ! 455: self.panels = [] ! 456: ! 457: def config_load(self, widget): ! 458: # user loads a new configuration? ! 459: if self.callbacks.config_load(widget): ! 460: print "TODO: reset toggle actions" ! 461: ! 462: # ----- toolbar / panel additions --------- ! 463: def set_actions(self, action_str, place): ! 464: "set_actions(actions,place) -> error string, None if all OK" ! 465: actions = action_str.split(",") ! 466: for action in actions: ! 467: if action in self.action_names: ! 468: # regular action ! 469: continue ! 470: if action in self.panels: ! 471: # user specified panel ! 472: continue ! 473: if action in ("close", ">"): ! 474: if place != "panel": ! 475: return "'close' and '>' can be only in a panel" ! 476: continue ! 477: if action == "|": ! 478: # divider ! 479: continue ! 480: if action.find("=") >= 0: ! 481: # special keycode/string action ! 482: continue ! 483: return "unrecognized action '%s'" % action ! 484: ! 485: if place in ("left", "right", "top", "bottom"): ! 486: self.toolbars[place] = actions ! 487: return None ! 488: if place == "panel": ! 489: if len(actions) < 3: ! 490: return "panel has too few items to be useful" ! 491: return None ! 492: return "unknown actions position '%s'" % place ! 493: ! 494: def add_panel(self, spec): ! 495: "add_panel(panel_specification) -> error string, None if all is OK" ! 496: offset = spec.find(",") ! 497: if offset <= 0: ! 498: return "invalid panel specification '%s'" % spec ! 499: ! 500: name, panelcontrols = spec[:offset], spec[offset+1:] ! 501: error = self.set_actions(panelcontrols, "panel") ! 502: if error: ! 503: return error ! 504: ! 505: if ",>," in panelcontrols: ! 506: box = gtk.VBox() ! 507: splitcontrols = panelcontrols.split(",>,") ! 508: for controls in splitcontrols: ! 509: box.add(self._get_container(controls.split(","))) ! 510: else: ! 511: box = self._get_container(panelcontrols.split(",")) ! 512: ! 513: self.panels.append(name) ! 514: self.actions.add_actions( ! 515: ((name, gtk.STOCK_ADD, name, None, name, self.callbacks.panel),), ! 516: box ! 517: ) ! 518: return None ! 519: ! 520: def list_actions(self): ! 521: yield ("|", "Separator between controls") ! 522: yield (">", "Next toolbar in panel windows") ! 523: # generate the list from action information ! 524: for act in self.actions.list_actions(): ! 525: note = act.get_property("tooltip") ! 526: if not note: ! 527: note = act.get_property("label") ! 528: yield(act.get_name(), note) ! 529: yield ("<panel name>", "Button for the specified panel window") ! 530: yield ("<name>=<string/code>", "Synthetize string or single key <code>") ! 531: ! 532: # ------- panel special actions ----------- ! 533: def _close_cb(self, widget): ! 534: widget.get_toplevel().hide() ! 535: ! 536: # ------- key special action ----------- ! 537: def _create_key_control(self, name, textcode): ! 538: "Simulate Atari key press/release and string inserting" ! 539: if not textcode: ! 540: return (None, None) ! 541: widget = gtk.ToolButton(gtk.STOCK_PASTE) ! 542: widget.set_label(name) ! 543: try: ! 544: # part after "=" converts to an int? ! 545: code = int(textcode, 0) ! 546: widget.connect("clicked", self.callbacks.keypress, code) ! 547: tip = "keycode: %d" % code ! 548: except ValueError: ! 549: # no, assume a string macro is wanted instead ! 550: widget.connect("clicked", self.callbacks.textinsert, textcode) ! 551: tip = "string '%s'" % textcode ! 552: return (widget, tip) ! 553: ! 554: def _get_container(self, actions, horiz = True): ! 555: "return Gtk container with the specified actions or None for no actions" ! 556: if not actions: ! 557: return None ! 558: ! 559: #print "ACTIONS:", actions ! 560: if len(actions) > 1: ! 561: bar = gtk.Toolbar() ! 562: if horiz: ! 563: bar.set_orientation(gtk.ORIENTATION_HORIZONTAL) ! 564: else: ! 565: bar.set_orientation(gtk.ORIENTATION_VERTICAL) ! 566: bar.set_style(gtk.TOOLBAR_BOTH) ! 567: # disable overflow menu to get toolbar sized correctly for panels ! 568: bar.set_show_arrow(False) ! 569: bar.set_tooltips(True) ! 570: else: ! 571: bar = None ! 572: ! 573: tooltips = gtk.Tooltips() ! 574: for action in actions: ! 575: #print action ! 576: offset = action.find("=") ! 577: if offset >= 0: ! 578: # handle "<name>=<keycode>" action specification ! 579: name = action[:offset] ! 580: text = action[offset+1:] ! 581: (widget, tip) = self._create_key_control(name, text) ! 582: widget.set_tooltip(tooltips, "Insert " + tip) ! 583: elif action == "|": ! 584: widget = gtk.SeparatorToolItem() ! 585: elif action == "close": ! 586: if bar: ! 587: widget = create_toolbutton(gtk.STOCK_CLOSE, self._close_cb) ! 588: else: ! 589: widget = create_button("Close", self._close_cb) ! 590: else: ! 591: widget = self.actions.get_action(action).create_tool_item() ! 592: if not widget: ! 593: continue ! 594: if bar: ! 595: if action != "|": ! 596: widget.set_expand(True) ! 597: bar.insert(widget, -1) ! 598: if bar: ! 599: return bar ! 600: return widget ! 601: ! 602: # ------------- handling menu ------------- ! 603: def _add_submenu(self, bar, title, items): ! 604: submenu = gtk.Menu() ! 605: for name in items: ! 606: if name: ! 607: action = self.actions.get_action(name) ! 608: item = action.create_menu_item() ! 609: else: ! 610: item = gtk.SeparatorMenuItem() ! 611: submenu.add(item) ! 612: baritem = gtk.MenuItem(title, False) ! 613: baritem.set_submenu(submenu) ! 614: bar.add(baritem) ! 615: ! 616: def _get_menu(self): ! 617: allmenus = ( ! 618: ("File", ("load", "save", None, "shot", "recanim", "recsound", None, "quit")), ! 619: ("Emulation", ("run", "pause", "forward", None, "full", None, "input", None, "reset")), ! 620: ("Devices", ("display", "floppy", "harddisk", "joystick", "machine", "device", "sound")), ! 621: ("Configuration", ("path", None, "lconfig", "sconfig")), ! 622: ("Debug", ("debug", "trace")), ! 623: ("Help", ("manual", "compatibility", "release", "todo", None, "bugs", "mails", "changes", None, "authors", "hatari", "hatariui", "about",)) ! 624: ) ! 625: bar = gtk.MenuBar() ! 626: ! 627: for title, items in allmenus: ! 628: self._add_submenu(bar, title, items) ! 629: ! 630: if self.panels: ! 631: self._add_submenu(bar, "Panels", self.panels) ! 632: ! 633: return bar ! 634: ! 635: # ------------- run the whole UI ------------- ! 636: def run(self, floppy, havemenu, fullscreen, embed): ! 637: accelgroup = None ! 638: # create menu? ! 639: if havemenu: ! 640: # this would steal keys from embedded Hatari ! 641: if not embed: ! 642: accelgroup = gtk.AccelGroup() ! 643: for action in self.actions.list_actions(): ! 644: action.set_accel_group(accelgroup) ! 645: menu = self._get_menu() ! 646: else: ! 647: menu = None ! 648: ! 649: # create toolbars ! 650: toolbars = { "left":None, "right":None, "top":None, "bottom":None} ! 651: for side in ("left", "right"): ! 652: if side in self.toolbars: ! 653: toolbars[side] = self._get_container(self.toolbars[side], False) ! 654: for side in ("top", "bottom"): ! 655: if side in self.toolbars: ! 656: toolbars[side] = self._get_container(self.toolbars[side], True) ! 657: ! 658: self.callbacks.create_ui(accelgroup, menu, toolbars, fullscreen, embed) ! 659: self.help.set_mainwin(self.callbacks.mainwin) ! 660: self.callbacks.set_floppy(floppy) ! 661: ! 662: # ugly, Hatari socket window ID can be gotten only ! 663: # after Socket window is realized by gtk_main() ! 664: gobject.idle_add(self.callbacks.run) ! 665: gtk.main() ! 666: ! 667: ! 668: # ------------- usage / argument handling -------------- ! 669: def usage(actions, msg=None): ! 670: name = os.path.basename(sys.argv[0]) ! 671: uiname = "%s %s" % (UInfo.name, UInfo.version) ! 672: print "\n%s" % uiname ! 673: print "=" * len(uiname) ! 674: print "\nUsage: %s [options] [floppy image]" % name ! 675: print "\nOptions:" ! 676: print "\t-h, --help\t\tthis help" ! 677: print "\t-n, --nomenu\t\tomit menus" ! 678: print "\t-e, --embed\t\tembed Hatari window in middle of controls" ! 679: print "\t-f, --fullscreen\tstart in fullscreen" ! 680: print "\t-l, --left <controls>\ttoolbar at left" ! 681: print "\t-r, --right <controls>\ttoolbar at right" ! 682: print "\t-t, --top <controls>\ttoolbar at top" ! 683: print "\t-b, --bottom <controls>\ttoolbar at bottom" ! 684: print "\t-p, --panel <name>,<controls>" ! 685: print "\t\t\t\tseparate window with given name and controls" ! 686: print "\nAvailable (panel/toolbar) controls:" ! 687: for action, description in actions.list_actions(): ! 688: size = len(action) ! 689: if size < 8: ! 690: tabs = "\t\t" ! 691: elif size < 16: ! 692: tabs = "\t" ! 693: else: ! 694: tabs = "\n\t\t\t" ! 695: print "\t%s%s%s" % (action, tabs, description) ! 696: print """ ! 697: You can have as many panels as you wish. For each panel you need to add ! 698: a control with the name of the panel (see "MyPanel" below). ! 699: ! 700: For example: ! 701: \t%s --embed \\ ! 702: \t-t "about,run,pause,quit" \\ ! 703: \t-p "MyPanel,Macro=Test,Undo=97,Help=98,>,F1=59,F2=60,F3=61,F4=62,>,close" \\ ! 704: \t-r "paste,debug,trace,machine,MyPanel" \\ ! 705: \t-b "sound,|,fastforward,|,fullscreen" ! 706: ! 707: if no options are given, the UI uses basic controls. ! 708: """ % name ! 709: if msg: ! 710: print "ERROR: %s\n" % msg ! 711: sys.exit(1) ! 712: ! 713: ! 714: def main(): ! 715: info = UInfo() ! 716: actions = UIActions() ! 717: try: ! 718: longopts = ["embed", "fullscreen", "nomenu", "help", ! 719: "left=", "right=", "top=", "bottom=", "panel="] ! 720: opts, floppies = getopt.getopt(sys.argv[1:], "efnhl:r:t:b:p:", longopts) ! 721: del longopts ! 722: except getopt.GetoptError, err: ! 723: usage(actions, err) ! 724: ! 725: menu = True ! 726: embed = False ! 727: fullscreen = False ! 728: ! 729: error = None ! 730: for opt, arg in opts: ! 731: print opt, arg ! 732: if opt in ("-e", "--embed"): ! 733: embed = True ! 734: elif opt in ("-f", "--fullscreen"): ! 735: fullscreen = True ! 736: elif opt in ("-n", "--nomenu"): ! 737: menu = False ! 738: elif opt in ("-h", "--help"): ! 739: usage(actions) ! 740: elif opt in ("-l", "--left"): ! 741: error = actions.set_actions(arg, "left") ! 742: elif opt in ("-r", "--right"): ! 743: error = actions.set_actions(arg, "right") ! 744: elif opt in ("-t", "--top"): ! 745: error = actions.set_actions(arg, "top") ! 746: elif opt in ("-b", "--bottom"): ! 747: error = actions.set_actions(arg, "bottom") ! 748: elif opt in ("-p", "--panel"): ! 749: error = actions.add_panel(arg) ! 750: else: ! 751: assert False, "getopt returned unhandled option" ! 752: if error: ! 753: usage(actions, error) ! 754: ! 755: if len(floppies) > 1: ! 756: usage(actions, "multiple floppy images given: %s" % str(floppies)) ! 757: if floppies: ! 758: if not os.path.exists(floppies[0]): ! 759: usage(actions, "floppy image '%s' doesn't exist" % floppies[0]) ! 760: ! 761: actions.run(floppies, menu, fullscreen, embed) ! 762: ! 763: ! 764: if __name__ == "__main__": ! 765: main()
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.