|
|
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: #
1.1.1.3 ! root 7: # Copyright (C) 2008-2011 by Eero Tamminen <eerot at berlios>
1.1 root 8: #
9: # This program is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # This program is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18:
19: import os
20: import sys
21: import getopt
22:
23: # use correct version of pygtk/gtk
24: import pygtk
25: pygtk.require('2.0')
26: import gtk
27: import gobject
28:
29: from debugui import HatariDebugUI
30: from hatari import Hatari, HatariConfigMapping
31: from uihelpers import UInfo, UIHelp, create_button, create_toolbutton, \
32: create_toggle, HatariTextInsert, get_open_filename, get_save_filename
1.1.1.2 root 33: from dialogs import AboutDialog, TodoDialog, NoteDialog, ErrorDialog, \
34: InputDialog, KillDialog, QuitSaveDialog, ResetDialog, TraceDialog, \
35: FloppyDialog, HardDiskDialog, DisplayDialog, JoystickDialog, \
36: MachineDialog, PeripheralDialog, PathDialog, SoundDialog
1.1 root 37:
38:
39: # helper functions to match callback args
40: def window_hide_cb(window, arg):
41: window.hide()
42: return True
43:
44:
45: # ---------------------------------------------------------------
46: # Class with Hatari and configuration instances which methods are
47: # called to change those (with additional dialogs or directly).
48: # Owns the application window and socket widget embedding Hatari.
49: class UICallbacks:
50: tmpconfpath = os.path.expanduser("~/.hatari/.tmp.cfg")
51: def __init__(self):
52: # Hatari and configuration
53: self.hatari = Hatari()
1.1.1.2 root 54: error = self.hatari.is_compatible()
55: if error:
56: ErrorDialog(None).run(error)
57: sys.exit(-1)
58:
1.1 root 59: self.config = HatariConfigMapping(self.hatari)
1.1.1.2 root 60: try:
61: self.config.validate()
62: except (KeyError, AttributeError):
63: NoteDialog(None).run("Loading Hatari configuration failed!\nRetrying after saving Hatari configuration.")
64: self.hatari.save_config()
65: self.config = HatariConfigMapping(self.hatari)
66: self.config.validate()
1.1 root 67:
68: # windows are created when needed
69: self.mainwin = None
70: self.hatariwin = None
71: self.debugui = None
72: self.panels = {}
73:
74: # dialogs are created when needed
75: self.aboutdialog = None
76: self.inputdialog = None
77: self.tracedialog = None
78: self.resetdialog = None
79: self.quitdialog = None
80: self.killdialog = None
81:
1.1.1.2 root 82: self.floppydialog = None
83: self.harddiskdialog = None
1.1 root 84: self.displaydialog = None
85: self.joystickdialog = None
86: self.machinedialog = None
87: self.peripheraldialog = None
88: self.sounddialog = None
89: self.pathdialog = None
90:
91: # used by run()
92: self.memstate = None
93: self.floppy = None
94: self.io_id = None
95:
96: # TODO: Hatari UI own configuration settings save/load
97: self.tracepoints = None
98:
99: def _reset_config_dialogs(self):
1.1.1.2 root 100: self.floppydialog = None
101: self.harddiskdialog = None
1.1 root 102: self.displaydialog = None
103: self.joystickdialog = None
104: self.machinedialog = None
105: self.peripheraldialog = None
106: self.sounddialog = None
107: self.pathdialog = None
108:
109: # ---------- create UI ----------------
110: def create_ui(self, accelgroup, menu, toolbars, fullscreen, embed):
111: "create_ui(menu, toolbars, fullscreen, embed)"
112: # add horizontal elements
113: hbox = gtk.HBox()
114: if toolbars["left"]:
115: hbox.pack_start(toolbars["left"], False, True)
116: if embed:
117: self._add_uisocket(hbox)
118: if toolbars["right"]:
119: hbox.pack_start(toolbars["right"], False, True)
120: # add vertical elements
121: vbox = gtk.VBox()
122: if menu:
123: vbox.add(menu)
124: if toolbars["top"]:
125: vbox.pack_start(toolbars["top"], False, True)
126: vbox.add(hbox)
127: if toolbars["bottom"]:
128: vbox.pack_start(toolbars["bottom"], False, True)
129: # put them to main window
130: mainwin = gtk.Window(gtk.WINDOW_TOPLEVEL)
131: mainwin.set_title("%s %s" % (UInfo.name, UInfo.version))
132: mainwin.set_icon_from_file(UInfo.icon)
133: if accelgroup:
134: mainwin.add_accel_group(accelgroup)
135: if fullscreen:
136: mainwin.fullscreen()
137: mainwin.add(vbox)
138: mainwin.show_all()
139: # for run and quit callbacks
140: self.killdialog = KillDialog(mainwin)
141: mainwin.connect("delete_event", self.quit)
142: self.mainwin = mainwin
143:
144: def _add_uisocket(self, box):
145: # add Hatari parent container to given box
146: socket = gtk.Socket()
147: # without this, closing Hatari would remove the socket widget
148: socket.connect("plug-removed", lambda obj: True)
149: socket.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
150: socket.set_events(gtk.gdk.ALL_EVENTS_MASK)
151: socket.set_flags(gtk.CAN_FOCUS)
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()
1.1.1.3 ! root 166: print("New size = %d x %d" % (width, height))
1.1 root 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
1.1.1.2 root 187: # only way to change boot order is to specify disk on command line
1.1 root 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:
1.1.1.2 root 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)
1.1 root 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:
370: def textinsert(self, widget, text):
371: HatariTextInsert(self.hatari, text)
372:
373: # ------- panel callback -----------
374: def panel(self, action, box):
375: title = action.get_name()
376: if title not in self.panels:
377: window = gtk.Window(gtk.WINDOW_TOPLEVEL)
378: window.set_transient_for(self.mainwin)
379: window.set_icon_from_file(UInfo.icon)
380: window.set_title(title)
381: window.add(box)
382: window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
383: window.connect("delete_event", window_hide_cb)
384: self.panels[title] = window
385: else:
386: window = self.panels[title]
387: window.show_all()
388: window.deiconify()
389:
390:
391: # ---------------------------------------------------------------
392: # class for creating menus, toolbars and panels
393: # and managing actions bound to them
394: class UIActions:
395: def __init__(self):
396: cb = self.callbacks = UICallbacks()
397:
398: self.help = UIHelp()
399:
400: self.actions = gtk.ActionGroup("All")
401:
402: # name, icon ID, label, accel, tooltip, callback
403: self.actions.add_toggle_actions((
404: # TODO: how to know when these are changed from inside Hatari?
405: ("recanim", gtk.STOCK_MEDIA_RECORD, "Record animation", "<Ctrl>A", "Record animation", cb.recanim),
406: ("recsound", gtk.STOCK_MEDIA_RECORD, "Record sound", "<Ctrl>W", "Record YM/Wav", cb.recsound),
407: ("pause", gtk.STOCK_MEDIA_PAUSE, "Pause", "<Ctrl>P", "Pause Hatari to save battery", cb.pause),
408: ("forward", gtk.STOCK_MEDIA_FORWARD, "Forward", "<Ctrl>F", "Whether to fast forward Hatari (needs fast machine)", cb.set_fastforward, cb.get_fastforward())
409: ))
410:
411: # name, icon ID, label, accel, tooltip, callback
412: self.actions.add_actions((
413: ("load", gtk.STOCK_OPEN, "Load snapshot...", "<Ctrl>L", "Load emulation snapshot", cb.load),
414: ("save", gtk.STOCK_SAVE, "Save snapshot", "<Ctrl>S", "Save emulation snapshot", cb.save),
415: ("shot", gtk.STOCK_MEDIA_RECORD, "Grab screenshot", "<Ctrl>G", "Grab a screenshot", cb.screenshot),
416: ("quit", gtk.STOCK_QUIT, "Quit", "<Ctrl>Q", "Quit Hatari UI", cb.quit),
417:
418: ("run", gtk.STOCK_MEDIA_PLAY, "Run", "<Ctrl>R", "(Re-)run Hatari", cb.run),
419: ("full", gtk.STOCK_FULLSCREEN, "Fullscreen", "<Ctrl>U", "Toggle whether Hatari is fullscreen", cb.set_fullscreen),
420: ("input", gtk.STOCK_SPELL_CHECK, "Inputs...", "<Ctrl>N", "Simulate text input and mouse clicks", cb.inputs),
421: ("reset", gtk.STOCK_REFRESH, "Reset...", "<Ctrl>E", "Warm or cold reset Hatari", cb.reset),
422:
423: ("display", gtk.STOCK_PREFERENCES, "Display...", "<Ctrl>Y", "Display settings", cb.display),
1.1.1.2 root 424: ("floppy", gtk.STOCK_FLOPPY, "Floppies...", "<Ctrl>D", "Floppy images", cb.floppydisk),
425: ("harddisk", gtk.STOCK_HARDDISK, "Hard disks...", "<Ctrl>H", "Hard disk images and directories", cb.harddisk),
1.1 root 426: ("joystick", gtk.STOCK_CONNECT, "Joysticks...", "<Ctrl>J", "Joystick settings", cb.joystick),
427: ("machine", gtk.STOCK_HARDDISK, "Machine...", "<Ctrl>M", "Hatari st/e/tt/falcon configuration", cb.machine),
428: ("device", gtk.STOCK_PRINT, "Peripherals...", "<Ctrl>V", "Toggle Midi, Printer, RS232 peripherals", cb.peripheral),
429: ("sound", gtk.STOCK_PROPERTIES, "Sound...", "<Ctrl>O", "Sound settings", cb.sound),
430:
431: ("path", gtk.STOCK_DIRECTORY, "Paths...", None, "Device & save file paths", cb.path),
432: ("lconfig", gtk.STOCK_OPEN, "Load config...", "<Ctrl>C", "Load configuration", self.config_load),
433: ("sconfig", gtk.STOCK_SAVE_AS, "Save config as...", None, "Save configuration", cb.config_save),
434:
435: ("debug", gtk.STOCK_FIND, "Debugger...", "<Ctrl>B", "Activate Hatari debugger", cb.debugger),
436: ("trace", gtk.STOCK_EXECUTE, "Trace settings...", "<Ctrl>T", "Hatari tracing setup", cb.trace),
437:
438: ("manual", None, "Hatari manual", None, None, self.help.view_hatari_manual),
439: ("compatibility", None, "Hatari compatibility list", None, None, self.help.view_hatari_compatibility),
440: ("release", None, "Hatari release notes", None, None, self.help.view_hatari_releasenotes),
441: ("todo", None, "Hatari TODO", None, None, self.help.view_hatari_todo),
442: ("bugs", None, "Report a bug", None, None, self.help.view_hatari_bugs),
443: ("mails", None, "Hatari mailing lists", None, None, self.help.view_hatari_mails),
444: ("changes", None, "Latest Hatari changes", None, None, self.help.view_hatari_repository),
445: ("authors", None, "Hatari authors", None, None, self.help.view_hatari_authors),
446: ("hatari", None, "Hatari home page", None, None, self.help.view_hatari_page),
447: ("hatariui", None, "Hatari UI home page", None, None, self.help.view_hatariui_page),
448: ("about", gtk.STOCK_INFO, "Hatari UI info", "<Ctrl>I", "Hatari UI information", cb.about)
449: ))
450: self.action_names = [x.get_name() for x in self.actions.list_actions()]
451:
452: # no actions set yet to panels or toolbars
453: self.toolbars = {}
454: self.panels = []
455:
456: def config_load(self, widget):
457: # user loads a new configuration?
458: if self.callbacks.config_load(widget):
1.1.1.3 ! root 459: print("TODO: reset toggle actions")
1.1 root 460:
461: # ----- toolbar / panel additions ---------
462: def set_actions(self, action_str, place):
463: "set_actions(actions,place) -> error string, None if all OK"
464: actions = action_str.split(",")
465: for action in actions:
466: if action in self.action_names:
467: # regular action
468: continue
469: if action in self.panels:
470: # user specified panel
471: continue
472: if action in ("close", ">"):
473: if place != "panel":
474: return "'close' and '>' can be only in a panel"
475: continue
476: if action == "|":
477: # divider
478: continue
479: if action.find("=") >= 0:
480: # special keycode/string action
481: continue
482: return "unrecognized action '%s'" % action
483:
484: if place in ("left", "right", "top", "bottom"):
485: self.toolbars[place] = actions
486: return None
487: if place == "panel":
488: if len(actions) < 3:
489: return "panel has too few items to be useful"
490: return None
491: return "unknown actions position '%s'" % place
492:
493: def add_panel(self, spec):
494: "add_panel(panel_specification) -> error string, None if all is OK"
495: offset = spec.find(",")
496: if offset <= 0:
497: return "invalid panel specification '%s'" % spec
498:
499: name, panelcontrols = spec[:offset], spec[offset+1:]
500: error = self.set_actions(panelcontrols, "panel")
501: if error:
502: return error
503:
504: if ",>," in panelcontrols:
505: box = gtk.VBox()
506: splitcontrols = panelcontrols.split(",>,")
507: for controls in splitcontrols:
508: box.add(self._get_container(controls.split(",")))
509: else:
510: box = self._get_container(panelcontrols.split(","))
511:
512: self.panels.append(name)
513: self.actions.add_actions(
514: ((name, gtk.STOCK_ADD, name, None, name, self.callbacks.panel),),
515: box
516: )
517: return None
518:
519: def list_actions(self):
520: yield ("|", "Separator between controls")
521: yield (">", "Next toolbar in panel windows")
522: # generate the list from action information
523: for act in self.actions.list_actions():
1.1.1.2 root 524: note = act.get_property("tooltip")
525: if not note:
526: note = act.get_property("label")
527: yield(act.get_name(), note)
1.1 root 528: yield ("<panel name>", "Button for the specified panel window")
529: yield ("<name>=<string/code>", "Synthetize string or single key <code>")
530:
531: # ------- panel special actions -----------
532: def _close_cb(self, widget):
533: widget.get_toplevel().hide()
534:
535: # ------- key special action -----------
536: def _create_key_control(self, name, textcode):
537: "Simulate Atari key press/release and string inserting"
538: if not textcode:
1.1.1.3 ! root 539: return None
1.1 root 540: widget = gtk.ToolButton(gtk.STOCK_PASTE)
541: widget.set_label(name)
542: try:
543: # part after "=" converts to an int?
544: code = int(textcode, 0)
545: widget.connect("clicked", self.callbacks.keypress, code)
546: tip = "keycode: %d" % code
547: except ValueError:
548: # no, assume a string macro is wanted instead
549: widget.connect("clicked", self.callbacks.textinsert, textcode)
550: tip = "string '%s'" % textcode
1.1.1.3 ! root 551: widget.set_tooltip_text("Insert " + tip)
! 552: return widget
1.1 root 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:
1.1.1.3 ! root 559: #print("ACTIONS:", actions)
1.1 root 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: else:
570: bar = None
571:
572: for action in actions:
1.1.1.3 ! root 573: #print(action)
1.1 root 574: offset = action.find("=")
575: if offset >= 0:
576: # handle "<name>=<keycode>" action specification
577: name = action[:offset]
578: text = action[offset+1:]
1.1.1.3 ! root 579: widget = self._create_key_control(name, text)
1.1 root 580: elif action == "|":
581: widget = gtk.SeparatorToolItem()
582: elif action == "close":
583: if bar:
584: widget = create_toolbutton(gtk.STOCK_CLOSE, self._close_cb)
585: else:
586: widget = create_button("Close", self._close_cb)
587: else:
588: widget = self.actions.get_action(action).create_tool_item()
589: if not widget:
590: continue
591: if bar:
592: if action != "|":
593: widget.set_expand(True)
594: bar.insert(widget, -1)
595: if bar:
596: return bar
597: return widget
598:
599: # ------------- handling menu -------------
600: def _add_submenu(self, bar, title, items):
601: submenu = gtk.Menu()
602: for name in items:
603: if name:
604: action = self.actions.get_action(name)
605: item = action.create_menu_item()
606: else:
607: item = gtk.SeparatorMenuItem()
608: submenu.add(item)
609: baritem = gtk.MenuItem(title, False)
610: baritem.set_submenu(submenu)
611: bar.add(baritem)
612:
613: def _get_menu(self):
614: allmenus = (
615: ("File", ("load", "save", None, "shot", "recanim", "recsound", None, "quit")),
616: ("Emulation", ("run", "pause", "forward", None, "full", None, "input", None, "reset")),
1.1.1.2 root 617: ("Devices", ("display", "floppy", "harddisk", "joystick", "machine", "device", "sound")),
1.1 root 618: ("Configuration", ("path", None, "lconfig", "sconfig")),
619: ("Debug", ("debug", "trace")),
620: ("Help", ("manual", "compatibility", "release", "todo", None, "bugs", "mails", "changes", None, "authors", "hatari", "hatariui", "about",))
621: )
622: bar = gtk.MenuBar()
623:
624: for title, items in allmenus:
625: self._add_submenu(bar, title, items)
626:
627: if self.panels:
628: self._add_submenu(bar, "Panels", self.panels)
629:
630: return bar
631:
632: # ------------- run the whole UI -------------
633: def run(self, floppy, havemenu, fullscreen, embed):
634: accelgroup = None
635: # create menu?
636: if havemenu:
637: # this would steal keys from embedded Hatari
638: if not embed:
639: accelgroup = gtk.AccelGroup()
640: for action in self.actions.list_actions():
641: action.set_accel_group(accelgroup)
642: menu = self._get_menu()
643: else:
644: menu = None
645:
646: # create toolbars
647: toolbars = { "left":None, "right":None, "top":None, "bottom":None}
648: for side in ("left", "right"):
649: if side in self.toolbars:
650: toolbars[side] = self._get_container(self.toolbars[side], False)
651: for side in ("top", "bottom"):
652: if side in self.toolbars:
653: toolbars[side] = self._get_container(self.toolbars[side], True)
654:
655: self.callbacks.create_ui(accelgroup, menu, toolbars, fullscreen, embed)
656: self.help.set_mainwin(self.callbacks.mainwin)
657: self.callbacks.set_floppy(floppy)
658:
659: # ugly, Hatari socket window ID can be gotten only
660: # after Socket window is realized by gtk_main()
661: gobject.idle_add(self.callbacks.run)
662: gtk.main()
663:
664:
665: # ------------- usage / argument handling --------------
666: def usage(actions, msg=None):
667: name = os.path.basename(sys.argv[0])
668: uiname = "%s %s" % (UInfo.name, UInfo.version)
1.1.1.3 ! root 669: print("\n%s" % uiname)
! 670: print("=" * len(uiname))
! 671: print("\nUsage: %s [options] [directory|disk image|Atari program]" % name)
! 672: print("\nOptions:")
! 673: print("\t-h, --help\t\tthis help")
! 674: print("\t-n, --nomenu\t\tomit menus")
! 675: print("\t-e, --embed\t\tembed Hatari window in middle of controls")
! 676: print("\t-f, --fullscreen\tstart in fullscreen")
! 677: print("\t-l, --left <controls>\ttoolbar at left")
! 678: print("\t-r, --right <controls>\ttoolbar at right")
! 679: print("\t-t, --top <controls>\ttoolbar at top")
! 680: print("\t-b, --bottom <controls>\ttoolbar at bottom")
! 681: print("\t-p, --panel <name>,<controls>")
! 682: print("\t\t\t\tseparate window with given name and controls")
! 683: print("\nAvailable (panel/toolbar) controls:")
1.1 root 684: for action, description in actions.list_actions():
685: size = len(action)
686: if size < 8:
687: tabs = "\t\t"
688: elif size < 16:
689: tabs = "\t"
690: else:
691: tabs = "\n\t\t\t"
1.1.1.3 ! root 692: print("\t%s%s%s" % (action, tabs, description))
! 693: print("""
1.1 root 694: You can have as many panels as you wish. For each panel you need to add
695: a control with the name of the panel (see "MyPanel" below).
696:
697: For example:
698: \t%s --embed \\
699: \t-t "about,run,pause,quit" \\
700: \t-p "MyPanel,Macro=Test,Undo=97,Help=98,>,F1=59,F2=60,F3=61,F4=62,>,close" \\
701: \t-r "paste,debug,trace,machine,MyPanel" \\
702: \t-b "sound,|,fastforward,|,fullscreen"
703:
704: if no options are given, the UI uses basic controls.
1.1.1.3 ! root 705: """ % name)
1.1 root 706: if msg:
1.1.1.3 ! root 707: print("ERROR: %s\n" % msg)
1.1 root 708: sys.exit(1)
709:
710:
711: def main():
712: info = UInfo()
713: actions = UIActions()
714: try:
715: longopts = ["embed", "fullscreen", "nomenu", "help",
716: "left=", "right=", "top=", "bottom=", "panel="]
717: opts, floppies = getopt.getopt(sys.argv[1:], "efnhl:r:t:b:p:", longopts)
718: del longopts
1.1.1.3 ! root 719: except getopt.GetoptError as err:
1.1 root 720: usage(actions, err)
721:
722: menu = True
723: embed = False
724: fullscreen = False
725:
726: error = None
727: for opt, arg in opts:
1.1.1.3 ! root 728: print(opt, arg)
1.1 root 729: if opt in ("-e", "--embed"):
730: embed = True
731: elif opt in ("-f", "--fullscreen"):
732: fullscreen = True
733: elif opt in ("-n", "--nomenu"):
734: menu = False
735: elif opt in ("-h", "--help"):
736: usage(actions)
737: elif opt in ("-l", "--left"):
738: error = actions.set_actions(arg, "left")
739: elif opt in ("-r", "--right"):
740: error = actions.set_actions(arg, "right")
741: elif opt in ("-t", "--top"):
742: error = actions.set_actions(arg, "top")
743: elif opt in ("-b", "--bottom"):
744: error = actions.set_actions(arg, "bottom")
745: elif opt in ("-p", "--panel"):
746: error = actions.add_panel(arg)
747: else:
748: assert False, "getopt returned unhandled option"
749: if error:
750: usage(actions, error)
751:
752: if len(floppies) > 1:
753: usage(actions, "multiple floppy images given: %s" % str(floppies))
754: if floppies:
755: if not os.path.exists(floppies[0]):
756: usage(actions, "floppy image '%s' doesn't exist" % floppies[0])
757:
758: actions.run(floppies, menu, fullscreen, embed)
759:
760:
761: if __name__ == "__main__":
762: main()
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.