Annotation of hatari/python-ui/hatariui.py, revision 1.1

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.