From fdd6cefe4813f8fb2c4f06eea845e40686dfbd2f Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 19 Jan 2026 08:27:10 +0900 Subject: [PATCH] feat: server threading WIP --- dzgui/api/steam.py | 2 +- dzgui/controllers/mc.py | 183 ++++++++++++++---------- dzgui/util/ip.py | 1 + dzgui/views/base.py | 73 +--------- dzgui/views/components/buttonbox.py | 3 +- dzgui/views/components/connect_panel.py | 5 +- dzgui/views/components/statusbar.py | 24 ++-- dzgui/views/pages/options.py | 8 +- dzgui/views/pages/servers.py | 2 +- dzgui/views/trees/tree_base.py | 4 +- dzgui/views/trees/tree_servers.py | 4 +- 11 files changed, 147 insertions(+), 162 deletions(-) diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index ab24526..87691e0 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -53,7 +53,7 @@ def concat_mods(mods: list[str]) -> str: # TEST: set config to name=user, use official server and no mods, # ensure that formatted string is identical to fixture -def connect(addr: str, appid: int, name: str, mods: list): +def connect(addr: str, appid: int, name: str, mods: list) -> None: # TODO: get name from configs # TODO: concat_mods(mods): # @;@; diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index dd52852..4f1b4c2 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -5,7 +5,7 @@ import textwrap import traceback from pathlib import Path -from typing import Any, Callable, TYPE_CHECKING +from typing import Any, Callable, Literal, TYPE_CHECKING import dzgui.api.pefile as PeFile import dzgui.util._json as JSON # noqa @@ -246,6 +246,7 @@ class Controller: self.mediator.statusbar.set_text(text) def set_statusbar_placeholder(self, text: str) -> None: + # TODO: use statusbar stacks instead self.statusbar_placeholder = text def set_statusbar_dist(self, haversine: "Haversine") -> None: @@ -263,6 +264,7 @@ class Controller: dist = str(separated) + " km" text = self.statusbar_placeholder self.set_statusbar(f"{text} | Distance: {dist}") + self.mediator.statusbar.spinner.stop() def delete_multiple_mods(self) -> None: sel = self.mediator.modtreeview.get_selection() @@ -275,6 +277,7 @@ class Controller: self.update_mod_statusbar() def load_mods(self) -> None: + # TODO: threading model = self.model_manager.get_mod_store() model.clear() path = self.query_config(Preferences.DEFAULT) @@ -310,10 +313,6 @@ class Controller: self.mediator.grid.notebook.settings.populate_settings() return - # NOTE: disabled for now - #def present_toast(self, text: str) -> None: - # self.mediator.window.toast.set_text_and_fade(text) - def open_keybindings(self) -> None: notebook = self.mediator.grid.notebook notebook.toggle_keybindings() @@ -322,23 +321,6 @@ class Controller: notebook = self.mediator.grid.notebook notebook.focus_current() - # TODO: deprecated - def spawn_dialog(self, msg: str, mode: Popup) -> bool: - """ - Spawns a GenericDialog transient to the OuterWindow - """ - msg = textwrap.dedent(msg) - dialog = GenericDialog(self, msg, mode) - response = dialog.run() - dialog.destroy() - - match response: - case Gtk.ResponseType.OK: - return False - case Gtk.ResponseType.CANCEL | Gtk.ResponseType.DELETE_EVENT: - return True - return False - def set_statusbar_by_row(self, row: "RowType") -> None: self.mediator.statusbar.refresh(row) @@ -366,7 +348,11 @@ class Controller: self.save_res_and_quit() return case ButtonType.OPTIONS: - self.mediator.grid.notebook.settings.populate_settings() + try: + # TODO: where to put config file check + self.mediator.grid.notebook.settings.populate_settings() + except Exception: + return case ButtonType.MODS: self.load_mods() case ButtonType.HELP: @@ -374,11 +360,10 @@ class Controller: pass case ButtonType.SERVERS: self.mediator.notebook.set_page_by_enum(button.opens) - # TODO: use cache - self.update_server_status() return self.mediator.notebook.set_page_by_enum(button.opens) + # TODO: set crumbs by signal self.set_crumbs(button.get_label()) def open_user_workshop(self, uid: str) -> None: @@ -445,7 +430,9 @@ class Controller: def update_mod_statusbar(self) -> None: total_mods, total_size = self.calc_mod_size() msg = format_mods(total_size, total_mods) + # TODO: combine self.mediator.statusbar.set_text(msg) + self.mediator.statusbar.spinner.stop() def calc_mod_size(self) -> tuple[int, int]: model = self.model_manager.get_mod_store() @@ -508,13 +495,16 @@ class Controller: log = self.prefs.paths.debug store = self.model_manager.get_log_store() store.clear() - - # TODO: pop dialog if log is missing - with open(log, "r") as f: - lines = [line.split(strings.delimiter) for line in f.read().splitlines()] - for record in lines: - clean = redact_log(record) - store.append(clean) + # NOTE: this model is reloaded each time as log changes + try: + with open(log, "r") as f: + lines = [line.split(strings.delimiter) for line in f.read().splitlines()] + for record in lines: + clean = redact_log(record) + store.append(clean) + except Exception as e: + dialog = ExceptionDialog(self, str(e)) + dialog.run() self.open_page(NotebookPage.LOG) def select_colorized(self) -> None: @@ -552,20 +542,51 @@ class Controller: import time time.sleep(1) # TODO: use model managers, etc. - self.data = (["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False]) + self.data = ( + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ) # TODO: refer to prior implementation--should need to load data into model while in thread self.get_func_data() self.destroy_on_idle() def get_func_data(self) -> tuple: + def test(): + # NOTE: do not insert model until main thread is idle + # TODO: grab from model manager and insert entire model + # TODO: do not start refresh button count until load finished + for row in data: + model.append(row) + treeview.set_loaded(True) + self.update_server_status() + treeview.grab_focus() # TODO: use model manager treeview = self.get_active_treeview() model = treeview.get_model() data = self.data - model.append(data) - treeview.set_loaded(True) - self.update_server_status() - treeview.grab_focus() + GLib.idle_add(test) def highlight_stale(self) -> None: self.call_on_thread(self.colorize_mods) @@ -577,14 +598,11 @@ class Controller: thread = threading.Thread(target=func, args=args) thread.start() - def destroy_on_idle(self) -> None: - self.wait_dialog.destroy() - # TODO: improve upon this - func = self.callback["func"] - if func is not None: - args = self.callback["args"] - GLib.idle_add(func, *args) - self.set_callback(None, None) + def get_callback(self) -> Callable | None: + return self.callback["func"] + + def get_callback_args(self) -> Any: + return self.callback["args"] def set_callback(self, callback: Callable | None, *args) -> None: """ @@ -593,6 +611,14 @@ class Controller: """ self.callback = { "func": callback, "args": args } + def destroy_on_idle(self) -> None: + self.wait_dialog.destroy() + func = self.get_callback() + if func is not None: + args = self.get_callback_args() + GLib.idle_add(func, *args) + self.set_callback(None, None) + def dump_diagnostics(self) -> None: picker = FilePicker(self.mediator.window) file = picker.pick_file() @@ -600,7 +626,8 @@ class Controller: try: write_diagnostic(self.prefs.paths.config, file) except Exception as e: - self.spawn_dialog(str(e), Popup.NOTIFY) + dialog = ExceptionDialog(self, str(e)) + dialog.run() def test_api_response(self, text: str, key: Preferences) -> None: if key is Preferences.STEAM: @@ -614,7 +641,8 @@ class Controller: self.destroy_on_idle() else: self.destroy_on_idle() - self.spawn_dialog(strings.api_error, Popup.NOTIFY) + dialog = ExceptionDialog(self, strings.api_error) + dialog.run() def update_api_key(self, text: str, key: Preferences) -> None: @@ -645,13 +673,6 @@ class Controller: logger.info(f"Using default window size {w},{h}") window.set_default_size(w, h) - def update_server_status(self) -> None: - treeview = self.mediator.notebook.servers.get_active_treeview() - model = treeview.get_model() - status = format_player_count(model) - self.set_statusbar_placeholder(status) - self.set_statusbar(status + "| Calculating...") - def propagate_column_width(self, col: Gtk.TreeViewColumn) -> None: GLib.idle_add(self.mediator.servers.update_tab_widths, col) @@ -662,25 +683,31 @@ class Controller: return self.crumbs_cache def refresh_tree(self) -> None: - treeview = self.mediator.notebook.servers.get_active_treeview() + treeview = self.get_active_treeview() treeview.set_loaded(False) self.populate_model() + def update_server_status(self) -> None: + # TODO: emit signal on statusbar only if page changed + treeview = self.get_active_treeview() + model = treeview.get_model() + status = format_player_count(model) + self.set_statusbar_placeholder(status) + self.set_statusbar(status) + self.mediator.statusbar.spinner.start() + def populate_model(self) -> None: - print("POPULATING MODEL") # TODO: always use same server model, store in servertreeview class treeview = self.get_active_treeview() if treeview.get_loaded() is False: new_model = self.model_manager.new_model() # NOTE: set_query_func() func = treeview.get_query_func() - print(func) if func is not None: model = treeview.get_model() - print(model) # TODO: this may lag? model.clear() - self.set_callback(None, None)#self.get_func_data) + self.set_callback(None, None) self.call_on_thread(func) def focus_button_box(self) -> None: @@ -688,7 +715,7 @@ class Controller: def present_servers(self) -> None: # TODO: abstract - self.grab_active_treeview() + # TODO: signal for crumbs self.update_server_status() crumbs = self.mediator.servers.get_cached_label() self.set_crumbs(crumbs) @@ -696,21 +723,26 @@ class Controller: #tree = self.get_active_treeview() #tree.emit("on_distcalc_started") - def toggle_check(self, event: Gdk.EventKey) -> None: - keyname = Gdk.keyval_name(event.keyval) - if keyname.isnumeric() and int(keyname) > 0: - digit = int(keyname) - 1 - self.mediator.grid.right_panel.filters_vbox.toggle_check(digit) - else: - match event.keyval: - case Gdk.KEY_0: - self.mediator.grid.right_panel.filters_vbox.toggle_check(9) - case Gdk.KEY_minus: - self.mediator.grid.right_panel.filters_vbox.toggle_check(10) - case Gdk.KEY_backslash: - self.mediator.grid.right_panel.filters_vbox.toggle_check(11) - case _: - return False + 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] + # TODO: register filter panel widget + self.mediator.grid.right_panel.filters_vbox.toggle_check(index) def get_favorite(self) -> tuple[str, str] | tuple[None, None]: fav = str(self.query_config(Preferences.FAV_LBL)) @@ -723,5 +755,6 @@ class Controller: def get_dist_cache(self) -> dict[str, "Haversine"]: return self.dist_cache + # TODO: deprecated in favor of map/unmap def toggle_lan_panel(self, state: bool) -> None: self.mediator.grid.conpan.set_visible(state) diff --git a/dzgui/util/ip.py b/dzgui/util/ip.py index 5b2057b..f4dceb5 100644 --- a/dzgui/util/ip.py +++ b/dzgui/util/ip.py @@ -74,6 +74,7 @@ def get_coords(ips: "Path", ip: str) -> Coords: if split[2] == upper_digits[2]: if split[3] > upper_digits[3]: continue + # TODO: handle last digit equals if split[2] <= upper_digits[2]: return Coords(float(fields[-2]), float(fields[-1])) diff --git a/dzgui/views/base.py b/dzgui/views/base.py index 1628ad9..0a7102d 100644 --- a/dzgui/views/base.py +++ b/dzgui/views/base.py @@ -1,26 +1,16 @@ -import json -import locale import logging -import multiprocessing -import os -import re +#import multiprocessing import signal -import subprocess -import textwrap -import threading +#import threading import typing # noqa import warnings #from concurrent.futures import wait #from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass -from enum import Enum -from pathlib import Path from typing import TYPE_CHECKING, Literal -from dzgui.const.enum import NotebookPage, VAdjustment -from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING +from dzgui.const.enum import NotebookPage from dzgui.const.constants import APP_NAME, APP_NAME_LOWER from dzgui.controllers.mc import Controller from dzgui.util import css, strings @@ -40,21 +30,9 @@ from dzgui.views.pages.servers import ServerNotebook from dzgui.views.pages.thanks import Thanks from dzgui.views.components.statusbar import Statusbar -from dzgui.views.components.mod_panel import ModSelectionPanel from dzgui.views.components.right_panel import RightPanel -from dzgui.views.components.toast import Toast -from dzgui.views.dialogs.generic import GenericDialog from dzgui.views.mixins.scrollable_mixin import ScrollableMixin -# TREES -from dzgui.views.trees.tree_menu import MenuTreeView -from dzgui.views.trees.tree_log import LogTreeView -from dzgui.views.trees.tree_mods import ModTreeView -from dzgui.views.trees.tree_servers import ServerTreeView - -# TODO: not going to be in base anymore -import dzgui.util._json as JSON # noqa - if TYPE_CHECKING: from dzgui.config.userprefs import UserPrefs @@ -160,41 +138,6 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning) # #return # # -#def process_user_input(enum: RowType) -> None: -# prompt = enum.dict["prompt"] -# link_label = enum.dict["link_label"] -# cmd_string = enum.dict["label"] -# -# if enum == RowType.CONN_BY_ID: -# key = MainController.query_config(Preferences.BM) -# if len(key) == 0: -# spawn_dialog( -# "No Battlemetrics API key is set; see Options", Popup.NOTIFY -# ) -# return -# -# user_entry = EntryDialog(prompt, Popup.ENTRY, link_label, button_type=enum) -# response = user_entry.get_input() -# -# if response is None: -# logger.info("User aborted entry dialog") -# return -# logger.info(f"User entered: '{response}'") -# -# if enum == RowType.CONN_BY_IP: -# connect_by_ip(enum, response) -# return -# -# if enum == RowType.CONN_BY_ID: -# connect_by_id(enum, response, key) -# return -# -# show_wait_dialog = True -# wait_msg = "Working" -# call_on_thread( -# show_wait_dialog, cmd_string, wait_msg, response, choice=enum -# ) -# return class OuterWindow(Gtk.Window): def __init__(self) -> None: @@ -216,6 +159,7 @@ class OuterWindow(Gtk.Window): self.show_all() css.load_css() + # TODO: first run MainController.open_page(NotebookPage.SERVERS) MainController.mediator.grid.conpan.lan.set_visible(False) @@ -344,7 +288,7 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore try: w.focus_first_row() w.grab_focus() - except Exception as e: + except Exception: w.grab_focus() def get_page(self) -> Gtk.Widget | None: @@ -370,12 +314,6 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore status = enum.dict["statusbar"] MainController.set_crumbs(crumbs) - #is_mods = True if enum is NotebookPage.MODS else False - #is_servers = True if enum is NotebookPage.SERVERS else False - # TODO: use signals internal to servers.py - #MainController.toggle_mod_panel(is_mods) - #MainController.toggle_server_panels(is_servers) - if status is False: MainController.set_statusbar("") if enum is NotebookPage.SERVERS: @@ -409,7 +347,6 @@ class Grid(Gtk.Grid): self.attach(self.notebook, 0, 0, MAX_COLS, SINGLE_ROW) - els = ( (self.crumb_box, self.notebook, Gtk.PositionType.TOP, MAX_COLS, SINGLE_ROW), (self.conpan, self.notebook, Gtk.PositionType.BOTTOM, MAX_COLS, SINGLE_ROW), diff --git a/dzgui/views/components/buttonbox.py b/dzgui/views/components/buttonbox.py index 8020698..6b2f99a 100644 --- a/dzgui/views/components/buttonbox.py +++ b/dzgui/views/components/buttonbox.py @@ -13,7 +13,8 @@ class ContextualButton(Gtk.Button): def __init__(self, label, opens, tooltip, context): super().__init__( label=label, - tooltip_text=tooltip + tooltip_text=tooltip, + focus_on_click=False, ) self.context = context diff --git a/dzgui/views/components/connect_panel.py b/dzgui/views/components/connect_panel.py index efdb26d..dd3b31e 100644 --- a/dzgui/views/components/connect_panel.py +++ b/dzgui/views/components/connect_panel.py @@ -1,7 +1,5 @@ from typing import TYPE_CHECKING -from dzgui.api.servers import validate_ip -from dzgui.util.css import add_class, remove_class from dzgui.util.strings import connect_panel, fav_panel, lan_panel from dzgui.views.components.buttons import AddButton, ClipboardButton, SteamConnectButton from dzgui.views.components.entry import IpEntry, PortEntry @@ -63,7 +61,8 @@ class FavPanel(Gtk.Frame): user_fav, self.fav_ip = self.controller.get_favorite() server_name = f"{user_fav} ({self.fav_ip})" if user_fav is not None else connect_panel.no_fav self.fav_label = Gtk.Label(label=server_name, track_visited_links=False, halign=Gtk.Align.START, hexpand=True) - scrollable_label = Gtk.ScrolledWindow() + # NOTE: disable vscrollbar to prevent layout jumping behavior + scrollable_label = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER) scrollable_label.add(self.fav_label) self.fav_button = SteamConnectButton() diff --git a/dzgui/views/components/statusbar.py b/dzgui/views/components/statusbar.py index 4e817ca..914bfb7 100644 --- a/dzgui/views/components/statusbar.py +++ b/dzgui/views/components/statusbar.py @@ -10,32 +10,40 @@ from gi.repository import Gtk # noqa E402 if TYPE_CHECKING: from dzgui.controllers.mc import Controller -class Statusbar(Gtk.Statusbar): +class Statusbar(Gtk.Grid): def __init__(self, controller: "Controller") -> None: - super().__init__() + super().__init__(orientation=Gtk.Orientation.HORIZONTAL) self.controller = controller self.controller.register_widget("statusbar", self) help_text = strings.statusbar_helptext - self.set_text(help_text) + + self.statusbar = Gtk.Statusbar() + + self.spinner = Gtk.Spinner() + self.spinner.start() version = self.controller.get_prefs().version - self.status_right_label = Gtk.Label(label=version) - self.add(self.status_right_label) + self.status_right_label = Gtk.Label(label=version, hexpand=True, halign=Gtk.Align.END) + self.attach(self.statusbar, 0, 0, 3, 1) + self.attach_next_to(self.spinner, self.statusbar, Gtk.PositionType.RIGHT, 3, 1) + self.attach_next_to(self.status_right_label, self.spinner, Gtk.PositionType.RIGHT, 3, 1) + + self.set_text(help_text) self.players = "" def get_text(self) -> str: - area = self.get_message_area() + area = self.statusbar.get_message_area() label = area.get_children()[0] return label.get_text() def set_text(self, string: str) -> None: if string is None: return - meta = self.get_context_id("Statusbar") - self.push(meta, string) + meta = self.statusbar.get_context_id("Statusbar") + self.statusbar.push(meta, string) def refresh(self, row: "RowType") -> None: if row is None: diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index efcaf90..1c36660 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -9,9 +9,10 @@ from dzgui.util import strings, css, open_links from dzgui.views.components.label import LeftLabel from dzgui.views.components.eventbox import InfoEventBox from dzgui.views.components.buttons import WebButton +from dzgui.views.dialogs.generic import ExceptionDialog from dzgui.views.dialogs.link_dialog import WorkshopLinkDialog -from dzgui.const.enum import Preferences, Popup +from dzgui.const.enum import Preferences from dzgui.const.endpoints import STEAM_API_SETUP, BM_API_SETUP from dzgui.const.constants import ( APPID_DAYZ, @@ -427,7 +428,10 @@ class Options(Gtk.Box): prefs = self.controller.get_prefs() if prefs.paths.config.is_file() is False: # NOTE: in case file got deleted locally - self.controller.spawn_dialog(strings.config_not_found, Popup.QUIT) + dialog = ExceptionDialog(self.controller, strings.config_not_found) + dialog.run() + raise Exception + # FIXME: return to main menu return config = query.get_config(prefs.paths.config) diff --git a/dzgui/views/pages/servers.py b/dzgui/views/pages/servers.py index baf719a..e06daba 100644 --- a/dzgui/views/pages/servers.py +++ b/dzgui/views/pages/servers.py @@ -51,7 +51,7 @@ class ServerNotebook(Gtk.ScrolledWindow): pass else: pass - # TODO: set model on init of servertreeview + # TODO: set model only on init of servertreeview # TODO: set model manager for each tab on init # TODO: add set_ and get_model_manager() methods tree.set_model(store) diff --git a/dzgui/views/trees/tree_base.py b/dzgui/views/trees/tree_base.py index 2886990..4243d1c 100644 --- a/dzgui/views/trees/tree_base.py +++ b/dzgui/views/trees/tree_base.py @@ -88,8 +88,10 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore ) -> None: if is_navkey(event.keyval): - # TODO: investigate this + # FIXME: will fail on mod list tv = self.controller.get_active_treeview() + if len(tv.get_model()) < 1: + return if self.sel_blocked is False: self.controller.suppress_signal( tv, diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index 34d3621..76c47ed 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -1,6 +1,7 @@ import logging import multiprocessing import subprocess +from typing import Self import gi gi.require_version("Gtk", "3.0") @@ -145,7 +146,6 @@ class ServerTreeView(TreeView): self.controller.mediator.grid.conpan.lan.set_visible(True) def _on_unmap(self, a) -> None: - # TODO: could be done just when changing pages? if self.get_enum() is ServerTab.LAN: self.controller.mediator.grid.conpan.lan.set_visible(False) @@ -199,7 +199,7 @@ class ServerTreeView(TreeView): if self.current_proc and self.current_proc.is_alive(): self.current_proc.terminate() - def _on_distcalc_started(self, treeview): + def _on_distcalc_started(self, treeview: Self): record = self.get_record() if record is None: return