Compare commits

..

5 Commits

Author SHA1 Message Date
aclist
ab439144ea fix: use StoredFunc
Some checks are pending
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
2026-06-10 15:21:04 +09:00
aclist
db14c5252c change: clearer startup error 2026-06-10 15:18:42 +09:00
aclist
7d8b3b402f feat: use emitter 2026-06-10 14:30:50 +09:00
aclist
4506a0cfda chore: revert IconButton alignment changes 2026-06-10 08:27:24 +09:00
aclist
978edc5f5f chore: add format strings 2026-06-10 08:26:50 +09:00
8 changed files with 71 additions and 26 deletions

View File

@ -1,13 +1,13 @@
from typing import TYPE_CHECKING
import gi
gi.require_version("Gtk", "3.0")
from gi.repository.Gtk import ListStore # noqa E402
from gi.repository import Gdk, GObject # noqa E402
if TYPE_CHECKING:
from dzgui.const.enum import NotebookPage, ServerTab
from dzgui.model.model_factory import FastInsertListStore
# TODO: if servers fail to load, may leave dangling widgets waiting for a signal
@ -154,3 +154,7 @@ class Emitter(GObject.GObject):
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
def lan_page_initialized(self) -> None:
pass
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object, str))
def custom_mods_loaded(self, store: "FastInsertListStore", folder: str) -> None:
pass

View File

@ -6,6 +6,7 @@ from pathlib import Path
from dzgui.config.query import lookup
from dzgui.const.constants import APPID_DAYZ, APP_NAME, LIBRARYFOLDERS_PATH
from dzgui.const.enum import Preferences
from dzgui.strings.errors import dayz_not_found
from dzgui.views.dialogs.early_alert import EarlyAlertDialog
import dzgui.api.pefile as PeFile
@ -19,5 +20,5 @@ def is_dayz_installed(config: Path) -> None:
PeFile.get_app_path(Path(path) / LIBRARYFOLDERS_PATH, APPID_DAYZ)
except Exception as e:
logger.critical(e)
EarlyAlertDialog(str(e))
EarlyAlertDialog(dayz_not_found)
sys.exit(1)

View File

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
from dzgui.api.mods import get_custom_mods
from dzgui.api.steam import launch_offline
from dzgui.const.enum import Preferences
from dzgui.managers.threading import call_on_thread, ThreadingManager
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
from dzgui.model.model_factory import FastInsertListStore, ModelFactory
from dzgui.strings import dialogs
from dzgui.util.symlink import clone_symlinks
@ -21,6 +21,7 @@ class OfflineManager:
super().__init__()
self.controller = controller
self.emitter = controller.get_emitter()
self.thread_man = ThreadingManager(controller)
self.appid: int
@ -30,12 +31,20 @@ class OfflineManager:
# TODO: strings
@call_on_thread("parsing")
def parse_custom_mods(self, folder: str) -> "FastInsertListStore":
def parse_custom_mods(self, local_mods: list[str], folder: str) -> "FastInsertListStore":
mods = get_custom_mods(Path(folder))
store = ModelFactory().make_mod_store()
store.extend(mods)
# TODO: manipulate tree columns to only show name and size
return store
has_duplicates = False
for row in store:
if row[1] in local_mods:
row[-1]=True
has_duplicates = True
func = StoredFunc(self.emitter.emit, "custom_mods_loaded", store, folder, has_duplicates)
self.thread_man.set_cleanup_func(func)
# TODO: return store from cleanup function
def setup(
self,

View File

@ -3,3 +3,4 @@ api_validation_error = (
)
api_popover = "API key validation failed."
dayz_not_found = "Could not find DayZ and/or Steam: are they installed?"

1
dzgui/strings/generic.py Normal file
View File

@ -0,0 +1 @@
selectable_tree = "Ctrl-click to select multiple; Shift-click to select a range."

View File

@ -1,6 +1,7 @@
import re
from dzgui.util.localize import number
from dzgui.strings.generic import selectable_tree
from dzgui.util.strings import no_mods, no_servers, workshop
import gi
@ -46,8 +47,7 @@ def format_mods(size: int, mods: int) -> str:
return no_mods
l_size = number(size)
plural = pluralize("mods", mods)
# TODO: strings
suffix = "Ctrl-click to select multiple; Shift-click to select a range."
suffix = selectable_tree
return f"Found {mods:n} {plural} taking up {l_size} MiB. {suffix}"

View File

@ -48,10 +48,8 @@ class LargeIcon(Gtk.Image):
class IconButton(Gtk.Button):
def __init__(
self, icon: str, margin: int = 0, halign: Gtk.Align = Gtk.Align.START
) -> None:
super().__init__(halign=halign)
def __init__(self, icon: str, margin: int = 0) -> None:
super().__init__()
self.icon = Icon(icon, l_margin=margin)
self.set_image(self.icon)
self.set_image_position(Gtk.PositionType.RIGHT)
@ -59,10 +57,8 @@ class IconButton(Gtk.Button):
class IconTextButton(IconButton):
def __init__(
self, icon: str, label: str, halign: Gtk.Align = Gtk.Align.START
) -> None:
super().__init__(icon, margin=5, halign=halign)
def __init__(self, icon: str, label: str) -> None:
super().__init__(icon, margin=5)
self.set_label(label)

View File

@ -12,13 +12,14 @@ from dzgui.const.constants import (
)
from dzgui.const.enum import NotebookPage, Preferences
from dzgui.managers.offline import OfflineManager
from dzgui.strings import offline
from dzgui.strings import generic, offline
from dzgui.views.components.buttons import IconTextButton
from dzgui.views.components.frame import HeadingFrame
from dzgui.views.components.scrollable import NoOverlayScrolledWindow
from dzgui.views.trees.tree_mods import OfflineModTreeView
import gi
gi.require_version("Gtk", "3.0")
@ -64,7 +65,9 @@ class FolderHBox(HBox):
# TODO: alternate class for left-aligned icons
self.button = IconTextButton(FOLDER, btn_label)
self.button.set_halign(Gtk.Align.START)
self.button.set_image_position(Gtk.PositionType.LEFT)
self.label = Gtk.Label()
self.extend([self.button, self.label])
@ -119,8 +122,7 @@ class ModFrame(HeadingFrame):
def _on_selection_changed(self, sel: Gtk.TreeSelection) -> None:
model, rows = sel.get_selected_rows()
if len(rows) == 0:
# TODO recycle (util.format)
status = "Ctrl-click to select multiple; Shift-click to select a range."
status = generic.selectable_tree
else:
status = f"Mods selected: {len(rows)}"
self.status.set_label(status)
@ -132,26 +134,45 @@ class ModFrame(HeadingFrame):
class CustomModFrame(ModFrame):
def __init__(self, controller: "Controller", heading: str) -> None:
def __init__(self, parent: Gtk.Widget, controller: "Controller", heading: str) -> None:
super().__init__(controller, heading)
self.parent = parent
self.controller = controller
self.emitter = controller.get_emitter()
# TODO: descriptive text here explaining how this area works
self.custom_hbox = FolderHBox(offline.custom_button)
self.custom_hbox.get_button().connect("clicked", self._on_custom_button_clicked)
self.pack(self.custom_hbox)
self.emitter.connect("custom_mods_loaded", self._on_custom_mods_loaded)
def _on_custom_mods_loaded(self, store: "FastInsertListStore", folder: str, has_duplicates: bool) -> None:
if len(store) == 0:
# TODO: pop error dialog area
# block button access
pass
else:
self.custom_hbox.set_label(folder)
self.tree.set_model(store)
self.tree_vbox.show()
if has_duplicates:
# TODO: pop relevant error
# duplicates should block button access
pass
def _on_custom_button_clicked(self, button: Gtk.Button) -> None:
# TODO: recycle for mission folder
# TODO: propagate results back to parent
folder = self.controller.set_custom_folder()
if folder is not None:
self.custom_hbox.set_label(str(folder))
self.parent.parse_mods()
def get_mods(self) -> list[str]:
rows = self.tree.get_selection().get_selected_rows()
dirs = [str(col[0]) for col in rows]
dirs = [str(col[1]) for col in rows]
return dirs
@ -252,6 +273,14 @@ class OfflineLoader(Gtk.Box):
self.add(self.scrollable)
self.add(self.button_box)
def parse_mods(self) -> None:
# TODO:
local_mods = self.local_frame.get_mods()
folder = self.controller.set_custom_folder()
if folder is not None:
# TODO:
self.offline_man.parse_custom_mods(local_mods, folder)
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> None:
if event.keyval == Gdk.KEY_Escape:
self.back.emit("clicked")
@ -259,6 +288,7 @@ class OfflineLoader(Gtk.Box):
def populate(self, store: "FastInsertListStore") -> None:
self.local_frame.set_model(store)
# TODO: toggle if empty model, show warning label
# NOTE: there may be no local mods
# TODO: suppress trees if there are no mods
self.custom_frame.collapse_tree()
@ -266,6 +296,9 @@ class OfflineLoader(Gtk.Box):
self.controller.open_page(NotebookPage.MODS)
def _on_ok_clicked(self, button: Gtk.Button) -> None:
# TODO; block button access if no mods are selected
# TODO: consider hooking up to emitter and changing button state whenever mods are selected
# appid = self.radio_frame.get_appid()
# mission = self.mission_frame.get_mission()
# local_mods = self.local_frame.get_mods()