mirror of
https://github.com/libretro/hatari.git
synced 2026-11-07 10:36:00 -10:00
1047 lines
37 KiB
Python
1047 lines
37 KiB
Python
# Classes for the Hatari UI dialogs
|
|
#
|
|
# Copyright (C) 2008-2025 by Eero Tamminen
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import gi
|
|
# use correct version of gtk
|
|
gi.require_version('Gtk', '3.0')
|
|
from gi.repository import Gtk
|
|
from gi.repository import GdkPixbuf
|
|
|
|
from uihelpers import UInfo, HatariTextInsert, create_table_dialog, \
|
|
table_add_widget_row, table_add_combo_row, create_button, \
|
|
FselEntry, FselAndEjectFactory
|
|
|
|
|
|
# -----------------
|
|
# Dialog base class
|
|
|
|
class HatariUIDialog:
|
|
def __init__(self, parent):
|
|
"<any>Dialog(parent) -> object"
|
|
self.parent = parent
|
|
self.dialog = None
|
|
|
|
def run(self, _dummy = None):
|
|
"""run() -> response. Shows dialog and returns response,
|
|
subclasses overriding run() require also argument(s)."""
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
return response
|
|
|
|
|
|
# ---------------------------
|
|
# Note/Todo/Error/Ask dialogs
|
|
|
|
class NoteDialog(HatariUIDialog):
|
|
button = Gtk.ButtonsType.OK
|
|
icontype = Gtk.MessageType.INFO
|
|
textpattern = "\n%s"
|
|
def run(self, text):
|
|
"run(text), show message dialog with given text"
|
|
dialog = Gtk.MessageDialog(self.parent,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
self.icontype, self.button, self.textpattern % text)
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
class TodoDialog(NoteDialog):
|
|
textpattern = "\nTODO: %s"
|
|
|
|
class ErrorDialog(NoteDialog):
|
|
button = Gtk.ButtonsType.CLOSE
|
|
icontype = Gtk.MessageType.ERROR
|
|
textpattern = "\nERROR: %s"
|
|
|
|
|
|
class AskDialog(HatariUIDialog):
|
|
def run(self, text):
|
|
"run(text) -> bool, show question dialog and return True if user OKed it"
|
|
dialog = Gtk.MessageDialog(self.parent,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, text)
|
|
response = dialog.run()
|
|
dialog.destroy()
|
|
return (response == Gtk.ResponseType.YES)
|
|
|
|
|
|
# ---------------------------
|
|
# About dialog
|
|
|
|
class AboutDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
info = UInfo()
|
|
dialog = Gtk.AboutDialog()
|
|
dialog.set_transient_for(parent)
|
|
dialog.set_name(info.name)
|
|
dialog.set_version(info.version)
|
|
dialog.set_website("https://www.hatari-emu.org")
|
|
dialog.set_website_label("Hatari emulator website")
|
|
dialog.set_authors(["Eero Tamminen"])
|
|
dialog.set_artists(["The logo is from Hatari"])
|
|
dialog.set_logo(GdkPixbuf.Pixbuf.new_from_file(info.logo))
|
|
dialog.set_translator_credits("translator-credits")
|
|
dialog.set_copyright(info.copyright)
|
|
dialog.set_license("""
|
|
This software is licensed under GPL v2 or later.
|
|
|
|
You can see the whole license at:
|
|
https://spdx.org/licenses/GPL-2.0-or-later.html""")
|
|
self.dialog = dialog
|
|
|
|
|
|
# ---------------------------
|
|
# Input dialog
|
|
|
|
class InputDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
dialog = Gtk.Dialog("Key/mouse input", parent,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
("Close", Gtk.ResponseType.CLOSE))
|
|
|
|
entry = Gtk.Entry()
|
|
entry.connect("activate", self._entry_cb)
|
|
insert = create_button("Insert", self._entry_cb)
|
|
insert.set_tooltip_text("Insert given text to Hatari window")
|
|
enter = create_button("Enter key", self._enter_cb)
|
|
enter.set_tooltip_text("Simulate Enter key press")
|
|
|
|
hbox1 = Gtk.HBox()
|
|
hbox1.add(Gtk.Label(label="Text:"))
|
|
hbox1.add(entry)
|
|
hbox1.add(insert)
|
|
hbox1.add(enter)
|
|
dialog.vbox.add(hbox1)
|
|
|
|
rclick = Gtk.Button("Right click")
|
|
rclick.connect("pressed", self._rightpress_cb)
|
|
rclick.connect("released", self._rightrelease_cb)
|
|
rclick.set_tooltip_text("Simulate Atari right button press & release")
|
|
dclick = create_button("Double click", self._doubleclick_cb)
|
|
dclick.set_tooltip_text("Simulate Atari left button double-click")
|
|
|
|
hbox2 = Gtk.HBox()
|
|
hbox2.add(dclick)
|
|
hbox2.add(rclick)
|
|
dialog.vbox.add(hbox2)
|
|
|
|
dialog.show_all()
|
|
self.dialog = dialog
|
|
self.entry = entry
|
|
|
|
def _entry_cb(self, widget):
|
|
text = self.entry.get_text()
|
|
if text:
|
|
HatariTextInsert(self.hatari, text)
|
|
self.entry.set_text("")
|
|
|
|
def _enter_cb(self, widget):
|
|
self.hatari.insert_event("keypress 28") # Enter key scancode
|
|
|
|
def _doubleclick_cb(self, widget):
|
|
self.hatari.insert_event("doubleclick")
|
|
|
|
def _rightpress_cb(self, widget):
|
|
self.hatari.insert_event("rightdown")
|
|
|
|
def _rightrelease_cb(self, widget):
|
|
self.hatari.insert_event("rightup")
|
|
|
|
def run(self, hatari):
|
|
"run(hatari), do text/mouse click input"
|
|
self.hatari = hatari
|
|
self.dialog.run()
|
|
self.dialog.hide()
|
|
|
|
|
|
# ---------------------------
|
|
# Quit and Save dialog
|
|
|
|
class QuitSaveDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
dialog = Gtk.Dialog("Quit and Save?", parent,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
("Save changes", Gtk.ResponseType.YES,
|
|
"Ignore changes", Gtk.ResponseType.NO,
|
|
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))
|
|
dialog.vbox.add(Gtk.Label(label="You have unsaved configuration changes:"))
|
|
viewport = Gtk.Viewport()
|
|
viewport.add(Gtk.Label())
|
|
scrolledwindow = Gtk.ScrolledWindow()
|
|
scrolledwindow.set_propagate_natural_width(True)
|
|
scrolledwindow.set_propagate_natural_height(True)
|
|
scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
scrolledwindow.add(viewport)
|
|
dialog.vbox.add(scrolledwindow)
|
|
dialog.show_all()
|
|
self.scrolledwindow = scrolledwindow
|
|
self.viewport = viewport
|
|
self.dialog = dialog
|
|
|
|
def run(self, config):
|
|
"run(config) -> False if canceled, True otherwise or if no changes"
|
|
changes = []
|
|
for key, value in config.get_changes():
|
|
changes.append("%s = %s" % (key, str(value)))
|
|
if not changes:
|
|
return True
|
|
child = self.viewport.get_child()
|
|
child.set_text(config.get_path() + ":\n" + "\n".join(changes))
|
|
width, height = child.get_size_request()
|
|
if height < 320:
|
|
self.scrolledwindow.set_size_request(width, height)
|
|
else:
|
|
self.scrolledwindow.set_size_request(-1, 320)
|
|
self.viewport.show_all()
|
|
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == Gtk.ResponseType.CANCEL:
|
|
return False
|
|
if response == Gtk.ResponseType.YES:
|
|
config.save()
|
|
return True
|
|
|
|
|
|
# ---------------------------
|
|
# Kill Hatari dialog
|
|
|
|
class KillDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
self.dialog = Gtk.MessageDialog(parent,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL,
|
|
"""\
|
|
Hatari emulator is already/still running and it needs to be terminated first. If emulated applications contain unsaved data, that will be lost.
|
|
|
|
Terminate Hatari anyway?""")
|
|
|
|
def run(self, hatari):
|
|
"run(hatari) -> True if Hatari killed, False if left running"
|
|
if not hatari.is_running():
|
|
return True
|
|
# Hatari is running, OK to kill?
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == Gtk.ResponseType.OK:
|
|
hatari.kill()
|
|
return True
|
|
return False
|
|
|
|
|
|
# ---------------------------
|
|
# Reset Hatari dialog
|
|
|
|
class ResetDialog(HatariUIDialog):
|
|
COLD = 1
|
|
WARM = 2
|
|
def __init__(self, parent):
|
|
self.dialog = Gtk.Dialog("Reset Atari?", parent,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
("Cold reset", self.COLD, "Warm reset", self.WARM,
|
|
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))
|
|
label = Gtk.Label(label="\nRebooting will lose changes in currently\nrunning Atari programs.\n\nReset anyway?\n")
|
|
self.dialog.vbox.add(label)
|
|
label.show()
|
|
|
|
def run(self, hatari):
|
|
"run(hatari) -> True if Hatari rebooted, False if canceled"
|
|
if not hatari.is_running():
|
|
return False
|
|
# Hatari is running, how to reboot?
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == self.COLD:
|
|
hatari.trigger_shortcut("coldreset")
|
|
elif response == self.WARM:
|
|
hatari.trigger_shortcut("warmreset")
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
|
|
# ----------------------------------
|
|
# Floppy image dialog
|
|
|
|
class FloppyDialog(HatariUIDialog):
|
|
def _create_dialog(self, config):
|
|
table, self.dialog = create_table_dialog(self.parent, "Floppy images", 4, 2)
|
|
factory = FselAndEjectFactory()
|
|
|
|
row = 0
|
|
self.floppy = []
|
|
path = config.get_floppydir()
|
|
for drive in ("A", "B"):
|
|
label = "Disk %c image:" % drive
|
|
fname = config.get_floppy(row)
|
|
fsel, box = factory.get(label, path, fname, Gtk.FileChooserAction.OPEN)
|
|
table_add_widget_row(table, row, 0, label, box)
|
|
self.floppy.append(fsel)
|
|
row += 1
|
|
|
|
protect = table_add_combo_row(table, row, 0, "Write protection:", config.get_protection_types())
|
|
protect.set_tooltip_text("Write protect floppy image contents")
|
|
protect.set_active(config.get_floppy_protection())
|
|
row += 1
|
|
|
|
vbox = Gtk.VBox()
|
|
ds = Gtk.CheckButton("Double sided drives")
|
|
ds.set_tooltip_text("Whether drives are double or single sided. Can affect behavior of some games")
|
|
ds.set_active(config.get_doublesided())
|
|
vbox.add(ds)
|
|
|
|
driveb = Gtk.CheckButton("Drive B connected")
|
|
driveb.set_tooltip_text("Whether drive B is connected. Can affect behavior of some demos & games")
|
|
driveb.set_active(config.get_floppy_drives()[1])
|
|
vbox.add(driveb)
|
|
|
|
fastfdc = Gtk.CheckButton("Fast floppy access")
|
|
fastfdc.set_tooltip_text("Can cause incompatibilities with some games/demos")
|
|
fastfdc.set_active(config.get_fastfdc())
|
|
vbox.add(fastfdc)
|
|
|
|
table_add_widget_row(table, row, 0, None, vbox)
|
|
row += 1
|
|
|
|
table.show_all()
|
|
|
|
self.protect = protect
|
|
self.fastfdc = fastfdc
|
|
self.driveb = driveb
|
|
self.ds = ds
|
|
|
|
def run(self, config):
|
|
"run(config), show disk image dialog"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
|
|
if response == Gtk.ResponseType.APPLY:
|
|
config.lock_updates()
|
|
for drive in range(2):
|
|
config.set_floppy(drive, self.floppy[drive].get_filename())
|
|
config.set_floppy_protection(self.protect.get_active())
|
|
config.set_doublesided(self.ds.get_active())
|
|
config.set_fastfdc(self.fastfdc.get_active())
|
|
drives = (config.get_floppy_drives()[0], self.driveb.get_active())
|
|
config.set_floppy_drives(drives)
|
|
config.flush_updates()
|
|
|
|
|
|
# ----------------------------------
|
|
# Hard disk dialog
|
|
|
|
class HardDiskDialog(HatariUIDialog):
|
|
def _create_dialog(self, config):
|
|
table, self.dialog = create_table_dialog(self.parent, "Hard disks", 4, 2, "Set and reboot")
|
|
factory = FselAndEjectFactory()
|
|
|
|
row = 0
|
|
label = "ASCI HD image:"
|
|
path = config.get_acsi_image()
|
|
fsel, box = factory.get(label, None, path, Gtk.FileChooserAction.OPEN)
|
|
table_add_widget_row(table, row, 0, label, box, True)
|
|
self.acsi = fsel
|
|
row += 1
|
|
|
|
label = "IDE HD master image:"
|
|
path = config.get_idemaster_image()
|
|
fsel, box = factory.get(label, None, path, Gtk.FileChooserAction.OPEN)
|
|
table_add_widget_row(table, row, 0, label, box, True)
|
|
self.idemaster = fsel
|
|
row += 1
|
|
|
|
label = "IDE HD slave image:"
|
|
path = config.get_ideslave_image()
|
|
fsel, box = factory.get(label, None, path, Gtk.FileChooserAction.OPEN)
|
|
table_add_widget_row(table, row, 0, label, box, True)
|
|
self.ideslave = fsel
|
|
row += 1
|
|
|
|
table_add_widget_row(table, row, 0, " ", Gtk.HSeparator(), True)
|
|
row += 1
|
|
|
|
label = "GEMDOS drive directory:"
|
|
path = config.get_hd_dir()
|
|
fsel, box = factory.get(label, None, path, Gtk.FileChooserAction.SELECT_FOLDER)
|
|
table_add_widget_row(table, row, 0, label, box, True)
|
|
self.hddir = fsel
|
|
row += 1
|
|
|
|
hddrive = table_add_combo_row(table, row, 0, "GEMDOS HD drive:", config.get_hd_drives())
|
|
hddrive.set_tooltip_text("Whether GEMDOS HD emulation uses fixed drive letter, or first free drive letter after ASCI & IDE drives (detection unreliable)")
|
|
self.hddrive = hddrive
|
|
row += 1
|
|
|
|
protect = table_add_combo_row(table, row, 0, "Write protection:", config.get_protection_types())
|
|
protect.set_tooltip_text("Whether/how to write protect (GEMDOS HD) emulation files, 'auto' means using host files' own properties")
|
|
self.protect = protect
|
|
row += 1
|
|
|
|
lower = table_add_combo_row(table, row, 0, "File names:", config.get_hd_cases())
|
|
lower.set_tooltip_text("What to do with names of files created by Atari programs through GEMDOS HD emulation")
|
|
self.lower = lower
|
|
|
|
table.show_all()
|
|
|
|
def _get_config(self, config):
|
|
path = config.get_acsi_image()
|
|
if path:
|
|
self.acsi.set_filename(path)
|
|
path = config.get_idemaster_image()
|
|
if path:
|
|
self.idemaster.set_filename(path)
|
|
path = config.get_ideslave_image()
|
|
if path:
|
|
self.ideslave.set_filename(path)
|
|
path = config.get_hd_dir()
|
|
if path:
|
|
self.hddir.set_filename(path)
|
|
self.hddrive.set_active(config.get_hd_drive())
|
|
self.protect.set_active(config.get_hd_protection())
|
|
self.lower.set_active(config.get_hd_case())
|
|
|
|
def _set_config(self, config):
|
|
config.lock_updates()
|
|
config.set_acsi_image(self.acsi.get_filename())
|
|
config.set_idemaster_image(self.idemaster.get_filename())
|
|
config.set_ideslave_image(self.ideslave.get_filename())
|
|
config.set_hd_dir(self.hddir.get_filename())
|
|
config.set_hd_drive(self.hddrive.get_active())
|
|
config.set_hd_protection(self.protect.get_active())
|
|
config.set_hd_case(self.lower.get_active())
|
|
config.flush_updates()
|
|
|
|
def run(self, config):
|
|
"run(config) -> bool, whether to reboot"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
|
|
self._get_config(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == Gtk.ResponseType.APPLY:
|
|
self._set_config(config)
|
|
return True
|
|
return False
|
|
|
|
|
|
# ---------------------------
|
|
# Display dialog
|
|
|
|
class DisplayDialog(HatariUIDialog):
|
|
|
|
def _create_dialog(self, config):
|
|
table, self.dialog = create_table_dialog(self.parent, "Display settings", 9, 2)
|
|
|
|
row = 0
|
|
col = 0
|
|
self.skip = table_add_combo_row(table, row, col, "Frameskip:", config.get_frameskip_names())
|
|
self.skip.set_tooltip_text("Set how many frames are skipped to speed up emulation")
|
|
row += 1
|
|
|
|
self.slow = table_add_combo_row(table, row, col, "Slowdown:", config.get_slowdown_names())
|
|
self.slow.set_tooltip_text("VBL wait multiplier to slow down emulation. Breaks sound and large enough slowdown causes mouse clicks not to work.")
|
|
row += 1
|
|
|
|
topw, toph = config.get_desktop_size()
|
|
maxw = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 320, topw, 8)
|
|
maxh = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 200, toph, 8)
|
|
maxw.set_tooltip_text("Preferred/maximum zoomed width")
|
|
maxh.set_tooltip_text("Preferred/maximum zoomed height")
|
|
maxw.set_digits(0)
|
|
maxh.set_digits(0)
|
|
|
|
self.maxw = table_add_widget_row(table, row, col, "Max zoom width:", maxw)
|
|
row += 1
|
|
self.maxh = table_add_widget_row(table, row, col, "Max zoom height:", maxh)
|
|
row += 1
|
|
|
|
vbox = Gtk.VBox()
|
|
force_max = Gtk.CheckButton("Force max resolution (Falcon)")
|
|
force_max.set_tooltip_text("Force maximum resolution to help recording videos of demos which do resolution changes")
|
|
self.force_max = force_max
|
|
vbox.add(force_max)
|
|
|
|
desktop = Gtk.CheckButton("Keep desktop resolution (scales)")
|
|
desktop.set_tooltip_text("Keep screen resolution in fullscreen. Avoids potential monitor res switch delay & resulting sound skips")
|
|
self.desktop = desktop
|
|
vbox.add(desktop)
|
|
|
|
borders = Gtk.CheckButton("Atari screen borders")
|
|
borders.set_tooltip_text("Show overscan borders in ST/STE low/mid-rez and in Falcon color resolutions. Visible border area is affected by max zoom size")
|
|
self.borders = borders
|
|
vbox.add(borders)
|
|
|
|
led = Gtk.CheckButton("Show overlay led")
|
|
led.set_tooltip_text("Show overlay drive led when statusbar is not visible")
|
|
self.led = led
|
|
vbox.add(led)
|
|
|
|
statusbar = Gtk.CheckButton("Show statusbar")
|
|
statusbar.set_tooltip_text("Show statusbar with TOS and machine info, disk leds etc")
|
|
self.statusbar = statusbar
|
|
vbox.add(statusbar)
|
|
|
|
crop = Gtk.CheckButton("Remove statusbar from screen capture")
|
|
crop.set_tooltip_text("Crop statusbar from screenshots and video recordings")
|
|
self.crop = crop
|
|
vbox.add(crop)
|
|
|
|
table_add_widget_row(table, row, col, None, vbox)
|
|
table.show_all()
|
|
|
|
|
|
def _get_config(self, config):
|
|
self.slow.set_active(0)
|
|
self.skip.set_active(config.get_frameskip())
|
|
confw, confh = config.get_max_size()
|
|
self.maxw.set_value(confw)
|
|
self.maxh.set_value(confh)
|
|
self.force_max.set_active(config.get_force_max())
|
|
self.desktop.set_active(config.get_desktop())
|
|
self.borders.set_active(config.get_borders())
|
|
self.led.set_active(config.get_led())
|
|
self.statusbar.set_active(config.get_statusbar())
|
|
self.crop.set_active(config.get_crop())
|
|
|
|
def _set_config(self, config):
|
|
config.lock_updates()
|
|
config.set_slowdown(self.slow.get_active())
|
|
config.set_frameskip(self.skip.get_active())
|
|
config.set_max_size(self.maxw.get_value(), self.maxh.get_value())
|
|
config.set_force_max(self.force_max.get_active())
|
|
config.set_desktop(self.desktop.get_active())
|
|
config.set_borders(self.borders.get_active())
|
|
config.set_led(self.led.get_active())
|
|
config.set_statusbar(self.statusbar.get_active())
|
|
config.set_crop(self.crop.get_active())
|
|
config.flush_updates()
|
|
|
|
def run(self, config):
|
|
"run(config), show display dialog"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
self._get_config(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == Gtk.ResponseType.APPLY:
|
|
self._set_config(config)
|
|
|
|
|
|
# ----------------------------------
|
|
# Joystick dialog
|
|
|
|
class JoystickDialog(HatariUIDialog):
|
|
def _create_dialog(self, config):
|
|
table, self.dialog = create_table_dialog(self.parent, "Joystick settings", 9, 2)
|
|
|
|
joy = 0
|
|
self.joy = []
|
|
joytypes = config.get_joystick_types()
|
|
for label in config.get_joystick_names():
|
|
combo = table_add_combo_row(table, joy, 0, "%s:" % label, joytypes)
|
|
combo.set_active(config.get_joystick(joy))
|
|
self.joy.append(combo)
|
|
joy += 1
|
|
|
|
table.show_all()
|
|
|
|
def run(self, config):
|
|
"run(config), show joystick dialog"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
|
|
if response == Gtk.ResponseType.APPLY:
|
|
config.lock_updates()
|
|
for joy in range(6):
|
|
config.set_joystick(joy, self.joy[joy].get_active())
|
|
config.flush_updates()
|
|
|
|
|
|
# ---------------------------------------
|
|
# Peripherals (midi,printer,rs232) dialog
|
|
|
|
class PeripheralDialog(HatariUIDialog):
|
|
def _create_dialog(self, config):
|
|
midi = Gtk.CheckButton("Enable MIDI")
|
|
midi.set_active(config.get_midi())
|
|
|
|
printer = Gtk.CheckButton("Enable printer output")
|
|
printer.set_active(config.get_printer())
|
|
|
|
rs232 = Gtk.CheckButton("Enable RS232/MFP (!Falcon)")
|
|
rs232.set_active(config.get_rs232())
|
|
|
|
scca = Gtk.CheckButton("Enable RS232/SCC-A output (MegaSTE/TT/Falcon)")
|
|
scca.set_active(config.get_scca())
|
|
|
|
scca_lan = Gtk.CheckButton("Enable RS232/sCC-A Lan output (MegaSTE/TT/Falcon)")
|
|
scca_lan.set_active(config.get_scca_lan())
|
|
|
|
sccb = Gtk.CheckButton("Enable RS232/SCC-B output (MegaSTE/TT/Falcon)")
|
|
sccb.set_active(config.get_sccb())
|
|
|
|
dialog = Gtk.Dialog("Peripherals", self.parent,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY,
|
|
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))
|
|
dialog.vbox.add(midi)
|
|
dialog.vbox.add(printer)
|
|
dialog.vbox.add(rs232)
|
|
dialog.vbox.add(scca)
|
|
dialog.vbox.add(scca_lan)
|
|
dialog.vbox.add(sccb)
|
|
dialog.vbox.show_all()
|
|
|
|
self.dialog = dialog
|
|
self.printer = printer
|
|
self.rs232 = rs232
|
|
self.scca = scca
|
|
self.scca_lan = scca_lan
|
|
self.sccb = sccb
|
|
self.midi = midi
|
|
|
|
def run(self, config):
|
|
"run(config), show peripherals dialog"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
|
|
if response == Gtk.ResponseType.APPLY:
|
|
config.lock_updates()
|
|
config.set_midi(self.midi.get_active())
|
|
config.set_printer(self.printer.get_active())
|
|
config.set_rs232(self.rs232.get_active())
|
|
config.set_scca(self.scca.get_active())
|
|
config.set_scca_lan(self.scca_lan.get_active())
|
|
config.set_sccb(self.sccb.get_active())
|
|
config.flush_updates()
|
|
|
|
|
|
# ---------------------------------------
|
|
# Path dialog
|
|
|
|
class PathDialog(HatariUIDialog):
|
|
def _create_dialog(self, config):
|
|
paths = config.get_paths()
|
|
table, self.dialog = create_table_dialog(self.parent, "File path settings", len(paths), 2)
|
|
row = 0
|
|
self.paths = []
|
|
# sort by path label
|
|
paths.sort(key=lambda info: info[2])
|
|
for (key, path, label) in paths:
|
|
label = "%s:" % label
|
|
fsel = FselEntry(self.dialog, title=label, validate=self._validate_fname, data=key)
|
|
fsel.set_filename(path)
|
|
self.paths.append((key, fsel))
|
|
table_add_widget_row(table, row, 0, label, fsel.get_container())
|
|
row += 1
|
|
table.show_all()
|
|
|
|
def _validate_fname(self, key, fname):
|
|
if key != "soundout":
|
|
return True
|
|
if fname.rsplit(".", 1)[-1].lower() in ("ym", "wav"):
|
|
return True
|
|
ErrorDialog(self.dialog).run("Sound output file name:\n\t%s\nneeds to end with '.ym' or '.wav'." % fname)
|
|
return False
|
|
|
|
def run(self, config):
|
|
"run(config), show paths dialog"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
|
|
if response == Gtk.ResponseType.APPLY:
|
|
paths = []
|
|
for key, fsel in self.paths:
|
|
paths.append((key, fsel.get_filename()))
|
|
config.set_paths(paths)
|
|
|
|
|
|
# ---------------------------
|
|
# Sound dialog
|
|
|
|
class SoundDialog(HatariUIDialog):
|
|
|
|
def _create_dialog(self, config):
|
|
table, self.dialog = create_table_dialog(self.parent, "Sound settings", 6, 2, "Apply")
|
|
row = 0
|
|
|
|
col = 1
|
|
fullspan = True
|
|
self.enabled = table_add_widget_row(table, row, col, None, Gtk.CheckButton("Sound enabled"), fullspan)
|
|
row += 1
|
|
|
|
col = 0
|
|
self.hz = table_add_combo_row(table, row, col, "Sound frequency:", config.get_sound_values())
|
|
row += 1
|
|
|
|
self.ymmixer = table_add_combo_row(table, row, col, "YM mixing method:", config.get_ymmixer_types())
|
|
self.ymmixer.set_tooltip_text("Which method is used to mix YM voices")
|
|
row += 1
|
|
|
|
bufsize = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 100, 10)
|
|
for pos in ((0, "auto"), (10, "min"), (100, "max")):
|
|
bufsize.add_mark(pos[0], Gtk.PositionType.BOTTOM, pos[1])
|
|
bufsize.set_tooltip_text("SDL sound buffer size in ms. 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.")
|
|
bufsize.set_digits(0)
|
|
self.bufsize = table_add_widget_row(table, row, col, "Sound buffer size:", bufsize)
|
|
row += 1
|
|
|
|
vbox = Gtk.VBox()
|
|
self.sync = Gtk.CheckButton("Emulation speed synched to sound output")
|
|
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.")
|
|
vbox.add(self.sync)
|
|
|
|
self.mic = Gtk.CheckButton("Enable (Falcon) microphone")
|
|
vbox.add(self.mic)
|
|
|
|
col = 1
|
|
table_add_widget_row(table, row, col, None, vbox, fullspan)
|
|
row += 1
|
|
|
|
table.show_all()
|
|
|
|
def _get_config(self, config):
|
|
enabled, curhz = config.get_sound()
|
|
self.enabled.set_active(enabled)
|
|
self.hz.set_active(curhz)
|
|
|
|
self.sync.set_active(config.get_sync())
|
|
self.mic.set_active(config.get_mic())
|
|
self.ymmixer.set_active(config.get_ymmixer())
|
|
self.bufsize.set_value(config.get_bufsize())
|
|
|
|
def _set_config(self, config):
|
|
config.lock_updates()
|
|
enabled = self.enabled.get_active()
|
|
hz = self.hz.get_active()
|
|
config.set_sound(enabled, hz)
|
|
config.set_mic(self.mic.get_active())
|
|
config.set_sync(self.sync.get_active())
|
|
config.set_ymmixer(self.ymmixer.get_active())
|
|
config.set_bufsize(self.bufsize.get_value())
|
|
config.flush_updates()
|
|
|
|
def run(self, config):
|
|
"run(config), show sound dialog"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
|
|
self._get_config(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == Gtk.ResponseType.APPLY:
|
|
self._set_config(config)
|
|
|
|
|
|
# ---------------------------
|
|
# Trace settings dialog
|
|
|
|
class TraceDialog(HatariUIDialog):
|
|
# you can get this list with:
|
|
# hatari --trace help 2>&1|awk '/all$/{next} /^ [^-]/ {printf(" \"%s\",\n", $1)}'|sort
|
|
tracepoints = [
|
|
"acia",
|
|
"aes",
|
|
"bios",
|
|
"blitter",
|
|
"cpu_disasm",
|
|
"cpu_exception",
|
|
"cpu_pairing",
|
|
"cpu_regs",
|
|
"cpu_symbols",
|
|
"cpu_video_cycles",
|
|
"crossbar",
|
|
"dmasound",
|
|
"dsp_disasm",
|
|
"dsp_disasm_mem",
|
|
"dsp_disasm_reg",
|
|
"dsp_host_command",
|
|
"dsp_host_interface",
|
|
"dsp_host_ssi",
|
|
"dsp_interrupt",
|
|
"dsp_state",
|
|
"dsp_symbols",
|
|
"fdc",
|
|
"gemdos",
|
|
"ide",
|
|
"ikbd_acia",
|
|
"ikbd_cmds",
|
|
"ikbd_exec",
|
|
"int",
|
|
"io_read",
|
|
"io_write",
|
|
"keymap",
|
|
"mem",
|
|
"mfp_exception",
|
|
"mfp_read",
|
|
"mfp_start",
|
|
"mfp_write",
|
|
"midi",
|
|
"midi_raw",
|
|
"natfeats",
|
|
"nvram",
|
|
"os_base",
|
|
"psg_read",
|
|
"psg_write",
|
|
"scc",
|
|
"scsi_cmd",
|
|
"scsidrv",
|
|
"scu",
|
|
"vdi",
|
|
"videl",
|
|
"video_addr",
|
|
"video_border_h",
|
|
"video_border_v",
|
|
"video_color",
|
|
"video_hbl",
|
|
"video_res",
|
|
"video_ste",
|
|
"video_sync",
|
|
"video_vbl",
|
|
"xbios"
|
|
]
|
|
def __init__(self, parent):
|
|
self.savedpoints = None
|
|
hbox1 = Gtk.HBox()
|
|
hbox1.add(create_button("Load", self._load_traces))
|
|
hbox1.add(create_button("Clear", self._clear_traces))
|
|
hbox1.add(create_button("Save", self._save_traces))
|
|
hbox2 = Gtk.HBox()
|
|
vboxes = []
|
|
for idx in (0,1,2,3):
|
|
vboxes.append(Gtk.VBox())
|
|
hbox2.add(vboxes[idx])
|
|
|
|
count = 0
|
|
per_side = (len(self.tracepoints)+3)//4
|
|
self.tracewidgets = {}
|
|
for trace in self.tracepoints:
|
|
name = trace.replace("_", "-")
|
|
widget = Gtk.CheckButton(name)
|
|
self.tracewidgets[trace] = widget
|
|
vboxes[count//per_side].pack_start(widget, False, True, 0)
|
|
count += 1
|
|
|
|
dialog = Gtk.Dialog("Trace settings", parent,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY,
|
|
Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE))
|
|
dialog.vbox.add(hbox1)
|
|
dialog.vbox.add(Gtk.Label(label="Select trace points:"))
|
|
dialog.vbox.add(hbox2)
|
|
dialog.vbox.show_all()
|
|
self.dialog = dialog
|
|
|
|
def _get_traces(self):
|
|
traces = []
|
|
for trace in self.tracepoints:
|
|
if self.tracewidgets[trace].get_active():
|
|
traces.append(trace)
|
|
if traces:
|
|
return ",".join(traces)
|
|
return "none"
|
|
|
|
def _set_traces(self, tracepoints):
|
|
self._clear_traces()
|
|
if not tracepoints:
|
|
return
|
|
for trace in tracepoints.split(","):
|
|
if trace in self.tracewidgets:
|
|
self.tracewidgets[trace].set_active(True)
|
|
else:
|
|
print("ERROR: unknown trace setting '%s'" % trace)
|
|
|
|
def _clear_traces(self, widget = None):
|
|
for trace in self.tracepoints:
|
|
self.tracewidgets[trace].set_active(False)
|
|
|
|
def _load_traces(self, widget):
|
|
# this does not load traces, just sets them from internal variable
|
|
# that run method gets from caller and sets. It is up to caller
|
|
# whether the saving or loading happens actually to disk
|
|
self._set_traces(self.savedpoints)
|
|
|
|
def _save_traces(self, widget):
|
|
self.savedpoints = self._get_traces()
|
|
|
|
def run(self, hatari, savedpoints):
|
|
"run(hatari,tracepoints) -> tracepoints, caller saves tracepoints"
|
|
self.savedpoints = savedpoints
|
|
while 1:
|
|
response = self.dialog.run()
|
|
if response == Gtk.ResponseType.APPLY:
|
|
hatari.change_option("--trace %s" % self._get_traces())
|
|
else:
|
|
self.dialog.hide()
|
|
return self.savedpoints
|
|
|
|
|
|
# ------------------------------------------
|
|
# Machine dialog for settings needing reboot
|
|
|
|
class MachineDialog(HatariUIDialog):
|
|
def _machine_cb(self, widget, types):
|
|
machine = types[widget.get_active()].lower()
|
|
if "mega" in machine:
|
|
self.clocks.set_active(1)
|
|
self.cpulevel.set_active(0)
|
|
elif "st" in machine:
|
|
self.clocks.set_active(0)
|
|
self.cpulevel.set_active(0)
|
|
elif machine == "tt":
|
|
self.clocks.set_active(2)
|
|
self.cpulevel.set_active(3)
|
|
elif machine == "falcon":
|
|
self.clocks.set_active(1)
|
|
self.cpulevel.set_active(3)
|
|
self.dsp.set_active(2)
|
|
|
|
def _create_dialog(self, config):
|
|
table, self.dialog = create_table_dialog(self.parent, "Machine configuration", 6, 2, "Set and reboot")
|
|
|
|
# option for non-combos
|
|
fullspan = True
|
|
col = 0
|
|
row = 0
|
|
types = config.get_machine_types()
|
|
self.machine = table_add_combo_row(table, row, col, "Machine:", types, self._machine_cb, types)
|
|
row += 1
|
|
|
|
vbox1 = Gtk.VBox()
|
|
self.blitter = Gtk.CheckButton("Blitter (ST only)")
|
|
self.blitter.set_tooltip_text("Whether to emulate add-on Blitter chip for ST")
|
|
self.timerd = Gtk.CheckButton("Patch Timer-D")
|
|
self.timerd.set_tooltip_text("Improves ST/STE emulation performance, but some rare demos/games do not work with this")
|
|
vbox1.add(self.blitter)
|
|
vbox1.add(self.timerd)
|
|
table_add_widget_row(table, row, col, "Misc:", vbox1, fullspan)
|
|
row += 1
|
|
|
|
self.cpulevel = table_add_combo_row(table, row, col, "CPU type:", config.get_cpulevel_types())
|
|
row += 1
|
|
|
|
vbox2 = Gtk.VBox()
|
|
self.compatible = Gtk.CheckButton("Prefetch emulation")
|
|
self.compatible.set_tooltip_text("Needed for overscan and other timing sensitive things to work correctly. Uses more host CPU")
|
|
self.cache = Gtk.CheckButton(">=030 data cache emulation")
|
|
self.cache.set_tooltip_text("Data cache emulation increases emulated code performance, but uses significantly more host CPU")
|
|
self.exact = Gtk.CheckButton("Cycle exact emulation")
|
|
self.exact.set_tooltip_text("Cycle exactness increases emulation accuracy, but uses more host CPU")
|
|
self.mmu = Gtk.CheckButton("MMU emulation")
|
|
vbox2.add(self.compatible)
|
|
vbox2.add(self.cache)
|
|
vbox2.add(self.exact)
|
|
vbox2.add(self.mmu)
|
|
table_add_widget_row(table, row, col, None, vbox2, fullspan)
|
|
row += 1
|
|
|
|
self.clocks = table_add_combo_row(table, row, col, "CPU clock:", config.get_cpuclock_types())
|
|
row += 1
|
|
|
|
self.fpu = table_add_combo_row(table, row, col, "FPU type:", config.get_fpu_types())
|
|
row += 1
|
|
|
|
self.softfp = Gtk.CheckButton("Accurate FPU emulation")
|
|
self.softfp.set_tooltip_text("Emulate FPU in software instead of using host FPU. Uses more host CPU")
|
|
table_add_widget_row(table, row, col, None, self.softfp, fullspan)
|
|
row += 1
|
|
|
|
self.dsp = table_add_combo_row(table, row, col, "DSP type:", config.get_dsp_types())
|
|
self.dsp.set_tooltip_text("Disable DSP to improve Hatari performance significantly for Falcon programs that work (also) without it. Some programs using DSP unconditionally, may work with dummy mode (and just lack e.g. sound).")
|
|
row += 1
|
|
|
|
self.monitors = table_add_combo_row(table, row, col, "Monitor:", config.get_monitor_types())
|
|
row += 1
|
|
|
|
self.memory = table_add_combo_row(table, row, col, "Memory:", config.get_memory_names())
|
|
row += 1
|
|
|
|
# use next table column
|
|
col = 2
|
|
|
|
ttram = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 1024, 4)
|
|
ttram.set_digits(0)
|
|
ttram.set_tooltip_text("TT-RAM requires Falcon/TT and its use disables 24-bit addressing.")
|
|
self.ttram = table_add_widget_row(table, row, col, "TT-RAM:", ttram, fullspan)
|
|
row += 1
|
|
|
|
label = "TOS image:"
|
|
fsel = self._fsel(label, Gtk.FileChooserAction.OPEN)
|
|
self.tos = table_add_widget_row(table, row, col, label, fsel, fullspan)
|
|
row += 1
|
|
|
|
table.show_all()
|
|
|
|
def _fsel(self, label, action):
|
|
fsel = Gtk.FileChooserButton(title=label)
|
|
# Hatari cannot access URIs
|
|
fsel.set_local_only(True)
|
|
fsel.set_width_chars(12)
|
|
fsel.set_action(action)
|
|
return fsel
|
|
|
|
def _get_config(self, config):
|
|
self.machine.set_active(config.get_machine())
|
|
self.blitter.set_active(config.get_blitter())
|
|
self.timerd.set_active(config.get_timerd())
|
|
self.cpulevel.set_active(config.get_cpulevel())
|
|
self.compatible.set_active(config.get_compatible())
|
|
self.cache.set_active(config.get_data_cache())
|
|
self.exact.set_active(config.get_cycle_exact())
|
|
self.mmu.set_active(config.get_mmu())
|
|
self.clocks.set_active(config.get_cpuclock())
|
|
self.fpu.set_active(config.get_fpu_type())
|
|
self.softfp.set_active(config.get_fpu_soft())
|
|
self.dsp.set_active(config.get_dsp())
|
|
self.monitors.set_active(config.get_monitor())
|
|
self.memory.set_active(config.get_memory())
|
|
self.ttram.set_value(config.get_ttram())
|
|
tos = config.get_tos()
|
|
if tos:
|
|
self.tos.set_filename(tos)
|
|
|
|
def _set_config(self, config):
|
|
config.lock_updates()
|
|
config.set_machine(self.machine.get_active())
|
|
config.set_blitter(self.blitter.get_active())
|
|
config.set_timerd(self.timerd.get_active())
|
|
config.set_cpulevel(self.cpulevel.get_active())
|
|
config.set_compatible(self.compatible.get_active())
|
|
config.set_data_cache(self.cache.get_active())
|
|
config.set_cycle_exact(self.exact.get_active())
|
|
config.set_mmu(self.mmu.get_active())
|
|
config.set_cpuclock(self.clocks.get_active())
|
|
config.set_fpu_type(self.fpu.get_active())
|
|
config.set_fpu_soft(self.softfp.get_active())
|
|
config.set_dsp(self.dsp.get_active())
|
|
config.set_monitor(self.monitors.get_active())
|
|
config.set_memory(self.memory.get_active())
|
|
# changes 24/32-bit addressing based on TT-RAM & machine type
|
|
config.set_ttram(self.ttram.get_value(), self.machine.get_active())
|
|
config.set_tos(self.tos.get_filename())
|
|
config.flush_updates()
|
|
|
|
def run(self, config):
|
|
"run(config) -> bool, whether to reboot"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
|
|
self._get_config(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == Gtk.ResponseType.APPLY:
|
|
self._set_config(config)
|
|
return True
|
|
return False
|