feat: initialize ServerTreeViews with atomic FilterModelManagers

This commit is contained in:
aclist committed 2026-01-19 14:55:32 +09:00
1 parent fdd6cefe48
commit 6f29b1318a
5 files changed
+66 -163

No files matched your search

+28 -40
View File
@@ -39,7 +39,7 @@ from dzgui.const.constants import (
from dzgui.config import update from dzgui.config import update
from dzgui.config.query import lookup from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs 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 import strings
from dzgui.util.diag import write_diagnostic from dzgui.util.diag import write_diagnostic
from dzgui.util._json import read_json, write_json from dzgui.util._json import read_json, write_json
@@ -91,7 +91,9 @@ class Controller:
self.crumbs_cache = "" self.crumbs_cache = ""
self.mediator = AppNavigation() self.mediator = AppNavigation()
self.prefs: UserPrefs self.prefs: UserPrefs
self.model_manager = ModelManager()
self.model_man = ModelManager()
# TODO: poc # TODO: poc
self.loaded = False self.loaded = False
@@ -107,32 +109,20 @@ class Controller:
def get_crumbs(self) -> str: def get_crumbs(self) -> str:
return self.mediator.grid.get_breadcrumbs() 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: 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: 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: 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: 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: 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: def terminate_process(self) -> None:
# TODO: only used by server table multiprocessing queue # TODO: only used by server table multiprocessing queue
@@ -152,10 +142,10 @@ class Controller:
return self.query_config(Preferences.INSTALL) return self.query_config(Preferences.INSTALL)
def reinit_map_store(self) -> None: 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: 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: def unblock_signals(self) -> None:
self.block_signals(False) self.block_signals(False)
@@ -197,7 +187,7 @@ class Controller:
def toggle_debug_mode(self) -> None: def toggle_debug_mode(self) -> None:
self.toggle_config(Preferences.DEBUG) 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() return self.mediator.notebook.servers.get_active_treeview()
def grab_active_treeview(self) -> None: def grab_active_treeview(self) -> None:
@@ -278,7 +268,7 @@ class Controller:
def load_mods(self) -> None: def load_mods(self) -> None:
# TODO: threading # TODO: threading
model = self.model_manager.get_mod_store() model = self.model_man.get_mod_store()
model.clear() model.clear()
path = self.query_config(Preferences.DEFAULT) path = self.query_config(Preferences.DEFAULT)
mods = get_delimited_mods(Path(path)) mods = get_delimited_mods(Path(path))
@@ -394,7 +384,7 @@ class Controller:
# TODO: put in model manager (dedicated manager for mod store) # 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]: 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) tree_iter = model.get_iter(tree_path)
mod = model.get(tree_iter, 2)[0] mod = model.get(tree_iter, 2)[0]
return mod, tree_iter return mod, tree_iter
@@ -423,7 +413,7 @@ class Controller:
except PeFile.AppNotInstalledError: except PeFile.AppNotInstalledError:
pass pass
model = self.model_manager.get_mod_store() model = self.model_man.get_mod_store()
model.remove(it) model.remove(it)
@@ -435,7 +425,7 @@ class Controller:
self.mediator.statusbar.spinner.stop() self.mediator.statusbar.spinner.stop()
def calc_mod_size(self) -> tuple[int, int]: 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_mods = len(model)
total_size = 0 total_size = 0
for mod in model: for mod in model:
@@ -493,7 +483,7 @@ class Controller:
def populate_log(self) -> None: def populate_log(self) -> None:
log = self.prefs.paths.debug log = self.prefs.paths.debug
store = self.model_manager.get_log_store() store = self.model_man.get_log_store()
store.clear() store.clear()
# NOTE: this model is reloaded each time as log changes # NOTE: this model is reloaded each time as log changes
try: try:
@@ -508,7 +498,7 @@ class Controller:
self.open_page(NotebookPage.LOG) self.open_page(NotebookPage.LOG)
def select_colorized(self) -> None: 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() sel = self.mediator.modtreeview.get_selection()
for mod in model: for mod in model:
it = mod.iter it = mod.iter
@@ -517,14 +507,14 @@ class Controller:
sel.select_path(path) sel.select_path(path)
def uncolorize_mods(self) -> None: def uncolorize_mods(self) -> None:
model = self.model_manager.get_mod_store() model = self.model_man.get_mod_store()
for mod in model: for mod in model:
it = mod.iter it = mod.iter
path = model.get_path(it) path = model.get_path(it)
model[path][4] = None model[path][4] = None
def colorize_mods(self) -> 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) stale = find_stale_mods(self.prefs.paths.config)
for mod in model: for mod in model:
it = mod.iter it = mod.iter
@@ -544,7 +534,7 @@ class Controller:
# TODO: use model managers, etc. # TODO: use model managers, etc.
self.data = ( 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, "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], ["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.get_func_data()
self.destroy_on_idle() self.destroy_on_idle()
def get_func_data(self) -> tuple: def get_func_data(self):
def test(): def test():
# NOTE: do not insert model until main thread is idle # NOTE: do not insert model until main thread is idle
# TODO: grab from model manager and insert entire model # TODO: grab from model manager and insert entire model
# TODO: do not start refresh button count until load finished # TODO: do not start refresh button count until load finished
for row in data: for row in data:
model.append(row) manager.append_row(row)
treeview.set_loaded(True) treeview.set_loaded(True)
self.update_server_status() self.update_server_status()
treeview.grab_focus() treeview.grab_focus()
# TODO: use model manager
treeview = self.get_active_treeview() treeview = self.get_active_treeview()
model = treeview.get_model()
manager = treeview.get_filter_man()
data = self.data data = self.data
GLib.idle_add(test) GLib.idle_add(test)
@@ -697,16 +687,14 @@ class Controller:
self.mediator.statusbar.spinner.start() self.mediator.statusbar.spinner.start()
def populate_model(self) -> None: def populate_model(self) -> None:
# TODO: always use same server model, store in servertreeview class
treeview = self.get_active_treeview() treeview = self.get_active_treeview()
if treeview.get_loaded() is False: if treeview.get_loaded() is False:
new_model = self.model_manager.new_model() # NOTE: cf. set_query_func()
# NOTE: set_query_func()
func = treeview.get_query_func() func = treeview.get_query_func()
if func is not None: if func is not None:
model = treeview.get_model() manager = treeview.get_filter_man()
# TODO: this may lag? # TODO: this may lag?
model.clear() manager.clear_model()
self.set_callback(None, None) self.set_callback(None, None)
self.call_on_thread(func) self.call_on_thread(func)
@@ -2,7 +2,7 @@ import re
from dataclasses import dataclass from dataclasses import dataclass
from typing import Self from typing import Self
from dzgui.const.enum import FilterMode, HELP_MENU_ROWS from dzgui.const.enum import FilterMode
from dzgui.util import strings from dzgui.util import strings
import gi import gi
@@ -10,7 +10,6 @@ gi.require_version("Gtk", "3.0")
from gi.repository.Gtk import ListStore # noqa E402 from gi.repository.Gtk import ListStore # noqa E402
from gi.repository import GObject, GLib # noqa E402 from gi.repository import GObject, GLib # noqa E402
@dataclass
@dataclass(slots=True, frozen=True) @dataclass(slots=True, frozen=True)
class ServerColumns: class ServerColumns:
name: str name: str
@@ -27,105 +26,38 @@ class ServerColumns:
modded: bool modded: bool
@dataclass(slots=True, frozen=True) class FilteredModelManager:
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:
""" """
Manages access to cached ListStore resources and 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: def __init__(self) -> None:
self.filter_cache: tuple self.filter_cache: tuple
self.ping_cache: dict[str, int] = {} self.ping_cache: dict[str, int] = {}
self.map_store = ListStore(str) self.model = self.new_model_from_class(ServerColumns)
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.control_model = None self.control_model = None
self.filtered = None self.filtered = None
self.success = True self.success = True
#def __new__(cls) -> Self: def append_row(self, row: list) -> None:
# if not hasattr(cls, "instance"): self.model.append(row)
# cls.instance = super(ModelManager, cls).__new__(cls)
# return cls.instance
def get_recent_store(self) -> ListStore: def clear_model(self) -> None:
return self.recent_store self.model.clear()
def get_lan_store(self) -> ListStore: def get_model(self) -> ListStore:
return self.lan_store return self.model
def get_saved_store(self) -> ListStore:
return self.saved_store
def get_server_store(self) -> ListStore:
return self.server_store
def new_model_from_class(self, cls: type) -> ListStore: def new_model_from_class(self, cls: type) -> ListStore:
store = ListStore(*[ftype for field, ftype in cls.__annotations__.items()]) store = ListStore(*[ftype for field, ftype in cls.__annotations__.items()])
return store 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: def filter(self, mode: FilterMode, *args, **kwargs) -> None:
""" """
Native Gtk.TreeView.refilter() method was not performant enough Native Gtk.TreeView.refilter() method was not performant enough
@@ -290,10 +222,6 @@ class ModelManager:
) -> None: ) -> None:
self.filter_cache[filters] = (model, rows) 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: def resync_model(self, addr: str, qport: int) -> None:
""" """
Handle in-situ updates to model during Handle in-situ updates to model during
@@ -344,10 +272,3 @@ class ModelManager:
self.ping_cache = {} self.ping_cache = {}
if full: if full:
self.control_model = None 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)
+9 -25
View File
@@ -24,38 +24,22 @@ class ServerNotebook(Gtk.ScrolledWindow):
self.controller.register_widget("servers", self) self.controller.register_widget("servers", self)
self.notebook = Gtk.Notebook(show_tabs=True) self.notebook = Gtk.Notebook(show_tabs=True)
self.browser = ServerTreeView(controller, ServerTab.BROWSER) self.browser = ServerTreeView(controller, ServerTab.BROWSER, ContextMenuGroup.SERVER_BROWSER)
self.saved = ServerTreeView(controller, ServerTab.SAVED) self.saved = ServerTreeView(controller, ServerTab.SAVED, ContextMenuGroup.SAVED)
self.recent = ServerTreeView(controller, ServerTab.RECENT) self.recent = ServerTreeView(controller, ServerTab.RECENT, ContextMenuGroup.RECENT)
self.lan = ServerTreeView(controller, ServerTab.LAN) self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN)
self.browser.set_query_func(self.query_test2) self.browser.set_query_func(self.query_test2)
self.lan.set_query_func(self.query_test) 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 = [ tabs = [
(self.browser, server_labels.browser, self.controller.get_server_store), (self.browser, server_labels.browser),
(self.saved, server_labels.saved, self.controller.get_saved_store), (self.saved, server_labels.saved),
(self.recent, server_labels.recent, self.controller.get_recent_store), (self.recent, server_labels.recent),
(self.lan, server_labels.lan, self.controller.get_lan_store), (self.lan, server_labels.lan),
] ]
for tree, label, func in tabs: for tree, label 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)
scrolled = Gtk.ScrolledWindow() scrolled = Gtk.ScrolledWindow()
scrolled.add(tree) scrolled.add(tree)
self.notebook.append_page(scrolled, Gtk.Label(label=label)) self.notebook.append_page(scrolled, Gtk.Label(label=label))
+4 -5
View File
@@ -88,14 +88,13 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
) -> None: ) -> None:
if is_navkey(event.keyval): if is_navkey(event.keyval):
# FIXME: will fail on mod list # TODO: if model is None
tv = self.controller.get_active_treeview() if len(self.get_model()) < 1:
if len(tv.get_model()) < 1:
return return
if self.sel_blocked is False: if self.sel_blocked is False:
self.controller.suppress_signal( self.controller.suppress_signal(
tv, self,
tv.selected_row, self.selected_row,
"_on_tree_selection_changed", "_on_tree_selection_changed",
True, True,
) )
+12 -1
View File
@@ -15,6 +15,7 @@ from dzgui.const.enum import (
from dzgui.const.constants import UDP_PORT from dzgui.const.constants import UDP_PORT
from dzgui.const.enum import ServerTab from dzgui.const.enum import ServerTab
from dzgui.api.servers import Record from dzgui.api.servers import Record
from dzgui.model.filtered_model import FilteredModelManager
from dzgui.util.dist import CalcDist from dzgui.util.dist import CalcDist
from dzgui.util.keys import is_navkey from dzgui.util.keys import is_navkey
from dzgui.util import strings from dzgui.util import strings
@@ -40,7 +41,7 @@ class ServerTreeView(TreeView):
__gsignals__ = { __gsignals__ = {
"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ()) "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) super().__init__(controller)
QUEUE_CHECK_DELAY = 200 QUEUE_CHECK_DELAY = 200
@@ -49,10 +50,15 @@ class ServerTreeView(TreeView):
self.loaded = False self.loaded = False
self.query_func: Callable = None 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 = Gtk.Menu()
self.menu.connect("key-press-event", self._on_key) self.menu.connect("key-press-event", self._on_key)
self.controller = controller self.controller = controller
self.set_context_menu(menu)
self.set_fixed_height_mode(True) self.set_fixed_height_mode(True)
self.set_headers_visible(True) self.set_headers_visible(True)
@@ -113,6 +119,9 @@ class ServerTreeView(TreeView):
GLib.timeout_add(QUEUE_CHECK_DELAY, self._check_result_queue) 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: def shrink_to_fit(self) -> None:
cols = self.get_columns() cols = self.get_columns()
# TODO: run on only one treeview and propagate results # TODO: run on only one treeview and propagate results
@@ -203,6 +212,8 @@ class ServerTreeView(TreeView):
record = self.get_record() record = self.get_record()
if record is None: if record is None:
return return
# TODO:
self.controller.mediator.statusbar.spinner.start()
ip = record.ip ip = record.ip
self.current_proc = CalcDist(record.ip, self.queue, self.controller) self.current_proc = CalcDist(record.ip, self.queue, self.controller)
self.current_proc.start() self.current_proc.start()