diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index 4f1b4c2..df44210 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -39,7 +39,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.model import ModelManager +from dzgui.model.misc_model import ModelManager from dzgui.util import strings from dzgui.util.diag import write_diagnostic from dzgui.util._json import read_json, write_json @@ -91,7 +91,9 @@ class Controller: self.crumbs_cache = "" self.mediator = AppNavigation() self.prefs: UserPrefs - self.model_manager = ModelManager() + + self.model_man = ModelManager() + # TODO: poc self.loaded = False @@ -107,32 +109,20 @@ class Controller: def get_crumbs(self) -> str: return self.mediator.grid.get_breadcrumbs() - def get_server_store(self) -> Gtk.ListStore: - return self.model_manager.get_server_store() - - def get_saved_store(self) -> Gtk.ListStore: - return self.model_manager.get_saved_store() - - def get_recent_store(self) -> Gtk.ListStore: - return self.model_manager.get_recent_store() - - def get_lan_store(self) -> Gtk.ListStore: - return self.model_manager.get_lan_store() - def get_help_store(self) -> Gtk.ListStore: - return self.model_manager.get_help_store() + return self.model_man.get_help_store() def get_map_store(self) -> Gtk.ListStore: - return self.model_manager.get_map_store() + return self.model_man.get_map_store() def get_modlist_store(self) -> Gtk.ListStore: - return self.model_manager.get_modlist_store() + return self.model_man.get_modlist_store() def get_mod_store(self) -> Gtk.ListStore: - return self.model_manager.get_mod_store() + return self.model_man.get_mod_store() def get_log_store(self) -> Gtk.ListStore: - return self.model_manager.get_log_store() + return self.model_man.get_log_store() def terminate_process(self) -> None: # TODO: only used by server table multiprocessing queue @@ -152,10 +142,10 @@ class Controller: return self.query_config(Preferences.INSTALL) def reinit_map_store(self) -> None: - self.model_manager.set_all_maps() + self.model_man.set_all_maps() def append_map(self, map_row: list) -> None: - self.model_manager.append_map(map_row) + self.model_man.append_map(map_row) def unblock_signals(self) -> None: self.block_signals(False) @@ -197,7 +187,7 @@ class Controller: def toggle_debug_mode(self) -> None: self.toggle_config(Preferences.DEBUG) - def get_active_treeview(self) -> "TreeView": + def get_active_treeview(self) -> "ServerTreeView": return self.mediator.notebook.servers.get_active_treeview() def grab_active_treeview(self) -> None: @@ -278,7 +268,7 @@ class Controller: def load_mods(self) -> None: # TODO: threading - model = self.model_manager.get_mod_store() + model = self.model_man.get_mod_store() model.clear() path = self.query_config(Preferences.DEFAULT) mods = get_delimited_mods(Path(path)) @@ -394,7 +384,7 @@ class Controller: # TODO: put in model manager (dedicated manager for mod store) def get_mod_from_tree_path(self, tree_path: Gtk.TreePath) -> tuple[str, Gtk.TreeIter]: - model = self.model_manager.get_mod_store() + model = self.model_man.get_mod_store() tree_iter = model.get_iter(tree_path) mod = model.get(tree_iter, 2)[0] return mod, tree_iter @@ -423,7 +413,7 @@ class Controller: except PeFile.AppNotInstalledError: pass - model = self.model_manager.get_mod_store() + model = self.model_man.get_mod_store() model.remove(it) @@ -435,7 +425,7 @@ class Controller: self.mediator.statusbar.spinner.stop() def calc_mod_size(self) -> tuple[int, int]: - model = self.model_manager.get_mod_store() + model = self.model_man.get_mod_store() total_mods = len(model) total_size = 0 for mod in model: @@ -493,7 +483,7 @@ class Controller: def populate_log(self) -> None: log = self.prefs.paths.debug - store = self.model_manager.get_log_store() + store = self.model_man.get_log_store() store.clear() # NOTE: this model is reloaded each time as log changes try: @@ -508,7 +498,7 @@ class Controller: self.open_page(NotebookPage.LOG) def select_colorized(self) -> None: - model = self.model_manager.get_mod_store() + model = self.model_man.get_mod_store() sel = self.mediator.modtreeview.get_selection() for mod in model: it = mod.iter @@ -517,14 +507,14 @@ class Controller: sel.select_path(path) def uncolorize_mods(self) -> None: - model = self.model_manager.get_mod_store() + model = self.model_man.get_mod_store() for mod in model: it = mod.iter path = model.get_path(it) model[path][4] = None def colorize_mods(self) -> None: - model = self.model_manager.get_mod_store() + model = self.model_man.get_mod_store() stale = find_stale_mods(self.prefs.paths.config) for mod in model: it = mod.iter @@ -544,7 +534,7 @@ class Controller: # TODO: use model managers, etc. 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, "172.111.51.156: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], @@ -572,19 +562,19 @@ class Controller: self.get_func_data() self.destroy_on_idle() - def get_func_data(self) -> tuple: + def get_func_data(self): 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) + manager.append_row(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() + + manager = treeview.get_filter_man() data = self.data GLib.idle_add(test) @@ -697,16 +687,14 @@ class Controller: self.mediator.statusbar.spinner.start() def populate_model(self) -> None: - # 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() + # NOTE: cf. set_query_func() func = treeview.get_query_func() if func is not None: - model = treeview.get_model() + manager = treeview.get_filter_man() # TODO: this may lag? - model.clear() + manager.clear_model() self.set_callback(None, None) self.call_on_thread(func) diff --git a/dzgui/controllers/model.py b/dzgui/model/filtered_model.py similarity index 77% rename from dzgui/controllers/model.py rename to dzgui/model/filtered_model.py index 8dae7e4..06010b5 100644 --- a/dzgui/controllers/model.py +++ b/dzgui/model/filtered_model.py @@ -2,7 +2,7 @@ import re from dataclasses import dataclass from typing import Self -from dzgui.const.enum import FilterMode, HELP_MENU_ROWS +from dzgui.const.enum import FilterMode from dzgui.util import strings import gi @@ -10,7 +10,6 @@ gi.require_version("Gtk", "3.0") from gi.repository.Gtk import ListStore # noqa E402 from gi.repository import GObject, GLib # noqa E402 -@dataclass @dataclass(slots=True, frozen=True) class ServerColumns: name: str @@ -27,105 +26,38 @@ class ServerColumns: modded: bool -@dataclass(slots=True, frozen=True) -class ModCols: - name: str - symlink: str - directory: str - size: float - color: str - - -@dataclass(slots=True, frozen=True) -class LogCols: - timestamp: str - flag: str - traceback: str - msg: str - - -@dataclass(slots=True, frozen=True) -class ServerModCols: - name: str - uid: GObject.TYPE_INT64 - installed: str - - -@dataclass(slots=True, frozen=True) -class MenuCols: - name: str - hidden: GObject.TYPE_PYOBJECT - - -class ModelManager: +class FilteredModelManager: """ Manages access to cached ListStore resources and - performs filtering on behalf of TreeViews. + performs filtering on behalf of atomic TreeViews, + which share the same column structure. - Methods are not thread-safe in themselves. + A FilteredModelManager is attached to each ServerTreeView. + Filter methods are not thread-safe in themselves. """ def __init__(self) -> None: self.filter_cache: tuple self.ping_cache: dict[str, int] = {} - self.map_store = ListStore(str) - self.help_store = self.new_model_from_class(MenuCols) - self.mod_store = self.new_model_from_class(ModCols) - self.log_store = self.new_model_from_class(LogCols) - self.modlist_store = self.new_model_from_class(ServerModCols) - - self.server_store = self.new_model() - self.saved_store = self.new_model() - self.recent_store = self.new_model() - self.lan_store = self.new_model() - - for row in HELP_MENU_ROWS: - label = row.dict["label"] - self.help_store.append([label, row]) + self.model = self.new_model_from_class(ServerColumns) self.control_model = None self.filtered = None self.success = True - #def __new__(cls) -> Self: - # if not hasattr(cls, "instance"): - # cls.instance = super(ModelManager, cls).__new__(cls) - # return cls.instance + def append_row(self, row: list) -> None: + self.model.append(row) - def get_recent_store(self) -> ListStore: - return self.recent_store + def clear_model(self) -> None: + self.model.clear() - def get_lan_store(self) -> ListStore: - return self.lan_store - - def get_saved_store(self) -> ListStore: - return self.saved_store - - def get_server_store(self) -> ListStore: - return self.server_store + def get_model(self) -> ListStore: + return self.model def new_model_from_class(self, cls: type) -> ListStore: store = ListStore(*[ftype for field, ftype in cls.__annotations__.items()]) return store - def get_map_store(self) -> ListStore: - return self.map_store - - #def get_row_store(self) -> ListStore: - # return self.row_store - - def get_help_store(self) -> ListStore: - return self.help_store - - def get_mod_store(self) -> ListStore: - return self.mod_store - - def get_modlist_store(self) -> ListStore: - return self.modlist_store - - def get_log_store(self) -> ListStore: - return self.log_store - def filter(self, mode: FilterMode, *args, **kwargs) -> None: """ Native Gtk.TreeView.refilter() method was not performant enough @@ -290,10 +222,6 @@ class ModelManager: ) -> None: self.filter_cache[filters] = (model, rows) - def new_model(self) -> ListStore: - store = self.new_model_from_class(ServerColumns) - return store - def resync_model(self, addr: str, qport: int) -> None: """ Handle in-situ updates to model during @@ -344,10 +272,3 @@ class ModelManager: self.ping_cache = {} if full: self.control_model = None - - def set_all_maps(self) -> None: - self.map_store.clear() - self.map_store.append(["All maps"]) - - def append_map(self, row: list) -> None: - self.map_store.append(row) diff --git a/dzgui/views/pages/servers.py b/dzgui/views/pages/servers.py index e06daba..42fdfc2 100644 --- a/dzgui/views/pages/servers.py +++ b/dzgui/views/pages/servers.py @@ -24,38 +24,22 @@ class ServerNotebook(Gtk.ScrolledWindow): self.controller.register_widget("servers", self) self.notebook = Gtk.Notebook(show_tabs=True) - self.browser = ServerTreeView(controller, ServerTab.BROWSER) - self.saved = ServerTreeView(controller, ServerTab.SAVED) - self.recent = ServerTreeView(controller, ServerTab.RECENT) - self.lan = ServerTreeView(controller, ServerTab.LAN) + self.browser = ServerTreeView(controller, ServerTab.BROWSER, ContextMenuGroup.SERVER_BROWSER) + self.saved = ServerTreeView(controller, ServerTab.SAVED, ContextMenuGroup.SAVED) + self.recent = ServerTreeView(controller, ServerTab.RECENT, ContextMenuGroup.RECENT) + self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN) self.browser.set_query_func(self.query_test2) self.lan.set_query_func(self.query_test) - # TODO: set context menu on init of ServerTreeView? - self.browser.set_context_menu(ContextMenuGroup.SERVER_BROWSER) - self.saved.set_context_menu(ContextMenuGroup.SAVED) - self.recent.set_context_menu(ContextMenuGroup.RECENT) - self.lan.set_context_menu(ContextMenuGroup.SCAN_LAN) - tabs = [ - (self.browser, server_labels.browser, self.controller.get_server_store), - (self.saved, server_labels.saved, self.controller.get_saved_store), - (self.recent, server_labels.recent, self.controller.get_recent_store), - (self.lan, server_labels.lan, self.controller.get_lan_store), - ] - - for tree, label, func in tabs: - store = func() - if label == "LAN": - pass - else: - pass - # 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) + (self.browser, server_labels.browser), + (self.saved, server_labels.saved), + (self.recent, server_labels.recent), + (self.lan, server_labels.lan), + ] + for tree, label in tabs: scrolled = Gtk.ScrolledWindow() scrolled.add(tree) self.notebook.append_page(scrolled, Gtk.Label(label=label)) diff --git a/dzgui/views/trees/tree_base.py b/dzgui/views/trees/tree_base.py index 4243d1c..b743958 100644 --- a/dzgui/views/trees/tree_base.py +++ b/dzgui/views/trees/tree_base.py @@ -88,14 +88,13 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore ) -> None: if is_navkey(event.keyval): - # FIXME: will fail on mod list - tv = self.controller.get_active_treeview() - if len(tv.get_model()) < 1: + # TODO: if model is None + if len(self.get_model()) < 1: return if self.sel_blocked is False: self.controller.suppress_signal( - tv, - tv.selected_row, + self, + self.selected_row, "_on_tree_selection_changed", True, ) diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index 76c47ed..c1e810d 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -15,6 +15,7 @@ from dzgui.const.enum import ( from dzgui.const.constants import UDP_PORT from dzgui.const.enum import ServerTab from dzgui.api.servers import Record +from dzgui.model.filtered_model import FilteredModelManager from dzgui.util.dist import CalcDist from dzgui.util.keys import is_navkey from dzgui.util import strings @@ -40,7 +41,7 @@ class ServerTreeView(TreeView): __gsignals__ = { "on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ()) } - def __init__(self, controller: "Controller", enum: ServerTab) -> None: + def __init__(self, controller: "Controller", enum: ServerTab, menu: ContextMenuGroup) -> None: super().__init__(controller) QUEUE_CHECK_DELAY = 200 @@ -49,10 +50,15 @@ class ServerTreeView(TreeView): self.loaded = False self.query_func: Callable = None + self.filter_man = FilteredModelManager() + model = self.filter_man.get_model() + self.set_model(model) + self.menu = Gtk.Menu() self.menu.connect("key-press-event", self._on_key) self.controller = controller + self.set_context_menu(menu) self.set_fixed_height_mode(True) self.set_headers_visible(True) @@ -113,6 +119,9 @@ class ServerTreeView(TreeView): GLib.timeout_add(QUEUE_CHECK_DELAY, self._check_result_queue) + def get_filter_man(self) -> FilteredModelManager: + return self.filter_man + def shrink_to_fit(self) -> None: cols = self.get_columns() # TODO: run on only one treeview and propagate results @@ -203,6 +212,8 @@ class ServerTreeView(TreeView): record = self.get_record() if record is None: return + # TODO: + self.controller.mediator.statusbar.spinner.start() ip = record.ip self.current_proc = CalcDist(record.ip, self.queue, self.controller) self.current_proc.start()