mirror of
https://github.com/libretro/hatari.git
synced 2026-11-12 18:40:15 -10:00
for all the required hatari variables - list (in comment) which Hatari variables are not yet supported - disable Hatari internal quit confirmation when starting Hatari
588 lines
20 KiB
Python
588 lines
20 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# Classes for the Hatari UI dialogs
|
|
#
|
|
# Copyright (C) 2008 by Eero Tamminen <[email protected]>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
|
|
import os
|
|
# use correct version of pygtk/gtk
|
|
import pygtk
|
|
pygtk.require('2.0')
|
|
import gtk
|
|
import pango
|
|
|
|
from uihelpers import UInfo, create_button, create_table_dialog, \
|
|
table_add_entry_row, table_add_widget_row, table_add_separator
|
|
|
|
|
|
# -----------------
|
|
# Dialog base class
|
|
|
|
class HatariUIDialog():
|
|
def __init__(self, parent):
|
|
"<any>Dialog(parent) -> object"
|
|
self.parent = parent
|
|
self.dialog = None
|
|
|
|
def run(self):
|
|
"""run() -> response. Shows dialog and returns response,
|
|
subclasses overriding run() require also an argument."""
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
return response
|
|
|
|
|
|
# ---------------------------
|
|
# Note/Todo/Error/Ask dialogs
|
|
|
|
class NoteDialog(HatariUIDialog):
|
|
icontype = gtk.MESSAGE_INFO
|
|
textpattern = "\n%s"
|
|
def run(self, text):
|
|
"run(text), show message dialog with given text"
|
|
dialog = gtk.MessageDialog(self.parent,
|
|
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
self.icontype, gtk.BUTTONS_CLOSE, self.textpattern % text)
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
class TodoDialog(NoteDialog):
|
|
textpattern = "\nTODO: %s"
|
|
|
|
class ErrorDialog(NoteDialog):
|
|
icontype = gtk.MESSAGE_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.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, text)
|
|
response = dialog.run()
|
|
dialog.destroy()
|
|
return (response == gtk.RESPONSE_YES)
|
|
|
|
|
|
# ---------------------------
|
|
# About dialog
|
|
|
|
class AboutDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
dialog = gtk.AboutDialog()
|
|
dialog.set_transient_for(parent)
|
|
dialog.set_name(UInfo.name)
|
|
dialog.set_version(UInfo.version)
|
|
dialog.set_website("http://hatari.sourceforge.net/")
|
|
dialog.set_website_label("Hatari emulator www-site")
|
|
dialog.set_authors(["Eero Tamminen"])
|
|
dialog.set_artists(["The logo is from Hatari"])
|
|
dialog.set_logo(gtk.gdk.pixbuf_new_from_file(UInfo.logo))
|
|
dialog.set_translator_credits("translator-credits")
|
|
dialog.set_copyright("UI copyright (C) 2008 by Eero Tamminen")
|
|
dialog.set_license("""
|
|
This software is licenced under GPL v2.
|
|
|
|
You can see the whole license at:
|
|
http://www.gnu.org/licenses/info/GPLv2.html""")
|
|
self.dialog = dialog
|
|
|
|
|
|
# ---------------------------
|
|
# Paste dialog
|
|
|
|
class PasteDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
dialog = gtk.Dialog("Insert text", parent,
|
|
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
("Paste", gtk.RESPONSE_YES))
|
|
entry = gtk.Entry()
|
|
entry.connect("activate", self._entry_cb)
|
|
hbox = gtk.HBox()
|
|
hbox.add(gtk.Label("Text:"))
|
|
hbox.add(entry)
|
|
dialog.vbox.add(hbox)
|
|
dialog.show_all()
|
|
self.dialog = dialog
|
|
self.entry = entry
|
|
|
|
def _entry_cb(self, widget):
|
|
self.dialog.response(gtk.RESPONSE_YES)
|
|
|
|
def run(self):
|
|
"run() -> text to insert"
|
|
self.entry.set_text("")
|
|
if self.dialog.run() == gtk.RESPONSE_YES:
|
|
text = self.entry.get_text()
|
|
else:
|
|
text = None
|
|
self.dialog.hide()
|
|
return text
|
|
|
|
|
|
# ---------------------------
|
|
# Quit and Save dialog
|
|
|
|
class QuitSaveDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
dialog = gtk.Dialog("Quit and Save?", parent,
|
|
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
("Save changes", gtk.RESPONSE_YES,
|
|
"Discard changes", gtk.RESPONSE_NO,
|
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
|
dialog.vbox.add(gtk.Label("You have unsaved configuration changes:"))
|
|
viewport = gtk.Viewport()
|
|
viewport.add(gtk.Label())
|
|
scrolledwindow = gtk.ScrolledWindow()
|
|
scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_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) -> RESPONSE_CANCEL if dialog is canceled"
|
|
changes = []
|
|
for key, value in config.get_changes():
|
|
changes.append("%s = %s" % (key, str(value)))
|
|
if not changes:
|
|
return gtk.RESPONSE_NO
|
|
child = self.viewport.get_child()
|
|
child.set_text("\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.RESPONSE_YES:
|
|
config.save()
|
|
return response
|
|
|
|
|
|
# ---------------------------
|
|
# Kill Hatari dialog
|
|
|
|
class KillDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
self.dialog = gtk.MessageDialog(parent,
|
|
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,
|
|
"""\
|
|
Hatari emulator is already/still running and it needs to be terminated first. However, if it contains unsaved data, that will be lost.
|
|
|
|
Terminate Hatari anyway?""")
|
|
|
|
def run(self, hatari):
|
|
"run(hatari) -> False if Hatari killed, True if left running"
|
|
if not hatari.is_running():
|
|
return False
|
|
# Hatari is running, OK to kill?
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == gtk.RESPONSE_OK:
|
|
hatari.kill()
|
|
return False
|
|
return True
|
|
|
|
|
|
# ---------------------------
|
|
# Reset Hatari dialog
|
|
|
|
class ResetDialog(HatariUIDialog):
|
|
COLD = 1
|
|
WARM = 2
|
|
def __init__(self, parent):
|
|
self.dialog = gtk.Dialog("Reset Atari?", parent,
|
|
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
("Cold reset", self.COLD, "Warm reset", self.WARM,
|
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
|
label = gtk.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
|
|
|
|
|
|
# ---------------------------
|
|
# Trace settings dialog
|
|
|
|
class TraceDialog(HatariUIDialog):
|
|
# you can get this list with:
|
|
# hatari --trace help 2>&1|awk '/all$/{next} /^ [^-]/ {printf("\"%s\",\n", $1)}'
|
|
tracepoints = [
|
|
"video_sync",
|
|
"video_res",
|
|
"video_color",
|
|
"video_border_v",
|
|
"video_border_h",
|
|
"video_addr",
|
|
"video_hbl",
|
|
"video_vbl",
|
|
"video_ste",
|
|
"mfp_exception",
|
|
"mfp_start",
|
|
"mfp_read",
|
|
"mfp_write",
|
|
"psg_write_reg",
|
|
"psg_write_data",
|
|
"cpu_pairing",
|
|
"cpu_disasm",
|
|
"cpu_exception",
|
|
"int",
|
|
"fdc",
|
|
"ikbd",
|
|
"bios",
|
|
"xbios",
|
|
"gemdos",
|
|
"vdi",
|
|
"blitter"
|
|
]
|
|
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):
|
|
vboxes.append(gtk.VBox())
|
|
hbox2.add(vboxes[idx])
|
|
|
|
count = 0
|
|
per_side = (len(self.tracepoints)+2)/3
|
|
self.tracewidgets = {}
|
|
for trace in self.tracepoints:
|
|
widget = gtk.CheckButton(trace)
|
|
self.tracewidgets[trace] = widget
|
|
vboxes[count/per_side].pack_start(widget, False, True)
|
|
count += 1
|
|
|
|
dialog = gtk.Dialog("Trace settings", parent,
|
|
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
(gtk.STOCK_APPLY, gtk.RESPONSE_APPLY,
|
|
gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
|
|
dialog.vbox.add(hbox1)
|
|
dialog.vbox.add(gtk.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()
|
|
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 doesn't load traces, just sets them from internal variable
|
|
# that run method gets from caller and sets. It's 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.RESPONSE_APPLY:
|
|
hatari.change_option("--trace %s" % self._get_traces())
|
|
else:
|
|
self.dialog.hide()
|
|
return self.savedpoints
|
|
|
|
|
|
# ----------------------------------
|
|
# Peripherals (disk/joystick) dialog
|
|
|
|
class PeripheralsDialog(HatariUIDialog):
|
|
def _create_dialog(self, config):
|
|
table, self.dialog = create_table_dialog(self.parent, "Peripheral settings", 9)
|
|
|
|
row = 0
|
|
self.file = []
|
|
for drive in ("A", "B"):
|
|
label = "Disk %c:" % drive
|
|
fsel = gtk.FileChooserButton(label)
|
|
# Hatari cannot access URIs
|
|
fsel.set_local_only(True)
|
|
fsel.set_width_chars(12)
|
|
filename = config.get_floppy(row)
|
|
if filename:
|
|
fsel.set_filename(filename)
|
|
self.file.append(table_add_widget_row(table, row, label, fsel))
|
|
row += 1
|
|
|
|
table_add_separator(table, row)
|
|
row += 1
|
|
|
|
joy = 0
|
|
self.joy = []
|
|
joytypes = config.get_joystick_types()
|
|
for label in config.get_joystick_names():
|
|
combo = gtk.combo_box_new_text()
|
|
for text in joytypes:
|
|
combo.append_text(text)
|
|
combo.set_active(config.get_joystick(joy))
|
|
self.joy.append(table_add_widget_row(table, row + joy, label, combo))
|
|
joy += 1
|
|
|
|
# TODO: add printer, serial, midi, RTC to peripherals?
|
|
table.show_all()
|
|
|
|
def run(self, config):
|
|
"run() -> file name, file name for given disk"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
|
|
if response == gtk.RESPONSE_APPLY:
|
|
for drive in range(2):
|
|
config.set_floppy(drive, self.file[drive].get_filename())
|
|
for joy in range(6):
|
|
config.set_joystick(joy, self.joy[joy].get_active())
|
|
|
|
|
|
# ----------------------------------------
|
|
# Setup dialog for settings needing reboot
|
|
|
|
class SetupDialog(HatariUIDialog):
|
|
def __init__(self, parent):
|
|
self.setups = []
|
|
self.parent = parent
|
|
self.editdialog = None
|
|
self.dialog = None
|
|
|
|
def _create_dialog(self, config):
|
|
dialog = gtk.Dialog("Machine configurations", self.parent,
|
|
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
|
("Set and reboot", gtk.RESPONSE_APPLY,
|
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
|
self.editdialog = EditSetupDialog(dialog)
|
|
|
|
box1 = gtk.HBox()
|
|
box1.add(create_button("Add", self._add_setup, config))
|
|
box1.add(create_button("Edit", self._edit_setup, config))
|
|
box1.add(create_button("Remove", self._remove_setup))
|
|
dialog.vbox.add(box1)
|
|
|
|
box2 = gtk.HBox()
|
|
self.combo = gtk.combo_box_new_text()
|
|
self.combo.connect("changed", self._show_setup, config)
|
|
box2.pack_start(gtk.Label("Machine setup:"), False, False)
|
|
box2.add(self.combo)
|
|
dialog.vbox.add(box2)
|
|
|
|
dialog.vbox.add(gtk.HSeparator())
|
|
self.label = gtk.Label()
|
|
dialog.vbox.add(self.label)
|
|
|
|
dialog.vbox.show_all()
|
|
self.dialog = dialog
|
|
|
|
self.setups.append({
|
|
"machine": config.get_machine(),
|
|
"monitor": config.get_monitor(),
|
|
"memory": config.get_memory(),
|
|
"tos": config.get_tos(),
|
|
"harddisk": config.get_harddisk(),
|
|
"compatible": config.get_compatible(),
|
|
"name": "Default"
|
|
})
|
|
self.combo.append_text("Default")
|
|
self.combo.set_active(0)
|
|
|
|
def _add_setup(self, widget, config):
|
|
current = self.combo.get_active()
|
|
setup = self.setups[current].copy()
|
|
setup["name"] = "NEW"
|
|
setup = self.editdialog.run(config, setup)
|
|
if setup:
|
|
self.setups.append(setup)
|
|
self.combo.append_text(setup["name"])
|
|
self.combo.set_active(current + 1)
|
|
|
|
def _edit_setup(self, widget, config):
|
|
current = self.combo.get_active()
|
|
if not current:
|
|
return ErrorDialog(self.parent).run("Cannot edit default setup")
|
|
setup = self.setups[current].copy()
|
|
setup = self.editdialog.run(config, setup)
|
|
if setup:
|
|
self.setups[current] = setup
|
|
self._show_setup(self.combo, config)
|
|
self.combo.remove_text(current)
|
|
self.combo.insert_text(current, setup["name"])
|
|
self.combo.set_active(current)
|
|
|
|
def _remove_setup(self, widget):
|
|
current = self.combo.get_active()
|
|
if not current:
|
|
return ErrorDialog(self.parent).run("Cannot delete default setup")
|
|
name = self.setups[current]["name"]
|
|
self.combo.set_active(current - 1)
|
|
self.combo.remove_text(current)
|
|
del(self.setups[current])
|
|
NoteDialog(self.parent).run("Current '%s' setup deleted" % name)
|
|
|
|
def _apply_setup(self, config):
|
|
setup = self.setups[self.combo.get_active()]
|
|
config.set_machine(setup["machine"])
|
|
config.set_monitor(setup["monitor"])
|
|
config.set_memory(setup["memory"])
|
|
config.set_tos(setup["tos"])
|
|
config.set_harddisk(setup["harddisk"])
|
|
config.set_compatible(setup["compatible"])
|
|
|
|
def _show_setup(self, combo, config):
|
|
setup = self.setups[combo.get_active()]
|
|
info = []
|
|
info.append("Machine type: %s" % config.get_machine_types()[setup["machine"]])
|
|
info.append("Monitor type: %s" % config.get_monitor_types()[setup["monitor"]])
|
|
info.append("Memory size: %s" % config.get_memory_sizes()[setup["memory"]])
|
|
info.append("TOS image: %s" % os.path.basename(setup["tos"]))
|
|
info.append("Harddisk dir: %s" % setup["harddisk"])
|
|
info.append("Compatible CPU: %s" % str(setup["compatible"]))
|
|
self.label.set_text("\n".join(info))
|
|
|
|
def run(self, config):
|
|
"run() -> bool, whether to reboot"
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == gtk.RESPONSE_APPLY:
|
|
self._apply_setup(config)
|
|
return True
|
|
return False
|
|
|
|
# ----------------------------------------------
|
|
# Dialog for adding/editing setup configurations
|
|
|
|
class EditSetupDialog(HatariUIDialog):
|
|
def _create_dialog(self, config):
|
|
table, self.dialog = create_table_dialog(self.parent, "Add/edit setup", 9)
|
|
|
|
row = 0
|
|
self.name = table_add_entry_row(table, row, "Setup name:")
|
|
row += 1
|
|
|
|
combo = gtk.combo_box_new_text()
|
|
for text in config.get_machine_types():
|
|
combo.append_text(text)
|
|
self.machine = table_add_widget_row(table, row, "Machine type:", combo)
|
|
row += 1
|
|
|
|
combo = gtk.combo_box_new_text()
|
|
for text in config.get_monitor_types():
|
|
combo.append_text(text)
|
|
self.monitor = table_add_widget_row(table, row, "Monitor type:", combo)
|
|
row += 1
|
|
|
|
combo = gtk.combo_box_new_text()
|
|
for text in config.get_memory_sizes():
|
|
combo.append_text(text)
|
|
self.memory = table_add_widget_row(table, row, "Memory size:", combo)
|
|
row += 1
|
|
|
|
label = "TOS image:"
|
|
fsel = gtk.FileChooserButton(label)
|
|
# Hatari cannot access URIs
|
|
fsel.set_local_only(True)
|
|
fsel.set_width_chars(12)
|
|
self.tos = table_add_widget_row(table, row, label, fsel)
|
|
row += 1
|
|
|
|
label = "Harddisk:"
|
|
fsel = gtk.FileChooserButton(label)
|
|
fsel.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
|
# Hatari cannot access URIs
|
|
fsel.set_local_only(True)
|
|
fsel.set_width_chars(12)
|
|
self.harddisk = table_add_widget_row(table, row, label, fsel)
|
|
row += 1
|
|
|
|
widget = gtk.CheckButton("Compatible CPU")
|
|
self.compatible = table_add_widget_row(table, row, None, widget)
|
|
row += 1
|
|
|
|
table.show_all()
|
|
|
|
def run(self, config, setup):
|
|
if not self.dialog:
|
|
self._create_dialog(config)
|
|
|
|
self.name.set_text(setup["name"])
|
|
self.machine.set_active(setup["machine"])
|
|
self.monitor.set_active(setup["monitor"])
|
|
self.memory.set_active(setup["memory"])
|
|
if setup["tos"]:
|
|
self.tos.set_filename(setup["tos"])
|
|
if setup["harddisk"]:
|
|
self.harddisk.set_filename(setup["harddisk"])
|
|
self.compatible.set_active(setup["compatible"])
|
|
|
|
response = self.dialog.run()
|
|
self.dialog.hide()
|
|
if response == gtk.RESPONSE_CANCEL:
|
|
return None
|
|
|
|
setup["name"] = self.name.get_text()
|
|
setup["machine"] = self.machine.get_active()
|
|
setup["monitor"] = self.monitor.get_active()
|
|
setup["memory"] = self.memory.get_active()
|
|
setup["tos"] = self.tos.get_filename()
|
|
setup["harddisk"] = self.harddisk.get_filename()
|
|
setup["compatible"] = self.compatible.get_active()
|
|
return setup
|