feat: initialize ServerTreeViews with atomic FilterModelManagers

This commit is contained in:
aclist 2026-01-19 14:55:32 +09:00
parent fdd6cefe48
commit 6f29b1318a
5 changed files with 67 additions and 164 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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,
)

View File

@ -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()