diff --git a/dzgui/api/servers.py b/dzgui/api/servers.py index c31ee5b..f2a3aaf 100644 --- a/dzgui/api/servers.py +++ b/dzgui/api/servers.py @@ -254,7 +254,6 @@ class Record: qport: int -# TODO: refactor this def get_prereqs(ip: str, qport: int) -> Prereqs: try: info = a2s.info((ip, qport)) diff --git a/dzgui/const/constants.py b/dzgui/const/constants.py index 29d72d5..b47a05b 100644 --- a/dzgui/const/constants.py +++ b/dzgui/const/constants.py @@ -58,3 +58,4 @@ LEGACY_IPS_PATH = ".local/share/dzgui/helpers/ips.csv" DEBUG_LOG = f"{APP_NAME}_DEBUG.LOG" SYSTEM_LOG = f"{APP_NAME}_SYSTEM.LOG" +CHANGELOG_PATH = "data/CHANGELOG.md" diff --git a/dzgui/const/enum.py b/dzgui/const/enum.py index 50b1755..490317a 100644 --- a/dzgui/const/enum.py +++ b/dzgui/const/enum.py @@ -110,14 +110,14 @@ class Preferences(EnumWithAttrs): class NotebookPage(EnumWithAttrs): - CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": False} - KEYS = {"crumbs": strings.crumbs.keys, "statusbar": False} - LOG = {"crumbs": strings.crumbs.log, "statusbar": False} + CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": True} + KEYS = {"crumbs": strings.crumbs.keys, "statusbar": True} + LOG = {"crumbs": strings.crumbs.log, "statusbar": True} HELP = {"crumbs": strings.crumbs._help, "statusbar": True} MODS = {"crumbs": strings.crumbs.mods, "statusbar": True} OPTIONS = {"crumbs": strings.crumbs.options, "statusbar": False} SERVERS = {"crumbs": strings.crumbs.servers, "statusbar": True} - THANKS = {"crumbs": strings.crumbs.thanks, "statusbar": False} + THANKS = {"crumbs": strings.crumbs.thanks, "statusbar": True} DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False} diff --git a/dzgui/controllers/emitter.py b/dzgui/controllers/emitter.py new file mode 100644 index 0000000..6bad77f --- /dev/null +++ b/dzgui/controllers/emitter.py @@ -0,0 +1,61 @@ +from typing import Union, TYPE_CHECKING + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gdk, GObject # noqa E402 + +if TYPE_CHECKING: + from dzgui.const.enum import NotebookPage, ServerTab + +# TODO: rename signals to e.g. maps_keybinding_pressed + +class Emitter(GObject.GObject): + def __init__(self) -> None: + super().__init__() + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) + def request_keyword_focus(self) -> None: + """User invoked Ctrl-f keybinding from ServerTreeView""" + pass + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) + def request_maps_focus(self) -> None: + """User invoked Ctrl-m keybinding from ServerTreeView""" + pass + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) + def request_ip_entry_focus(self) -> None: + """User invoked Ctrl-i keybinding from ServerTreeView""" + pass + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) + def request_lan_entry_focus(self) -> None: + """User invoked Ctrl-p keybinding from ServerTreeView""" + pass + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) + def request_button_box_focus(self) -> None: + """User invoked right movement keybinding from ServerTreeView""" + pass + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(int,)) + def check_button_pressed(self, keyval: int) -> None: + """User toggled filter panel check button via keyboard""" + pass + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) + def distcalc_started(self) -> None: + pass + + @GObject.Signal( + flags=GObject.SignalFlags.RUN_LAST, + arg_types=( + object, + object, + ), + ) + def distcalc_ended( + self, dist: Union[str, None], context: Union["ServerTab", "NotebookPage"] + ) -> None: + pass diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index bfa3086..b54fd19 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -12,7 +12,6 @@ from typing import Any, Callable, Literal, Union, TYPE_CHECKING import dzgui.api.pefile as PeFile import dzgui.api.servers as Servers import dzgui.util._json as JSON # noqa -from dzgui.views.dialogs.generic import ExceptionDialog, WaitDialog from dzgui.api.probe import test_steam_api, test_bm_api from dzgui.api.mods import ( @@ -35,6 +34,7 @@ from dzgui.const.constants import ( from dzgui.config import update from dzgui.config.query import lookup from dzgui.config.userprefs import UserPrefs +from dzgui.controllers.emitter import Emitter from dzgui.model.misc_model import ModelManager from dzgui.util import strings from dzgui.util.diag import write_diagnostic @@ -43,7 +43,7 @@ from dzgui.util.localize import number from dzgui.util.open_links import open_workshop_page, open_user_workshop from dzgui.util.format import format_mods, format_player_count from dzgui.util.redact import redact_log - +from dzgui.views.dialogs.generic import ExceptionDialog, WaitDialog from dzgui.views.dialogs.filepicker import FilePicker import gi @@ -83,43 +83,6 @@ class AppNavigation: logtreeview: "LogTreeView" filters: "FilterPanel" -class Emitter(GObject.GObject): - def __init__(self) -> None: - super().__init__() - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) - def request_keyword_focus(self) -> None: - """User invoked Ctrl-f keybinding from ServerTreeView""" - pass - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) - def request_maps_focus(self) -> None: - """User invoked Ctrl-m keybinding from ServerTreeView""" - pass - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) - def request_ip_entry_focus(self) -> None: - """User invoked Ctrl-i keybinding from ServerTreeView""" - pass - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) - def request_lan_entry_focus(self) -> None: - """User invoked Ctrl-p keybinding from ServerTreeView""" - pass - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) - def request_button_box_focus(self) -> None: - """User invoked right movement keybinding from ServerTreeView""" - pass - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) - def distcalc_started(self) -> None: - pass - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object, object,)) - def distcalc_ended(self, dist: Union[str, None], context: Union["ServerTab", NotebookPage]) -> None: - pass - class Controller(GObject.GObject): def __init__(self) -> None: @@ -128,11 +91,11 @@ class Controller(GObject.GObject): self.prefs: UserPrefs self.model_man = ModelManager() + self.emitter = Emitter() # NOTE: suppress requests until entire UI is loaded self.loaded = False - self.emitter = Emitter() def get_emitter(self) -> Emitter: return self.emitter @@ -169,8 +132,7 @@ class Controller(GObject.GObject): def terminate_process(self) -> None: # TODO: only used by server table multiprocessing queue - tv = self.get_active_treeview() - tv.terminate_process() + self.get_active_treeview().terminate_process() def get_prefs(self) -> UserPrefs: return self.prefs @@ -626,16 +588,6 @@ class Controller(GObject.GObject): def unselect_all_mods(self) -> None: self.mediator.modtreeview.get_selection().unselect_all() - def dump_test_1(self) -> None: - import time - - time.sleep(1) - data = ( - ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], - ["BAR", "a", "a", "a", 1, 1, 1, "172.111.51.156:2302", 0, 0, "a", False], - ) - self.push_data(data) - def dump_test_2(self) -> None: import time time.sleep(1) @@ -741,6 +693,7 @@ class Controller(GObject.GObject): dialog.run() @call_on_thread + # FIXME: entire process is behind thread, including destroy_on_idle() def update_api_key(self, text: str, key: Preferences) -> None: self.test_api_response(text, key) @@ -825,26 +778,6 @@ class Controller(GObject.GObject): def focus_button_box(self) -> None: self.mediator.right_panel.focus_button_box() - def toggle_check(self, event: Gdk.EventKey) -> None | Literal[False]: - mappings = { - Gdk.KEY_1: 0, - Gdk.KEY_2: 1, - Gdk.KEY_3: 2, - Gdk.KEY_4: 3, - Gdk.KEY_5: 4, - Gdk.KEY_6: 5, - Gdk.KEY_7: 6, - Gdk.KEY_8: 7, - Gdk.KEY_9: 8, - Gdk.KEY_0: 9, - Gdk.KEY_minus: 10, - Gdk.KEY_backslash: 11, - } - if event.keyval not in mappings: - return False - index = mappings[event.keyval] - self.mediator.filters.toggle_check(index) - def get_favorite(self) -> tuple[str, str] | tuple[None, None]: fav = str(self.query_config(Preferences.FAV_LBL)) if len(fav) < 1: diff --git a/dzgui/util/strings.py b/dzgui/util/strings.py index 94529cd..b94eb7c 100644 --- a/dzgui/util/strings.py +++ b/dzgui/util/strings.py @@ -1,5 +1,4 @@ from dataclasses import dataclass - from dzgui.const.constants import SYSTEM_LOG @@ -605,3 +604,7 @@ atomic_buttons = AtomicButton( ) steam_icon_missing="Steam icon not found in IconTheme" + +missing_changelog = "Error: failed to read changelog" +esc_to_return = "Press ESC to return" +question_to_return = "Press ? to return" diff --git a/dzgui/views/base.py b/dzgui/views/base.py index f842202..288407a 100644 --- a/dzgui/views/base.py +++ b/dzgui/views/base.py @@ -191,13 +191,13 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore self.help = Help(MainController) self.clog = Changelog(MainController) - self.keys = Keybindings() + self.keys = Keybindings(MainController) self.settings = Options(MainController) self.servers = ServerNotebook(MainController) self.mods = Mods(MainController) - self.thanks = Thanks() + self.thanks = Thanks(MainController) self.log = Log(MainController) self.developers = Developers(MainController) @@ -272,12 +272,7 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore if widget is None: return - if widget is self.servers: - MainController.grab_active_treeview() - return - - w = widget.get_children()[0] - w.grab_focus() + widget.grab_content_area() def get_page(self) -> Gtk.Widget | None: ind = self.get_current_page() diff --git a/dzgui/views/components/buttonbox.py b/dzgui/views/components/buttonbox.py index 6b2f99a..e5b2d6f 100644 --- a/dzgui/views/components/buttonbox.py +++ b/dzgui/views/components/buttonbox.py @@ -4,11 +4,13 @@ from dzgui.const.enum import ButtonType from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING import gi + gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk # noqa E402 logger = logging.getLogger(__name__) + class ContextualButton(Gtk.Button): def __init__(self, label, opens, tooltip, context): super().__init__( @@ -20,6 +22,7 @@ class ContextualButton(Gtk.Button): self.context = context self.opens = opens + class ButtonBox(Gtk.Box): def __init__(self, controller) -> None: super().__init__( @@ -41,7 +44,7 @@ class ButtonBox(Gtk.Box): opens=side_button.dict["opens"], tooltip=side_button.dict["tooltip"], context=side_button, - ) + ) # FIXME: if debug log fails to load, still opens table size = (10, 10) if prefs.is_steam_deck else (50, 50) @@ -52,7 +55,6 @@ class ButtonBox(Gtk.Box): button.connect("clicked", self._on_selection_button_clicked) self.pack_start(button, NO_EXPAND, NO_FILL, NO_PADDING) - def _on_selection_button_clicked(self, button: Gtk.Button) -> None: self.controller.open_page_by_button(button) diff --git a/dzgui/views/components/filter_panel.py b/dzgui/views/components/filter_panel.py index c8c8e15..189db15 100644 --- a/dzgui/views/components/filter_panel.py +++ b/dzgui/views/components/filter_panel.py @@ -16,6 +16,7 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: from dzgui.controllers.mc import Controller + from dzgui.controllers.emitter import Emitter class FilterPanel(Gtk.Box): @@ -94,6 +95,7 @@ class FilterPanel(Gtk.Box): self.emitter.connect( "request_maps_focus", lambda _: self.maps_entry.grab_focus() ) + self.emitter.connect("check_button_pressed", self.toggle_check_by_key) completion = Gtk.EntryCompletion(inline_completion=True) completion.set_text_column(0) @@ -251,6 +253,26 @@ class FilterPanel(Gtk.Box): def set_active_combo(self, row: int) -> None: self.maps_combo.set_active(row) + def toggle_check_by_key(self, emitter: "Emitter", keyval: int) -> None | Literal[False]: + mappings = { + Gdk.KEY_1: 0, + Gdk.KEY_2: 1, + Gdk.KEY_3: 2, + Gdk.KEY_4: 3, + Gdk.KEY_5: 4, + Gdk.KEY_6: 5, + Gdk.KEY_7: 6, + Gdk.KEY_8: 7, + Gdk.KEY_9: 8, + Gdk.KEY_0: 9, + Gdk.KEY_minus: 10, + Gdk.KEY_backslash: 11, + } + if keyval not in mappings: + return False + index = mappings[keyval] + self.toggle_check(index) + def toggle_check(self, digit: int) -> None: check = self.checks[digit] state = check.get_active() diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index 9381a8f..9e44dba 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from dzgui.views.components.buttonbox import ButtonBox from dzgui.views.components.filter_panel import FilterPanel @@ -13,6 +13,7 @@ from gi.repository import Gtk, GLib # noqa E402 if TYPE_CHECKING: from dzgui.controllers.mc import Controller + from dzgui.controllers.emitter import Emitter class RightPanel(Gtk.Box): @@ -78,5 +79,5 @@ class RightPanel(Gtk.Box): # thread = threading.Thread(target=_update_pings, args=()) # thread.start() - def focus_button_box(self) -> None: + def focus_button_box(self, emitter: Optional["Emitter"] = None) -> None: self.button_vbox.buttons[0].grab_focus() diff --git a/dzgui/views/components/statusbar.py b/dzgui/views/components/statusbar.py index a4e59ec..95f7130 100644 --- a/dzgui/views/components/statusbar.py +++ b/dzgui/views/components/statusbar.py @@ -1,6 +1,7 @@ from typing import Self, Union, TYPE_CHECKING from dzgui.const.enum import NotebookPage, ServerTab +from dzgui.util.strings import esc_to_return, question_to_return import gi @@ -46,8 +47,8 @@ class Statusbar(Gtk.Grid): controller.mediator.notebook.connect_after( "switch-page", self._on_notebook_page_changed ) - self.emitter.connect("distcalc_started", self._on_distcalc_started) # TODO: + self.emitter.connect("distcalc_started", lambda _: self.spinner.start()) self.connect("distcalc_ended", self._on_distcalc_ended) self.connect("server_page_changed", self._on_server_page_changed) @@ -77,10 +78,19 @@ class Statusbar(Gtk.Grid): enum = notebook.get_page_by_enum() show_statusbar = enum.dict["statusbar"] + if show_statusbar is False: self.set_by_context(enum, "") return + if enum in ( + NotebookPage.THANKS, + NotebookPage.CHANGELOG, + NotebookPage.LOG, + ): + self.set_by_context(enum, esc_to_return) + return + match enum: case NotebookPage.MODS: bar = self.controller.format_mod_statusbar() @@ -89,6 +99,8 @@ class Statusbar(Gtk.Grid): case NotebookPage.SERVERS: self.emit("server_page_changed", ServerTab.BROWSER) return + case NotebookPage.KEYS: + bar = question_to_return self.set_by_context(enum, bar) @@ -122,7 +134,7 @@ class Statusbar(Gtk.Grid): self.set_by_context(context, count) tree = self.controller.get_active_treeview() - # TODO: emit page change signal on emitter, treeview catches signal and calls distcalc + # TODO: emit page change signal on emitter, treeview catches signal and calls distcalc directly tree.emit("distcalc_started") def pop(self, context: Union["ServerTab", "NotebookPage"]) -> None: diff --git a/dzgui/views/mixins/cursor_mixin.py b/dzgui/views/mixins/cursor_mixin.py index ea61d62..dd7d2ac 100644 --- a/dzgui/views/mixins/cursor_mixin.py +++ b/dzgui/views/mixins/cursor_mixin.py @@ -2,6 +2,7 @@ from dzgui.const.enum import CursorPosition from dzgui.const.constants import SEPARATOR import gi + gi.require_version("Gtk", "3.0") from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402 @@ -20,7 +21,7 @@ class CursorMixin: case Gdk.KEY_l | Gdk.KEY_Right: if event.state is Gdk.ModifierType.CONTROL_MASK: return - self.controller.mediator.right_panel.focus_button_box() + self.emitter.emit("request_button_box_focus") case _: return False diff --git a/dzgui/views/mixins/help_menu_mixin.py b/dzgui/views/mixins/help_menu_mixin.py new file mode 100644 index 0000000..c55ca03 --- /dev/null +++ b/dzgui/views/mixins/help_menu_mixin.py @@ -0,0 +1,13 @@ +from typing import Literal + +from dzgui.const.enum import NotebookPage + +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Gdk # noqa + +class HelpMenuMixin: + def _on_esc_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> Literal[True]: + if event.keyval == Gdk.KEY_Escape: + self.controller.open_page(NotebookPage.HELP) + return True diff --git a/dzgui/views/mixins/scrollable_mixin.py b/dzgui/views/mixins/scrollable_mixin.py index 4d9e406..775a522 100644 --- a/dzgui/views/mixins/scrollable_mixin.py +++ b/dzgui/views/mixins/scrollable_mixin.py @@ -2,9 +2,11 @@ from dzgui.const.enum import VAdjustment from dzgui.const.constants import SCROLL_INCREMENT import gi + gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk # noqa + class ScrollableMixin: def _on_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None: match event.keyval: @@ -16,6 +18,8 @@ class ScrollableMixin: self._set_adjustment(VAdjustment.TOP) case Gdk.KEY_G: self._set_adjustment(VAdjustment.BOTTOM) + case Gdk.KEY_l | Gdk.KEY_Right: + self.controller.get_emitter().emit("request_button_box_focus") def _set_adjustment(self, adjustment: VAdjustment) -> None: vadj = self.get_vadjustment() @@ -29,4 +33,3 @@ class ScrollableMixin: case VAdjustment.DOWN: adj = vadj.get_value() + SCROLL_INCREMENT vadj.set_value(adj) - diff --git a/dzgui/views/pages/changelog.py b/dzgui/views/pages/changelog.py index 4ba2437..d429ad8 100644 --- a/dzgui/views/pages/changelog.py +++ b/dzgui/views/pages/changelog.py @@ -3,31 +3,35 @@ import logging from typing import TYPE_CHECKING from importlib import resources -from dzgui.const.constants import APP_NAME_LOWER +from dzgui.const.constants import APP_NAME_LOWER, CHANGELOG_PATH +from dzgui.util.strings import missing_changelog from dzgui.util.format import format_pango +from dzgui.views.mixins.help_menu_mixin import HelpMenuMixin from dzgui.views.mixins.scrollable_mixin import ScrollableMixin import gi + gi.require_version("Gtk", "3.0") from gi.repository import Gtk # noqa E402 logger = logging.getLogger(__name__) - if TYPE_CHECKING: from dzgui.controllers.mc import Controller -class Changelog(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore + +class Changelog(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ignore def __init__(self, controller: "Controller"): super().__init__(propagate_natural_width=False) try: - changelog = resources.read_text(APP_NAME_LOWER, "data/CHANGELOG.md") + changelog = resources.read_text(APP_NAME_LOWER, CHANGELOG_PATH) except OSError as e: logger.critical(e) - changelog = "Error: Failed to read changelog" + changelog = missing_changelog # TODO: should long text be wrapped? + self.controller = controller formatted = format_pango(changelog) self.changelog_label = Gtk.Label(valign=Gtk.Align.START, margin=15) self.changelog_label.set_markup(formatted) @@ -36,3 +40,7 @@ class Changelog(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore self.add(self.box) self.connect("key-press-event", self._on_keypress) + self.connect("key-press-event", self._on_esc_keypress) + + def grab_content_area(self) -> None: + self.grab_focus() diff --git a/dzgui/views/pages/help.py b/dzgui/views/pages/help.py index 5e4d8a5..531dcc9 100644 --- a/dzgui/views/pages/help.py +++ b/dzgui/views/pages/help.py @@ -18,7 +18,11 @@ class Help(Gtk.ScrolledWindow): self.controller = controller - self.treeview.focus_first_row() + # FIXME: remember focused row + #self.treeview.focus_first_row() def get_treeview(self) -> MenuTreeView: return self.treeview + + def grab_content_area(self) -> None: + self.treeview.grab_focus() diff --git a/dzgui/views/pages/keys.py b/dzgui/views/pages/keys.py index 9fa357b..46ef945 100644 --- a/dzgui/views/pages/keys.py +++ b/dzgui/views/pages/keys.py @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING + from dzgui.views.mixins.scrollable_mixin import ScrollableMixin from dzgui.util.strings import navigation, servers, vim, key_header, key_contexts from dzgui.util.css import add_class @@ -6,15 +8,18 @@ import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk # noqa E402 +if TYPE_CHECKING: + from dzgui.controllers.mc import Controller class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): """ Notebook page holding a prearranged grid of keybindings and their descriptions """ - def __init__(self) -> None: + def __init__(self, controller: "Controller") -> None: super().__init__() + self.controller = controller self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) label = Gtk.Label(label=key_header) @@ -101,3 +106,6 @@ class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): grid.attach_next_to(separator, sidebar, Gtk.PositionType.RIGHT, w, h) grid.attach_next_to(keys_box, separator, Gtk.PositionType.RIGHT, w, h) return grid + + def grab_content_area(self) -> None: + self.box.grab_focus() diff --git a/dzgui/views/pages/log.py b/dzgui/views/pages/log.py index 057ab23..b9a4343 100644 --- a/dzgui/views/pages/log.py +++ b/dzgui/views/pages/log.py @@ -1,14 +1,19 @@ from typing import TYPE_CHECKING +from dzgui.views.mixins.cursor_mixin import CursorMixin +from dzgui.views.mixins.help_menu_mixin import HelpMenuMixin from dzgui.views.trees.tree_log import LogTreeView + import gi + gi.require_version("Gtk", "3.0") from gi.repository import Gtk # noqa E402 if TYPE_CHECKING: from dzgui.controllers.mc import Controller -class Log(Gtk.ScrolledWindow): + +class Log(CursorMixin, HelpMenuMixin, Gtk.ScrolledWindow): def __init__(self, controller: "Controller") -> None: super().__init__() self.treeview = LogTreeView(controller) @@ -17,5 +22,10 @@ class Log(Gtk.ScrolledWindow): self.controller = controller self.controller.register_widget("logtreeview", self.treeview) + self.connect("key-press-event", self._on_esc_keypress) + def get_treeview(self) -> LogTreeView: return self.treeview + + def grab_content_area(self) -> None: + self.treeview.grab_focus() diff --git a/dzgui/views/pages/mods.py b/dzgui/views/pages/mods.py index 7947a6d..9ac5767 100644 --- a/dzgui/views/pages/mods.py +++ b/dzgui/views/pages/mods.py @@ -28,3 +28,6 @@ class Mods(Gtk.ScrolledWindow): def _on_map(self, widget: Self) -> None: self.controller.toggle_mod_panel(True) + + def grab_content_area(self) -> None: + self.tree.grab_focus() diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index 2183cab..7342e0e 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -524,3 +524,6 @@ class Options(Gtk.Box): icon, state = VIEW_CONCEAL, True widget.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) widget.set_visibility(state) + + def grab_content_area(self) -> None: + return diff --git a/dzgui/views/pages/servers.py b/dzgui/views/pages/servers.py index 55589e9..23c739a 100644 --- a/dzgui/views/pages/servers.py +++ b/dzgui/views/pages/servers.py @@ -71,6 +71,9 @@ class ServerNotebook(Gtk.ScrolledWindow): self.notebook.prev_page() case _: return + self.grab_content_area() + + def grab_content_area(self) -> None: self.get_active_treeview().grab_focus() def get_current_tab_text(self) -> None: diff --git a/dzgui/views/pages/thanks.py b/dzgui/views/pages/thanks.py index e59b807..4adc39c 100644 --- a/dzgui/views/pages/thanks.py +++ b/dzgui/views/pages/thanks.py @@ -1,26 +1,37 @@ +from typing import TYPE_CHECKING + from dzgui.util.format import format_pango from dzgui.util.strings import thanks +from dzgui.views.mixins.help_menu_mixin import HelpMenuMixin from dzgui.views.mixins.scrollable_mixin import ScrollableMixin import gi + gi.require_version("Gtk", "3.0") from gi.repository import Gtk # noqa E402 # TODO: wrap and center long values +if TYPE_CHECKING: + from dzgui.controllers.mc import Controller -class Thanks(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore + +class Thanks(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ignore """ Special thanks page recognizing contributors to the project in alpha-order """ - def __init__(self) -> None: + + def __init__(self, controller: "Controller") -> None: super().__init__() + self.controller = controller self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) label = format_pango(thanks.header) header = Gtk.Label() header.set_markup(label) - description = Gtk.Label(label=thanks.description, justify=Gtk.Justification.CENTER) + description = Gtk.Label( + label=thanks.description, justify=Gtk.Justification.CENTER + ) users = [f"- {user}" for user in sorted(thanks.users, key=str.lower)] pretty_users = "\n".join(users) @@ -32,3 +43,7 @@ class Thanks(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore self.add(self.box) self.connect("key-press-event", self._on_keypress) + self.connect("key-press-event", self._on_esc_keypress) + + def grab_content_area(self) -> None: + self.grab_focus() diff --git a/dzgui/views/trees/tree_base.py b/dzgui/views/trees/tree_base.py index a39dd91..3663157 100644 --- a/dzgui/views/trees/tree_base.py +++ b/dzgui/views/trees/tree_base.py @@ -32,6 +32,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore self.controller = controller self.sel_blocked = False self.set_row_separator_func(self._separate) + self.emitter = self.controller.get_emitter() self.selected_row = self.get_selection() self.selected_row.set_mode(Gtk.SelectionMode.SINGLE) diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index 8543b71..b62e8a8 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -203,14 +203,11 @@ class ServerTreeView(TreeView): self.current_proc.terminate() def _on_distcalc_started(self, treeview: Self): - # bar = self.controller.get_statusbar() - record = self.get_record() if record is None: return self.emitter.emit("distcalc_started") - # bar.emit("server_row_changed") cache = self.controller.get_dist_cache() if record.ip in cache: @@ -242,8 +239,6 @@ class ServerTreeView(TreeView): def _on_server_keypress( self, treeview: Gtk.TreeView, event: Gdk.EventKey ) -> bool | None: - # TODO: use mixins - # CONTROL_MASK + KEY_l if event.state is Gdk.ModifierType.CONTROL_MASK: match event.keyval: case Gdk.KEY_l: @@ -266,7 +261,7 @@ class ServerTreeView(TreeView): case Gdk.KEY_l | Gdk.KEY_Right: self.emitter.emit("request_button_box_focus") case _: - self.controller.toggle_check(event) + self.emitter.emit("check_button_pressed", event.keyval) def set_context_menu(self, items: ContextMenuGroup) -> None: # TODO: if debug is on, add raw command copy to context menu @@ -286,7 +281,6 @@ class ServerTreeView(TreeView): # TODO: use ContextMixin if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3: return - try: pathinfo = self.get_path_at_pos(int(event.x), int(event.y)) if pathinfo is None: