|
|
1.1 root 1: #!/usr/bin/env python
2: #
3: # Classes for the Hatari UI dialogs
4: #
1.1.1.10! root 5: # Copyright (C) 2008-2015 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
1.1.1.10! root 373:
! 374: table_add_widget_row(table, row, " ", gtk.HSeparator(), True)
! 375: row += 1
! 376:
1.1.1.3 root 377: label = "GEMDOS drive directory:"
1.1.1.10! root 378: path = config.get_hd_dir()
1.1.1.4 root 379: fsel, box = factory.get(label, None, path, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
1.1.1.3 root 380: table_add_widget_row(table, row, label, box, True)
1.1.1.10! root 381: self.hddir = fsel
! 382: row += 1
! 383:
! 384: hddrive = gtk.combo_box_new_text()
! 385: for text in config.get_hd_drives():
! 386: hddrive.append_text(text)
! 387: hddrive.set_tooltip_text("Whether GEMDOS HD emulation uses fixed drive letter, or first free drive letter after ASCI & IDE drives (detection unreliable)")
! 388: table_add_widget_row(table, row, "GEMDOS HD drive:", hddrive)
! 389: self.hddrive = hddrive
1.1.1.3 root 390: row += 1
391:
392: protect = gtk.combo_box_new_text()
393: for text in config.get_protection_types():
394: protect.append_text(text)
1.1.1.9 root 395: 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 396: table_add_widget_row(table, row, "Write protection:", protect)
397: self.protect = protect
1.1.1.9 root 398: row += 1
399:
400: lower = gtk.combo_box_new_text()
401: for text in config.get_hd_cases():
402: lower.append_text(text)
403: lower.set_tooltip_text("What to do with names of files created by Atari programs through GEMDOS HD emulation")
404: table_add_widget_row(table, row, "File names:", lower)
405: self.lower = lower
1.1.1.3 root 406:
407: table.show_all()
1.1 root 408:
1.1.1.3 root 409: def _get_config(self, config):
410: path = config.get_acsi_image()
411: if path:
412: self.acsi.set_filename(path)
413: path = config.get_idemaster_image()
414: if path:
415: self.idemaster.set_filename(path)
416: path = config.get_ideslave_image()
417: if path:
418: self.ideslave.set_filename(path)
1.1.1.10! root 419: path = config.get_hd_dir()
! 420: if path:
! 421: self.hddir.set_filename(path)
! 422: self.hddrive.set_active(config.get_hd_drive())
1.1.1.3 root 423: self.protect.set_active(config.get_hd_protection())
1.1.1.9 root 424: self.lower.set_active(config.get_hd_case())
425:
1.1.1.3 root 426: def _set_config(self, config):
427: config.lock_updates()
428: config.set_acsi_image(self.acsi.get_filename())
429: config.set_idemaster_image(self.idemaster.get_filename())
430: config.set_ideslave_image(self.ideslave.get_filename())
1.1.1.10! root 431: config.set_hd_dir(self.hddir.get_filename())
! 432: config.set_hd_drive(self.hddrive.get_active())
1.1.1.3 root 433: config.set_hd_protection(self.protect.get_active())
1.1.1.9 root 434: config.set_hd_case(self.lower.get_active())
1.1.1.3 root 435: config.flush_updates()
436:
437: def run(self, config):
438: "run(config) -> bool, whether to reboot"
439: if not self.dialog:
440: self._create_dialog(config)
441:
442: self._get_config(config)
443: response = self.dialog.run()
444: self.dialog.hide()
445: if response == gtk.RESPONSE_APPLY:
446: self._set_config(config)
447: return True
448: return False
449:
450:
1.1 root 451: # ---------------------------
452: # Display dialog
453:
454: class DisplayDialog(HatariUIDialog):
455:
456: def _create_dialog(self, config):
457:
458: skip = gtk.combo_box_new_text()
459: for text in config.get_frameskip_names():
460: skip.append_text(text)
461: skip.set_active(config.get_frameskip())
1.1.1.9 root 462: skip.set_tooltip_text("Set how many frames are skipped to speed up emulation")
463:
464: slow = gtk.combo_box_new_text()
465: for text in config.get_slowdown_names():
466: slow.append_text(text)
467: slow.set_active(0)
468: 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 469:
1.1.1.3 root 470: maxw, maxh = config.get_max_size()
1.1.1.6 root 471: topw, toph = config.get_desktop_size()
472: maxadjw = gtk.Adjustment(maxw, 320, topw, 8, 40)
473: maxadjh = gtk.Adjustment(maxh, 200, toph, 8, 40)
1.1.1.3 root 474: scalew = gtk.HScale(maxadjw)
475: scaleh = gtk.HScale(maxadjh)
476: scalew.set_digits(0)
477: scaleh.set_digits(0)
1.1.1.4 root 478: scalew.set_tooltip_text("Preferred/maximum zoomed width")
479: scaleh.set_tooltip_text("Preferred/maximum zoomed height")
480:
1.1.1.7 root 481: force_max = gtk.CheckButton("Force max resolution (Falcon)")
482: force_max.set_active(config.get_force_max())
483: force_max.set_tooltip_text("Whether to force maximum resolution to help recording videos of demos which do resolution changes")
484:
485: desktop = gtk.CheckButton("Keep desktop resolution (Falcon/TT)")
1.1.1.4 root 486: desktop.set_active(config.get_desktop())
1.1.1.7 root 487: desktop.set_tooltip_text("Whether to keep screen resolution in fullscreen and (try to) scale Atari screen by an integer factor instead")
488:
489: desktop_st = gtk.CheckButton("Keep desktop resolution (ST/STE)")
490: desktop_st.set_active(config.get_desktop_st())
491: desktop_st.set_tooltip_text("Whether to keep screen resolution in fullscreen to avoid potential sound skips + delay (NO SCALING)")
1.1 root 492:
1.1.1.7 root 493: borders = gtk.CheckButton("Atari screen borders")
1.1 root 494: borders.set_active(config.get_borders())
1.1.1.7 root 495: 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 496:
1.1.1.3 root 497: statusbar = gtk.CheckButton("Show statusbar")
1.1 root 498: statusbar.set_active(config.get_statusbar())
1.1.1.4 root 499: statusbar.set_tooltip_text("Whether to show statusbar with floppy leds etc")
1.1 root 500:
1.1.1.3 root 501: led = gtk.CheckButton("Show overlay led")
1.1 root 502: led.set_active(config.get_led())
1.1.1.4 root 503: led.set_tooltip_text("Whether to show overlay drive led when statusbar isn't visible")
504:
505: crop = gtk.CheckButton("Remove statusbar from screen capture")
506: crop.set_active(config.get_crop())
507: crop.set_tooltip_text("Whether to crop statusbar from screenshots and video recordings")
1.1 root 508:
509: dialog = gtk.Dialog("Display settings", self.parent,
510: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
511: (gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
512: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
513:
1.1.1.3 root 514: dialog.vbox.add(gtk.Label("Max zoomed size:"))
515: dialog.vbox.add(scalew)
516: dialog.vbox.add(scaleh)
1.1.1.7 root 517: dialog.vbox.add(force_max)
1.1.1.4 root 518: dialog.vbox.add(desktop)
1.1.1.7 root 519: dialog.vbox.add(desktop_st)
1.1 root 520: dialog.vbox.add(borders)
1.1.1.9 root 521: dialog.vbox.add(gtk.Label("Frameskip:"))
522: dialog.vbox.add(skip)
523: dialog.vbox.add(gtk.Label("Slowdown:"))
524: dialog.vbox.add(slow)
1.1 root 525: dialog.vbox.add(statusbar)
526: dialog.vbox.add(led)
1.1.1.4 root 527: dialog.vbox.add(crop)
1.1 root 528: dialog.vbox.show_all()
529:
530: self.dialog = dialog
531: self.skip = skip
1.1.1.9 root 532: self.slow = slow
1.1.1.3 root 533: self.maxw = maxadjw
534: self.maxh = maxadjh
1.1.1.7 root 535: self.force_max = force_max
1.1.1.4 root 536: self.desktop = desktop
1.1.1.7 root 537: self.desktop_st = desktop_st
1.1 root 538: self.borders = borders
539: self.statusbar = statusbar
540: self.led = led
1.1.1.4 root 541: self.crop = crop
1.1 root 542:
543: def run(self, config):
544: "run(config), show display dialog"
545: if not self.dialog:
546: self._create_dialog(config)
547: response = self.dialog.run()
548: self.dialog.hide()
549: if response == gtk.RESPONSE_APPLY:
550: config.lock_updates()
551: config.set_frameskip(self.skip.get_active())
1.1.1.9 root 552: config.set_slowdown(self.slow.get_active())
1.1.1.3 root 553: config.set_max_size(self.maxw.get_value(), self.maxh.get_value())
1.1.1.7 root 554: config.set_force_max(self.force_max.get_active())
1.1.1.4 root 555: config.set_desktop(self.desktop.get_active())
1.1.1.7 root 556: config.set_desktop_st(self.desktop_st.get_active())
1.1 root 557: config.set_borders(self.borders.get_active())
558: config.set_statusbar(self.statusbar.get_active())
559: config.set_led(self.led.get_active())
1.1.1.4 root 560: config.set_crop(self.crop.get_active())
1.1 root 561: config.flush_updates()
562:
563:
564: # ----------------------------------
565: # Joystick dialog
566:
567: class JoystickDialog(HatariUIDialog):
568: def _create_dialog(self, config):
1.1.1.3 root 569: table, self.dialog = create_table_dialog(self.parent, "Joystick settings", 9, 2)
1.1 root 570:
571: joy = 0
572: self.joy = []
573: joytypes = config.get_joystick_types()
574: for label in config.get_joystick_names():
575: combo = gtk.combo_box_new_text()
576: for text in joytypes:
577: combo.append_text(text)
578: combo.set_active(config.get_joystick(joy))
579: widget = table_add_widget_row(table, joy, "%s:" % label, combo)
580: self.joy.append(widget)
581: joy += 1
582:
583: table.show_all()
584:
585: def run(self, config):
586: "run(config), show joystick dialog"
587: if not self.dialog:
588: self._create_dialog(config)
589: response = self.dialog.run()
590: self.dialog.hide()
591:
592: if response == gtk.RESPONSE_APPLY:
593: config.lock_updates()
594: for joy in range(6):
595: config.set_joystick(joy, self.joy[joy].get_active())
596: config.flush_updates()
597:
598:
599: # ---------------------------------------
600: # Peripherals (midi,printer,rs232) dialog
601:
602: class PeripheralDialog(HatariUIDialog):
603: def _create_dialog(self, config):
604: midi = gtk.CheckButton("Enable MIDI")
605: midi.set_active(config.get_midi())
606:
607: printer = gtk.CheckButton("Enable printer output")
608: printer.set_active(config.get_printer())
609:
610: rs232 = gtk.CheckButton("Enable RS232")
611: rs232.set_active(config.get_rs232())
612:
613: dialog = gtk.Dialog("Peripherals", self.parent,
614: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
615: (gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
616: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
617: dialog.vbox.add(midi)
618: dialog.vbox.add(printer)
619: dialog.vbox.add(rs232)
620: dialog.vbox.show_all()
621:
622: self.dialog = dialog
623: self.printer = printer
624: self.rs232 = rs232
625: self.midi = midi
626:
627: def run(self, config):
628: "run(config), show peripherals dialog"
629: if not self.dialog:
630: self._create_dialog(config)
631: response = self.dialog.run()
632: self.dialog.hide()
633:
634: if response == gtk.RESPONSE_APPLY:
635: config.lock_updates()
636: config.set_midi(self.midi.get_active())
637: config.set_printer(self.printer.get_active())
638: config.set_rs232(self.rs232.get_active())
639: config.flush_updates()
640:
641:
642: # ---------------------------------------
643: # Path dialog
644:
645: class PathDialog(HatariUIDialog):
646: def _create_dialog(self, config):
647: paths = config.get_paths()
1.1.1.3 root 648: table, self.dialog = create_table_dialog(self.parent, "File path settings", len(paths), 2)
1.1 root 649: paths.sort()
650: row = 0
651: self.paths = []
652: for (key, path, label) in paths:
653: fsel = FselEntry(self.dialog, self._validate_fname, key)
654: fsel.set_filename(path)
655: self.paths.append((key, fsel))
656: table_add_widget_row(table, row, label, fsel.get_container())
657: row += 1
658: table.show_all()
659:
660: def _validate_fname(self, key, fname):
661: if key != "soundout":
662: return True
663: if fname.rsplit(".", 1)[-1].lower() in ("ym", "wav"):
664: return True
665: ErrorDialog(self.dialog).run("Sound output file name:\n\t%s\nneeds to end with '.ym' or '.wav'." % fname)
666: return False
667:
668: def run(self, config):
669: "run(config), show paths dialog"
670: if not self.dialog:
671: self._create_dialog(config)
672: response = self.dialog.run()
673: self.dialog.hide()
674:
675: if response == gtk.RESPONSE_APPLY:
676: paths = []
677: for key, fsel in self.paths:
678: paths.append((key, fsel.get_filename()))
679: config.set_paths(paths)
680:
681:
682: # ---------------------------
683: # Sound dialog
684:
685: class SoundDialog(HatariUIDialog):
686:
687: def _create_dialog(self, config):
1.1.1.6 root 688: enabled, curhz = config.get_sound()
689:
1.1 root 690: self.enabled = gtk.CheckButton("Sound enabled")
691: self.enabled.set_active(enabled)
1.1.1.6 root 692:
693: hz = gtk.combo_box_new_text()
694: for text in config.get_sound_values():
695: hz.append_text(text)
696: hz.set_active(curhz)
697: self.hz = hz
698:
699: ymmixer = gtk.combo_box_new_text()
700: for text in config.get_ymmixer_types():
701: ymmixer.append_text(text)
702: ymmixer.set_active(config.get_ymmixer())
703: self.ymmixer = ymmixer
704:
1.1.1.9 root 705: adj = gtk.Adjustment(config.get_bufsize(), 0, 110, 10, 10, 10)
706: bufsize = gtk.HScale(adj)
707: bufsize.set_digits(0)
708: 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.")
709: self.bufsize = bufsize
710:
711: self.sync = gtk.CheckButton("Emulation speed synched to sound output")
712: 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.")
713: self.sync.set_active(config.get_sync())
714:
1.1.1.6 root 715: self.mic = gtk.CheckButton("Enable (Falcon) microphone")
716: self.mic.set_active(config.get_mic())
1.1 root 717:
718: dialog = gtk.Dialog("Sound settings", self.parent,
719: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
720: (gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
721: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
722: dialog.vbox.add(self.enabled)
1.1.1.6 root 723: dialog.vbox.add(gtk.Label("Sound frequency::"))
724: dialog.vbox.add(hz)
725: dialog.vbox.add(gtk.Label("YM voices mixing method:"))
726: dialog.vbox.add(ymmixer)
1.1.1.9 root 727: dialog.vbox.add(gtk.Label("SDL sound buffer size (ms):"))
728: dialog.vbox.add(bufsize)
729: dialog.vbox.add(self.sync)
1.1.1.6 root 730: dialog.vbox.add(self.mic)
1.1 root 731: dialog.vbox.show_all()
732: self.dialog = dialog
733:
734: def run(self, config):
735: "run(config), show sound dialog"
736: if not self.dialog:
737: self._create_dialog(config)
738: response = self.dialog.run()
739: self.dialog.hide()
740: if response == gtk.RESPONSE_APPLY:
1.1.1.6 root 741: config.lock_updates()
742: config.set_mic(self.mic.get_active())
1.1.1.9 root 743: config.set_sync(self.sync.get_active())
744: config.set_bufsize(self.bufsize.get_value())
1.1.1.6 root 745: config.set_ymmixer(self.ymmixer.get_active())
1.1 root 746: enabled = self.enabled.get_active()
1.1.1.6 root 747: hz = self.hz.get_active()
748: config.set_sound(enabled, hz)
749: config.flush_updates()
1.1 root 750:
751:
752: # ---------------------------
753: # Trace settings dialog
754:
755: class TraceDialog(HatariUIDialog):
756: # you can get this list with:
757: # hatari --trace help 2>&1|awk '/all$/{next} /^ [^-]/ {printf("\"%s\",\n", $1)}'
758: tracepoints = [
759: "video_sync",
760: "video_res",
761: "video_color",
762: "video_border_v",
763: "video_border_h",
764: "video_addr",
765: "video_hbl",
766: "video_vbl",
767: "video_ste",
768: "mfp_exception",
769: "mfp_start",
770: "mfp_read",
771: "mfp_write",
1.1.1.4 root 772: "psg_read",
773: "psg_write",
1.1 root 774: "cpu_pairing",
775: "cpu_disasm",
776: "cpu_exception",
777: "int",
778: "fdc",
1.1.1.8 root 779: "acia",
1.1 root 780: "ikbd_cmds",
781: "ikbd_acia",
782: "ikbd_exec",
783: "blitter",
784: "bios",
785: "xbios",
786: "gemdos",
787: "vdi",
1.1.1.4 root 788: "aes",
1.1 root 789: "io_read",
790: "io_write",
1.1.1.4 root 791: "dmasound",
792: "crossbar",
793: "videl",
794: "dsp_host_interface",
795: "dsp_host_command",
796: "dsp_host_ssi",
797: "dsp_interrupt",
798: "dsp_disasm",
1.1.1.8 root 799: "dsp_disasm_reg",
800: "dsp_disasm_mem",
801: "dsp_state",
802: "dsp_symbols",
803: "cpu_symbols",
1.1.1.9 root 804: "nvram",
805: "scsi_cmd",
806: "natfeats",
1.1.1.10! root 807: "keymap",
! 808: "midi",
! 809: "ide",
1.1 root 810: ]
811: def __init__(self, parent):
1.1.1.6 root 812: self.savedpoints = None
1.1 root 813: hbox1 = gtk.HBox()
814: hbox1.add(create_button("Load", self._load_traces))
815: hbox1.add(create_button("Clear", self._clear_traces))
816: hbox1.add(create_button("Save", self._save_traces))
817: hbox2 = gtk.HBox()
818: vboxes = []
1.1.1.4 root 819: for idx in (0,1,2,3):
1.1 root 820: vboxes.append(gtk.VBox())
821: hbox2.add(vboxes[idx])
822:
823: count = 0
1.1.1.4 root 824: per_side = (len(self.tracepoints)+3)/4
1.1 root 825: self.tracewidgets = {}
826: for trace in self.tracepoints:
1.1.1.2 root 827: name = trace.replace("_", "-")
828: widget = gtk.CheckButton(name)
1.1 root 829: self.tracewidgets[trace] = widget
830: vboxes[count/per_side].pack_start(widget, False, True)
831: count += 1
832:
833: dialog = gtk.Dialog("Trace settings", parent,
834: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
835: (gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
836: gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
837: dialog.vbox.add(hbox1)
838: dialog.vbox.add(gtk.Label("Select trace points:"))
839: dialog.vbox.add(hbox2)
840: dialog.vbox.show_all()
841: self.dialog = dialog
842:
843: def _get_traces(self):
844: traces = []
845: for trace in self.tracepoints:
846: if self.tracewidgets[trace].get_active():
847: traces.append(trace)
848: if traces:
849: return ",".join(traces)
850: return "none"
851:
852: def _set_traces(self, tracepoints):
853: self._clear_traces()
1.1.1.6 root 854: if not tracepoints:
855: return
1.1 root 856: for trace in tracepoints.split(","):
857: if trace in self.tracewidgets:
858: self.tracewidgets[trace].set_active(True)
859: else:
1.1.1.4 root 860: print("ERROR: unknown trace setting '%s'" % trace)
1.1 root 861:
862: def _clear_traces(self, widget = None):
863: for trace in self.tracepoints:
864: self.tracewidgets[trace].set_active(False)
865:
866: def _load_traces(self, widget):
867: # this doesn't load traces, just sets them from internal variable
868: # that run method gets from caller and sets. It's up to caller
869: # whether the saving or loading happens actually to disk
870: self._set_traces(self.savedpoints)
871:
872: def _save_traces(self, widget):
873: self.savedpoints = self._get_traces()
874:
875: def run(self, hatari, savedpoints):
876: "run(hatari,tracepoints) -> tracepoints, caller saves tracepoints"
877: self.savedpoints = savedpoints
878: while 1:
879: response = self.dialog.run()
880: if response == gtk.RESPONSE_APPLY:
881: hatari.change_option("--trace %s" % self._get_traces())
882: else:
883: self.dialog.hide()
884: return self.savedpoints
885:
886:
887: # ------------------------------------------
888: # Machine dialog for settings needing reboot
889:
890: class MachineDialog(HatariUIDialog):
1.1.1.3 root 891: def _machine_cb(self, widget, data):
892: if not widget.get_active():
893: return
894: machine = data.lower()
895: if machine == "ste" or machine == "st":
896: self.clocks[0].set_active(True)
897: self.cpulevel.set_active(0)
898: elif machine == "falcon":
899: self.clocks[1].set_active(True)
1.1.1.4 root 900: self.dsps[2].set_active(True)
1.1.1.3 root 901: self.cpulevel.set_active(3)
902: elif machine == "tt":
903: self.clocks[2].set_active(True)
904: self.cpulevel.set_active(3)
905:
1.1 root 906: def _create_dialog(self, config):
1.1.1.3 root 907: table, self.dialog = create_table_dialog(self.parent, "Machine configuration", 6, 4, "Set and reboot")
908:
909: row = 0
910: self.machines = table_add_radio_rows(table, row, "Machine:",
911: config.get_machine_types(), self._machine_cb)
912: row += 1
1.1 root 913:
1.1.1.4 root 914: self.dsps = table_add_radio_rows(table, row, "DSP type:", config.get_dsp_types())
1.1.1.3 root 915: row += 1
916:
917: # start next table column
1.1 root 918: row = 0
1.1.1.3 root 919: table_set_col_offset(table, 2)
920: self.monitors = table_add_radio_rows(table, row, "Monitor:", config.get_monitor_types())
1.1 root 921: row += 1
1.1.1.3 root 922:
923: self.clocks = table_add_radio_rows(table, row, "CPU clock:", config.get_cpuclock_types())
924: row += 1
925:
926: # fullspan at bottom
927: fullspan = True
928:
1.1 root 929: combo = gtk.combo_box_new_text()
1.1.1.3 root 930: for text in config.get_cpulevel_types():
1.1 root 931: combo.append_text(text)
1.1.1.3 root 932: self.cpulevel = table_add_widget_row(table, row, "CPU type:", combo, fullspan)
1.1 root 933: row += 1
1.1.1.3 root 934:
1.1 root 935: combo = gtk.combo_box_new_text()
936: for text in config.get_memory_names():
937: combo.append_text(text)
1.1.1.3 root 938: self.memory = table_add_widget_row(table, row, "Memory:", combo, fullspan)
1.1 root 939: row += 1
1.1.1.10! root 940:
! 941: self.ttram = gtk.Adjustment(config.get_ttram(), 0, 260, 4, 4, 4)
! 942: ttram = gtk.HScale(self.ttram)
! 943: ttram.set_digits(0)
! 944: ttram.set_tooltip_text("TT-RAM needs Falcon/TT with WinUAE CPU core and implies 32-bit addressing. 0 = disabled, 24-bit addressing.")
! 945: table_add_widget_row(table, row, "TT-RAM", ttram, fullspan)
! 946: row += 1
1.1 root 947:
948: label = "TOS image:"
949: fsel = self._fsel(label, gtk.FILE_CHOOSER_ACTION_OPEN)
1.1.1.3 root 950: self.tos = table_add_widget_row(table, row, label, fsel, fullspan)
1.1 root 951: row += 1
952:
1.1.1.3 root 953: vbox = gtk.VBox()
954: self.compatible = gtk.CheckButton("Compatible CPU")
1.1.1.4 root 955: self.rtc = gtk.CheckButton("Real-time clock")
1.1.1.3 root 956: self.timerd = gtk.CheckButton("Patch Timer-D")
1.1.1.9 root 957: self.compatible.set_tooltip_text("Needed for overscan and other timing sensitive things to work correctly")
958: self.rtc.set_tooltip_text("Some rare games and demos don't work with this")
959: 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 960: vbox.add(self.compatible)
961: vbox.add(self.timerd)
1.1.1.4 root 962: vbox.add(self.rtc)
1.1.1.3 root 963: table_add_widget_row(table, row, "Misc.:", vbox, fullspan)
1.1 root 964: row += 1
965:
966: table.show_all()
967:
968: def _fsel(self, label, action):
969: fsel = gtk.FileChooserButton(label)
970: # Hatari cannot access URIs
971: fsel.set_local_only(True)
972: fsel.set_width_chars(12)
973: fsel.set_action(action)
974: return fsel
975:
976: def _get_config(self, config):
1.1.1.3 root 977: self.machines[config.get_machine()].set_active(True)
978: self.monitors[config.get_monitor()].set_active(True)
979: self.clocks[config.get_cpuclock()].set_active(True)
980: self.dsps[config.get_dsp()].set_active(True)
981: self.cpulevel.set_active(config.get_cpulevel())
1.1 root 982: self.memory.set_active(config.get_memory())
1.1.1.10! root 983: self.ttram.set_value(config.get_ttram())
1.1 root 984: tos = config.get_tos()
985: if tos:
986: self.tos.set_filename(tos)
987: self.compatible.set_active(config.get_compatible())
988: self.timerd.set_active(config.get_timerd())
1.1.1.4 root 989: self.rtc.set_active(config.get_rtc())
1.1 root 990:
1.1.1.3 root 991: def _get_active_radio(self, radios):
992: idx = 0
993: for radio in radios:
994: if radio.get_active():
995: return idx
996: idx += 1
997:
1.1 root 998: def _set_config(self, config):
999: config.lock_updates()
1.1.1.3 root 1000: config.set_machine(self._get_active_radio(self.machines))
1001: config.set_monitor(self._get_active_radio(self.monitors))
1002: config.set_cpuclock(self._get_active_radio(self.clocks))
1003: config.set_dsp(self._get_active_radio(self.dsps))
1004: config.set_cpulevel(self.cpulevel.get_active())
1.1 root 1005: config.set_memory(self.memory.get_active())
1.1.1.10! root 1006: config.set_ttram(self.ttram.get_value())
1.1 root 1007: config.set_tos(self.tos.get_filename())
1008: config.set_compatible(self.compatible.get_active())
1009: config.set_timerd(self.timerd.get_active())
1.1.1.4 root 1010: config.set_rtc(self.rtc.get_active())
1.1 root 1011: config.flush_updates()
1012:
1013: def run(self, config):
1014: "run(config) -> bool, whether to reboot"
1015: if not self.dialog:
1016: self._create_dialog(config)
1017:
1018: self._get_config(config)
1019: response = self.dialog.run()
1020: self.dialog.hide()
1021: if response == gtk.RESPONSE_APPLY:
1022: self._set_config(config)
1023: return True
1024: return False
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.