Clean up dialog 'response' handling - ensure dialogs can be closed.

This fixes the bug where the Wine app-usage dialog can't be closed with its 'OK' button.

This commit moves on_response() up to the 'Dialog' base class and override it in a few places where this is required.

 'response' is how dialogs close,  'delete-event' doesn't even fire for them, so I've removed this event handler from the dialogs.
This commit is contained in:
Daniel Johnson 2023-12-09 08:57:55 -05:00
parent b9970ff33d
commit 37fc312800
10 changed files with 55 additions and 60 deletions

View file

@ -18,22 +18,47 @@ from lutris.util.strings import gtk_safe
class Dialog(Gtk.Dialog):
"""A base class for dialogs that provides handling for the response signal;
you can override its on_response() methods, but that method will record
the response for you via 'response_type' or 'confirmed' and destory this
dialog if it isn't NONE."""
def __init__(self, title=None, parent=None, flags=0, buttons=None, **kwargs):
def __init__(self, title: str = None, parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0, buttons: Gtk.ButtonsType = None,
**kwargs):
super().__init__(title, parent, flags, buttons, **kwargs)
self.connect("delete-event", self.on_destroy)
self._response_type = Gtk.ResponseType.NONE
self.connect("response", self.on_response)
def on_destroy(self, _widget, _data=None):
self.destroy()
@property
def response_type(self) -> Gtk.ResponseType:
"""The response type of the response that occurred; initially this is NONE.
Use the GTK response() method to artificially generate a response, rather than
setting this."""
return self._response_type
def add_styled_button(self, button_text, response_id, css_class):
@property
def confirmed(self) -> bool:
"""True if 'response_type' is OK or YES."""
return self.response_type in (Gtk.ResponseType.OK, Gtk.ResponseType.YES)
def on_response(self, _dialog, response: Gtk.ResponseType) -> None:
"""Handles the dialog response; you can override this but by default
this records the response for 'response_type' and destroys the dialog."""
self._response_type = response
if response != Gtk.ResponseType.NONE:
self.destroy()
def add_styled_button(self, button_text: str, response_id: Gtk.ResponseType,
css_class: str):
button = self.add_button(button_text, response_id)
if css_class:
style_context = button.get_style_context()
style_context.add_class(css_class)
return button
def add_default_button(self, button_text, response_id, css_class="suggested-action"):
def add_default_button(self, button_text: str, response_id: Gtk.ResponseType,
css_class: str = "suggested-action"):
"""Adds a button to the dialog with a particular response id, but
also makes it the default and styles it as the suggested action."""
button = self.add_styled_button(button_text, response_id, css_class)
@ -44,7 +69,9 @@ class Dialog(Gtk.Dialog):
class ModalDialog(Dialog):
"""A base class of modal dialogs, which sets the flag for you."""
def __init__(self, title=None, parent=None, flags=0, buttons=None, **kwargs):
def __init__(self, title: str = None, parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0, buttons: Gtk.ButtonsType = None,
**kwargs):
super().__init__(title, parent, flags | Gtk.DialogFlags.MODAL, buttons, **kwargs)
self.set_destroy_with_parent(True)
@ -55,7 +82,9 @@ class ModelessDialog(Dialog):
its own window group, so it treats its own modal dialogs separately, and it resets
its transient-for after being created."""
def __init__(self, title=None, parent=None, flags=0, buttons=None, **kwargs):
def __init__(self, title: str = None, parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0, buttons: Gtk.ButtonsType = None,
**kwargs):
super().__init__(title, parent, flags, buttons, **kwargs)
# These are not stuck above the 'main' window, but can be
# re-ordered freely.
@ -80,7 +109,7 @@ class SavableModelessDialog(ModelessDialog):
"""This is a modeless dialog that has a Cancel and a Save button in the header-bar,
with a ctrl-S keyboard shortcut to save."""
def __init__(self, title, parent=None, **kwargs):
def __init__(self, title: str, parent: Gtk.Widget = None, **kwargs):
super().__init__(title, parent=parent, use_header_bar=True, **kwargs)
self.cancel_button = self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
@ -95,12 +124,6 @@ class SavableModelessDialog(ModelessDialog):
key, mod = Gtk.accelerator_parse("<Primary>s")
self.save_button.add_accelerator("clicked", self.accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
self.connect("response", self.on_response)
def on_response(self, _widget, response):
if response != Gtk.ResponseType.NONE:
self.destroy()
def on_save(self, _button):
pass
@ -320,7 +343,6 @@ class InstallOrPlayDialog(ModalDialog):
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
self.connect("response", self.on_response)
self.set_size_request(320, 120)
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 6)
@ -341,10 +363,9 @@ class InstallOrPlayDialog(ModalDialog):
self.action = action
def on_response(self, _widget, response):
logger.debug("Dialog response %s", response)
if response == Gtk.ResponseType.CANCEL:
self.action = None
self.destroy()
super().on_response(_widget, response)
class LaunchConfigSelectDialog(ModalDialog):
@ -352,11 +373,9 @@ class LaunchConfigSelectDialog(ModalDialog):
super().__init__(title=title, parent=parent, border_width=10)
self.config_index = 0
self.dont_show_again = False
self.confirmed = False
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
self.connect("response", self.on_response)
self.set_size_request(320, 120)
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 6)
@ -384,10 +403,6 @@ class LaunchConfigSelectDialog(ModalDialog):
def on_dont_show_checkbutton_toggled(self, _button):
self.dont_show_again = _button.get_active()
def on_response(self, _widget, response):
self.confirmed = response == Gtk.ResponseType.OK
self.destroy()
class ClientLoginDialog(GtkBuilderDialog):
glade_file = "dialog-lutris-login.ui"
@ -445,7 +460,6 @@ class InstallerSourceDialog(ModelessDialog):
ok_button = self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
ok_button.set_border_width(10)
self.connect("response", self.on_response)
self.scrolled_window = Gtk.ScrolledWindow()
self.scrolled_window.set_hexpand(True)
@ -462,9 +476,6 @@ class InstallerSourceDialog(ModelessDialog):
self.show_all()
def on_response(self, *args):
self.destroy()
class WarningMessageDialog(Gtk.MessageDialog):
def __init__(self, message, secondary_message="", parent=None):
@ -526,7 +537,6 @@ class HumbleBundleCookiesDialog(ModalDialog):
self.cookies_content = None
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
self.connect("response", self.on_response)
self.set_size_request(640, 512)
@ -561,10 +571,11 @@ class HumbleBundleCookiesDialog(ModalDialog):
self.show_all()
self.run()
def on_response(self, _widget, response):
def on_response(self, dialog, response):
if response == Gtk.ResponseType.CANCEL:
self.cookies_content = None
else:
buffer = self.textview.get_buffer()
self.cookies_content = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True)
self.destroy()
super().on_response(dialog, response)

View file

@ -17,7 +17,6 @@ class DownloadDialog(ModalDialog):
self.dialog_progress_box.connect("complete", self.download_complete)
self.dialog_progress_box.connect("cancel", self.download_cancelled)
self.connect("response", self.on_response)
self.get_content_area().add(self.dialog_progress_box)
self.show_all()
@ -29,13 +28,11 @@ class DownloadDialog(ModalDialog):
def download_complete(self, _widget, _data):
self.response(Gtk.ResponseType.OK)
self.destroy()
def download_cancelled(self, _widget):
self.response(Gtk.ResponseType.CANCEL)
self.destroy()
def on_response(self, _dialog, response):
if response == Gtk.ResponseType.DELETE_EVENT:
def on_response(self, dialog, response):
if response in (Gtk.ResponseType.DELETE_EVENT, Gtk.ResponseType.CANCEL):
self.dialog_progress_box.downloader.cancel()
self.destroy()
super().on_response(dialog, response)

View file

@ -49,17 +49,17 @@ class ImportGameDialog(ModelessDialog):
self.close_button = self.add_button(Gtk.STOCK_STOP, Gtk.ResponseType.CANCEL)
key, mod = Gtk.accelerator_parse("Escape")
self.close_button.add_accelerator("clicked", self.accelerators, key, mod, Gtk.AccelFlags.VISIBLE)
self.connect("response", self.on_response)
self.show_all()
self.search_call = AsyncCall(self.search_checksums, self.search_result_finished)
def on_response(self, _widget, response):
def on_response(self, dialog, response: Gtk.ResponseType) -> None:
if response in (Gtk.ResponseType.CLOSE, Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
if self.search_call:
self.search_call.stop_request.set()
else:
self.destroy()
return # don't actually close the dialog
super().on_response(dialog, response)
def get_file_labels_listbox(self, files):
listbox = Gtk.ListBox(vexpand=True)

View file

@ -11,7 +11,6 @@ from lutris.util.linux import gather_system_info
class IssueReportWindow(BaseApplicationWindow):
"""Window for collecting and sending issue reports"""
def __init__(self, application):
@ -46,7 +45,7 @@ class IssueReportWindow(BaseApplicationWindow):
action_buttons_alignment.add(self.action_buttons)
self.vbox.pack_start(action_buttons_alignment, False, True, 0)
cancel_button = self.get_action_button(_("C_ancel"), handler=self.on_destroy)
cancel_button = self.get_action_button(_("C_ancel"), handler=lambda *x: self.destroy())
self.action_buttons.add(cancel_button)
save_button = self.get_action_button(_("_Save"), handler=self.on_save)

View file

@ -125,7 +125,6 @@ class RunnerInstallDialog(ModelessDialog):
label = Gtk.Label.new(_("%s version management") % self.runner_info["name"])
self.vbox.add(label)
self.installing = {}
self.connect("response", self.on_destroy)
scrolled_listbox = Gtk.ScrolledWindow()
self.listbox = Gtk.ListBox()
@ -410,9 +409,7 @@ class RunnerInstallDialog(ModelessDialog):
from lutris.util.wine.wine import get_installed_wine_versions
get_installed_wine_versions.cache_clear()
def on_destroy(self, _dialog, _data=None):
"""Override delete handler to prevent closing while downloads are active"""
def on_response(self, dialog, response: Gtk.ResponseType) -> None:
if self.installing:
return True
self.destroy()
return True
return
super().on_response(dialog, response)

View file

@ -121,7 +121,6 @@ class UninstallMultipleGamesDialog(Gtk.Dialog):
self.update_all_checkboxes()
self.show_all()
self.connect("response", self.on_response)
def update_all_checkboxes(self) -> None:
"""Sets the state of the checkboxes at the button that are used to control all

View file

@ -200,9 +200,6 @@ class InstallerWindow(ModelessDialog,
def on_navigate_home(self, _accel_group, _window, _keyval, _modifier):
self.stack.navigate_home()
def on_destroy(self, _widget, _data=None):
self.on_cancel_clicked()
@watch_errors()
def on_cancel_clicked(self, _button=None):
"""Ask a confirmation before cancelling the installation, if it has started."""

View file

@ -3,7 +3,6 @@ from gi.repository import Gtk
class BaseApplicationWindow(Gtk.ApplicationWindow):
"""Window used to guide the user through a issue reporting process"""
def __init__(self, application):
@ -12,7 +11,6 @@ class BaseApplicationWindow(Gtk.ApplicationWindow):
self.set_show_menubar(False)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", self.on_destroy)
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12, visible=True)
self.vbox.set_margin_top(18)
@ -32,10 +30,6 @@ class BaseApplicationWindow(Gtk.ApplicationWindow):
button.set_tooltip_text(tooltip)
return button
def on_destroy(self, _widget=None, _data=None):
"""Destroy callback"""
self.destroy()
def present(self): # pylint: disable=arguments-differ
"""The base implementation doesn't always work, this one does."""
self.set_keep_above(True)

View file

@ -252,7 +252,7 @@ class ScriptInterpreter(GObject.Object, CommandsMixin):
"""Install required runners for a game"""
if self.runners_to_install:
self.install_runner(self.runners_to_install.pop(0), ui_delegate)
return
self.emit("runners-installed")
def install_runner(self, runner, ui_delegate):

View file

@ -9,6 +9,7 @@
<property name="default-width">800</property>
<property name="default-height">600</property>
<property name="type-hint">dialog</property>
<signal name="response" handler="on_response" swapped="no"/>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can-focus">False</property>