Compare commits

..

No commits in common. "ab439144ea0aea2c37ffdf3e34c245ae30a4906a" and "24668870148df81e95a0a073a59382cc7ea8a2c1" have entirely different histories.

8 changed files with 26 additions and 71 deletions

View File

@ -1,13 +1,13 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import gi import gi
gi.require_version("Gtk", "3.0") 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 Gdk, GObject # noqa E402 from gi.repository import Gdk, GObject # noqa E402
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.const.enum import NotebookPage, ServerTab 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 # TODO: if servers fail to load, may leave dangling widgets waiting for a signal
@ -154,7 +154,3 @@ class Emitter(GObject.GObject):
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
def lan_page_initialized(self) -> None: def lan_page_initialized(self) -> None:
pass 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,7 +6,6 @@ from pathlib import Path
from dzgui.config.query import lookup from dzgui.config.query import lookup
from dzgui.const.constants import APPID_DAYZ, APP_NAME, LIBRARYFOLDERS_PATH from dzgui.const.constants import APPID_DAYZ, APP_NAME, LIBRARYFOLDERS_PATH
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.strings.errors import dayz_not_found
from dzgui.views.dialogs.early_alert import EarlyAlertDialog from dzgui.views.dialogs.early_alert import EarlyAlertDialog
import dzgui.api.pefile as PeFile import dzgui.api.pefile as PeFile
@ -20,5 +19,5 @@ def is_dayz_installed(config: Path) -> None:
PeFile.get_app_path(Path(path) / LIBRARYFOLDERS_PATH, APPID_DAYZ) PeFile.get_app_path(Path(path) / LIBRARYFOLDERS_PATH, APPID_DAYZ)
except Exception as e: except Exception as e:
logger.critical(e) logger.critical(e)
EarlyAlertDialog(dayz_not_found) EarlyAlertDialog(str(e))
sys.exit(1) 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.mods import get_custom_mods
from dzgui.api.steam import launch_offline from dzgui.api.steam import launch_offline
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.managers.threading import call_on_thread, ThreadingManager
from dzgui.model.model_factory import FastInsertListStore, ModelFactory from dzgui.model.model_factory import FastInsertListStore, ModelFactory
from dzgui.strings import dialogs from dzgui.strings import dialogs
from dzgui.util.symlink import clone_symlinks from dzgui.util.symlink import clone_symlinks
@ -21,7 +21,6 @@ class OfflineManager:
super().__init__() super().__init__()
self.controller = controller self.controller = controller
self.emitter = controller.get_emitter()
self.thread_man = ThreadingManager(controller) self.thread_man = ThreadingManager(controller)
self.appid: int self.appid: int
@ -31,20 +30,12 @@ class OfflineManager:
# TODO: strings # TODO: strings
@call_on_thread("parsing") @call_on_thread("parsing")
def parse_custom_mods(self, local_mods: list[str], folder: str) -> "FastInsertListStore": def parse_custom_mods(self, folder: str) -> "FastInsertListStore":
mods = get_custom_mods(Path(folder)) mods = get_custom_mods(Path(folder))
store = ModelFactory().make_mod_store() store = ModelFactory().make_mod_store()
store.extend(mods) store.extend(mods)
# TODO: manipulate tree columns to only show name and size
has_duplicates = False return store
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( def setup(
self, self,

View File

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

View File

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

View File

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

View File

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

View File

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