|
|
1.1.1.7 ! root 1: #!/usr/bin/python2 1.1 root 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: # 1.1.1.5 root 7: # Copyright (C) 2008-2011 by Eero Tamminen 1.1 root 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 1.1.1.2 root 33: from dialogs import AboutDialog, TodoDialog, NoteDialog, ErrorDialog, \ 34: InputDialog, KillDialog, QuitSaveDialog, ResetDialog, TraceDialog, \ 35: FloppyDialog, HardDiskDialog, DisplayDialog, JoystickDialog, \ 36: MachineDialog, PeripheralDialog, PathDialog, SoundDialog 1.1 root 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() 1.1.1.2 root 54: error = self.hatari.is_compatible() 55: if error: 56: ErrorDialog(None).run(error) 57: sys.exit(-1) 58: 1.1 root 59: self.config = HatariConfigMapping(self.hatari) 1.1.1.2 root 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() 1.1 root 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: 1.1.1.2 root 82: self.floppydialog = None 83: self.harddiskdialog = None 1.1 root 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): 1.1.1.2 root 100: self.floppydialog = None 101: self.harddiskdialog = None 1.1 root 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) 1.1.1.4 root 152: # set max Hatari window size = desktop size 153: self.config.set_desktop_size(gtk.gdk.screen_width(), gtk.gdk.screen_height()) 1.1 root 154: # set initial embedded hatari size 155: width, height = self.config.get_window_size() 156: socket.set_size_request(width, height) 157: # no resizing for the Hatari window 158: box.pack_start(socket, False, False) 159: self.hatariwin = socket 160: 161: # ------- run callback ----------- 162: def _socket_cb(self, fd, event): 163: if event != gobject.IO_IN: 164: # hatari process died, make sure Hatari instance notices 165: self.hatari.kill() 166: return False 167: width, height = self.hatari.get_embed_info() 1.1.1.3 root 168: print("New size = %d x %d" % (width, height)) 1.1 root 169: oldwidth, oldheight = self.hatariwin.get_size_request() 170: self.hatariwin.set_size_request(width, height) 171: if width < oldwidth or height < oldheight: 172: # force also mainwin smaller (it automatically grows) 173: self.mainwin.resize(width, height) 174: return True 175: 176: def run(self, widget = None): 177: if not self.killdialog.run(self.hatari): 178: return 179: if self.io_id: 180: gobject.source_remove(self.io_id) 181: args = ["--configfile"] 182: # whether to use Hatari config or unsaved Hatari UI config? 183: if self.config.is_changed(): 184: args += [self.config.save_tmp(self.tmpconfpath)] 185: else: 186: args += [self.config.get_path()] 187: if self.memstate: 188: args += self.memstate 1.1.1.2 root 189: # only way to change boot order is to specify disk on command line 1.1 root 190: if self.floppy: 191: args += self.floppy 192: if self.hatariwin: 193: size = self.hatariwin.window.get_size() 1.1.1.7 ! root 194: self.hatari.run(args, self.hatariwin.get_id()) 1.1 root 195: # get notifications of Hatari window size changes 196: self.hatari.enable_embed_info() 197: socket = self.hatari.get_control_socket().fileno() 198: events = gobject.IO_IN | gobject.IO_HUP | gobject.IO_ERR 199: self.io_id = gobject.io_add_watch(socket, events, self._socket_cb) 200: # all keyboard events should go to Hatari window 201: self.hatariwin.grab_focus() 202: else: 203: self.hatari.run(args) 204: 205: def set_floppy(self, floppy): 206: self.floppy = floppy 207: 208: # ------- quit callback ----------- 209: def quit(self, widget, arg = None): 210: # due to Gtk API, needs to return True when *not* quitting 211: if not self.killdialog.run(self.hatari): 212: return True 213: if self.io_id: 214: gobject.source_remove(self.io_id) 215: if self.config.is_changed(): 216: if not self.quitdialog: 217: self.quitdialog = QuitSaveDialog(self.mainwin) 218: if not self.quitdialog.run(self.config): 219: return True 220: gtk.main_quit() 221: if os.path.exists(self.tmpconfpath): 222: os.unlink(self.tmpconfpath) 223: # continue to mainwin destroy if called by delete_event 224: return False 225: 226: # ------- pause callback ----------- 227: def pause(self, widget): 228: if widget.get_active(): 229: self.hatari.pause() 230: else: 231: self.hatari.unpause() 232: 233: # dialogs 234: # ------- reset callback ----------- 235: def reset(self, widget): 236: if not self.resetdialog: 237: self.resetdialog = ResetDialog(self.mainwin) 238: self.resetdialog.run(self.hatari) 239: 240: # ------- about callback ----------- 241: def about(self, widget): 242: if not self.aboutdialog: 243: self.aboutdialog = AboutDialog(self.mainwin) 244: self.aboutdialog.run() 245: 246: # ------- input callback ----------- 247: def inputs(self, widget): 248: if not self.inputdialog: 249: self.inputdialog = InputDialog(self.mainwin) 250: self.inputdialog.run(self.hatari) 251: 1.1.1.2 root 252: # ------- floppydisk callback ----------- 253: def floppydisk(self, widget): 254: if not self.floppydialog: 255: self.floppydialog = FloppyDialog(self.mainwin) 256: self.floppydialog.run(self.config) 257: 258: # ------- harddisk callback ----------- 259: def harddisk(self, widget): 260: if not self.harddiskdialog: 261: self.harddiskdialog = HardDiskDialog(self.mainwin) 262: self.harddiskdialog.run(self.config) 1.1 root 263: 264: # ------- display callback ----------- 265: def display(self, widget): 266: if not self.displaydialog: 267: self.displaydialog = DisplayDialog(self.mainwin) 268: self.displaydialog.run(self.config) 269: 270: # ------- joystick callback ----------- 271: def joystick(self, widget): 272: if not self.joystickdialog: 273: self.joystickdialog = JoystickDialog(self.mainwin) 274: self.joystickdialog.run(self.config) 275: 276: # ------- machine callback ----------- 277: def machine(self, widget): 278: if not self.machinedialog: 279: self.machinedialog = MachineDialog(self.mainwin) 280: if self.machinedialog.run(self.config): 281: self.hatari.trigger_shortcut("coldreset") 282: 283: # ------- peripheral callback ----------- 284: def peripheral(self, widget): 285: if not self.peripheraldialog: 286: self.peripheraldialog = PeripheralDialog(self.mainwin) 287: self.peripheraldialog.run(self.config) 288: 289: # ------- sound callback ----------- 290: def sound(self, widget): 291: if not self.sounddialog: 292: self.sounddialog = SoundDialog(self.mainwin) 293: self.sounddialog.run(self.config) 294: 295: # ------- path callback ----------- 296: def path(self, widget): 297: if not self.pathdialog: 298: self.pathdialog = PathDialog(self.mainwin) 299: self.pathdialog.run(self.config) 300: 301: # ------- debug callback ----------- 302: def debugger(self, widget): 303: if not self.debugui: 304: self.debugui = HatariDebugUI(self.hatari) 305: self.debugui.show() 306: 307: # ------- trace callback ----------- 308: def trace(self, widget): 309: if not self.tracedialog: 310: self.tracedialog = TraceDialog(self.mainwin) 311: self.tracepoints = self.tracedialog.run(self.hatari, self.tracepoints) 312: 313: # ------ snapshot load/save callbacks --------- 314: def load(self, widget): 315: path = os.path.expanduser("~/.hatari/hatari.sav") 316: filename = get_open_filename("Select snapshot", self.mainwin, path) 317: if filename: 318: self.memstate = ["--memstate", filename] 319: self.run() 320: return True 321: return False 322: 323: def save(self, widget): 324: self.hatari.trigger_shortcut("savemem") 325: 326: # ------ config load/save callbacks --------- 327: def config_load(self, widget): 328: path = self.config.get_path() 329: filename = get_open_filename("Select configuration file", self.mainwin, path) 330: if filename: 331: self.hatari.change_option("--configfile %s" % filename) 332: self.config.load(filename) 333: self._reset_config_dialogs() 334: return True 335: return False 336: 337: def config_save(self, widget): 338: path = self.config.get_path() 339: filename = get_save_filename("Save configuration as...", self.mainwin, path) 340: if filename: 341: self.config.save_as(filename) 342: return True 343: return False 344: 345: # ------- fast forward callback ----------- 346: def set_fastforward(self, widget): 347: self.config.set_fastforward(widget.get_active()) 348: 349: def get_fastforward(self): 350: return self.config.get_fastforward() 351: 352: # ------- fullscreen callback ----------- 353: def set_fullscreen(self, widget): 354: # if user can select this, Hatari isn't in fullscreen 355: self.hatari.change_option("--fullscreen") 356: 357: # ------- screenshot callback ----------- 358: def screenshot(self, widget): 359: self.hatari.trigger_shortcut("screenshot") 360: 361: # ------- record callbacks ----------- 362: def recanim(self, widget): 363: self.hatari.trigger_shortcut("recanim") 364: 365: def recsound(self, widget): 366: self.hatari.trigger_shortcut("recsound") 367: 368: # ------- insert key special callback ----------- 369: def keypress(self, widget, code): 370: self.hatari.insert_event("keypress %s" % code) 371: 372: def textinsert(self, widget, text): 373: HatariTextInsert(self.hatari, text) 374: 375: # ------- panel callback ----------- 376: def panel(self, action, box): 377: title = action.get_name() 378: if title not in self.panels: 379: window = gtk.Window(gtk.WINDOW_TOPLEVEL) 380: window.set_transient_for(self.mainwin) 381: window.set_icon_from_file(UInfo.icon) 382: window.set_title(title) 383: window.add(box) 384: window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) 385: window.connect("delete_event", window_hide_cb) 386: self.panels[title] = window 387: else: 388: window = self.panels[title] 389: window.show_all() 390: window.deiconify() 391: 392: 393: # --------------------------------------------------------------- 394: # class for creating menus, toolbars and panels 395: # and managing actions bound to them 396: class UIActions: 397: def __init__(self): 398: cb = self.callbacks = UICallbacks() 399: 400: self.help = UIHelp() 401: 402: self.actions = gtk.ActionGroup("All") 403: 404: # name, icon ID, label, accel, tooltip, callback 405: self.actions.add_toggle_actions(( 406: # TODO: how to know when these are changed from inside Hatari? 407: ("recanim", gtk.STOCK_MEDIA_RECORD, "Record animation", "<Ctrl>A", "Record animation", cb.recanim), 408: ("recsound", gtk.STOCK_MEDIA_RECORD, "Record sound", "<Ctrl>W", "Record YM/Wav", cb.recsound), 409: ("pause", gtk.STOCK_MEDIA_PAUSE, "Pause", "<Ctrl>P", "Pause Hatari to save battery", cb.pause), 410: ("forward", gtk.STOCK_MEDIA_FORWARD, "Forward", "<Ctrl>F", "Whether to fast forward Hatari (needs fast machine)", cb.set_fastforward, cb.get_fastforward()) 411: )) 412: 413: # name, icon ID, label, accel, tooltip, callback 414: self.actions.add_actions(( 415: ("load", gtk.STOCK_OPEN, "Load snapshot...", "<Ctrl>L", "Load emulation snapshot", cb.load), 416: ("save", gtk.STOCK_SAVE, "Save snapshot", "<Ctrl>S", "Save emulation snapshot", cb.save), 417: ("shot", gtk.STOCK_MEDIA_RECORD, "Grab screenshot", "<Ctrl>G", "Grab a screenshot", cb.screenshot), 418: ("quit", gtk.STOCK_QUIT, "Quit", "<Ctrl>Q", "Quit Hatari UI", cb.quit), 419: 420: ("run", gtk.STOCK_MEDIA_PLAY, "Run", "<Ctrl>R", "(Re-)run Hatari", cb.run), 421: ("full", gtk.STOCK_FULLSCREEN, "Fullscreen", "<Ctrl>U", "Toggle whether Hatari is fullscreen", cb.set_fullscreen), 422: ("input", gtk.STOCK_SPELL_CHECK, "Inputs...", "<Ctrl>N", "Simulate text input and mouse clicks", cb.inputs), 423: ("reset", gtk.STOCK_REFRESH, "Reset...", "<Ctrl>E", "Warm or cold reset Hatari", cb.reset), 424: 425: ("display", gtk.STOCK_PREFERENCES, "Display...", "<Ctrl>Y", "Display settings", cb.display), 1.1.1.2 root 426: ("floppy", gtk.STOCK_FLOPPY, "Floppies...", "<Ctrl>D", "Floppy images", cb.floppydisk), 427: ("harddisk", gtk.STOCK_HARDDISK, "Hard disks...", "<Ctrl>H", "Hard disk images and directories", cb.harddisk), 1.1 root 428: ("joystick", gtk.STOCK_CONNECT, "Joysticks...", "<Ctrl>J", "Joystick settings", cb.joystick), 429: ("machine", gtk.STOCK_HARDDISK, "Machine...", "<Ctrl>M", "Hatari st/e/tt/falcon configuration", cb.machine), 430: ("device", gtk.STOCK_PRINT, "Peripherals...", "<Ctrl>V", "Toggle Midi, Printer, RS232 peripherals", cb.peripheral), 431: ("sound", gtk.STOCK_PROPERTIES, "Sound...", "<Ctrl>O", "Sound settings", cb.sound), 432: 433: ("path", gtk.STOCK_DIRECTORY, "Paths...", None, "Device & save file paths", cb.path), 434: ("lconfig", gtk.STOCK_OPEN, "Load config...", "<Ctrl>C", "Load configuration", self.config_load), 435: ("sconfig", gtk.STOCK_SAVE_AS, "Save config as...", None, "Save configuration", cb.config_save), 436: 437: ("debug", gtk.STOCK_FIND, "Debugger...", "<Ctrl>B", "Activate Hatari debugger", cb.debugger), 438: ("trace", gtk.STOCK_EXECUTE, "Trace settings...", "<Ctrl>T", "Hatari tracing setup", cb.trace), 439: 440: ("manual", None, "Hatari manual", None, None, self.help.view_hatari_manual), 441: ("compatibility", None, "Hatari compatibility list", None, None, self.help.view_hatari_compatibility), 442: ("release", None, "Hatari release notes", None, None, self.help.view_hatari_releasenotes), 443: ("todo", None, "Hatari TODO", None, None, self.help.view_hatari_todo), 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): 1.1.1.3 root 460: print("TODO: reset toggle actions") 1.1 root 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") 1.1.1.6 root 522: yield (">", "Start next toolbar row in panel windows") 1.1 root 523: # generate the list from action information 524: for act in self.actions.list_actions(): 1.1.1.2 root 525: note = act.get_property("tooltip") 526: if not note: 527: note = act.get_property("label") 528: yield(act.get_name(), note) 1.1 root 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: 1.1.1.3 root 540: return None 1.1 root 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 1.1.1.3 root 552: widget.set_tooltip_text("Insert " + tip) 553: return widget 1.1 root 554: 555: def _get_container(self, actions, horiz = True): 556: "return Gtk container with the specified actions or None for no actions" 557: if not actions: 558: return None 559: 1.1.1.3 root 560: #print("ACTIONS:", actions) 1.1 root 561: if len(actions) > 1: 562: bar = gtk.Toolbar() 563: if horiz: 564: bar.set_orientation(gtk.ORIENTATION_HORIZONTAL) 565: else: 566: bar.set_orientation(gtk.ORIENTATION_VERTICAL) 567: bar.set_style(gtk.TOOLBAR_BOTH) 568: # disable overflow menu to get toolbar sized correctly for panels 569: bar.set_show_arrow(False) 570: else: 571: bar = None 572: 573: for action in actions: 1.1.1.3 root 574: #print(action) 1.1 root 575: offset = action.find("=") 576: if offset >= 0: 577: # handle "<name>=<keycode>" action specification 578: name = action[:offset] 579: text = action[offset+1:] 1.1.1.3 root 580: widget = self._create_key_control(name, text) 1.1 root 581: elif action == "|": 582: widget = gtk.SeparatorToolItem() 583: elif action == "close": 584: if bar: 585: widget = create_toolbutton(gtk.STOCK_CLOSE, self._close_cb) 586: else: 587: widget = create_button("Close", self._close_cb) 588: else: 589: widget = self.actions.get_action(action).create_tool_item() 590: if not widget: 591: continue 592: if bar: 593: if action != "|": 594: widget.set_expand(True) 595: bar.insert(widget, -1) 596: if bar: 597: return bar 598: return widget 599: 600: # ------------- handling menu ------------- 601: def _add_submenu(self, bar, title, items): 602: submenu = gtk.Menu() 603: for name in items: 604: if name: 605: action = self.actions.get_action(name) 606: item = action.create_menu_item() 607: else: 608: item = gtk.SeparatorMenuItem() 609: submenu.add(item) 610: baritem = gtk.MenuItem(title, False) 611: baritem.set_submenu(submenu) 612: bar.add(baritem) 613: 614: def _get_menu(self): 615: allmenus = ( 616: ("File", ("load", "save", None, "shot", "recanim", "recsound", None, "quit")), 617: ("Emulation", ("run", "pause", "forward", None, "full", None, "input", None, "reset")), 1.1.1.2 root 618: ("Devices", ("display", "floppy", "harddisk", "joystick", "machine", "device", "sound")), 1.1 root 619: ("Configuration", ("path", None, "lconfig", "sconfig")), 620: ("Debug", ("debug", "trace")), 1.1.1.4 root 621: ("Help", ("manual", "compatibility", "release", "todo", None, "mails", "changes", None, "authors", "hatari", "hatariui", "about",)) 1.1 root 622: ) 623: bar = gtk.MenuBar() 624: 625: for title, items in allmenus: 626: self._add_submenu(bar, title, items) 627: 628: if self.panels: 629: self._add_submenu(bar, "Panels", self.panels) 630: 631: return bar 632: 633: # ------------- run the whole UI ------------- 634: def run(self, floppy, havemenu, fullscreen, embed): 635: accelgroup = None 636: # create menu? 637: if havemenu: 638: # this would steal keys from embedded Hatari 639: if not embed: 640: accelgroup = gtk.AccelGroup() 641: for action in self.actions.list_actions(): 642: action.set_accel_group(accelgroup) 643: menu = self._get_menu() 644: else: 645: menu = None 646: 647: # create toolbars 648: toolbars = { "left":None, "right":None, "top":None, "bottom":None} 649: for side in ("left", "right"): 650: if side in self.toolbars: 651: toolbars[side] = self._get_container(self.toolbars[side], False) 652: for side in ("top", "bottom"): 653: if side in self.toolbars: 654: toolbars[side] = self._get_container(self.toolbars[side], True) 655: 656: self.callbacks.create_ui(accelgroup, menu, toolbars, fullscreen, embed) 657: self.help.set_mainwin(self.callbacks.mainwin) 658: self.callbacks.set_floppy(floppy) 659: 660: # ugly, Hatari socket window ID can be gotten only 661: # after Socket window is realized by gtk_main() 662: gobject.idle_add(self.callbacks.run) 663: gtk.main() 664: 665: 666: # ------------- usage / argument handling -------------- 667: def usage(actions, msg=None): 668: name = os.path.basename(sys.argv[0]) 669: uiname = "%s %s" % (UInfo.name, UInfo.version) 1.1.1.3 root 670: print("\n%s" % uiname) 671: print("=" * len(uiname)) 672: print("\nUsage: %s [options] [directory|disk image|Atari program]" % name) 673: print("\nOptions:") 674: print("\t-h, --help\t\tthis help") 675: print("\t-n, --nomenu\t\tomit menus") 676: print("\t-e, --embed\t\tembed Hatari window in middle of controls") 677: print("\t-f, --fullscreen\tstart in fullscreen") 678: print("\t-l, --left <controls>\ttoolbar at left") 679: print("\t-r, --right <controls>\ttoolbar at right") 680: print("\t-t, --top <controls>\ttoolbar at top") 681: print("\t-b, --bottom <controls>\ttoolbar at bottom") 682: print("\t-p, --panel <name>,<controls>") 683: print("\t\t\t\tseparate window with given name and controls") 684: print("\nAvailable (panel/toolbar) controls:") 1.1 root 685: for action, description in actions.list_actions(): 686: size = len(action) 687: if size < 8: 688: tabs = "\t\t" 689: elif size < 16: 690: tabs = "\t" 691: else: 692: tabs = "\n\t\t\t" 1.1.1.3 root 693: print("\t%s%s%s" % (action, tabs, description)) 694: print(""" 1.1 root 695: You can have as many panels as you wish. For each panel you need to add 696: a control with the name of the panel (see "MyPanel" below). 697: 698: For example: 699: \t%s --embed \\ 700: \t-t "about,run,pause,quit" \\ 701: \t-p "MyPanel,Macro=Test,Undo=97,Help=98,>,F1=59,F2=60,F3=61,F4=62,>,close" \\ 702: \t-r "paste,debug,trace,machine,MyPanel" \\ 703: \t-b "sound,|,fastforward,|,fullscreen" 704: 705: if no options are given, the UI uses basic controls. 1.1.1.3 root 706: """ % name) 1.1 root 707: if msg: 1.1.1.3 root 708: print("ERROR: %s\n" % msg) 1.1 root 709: sys.exit(1) 710: 711: 712: def main(): 713: info = UInfo() 714: actions = UIActions() 715: try: 716: longopts = ["embed", "fullscreen", "nomenu", "help", 717: "left=", "right=", "top=", "bottom=", "panel="] 718: opts, floppies = getopt.getopt(sys.argv[1:], "efnhl:r:t:b:p:", longopts) 719: del longopts 1.1.1.3 root 720: except getopt.GetoptError as err: 1.1 root 721: usage(actions, err) 722: 723: menu = True 724: embed = False 725: fullscreen = False 726: 727: error = None 728: for opt, arg in opts: 1.1.1.3 root 729: print(opt, arg) 1.1 root 730: if opt in ("-e", "--embed"): 731: embed = True 732: elif opt in ("-f", "--fullscreen"): 733: fullscreen = True 734: elif opt in ("-n", "--nomenu"): 735: menu = False 736: elif opt in ("-h", "--help"): 737: usage(actions) 738: elif opt in ("-l", "--left"): 739: error = actions.set_actions(arg, "left") 740: elif opt in ("-r", "--right"): 741: error = actions.set_actions(arg, "right") 742: elif opt in ("-t", "--top"): 743: error = actions.set_actions(arg, "top") 744: elif opt in ("-b", "--bottom"): 745: error = actions.set_actions(arg, "bottom") 746: elif opt in ("-p", "--panel"): 747: error = actions.add_panel(arg) 748: else: 749: assert False, "getopt returned unhandled option" 750: if error: 751: usage(actions, error) 752: 753: if len(floppies) > 1: 754: usage(actions, "multiple floppy images given: %s" % str(floppies)) 755: if floppies: 756: if not os.path.exists(floppies[0]): 757: usage(actions, "floppy image '%s' doesn't exist" % floppies[0]) 758: 759: actions.run(floppies, menu, fullscreen, embed) 760: 761: 762: if __name__ == "__main__": 763: main()
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.