Annotation of previous/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-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()

unix.superglobalmegacorp.com

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