feat: MaskedTree (WIP)

This commit is contained in:
aclist 2026-05-06 05:38:29 +09:00
parent b42aea1e02
commit a9863ff86b
5 changed files with 111 additions and 44 deletions

View File

@ -116,7 +116,8 @@ def _hash(uid: str) -> str:
def remove_stale_signatures(config: Path, versions: Path) -> None:
if versions.is_file() is False:
logger.warning("No mod signatures file found")
logger.warning("Creating new version signatures file")
versions.touch()
return
path = lookup(config, Preferences.DEFAULT)
steam_path = Path(path)

View File

@ -31,3 +31,7 @@
font-size: 1.2rem;
font-weight: 800;
}
.masked-tree {
background-color: transparent;
}

View File

@ -70,18 +70,19 @@ class ConnectionManager:
version_file = self.controller.get_prefs().paths.version
needs_update = get_needs_update(version_file, hashes)
# TODO: populate version file at boot if nonexistent (after symlinking)
# TODO: store mods that need update in class object for referencing later
# TODO: store remote destination to connect to
# missing mods should be the totality of all mods with no signature
# missing = get_missing_mods(local_mod_ids, remote_mod_ids)
# print(missing)
# TODO: when updating, create symlinks of everything
# contains id and signature
# TODO: when downloading mods, create symlinks if missing
# TODO: get missing mod sizes, warn if not enough space
info = res.source
try:
dayz_path = PeFile.get_pefile_path(steam_path, info.game_id)
# TODO: handle missing path; do not calculate size if appid is missing
total, used, free = shutil.disk_usage(dayz_path)
if len(needs_update) > 0:
# TODO: generic mib function
@ -97,8 +98,9 @@ class ConnectionManager:
self.thread_man.set_cleanup_func(failure_func, destroy_first=True)
# TODO: number separator func
# TODO: pack a final PreReq struct with pre-process values
# TODO: pack a final PreReq struct with pre-processed values
# TODO: connection assistant only receives user-facing warnings and list of mods
func = StoredFunc(self.controller.open_connection_assistant, res, remote_mods)
self.thread_man.set_cleanup_func(func, destroy_first=True)
@ -154,3 +156,11 @@ class ConnectionManager:
def _server_timeout(self) -> None:
dialog = ExceptionDialog(self.controller, server_timeout)
dialog.run()
def update_mods(self) -> None:
# self.needs_update
pass
def connect(self) -> None:
# steam api, concat mods
pass

View File

@ -20,7 +20,7 @@ from gi.repository import Gtk, GLib # noqa E402
logger = logging.getLogger(APP_NAME)
def call_on_thread(dialog_str: str) -> Callable:
def call_on_thread(dialog_str: str, show_dialog: bool = True) -> Callable:
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> None:
@ -32,7 +32,7 @@ def call_on_thread(dialog_str: str) -> Callable:
raise TypeError(
"Attribute 'thread_man' must be of type 'ThreadingManager'"
)
self.thread_man.call_on_thread(dialog_str, stored)
self.thread_man.call_on_thread(dialog_str, stored, show_dialog)
return wrapper
@ -56,13 +56,16 @@ class ThreadingManager:
self.cleanup_func: StoredFunc | None = None
self.destroy_first = False
def call_on_thread(self, dialog_str: str, func: StoredFunc) -> None:
def call_on_thread(
self, dialog_str: str, func: StoredFunc, show_dialog: bool = True
) -> None:
def callback() -> None:
func.call()
GLib.idle_add(self._destroy_on_idle)
self.wait_dialog = WaitDialog(self.controller, dialog_str, jobs=self.jobs)
self.wait_dialog.show_all()
if show_dialog:
self.wait_dialog = WaitDialog(self.controller, dialog_str, jobs=self.jobs)
self.wait_dialog.show_all()
thread = threading.Thread(target=callback)
thread.start()
@ -88,16 +91,20 @@ class ThreadingManager:
def get_cleanup_func(self) -> StoredFunc | None:
return self.cleanup_func
def destroy_dialog(self) -> None:
if hasattr(self, "wait_dialog"):
self.wait_dialog.destroy()
def _destroy_on_idle(self) -> Literal[False]:
if self.destroy_first:
self.wait_dialog.destroy()
self.destroy_dialog()
func = self.get_cleanup_func()
if func is not None:
func.call()
self.set_cleanup_func(None)
if not self.destroy_first:
self.wait_dialog.destroy()
self.destroy_dialog()
return False

View File

@ -1,6 +1,6 @@
from typing import Any, TYPE_CHECKING
from dataclasses import dataclass
from typing import Self, TYPE_CHECKING
from dzgui.api import pefile as PeFile
from dzgui.const.constants import (
APPID_DAYZ,
APPID_DAYZ_EXP,
@ -13,7 +13,6 @@ from dayzquery import DayzMod
from dzgui.util.css import add_class
from dzgui.strings import preconnect
from dzgui.views.components.frame import HeadingFrame
from dzgui.views.components.labels import IconLabel
from dzgui.views.trees.tree_server_mods import ServerModTreeView
import gi
@ -27,14 +26,48 @@ if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
class WarningLabel(IconLabel):
def __init__(self, text: str) -> None:
super().__init__(text, WARNING)
@dataclass
class Warnings:
passworded: bool
dayz_running: bool
no_steam: bool
wrong_version: bool
class ErrorLabel(IconLabel):
def __init__(self, text: str) -> None:
super().__init__(text, ERROR)
@dataclass
class Errors:
no_space: bool
no_dayz_exp: bool
no_dayz: bool
class MaskedTree(Gtk.TreeView):
def __init__(self, icon: str) -> None:
super().__init__(headers_visible=False, can_focus=False)
self.icon = icon
self.store = Gtk.ListStore(str, str)
self.set_model(self.store)
self.get_selection().set_mode(Gtk.SelectionMode.NONE)
icon_renderer = Gtk.CellRendererPixbuf()
# NOTE: adjust vertical offset between columns
icon_renderer.set_property("yalign", 0.6)
icon_column = Gtk.TreeViewColumn("", icon_renderer)
icon_column.add_attribute(icon_renderer, "icon-name", 0)
icon_column.set_fixed_width(50)
text_renderer = Gtk.CellRendererText()
text_column = Gtk.TreeViewColumn("", text_renderer, text=1)
self.append_column(icon_column)
self.append_column(text_column)
add_class(self, "masked-tree")
def append(self, items: list[str]) -> None:
self.store.clear()
for item in items:
self.store.append([self.icon, item])
class PreConnectionAssistant(Gtk.ScrolledWindow):
@ -54,7 +87,6 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
self.controller.register_widget("preconnect", self)
# TODO: strings
# TODO: dynamic button text if no mods needed
self.back = Gtk.Button(label=preconnect.back, halign=Gtk.Align.START)
self.cancel = Gtk.Button(
@ -94,28 +126,12 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
self.tree_frame = HeadingFrame(self.tree_box, preconnect.mods)
self.warnings: list[WarningLabel] = []
# TODO: populate with strings and icons
# set visibility if warnings > 1
# warning category enums with matching strings
"""
blocking warning types:
- build mismatch
- version mismatch
- not enough drive space
passing warning types:
- dayz is running
- steam is not running
- server has password
"""
# TODO: abstract into components
warning_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
warning_box.add(WarningLabel("Warning 1"))
self.warning_frame = HeadingFrame(warning_box, preconnect.warnings)
self.warning_tree = MaskedTree(WARNING)
self.warning_frame = HeadingFrame(self.warning_tree, preconnect.warnings)
error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
error_box.add(ErrorLabel("Error 1"))
self.error_frame = HeadingFrame(error_box, preconnect.errors)
self.error_tree = MaskedTree(ERROR)
self.error_frame = HeadingFrame(self.error_tree, preconnect.errors)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.box.add(self.title)
@ -126,6 +142,17 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
self.add(self.box)
# self.connect("key-press-event", self._on_keypress)
self.connect("map", self._on_map)
def _on_map(self, widget: Self) -> None:
self.tree_frame.set_visible(True)
self.mod_count.set_visible(True)
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> None:
if event.keyval == Gdk.KEY_Escape:
self.back.emit("clicked")
def _on_ok_clicked(self, button: Gtk.Button) -> None:
# TODO: update mod store in place with spinner/toast
# TODO: cancel mod downloads
@ -139,6 +166,8 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
self.tree.populate(mods)
total = len(mods)
self._set_warnings()
name = res.source.server_name
self.title.set_text(name)
if total < 1:
@ -148,10 +177,26 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
else:
self.tree.set_visible(True)
self.mod_count.set_visible(True)
# TODO: print no. of mods that need updating
prefix = preconnect.total_mods
self.mod_count.set_text(f"{prefix}{str(total)}")
# TODO: reset warning and error dialogs
"""
blocking warning types:
- build mismatch
- version mismatch
- not enough drive space
passing warning types:
- dayz is running
- steam is not running
- server has password
"""
# TODO: if errors > 1, disable buttons
def _set_warnings(self) -> None:
self.warning_tree.append(["Password protected", "Some other error", "Error 3"])
self.error_tree.append(["Password protected", "Some other error", "Error 3"])
pass
def download_mods(self) -> None:
pass