feat: add extend() method to ListStore

This commit is contained in:
aclist 2026-02-23 06:22:32 +09:00
parent 3859188ade
commit d6fd7b2494
6 changed files with 94 additions and 42 deletions

View File

@ -315,10 +315,10 @@ class Controller(GObject.GObject):
path = self.query_config(Preferences.DEFAULT)
mods = get_delimited_mods(Path(path))
# NOTE: cell renderer highlight toggle
for mod in mods:
# NOTE: show highlight color bool
mod.append(False)
model.append(mod)
model.extend(mods)
self.cleanup_func = StoredFunc(self.load_mods_cleanup, model)

View File

@ -1,11 +1,10 @@
import datetime
import re
from dataclasses import dataclass
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
@ -19,22 +18,6 @@ if TYPE_CHECKING:
from dzgui.controllers.emitter import Emitter
@dataclass(slots=True, frozen=True)
class ServerColumns:
name: str
_map: str
perspective: str
gametime: str
players: int
_max: int
queue: int
ip: str
qport: int
ping: int
provider: str
modded: bool
class FilteredModelManager:
"""
Manages access to cached ListStore resources and
@ -42,7 +25,9 @@ class FilteredModelManager:
which share the same column structure.
A FilteredModelManager is attached to each ServerTreeView.
Filter methods are not thread-safe in themselves.
Raw data is cached before being packed into a ListStore, see get_control()
Filtration creates a proxy of the TreeView's model, see get_proxy_model()
"""
def __init__(self, controller: "Controller") -> None:
@ -117,21 +102,17 @@ class FilteredModelManager:
rows = self.filter_toggle_on(filters, *args)
# TODO: unimplemented
# just write pings into control model instead
# if mode is not FilterMode.INITIAL:
# for row in rows:
# if row[7] in self.ping_cache:
# row[9] = self.ping_cache[row[7]]
# NOTE: this ListStore manipulation must remain local to the thread
clone = self.new_model_from_class(ServerColumns)
n_cols = clone.get_n_columns()
clone = ModelFactory().make_server_store()
if len(rows) > 0:
rows = self.sort_rows(rows)
for i, row in enumerate(rows):
# TODO: consider overriding append() method of Gtk.ListStore
# check Gtk source code
clone.insert_with_values(i, tuple(range(0, n_cols)), row)
# clone.append(row)
clone.extend(rows)
self.set_cache(filters, clone, rows)
self.set_proxy_model(clone)

View File

@ -1,9 +1,9 @@
from dataclasses import dataclass
from typing import Any
from dzgui.const.enum import HELP_MENU_ROWS
from dzgui.util.redact import redact_log
from dzgui.util.strings import delimiter
from dzgui.views.dialogs.generic import ExceptionDialog
import gi
@ -11,6 +11,27 @@ gi.require_version("Gtk", "3.0")
from gi.repository.Gtk import ListStore # noqa E402
from gi.repository import GObject # noqa E402
GTYPE_TO_PYTHON = {
GObject.type_from_name(GObject.type_name(ptype)): ptype
for ptype in (int, float, str, bool, object)
}
@dataclass(slots=True, frozen=True)
class ServerCols:
name: str
_map: str
perspective: str
gametime: str
players: int
_max: int
queue: int
ip: str
qport: int
ping: int
provider: str
modded: bool
@dataclass(slots=True, frozen=True)
class ModCols:
@ -38,7 +59,7 @@ class ServerModCols:
@dataclass(slots=True, frozen=True)
class MenuCols:
name: str
name: GObject.TYPE_STRING # str
hidden: GObject.TYPE_PYOBJECT
@ -46,10 +67,52 @@ class FastInsertListStore(ListStore):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
def _is_same_length(self, lists: list[list[Any]]) -> bool:
first_len = len(lists[0])
if not all(len(sublist) == first_len for sublist in lists):
return False
return True
def _is_type_homogeneous(self, lists: list[list[Any]]) -> bool:
transposed = zip(*lists)
for column in transposed:
first_type = type(column[0])
if not all(type(item) is first_type for item in column):
return False
return True
def extend(self, rows: list[list[Any]]) -> None:
"""
Compared to calling append() directly, introduces negligible overhead,
but guarantees type and length equivalence prior to insertion
"""
n_cols = self.get_n_columns()
expected_types = [
GTYPE_TO_PYTHON[self.get_column_type(i)] for i in range(n_cols)
]
if not self._is_same_length(rows):
raise ValueError("Sublists are not of uniform length")
if not self._is_type_homogeneous(rows):
raise TypeError("Sublists are not type homogeneous")
if not all(isinstance(a, b) for a, b in zip(rows[0], expected_types)):
raise TypeError("Sublist types are not same as ListStore")
if len(rows[0]) != n_cols:
raise ValueError("Sublist column length is not same as ListStore")
for row in rows:
self.append(row)
def append(self, row) -> None:
"""
Optimized for speed, but makes no assurances about row homogeneity
and may segfault if types and length are not identical to ListStore.
For this reason, it is recommended to use the extend() method to insert
an entire list of lists
"""
total = len(row)
i = len(self)
tree_iter = self.insert_with_values(i, tuple(range(0, total)), row)
tree_iter = self.insert_with_values(i, tuple(range(total)), row)
return tree_iter
@ -62,7 +125,7 @@ class ModelFactory:
with open(path, "r") as f:
lines = [line.split(delimiter) for line in f.read().splitlines()]
for record in lines:
# NOTE: strip PII and API keys
# NOTE: strips PII and API keys
clean = redact_log(record)
store.append(clean)
return store
@ -73,21 +136,28 @@ class ModelFactory:
)
return store
def make_map_store(self) -> ListStore:
def make_map_store(self) -> FastInsertListStore:
return ListStore(str)
def make_help_store(self) -> ListStore:
def make_help_store(self) -> FastInsertListStore:
store = self.new_model_from_class(MenuCols)
for row in HELP_MENU_ROWS:
label = row.dict["label"]
store.append([label, row])
rows = [[row.dict["label"], row] for row in HELP_MENU_ROWS]
# for row in rows:HELP_MENU_ROWS:
# print(type(row) is object)
store.extend(rows)
# for row in HELP_MENU_ROWS:
# label = row.dict["label"]
# store.append([label, row])
return store
def make_mod_store(self) -> ListStore:
def make_mod_store(self) -> FastInsertListStore:
return self.new_model_from_class(ModCols)
def make_log_store(self) -> ListStore:
def make_log_store(self) -> FastInsertListStore:
return self.new_model_from_class(LogCols)
def make_server_mod_store(self) -> ListStore:
def make_server_mod_store(self) -> FastInsertListStore:
return self.new_model_from_class(ServerModCols)
def make_server_store(self) -> FastInsertListStore:
return self.new_model_from_class(ServerCols)

View File

@ -1,6 +1,7 @@
import os
import locale
def number(num: int | float) -> str:
spec = ""
if type(num) is int:

View File

@ -36,7 +36,6 @@ class ServerNotebook(Gtk.ScrolledWindow):
controller, ServerTab.RECENT, ContextMenuGroup.RECENT
)
self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN)
self.lan.set_headers_visible(False)
tabs = [
(self.browser, server_labels.browser),

View File

@ -53,7 +53,8 @@ class ServerTreeView(ContextMixin, TreeView):
self.map_man = MapManager()
self.set_fixed_height_mode(True)
self.set_headers_visible(True)
# NOTE: headers become visible on model load
self.set_headers_visible(False)
self.queue_id: int
self.handler_id: int