diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index c9874fd..e67bc07 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -204,8 +204,10 @@ class Controller(GObject.GObject): self.toggle_config(Preferences.DEBUG) def get_active_context(self) -> Gtk.TreeView: - tv = self.get_active_treeview() - return tv.get_enum() + return self.get_active_treeview().get_enum() + + def get_active_filterman(self) -> "FilterManager": + return self.get_active_treeview().get_filter_man() def get_active_treeview(self) -> "ServerTreeView": return self.mediator.notebook.servers.get_active_treeview() @@ -596,10 +598,6 @@ class Controller(GObject.GObject): def get_proxy_man(self) -> "ProxyModelManager": return self.proxy_man - # def set_proxy_man(self, proxy_man: "ProxyModelManager") -> None: - # # TODO: used when staging filter man outside of thread - # self.proxy_man = proxy_man - def populate_model(self, tv: Gtk.TreeView) -> None: # NOTE: skip on previously loaded tabs if tv.is_loaded(): @@ -607,26 +605,13 @@ class Controller(GObject.GObject): return ServerModelManager(self, tv).load() - def get_favorite(self) -> tuple[str, str] | tuple[None, None]: - fav = str(self.query_config(Preferences.FAV_LBL)) - if len(fav) < 1: - return None, None - ip = str(self.query_config(Preferences.FAV_SRV)) - addr = ip.split(":") - return fav, f"{addr[0]}:{addr[2]}" - def get_dist_cache(self) -> dict[str, "Haversine", "ServerTab"]: return self.dist_cache def get_filters(self) -> list: return self.mediator.filters.get_filters() - # TODO: filterman calls back to here, gets convoluted - def get_keyword(self) -> str: - tv = self.get_active_treeview() - proxy_man = tv.get_proxy_man() - return proxy_man.get_keyword_filter() - + # TODO: clean up routes between controller and filter panel def get_map_store(self) -> Gtk.ListStore: filter_man = self.get_filter_man() return filter_man.get_map_store() @@ -639,16 +624,7 @@ class Controller(GObject.GObject): filter_man = self.get_filter_man() return filter_man.get_filters() - def get_prior_map(self) -> str: - filter_man = self.get_filter_man() - return filter_man.get_prior_map() - - def get_active_map(self) -> None: - return self.get_filter_man().get_active_map() - - def set_active_map(self, ind: int) -> None: - self.get_filter_man().set_active_map(ind) - + # TODO: rename def _on_servers_loaded_init(self, emitter: "Emitter") -> None: # FIXME: wipe maps store when changing tabs if model is none tv = self.get_active_treeview() @@ -661,23 +637,16 @@ class Controller(GObject.GObject): treeview = self.get_active_treeview() return treeview.get_model() is not None - # TODO: this could be internal to filter panel now def _on_check_toggled(self, emitter: Emitter, label: str, state: bool) -> None: filter_man = self.get_filter_man() filter_man.set_filter(label, state) - if state: - mode = FilterMode.TOGGLE_ON - else: - mode = FilterMode.TOGGLE_OFF + mode = FilterMode.TOGGLE_ON if state else FilterMODE.TOGGLE_OFF ServerModelManager(self, self.get_active_treeview()).refilter(mode, label) - # TODO: map man should be consolidated into filter man def _on_map_selection_changed(self, emitter: Emitter, selection: str) -> None: - filter_man = self.get_filter_man() - filter_man.set_selected_map(selection) - # FIXME: label should not be mandatory - ServerModelManager(self, self.get_active_treeview()).refilter(FilterMode.MAP, selection) + smm = ServerModelManager(self, self.get_active_treeview()) + smm.refilter(FilterMode.MAP, selection) def get_notebook(self) -> "Notebook": return self.mediator.notebook diff --git a/dzgui/managers/config.py b/dzgui/managers/config.py index 470af34..ba5ea1b 100644 --- a/dzgui/managers/config.py +++ b/dzgui/managers/config.py @@ -54,6 +54,14 @@ class ConfigManager: def get_favorites(self) -> list[str]: return self.lookup(Preferences.IP_LIST) + def get_favorite(self) -> tuple[str, str] | tuple[None, None]: + fav = str(self.lookup(Preferences.FAV_LBL)) + if len(fav) < 1: + return None, None + ip = str(self.lookup(Preferences.FAV_SRV)) + addr = ip.split(":") + return fav, f"{addr[0]}:{addr[2]}" + def is_in_favs(self, record: str) -> bool: favs = self.get_favorites() if record in favs: diff --git a/dzgui/managers/filter_man.py b/dzgui/managers/filter_man.py index 101f9e1..60e0102 100644 --- a/dzgui/managers/filter_man.py +++ b/dzgui/managers/filter_man.py @@ -12,8 +12,6 @@ class FilterManager: def __init__(self) -> None: self.map_store = ModelFactory().make_map_store() - self.prior_map: str - self.selected_map = all_maps # TODO: namespace under strings.filters self.default_filters = { @@ -30,18 +28,37 @@ class FilterManager: strings.filter_unofficial: True, strings.filter_modded: True, } - self.active_map = 0 + + self.active_keyword = "" + self.active_map = (0, all_maps) + self.prior_map = all_maps + self.filters: list self.enabled_filters = dict(self.default_filters) - def reinit_filters(self) -> None: - self.enabled_filters = dict(self.default_filters) + # def reinit_filters(self) -> None: + # self.enabled_filters = dict(self.default_filters) + + def get_prior_map(self) -> str: + return self.prior_map + + def set_prior_map(self, name: str) -> None: + self.prior_map = name + + def get_active_map_name(self) -> str: + return self.active_map[1] def get_active_map(self) -> int: return self.active_map - def set_active_map(self, ind: int) -> None: - self.active_map = ind + def set_active_map(self, ind: int, name: str) -> None: + self.active_map = (ind, name) + + def get_active_keyword(self) -> str: + return self.active_keyword + + def set_active_keyword(self, word: str) -> None: + self.active_keyword = word def get_default_filters(self) -> dict: """Deep copy of defaults""" @@ -60,24 +77,14 @@ class FilterManager: model = ModelFactory().make_map_store() model.append([all_maps]) self.map_store = model - self.selected_map = all_maps + self.active_map = (0, all_maps) def get_prior_map(self) -> str: return self.prior_map - def get_selected_map(self) -> str: - return self.selected_map - - def set_selected_map(self, selection: str) -> str: - self.prior_map = self.selected_map - self.selected_map = selection - def append_map(self, row: list[str]) -> None: self.map_store.append(row) - def clear_map_store(self) -> None: - self.map_store.clear() - def set_unique_maps(self, maps: list) -> None: if maps is None: return @@ -88,6 +95,14 @@ class FilterManager: for m in maps: self.append_map([m]) - # when switching views, just grab the map store, active keyword, active map, and selected checks - # for that view and apply them to filter panel outside of thread - # initialize proxyman with access to filterman + def get_all_filters(self) -> tuple: + map_name = self.get_active_map_name() + enabled = self.get_filters() + kw = self.get_active_keyword() + filters = [] + filters.append(map_name) + filters.append(kw) + for filt in enabled: + if enabled[filt] is False: + filters.append(filt) + return tuple(filters) diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index 33df8e6..21f3e32 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -1,55 +1,57 @@ import re from typing import Optional, TYPE_CHECKING +from warnings import deprecated from dzgui.const.enum import FilterMode from dzgui.model.model_factory import ModelFactory from dzgui.util import strings -import gi - -gi.require_version("Gtk", "3.0") -from gi.repository.Gtk import ListStore # noqa E402 -from gi.repository import GObject, GLib # noqa E402 +# import gi +# +# gi.require_version("Gtk", "3.0") +# from gi.repository.Gtk import ListStore # noqa E402 +# from gi.repository import GObject, GLib # noqa E402 if TYPE_CHECKING: - from dzgui.controllers.mc import Controller - from dzgui.controllers.emitter import Emitter + # from dzgui.controllers.mc import Controller + # from dzgui.controllers.emitter import Emitter + from dzgui.managers.filter_man import FilterManager + from dzgui.model.model_factory import FastInsertListStore class ProxyModelManager: """ - Manages access to cached ListStore resources and + Manages access to cached FastInsertListStore resources and performs filtering on behalf of atomic TreeViews, which share the same column structure. A ProxyModelManager is attached to each ServerTreeView. - Raw data is cached before being packed into a ListStore, see get_control() + Raw data is cached before being packed into a FastInsertListStore, see get_control() Filtration creates a proxy of the TreeView's model, see get_proxy_model() """ - def __init__(self, controller: "Controller") -> None: - self.controller = controller - self.emitter = controller.get_emitter() - self.emitter.connect("keyword_set", self._on_keyword_set) + def __init__(self, filter_man: "FilterManager") -> None: + # self.emitter = controller.get_emitter() + # self.emitter.connect("keyword_set", self._on_keyword_set) self.filter_cache = {} self.ping_cache: dict[str, int] = {} - self.proxy_model: ListStore = None + self.proxy_model: "FastInsertListStore" = None + self.filter_man = filter_man self.control_model: list = None self.filtered: list = None self.success = True - self.keyword_filter = "" - def get_keyword_filter(self) -> str: - return self.keyword_filter + # def get_keyword_filter(self) -> str: + # return self.keyword_filter - def _on_keyword_set(self, emitter: "Emitter", keyword: str) -> None: - self.keyword_filter = keyword + # def _on_keyword_set(self, emitter: "Emitter", keyword: str) -> None: + # self.keyword_filter = keyword def append_row(self, row: list) -> None: self.proxy_model.append(row) @@ -57,19 +59,19 @@ class ProxyModelManager: def clear_proxy_model(self) -> None: self.proxy_model.clear() - def get_proxy_model(self) -> ListStore: + def get_proxy_model(self) -> "FastInsertListStore": return self.proxy_model - def new_model_from_class(self, cls: type) -> ListStore: - store = ListStore(*[ftype for field, ftype in cls.__annotations__.items()]) - return store - + # def new_model_from_class(self, cls: type) -> ListStore: + # store = ListStore(*[ftype for field, ftype in cls.__annotations__.items()]) + # return store + # def filter(self, mode: FilterMode, *args, **kwargs) -> None: """ Native Gtk.TreeView.refilter() method was not performant enough when running in the main loop with 40k+ records """ - filters = self.controller.get_filters() + filters = self.filter_man.get_all_filters() if filters in self.filter_cache: cache = self.filter_cache[filters] @@ -82,7 +84,7 @@ class ProxyModelManager: rows = self.filter_initial(filters) case FilterMode.MAP: - prior_map = self.controller.get_prior_map() + prior_map = self.filter_man.get_prior_map() if prior_map == strings.all_maps: rows = self.filter_map(filters) @@ -107,7 +109,7 @@ class ProxyModelManager: # if row[7] in self.ping_cache: # row[9] = self.ping_cache[row[7]] - # NOTE: this ListStore manipulation must remain local to the thread + # NOTE: this FastInsertListStore manipulation must remain local to the thread clone = ModelFactory().make_server_store() if len(rows) > 0: rows = self.sort_rows(rows) @@ -135,7 +137,7 @@ class ProxyModelManager: Multi-filtration for any context starts by narrowing by map """ rows = self.filtered - sel_map = self.controller.get_selected_map() + sel_map = self.filter_man.get_active_map_name() if sel_map == strings.all_maps: return rows @@ -144,7 +146,7 @@ class ProxyModelManager: return rows def filter_keyword(self, filters: tuple) -> list: - keyword = self.keyword_filter + keyword = self.filter_man.get_active_keyword() rows = self.filtered if keyword == "": @@ -219,7 +221,7 @@ class ProxyModelManager: self.set_filtered(self.filter_toggle_off(filters, f)) return self.filtered - def set_cache(self, filters: tuple, model: ListStore | None, rows: list) -> None: + def set_cache(self, filters: tuple, model: Optional["FastInsertListStore"], rows: list) -> None: self.filter_cache[filters] = (model, rows) def resync_model(self, addr: str, qport: int) -> None: @@ -233,11 +235,12 @@ class ProxyModelManager: self.control_model.remove(row) self.wipe_cache() - filters = self.controller.get_filters() + filters = self.get_filters() refiltered = self.filter_toggle_on(filters) self.set_filtered(refiltered) - def convert_model_to_list(self, model: ListStore) -> list: + @deprecated("Currently unused") + def convert_model_to_list(self, model: "FastInsertListStore") -> list: return [[el for el in row] for row in model] def set_filtered(self, rows: Optional[list]) -> None: @@ -248,9 +251,9 @@ class ProxyModelManager: def get_filtered(self) -> list: return self.filtered - def set_proxy_model(self, model: ListStore | None) -> None: + def set_proxy_model(self, model: Optional["FastInsertListStore"]) -> None: """ - ListStore representation of the raw model after filtration + FastInsertListStore representation of the raw model after filtration """ self.proxy_model = model @@ -282,9 +285,3 @@ class ProxyModelManager: self.ping_cache = {} # if full: # self.control_model = None - - def set_active_map(self, _map: str) -> None: - self.active_map = _map - - def get_active_map(self) -> str: - return self.active_map diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index ed28ad6..f5e9ddc 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -304,10 +304,9 @@ class ServerModelManager: # if data is None: # self.to_insert = None # else: - # FIXME: filterman calls GTK methods in thread - # if mode == FilterMode.INITIAL: manager = self._get_proxy_man() + # TODO: consolidate these methods manager.wipe_cache() manager.set_control(data) manager.filter(FilterMode.INITIAL) diff --git a/dzgui/views/components/connect_panel.py b/dzgui/views/components/connect_panel.py index 18f31c7..d51834c 100644 --- a/dzgui/views/components/connect_panel.py +++ b/dzgui/views/components/connect_panel.py @@ -123,7 +123,7 @@ class FavPanel(Gtk.Frame): emitter = self.controller.get_emitter() emitter.connect("fav_server_changed", self._on_fav_server_changed) - self.server_name, self.server_ip = self.controller.get_favorite() + self.server_name, self.server_ip = self.controller.get_config_man().get_favorite() #self.controller.get_favorite() server_name = ( f"{self.server_name} ({self.server_ip})" if self.server_name is not None @@ -207,6 +207,7 @@ class AddPanel(Gtk.Frame): def _submit_query(self) -> None: text = self.entry.get_text() + # TODO: grabs ServerTab.SAVED ServerTreeView saved = self.controller.get_servers().get_saved() ServerModelManager(self.controller, saved)._add_by_id_or_ip(text) diff --git a/dzgui/views/components/filter_panel.py b/dzgui/views/components/filter_panel.py index 700074e..49cd92c 100644 --- a/dzgui/views/components/filter_panel.py +++ b/dzgui/views/components/filter_panel.py @@ -4,6 +4,7 @@ from typing import Literal, TYPE_CHECKING from dzgui.const.enum import FilterMode from dzgui.const.constants import EXPAND, NO_EXPAND, NO_FILL, NO_PADDING, SEARCH_ICON from dzgui.model.servers import ServerModelManager +from dzgui.util.strings import all_maps from dzgui.views.components.labels import BoldLabel import gi @@ -82,6 +83,7 @@ class KeywordEntry(Gtk.Entry): # TODO :strings super().__init__(placeholder_text="Filter by keyword") + self.keyword = "" self.controller = controller self.emitter = controller.get_emitter() self.connect("activate", self._on_activated) @@ -112,13 +114,18 @@ class KeywordEntry(Gtk.Entry): # self.controller.mediator.window.set_keep_below(False) keyword = entry.get_text().lower() - if keyword == self.controller.get_keyword(): + if keyword == self.keyword: return if keyword.isspace(): return + self.keyword = keyword + # TODO: delegate to controller + tv = self.controller.get_active_treeview() + filter_man = tv.get_filter_man() + filter_man.set_active_keyword(keyword) + logger.info(f"User filtered by keyword '{keyword}'") - self.emitter.emit("keyword_set", keyword) ServerModelManager( self.controller, self.controller.get_active_treeview() @@ -141,15 +148,8 @@ class FilterPanel(Gtk.Box): self.controller.register_widget("filters", self) self.emitter = self.controller.get_emitter() - # TODO: strings - self.sel_map = "All maps" + self.sel_map = all_maps - # TODO: each treeview for that tab context has its own filter manager, independent of this - # self.selected_map: str = strings.all_maps - # self.prior_map: str = strings.all_maps - # self.set_orientation(Gtk.Orientation.VERTICAL) - - # TODO: initialize to empty filter_man = self.controller.get_filter_man() defaults = filter_man.get_default_filters() self.map_store = filter_man.get_map_store() @@ -191,9 +191,6 @@ class FilterPanel(Gtk.Box): self.maps_combo.connect("changed", self._on_map_changed) self.maps_combo.connect("key-press-event", self._on_combo_keypress) - # FIXME: should be a property of treeview's meta manager - self.active_map = 0 - for el in ( self.filters_label, self.keyword_entry, @@ -212,38 +209,15 @@ class FilterPanel(Gtk.Box): for i, row in enumerate(model): if text == row[0]: self.maps_combo.set_active(i) - # self.controller.set_active_map(i) - # print("HERE3") + # TODO: use same sort of signal to reinitialize keyword and checks def _on_maps_loaded(self, emitter: "Emitter", store: Gtk.ListStore) -> None: self.maps_combo.set_model(store) - ind = self.controller.get_active_map() + tv = self.controller.get_active_treeview() + ind, name = tv.filter_man.get_active_map() self.maps_combo.set_active(ind) self.button_grid.reload_filters() - # TODO: move into metamanager - def get_filters(self) -> tuple: - selected = self.controller.get_selected_map() - enabled = self.controller.get_enabled_filters() - filters = [] - filters.append(selected) - filters.append(self.controller.get_keyword()) - for filt in enabled: - if enabled[filt] is False: - filters.append(filt) - return tuple(filters) - - # TODO: should be used when switching ServerTab contexts - # use signals here - # def reinit_panel(self) -> None: - # self.keyword_entry.set_text("") - # self.keyword_filter = "" - # self.reinit_filters() - # self.set_visible(False) - # sel_panel = self.controller.mediator.grid.sel_panel - # if sel_panel.is_visible(): - # sel_panel.set_visible(False) - # TODO: this chiefly applies when clicking refresh button, etc. # and setting filters to default state # def reinit_filters(self) -> None: @@ -296,12 +270,6 @@ class FilterPanel(Gtk.Box): case _: return False - # def get_active_combo(self) -> int: - # return self.maps_combo.get_active() - # - # def set_active_combo(self, row: int) -> None: - # self.maps_combo.set_active(row) - def toggle_check_by_key(self, emitter: "Emitter", keyval: int) -> bool: mappings = { Gdk.KEY_1: 0, @@ -333,10 +301,15 @@ class FilterPanel(Gtk.Box): ind = combo.get_active() if ind < 0: return - selection = self.maps_entry.get_text() - self.sel_map = selection - self.emitter.emit("map_selection_changed", selection) + name = self.maps_entry.get_text() + self.prior_map = self.sel_map + self.sel_map = name + # TODO: abstraction into controller: + # pass ind, name tv = self.controller.get_active_treeview() - proxy_man = tv.get_proxy_man() - proxy_man.set_active_map(selection) + filter_man = tv.get_filter_man() + filter_man.set_prior_map(name) + filter_man.set_active_map(ind, name) + + self.emitter.emit("map_selection_changed", name) diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index 427df2e..bc6568c 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -45,12 +45,11 @@ class ServerTreeView(ContextMixin, TreeView): self.loaded = False - self.proxy_man = ProxyModelManager(controller) + self.filter_man = FilterManager() + self.proxy_man = ProxyModelManager(self.filter_man) model = self.proxy_man.get_proxy_model() self.set_model(model) - self.filter_man = FilterManager() - self.set_fixed_height_mode(True) # NOTE: headers become visible on model load self.set_headers_visible(False) @@ -125,7 +124,7 @@ class ServerTreeView(ContextMixin, TreeView): # TODO: why is this being saved? self.thread = None - def start_timeout(self) -> None: + def start_queue_checker(self) -> None: self.queue_id = GLib.timeout_add(QUEUE_CHECK_DELAY, self._check_result_queue) def get_filter_man(self) -> FilterManager: @@ -165,10 +164,11 @@ class ServerTreeView(ContextMixin, TreeView): self.emitter.emit("lan_tab_toggled", True) store = self.filter_man.get_map_store() + # FIXME: if model is none, wipe maps self.emitter.emit("load_maps", store) self.handler_id = self.emitter.connect("statusbar_loaded", self.start_distcalc) - self.start_timeout() + self.start_queue_checker() self.start_distcalc() def _on_unmap(self, a) -> None: