mirror of
https://github.com/aclist/dztui.git
synced 2026-08-29 19:27:13 +02:00
Compare commits
8 Commits
e081d0ad16
...
2466887014
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2466887014 | ||
|
|
6050a370f8 | ||
|
|
d4a99f8229 | ||
|
|
46fba4eef1 | ||
|
|
bc1118a29f | ||
|
|
775f582ea6 | ||
|
|
2dadd70be3 | ||
|
|
3473f03350 |
@ -87,15 +87,18 @@ def get_mod_size(path: Path) -> float:
|
||||
return size
|
||||
|
||||
|
||||
def get_delimited_mods(steam_path: Path) -> list[Any]:
|
||||
workshop_path = get_local_mod_path(steam_path)
|
||||
mods = get_local_mods(workshop_path)
|
||||
def get_custom_mods(path: Path) -> list[Any]:
|
||||
mods = get_local_mods(path)
|
||||
# TODO: error handling
|
||||
return parse_mods(mods)
|
||||
|
||||
|
||||
def parse_mods(mods: list[Path]) -> list[Any]:
|
||||
clean = []
|
||||
for mod in mods:
|
||||
mod_dir = mod.name
|
||||
symlink = _hash(mod_dir)
|
||||
# FIXME: malformed .cpp files could break this
|
||||
# mention that mods may be downloading
|
||||
meta = parse_meta(mod)
|
||||
if meta is None:
|
||||
continue
|
||||
@ -106,6 +109,12 @@ def get_delimited_mods(steam_path: Path) -> list[Any]:
|
||||
return clean
|
||||
|
||||
|
||||
def get_delimited_mods(steam_path: Path) -> list[Any]:
|
||||
workshop_path = get_local_mod_path(steam_path)
|
||||
mods = get_local_mods(workshop_path)
|
||||
return parse_mods(mods)
|
||||
|
||||
|
||||
def get_missing_mods(local: list, remote: list) -> list:
|
||||
return [mod for mod in remote if mod not in local]
|
||||
|
||||
|
||||
@ -162,7 +162,7 @@ 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:
|
||||
def launch_offline(client: 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)
|
||||
|
||||
@ -35,6 +35,7 @@ HEX_ORANGE = "#FFAC1C"
|
||||
CARET_DOWN = "go-down-symbolic"
|
||||
CARET_UP = "go-up-symbolic"
|
||||
CLIPBOARD = "edit-copy-symbolic"
|
||||
FOLDER = "folder-symbolic"
|
||||
ERROR = "dialog-error-symbolic"
|
||||
HELP_BUBBLE = "help-about-symbolic"
|
||||
INPUT_KEYBOARD = "input-keyboard-symbolic"
|
||||
|
||||
@ -20,7 +20,6 @@ from dzgui.controllers.emitter import Emitter
|
||||
from dzgui.managers.config import ConfigManager
|
||||
from dzgui.managers.connection import ConnectionManager
|
||||
from dzgui.managers.contextmenu import ContextMenuManager
|
||||
from dzgui.managers.mods import ModManager
|
||||
from dzgui.managers.notes import NoteManager
|
||||
from dzgui.model.servers import ServerModelManager
|
||||
from dzgui.util.diag import write_diagnostic
|
||||
@ -236,7 +235,7 @@ class Controller(GObject.GObject):
|
||||
mod_man.toggle_mod_selection(state)
|
||||
|
||||
def delete_mods(
|
||||
self, treeview: Union["ModTreeView", "OfflineModTreeView"] = None
|
||||
self, treeview: Union["ModTreeView", "OfflineModTreeView", None] = None
|
||||
) -> None:
|
||||
if treeview is None:
|
||||
view = self.mediator.modtreeview
|
||||
|
||||
73
dzgui/managers/offline.py
Normal file
73
dzgui/managers/offline.py
Normal file
@ -0,0 +1,73 @@
|
||||
from pathlib import Path
|
||||
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.model.model_factory import FastInsertListStore, ModelFactory
|
||||
from dzgui.strings import dialogs
|
||||
from dzgui.util.symlink import clone_symlinks
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
|
||||
|
||||
class OfflineManager:
|
||||
def __init__(
|
||||
self,
|
||||
controller: "Controller",
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.controller = controller
|
||||
self.thread_man = ThreadingManager(controller)
|
||||
|
||||
self.appid: int
|
||||
self.mission_folder: str
|
||||
self.local_mods: list[str] | None
|
||||
self.custom_mods: list[str] | None
|
||||
|
||||
# TODO: strings
|
||||
@call_on_thread("parsing")
|
||||
def parse_custom_mods(self, 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
|
||||
|
||||
def setup(
|
||||
self,
|
||||
appid: int,
|
||||
mission: str = "",
|
||||
local_mods: list[str] | None = None,
|
||||
custom_mods: list[str] | None = None,
|
||||
) -> None:
|
||||
|
||||
self.appid = appid
|
||||
self.mission_folder = mission
|
||||
self.local_mods = local_mods
|
||||
self.custom_mods = custom_mods
|
||||
|
||||
self.launch()
|
||||
|
||||
@call_on_thread(dialogs.waiting_for_launch)
|
||||
def launch(self) -> None:
|
||||
client = self.controller.query_config(Preferences.CLIENT)
|
||||
name = self.controller.query_config(Preferences.NAME)
|
||||
steam_path = self.controller.query_config(Preferences.DEFAULT)
|
||||
|
||||
new_symlinks: list[str] = []
|
||||
|
||||
combined_mods: list[str] = []
|
||||
if self.local_mods is not None:
|
||||
combined_mods.extend(self.local_mods)
|
||||
|
||||
if self.custom_mods is not None:
|
||||
# TODO: new function that creates symlinks in game path
|
||||
# based on selected mods
|
||||
clone_symlinks(Path(steam_path))
|
||||
combined_mods.extend(new_symlinks)
|
||||
|
||||
launch_offline(client, self.appid, name, combined_mods, self.mission_folder)
|
||||
@ -166,5 +166,5 @@ 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]
|
||||
# def convert_model_to_list(self, model: "FastInsertListStore") -> list:
|
||||
# return [[el for el in row] for row in model]
|
||||
|
||||
@ -48,8 +48,10 @@ class LargeIcon(Gtk.Image):
|
||||
|
||||
|
||||
class IconButton(Gtk.Button):
|
||||
def __init__(self, icon: str, margin: int = 0) -> None:
|
||||
super().__init__()
|
||||
def __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.set_image(self.icon)
|
||||
self.set_image_position(Gtk.PositionType.RIGHT)
|
||||
@ -57,8 +59,10 @@ class IconButton(Gtk.Button):
|
||||
|
||||
|
||||
class IconTextButton(IconButton):
|
||||
def __init__(self, icon: str, label: str) -> None:
|
||||
super().__init__(icon, margin=5)
|
||||
def __init__(
|
||||
self, icon: str, label: str, halign: Gtk.Align = Gtk.Align.START
|
||||
) -> None:
|
||||
super().__init__(icon, margin=5, halign=halign)
|
||||
self.set_label(label)
|
||||
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from typing import Self
|
||||
from dzgui.util import css
|
||||
|
||||
import gi
|
||||
@ -21,7 +22,7 @@ class HeadingFrame(Gtk.Box):
|
||||
self.add(self.frame)
|
||||
|
||||
@classmethod
|
||||
def new_with_widget_and_label(cls, widget: Gtk.Widget, label: str) -> None:
|
||||
def new_with_widget_and_label(cls, widget: Gtk.Widget, label: str) -> Self:
|
||||
n = cls()
|
||||
n.frame.add(widget)
|
||||
n.label.set_label(label)
|
||||
|
||||
@ -45,7 +45,7 @@ class ContextMixin(TreeView):
|
||||
|
||||
for row in group.value:
|
||||
if row is None:
|
||||
return
|
||||
return False
|
||||
item = self._process_dynamic_row(row)
|
||||
self.context_menu.append(item)
|
||||
|
||||
|
||||
@ -3,11 +3,19 @@ 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.constants import (
|
||||
APPID_DAYZ,
|
||||
APPID_DAYZ_EXP,
|
||||
APPNAME_DAYZ,
|
||||
APPNAME_DAYZ_EXP_HUMAN,
|
||||
FOLDER,
|
||||
)
|
||||
from dzgui.const.enum import NotebookPage, Preferences
|
||||
from dzgui.managers.offline import OfflineManager
|
||||
from dzgui.strings import offline
|
||||
from dzgui.views.components.scrollable import NoOverlayScrolledWindow
|
||||
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
|
||||
|
||||
|
||||
@ -54,8 +62,9 @@ class FolderHBox(HBox):
|
||||
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)
|
||||
# TODO: alternate class for left-aligned icons
|
||||
self.button = IconTextButton(FOLDER, btn_label)
|
||||
self.button.set_image_position(Gtk.PositionType.LEFT)
|
||||
self.label = Gtk.Label()
|
||||
self.extend([self.button, self.label])
|
||||
|
||||
@ -76,26 +85,30 @@ class ModFrame(HeadingFrame):
|
||||
self.tree.set_model(None)
|
||||
|
||||
self.scrolled = NoOverlayScrolledWindow()
|
||||
self.scrolled.set_margin_end(10)
|
||||
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.tree_vbox = VBox()
|
||||
self.tree_vbox.extend([self.scrolled, self.status])
|
||||
|
||||
self.vbox.add(self.tree_vbox)
|
||||
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()
|
||||
self.tree_vbox.hide()
|
||||
|
||||
def get_tree(self) -> OfflineModTreeView:
|
||||
return self.tree
|
||||
|
||||
def pack_start(self, widget: Gtk.Widget) -> None:
|
||||
def pack(self, widget: Gtk.Widget) -> None:
|
||||
self.vbox.pack_start(widget, expand=False, fill=False, padding=5)
|
||||
|
||||
def set_model(self, model: "FastInsertListStore") -> None:
|
||||
@ -105,12 +118,17 @@ class ModFrame(HeadingFrame):
|
||||
|
||||
def _on_selection_changed(self, sel: Gtk.TreeSelection) -> None:
|
||||
model, rows = sel.get_selected_rows()
|
||||
status = f"Mods selected: {len(rows)}"
|
||||
if len(rows) == 0:
|
||||
# TODO recycle (util.format)
|
||||
status = "Ctrl-click to select multiple; Shift-click to select a range."
|
||||
else:
|
||||
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)
|
||||
self.tree.get_selection().unselect_all()
|
||||
|
||||
|
||||
class CustomModFrame(ModFrame):
|
||||
@ -122,16 +140,20 @@ class CustomModFrame(ModFrame):
|
||||
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)
|
||||
self.pack(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))
|
||||
|
||||
def get_mods(self) -> list[str]:
|
||||
rows = self.tree.get_selection().get_selected_rows()
|
||||
dirs = [str(col[0]) for col in rows]
|
||||
return dirs
|
||||
|
||||
|
||||
class RadioFrame(HeadingFrame):
|
||||
def __init__(self, controller: "Controller") -> None:
|
||||
@ -140,7 +162,7 @@ class RadioFrame(HeadingFrame):
|
||||
self.controller = controller
|
||||
|
||||
self.id_map = {APPNAME_DAYZ: APPID_DAYZ, APPNAME_DAYZ_EXP_HUMAN: APPID_DAYZ_EXP}
|
||||
self.appid: APPID_DAYZ
|
||||
self.appid = APPID_DAYZ
|
||||
|
||||
self.dayz = Gtk.RadioButton.new_with_label(None, APPNAME_DAYZ)
|
||||
self.dayz_exp = Gtk.RadioButton.new_with_label_from_widget(
|
||||
@ -154,6 +176,7 @@ class RadioFrame(HeadingFrame):
|
||||
|
||||
self.frame.add(self.radio_box)
|
||||
|
||||
# TODO: abstract out of here
|
||||
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)
|
||||
@ -180,16 +203,15 @@ class OfflineLoader(Gtk.Box):
|
||||
margin_end=10,
|
||||
)
|
||||
|
||||
# TODO: spacing between inner and outer scrollbars
|
||||
self.controller = controller
|
||||
self.controller.register_widget("offline_loader", self)
|
||||
self.offline_man = OfflineManager(controller)
|
||||
|
||||
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)
|
||||
|
||||
@ -244,10 +266,9 @@ class OfflineLoader(Gtk.Box):
|
||||
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
|
||||
"""
|
||||
# appid = self.radio_frame.get_appid()
|
||||
# mission = self.mission_frame.get_mission()
|
||||
# local_mods = self.local_frame.get_mods()
|
||||
# custom_mods = self.custom_frame.get_mods()
|
||||
# self.offline_man.setup(appid, mission, local_mods, custom_mods)
|
||||
pass
|
||||
|
||||
@ -154,7 +154,7 @@ class PreConnectionAssistant(Gtk.Box):
|
||||
self.progress_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
||||
self.progress_box.add(self.mod_count)
|
||||
|
||||
self.scrolled = Gtk.ScrolledWindow()
|
||||
self.scrolled = Gtk.ScrolledWindow(margin_end=10)
|
||||
self.scrolled.add(self.tree)
|
||||
self.scrolled.set_size_request(600, 400)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user