Compare commits

..
8 Commits
Author SHA1 Message Date
aclist 20dab38fff Merge pull request #351 from aclist/fix/enqueue-mods
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
fix: enqueue mods
2026-06-07 21:59:49 +09:00
aclist 1ddc4a5ff1 chore: bump version 2026-06-07 21:58:44 +09:00
aclist d605447fca chore: clear typehinting errors 2026-06-07 21:51:55 +09:00
aclist 94fe3f9dff fix: suppress search popup (#353) 2026-06-07 21:49:41 +09:00
aclist a966993999 fix: invoke steam client directly (#348) 2026-06-07 21:21:49 +09:00
aclist 8ee3b36816 fix: restore mod statusbar when returning from other pages 2026-06-07 21:20:56 +09:00
aclist 3af53775cd fix: grab focus on tree 2026-06-07 21:20:32 +09:00
aclist e2d6a462db fix: suppress headers if no model is loaded 2026-06-07 20:23:13 +09:00
22 changed files with 67 additions and 530 deletions

No files matched your search

-1
View File
@@ -40,7 +40,6 @@
- Preboot progress dialog
- Choose to jump into splash screen instead of server
- Collapsible connection panel
- Play offline (load mods directly)
## Changed
- Conform to PEP 440 versioning for beta versions
+3 -24
View File
@@ -73,14 +73,9 @@ def get_local_signatures(version_file: Path) -> dict[str, int]:
return hashes
def enqueue_mod(mod: str, appid: int) -> None:
args = [
"steam",
f"steam://url/CommunityFilePage/{mod}+workshop_download_item",
str(appid),
mod,
]
subprocess.Popen(["/usr/bin/env", "bash", *args])
def enqueue_mod(client: str, mod: str, appid: int) -> None:
client_args = concat_bash_args(client)
subprocess.Popen([*client_args, "+workshop_download_item", str(appid), mod])
def get_needs_update(
@@ -162,22 +157,6 @@ def load_to_menu(client: str, appid: int, name: str, mods: list[str]) -> int:
proc = subprocess.run([*client_args, *params])
return proc.returncode
def launch_offline(client: str, addr: str, appid: int, name: str, mods: list[str], mission: str) -> int:
"""Launch offline with specific mods/missions"""
concat = concat_mods(mods)
client_args = concat_bash_args(client)
params = [
"-applaunch",
str(appid),
"-nolauncher",
"-nosplash",
"-skipintro",
f"-name={name}",
f"-mod={concat}",
f"-mission={mission}"
]
proc = subprocess.run([*client_args, *params])
return proc.returncode
def find_user_id(path: Path) -> str | None:
resolved_path = path / "config" / "loginusers.vdf"
-1
View File
@@ -103,7 +103,6 @@ class Preferences(EnumWithAttrs):
class NotebookPage(EnumWithAttrs):
OFFLINE = {"crumbs": strings.crumbs.offline, "statusbar": True}
CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": True}
DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False}
HELP = {"crumbs": strings.crumbs._help, "statusbar": True}
+9 -35
View File
@@ -26,7 +26,7 @@ from dzgui.model.servers import ServerModelManager
from dzgui.util.diag import write_diagnostic
from dzgui.util.format import format_player_count
from dzgui.util.open_links import open_user_workshop, open_workshop_page
from dzgui.views.dialogs.filepicker import FilePicker, FolderPicker
from dzgui.views.dialogs.filepicker import FilePicker
from dzgui.views.dialogs.generic import ExceptionDialog
import gi
@@ -37,7 +37,6 @@ from gi.repository import Gtk, Gdk, GLib, GObject # noqa E402
logger = logging.getLogger(APP_NAME)
if TYPE_CHECKING:
from pathlib import Path
from dzgui.api.servers import Record
from dzgui.const.enum import ServerTab
from dzgui.managers.connection import Prerequisites
@@ -48,13 +47,12 @@ if TYPE_CHECKING:
from dzgui.views.components.filter_panel import FilterPanel
from dzgui.views.components.right_panel import RightPanel
from dzgui.views.components.statusbar import Statusbar
from dzgui.views.pages.offline import OfflineLoader
from dzgui.views.pages.options import Options
from dzgui.views.pages.preconnect import PreConnectionAssistant
from dzgui.views.pages.servers import ServerNotebook
from dzgui.views.trees.tree_log import LogTreeView
from dzgui.views.trees.tree_menu import MenuTreeView
from dzgui.views.trees.tree_mods import ModTreeView, OfflineModTreeView
from dzgui.views.trees.tree_mods import ModTreeView
from dzgui.views.trees.tree_servers import ServerTreeView
@@ -74,7 +72,6 @@ class AppNavigation:
filters: "FilterPanel"
preconnect: "PreConnectionAssistant"
options: "Options"
offline_loader: "OfflineLoader"
class Controller(GObject.GObject):
@@ -221,29 +218,19 @@ class Controller(GObject.GObject):
open_user_workshop(uid, client)
def load_mods(self) -> None:
self.mediator.modtreeview.load_mods()
self.mod_man = ModManager(self)
def uncolorize_mods(self) -> None:
mod_man = self.mediator.modtreeview.get_mod_man()
mod_man.uncolorize_mods()
self.mod_man.uncolorize_mods()
def highlight_stale(self) -> None:
mod_man = self.mediator.modtreeview.get_mod_man()
mod_man.highlight_stale()
self.mod_man.highlight_stale()
def toggle_mod_selection(self, state: bool) -> None:
mod_man = self.mediator.modtreeview.get_mod_man()
mod_man.toggle_mod_selection(state)
self.mod_man.toggle_mod_selection(state)
def delete_mods(
self, treeview: Union["ModTreeView", "OfflineModTreeView"] = None
) -> None:
if treeview is None:
view = self.mediator.modtreeview
else:
view = treeview
mod_man = view.get_mod_man()
mod_man.delete_mods()
def delete_mods(self) -> None:
self.mod_man.delete_mods()
def get_mod_store(self) -> Gtk.TreeModel | None:
return self.mediator.modtreeview.get_model()
@@ -270,8 +257,7 @@ class Controller(GObject.GObject):
dialog.run()
def select_colorized(self) -> None:
mod_man = self.mediator.modtreeview.get_mod_man()
mod_man.select_colorized()
self.mod_man.select_colorized()
def get_all_tree_filters(self) -> dict[str, dict[str, bool]]:
servers = self.get_servers()
@@ -302,12 +288,6 @@ class Controller(GObject.GObject):
dialog = ExceptionDialog(self, str(e))
dialog.run()
def set_custom_folder(self) -> Union["Path", None]:
picker = FolderPicker(self.mediator.window)
folder = picker.pick_folder()
picker.destroy()
return folder
def update_api_key(self, key: Preferences, text: str) -> None:
self.config_man.update_api_key(key, text)
@@ -553,9 +533,3 @@ class Controller(GObject.GObject):
self.clear_cancel_event()
return True
return False
def open_offline(self) -> None:
self.open_page(NotebookPage.OFFLINE)
mod_man = self.mediator.modtreeview.get_mod_man()
store = mod_man.store
self.mediator.offline_loader.populate(store)
+6 -4
View File
@@ -88,6 +88,7 @@ class ConnectionManager:
self.record: Record
self.workshop: Path
self.client: str
self.remote_mod_ids: list[str] = []
self.missing_mods: list[tuple[str, str, int, int]] = []
@@ -171,6 +172,7 @@ class ConnectionManager:
client = self.controller.query_config(Preferences.CLIENT)
running = is_steam_running(client)
steam_proc = SteamProcess(client_name, running)
self.client = client
game_mode = prefs.is_game_mode
@@ -263,11 +265,10 @@ class ConnectionManager:
def _connect_steam(self, menu_only: bool) -> None:
addr = f"{self.record.ip}:{self.record.gameport}"
playername = self.controller.query_config(Preferences.NAME)
client = self.controller.query_config(Preferences.CLIENT)
if menu_only:
rc = load_to_menu(client, self.appid, playername, self.remote_mod_ids)
rc = load_to_menu(self.client, self.appid, playername, self.remote_mod_ids)
else:
rc = connect(client, addr, self.appid, playername, self.remote_mod_ids)
rc = connect(self.client, addr, self.appid, playername, self.remote_mod_ids)
if rc != 0:
# TODO: log/pop the error
func = StoredFunc(self.controller.update_status)
@@ -298,7 +299,8 @@ class ConnectionManager:
for title, mod, stamp, size in self.missing_mods:
if self.controller.is_cancel_pending():
return
enqueue_mod(mod, self.appid)
enqueue_mod(self.client, mod, self.appid)
# NOTE: prevents rate limiting
time.sleep(3)
if raise_window is True:
+4 -10
View File
@@ -8,7 +8,7 @@ from dzgui.util.clip import copy_clipboard
from dzgui.util.open_links import open_workshop_page
from dzgui.views.dialogs.note import NoteDialog
from dzgui.views.trees.tree_mods import ModTreeView, OfflineModTreeView
from dzgui.views.trees.tree_mods import ModTreeView
from dzgui.views.trees.tree_log import LogTreeView
from dzgui.views.trees.tree_servers import ServerTreeView
from dzgui.views.trees.tree_server_mods import ServerModTreeView
@@ -27,13 +27,7 @@ if TYPE_CHECKING:
class ContextMenuManager:
def __init__(
self,
treeview: (
LogTreeView
| ModTreeView
| ServerTreeView
| ServerModTreeView
| OfflineModTreeView
),
treeview: LogTreeView | ModTreeView | ServerTreeView | ServerModTreeView,
controller: "Controller",
) -> None:
self.controller = controller
@@ -78,10 +72,10 @@ class ContextMenuManager:
case ContextMenu.SHOW_MODS:
self.controller.get_modlist(record)
if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
if isinstance(self.treeview, ModTreeView):
match action:
case ContextMenu.DELETE_MOD:
self.controller.delete_mods(self.treeview)
self.controller.delete_mods()
case ContextMenu.OPEN_WORKSHOP:
self.open_mod_page()
+9 -9
View File
@@ -2,7 +2,7 @@ import logging
import shutil
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any, TYPE_CHECKING
from dzgui.api.mods import (
get_delimited_mods,
@@ -40,20 +40,20 @@ class ModManager:
this manager is instantiated each time the Mods page is opened
"""
def __init__(self, tree: Gtk.TreeView, controller: "Controller") -> None:
self.treeview = tree
def __init__(self, controller: "Controller") -> None:
self.controller = controller
self.emitter = controller.get_emitter()
self.prefs = controller.get_prefs()
self.path = controller.query_config(Preferences.DEFAULT)
self.treeview = self.controller.get_modtreeview()
self.store: FastInsertListStore
self.thread_man = ThreadingManager(controller)
self._get_mods()
@call_on_thread(dialogs.fetching_mods)
def load_mods(self) -> None:
def _get_mods(self) -> None:
mods = get_delimited_mods(self.path)
if len(mods) < 1:
msg = self.format_mod_statusbar()
@@ -61,12 +61,12 @@ class ModManager:
self.thread_man.set_cleanup_func(func)
return
self.store = ModelFactory().make_mod_store()
self.store.extend(mods)
func = StoredFunc(self._on_mods_loaded)
func = StoredFunc(self._on_mods_loaded, mods)
self.thread_man.set_cleanup_func(func)
def _on_mods_loaded(self) -> None:
def _on_mods_loaded(self, mods: list[list[Any]]) -> None:
self.store = ModelFactory().make_mod_store()
self.store.extend(mods)
self.treeview.set_model(self.store)
msg = self.format_mod_statusbar()
total_mods = len(self.store)
-3
View File
@@ -165,6 +165,3 @@ class ModelFactory:
def make_server_store(self) -> FastInsertListStore:
return self.new_model_from_class(ServerCols)
def convert_model_to_list(self, model: "FastInsertListStore") -> list:
return [[el for el in row] for row in model]
-11
View File
@@ -1,11 +0,0 @@
heading = "Play offline"
mission_button = "Select mission folder"
mission_frame = "Mission"
version = "DayZ Version"
custom_frame = "Custom mods"
custom_button = "Set custom mod folder"
local_frame = "Installed mods"
+1 -3
View File
@@ -64,7 +64,7 @@ connect = "Connect"
# Columns
server_mod_cols = ["Mod", "ID", "Installed"]
mod_cols = ["Mod", "Symlink", "ID", "Size (MiB)", "Color"]
mod_cols = ["Mod", "Symlink", "Dir", "Size (MiB)", "Color"]
log_cols = ["Timestamp", "Flag", "Traceback", "Message"]
browser_cols = [
"Name",
@@ -273,7 +273,6 @@ class Crumbs:
thanks: str
developers: str
default: str
offline: str
@dataclass(slots=True, frozen=True)
@@ -463,7 +462,6 @@ crumbs = Crumbs(
thanks="Help > Special thanks",
developers="Options > Developers",
default="Servers > ",
offline="Mods > Play offline"
)
no_mods = "No local mods found."
-5
View File
@@ -23,8 +23,6 @@ from dzgui.views.pages.help import Help
from dzgui.views.pages.keys import Keybindings
from dzgui.views.pages.log import Log
from dzgui.views.pages.mods import Mods
from dzgui.views.pages.offline import OfflineLoader
from dzgui.views.pages.options import Options
from dzgui.views.pages.preconnect import PreConnectionAssistant
from dzgui.views.pages.servers import ServerNotebook
@@ -112,11 +110,8 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
self.connection = PreConnectionAssistant(MainController)
self.offline = OfflineLoader(MainController)
self.pages = {
self.help: NotebookPage.HELP,
self.offline: NotebookPage.OFFLINE,
self.clog: NotebookPage.CHANGELOG,
self.keys: NotebookPage.KEYS,
self.settings: NotebookPage.OPTIONS,
-68
View File
@@ -1,68 +0,0 @@
from dzgui.strings import preconnect
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, Gtk # noqa E402
class ConnectBox(Gtk.Box):
def __init__(self) -> None:
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
self.back = Gtk.Button(
label=preconnect.back, halign=Gtk.Align.END, hexpand=True
)
# TODO: dynamically update this
self.ok = Gtk.Button(label="Launch", halign=Gtk.Align.END)
for button in self.back, self.ok:
self.add(button)
def get_back_button(self) -> Gtk.Button:
return self.back
def get_ok_button(self) -> Gtk.Button:
return self.ok
class PreconnectBox(Gtk.Box):
def __init__(self) -> None:
super().__init__(
orientation=Gtk.Orientation.VERTICAL,
valign=Gtk.Align.END,
vexpand=True,
spacing=5,
)
self.button_hbox = ConnectBox()
self.ok = self.button_hbox.get_ok_button()
self.ok.set_label(preconnect.update_mods)
self.connect_last = Gtk.Button(
label=preconnect.connect_last,
halign=Gtk.Align.END,
tooltip_text=preconnect.connect_last_tooltip,
)
self.button_hbox.add(self.connect_last)
self.raise_window = Gtk.CheckButton(
# TODO: strings
label="Foreground DZGUI while downloading",
halign=Gtk.Align.END,
hexpand=True,
valign=Gtk.Align.END,
visible=False,
has_tooltip=True,
sensitive=False,
tooltip_text="Foreground the DZGUI window after mod downloads are queued",
active=True,
)
self.add(self.button_hbox)
self.add(self.raise_window)
def get_back_button(self) -> Gtk.Button:
return self.button_hbox.get_back_button()
def get_ok_button(self) -> Gtk.Button:
return self.ok
+8 -15
View File
@@ -6,23 +6,16 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa
# TODO: subclass Gtk.Frame directly
class HeadingFrame(Gtk.Box):
def __init__(self, heading: str = "") -> None:
def __init__(self, widget: Gtk.Widget, heading: str) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.label = Gtk.Label(label=heading)
self.label.set_halign(Gtk.Align.START)
css.add_class(self.label, "settings-subheading")
label = Gtk.Label(label=heading)
label.set_halign(Gtk.Align.START)
css.add_class(label, "settings-subheading")
self.frame = Gtk.Frame(hexpand=True)
self.frame.set_label_widget(self.label)
frame = Gtk.Frame(hexpand=True)
frame.add(widget)
frame.set_label_widget(label)
self.add(self.frame)
@classmethod
def new_with_widget_and_label(cls, widget: Gtk.Widget, label: str) -> None:
n = cls()
n.frame.add(widget)
n.label.set_label(label)
return n
self.add(frame)
+11 -2
View File
@@ -25,6 +25,8 @@ class Statusbar(Gtk.Grid):
self.controller.register_widget("statusbar", self)
self.emitter = controller.get_emitter()
self.prior_enum: NotebookPage
self.prior_status: str
self.playercount = ""
self.statusbar = Gtk.Statusbar()
@@ -69,8 +71,8 @@ class Statusbar(Gtk.Grid):
NotebookPage.CHANGELOG,
NotebookPage.LOG,
NotebookPage.CONNECTION,
NotebookPage.OFFLINE,
):
self.prior_enum = enum
self.set_by_context(enum, esc_to_return)
return
@@ -80,8 +82,9 @@ class Statusbar(Gtk.Grid):
case NotebookPage.KEYS:
bar = question_to_return
case _:
return
bar = self.prior_status
self.prior_enum = enum
self.set_by_context(enum, bar)
def _on_server_row_changed(self, statusbar: Self) -> None:
@@ -97,6 +100,10 @@ class Statusbar(Gtk.Grid):
context: Union["ServerTab", NotebookPage],
) -> None:
self.spinner.stop()
if type(context) is NotebookPage:
self.prior_enum = context
self.prior_status = ""
# FIXME: CalcDist is being called when table is not loaded
if dist is None:
return
@@ -121,6 +128,8 @@ class Statusbar(Gtk.Grid):
def set_by_context(
self, context: Union[NotebookPage, "ServerTab"], string: str
) -> None:
if context != NotebookPage.KEYS:
self.prior_status = string
meta = self.statusbar.get_context_id(str(context))
self.statusbar.push(meta, string)
self.set_cache(string)
-25
View File
@@ -33,28 +33,3 @@ class FilePicker(Gtk.FileChooserDialog):
self.destroy()
return Path(file)
return None
class FolderPicker(Gtk.FileChooserDialog):
def __init__(self, parent: Gtk.Window) -> None:
super().__init__(
title=picker.title,
action=Gtk.FileChooserAction.SELECT_FOLDER,
parent=parent,
resizable=True,
)
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL)
self.add_buttons("_OK", Gtk.ResponseType.OK)
self.set_default_response(Gtk.ResponseType.OK)
def pick_folder(self) -> Path | None:
res = self.run()
if res in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
self.destroy()
return None
if res == Gtk.ResponseType.OK:
folder = self.get_current_folder()
if folder is not None:
self.destroy()
return Path(folder)
return None
-2
View File
@@ -44,8 +44,6 @@ class ContextMixin(TreeView):
self.context_menu.connect("key-press-event", self._on_key)
for row in group.value:
if row is None:
return
item = self._process_dynamic_row(row)
self.context_menu.append(item)
+6 -21
View File
@@ -11,36 +11,21 @@ if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
class Mods(Gtk.Box):
def __init__(self, controller: "Controller") -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.box = NoOverlayScrolledWindow()
self.tree = ModTreeView(controller)
self.tree.set_vexpand(True)
self.box.add(self.tree)
class Mods(NoOverlayScrolledWindow):
def __init__(self, controller: "Controller") -> None:
super().__init__()
self.controller = controller
self.controller.register_widget("modtreeview", self.tree)
self.emitter = controller.get_emitter()
self.tree = ModTreeView(controller)
self.offline_button = Gtk.Button(
label="Play offline",
halign=Gtk.Align.START,
margin_top=10,
margin_bottom=10,
)
self.offline_button.connect("clicked", self._on_offline_clicked)
self.add(self.offline_button)
self.add(self.box)
self.add(self.tree)
self.controller.register_widget("modtreeview", self.tree)
self.connect("map", self._on_map)
self.connect("unmap", self._on_unmap)
def _on_offline_clicked(self, button: Gtk.Button) -> None:
self.controller.open_offline()
def _on_unmap(self, widget: Self) -> None:
self.emitter.emit("mod_page_toggled", False)
-253
View File
@@ -1,253 +0,0 @@
from pathlib import Path
from typing import Self, Sequence, TYPE_CHECKING
from dzgui.util import css
import dzgui.api.pefile as PeFile
from dzgui.const.constants import APPID_DAYZ, APPID_DAYZ_EXP, APPNAME_DAYZ, APPNAME_DAYZ_EXP_HUMAN
from dzgui.const.enum import NotebookPage, Preferences
from dzgui.strings import offline
from dzgui.views.components.scrollable import NoOverlayScrolledWindow
from dzgui.views.components.frame import HeadingFrame
from dzgui.views.trees.tree_mods import OfflineModTreeView
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk # noqa
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
from dzgui.model.model_factory import FastInsertListStore
class GenericBox(Gtk.Box):
def __init__(self, orientation: Gtk.Orientation, spacing: int = 0) -> None:
super().__init__(orientation=orientation, spacing=spacing)
def extend(self, els: Sequence[Gtk.Widget]) -> None:
for el in els:
self.add(el)
class HBox(GenericBox):
def __init__(self, spacing: int = 0) -> None:
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=spacing)
class VBox(GenericBox):
def __init__(self, spacing: int = 0) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=spacing)
class PageHeading(Gtk.Label):
def __init__(self, label: str) -> None:
super().__init__(label=label, halign=Gtk.Align.CENTER)
css.add_class(self, "page-heading")
class FolderHBox(HBox):
def __init__(self, btn_label: str) -> None:
super().__init__(spacing=5)
self.set_margin_start(10)
self.set_margin_bottom(10)
# TODO: use IconButton with folder-symbolic
self.button = Gtk.Button(label=btn_label, halign=Gtk.Align.START)
self.label = Gtk.Label()
self.extend([self.button, self.label])
def get_button(self) -> Gtk.Button:
return self.button
def set_label(self, label: str) -> None:
self.label.set_label(label)
class ModFrame(HeadingFrame):
def __init__(self, controller: "Controller", label: str) -> None:
super().__init__(heading=label)
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.tree = OfflineModTreeView(controller)
self.tree.set_model(None)
self.scrolled = NoOverlayScrolledWindow()
self.scrolled.set_size_request(600, 400)
self.scrolled.add(self.tree)
self.status = Gtk.Label(
halign=Gtk.Align.START, margin_start=5, margin_top=3, margin_bottom=3
)
self.vbox.pack_end(self.status, expand=False, fill=False, padding=0)
self.vbox.pack_end(self.scrolled, expand=False, fill=False, padding=0)
self.frame.add(self.vbox)
sel = self.tree.get_selection()
sel.connect("changed", self._on_selection_changed)
def collapse_tree(self) -> None:
self.scrolled.hide()
def get_tree(self) -> OfflineModTreeView:
return self.tree
def pack_start(self, widget: Gtk.Widget) -> None:
self.vbox.pack_start(widget, expand=False, fill=False, padding=5)
def set_model(self, model: "FastInsertListStore") -> None:
self.tree.set_model(model)
self.tree.mod_man.store = model
self.set_cursor()
def _on_selection_changed(self, sel: Gtk.TreeSelection) -> None:
model, rows = sel.get_selected_rows()
status = f"Mods selected: {len(rows)}"
self.status.set_label(status)
def set_cursor(self) -> None:
path = Gtk.TreePath.new_from_indices([0])
self.tree.set_cursor(path)
class CustomModFrame(ModFrame):
def __init__(self, controller: "Controller", heading: str) -> None:
super().__init__(controller, heading)
self.controller = controller
# 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_start(self.custom_hbox)
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:
# TODO: CustomModManager
self.custom_hbox.set_label(str(folder))
class RadioFrame(HeadingFrame):
def __init__(self, controller: "Controller") -> None:
super().__init__(heading=offline.version)
self.controller = controller
self.id_map = {APPNAME_DAYZ: APPID_DAYZ, APPNAME_DAYZ_EXP_HUMAN: APPID_DAYZ_EXP}
self.appid: APPID_DAYZ
self.dayz = Gtk.RadioButton.new_with_label(None, APPNAME_DAYZ)
self.dayz_exp = Gtk.RadioButton.new_with_label_from_widget(
self.dayz, APPNAME_DAYZ_EXP_HUMAN
)
self.radio_box = HBox()
self.radio_box.extend([self.dayz, self.dayz_exp])
self.radio_box.set_margin_start(10)
self.radio_box.set_margin_bottom(5)
self.frame.add(self.radio_box)
default_steam_path = self.controller.query_config(Preferences.DEFAULT)
steam_path = Path(default_steam_path)
dayz_exp = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP)
self.dayz.connect("toggled", self._on_radio_toggled)
if dayz_exp is None:
self.dayz_exp.set_sensitive(False)
def _on_radio_toggled(self, radio: Gtk.RadioButton) -> None:
for el in self.dayz, self.dayz_exp:
if el.get_active():
label = el.get_label()
self.appid = self.id_map[label]
def get_appid(self) -> int:
return self.appid
class OfflineLoader(Gtk.Box):
def __init__(self, controller: "Controller"):
super().__init__(
orientation=Gtk.Orientation.VERTICAL,
margin_start=10,
margin_end=10,
)
# TODO: spacing between inner and outer scrollbars
self.controller = controller
self.controller.register_widget("offline_loader", self)
self.add(PageHeading(offline.heading))
self.local_frame = ModFrame(controller, offline.local_frame)
self.custom_frame = CustomModFrame(controller, offline.custom_frame)
# TODO: use ModelFactory
# TODO: suppress symlink column
self.custom_tree = OfflineModTreeView(controller)
self.mission_hbox = FolderHBox(offline.mission_button)
self.mission_frame = HeadingFrame.new_with_widget_and_label(
self.mission_hbox, offline.mission_frame
)
self.radio_frame = RadioFrame(controller)
self.scrollable = Gtk.ScrolledWindow(
vexpand=True, propagate_natural_height=True
)
self.content_box = VBox(spacing=5)
self.content_box.extend(
[
self.local_frame,
self.custom_frame,
self.mission_frame,
self.radio_frame,
]
)
# TODO: share ConnectBox class with preconnect dialog?
self.button_box = HBox(spacing=5)
self.button_box.set_halign(Gtk.Align.END)
self.button_box.set_margin_top(5)
self.back = Gtk.Button(label="Back")
self.ok = Gtk.Button(label="Launch")
self.back.connect("clicked", self._on_back_clicked)
self.ok.connect("clicked", self._on_ok_clicked)
self.connect("key-press-event", self._on_keypress)
self.button_box.extend([self.back, self.ok])
self.scrollable.add(self.content_box)
self.add(self.scrollable)
self.add(self.button_box)
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> None:
if event.keyval == Gdk.KEY_Escape:
self.back.emit("clicked")
def populate(self, store: "FastInsertListStore") -> None:
self.local_frame.set_model(store)
# TODO: toggle if empty model, show warning label
# TODO: suppress trees if there are no mods
self.custom_frame.collapse_tree()
def _on_back_clicked(self, button: Gtk.Button) -> None:
self.controller.open_page(NotebookPage.MODS)
def _on_ok_clicked(self, button: Gtk.Button) -> None:
appid = self.radio_frame.get_appid()
"""
- collect symlinks to selected mods
cf. rebuild_symlinks()
- create symlinks for custom mods
- collect mission folder
"""
+5 -7
View File
@@ -187,14 +187,12 @@ class Options(Gtk.Box):
if prefs.is_debug:
grid.attach(developers, 1, 0, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
for pair in [
(api_box, strings.options.api_keys),
(prefs_grid, strings.options.prefs),
(mods_grid, strings.options.mods),
(version_grid, strings.options.version),
for frame in [
HeadingFrame(api_box, strings.options.api_keys),
HeadingFrame(prefs_grid, strings.options.prefs),
HeadingFrame(mods_grid, strings.options.mods),
HeadingFrame(version_grid, strings.options.version),
]:
frame = HeadingFrame.new_with_widget_and_label(*pair)
grid.attach(frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
row += 1
+4 -13
View File
@@ -166,9 +166,7 @@ class PreConnectionAssistant(Gtk.Box):
self.mods_placeholder = Placeholder("This server has no mods.")
self.tree_box.add(self.mods_placeholder)
self.tree_frame = HeadingFrame.new_with_widget_and_label(
self.tree_box, preconnect.mods
)
self.tree_frame = HeadingFrame(self.tree_box, preconnect.mods)
# TODO: abstract into components
self.warning_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -177,9 +175,7 @@ class PreConnectionAssistant(Gtk.Box):
self.warning_placeholder = Placeholder("No warnings.")
self.warning_box.add(self.warning_tree)
self.warning_box.add(self.warning_placeholder)
self.warning_frame = HeadingFrame.new_with_widget_and_label(
self.warning_box, preconnect.warnings
)
self.warning_frame = HeadingFrame(self.warning_box, preconnect.warnings)
self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.error_tree = MaskedTree(ERROR)
@@ -187,9 +183,7 @@ class PreConnectionAssistant(Gtk.Box):
self.error_placeholder = Placeholder("No errors.")
self.error_box.add(self.error_tree)
self.error_box.add(self.error_placeholder)
self.error_frame = HeadingFrame.new_with_widget_and_label(
self.error_box, preconnect.errors
)
self.error_frame = HeadingFrame(self.error_box, preconnect.errors)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.box.add(self.title)
@@ -333,10 +327,7 @@ class PreConnectionAssistant(Gtk.Box):
self.connect_last.hide()
self._process_warnings(prereqs)
if self.tree.is_visible():
self.tree.grab_focus()
else:
self.grab_focus()
self.tree.grab_focus()
def mark_finished(self) -> None:
self.mod_count.set_label(preconnect.all_updated)
-17
View File
@@ -3,7 +3,6 @@ from typing import Any, TYPE_CHECKING
from dzgui.const.constants import APP_NAME, HEX_RED
from dzgui.const.enum import ContextMenuGroup
from dzgui.managers.mods import ModManager
from dzgui.util import strings, localize
from dzgui.views.mixins.context_mixin import ContextMixin
from dzgui.views.mixins.mods_mixin import ModsMixin
@@ -25,10 +24,7 @@ logger = logging.getLogger(APP_NAME)
class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore
def __init__(self, controller: "Controller") -> None:
super().__init__(controller, menu=ContextMenuGroup.MOD)
self.controller = controller
self.mod_man = ModManager(self, controller)
emitter = self.controller.get_emitter()
emitter.connect("mods_updated", self._on_mods_updated)
emitter.connect("mods_highlighted", self._on_mods_highlighted)
@@ -60,12 +56,6 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore
self.connect("button-press-event", self.present_menu)
self.connect("key-press-event", self.present_menu)
def get_mod_man(self) -> ModManager:
return self.mod_man
def load_mods(self) -> None:
self.mod_man.load_mods()
def _on_mods_highlighted(self, emitter: "Emitter") -> None:
self.get_selection().unselect_all()
@@ -115,10 +105,3 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore
val = model[it][3]
formatted = localize.number(val)
cell.set_property("text", formatted)
def set_menu(self, context: ContextMenuGroup) -> None:
self.menu = context
class OfflineModTreeView(ModTreeView):
def __init__(self, controller: "Controller") -> None:
super().__init__(controller)
+1 -1
View File
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [
{name = "aclist"}
]
version = "7.0.0b12"
version = "7.0.0b11"
license = "GPL-3.0-or-later"
license-files = ["LICENSE"]
readme = "README.md"