mirror of
https://github.com/aclist/dztui.git
synced 2026-10-30 14:42:54 +01:00
feat: add extend() method to ListStore
This commit is contained in:
1 parent
3859188ade
commit
d6fd7b2494
6 files changed
+94
-42
No files matched your search
@@ -315,10 +315,10 @@ class Controller(GObject.GObject):
|
|||||||
path = self.query_config(Preferences.DEFAULT)
|
path = self.query_config(Preferences.DEFAULT)
|
||||||
mods = get_delimited_mods(Path(path))
|
mods = get_delimited_mods(Path(path))
|
||||||
|
|
||||||
|
# NOTE: cell renderer highlight toggle
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
# NOTE: show highlight color bool
|
|
||||||
mod.append(False)
|
mod.append(False)
|
||||||
model.append(mod)
|
model.extend(mods)
|
||||||
|
|
||||||
self.cleanup_func = StoredFunc(self.load_mods_cleanup, model)
|
self.cleanup_func = StoredFunc(self.load_mods_cleanup, model)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
from warnings import deprecated
|
|
||||||
|
|
||||||
from dzgui.const.enum import FilterMode
|
from dzgui.const.enum import FilterMode
|
||||||
|
from dzgui.model.model_factory import ModelFactory
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -19,22 +18,6 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.controllers.emitter import Emitter
|
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:
|
class FilteredModelManager:
|
||||||
"""
|
"""
|
||||||
Manages access to cached ListStore resources and
|
Manages access to cached ListStore resources and
|
||||||
@@ -42,7 +25,9 @@ class FilteredModelManager:
|
|||||||
which share the same column structure.
|
which share the same column structure.
|
||||||
|
|
||||||
A FilteredModelManager is attached to each ServerTreeView.
|
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:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
@@ -117,21 +102,17 @@ class FilteredModelManager:
|
|||||||
rows = self.filter_toggle_on(filters, *args)
|
rows = self.filter_toggle_on(filters, *args)
|
||||||
|
|
||||||
# TODO: unimplemented
|
# TODO: unimplemented
|
||||||
|
# just write pings into control model instead
|
||||||
# if mode is not FilterMode.INITIAL:
|
# if mode is not FilterMode.INITIAL:
|
||||||
# for row in rows:
|
# for row in rows:
|
||||||
# if row[7] in self.ping_cache:
|
# if row[7] in self.ping_cache:
|
||||||
# row[9] = self.ping_cache[row[7]]
|
# row[9] = self.ping_cache[row[7]]
|
||||||
|
|
||||||
# NOTE: this ListStore manipulation must remain local to the thread
|
# NOTE: this ListStore manipulation must remain local to the thread
|
||||||
clone = self.new_model_from_class(ServerColumns)
|
clone = ModelFactory().make_server_store()
|
||||||
n_cols = clone.get_n_columns()
|
|
||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
rows = self.sort_rows(rows)
|
rows = self.sort_rows(rows)
|
||||||
for i, row in enumerate(rows):
|
clone.extend(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)
|
|
||||||
|
|
||||||
self.set_cache(filters, clone, rows)
|
self.set_cache(filters, clone, rows)
|
||||||
self.set_proxy_model(clone)
|
self.set_proxy_model(clone)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from dzgui.const.enum import HELP_MENU_ROWS
|
from dzgui.const.enum import HELP_MENU_ROWS
|
||||||
from dzgui.util.redact import redact_log
|
from dzgui.util.redact import redact_log
|
||||||
from dzgui.util.strings import delimiter
|
from dzgui.util.strings import delimiter
|
||||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
@@ -11,6 +11,27 @@ 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 # 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)
|
@dataclass(slots=True, frozen=True)
|
||||||
class ModCols:
|
class ModCols:
|
||||||
@@ -38,7 +59,7 @@ class ServerModCols:
|
|||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
class MenuCols:
|
class MenuCols:
|
||||||
name: str
|
name: GObject.TYPE_STRING # str
|
||||||
hidden: GObject.TYPE_PYOBJECT
|
hidden: GObject.TYPE_PYOBJECT
|
||||||
|
|
||||||
|
|
||||||
@@ -46,10 +67,52 @@ class FastInsertListStore(ListStore):
|
|||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
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:
|
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)
|
total = len(row)
|
||||||
i = len(self)
|
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
|
return tree_iter
|
||||||
|
|
||||||
|
|
||||||
@@ -62,7 +125,7 @@ class ModelFactory:
|
|||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
lines = [line.split(delimiter) for line in f.read().splitlines()]
|
lines = [line.split(delimiter) for line in f.read().splitlines()]
|
||||||
for record in lines:
|
for record in lines:
|
||||||
# NOTE: strip PII and API keys
|
# NOTE: strips PII and API keys
|
||||||
clean = redact_log(record)
|
clean = redact_log(record)
|
||||||
store.append(clean)
|
store.append(clean)
|
||||||
return store
|
return store
|
||||||
@@ -73,21 +136,28 @@ class ModelFactory:
|
|||||||
)
|
)
|
||||||
return store
|
return store
|
||||||
|
|
||||||
def make_map_store(self) -> ListStore:
|
def make_map_store(self) -> FastInsertListStore:
|
||||||
return ListStore(str)
|
return ListStore(str)
|
||||||
|
|
||||||
def make_help_store(self) -> ListStore:
|
def make_help_store(self) -> FastInsertListStore:
|
||||||
store = self.new_model_from_class(MenuCols)
|
store = self.new_model_from_class(MenuCols)
|
||||||
for row in HELP_MENU_ROWS:
|
rows = [[row.dict["label"], row] for row in HELP_MENU_ROWS]
|
||||||
label = row.dict["label"]
|
# for row in rows:HELP_MENU_ROWS:
|
||||||
store.append([label, row])
|
# print(type(row) is object)
|
||||||
|
store.extend(rows)
|
||||||
|
# for row in HELP_MENU_ROWS:
|
||||||
|
# label = row.dict["label"]
|
||||||
|
# store.append([label, row])
|
||||||
return store
|
return store
|
||||||
|
|
||||||
def make_mod_store(self) -> ListStore:
|
def make_mod_store(self) -> FastInsertListStore:
|
||||||
return self.new_model_from_class(ModCols)
|
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)
|
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)
|
return self.new_model_from_class(ServerModCols)
|
||||||
|
|
||||||
|
def make_server_store(self) -> FastInsertListStore:
|
||||||
|
return self.new_model_from_class(ServerCols)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import locale
|
import locale
|
||||||
|
|
||||||
|
|
||||||
def number(num: int | float) -> str:
|
def number(num: int | float) -> str:
|
||||||
spec = ""
|
spec = ""
|
||||||
if type(num) is int:
|
if type(num) is int:
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
|||||||
controller, ServerTab.RECENT, ContextMenuGroup.RECENT
|
controller, ServerTab.RECENT, ContextMenuGroup.RECENT
|
||||||
)
|
)
|
||||||
self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN)
|
self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN)
|
||||||
self.lan.set_headers_visible(False)
|
|
||||||
|
|
||||||
tabs = [
|
tabs = [
|
||||||
(self.browser, server_labels.browser),
|
(self.browser, server_labels.browser),
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ class ServerTreeView(ContextMixin, TreeView):
|
|||||||
self.map_man = MapManager()
|
self.map_man = MapManager()
|
||||||
|
|
||||||
self.set_fixed_height_mode(True)
|
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.queue_id: int
|
||||||
self.handler_id: int
|
self.handler_id: int
|
||||||
|
|||||||
Reference in new issue
Block a user