feat: launch offline (WIP)

This commit is contained in:
aclist committed 2026-06-09 02:37:50 +09:00
1 parent b17b8c24ff
commit e6026e84f9
15 files changed
+312 -148

No files matched your search

+26 -11
View File
@@ -54,7 +54,7 @@ if TYPE_CHECKING:
from dzgui.views.pages.servers import ServerNotebook from dzgui.views.pages.servers import ServerNotebook
from dzgui.views.trees.tree_log import LogTreeView from dzgui.views.trees.tree_log import LogTreeView
from dzgui.views.trees.tree_menu import MenuTreeView from dzgui.views.trees.tree_menu import MenuTreeView
from dzgui.views.trees.tree_mods import ModTreeView from dzgui.views.trees.tree_mods import ModTreeView, OfflineModTreeView
from dzgui.views.trees.tree_servers import ServerTreeView from dzgui.views.trees.tree_servers import ServerTreeView
@@ -221,19 +221,29 @@ class Controller(GObject.GObject):
open_user_workshop(uid, client) open_user_workshop(uid, client)
def load_mods(self) -> None: def load_mods(self) -> None:
self.mod_man = ModManager(self) self.mediator.modtreeview.load_mods()
def uncolorize_mods(self) -> None: def uncolorize_mods(self) -> None:
self.mod_man.uncolorize_mods() mod_man = self.mediator.modtreeview.get_mod_man()
mod_man.uncolorize_mods()
def highlight_stale(self) -> None: def highlight_stale(self) -> None:
self.mod_man.highlight_stale() mod_man = self.mediator.modtreeview.get_mod_man()
mod_man.highlight_stale()
def toggle_mod_selection(self, state: bool) -> None: def toggle_mod_selection(self, state: bool) -> None:
self.mod_man.toggle_mod_selection(state) mod_man = self.mediator.modtreeview.get_mod_man()
mod_man.toggle_mod_selection(state)
def delete_mods(self) -> None: def delete_mods(
self.mod_man.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 get_mod_store(self) -> Gtk.TreeModel | None: def get_mod_store(self) -> Gtk.TreeModel | None:
return self.mediator.modtreeview.get_model() return self.mediator.modtreeview.get_model()
@@ -260,7 +270,8 @@ class Controller(GObject.GObject):
dialog.run() dialog.run()
def select_colorized(self) -> None: def select_colorized(self) -> None:
self.mod_man.select_colorized() mod_man = self.mediator.modtreeview.get_mod_man()
mod_man.select_colorized()
def get_all_tree_filters(self) -> dict[str, dict[str, bool]]: def get_all_tree_filters(self) -> dict[str, dict[str, bool]]:
servers = self.get_servers() servers = self.get_servers()
@@ -293,7 +304,9 @@ class Controller(GObject.GObject):
def set_custom_folder(self) -> Union["Path", None]: def set_custom_folder(self) -> Union["Path", None]:
picker = FolderPicker(self.mediator.window) picker = FolderPicker(self.mediator.window)
return picker.pick_folder() folder = picker.pick_folder()
picker.destroy()
return folder
def update_api_key(self, key: Preferences, text: str) -> None: def update_api_key(self, key: Preferences, text: str) -> None:
self.config_man.update_api_key(key, text) self.config_man.update_api_key(key, text)
@@ -541,6 +554,8 @@ class Controller(GObject.GObject):
return True return True
return False return False
def open_offline(self, mods: Gtk.TreeModel | None) -> None: def open_offline(self) -> None:
self.open_page(NotebookPage.OFFLINE) self.open_page(NotebookPage.OFFLINE)
self.mediator.offline_loader.populate(mods) mod_man = self.mediator.modtreeview.get_mod_man()
store = mod_man.store
self.mediator.offline_loader.populate(store)
+10 -4
View File
@@ -8,7 +8,7 @@ from dzgui.util.clip import copy_clipboard
from dzgui.util.open_links import open_workshop_page from dzgui.util.open_links import open_workshop_page
from dzgui.views.dialogs.note import NoteDialog from dzgui.views.dialogs.note import NoteDialog
from dzgui.views.trees.tree_mods import ModTreeView from dzgui.views.trees.tree_mods import ModTreeView, OfflineModTreeView
from dzgui.views.trees.tree_log import LogTreeView from dzgui.views.trees.tree_log import LogTreeView
from dzgui.views.trees.tree_servers import ServerTreeView from dzgui.views.trees.tree_servers import ServerTreeView
from dzgui.views.trees.tree_server_mods import ServerModTreeView from dzgui.views.trees.tree_server_mods import ServerModTreeView
@@ -27,7 +27,13 @@ if TYPE_CHECKING:
class ContextMenuManager: class ContextMenuManager:
def __init__( def __init__(
self, self,
treeview: LogTreeView | ModTreeView | ServerTreeView | ServerModTreeView, treeview: (
LogTreeView
| ModTreeView
| ServerTreeView
| ServerModTreeView
| OfflineModTreeView
),
controller: "Controller", controller: "Controller",
) -> None: ) -> None:
self.controller = controller self.controller = controller
@@ -72,10 +78,10 @@ class ContextMenuManager:
case ContextMenu.SHOW_MODS: case ContextMenu.SHOW_MODS:
self.controller.get_modlist(record) self.controller.get_modlist(record)
if isinstance(self.treeview, ModTreeView): if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
match action: match action:
case ContextMenu.DELETE_MOD: case ContextMenu.DELETE_MOD:
self.controller.delete_mods() self.controller.delete_mods(self.treeview)
case ContextMenu.OPEN_WORKSHOP: case ContextMenu.OPEN_WORKSHOP:
self.open_mod_page() self.open_mod_page()
+5 -7
View File
@@ -2,7 +2,7 @@ import logging
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import Any, TYPE_CHECKING from typing import TYPE_CHECKING
from dzgui.api.mods import ( from dzgui.api.mods import (
get_delimited_mods, get_delimited_mods,
@@ -40,20 +40,20 @@ class ModManager:
this manager is instantiated each time the Mods page is opened this manager is instantiated each time the Mods page is opened
""" """
def __init__(self, controller: "Controller") -> None: def __init__(self, tree: Gtk.TreeView, controller: "Controller") -> None:
self.treeview = tree
self.controller = controller self.controller = controller
self.emitter = controller.get_emitter() self.emitter = controller.get_emitter()
self.prefs = controller.get_prefs() self.prefs = controller.get_prefs()
self.path = controller.query_config(Preferences.DEFAULT) self.path = controller.query_config(Preferences.DEFAULT)
self.treeview = self.controller.get_modtreeview()
self.store: FastInsertListStore self.store: FastInsertListStore
self.thread_man = ThreadingManager(controller) self.thread_man = ThreadingManager(controller)
self._get_mods()
@call_on_thread(dialogs.fetching_mods) @call_on_thread(dialogs.fetching_mods)
def _get_mods(self) -> None: def load_mods(self) -> None:
mods = get_delimited_mods(self.path) mods = get_delimited_mods(self.path)
if len(mods) < 1: if len(mods) < 1:
msg = self.format_mod_statusbar() msg = self.format_mod_statusbar()
@@ -66,8 +66,6 @@ class ModManager:
func = StoredFunc(self._on_mods_loaded) func = StoredFunc(self._on_mods_loaded)
self.thread_man.set_cleanup_func(func) self.thread_man.set_cleanup_func(func)
# def _on_mods_loaded(self, mods: list[list[Any]]) -> None:
def _on_mods_loaded(self) -> None: def _on_mods_loaded(self) -> None:
self.treeview.set_model(self.store) self.treeview.set_model(self.store)
msg = self.format_mod_statusbar() msg = self.format_mod_statusbar()
+3
View File
@@ -165,3 +165,6 @@ class ModelFactory:
def make_server_store(self) -> FastInsertListStore: def make_server_store(self) -> FastInsertListStore:
return self.new_model_from_class(ServerCols) 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]
+10
View File
@@ -1 +1,11 @@
heading = "Play offline" 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 -1
View File
@@ -64,7 +64,7 @@ connect = "Connect"
# Columns # Columns
server_mod_cols = ["Mod", "ID", "Installed"] server_mod_cols = ["Mod", "ID", "Installed"]
mod_cols = ["Mod", "Symlink", "Dir", "Size (MiB)", "Color"] mod_cols = ["Mod", "Symlink", "ID", "Size (MiB)", "Color"]
log_cols = ["Timestamp", "Flag", "Traceback", "Message"] log_cols = ["Timestamp", "Flag", "Traceback", "Message"]
browser_cols = [ browser_cols = [
"Name", "Name",
+1
View File
@@ -23,6 +23,7 @@ from dzgui.views.pages.help import Help
from dzgui.views.pages.keys import Keybindings from dzgui.views.pages.keys import Keybindings
from dzgui.views.pages.log import Log from dzgui.views.pages.log import Log
from dzgui.views.pages.mods import Mods from dzgui.views.pages.mods import Mods
from dzgui.views.pages.offline import OfflineLoader from dzgui.views.pages.offline import OfflineLoader
from dzgui.views.pages.options import Options from dzgui.views.pages.options import Options
from dzgui.views.pages.preconnect import PreConnectionAssistant from dzgui.views.pages.preconnect import PreConnectionAssistant
+15 -8
View File
@@ -6,16 +6,23 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa from gi.repository import Gtk # noqa
# TODO: subclass Gtk.Frame directly
class HeadingFrame(Gtk.Box): class HeadingFrame(Gtk.Box):
def __init__(self, widget: Gtk.Widget, heading: str) -> None: def __init__(self, heading: str = "") -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL) super().__init__(orientation=Gtk.Orientation.VERTICAL)
label = Gtk.Label(label=heading) self.label = Gtk.Label(label=heading)
label.set_halign(Gtk.Align.START) self.label.set_halign(Gtk.Align.START)
css.add_class(label, "settings-subheading") css.add_class(self.label, "settings-subheading")
frame = Gtk.Frame(hexpand=True) self.frame = Gtk.Frame(hexpand=True)
frame.add(widget) self.frame.set_label_widget(self.label)
frame.set_label_widget(label)
self.add(frame) 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
+2
View File
@@ -44,6 +44,8 @@ class ContextMixin(TreeView):
self.context_menu.connect("key-press-event", self._on_key) self.context_menu.connect("key-press-event", self._on_key)
for row in group.value: for row in group.value:
if row is None:
return
item = self._process_dynamic_row(row) item = self._process_dynamic_row(row)
self.context_menu.append(item) self.context_menu.append(item)
+6 -8
View File
@@ -11,12 +11,14 @@ if TYPE_CHECKING:
from dzgui.controllers.mc import Controller from dzgui.controllers.mc import Controller
class Mods(NoOverlayScrolledWindow): class Mods(Gtk.Box):
def __init__(self, controller: "Controller") -> None: def __init__(self, controller: "Controller") -> None:
super().__init__() super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.box = NoOverlayScrolledWindow()
self.tree = ModTreeView(controller) self.tree = ModTreeView(controller)
self.tree.set_vexpand(True) self.tree.set_vexpand(True)
self.box.add(self.tree)
self.controller = controller self.controller = controller
self.controller.register_widget("modtreeview", self.tree) self.controller.register_widget("modtreeview", self.tree)
@@ -30,18 +32,14 @@ class Mods(NoOverlayScrolledWindow):
) )
self.offline_button.connect("clicked", self._on_offline_clicked) self.offline_button.connect("clicked", self._on_offline_clicked)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.add(self.offline_button)
self.box.add(self.offline_button)
self.box.add(self.tree)
self.add(self.box) self.add(self.box)
self.connect("map", self._on_map) self.connect("map", self._on_map)
self.connect("unmap", self._on_unmap) self.connect("unmap", self._on_unmap)
def _on_offline_clicked(self, button: Gtk.Button) -> None: def _on_offline_clicked(self, button: Gtk.Button) -> None:
self.controller.open_offline(self.tree.get_model()) self.controller.open_offline()
# TODO: add offline page
def _on_unmap(self, widget: Self) -> None: def _on_unmap(self, widget: Self) -> None:
self.emitter.emit("mod_page_toggled", False) self.emitter.emit("mod_page_toggled", False)
+189 -96
View File
@@ -1,9 +1,14 @@
from typing import Self, TYPE_CHECKING from pathlib import Path
from typing import Self, Sequence, TYPE_CHECKING
from dzgui.strings import offline
from dzgui.util import css from dzgui.util import css
import dzgui.api.pefile as PeFile
from dzgui.const.constants import 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.components.frame import HeadingFrame
from dzgui.views.trees.tree_mods import ModTreeView from dzgui.views.trees.tree_mods import OfflineModTreeView
import gi import gi
@@ -13,9 +18,144 @@ from gi.repository import Gtk, Gdk # noqa
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.controllers.mc import Controller from dzgui.controllers.mc import Controller
from dzgui.model.model_factory import FastInsertListStore
# FIXME: grab focus of outer page
# TODO: if any custom mods, symlink them before connecting proceeding class GenericBox(Gtk.Box):
def __init__(self, orientation: Gtk.Orientation) -> None:
super().__init__(orientation=orientation)
def extend(self, els: Sequence[Gtk.Widget]) -> None:
for el in els:
self.add(el)
class HBox(GenericBox):
def __init__(self) -> None:
super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
class VBox(GenericBox):
def __init__(self) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL)
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__()
self.set_margin_start(10)
self.set_margin_bottom(10)
self.set_spacing(5)
# 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
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.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)
if dayz_exp is None:
self.dayz_exp.set_sensitive(False)
class OfflineLoader(Gtk.Box): class OfflineLoader(Gtk.Box):
@@ -26,127 +166,80 @@ class OfflineLoader(Gtk.Box):
margin_end=10, margin_end=10,
) )
# TODO: wrap entire page in scrollable # TODO: wrap entire page in scrollable, see preconnect dialog
# TODO: spacing between inner and outer scrollbars
self.controller = controller self.controller = controller
self.controller.register_widget("offline_loader", self) self.controller.register_widget("offline_loader", self)
label = Gtk.Label(label=offline.heading) self.add(PageHeading(offline.heading))
label.set_halign(Gtk.Align.CENTER)
css.add_class(label, "page-heading")
self.add(label)
# TODO: wrap trees in scrollable
# TODO: suppress trees if there are no mods
self.local_mods = ModTreeView(controller)
self.scr = Gtk.ScrolledWindow()
self.scr.add(self.local_mods)
self.scr.set_size_request(600, 400)
# TODO: strings
self.local_frame = HeadingFrame(self.scr, "Installed mods")
# TODO: update selected count statusbar when changed
sel = self.local_mods.get_selection()
sel.connect("changed", self._on_selection_changed)
# TODO: abstract into class
# TODO: folder label
# TODO: descriptive text here explaining how this area works # TODO: descriptive text here explaining how this area works
# TODO: use statusbar msg from format.format_mods self.local_frame = ModFrame(controller, offline.local_frame)
# TODO: strings self.custom_frame = CustomModFrame(controller, offline.custom_frame)
self.custom_button = Gtk.Button(
label="Set custom mod folder", halign=Gtk.Align.START self.mission_hbox = FolderHBox(offline.mission_button)
self.mission_frame = HeadingFrame.new_with_widget_and_label(
self.mission_hbox, offline.mission_frame
) )
self.custom_button.connect("clicked", self._on_custom_button_clicked)
self.custom_mods = Gtk.TreeView()
self.custom_mods_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, margin_start=10, margin_bottom=10
)
self.custom_mods_box.add(self.custom_button)
self.custom_mods_box.add(self.custom_mods)
# TODO: strings self.radio_frame = RadioFrame(controller)
self.custom_frame = HeadingFrame(self.custom_mods_box, "Custom mods") # HeadingFrame.new_with_widget_and_label(self.radio_box, offline.version)
self.mission_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, margin_start=10, margin_bottom=10
)
# TODO: strings
self.mission_button = Gtk.Button(label="Select mission folder")
self.mission_label = Gtk.Label()
self.mission_box.add(self.mission_button)
self.mission_box.add(self.mission_label)
# TODO: strings
self.mission_frame = HeadingFrame(self.mission_box, "Mission")
self.radio_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, margin_start=10, margin_bottom=10
)
# TODO: strings
# TODO: disable if not available, use PeFile
self.dayz = Gtk.RadioButton.new_with_label(None, "DayZ")
self.dayz_exp = Gtk.RadioButton.new_with_label_from_widget(
self.dayz, "DayZ Experimental"
)
self.radio_box.add(self.dayz)
self.radio_box.add(self.dayz_exp)
self.radio_frame = HeadingFrame(self.radio_box, "DayZ Version")
# TODO: abstraction
self.scrollable = Gtk.ScrolledWindow( self.scrollable = Gtk.ScrolledWindow(
vexpand=True, propagate_natural_height=True vexpand=True, propagate_natural_height=True
) )
# TODO: use same button anchoring logic as preconnect dialog # TODO: use same button anchoring logic as preconnect dialog
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) self.content_box = VBox()
self.box.add(self.local_frame) self.content_box.set_spacing(5)
self.box.add(self.custom_frame) self.content_box.extend(
self.box.add(self.mission_frame) [
self.box.add(self.radio_frame) self.local_frame,
self.scrollable.add(self.box) self.custom_frame,
self.mission_frame,
self.radio_frame,
]
)
self.scrollable.add(self.content_box)
self.add(self.scrollable) self.add(self.scrollable)
# TODO: use ModelFactory # TODO: use ModelFactory
self.custom_mods = Gtk.TreeView() # TODO: suppress symlink column
self.custom_tree = OfflineModTreeView(controller)
# TODO: shared ConnectBox class # TODO: share ConnectBox class with preconnect dialog
self.button_box = HBox()
self.back = Gtk.Button() self.back = Gtk.Button()
self.ok = Gtk.Button() self.ok = Gtk.Button()
self.back.connect("clicked", self._on_back_clicked) self.back.connect("clicked", self._on_back_clicked)
self.ok.connect("clicked", self._on_ok_clicked) self.ok.connect("clicked", self._on_ok_clicked)
self.connect("key-press-event", self._on_keypress) self.connect("key-press-event", self._on_keypress)
self.button_box.extend([self.back, self.ok])
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> None: def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> None:
# TODO: consider dropping this
if event.keyval == Gdk.KEY_Escape: if event.keyval == Gdk.KEY_Escape:
self.back.emit("clicked") self.back.emit("clicked")
def _on_selection_changed(self, sel: Gtk.TreeSelection) -> None: def populate(self, store: "FastInsertListStore") -> None:
model, rows = sel.get_selected_rows() self.local_frame.set_model(store)
# TODO: update sub-statusbar with selected mods # TODO: toggle if empty model, show warning label
print(len(rows)) # TODO: suppress trees if there are no mods
self.custom_frame.collapse_tree()
def populate(self, mods: Gtk.TreeModel | None) -> None:
self.local_mods.set_model(mods)
# TODO: push existing model
# TODO: set sensitivity of radio buttons
pass
def _on_custom_button_clicked(self, button: Gtk.Button) -> None:
# TODO: recycle this for both buttons
folder = self.controller.set_custom_folder()
if folder is not None:
# TODO: parse mods from here
# TODO: offline mod manager would be cleaner
print(folder)
def _on_back_clicked(self, button: Gtk.Button) -> None: def _on_back_clicked(self, button: Gtk.Button) -> None:
from dzgui.const.enum import NotebookPage # FIXME: returns to server page and not parent mod page
# consider using a toggle button instead, and suppressing statusbar
self.controller.open_page(NotebookPage.SERVERS) self.controller.open_page(NotebookPage.SERVERS)
def _on_ok_clicked(self, button: Gtk.Button) -> None: def _on_ok_clicked(self, button: Gtk.Button) -> None:
"""
- collect symlinks to selected mods
cf. rebuild_symlinks()
- create symlinks for custom mods
- collect appid
- collect mission folder
"""
pass pass
"""
create symlinks if there are custom mods
cf. rebuild_symlinks()
"""
+7 -5
View File
@@ -187,12 +187,14 @@ class Options(Gtk.Box):
if prefs.is_debug: if prefs.is_debug:
grid.attach(developers, 1, 0, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT) grid.attach(developers, 1, 0, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
for frame in [ for pair in [
HeadingFrame(api_box, strings.options.api_keys), (api_box, strings.options.api_keys),
HeadingFrame(prefs_grid, strings.options.prefs), (prefs_grid, strings.options.prefs),
HeadingFrame(mods_grid, strings.options.mods), (mods_grid, strings.options.mods),
HeadingFrame(version_grid, strings.options.version), (version_grid, strings.options.version),
]: ]:
frame = HeadingFrame.new_with_widget_and_label(*pair)
grid.attach(frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT) grid.attach(frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
row += 1 row += 1
+9 -3
View File
@@ -166,7 +166,9 @@ class PreConnectionAssistant(Gtk.Box):
self.mods_placeholder = Placeholder("This server has no mods.") self.mods_placeholder = Placeholder("This server has no mods.")
self.tree_box.add(self.mods_placeholder) self.tree_box.add(self.mods_placeholder)
self.tree_frame = HeadingFrame(self.tree_box, preconnect.mods) self.tree_frame = HeadingFrame.new_with_widget_and_label(
self.tree_box, preconnect.mods
)
# TODO: abstract into components # TODO: abstract into components
self.warning_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.warning_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -175,7 +177,9 @@ class PreConnectionAssistant(Gtk.Box):
self.warning_placeholder = Placeholder("No warnings.") self.warning_placeholder = Placeholder("No warnings.")
self.warning_box.add(self.warning_tree) self.warning_box.add(self.warning_tree)
self.warning_box.add(self.warning_placeholder) self.warning_box.add(self.warning_placeholder)
self.warning_frame = HeadingFrame(self.warning_box, preconnect.warnings) self.warning_frame = HeadingFrame.new_with_widget_and_label(
self.warning_box, preconnect.warnings
)
self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.error_tree = MaskedTree(ERROR) self.error_tree = MaskedTree(ERROR)
@@ -183,7 +187,9 @@ class PreConnectionAssistant(Gtk.Box):
self.error_placeholder = Placeholder("No errors.") self.error_placeholder = Placeholder("No errors.")
self.error_box.add(self.error_tree) self.error_box.add(self.error_tree)
self.error_box.add(self.error_placeholder) self.error_box.add(self.error_placeholder)
self.error_frame = HeadingFrame(self.error_box, preconnect.errors) self.error_frame = HeadingFrame.new_with_widget_and_label(
self.error_box, preconnect.errors
)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.box.add(self.title) self.box.add(self.title)
+11 -4
View File
@@ -4,7 +4,7 @@ from typing import Any, Optional, TYPE_CHECKING
from warnings import deprecated from warnings import deprecated
from dzgui.const.constants import APP_NAME, SEPARATOR from dzgui.const.constants import APP_NAME, SEPARATOR
from dzgui.util.keys import is_navkey from dzgui.util.keys import is_ctrl_mask, is_navkey
from dzgui.views.mixins.cursor_mixin import CursorMixin from dzgui.views.mixins.cursor_mixin import CursorMixin
import gi import gi
@@ -85,10 +85,17 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
model, rows = sel.get_selected_rows() model, rows = sel.get_selected_rows()
return [model[row] for row in rows] return [model[row] for row in rows]
def _on_keypress(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None: def _on_keypress(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> bool:
from dzgui.views.trees.tree_servers import ServerTreeView
# NOTE: suppress popup search and delegate binding to server view (cf. request_keyword_focus)
if not isinstance(treeview, ServerTreeView):
if is_ctrl_mask(event) and event.keyval == Gdk.KEY_f:
return True
if is_navkey(event.keyval): if is_navkey(event.keyval):
if self.get_model() is None: if self.get_model() is None:
return return True
if self.sel_blocked is False: if self.sel_blocked is False:
self.sel_blocked = True self.sel_blocked = True
self.controller.suppress_signal( self.controller.suppress_signal(
@@ -98,7 +105,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
True, True,
) )
self._vim_nav(event) self._vim_nav(event)
return return False
def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None: def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
""" """
+17 -1
View File
@@ -3,8 +3,8 @@ from typing import Any, TYPE_CHECKING
from dzgui.const.constants import APP_NAME, HEX_RED from dzgui.const.constants import APP_NAME, HEX_RED
from dzgui.const.enum import ContextMenuGroup from dzgui.const.enum import ContextMenuGroup
from dzgui.managers.mods import ModManager
from dzgui.util import strings, localize from dzgui.util import strings, localize
from dzgui.views.mixins.cursor_mixin import CursorMixin
from dzgui.views.mixins.context_mixin import ContextMixin from dzgui.views.mixins.context_mixin import ContextMixin
from dzgui.views.mixins.mods_mixin import ModsMixin from dzgui.views.mixins.mods_mixin import ModsMixin
from dzgui.views.trees.tree_base import TreeView from dzgui.views.trees.tree_base import TreeView
@@ -25,7 +25,10 @@ logger = logging.getLogger(APP_NAME)
class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore
def __init__(self, controller: "Controller") -> None: def __init__(self, controller: "Controller") -> None:
super().__init__(controller, menu=ContextMenuGroup.MOD) super().__init__(controller, menu=ContextMenuGroup.MOD)
self.controller = controller self.controller = controller
self.mod_man = ModManager(self, controller)
emitter = self.controller.get_emitter() emitter = self.controller.get_emitter()
emitter.connect("mods_updated", self._on_mods_updated) emitter.connect("mods_updated", self._on_mods_updated)
emitter.connect("mods_highlighted", self._on_mods_highlighted) emitter.connect("mods_highlighted", self._on_mods_highlighted)
@@ -57,6 +60,12 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore
self.connect("button-press-event", self.present_menu) self.connect("button-press-event", self.present_menu)
self.connect("key-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: def _on_mods_highlighted(self, emitter: "Emitter") -> None:
self.get_selection().unselect_all() self.get_selection().unselect_all()
@@ -106,3 +115,10 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore
val = model[it][3] val = model[it][3]
formatted = localize.number(val) formatted = localize.number(val)
cell.set_property("text", formatted) 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)