|
|
1.1 root 1: #!/usr/bin/env python
2: #
3: # Classes for the Hatari UI dialogs
4: #
1.1.1.6 root 5: # Copyright (C) 2008-2012 by Eero Tamminen
1.1 root 6: #
7: # This program is free software; you can redistribute it and/or modify
8: # it under the terms of the GNU General Public License as published by
9: # the Free Software Foundation; either version 2 of the License, or
10: # (at your option) any later version.
11: #
12: # This program is distributed in the hope that it will be useful,
13: # but WITHOUT ANY WARRANTY; without even the implied warranty of
14: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15: # GNU General Public License for more details.
16:
17: import os
18: # use correct version of pygtk/gtk
19: import pygtk
20: pygtk.require('2.0')
21: import gtk
22: import pango
23:
24: from uihelpers import UInfo, HatariTextInsert, create_table_dialog, \
25: table_add_entry_row, table_add_widget_row, table_add_separator, \
1.1.1.4 root 26: table_add_radio_rows, table_set_col_offset, create_button, FselEntry, \
27: FselAndEjectFactory
1.1 root 28:
29:
30: # -----------------
31: # Dialog base class
32:
33: class HatariUIDialog:
34: def __init__(self, parent):
35: "<any>Dialog(parent) -> object"
36: self.parent = parent
37: self.dialog = None
38:
39: def run(self):
40: """run() -> response. Shows dialog and returns response,
41: subclasses overriding run() require also an argument."""
42: response = self.dialog.run()
43: self.dialog.hide()
44: return response
45:
46:
47: # ---------------------------
48: # Note/Todo/Error/Ask dialogs
49:
50: class NoteDialog(HatariUIDialog):
1.1.1.3 root 51: button = gtk.BUTTONS_OK
1.1 root 52: icontype = gtk.MESSAGE_INFO
53: textpattern = "\n%s"
54: def run(self, text):
55: "run(text), show message dialog with given text"
56: dialog = gtk.MessageDialog(self.parent,
57: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1.1.1.3 root 58: self.icontype, self.button, self.textpattern % text)
1.1 root 59: dialog.run()
60: dialog.destroy()
61:
62: class TodoDialog(NoteDialog):
63: textpattern = "\nTODO: %s"
64:
65: class ErrorDialog(NoteDialog):
1.1.1.3 root 66: button = gtk.BUTTONS_CLOSE
1.1 root 67: icontype = gtk.MESSAGE_ERROR
68: textpattern = "\nERROR: %s"
69:
70:
71: class AskDialog(HatariUIDialog):
72: def run(self, text):
73: "run(text) -> bool, show question dialog and return True if user OKed it"
74: dialog = gtk.MessageDialog(self.parent,
75: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
76: gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, text)
77: response = dialog.run()
78: dialog.destroy()
79: return (response == gtk.RESPONSE_YES)
80:
81:
82: # ---------------------------
83: # About dialog
84:
85: class AboutDialog(HatariUIDialog):
86: def __init__(self, parent):
87: dialog = gtk.AboutDialog()
88: dialog.set_transient_for(parent)
89: dialog.set_name(UInfo.name)
90: dialog.set_version(UInfo.version)
1.1.1.5 root 91: dialog.set_website("http://hatari.tuxfamily.org/")
1.1 root 92: dialog.set_website_label("Hatari emulator www-site")
93: dialog.set_authors(["Eero Tamminen"])
94: dialog.set_artists(["The logo is from Hatari"])
95: dialog.set_logo(gtk.gdk.pixbuf_new_from_file(UInfo.logo))
96: dialog.set_translator_credits("translator-credits")
1.1.1.3 root 97: dialog.set_copyright(UInfo.copyright)
1.1 root 98: dialog.set_license("""
1.1.1.2 root 99: This software is licenced under GPL v2 or later.
1.1 root 100:
101: You can see the whole license at:
102: http://www.gnu.org/licenses/info/GPLv2.html""")
103: self.dialog = dialog
104:
105:
106: # ---------------------------
107: # Input dialog
108:
109: class InputDialog(HatariUIDialog):
110: def __init__(self, parent):
111: dialog = gtk.Dialog("Key/mouse input", parent,
112: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
113: ("Close", gtk.RESPONSE_CLOSE))
114:
115: entry = gtk.Entry()
116: entry.connect("activate", self._entry_cb)
117: insert = create_button("Insert", self._entry_cb)
1.1.1.4 root 118: insert.set_tooltip_text("Insert given text to Hatari window")
119: enter = create_button("Enter key", self._enter_cb)
120: enter.set_tooltip_text("Simulate Enter key press")
1.1 root 121:
122: hbox1 = gtk.HBox()
123: hbox1.add(gtk.Label("Text:"))
124: hbox1.add(entry)
125: hbox1.add(insert)
1.1.1.4 root 126: hbox1.add(enter)
1.1 root 127: dialog.vbox.add(hbox1)
128:
1.1.1.4 root 129: rclick = gtk.Button("Right click")
1.1 root 130: rclick.connect("pressed", self._rightpress_cb)
131: rclick.connect("released", self._rightrelease_cb)
1.1.1.4 root 132: rclick.set_tooltip_text("Simulate Atari right button press & release")
133: dclick = create_button("Double click", self._doubleclick_cb)
134: dclick.set_tooltip_text("Simulate Atari left button double-click")
1.1 root 135:
136: hbox2 = gtk.HBox()
137: hbox2.add(dclick)
1.1.1.4 root 138: hbox2.add(rclick)
1.1 root 139: dialog.vbox.add(hbox2)
140:
141: dialog.show_all()
142: self.dialog = dialog
143: self.entry = entry
144:
145: def _entry_cb(self, widget):
146: text = self.entry.get_text()
147: if text:
148: HatariTextInsert(self.hatari, text)
149: self.entry.set_text("")
150:
1.1.1.4 root 151: def _enter_cb(self, widget):
152: self.hatari.insert_event("keypress 28") # Enter key scancode
153:
1.1 root 154: def _doubleclick_cb(self, widget):
155: self.hatari.insert_event("doubleclick")
156:
157: def _rightpress_cb(self, widget):
1.1.1.4 root 158: self.hatari.insert_event("rightdown")
1.1 root 159:
160: def _rightrelease_cb(self, widget):
1.1.1.4 root 161: self.hatari.insert_event("rightup")
1.1 root 162:
163: def run(self, hatari):
164: "run(hatari), do text/mouse click input"
165: self.hatari = hatari
166: self.dialog.run()
167: self.dialog.hide()
168:
169:
170: # ---------------------------
171: # Quit and Save dialog
172:
173: class QuitSaveDialog(HatariUIDialog):
174: def __init__(self, parent):
175: dialog = gtk.Dialog("Quit and Save?", parent,
176: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
177: ("Save changes", gtk.RESPONSE_YES,
178: "Discard changes", gtk.RESPONSE_NO,
179: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
180: dialog.vbox.add(gtk.Label("You have unsaved configuration changes:"))
181: viewport = gtk.Viewport()
182: viewport.add(gtk.Label())
183: scrolledwindow = gtk.ScrolledWindow()
184: scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
185: scrolledwindow.add(viewport)
186: dialog.vbox.add(scrolledwindow)
187: dialog.show_all()
188: self.scrolledwindow = scrolledwindow
189: self.viewport = viewport
190: self.dialog = dialog
191:
192: def run(self, config):
193: "run(config) -> False if canceled, True otherwise or if no changes"
194: changes = []
195: for key, value in config.get_changes():
196: changes.append("%s = %s" % (key, str(value)))
197: if not changes:
198: return True
199: child = self.viewport.get_child()
200: child.set_text(config.get_path() + ":\n" + "\n".join(changes))
201: width, height = child.get_size_request()
202: if height < 320:
203: self.scrolledwindow.set_size_request(width, height)
204: else:
205: self.scrolledwindow.set_size_request(-1, 320)
206: self.viewport.show_all()
207:
208: response = self.dialog.run()
209: self.dialog.hide()
210: if response == gtk.RESPONSE_CANCEL:
211: return False
212: if response == gtk.RESPONSE_YES:
213: config.save()
214: return True
215:
216:
217: # ---------------------------
218: # Kill Hatari dialog
219:
220: class KillDialog(HatariUIDialog):
221: def __init__(self, parent):
222: self.dialog = gtk.MessageDialog(parent,
223: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
224: gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,
225: """\
226: Hatari emulator is already/still running and it needs to be terminated first. However, if it contains unsaved data, that will be lost.
227:
228: Terminate Hatari anyway?""")
229:
230: def run(self, hatari):
231: "run(hatari) -> True if Hatari killed, False if left running"
232: if not hatari.is_running():
233: return True
234: # Hatari is running, OK to kill?
235: response = self.dialog.run()
236: self.dialog.hide()
237: if response == gtk.RESPONSE_OK:
238: hatari.kill()
239: return True
240: return False
241:
242:
243: # ---------------------------
244: # Reset Hatari dialog
245:
246: class ResetDialog(HatariUIDialog):
247: COLD = 1
248: WARM = 2
249: def __init__(self, parent):
250: self.dialog = gtk.Dialog("Reset Atari?", parent,
251: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
252: ("Cold reset", self.COLD, "Warm reset", self.WARM,
253: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
254: label = gtk.Label("\nRebooting will lose changes in currently\nrunning Atari programs.\n\nReset anyway?\n")
255: self.dialog.vbox.add(label)
256: label.show()
257:
258: def run(self, hatari):
259: "run(hatari) -> True if Hatari rebooted, False if canceled"
260: if not hatari.is_running():
261: return False
262: # Hatari is running, how to reboot?
263: response = self.dialog.run()
264: self.dialog.hide()
265: if response == self.COLD:
266: hatari.trigger_shortcut("coldreset")
267: elif response == self.WARM:
268: hatari.trigger_shortcut("warmreset")
269: else:
270: return False
271: return True
272:
273:
274: # ----------------------------------
275: # Floppy image dialog
276:
1.1.1.3 root 277: class FloppyDialog(HatariUIDialog):
1.1 root 278: def _create_dialog(self, config):
1.1.1.3 root 279: table, self.dialog = create_table_dialog(self.parent, "Floppy images", 4, 2)
1.1.1.4 root 280: factory = FselAndEjectFactory()
1.1.1.3 root 281:
1.1 root 282: row = 0
283: self.floppy = []
284: path = config.get_floppydir()
285: for drive in ("A", "B"):
1.1.1.9 ! root 286: label = "Disk %c image:" % drive
1.1.1.4 root 287: fname = config.get_floppy(row)
288: fsel, box = factory.get(label, path, fname, gtk.FILE_CHOOSER_ACTION_OPEN)
1.1 root 289: table_add_widget_row(table, row, label, box)
1.1.1.4 root 290: self.floppy.append(fsel)
1.1 root 291: row += 1
292:
1.1.1.3 root 293: protect = gtk.combo_box_new_text()
294: for text in config.get_protection_types():
295: protect.append_text(text)
296: protect.set_active(config.get_floppy_protection())
1.1.1.4 root 297: protect.set_tooltip_text("Write protect floppy image contents")
1.1.1.3 root 298: table_add_widget_row(table, row, "Write protection:", protect)
1.1.1.9 ! root 299: row += 1
1.1.1.3 root 300:
1.1.1.9 ! root 301: ds = gtk.CheckButton("Double sided drives")
! 302: ds.set_active(config.get_doublesided())
! 303: ds.set_tooltip_text("Whether drives are double or single sided. Can affect behavior of some games")
! 304: table_add_widget_row(table, row, None, ds)
! 305: row += 1
! 306:
! 307: driveb = gtk.CheckButton("Drive B connected")
! 308: driveb.set_active(config.get_floppy_drives()[1])
! 309: driveb.set_tooltip_text("Wheter drive B is connected. Can affect behavior of some demos & games")
! 310: table_add_widget_row(table, row, None, driveb)
1.1.1.3 root 311: row += 1
1.1.1.9 ! root 312:
1.1.1.5 root 313: fastfdc = gtk.CheckButton("Fast floppy access")
314: fastfdc.set_active(config.get_fastfdc())
1.1.1.6 root 315: fastfdc.set_tooltip_text("Can cause incompatibilities with some games/demos")
1.1.1.5 root 316: table_add_widget_row(table, row, None, fastfdc)
1.1.1.3 root 317:
1.1 root 318: table.show_all()
319:
1.1.1.3 root 320: self.protect = protect
1.1.1.5 root 321: self.fastfdc = fastfdc
1.1.1.9 ! root 322: self.driveb = driveb
! 323: self.ds = ds
1.1 root 324:
325: def run(self, config):
326: "run(config), show disk image dialog"
327: if not self.dialog:
328: self._create_dialog(config)
329: response = self.dialog.run()
330: self.dialog.hide()
331:
332: if response == gtk.RESPONSE_APPLY:
333: config.lock_updates()
334: for drive in range(2):
335: config.set_floppy(drive, self.floppy[drive].get_filename())
1.1.1.4 root 336: config.set_floppy_protection(self.protect.get_active())
1.1.1.9 ! root 337: config.set_doublesided(self.ds.get_active())
1.1.1.5 root 338: config.set_fastfdc(self.fastfdc.get_active())
1.1.1.9 ! root 339: drives = (config.get_floppy_drives()[0], self.driveb.get_active())
! 340: config.set_floppy_drives(drives)
1.1 root 341: config.flush_updates()
342:
1.1.1.3 root 343:
344: # ----------------------------------
345: # Hard disk dialog
346:
347: class HardDiskDialog(HatariUIDialog):
348: def _create_dialog(self, config):
349: table, self.dialog = create_table_dialog(self.parent, "Hard disks", 4, 4, "Set and reboot")
1.1.1.4 root 350: factory = FselAndEjectFactory()
1.1.1.3 root 351:
1.1.1.4 root 352: row = 0
1.1.1.3 root 353: label = "ASCI HD image:"
354: path = config.get_acsi_image()
1.1.1.4 root 355: fsel, box = factory.get(label, None, path, gtk.FILE_CHOOSER_ACTION_OPEN)
1.1.1.3 root 356: table_add_widget_row(table, row, label, box, True)
357: self.acsi = fsel
358: row += 1
359:
360: label = "IDE HD master image:"
361: path = config.get_idemaster_image()
1.1.1.4 root 362: fsel, box = factory.get(label, None, path, gtk.FILE_CHOOSER_ACTION_OPEN)
1.1.1.3 root 363: table_add_widget_row(table, row, label, box, True)
364: self.idemaster = fsel
365: row += 1
366:
367: label = "IDE HD slave image:"
368: path = config.get_ideslave_image()
1.1.1.4 root 369: fsel, box = factory.get(label, None, path, gtk.FILE_CHOOSER_ACTION_OPEN)
1.1.1.3 root 370: table_add_widget_row(table, row, label, box, True)
371: self.ideslave = fsel
372: row += 1
373:
374: label = "GEMDOS drive directory:"
375: path = config.get_gemdos_dir()
1.1.1.4 root 376: fsel, box = factory.get(label, None, path, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
1.1.1.3 root 377: table_add_widget_row(table, row, label, box, True)
378: self.gemdos = fsel
379: row += 1
380:
381: protect = gtk.combo_box_new_text()
382: for text in config.get_protection_types():
383: protect.append_text(text)
1.1.1.9 ! root 384: protect.set_tooltip_text("Whether/how to write protect (GEMDOS HD) emulation files, 'auto' means using host files' own properties")
1.1.1.3 root 385: table_add_widget_row(table, row, "Write protection:", protect)
386: self.protect = protect
1.1.1.9 ! root 387: row += 1
! 388:
! 389: lower = gtk.combo_box_new_text()
! 390: for text in config.get_hd_cases():
! 391: lower.append_text(text)
! 392: lower.set_tooltip_text("What to do with names of files created by Atari programs through GEMDOS HD emulation")
! 393: table_add_widget_row(table, row, "File names:", lower)
! 394: self.lower = lower
1.1.1.3 root 395:
396: table.show_all()
1.1 root 397:
1.1.1.3 root 398: def _get_config(self, config):
399: path = config.get_gemdos_dir()
400: if path:
401: self.gemdos.set_filename(path)
402: path = config.get_acsi_image()
403: if path:
404: self.acsi.set_filename(path)
405: path = config.get_idemaster_image()
406: if path:
407: self.idemaster.set_filename(path)
408: path = config.get_ideslave_image()
409: if path:
410: self.ideslave.set_filename(path)
411: self.protect.set_active(config.get_hd_protection())
1.1.1.9 ! root 412: self.lower.set_active(config.get_hd_case())
! 413:
1.1.1.3 root 414: def _set_config(self, config):
415: config.lock_updates()
416: config.set_gemdos_dir(self.gemdos.get_filename())
417: config.set_acsi_image(self.acsi.get_filename())
418: config.set_idemaster_image(self.idemaster.get_filename())
419: config.set_ideslave_image(self.ideslave.get_filename())
420: config.set_hd_protection(self.protect.get_active())
1.1.1.9 ! root 421: config.set_hd_case(self.lower.get_active())
1.1.1.3 root 422: config.flush_updates()
423:
424: def run(self, config):
425: "run(config) -> bool, whether to reboot"
426: if not self.dialog:
427: self._create_dialog(config)
428:
429: self._get_config(config)
430: response = self.dialog.run()
431: self.dialog.hide()
432: if response == gtk.RESPONSE_APPLY:
433: self._set_config(config)
434: return True
435: return False
436:
437:
1.1 root 438: # ---------------------------
439: # Display dialog
440:
441: class DisplayDialog(HatariUIDialog):
442:
443: def _create_dialog(self, config):
444:
445: skip = gtk.combo_box_new_text()
446: for text in config.get_frameskip_names():
447: skip.append_text(text)
448: skip.set_active(config.get_frameskip())
1.1.1.9 ! root 449: skip.set_tooltip_text("Set how many frames are skipped to speed up emulation")
! 450:
! 451: slow = gtk.combo_box_new_text()
! 452: for text in config.get_slowdown_names():
! 453: slow.append_text(text)
! 454: slow.set_active(0)
! 455: slow.set_tooltip_text("VBL wait multiplier to slow down emulation. Breaks sound and large enough slowdown causes mouse clicks not to work.")
1.1 root 456:
1.1.1.3 root 457: maxw, maxh = config.get_max_size()
1.1.1.6 root 458: topw, toph = config.get_desktop_size()
459: maxadjw = gtk.Adjustment(maxw, 320, topw, 8, 40)
460: maxadjh = gtk.Adjustment(maxh, 200, toph, 8, 40)
1.1.1.3 root 461: scalew = gtk.HScale(maxadjw)
462: scaleh = gtk.HScale(maxadjh)
463: scalew.set_digits(0)
464: scaleh.set_digits(0)
1.1.1.4 root 465: scalew.set_tooltip_text("Preferred/maximum zoomed width")
466: scaleh.set_tooltip_text("Preferred/maximum zoomed height")
467:
1.1.1.7 root 468: force_max = gtk.CheckButton("Force max resolution (Falcon)")
469: force_max.set_active(config.get_force_max())
470: force_max.set_tooltip_text("Whether to force maximum resolution to help recording videos of demos which do resolution changes")
471:
472: desktop = gtk.CheckButton("Keep desktop resolution (Falcon/TT)")
1.1.1.4 root 473: desktop.set_active(config.get_desktop())
1.1.1.7 root 474: desktop.set_tooltip_text("Whether to keep screen resolution in fullscreen and (try to) scale Atari screen by an integer factor instead")
475:
476: desktop_st = gtk.CheckButton("Keep desktop resolution (ST/STE)")
477: desktop_st.set_active(config.get_desktop_st())
478: desktop_st.set_tooltip_text("Whether to keep screen resolution in fullscreen to avoid potential sound skips + delay (NO SCALING)")
1.1 root 479:
1.1.1.7 root 480: borders = gtk.CheckButton("Atari screen borders")
1.1 root 481: borders.set_active(config.get_borders())
1.1.1.7 root 482: borders.set_tooltip_text("Whether to show overscan borders in ST/STE low/mid-rez or in Falcon color resolutions. Visible border area is affected by max. zoom size")
1.1 root 483:
1.1.1.3 root 484: statusbar = gtk.CheckButton("Show statusbar")
1.1 root 485: statusbar.set_active(config.get_statusbar())
1.1.1.4 root 486: statusbar.set_tooltip_text("Whether to show statusbar with floppy leds etc")
1.1 root 487:
1.1.1.3 root 488: led = gtk.CheckButton("Show overlay led")
1.1 root 489: led.set_active(config.get_led())
1.1.1.4 root 490: led.set_tooltip_text("Whether to show overlay drive led when statusbar isn't visible")
491:
492: crop = gtk.CheckButton("Remove statusbar from screen capture")
493: crop.set_active(config.get_crop())
494: crop.set_tooltip_text("Whether to crop statusbar from screenshots and video recordings")
1.1 root 495:
496: dialog = gtk.Dialog("Display settings", self.parent,
497: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
498: (gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
499: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
500:
1.1.1.3 root 501: dialog.vbox.add(gtk.Label("Max zoomed size:"))
502: dialog.vbox.add(scalew)
503: dialog.vbox.add(scaleh)
1.1.1.7 root 504: dialog.vbox.add(force_max)
1.1.1.4 root 505: dialog.vbox.add(desktop)
1.1.1.7 root 506: dialog.vbox.add(desktop_st)
1.1 root 507: dialog.vbox.add(borders)
1.1.1.9 ! root 508: dialog.vbox.add(gtk.Label("Frameskip:"))
! 509: dialog.vbox.add(skip)
! 510: dialog.vbox.add(gtk.Label("Slowdown:"))
! 511: dialog.vbox.add(slow)
1.1 root 512: dialog.vbox.add(statusbar)
513: dialog.vbox.add(led)
1.1.1.4 root 514: dialog.vbox.add(crop)
1.1 root 515: dialog.vbox.show_all()
516:
517: self.dialog = dialog
518: self.skip = skip
1.1.1.9 ! root 519: self.slow = slow
1.1.1.3 root 520: self.maxw = maxadjw
521: self.maxh = maxadjh
1.1.1.7 root 522: self.force_max = force_max
1.1.1.4 root 523: self.desktop = desktop
1.1.1.7 root 524: self.desktop_st = desktop_st
1.1 root 525: self.borders = borders
526: self.statusbar = statusbar
527: self.led = led
1.1.1.4 root 528: self.crop = crop
1.1 root 529:
530: def run(self, config):
531: "run(config), show display dialog"
532: if not self.dialog:
533: self._create_dialog(config)
534: response = self.dialog.run()
535: self.dialog.hide()
536: if response == gtk.RESPONSE_APPLY:
537: config.lock_updates()
538: config.set_frameskip(self.skip.get_active())
1.1.1.9 ! root 539: config.set_slowdown(self.slow.get_active())
1.1.1.3 root 540: config.set_max_size(self.maxw.get_value(), self.maxh.get_value())
1.1.1.7 root 541: config.set_force_max(self.force_max.get_active())
1.1.1.4 root 542: config.set_desktop(self.desktop.get_active())
1.1.1.7 root 543: config.set_desktop_st(self.desktop_st.get_active())
1.1 root 544: config.set_borders(self.borders.get_active())
545: config.set_statusbar(self.statusbar.get_active())
546: config.set_led(self.led.get_active())
1.1.1.4 root 547: config.set_crop(self.crop.get_active())
1.1 root 548: config.flush_updates()
549:
550:
551: # ----------------------------------
552: # Joystick dialog
553:
554: class JoystickDialog(HatariUIDialog):
555: def _create_dialog(self, config):
1.1.1.3 root 556: table, self.dialog = create_table_dialog(self.parent, "Joystick settings", 9, 2)
1.1 root 557:
558: joy = 0
559: self.joy = []
560: joytypes = config.get_joystick_types()
561: for label in config.get_joystick_names():
562: combo = gtk.combo_box_new_text()
563: for text in joytypes:
564: combo.append_text(text)
565: combo.set_active(config.get_joystick(joy))
566: widget = table_add_widget_row(table, joy, "%s:" % label, combo)
567: self.joy.append(widget)
568: joy += 1
569:
570: table.show_all()
571:
572: def run(self, config):
573: "run(config), show joystick dialog"
574: if not self.dialog:
575: self._create_dialog(config)
576: response = self.dialog.run()
577: self.dialog.hide()
578:
579: if response == gtk.RESPONSE_APPLY:
580: config.lock_updates()
581: for joy in range(6):
582: config.set_joystick(joy, self.joy[joy].get_active())
583: config.flush_updates()
584:
585:
586: # ---------------------------------------
587: # Peripherals (midi,printer,rs232) dialog
588:
589: class PeripheralDialog(HatariUIDialog):
590: def _create_dialog(self, config):
591: midi = gtk.CheckButton("Enable MIDI")
592: midi.set_active(config.get_midi())
593:
594: printer = gtk.CheckButton("Enable printer output")
595: printer.set_active(config.get_printer())
596:
597: rs232 = gtk.CheckButton("Enable RS232")
598: rs232.set_active(config.get_rs232())
599:
600: dialog = gtk.Dialog("Peripherals", self.parent,
601: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
602: (gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
603: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
604: dialog.vbox.add(midi)
605: dialog.vbox.add(printer)
606: dialog.vbox.add(rs232)
607: dialog.vbox.show_all()
608:
609: self.dialog = dialog
610: self.printer = printer
611: self.rs232 = rs232
612: self.midi = midi
613:
614: def run(self, config):
615: "run(config), show peripherals dialog"
616: if not self.dialog:
617: self._create_dialog(config)
618: response = self.dialog.run()
619: self.dialog.hide()
620:
621: if response == gtk.RESPONSE_APPLY:
622: config.lock_updates()
623: config.set_midi(self.midi.get_active())
624: config.set_printer(self.printer.get_active())
625: config.set_rs232(self.rs232.get_active())
626: config.flush_updates()
627:
628:
629: # ---------------------------------------
630: # Path dialog
631:
632: class PathDialog(HatariUIDialog):
633: def _create_dialog(self, config):
634: paths = config.get_paths()
1.1.1.3 root 635: table, self.dialog = create_table_dialog(self.parent, "File path settings", len(paths), 2)
1.1 root 636: paths.sort()
637: row = 0
638: self.paths = []
639: for (key, path, label) in paths:
640: fsel = FselEntry(self.dialog, self._validate_fname, key)
641: fsel.set_filename(path)
642: self.paths.append((key, fsel))
643: table_add_widget_row(table, row, label, fsel.get_container())
644: row += 1
645: table.show_all()
646:
647: def _validate_fname(self, key, fname):
648: if key != "soundout":
649: return True
650: if fname.rsplit(".", 1)[-1].lower() in ("ym", "wav"):
651: return True
652: ErrorDialog(self.dialog).run("Sound output file name:\n\t%s\nneeds to end with '.ym' or '.wav'." % fname)
653: return False
654:
655: def run(self, config):
656: "run(config), show paths dialog"
657: if not self.dialog:
658: self._create_dialog(config)
659: response = self.dialog.run()
660: self.dialog.hide()
661:
662: if response == gtk.RESPONSE_APPLY:
663: paths = []
664: for key, fsel in self.paths:
665: paths.append((key, fsel.get_filename()))
666: config.set_paths(paths)
667:
668:
669: # ---------------------------
670: # Sound dialog
671:
672: class SoundDialog(HatariUIDialog):
673:
674: def _create_dialog(self, config):
1.1.1.6 root 675: enabled, curhz = config.get_sound()
676:
1.1 root 677: self.enabled = gtk.CheckButton("Sound enabled")
678: self.enabled.set_active(enabled)
1.1.1.6 root 679:
680: hz = gtk.combo_box_new_text()
681: for text in config.get_sound_values():
682: hz.append_text(text)
683: hz.set_active(curhz)
684: self.hz = hz
685:
686: ymmixer = gtk.combo_box_new_text()
687: for text in config.get_ymmixer_types():
688: ymmixer.append_text(text)
689: ymmixer.set_active(config.get_ymmixer())
690: self.ymmixer = ymmixer
691:
1.1.1.9 ! root 692: adj = gtk.Adjustment(config.get_bufsize(), 0, 110, 10, 10, 10)
! 693: bufsize = gtk.HScale(adj)
! 694: bufsize.set_digits(0)
! 695: bufsize.set_tooltip_text("0 = use default value. In some situations, SDL default may cause large (~0.5s) sound delay at lower frequency. If you have this problem, try with e.g. 20 ms, otherwise keep at 0.")
! 696: self.bufsize = bufsize
! 697:
! 698: self.sync = gtk.CheckButton("Emulation speed synched to sound output")
! 699: self.sync.set_tooltip_text("Constantly adjust emulation screen update rate to match sound output. Can help if you suffer from sound buffer under/overflow.")
! 700: self.sync.set_active(config.get_sync())
! 701:
1.1.1.6 root 702: self.mic = gtk.CheckButton("Enable (Falcon) microphone")
703: self.mic.set_active(config.get_mic())
1.1 root 704:
705: dialog = gtk.Dialog("Sound settings", self.parent,
706: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
707: (gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
708: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
709: dialog.vbox.add(self.enabled)
1.1.1.6 root 710: dialog.vbox.add(gtk.Label("Sound frequency::"))
711: dialog.vbox.add(hz)
712: dialog.vbox.add(gtk.Label("YM voices mixing method:"))
713: dialog.vbox.add(ymmixer)
1.1.1.9 ! root 714: dialog.vbox.add(gtk.Label("SDL sound buffer size (ms):"))
! 715: dialog.vbox.add(bufsize)
! 716: dialog.vbox.add(self.sync)
1.1.1.6 root 717: dialog.vbox.add(self.mic)
1.1 root 718: dialog.vbox.show_all()
719: self.dialog = dialog
720:
721: def run(self, config):
722: "run(config), show sound dialog"
723: if not self.dialog:
724: self._create_dialog(config)
725: response = self.dialog.run()
726: self.dialog.hide()
727: if response == gtk.RESPONSE_APPLY:
1.1.1.6 root 728: config.lock_updates()
729: config.set_mic(self.mic.get_active())
1.1.1.9 ! root 730: config.set_sync(self.sync.get_active())
! 731: config.set_bufsize(self.bufsize.get_value())
1.1.1.6 root 732: config.set_ymmixer(self.ymmixer.get_active())
1.1 root 733: enabled = self.enabled.get_active()
1.1.1.6 root 734: hz = self.hz.get_active()
735: config.set_sound(enabled, hz)
736: config.flush_updates()
1.1 root 737:
738:
739: # ---------------------------
740: # Trace settings dialog
741:
742: class TraceDialog(HatariUIDialog):
743: # you can get this list with:
744: # hatari --trace help 2>&1|awk '/all$/{next} /^ [^-]/ {printf("\"%s\",\n", $1)}'
745: tracepoints = [
746: "video_sync",
747: "video_res",
748: "video_color",
749: "video_border_v",
750: "video_border_h",
751: "video_addr",
752: "video_hbl",
753: "video_vbl",
754: "video_ste",
755: "mfp_exception",
756: "mfp_start",
757: "mfp_read",
758: "mfp_write",
1.1.1.4 root 759: "psg_read",
760: "psg_write",
1.1 root 761: "cpu_pairing",
762: "cpu_disasm",
763: "cpu_exception",
764: "int",
765: "fdc",
1.1.1.8 root 766: "acia",
1.1 root 767: "ikbd_cmds",
768: "ikbd_acia",
769: "ikbd_exec",
770: "blitter",
771: "bios",
772: "xbios",
773: "gemdos",
774: "vdi",
1.1.1.4 root 775: "aes",
1.1 root 776: "io_read",
777: "io_write",
1.1.1.4 root 778: "dmasound",
779: "crossbar",
780: "videl",
781: "dsp_host_interface",
782: "dsp_host_command",
783: "dsp_host_ssi",
784: "dsp_interrupt",
785: "dsp_disasm",
1.1.1.8 root 786: "dsp_disasm_reg",
787: "dsp_disasm_mem",
788: "dsp_state",
789: "dsp_symbols",
790: "cpu_symbols",
1.1.1.9 ! root 791: "nvram",
! 792: "scsi_cmd",
! 793: "natfeats",
1.1 root 794: ]
795: def __init__(self, parent):
1.1.1.6 root 796: self.savedpoints = None
1.1 root 797: hbox1 = gtk.HBox()
798: hbox1.add(create_button("Load", self._load_traces))
799: hbox1.add(create_button("Clear", self._clear_traces))
800: hbox1.add(create_button("Save", self._save_traces))
801: hbox2 = gtk.HBox()
802: vboxes = []
1.1.1.4 root 803: for idx in (0,1,2,3):
1.1 root 804: vboxes.append(gtk.VBox())
805: hbox2.add(vboxes[idx])
806:
807: count = 0
1.1.1.4 root 808: per_side = (len(self.tracepoints)+3)/4
1.1 root 809: self.tracewidgets = {}
810: for trace in self.tracepoints:
1.1.1.2 root 811: name = trace.replace("_", "-")
812: widget = gtk.CheckButton(name)
1.1 root 813: self.tracewidgets[trace] = widget
814: vboxes[count/per_side].pack_start(widget, False, True)
815: count += 1
816:
817: dialog = gtk.Dialog("Trace settings", parent,
818: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
819: (gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
820: gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
821: dialog.vbox.add(hbox1)
822: dialog.vbox.add(gtk.Label("Select trace points:"))
823: dialog.vbox.add(hbox2)
824: dialog.vbox.show_all()
825: self.dialog = dialog
826:
827: def _get_traces(self):
828: traces = []
829: for trace in self.tracepoints:
830: if self.tracewidgets[trace].get_active():
831: traces.append(trace)
832: if traces:
833: return ",".join(traces)
834: return "none"
835:
836: def _set_traces(self, tracepoints):
837: self._clear_traces()
1.1.1.6 root 838: if not tracepoints:
839: return
1.1 root 840: for trace in tracepoints.split(","):
841: if trace in self.tracewidgets:
842: self.tracewidgets[trace].set_active(True)
843: else:
1.1.1.4 root 844: print("ERROR: unknown trace setting '%s'" % trace)
1.1 root 845:
846: def _clear_traces(self, widget = None):
847: for trace in self.tracepoints:
848: self.tracewidgets[trace].set_active(False)
849:
850: def _load_traces(self, widget):
851: # this doesn't load traces, just sets them from internal variable
852: # that run method gets from caller and sets. It's up to caller
853: # whether the saving or loading happens actually to disk
854: self._set_traces(self.savedpoints)
855:
856: def _save_traces(self, widget):
857: self.savedpoints = self._get_traces()
858:
859: def run(self, hatari, savedpoints):
860: "run(hatari,tracepoints) -> tracepoints, caller saves tracepoints"
861: self.savedpoints = savedpoints
862: while 1:
863: response = self.dialog.run()
864: if response == gtk.RESPONSE_APPLY:
865: hatari.change_option("--trace %s" % self._get_traces())
866: else:
867: self.dialog.hide()
868: return self.savedpoints
869:
870:
871: # ------------------------------------------
872: # Machine dialog for settings needing reboot
873:
874: class MachineDialog(HatariUIDialog):
1.1.1.3 root 875: def _machine_cb(self, widget, data):
876: if not widget.get_active():
877: return
878: machine = data.lower()
879: if machine == "ste" or machine == "st":
880: self.clocks[0].set_active(True)
881: self.cpulevel.set_active(0)
882: elif machine == "falcon":
883: self.clocks[1].set_active(True)
1.1.1.4 root 884: self.dsps[2].set_active(True)
1.1.1.3 root 885: self.cpulevel.set_active(3)
886: elif machine == "tt":
887: self.clocks[2].set_active(True)
888: self.cpulevel.set_active(3)
889:
1.1 root 890: def _create_dialog(self, config):
1.1.1.3 root 891: table, self.dialog = create_table_dialog(self.parent, "Machine configuration", 6, 4, "Set and reboot")
892:
893: row = 0
894: self.machines = table_add_radio_rows(table, row, "Machine:",
895: config.get_machine_types(), self._machine_cb)
896: row += 1
1.1 root 897:
1.1.1.4 root 898: self.dsps = table_add_radio_rows(table, row, "DSP type:", config.get_dsp_types())
1.1.1.3 root 899: row += 1
900:
901: # start next table column
1.1 root 902: row = 0
1.1.1.3 root 903: table_set_col_offset(table, 2)
904: self.monitors = table_add_radio_rows(table, row, "Monitor:", config.get_monitor_types())
1.1 root 905: row += 1
1.1.1.3 root 906:
907: self.clocks = table_add_radio_rows(table, row, "CPU clock:", config.get_cpuclock_types())
908: row += 1
909:
910: # fullspan at bottom
911: fullspan = True
912:
1.1 root 913: combo = gtk.combo_box_new_text()
1.1.1.3 root 914: for text in config.get_cpulevel_types():
1.1 root 915: combo.append_text(text)
1.1.1.3 root 916: self.cpulevel = table_add_widget_row(table, row, "CPU type:", combo, fullspan)
1.1 root 917: row += 1
1.1.1.3 root 918:
1.1 root 919: combo = gtk.combo_box_new_text()
920: for text in config.get_memory_names():
921: combo.append_text(text)
1.1.1.3 root 922: self.memory = table_add_widget_row(table, row, "Memory:", combo, fullspan)
1.1 root 923: row += 1
924:
925: label = "TOS image:"
926: fsel = self._fsel(label, gtk.FILE_CHOOSER_ACTION_OPEN)
1.1.1.3 root 927: self.tos = table_add_widget_row(table, row, label, fsel, fullspan)
1.1 root 928: row += 1
929:
1.1.1.3 root 930: vbox = gtk.VBox()
931: self.compatible = gtk.CheckButton("Compatible CPU")
1.1.1.4 root 932: self.rtc = gtk.CheckButton("Real-time clock")
1.1.1.3 root 933: self.timerd = gtk.CheckButton("Patch Timer-D")
1.1.1.9 ! root 934: self.compatible.set_tooltip_text("Needed for overscan and other timing sensitive things to work correctly")
! 935: self.rtc.set_tooltip_text("Some rare games and demos don't work with this")
! 936: self.timerd.set_tooltip_text("Improves ST/STE emulation performance, but some rare demos/games don't work with this")
1.1.1.3 root 937: vbox.add(self.compatible)
938: vbox.add(self.timerd)
1.1.1.4 root 939: vbox.add(self.rtc)
1.1.1.3 root 940: table_add_widget_row(table, row, "Misc.:", vbox, fullspan)
1.1 root 941: row += 1
942:
943: table.show_all()
944:
945: def _fsel(self, label, action):
946: fsel = gtk.FileChooserButton(label)
947: # Hatari cannot access URIs
948: fsel.set_local_only(True)
949: fsel.set_width_chars(12)
950: fsel.set_action(action)
951: return fsel
952:
953: def _get_config(self, config):
1.1.1.3 root 954: self.machines[config.get_machine()].set_active(True)
955: self.monitors[config.get_monitor()].set_active(True)
956: self.clocks[config.get_cpuclock()].set_active(True)
957: self.dsps[config.get_dsp()].set_active(True)
958: self.cpulevel.set_active(config.get_cpulevel())
1.1 root 959: self.memory.set_active(config.get_memory())
960: tos = config.get_tos()
961: if tos:
962: self.tos.set_filename(tos)
963: self.compatible.set_active(config.get_compatible())
964: self.timerd.set_active(config.get_timerd())
1.1.1.4 root 965: self.rtc.set_active(config.get_rtc())
1.1 root 966:
1.1.1.3 root 967: def _get_active_radio(self, radios):
968: idx = 0
969: for radio in radios:
970: if radio.get_active():
971: return idx
972: idx += 1
973:
1.1 root 974: def _set_config(self, config):
975: config.lock_updates()
1.1.1.3 root 976: config.set_machine(self._get_active_radio(self.machines))
977: config.set_monitor(self._get_active_radio(self.monitors))
978: config.set_cpuclock(self._get_active_radio(self.clocks))
979: config.set_dsp(self._get_active_radio(self.dsps))
980: config.set_cpulevel(self.cpulevel.get_active())
1.1 root 981: config.set_memory(self.memory.get_active())
982: config.set_tos(self.tos.get_filename())
983: config.set_compatible(self.compatible.get_active())
984: config.set_timerd(self.timerd.get_active())
1.1.1.4 root 985: config.set_rtc(self.rtc.get_active())
1.1 root 986: config.flush_updates()
987:
988: def run(self, config):
989: "run(config) -> bool, whether to reboot"
990: if not self.dialog:
991: self._create_dialog(config)
992:
993: self._get_config(config)
994: response = self.dialog.run()
995: self.dialog.hide()
996: if response == gtk.RESPONSE_APPLY:
997: self._set_config(config)
998: return True
999: return False
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.