feat: launch offline (WIP)

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

View File

@ -54,7 +54,7 @@ if TYPE_CHECKING:
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
from dzgui.views.trees.tree_mods import ModTreeView, OfflineModTreeView
from dzgui.views.trees.tree_servers import ServerTreeView
@ -221,19 +221,29 @@ class Controller(GObject.GObject):
open_user_workshop(uid, client)
def load_mods(self) -> None:
self.mod_man = ModManager(self)
self.mediator.modtreeview.load_mods()
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:
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:
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:
self.mod_man.delete_mods()
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 get_mod_store(self) -> Gtk.TreeModel | None:
return self.mediator.modtreeview.get_model()
@ -260,7 +270,8 @@ class Controller(GObject.GObject):
dialog.run()
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]]:
servers = self.get_servers()
@ -293,7 +304,9 @@ class Controller(GObject.GObject):
def set_custom_folder(self) -> Union["Path", None]:
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:
self.config_man.update_api_key(key, text)
@ -541,6 +554,8 @@ class Controller(GObject.GObject):
return True
return False
def open_offline(self, mods: Gtk.TreeModel | None) -> None:
def open_offline(self) -> None:
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)

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
from dzgui.views.trees.tree_mods import ModTreeView, OfflineModTreeView
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,7 +27,13 @@ if TYPE_CHECKING:
class ContextMenuManager:
def __init__(
self,
treeview: LogTreeView | ModTreeView | ServerTreeView | ServerModTreeView,
treeview: (
LogTreeView
| ModTreeView
| ServerTreeView
| ServerModTreeView
| OfflineModTreeView
),
controller: "Controller",
) -> None:
self.controller = controller
@ -72,10 +78,10 @@ class ContextMenuManager:
case ContextMenu.SHOW_MODS:
self.controller.get_modlist(record)
if isinstance(self.treeview, ModTreeView):
if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
match action:
case ContextMenu.DELETE_MOD:
self.controller.delete_mods()
self.controller.delete_mods(self.treeview)
case ContextMenu.OPEN_WORKSHOP:
self.open_mod_page()

View File

@ -2,7 +2,7 @@ import logging
import shutil
from pathlib import Path
from typing import Any, TYPE_CHECKING
from typing import 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, controller: "Controller") -> None:
def __init__(self, tree: Gtk.TreeView, controller: "Controller") -> None:
self.treeview = tree
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 _get_mods(self) -> None:
def load_mods(self) -> None:
mods = get_delimited_mods(self.path)
if len(mods) < 1:
msg = self.format_mod_statusbar()
@ -66,8 +66,6 @@ class ModManager:
func = StoredFunc(self._on_mods_loaded)
self.thread_man.set_cleanup_func(func)
# def _on_mods_loaded(self, mods: list[list[Any]]) -> None:
def _on_mods_loaded(self) -> None:
self.treeview.set_model(self.store)
msg = self.format_mod_statusbar()

View File

@ -165,3 +165,6 @@ 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]

View File

@ -1 +1,11 @@
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"

View File

@ -64,7 +64,7 @@ connect = "Connect"
# Columns
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"]
browser_cols = [
"Name",

View File

@ -23,6 +23,7 @@ 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

View File

@ -6,16 +6,23 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa
# TODO: subclass Gtk.Frame directly
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)
label = Gtk.Label(label=heading)
label.set_halign(Gtk.Align.START)
css.add_class(label, "settings-subheading")
self.label = Gtk.Label(label=heading)
self.label.set_halign(Gtk.Align.START)
css.add_class(self.label, "settings-subheading")
frame = Gtk.Frame(hexpand=True)
frame.add(widget)
frame.set_label_widget(label)
self.frame = Gtk.Frame(hexpand=True)
self.frame.set_label_widget(self.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

View File

@ -44,6 +44,8 @@ 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)

View File

@ -11,12 +11,14 @@ if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
class Mods(NoOverlayScrolledWindow):
class Mods(Gtk.Box):
def __init__(self, controller: "Controller") -> None:
super().__init__()
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.box = NoOverlayScrolledWindow()
self.tree = ModTreeView(controller)
self.tree.set_vexpand(True)
self.box.add(self.tree)
self.controller = controller
self.controller.register_widget("modtreeview", self.tree)
@ -30,18 +32,14 @@ class Mods(NoOverlayScrolledWindow):
)
self.offline_button.connect("clicked", self._on_offline_clicked)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.box.add(self.offline_button)
self.box.add(self.tree)
self.add(self.offline_button)
self.add(self.box)
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(self.tree.get_model())
# TODO: add offline page
self.controller.open_offline()
def _on_unmap(self, widget: Self) -> None:
self.emitter.emit("mod_page_toggled", False)

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
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.trees.tree_mods import ModTreeView
from dzgui.views.trees.tree_mods import OfflineModTreeView
import gi
@ -13,9 +18,144 @@ from gi.repository import Gtk, Gdk # noqa
if TYPE_CHECKING:
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):
@ -26,127 +166,80 @@ class OfflineLoader(Gtk.Box):
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.register_widget("offline_loader", self)
label = Gtk.Label(label=offline.heading)
label.set_halign(Gtk.Align.CENTER)
css.add_class(label, "page-heading")
self.add(label)
self.add(PageHeading(offline.heading))
# 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: use statusbar msg from format.format_mods
# TODO: strings
self.custom_button = Gtk.Button(
label="Set custom mod folder", halign=Gtk.Align.START
self.local_frame = ModFrame(controller, offline.local_frame)
self.custom_frame = CustomModFrame(controller, offline.custom_frame)
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.custom_frame = HeadingFrame(self.custom_mods_box, "Custom mods")
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")
self.radio_frame = RadioFrame(controller)
# HeadingFrame.new_with_widget_and_label(self.radio_box, offline.version)
# TODO: abstraction
self.scrollable = Gtk.ScrolledWindow(
vexpand=True, propagate_natural_height=True
)
# TODO: use same button anchoring logic as preconnect dialog
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.box.add(self.local_frame)
self.box.add(self.custom_frame)
self.box.add(self.mission_frame)
self.box.add(self.radio_frame)
self.scrollable.add(self.box)
self.content_box = VBox()
self.content_box.set_spacing(5)
self.content_box.extend(
[
self.local_frame,
self.custom_frame,
self.mission_frame,
self.radio_frame,
]
)
self.scrollable.add(self.content_box)
self.add(self.scrollable)
# 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.ok = Gtk.Button()
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])
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> None:
# TODO: consider dropping this
if event.keyval == Gdk.KEY_Escape:
self.back.emit("clicked")
def _on_selection_changed(self, sel: Gtk.TreeSelection) -> None:
model, rows = sel.get_selected_rows()
# TODO: update sub-statusbar with selected mods
print(len(rows))
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 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:
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)
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
"""
create symlinks if there are custom mods
cf. rebuild_symlinks()
"""

View File

@ -187,12 +187,14 @@ class Options(Gtk.Box):
if prefs.is_debug:
grid.attach(developers, 1, 0, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
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),
for pair in [
(api_box, strings.options.api_keys),
(prefs_grid, strings.options.prefs),
(mods_grid, strings.options.mods),
(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

View File

@ -166,7 +166,9 @@ class PreConnectionAssistant(Gtk.Box):
self.mods_placeholder = Placeholder("This server has no mods.")
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
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_box.add(self.warning_tree)
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_tree = MaskedTree(ERROR)
@ -183,7 +187,9 @@ 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(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.add(self.title)

View File

@ -4,7 +4,7 @@ from typing import Any, Optional, TYPE_CHECKING
from warnings import deprecated
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
import gi
@ -85,10 +85,17 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
model, rows = sel.get_selected_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 self.get_model() is None:
return
return True
if self.sel_blocked is False:
self.sel_blocked = True
self.controller.suppress_signal(
@ -98,7 +105,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
True,
)
self._vim_nav(event)
return
return False
def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
"""

View File

@ -3,8 +3,8 @@ 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.cursor_mixin import CursorMixin
from dzgui.views.mixins.context_mixin import ContextMixin
from dzgui.views.mixins.mods_mixin import ModsMixin
from dzgui.views.trees.tree_base import TreeView
@ -25,7 +25,10 @@ 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)
@ -57,6 +60,12 @@ 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()
@ -106,3 +115,10 @@ 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)