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