diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index e0f872f..699e004 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -51,7 +51,7 @@ def query_defunct() -> None: def concat_mods(mods: list[str]) -> str: for mod in mods: - mods[mod] = "@" + mod + mods[mod] = f"@{mod}" return ";".join(mods) diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index caeb9af..fb165a1 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -1,7 +1,7 @@ import logging import threading -from typing import Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING, Union import dzgui.util._json as JSON # noqa @@ -110,14 +110,14 @@ class Controller(GObject.GObject): return bool(self.query_config(Preferences.INSTALL)) def suppress_signal( - self, owner: Gtk.Widget, widget: Gtk.Widget, func_name: str, state: bool + self, owner: Any, child: Any, func_name: str, state: bool ) -> None: func = getattr(owner, func_name) if state: - widget.handler_block_by_func(func) + child.handler_block_by_func(func) else: - widget.handler_unblock_by_func(func) + child.handler_unblock_by_func(func) def get_active_context(self) -> "ServerTab": return self.get_active_treeview().get_enum() @@ -139,7 +139,7 @@ class Controller(GObject.GObject): def remove_statusbar(self, context: "NotebookPage | ServerTab") -> None: self.mediator.statusbar.pop(context) - def set_statusbar_dist(self, haversine: "Haversine", enum: "ServerTab") -> None: + def set_statusbar_dist(self, haversine: Union["Haversine", None], enum: "ServerTab") -> None: """ NOTE: prevents race condition when server tab changed, but allows caching the distance in the background @@ -153,6 +153,9 @@ class Controller(GObject.GObject): self.emitter.emit("distcalc_ended", None, context) return + if haversine is None: + self.emitter.emit("distcalc_ended", None, context) + return dist = haversine.get_rounded(self.prefs.use_miles) self.emitter.emit("distcalc_ended", dist, context) diff --git a/dzgui/managers/config.py b/dzgui/managers/config.py index 15c0b03..8099cd1 100644 --- a/dzgui/managers/config.py +++ b/dzgui/managers/config.py @@ -36,6 +36,7 @@ class ConfigManager: def __init__(self, prefs: "UserPrefs", controller: "Controller") -> None: self.prefs = prefs self.config = prefs.paths.config + self.controller = controller self.emitter = controller.get_emitter() self.thread_man = ThreadingManager(controller) @@ -95,7 +96,7 @@ class ConfigManager: return def get_favorites(self) -> list[str]: - return self.lookup(Preferences.IP_LIST) + return list(self.lookup(Preferences.IP_LIST)) def get_favorite(self) -> tuple[str, str] | tuple[None, None]: fav = str(self.lookup(Preferences.FAV_LBL)) @@ -112,7 +113,7 @@ class ConfigManager: return False def enum_to_key(self, enum: Preferences) -> str: - return enum.dict["key"] + return str(enum.dict["key"]) def get_client_index(self, client: str) -> int: if client == STEAM_CMD: @@ -121,6 +122,7 @@ class ConfigManager: return 1 if client == FLATPAK_SANDBOX: return 2 + return 0 def toggle_config(self, key: Preferences) -> None: try: @@ -149,7 +151,7 @@ class ConfigManager: except Exception as e: logger.critical(e) trace = traceback.format_exc() - dialog = ExceptionDialog(self, trace) + dialog = ExceptionDialog(self.controller, trace) dialog.run() raise e diff --git a/dzgui/managers/contextmenu.py b/dzgui/managers/contextmenu.py index 642d645..412a03c 100644 --- a/dzgui/managers/contextmenu.py +++ b/dzgui/managers/contextmenu.py @@ -8,6 +8,8 @@ from dzgui.util.clip import copy_clipboard from dzgui.util.open_links import open_workshop_page from dzgui.views.dialogs.note import NoteDialog +from dzgui.views.trees.tree_mods import ModTreeView +from dzgui.views.trees.tree_log import LogTreeView from dzgui.views.trees.tree_servers import ServerTreeView @@ -19,13 +21,12 @@ logger = logging.getLogger(APP_NAME) if TYPE_CHECKING: from dzgui.controllers.mc import Controller - from dzgui.views.trees.tree_servers import ServerTreeView class ContextMenuManager: def __init__( self, - treeview: "ServerTreeView", + treeview: LogTreeView | ModTreeView | ServerTreeView, controller: "Controller", ) -> None: self.controller = controller @@ -49,28 +50,40 @@ class ContextMenuManager: self.open_mod_page() case ContextMenu.SET_FAV: name = self.treeview.get_value_at_index(0) - record = self.treeview.get_record_string() + record_str = self.treeview.get_record_string() simple = self.treeview.get_simplified_ip() - self.controller.set_fav(name, record, simple) + self.controller.set_fav(name, record_str, simple) # THREADED case ContextMenu.ADD_SERVER: record = self.treeview.get_record() + if record is None: + return self.controller.add_by_record(record) case ContextMenu.CONNECT: record = self.treeview.get_record() + if record is None: + return self.controller.connect_by_record(record) case ContextMenu.REFRESH_PLAYERS: record = self.treeview.get_record() + if record is None: + return self.controller.refresh_players(record) case ContextMenu.REMOVE_HISTORY: record = self.treeview.get_record() + if record is None: + return self.controller.remove_from_history(record) case ContextMenu.REMOVE_SERVER: record = self.treeview.get_record() + if record is None: + return self.controller.remove_by_record(record) case ContextMenu.SHOW_DETAILS: record = self.treeview.get_record() + if record is None: + return self.controller.get_details(record) case ContextMenu.SHOW_MODS: record = self.treeview.get_record() @@ -89,11 +102,15 @@ class ContextMenuManager: copy_clipboard(name) def open_mod_page(self) -> None: + if not isinstance(self.treeview, ModTreeView): + return mod = self.treeview.get_selected_mod() cmd = self.controller.query_config(Preferences.CLIENT) open_workshop_page(mod, cmd) def copy_log(self) -> None: + if not isinstance(self.treeview, LogTreeView): + return log = self.treeview.concatenate_rows() if log is None: return diff --git a/dzgui/managers/filter.py b/dzgui/managers/filter.py index 4dfefcf..b6f7a6d 100644 --- a/dzgui/managers/filter.py +++ b/dzgui/managers/filter.py @@ -42,7 +42,7 @@ class FilterManager: def get_active_map_name(self) -> str: return self.active_map[1] - def get_active_map(self) -> int: + def get_active_map(self) -> tuple[int, str]: return self.active_map def set_active_map(self, ind: int, name: str) -> None: diff --git a/dzgui/managers/threading.py b/dzgui/managers/threading.py index 60406d5..a778124 100644 --- a/dzgui/managers/threading.py +++ b/dzgui/managers/threading.py @@ -1,7 +1,7 @@ import inspect import logging import threading -from typing import Any, TYPE_CHECKING +from typing import Any, Literal, TYPE_CHECKING from functools import wraps from typing import Callable @@ -23,7 +23,7 @@ logger = logging.getLogger(APP_NAME) def call_on_thread(dialog_str: str) -> Callable: def decorator(func: Callable) -> Callable: @wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Callable: + def wrapper(*args: Any, **kwargs: Any) -> None: self = args[0] stored = StoredFunc(func, *args, **kwargs) if not hasattr(self, "thread_man"): @@ -40,7 +40,7 @@ def call_on_thread(dialog_str: str) -> Callable: class StoredFunc: - def __init__(self, func: Callable, *args, **kwargs) -> None: + def __init__(self, func: Callable, *args: Any, **kwargs: Any) -> None: sig = inspect.signature(func) self.func = func self.bindings = sig.bind(*args, **kwargs) @@ -53,7 +53,7 @@ class ThreadingManager: def __init__(self, controller: "Controller") -> None: self.controller = controller self.jobs = 1 - self.cleanup_func = None + self.cleanup_func: StoredFunc | None self.destroy_first = False def call_on_thread(self, dialog_str: str, func: StoredFunc) -> None: @@ -75,7 +75,7 @@ class ThreadingManager: def increment_dialog_with_str(self, text: str) -> None: GLib.idle_add(lambda: self.wait_dialog.increment(text)) - def set_cleanup_func(self, func: StoredFunc, destroy_first: bool = False) -> None: + def set_cleanup_func(self, func: StoredFunc | None, destroy_first: bool = False) -> None: if type(func) not in (StoredFunc, type(None)): msg = f"Callback function '{func}' is not of type StoredFunc or None" logger.critical(msg) @@ -83,10 +83,10 @@ class ThreadingManager: self.destroy_first = destroy_first self.cleanup_func = func - def get_cleanup_func(self) -> StoredFunc: + def get_cleanup_func(self) -> StoredFunc | None: return self.cleanup_func - def _destroy_on_idle(self) -> GLib.SOURCE_REMOVE: + def _destroy_on_idle(self) -> Literal[False]: if self.destroy_first: self.wait_dialog.destroy() @@ -97,7 +97,7 @@ class ThreadingManager: if not self.destroy_first: self.wait_dialog.destroy() - return GLib.SOURCE_REMOVE + return False def get_wait_dialog(self) -> WaitDialog: return self.wait_dialog diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index 4c53043..351e5c4 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -86,7 +86,7 @@ class ServerModelManager: key = config_man.lookup(Preferences.STEAM) job = Servers.query_api params = Servers.params - servers = [] + servers: list[dict[Any, Any]] = [] with ThreadPoolExecutor() as executor: futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params] for future in as_completed(futures): diff --git a/dzgui/views/base.py b/dzgui/views/base.py index d5ec8f7..bcbcffa 100644 --- a/dzgui/views/base.py +++ b/dzgui/views/base.py @@ -33,7 +33,7 @@ import gi gi.require_version("Gtk", "3.0") gi.require_version("GLibUnix", "2.0") -from gi.repository import Gtk, GLib, GLibUnix, Gdk, GObject, Pango # noqa E402 +from gi.repository import Gtk, GLib, GLibUnix, Gdk # type: ignore # noqa E402 logger = logging.getLogger(APP_NAME) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 @@ -201,7 +201,8 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore if widget is None: return - widget.grab_content_area() + if hasattr(widget, "grab_content_area"): + widget.grab_content_area() def get_page(self) -> Gtk.Widget | None: ind = self.get_current_page() diff --git a/dzgui/views/components/connect_panel.py b/dzgui/views/components/connect_panel.py index 1e9dd56..63324bd 100644 --- a/dzgui/views/components/connect_panel.py +++ b/dzgui/views/components/connect_panel.py @@ -133,6 +133,9 @@ class FavPanel(Gtk.Frame): emitter.connect("fav_server_changed", self._on_fav_server_changed) # TODO: do not return a None, None tuple + self.server_name: str + self.server_ip: str + self.server_name, self.server_ip = ( self.controller.get_config_man().get_favorite() ) diff --git a/dzgui/views/components/filter_panel.py b/dzgui/views/components/filter_panel.py index 9d14c3d..222c61d 100644 --- a/dzgui/views/components/filter_panel.py +++ b/dzgui/views/components/filter_panel.py @@ -45,7 +45,7 @@ class ButtonGrid(Gtk.Grid): checkbox = Gtk.CheckButton(label=check) label = checkbox.get_child() if label is not None: - label.set_ellipsize(Pango.EllipsizeMode.END) + label.set_ellipsize(Pango.EllipsizeMode.END) # type: ignore if defaults[check]: checkbox.set_active(True) @@ -186,7 +186,7 @@ class FilterPanel(Gtk.Box): self.maps_combo = Gtk.ComboBox.new_with_model_and_entry(self.map_store) self.maps_combo.set_entry_text_column(0) - self.maps_entry = self.maps_combo.get_child() + self.maps_entry: Gtk.Entry = self.maps_combo.get_child() # type: ignore self.maps_entry.set_completion(completion) self.maps_entry.set_placeholder_text("Filter by map") self.maps_entry.connect("changed", self._on_map_completion, True) @@ -215,9 +215,10 @@ class FilterPanel(Gtk.Box): i: int row: Gtk.TreeModelRow - for i, row in enumerate(model): - if text == row[0]: + for i, row in enumerate(model): # type: ignore + if text == row[0]: # type: ignore self.maps_combo.set_active(i) + return def _on_maps_loaded(self, emitter: "Emitter", store: Gtk.ListStore) -> None: self.maps_combo.set_model(store) @@ -262,7 +263,7 @@ class FilterPanel(Gtk.Box): ) -> None: self.maps_combo.set_active_iter(it) - def _on_map_completion(self, entry: Gtk.Entry, editable: Literal[True]): + def _on_map_completion(self, entry: Gtk.Entry, editable: Literal[True]) -> None: text = entry.get_text() completion = entry.get_completion() store = self.controller.get_map_store() diff --git a/dzgui/views/components/statusbar.py b/dzgui/views/components/statusbar.py index b873bb7..b334f0e 100644 --- a/dzgui/views/components/statusbar.py +++ b/dzgui/views/components/statusbar.py @@ -98,7 +98,7 @@ class Statusbar(Gtk.Grid): def _on_server_row_changed(self, statusbar: Self) -> None: self.spinner.start() - def _on_distcalc_started(self, e) -> None: + def _on_distcalc_started(self, emitter: "Emitter") -> None: self.spinner.start() def _on_distcalc_ended( diff --git a/dzgui/views/dialogs/generic.py b/dzgui/views/dialogs/generic.py index 606eb7b..79f2363 100644 --- a/dzgui/views/dialogs/generic.py +++ b/dzgui/views/dialogs/generic.py @@ -44,7 +44,7 @@ class GenericDialog(Gtk.MessageDialog): self.set_default_response(Gtk.ResponseType.OK) - self.get_action_area().set_layout(Gtk.ButtonBoxStyle.CENTER) + self.get_action_area().set_layout(Gtk.ButtonBoxStyle.CENTER) # type: ignore self.get_action_area().set_margin_bottom(20) self.outer = self.get_content_area() self.outer.set_margin_start(30) @@ -76,7 +76,10 @@ class IgnoreDialog(GenericDialog): secondary=secondary, ) cancel = self.get_widget_for_response(Gtk.ResponseType.CANCEL) - cancel.set_label("Ignore") + if cancel is None: + return + if hasattr(cancel, "set_label"): + cancel.set_label("Ignore") class NotifyDialog(GenericDialog): @@ -199,7 +202,11 @@ class ExceptionDialog(GenericDialog): match response: case Gtk.ResponseType.OK: self.destroy() + return None case Gtk.ResponseType.NONE: return True case Gtk.ResponseType.DELETE_EVENT: self.destroy() + return None + case _: + return None diff --git a/dzgui/views/mixins/context_mixin.py b/dzgui/views/mixins/context_mixin.py index 1b1d952..de07fc4 100644 --- a/dzgui/views/mixins/context_mixin.py +++ b/dzgui/views/mixins/context_mixin.py @@ -19,7 +19,7 @@ class ContextMixin(TreeView): self, widget: Gtk.Widget, event: Gdk.EventButton | Gdk.EventKey, - ) -> None: + ) -> bool: if self.is_selection_empty(): return False @@ -56,6 +56,7 @@ class ContextMixin(TreeView): self.context_menu.popup_at_pointer(event) self.context_menu.select_first(False) + return True def _process_dynamic_row(self, row: ContextMenu) -> Gtk.MenuItem: if row == ContextMenu.ADD_SERVER and self.is_in_favs(): # type: ignore diff --git a/dzgui/views/pages/help.py b/dzgui/views/pages/help.py index 531dcc9..bcfbfd8 100644 --- a/dzgui/views/pages/help.py +++ b/dzgui/views/pages/help.py @@ -18,9 +18,6 @@ class Help(Gtk.ScrolledWindow): self.controller = controller - # FIXME: remember focused row - #self.treeview.focus_first_row() - def get_treeview(self) -> MenuTreeView: return self.treeview diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index beb5f86..1bb4d53 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -59,8 +59,8 @@ class Options(Gtk.Box): css.add_class(label, "page-heading") self.add(label) - self.steam_entry: Gtk.Entry = None - self.bm_entry: Gtk.Entry = None + self.steam_entry: Gtk.Entry + self.bm_entry: Gtk.Entry self.steam = WebButton(label=strings.options.steam_web) self.steam.connect("clicked", self._on_link_button_clicked, STEAM_API_SETUP) @@ -436,7 +436,8 @@ class Options(Gtk.Box): install = config["auto_install"] steam_path = Path(default_steam_path) - self.uid = find_user_id(steam_path) + uid = find_user_id(steam_path) + self.uid = "" if uid is None else uid self.old_steam = steam self.old_bm = bm @@ -444,7 +445,9 @@ class Options(Gtk.Box): self.steam_entry.set_text(steam) self.bm_entry.set_text(bm) - self.player_box.get_children()[0].set_text(name) + p = self.player_box.get_children()[0] + if hasattr(p, "set_text"): + p.set_text(name) # NOTE: suppress toggle signal until radios are built self._suppress_toggles(True) diff --git a/dzgui/views/pages/servers.py b/dzgui/views/pages/servers.py index 0be914f..f3fb435 100644 --- a/dzgui/views/pages/servers.py +++ b/dzgui/views/pages/servers.py @@ -1,6 +1,6 @@ import logging -from typing import Optional, Self, TYPE_CHECKING +from typing import Self, TYPE_CHECKING from dzgui.const.constants import APP_NAME from dzgui.const.enum import ContextMenuGroup, ServerTab @@ -77,6 +77,8 @@ class ServerNotebook(Gtk.ScrolledWindow): return page = self.notebook.get_nth_page(saved) label = self.notebook.get_tab_label(page) + if label is None: + return label.set_text(f"{server_labels.saved}*") def _on_servers_loaded(self, emitter: "Emitter", tab: "ServerTab") -> None: @@ -86,6 +88,7 @@ class ServerNotebook(Gtk.ScrolledWindow): state = False if tv.get_model() is None else True tv.set_headers_visible(state) tv.set_loaded(True) + tv.focus_first_row() tv.grab_focus() def _on_map(self, widget: Self) -> None: @@ -94,7 +97,7 @@ class ServerNotebook(Gtk.ScrolledWindow): def _on_unmap(self, widget: Self) -> None: self.emitter.emit("server_page_toggled", False) - def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> Optional[False]: + def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> bool: # NOTE: abort if modifier mask is active if event.state != 0: return False @@ -116,6 +119,8 @@ class ServerNotebook(Gtk.ScrolledWindow): def get_current_tab_text(self) -> str: ind = self.notebook.get_current_page() child = self.notebook.get_nth_page(ind) + if child is None: + return "" return self.notebook.get_tab_label_text(child) def _on_page_changed( diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index 7bf10db..19492a2 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -172,7 +172,7 @@ class ServerTreeView(ContextMixin, TreeView): self.start_queue_checker() self.start_distcalc() - def _on_unmap(self, a) -> None: + def _on_unmap(self, tree: Self) -> None: # NOTE: removes queue checker for this tab GLib.Source.remove(self.queue_id) self.emitter.disconnect(self.handler_id) @@ -229,7 +229,7 @@ class ServerTreeView(ContextMixin, TreeView): def _on_server_keypress( self, treeview: Gtk.TreeView, event: Gdk.EventKey - ) -> bool | None: + ) -> None: if event.state is Gdk.ModifierType.CONTROL_MASK: match event.keyval: case Gdk.KEY_f: @@ -248,10 +248,14 @@ class ServerTreeView(ContextMixin, TreeView): case Gdk.KEY_r: # TODO: unimplemented, needs threading self.controller.menu_action(ContextMenu.REFRESH_PLAYERS, self) + case _: + return False + return True else: match event.keyval: case Gdk.KEY_l | Gdk.KEY_Right: self.emitter.emit("request_button_box_focus") + return True case _: self.emitter.emit("check_button_pressed", event.keyval) @@ -307,7 +311,8 @@ class ServerTreeView(ContextMixin, TreeView): try: ip, gameport, qport = r.split(":") return Record(ip, int(gameport), int(qport)) - except ValueError: + except ValueError as e: + logger.critical(e) return None def is_loaded(self) -> bool: