|
|
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()
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.