diff --git a/GTG/gtk/browser/main_window.py b/GTG/gtk/browser/main_window.py index 0b983a639..ba028a070 100644 --- a/GTG/gtk/browser/main_window.py +++ b/GTG/gtk/browser/main_window.py @@ -17,6 +17,7 @@ # ----------------------------------------------------------------------------- """ The main window for GTG, listing tags, and open and closed tasks """ +from __future__ import annotations import datetime import logging @@ -113,19 +114,14 @@ def __init__(self, app): self.sidebar = Sidebar(app, app.ds, self) self.sidebar_vbox.append(self.sidebar) - self.panes = { - 'active': None, - 'workview': None, - 'closed': None, + self.panes: dict[str, TaskPane] = { + 'active': TaskPane(self, 'active'), + 'workview': TaskPane(self, 'workview'), + 'closed': TaskPane(self, 'closed') } - self.panes['active'] = TaskPane(self, 'active') self.open_pane.append(self.panes['active']) - - self.panes['workview'] = TaskPane(self, 'workview') self.actionable_pane.append(self.panes['workview']) - - self.panes['closed'] = TaskPane(self, 'closed') self.closed_pane.append(self.panes['closed']) self._init_context_menus() diff --git a/GTG/gtk/browser/task_pane.py b/GTG/gtk/browser/task_pane.py index 82fc36e8c..f4396d7b8 100644 --- a/GTG/gtk/browser/task_pane.py +++ b/GTG/gtk/browser/task_pane.py @@ -35,7 +35,7 @@ class TaskBox(Gtk.Box): def __init__(self, config, is_actionable=False): self.config = config - super(TaskBox, self).__init__() + super().__init__() self.expander = Gtk.TreeExpander() self.expander.set_margin_end(6) diff --git a/GTG/gtk/errorhandler.py b/GTG/gtk/errorhandler.py index caf4d266c..1ae2a42e2 100644 --- a/GTG/gtk/errorhandler.py +++ b/GTG/gtk/errorhandler.py @@ -7,6 +7,7 @@ import functools import enum import logging +import queue from GTG.core import info from GTG.core.system_info import SystemInfo @@ -144,17 +145,53 @@ def handle_response(dialog: ExceptionHandlerDialog, response: int): log.info("Unhandled response: %r, interpreting as continue instead", response) dialog.close() + # Discard dialog from the queue, now that it has been closed + exception_dialog_queue.get() -def do_error_dialog(exception, context: str = None, ignorable: bool = True, main_msg=None): - """ - Show (and return) the error dialog. - It does NOT block execution, but should lock the UI - (by being a modal dialog). + # Another exception happened while the dialog was opened + # Open the dialog for that new exception + if not exception_dialog_queue.empty(): + next_dialog = exception_dialog_queue.get() + next_dialog.show() + + +# Queue of ExceptionHandlerDialogs to be shown +# Since GTG keeps running while the dialog is shown, it's possible that +# another exception will be thrown. Its dialog needs to be opened when the first +# one closes. +# Capping the size of the queue, to avoid trying to queue an infinite number of +# dialogs. This has happened in #1093. +exception_dialog_queue = queue.Queue(maxsize=5) + + +def do_error_dialog(exception, context: str = None, ignorable: bool = True, main_msg=None) -> None: + """Show (and return) the error dialog. + + It does NOT block execution, but should lock the UI (by being a modal dialog). + + Only show one exception dialog at a time, to avoid creating an infinity of + them in a loop (see #1093) + If an exception happens while the dialog is active for a previous + exception, add the new one to the exception queue. Its dialog will open when + the previous one gets closed. """ + # If the queue is empty, we just show the dialog + # It it's not, it means that a dialog is already being shown. In that case, + # the response handler of the current dialog will show the next one. + need_to_show_dialog = exception_dialog_queue.empty() + dialog = ExceptionHandlerDialog(exception, main_msg, ignorable, context) dialog.connect('response', handle_response) - dialog.show() - return dialog + + try: + exception_dialog_queue.put_nowait(dialog) + except queue.Full: + log.warning("Caught %s (%s) but not showing dialog for it because too" + " many exceptions are happening.", + type(exception).__name__, exception) + + if need_to_show_dialog: + dialog.show() def errorhandler(func, context: str = None, ignorable: bool = True, reraise: bool = True):