mirror of
https://github.com/aclist/dztui.git
synced 2026-11-06 18:10:58 +01:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20dab38fff | ||
|
|
1ddc4a5ff1 | ||
|
|
d605447fca | ||
|
|
94fe3f9dff | ||
|
|
a966993999 | ||
|
|
8ee3b36816 | ||
|
|
3af53775cd | ||
|
|
e2d6a462db |
No files matched your search
@@ -40,7 +40,6 @@
|
|||||||
- Preboot progress dialog
|
- Preboot progress dialog
|
||||||
- Choose to jump into splash screen instead of server
|
- Choose to jump into splash screen instead of server
|
||||||
- Collapsible connection panel
|
- Collapsible connection panel
|
||||||
- Play offline (load mods directly)
|
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
- Conform to PEP 440 versioning for beta versions
|
- Conform to PEP 440 versioning for beta versions
|
||||||
|
|||||||
+4
-13
@@ -87,18 +87,15 @@ def get_mod_size(path: Path) -> float:
|
|||||||
return size
|
return size
|
||||||
|
|
||||||
|
|
||||||
def get_custom_mods(path: Path) -> list[Any]:
|
def get_delimited_mods(steam_path: Path) -> list[Any]:
|
||||||
mods = get_local_mods(path)
|
workshop_path = get_local_mod_path(steam_path)
|
||||||
# TODO: error handling
|
mods = get_local_mods(workshop_path)
|
||||||
return parse_mods(mods)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_mods(mods: list[Path]) -> list[Any]:
|
|
||||||
clean = []
|
clean = []
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
mod_dir = mod.name
|
mod_dir = mod.name
|
||||||
symlink = _hash(mod_dir)
|
symlink = _hash(mod_dir)
|
||||||
# FIXME: malformed .cpp files could break this
|
# FIXME: malformed .cpp files could break this
|
||||||
|
# mention that mods may be downloading
|
||||||
meta = parse_meta(mod)
|
meta = parse_meta(mod)
|
||||||
if meta is None:
|
if meta is None:
|
||||||
continue
|
continue
|
||||||
@@ -109,12 +106,6 @@ def parse_mods(mods: list[Path]) -> list[Any]:
|
|||||||
return clean
|
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:
|
def get_missing_mods(local: list, remote: list) -> list:
|
||||||
return [mod for mod in remote if mod not in local]
|
return [mod for mod in remote if mod not in local]
|
||||||
|
|
||||||
|
|||||||
+3
-24
@@ -73,14 +73,9 @@ def get_local_signatures(version_file: Path) -> dict[str, int]:
|
|||||||
return hashes
|
return hashes
|
||||||
|
|
||||||
|
|
||||||
def enqueue_mod(mod: str, appid: int) -> None:
|
def enqueue_mod(client: str, mod: str, appid: int) -> None:
|
||||||
args = [
|
client_args = concat_bash_args(client)
|
||||||
"steam",
|
subprocess.Popen([*client_args, "+workshop_download_item", str(appid), mod])
|
||||||
f"steam://url/CommunityFilePage/{mod}+workshop_download_item",
|
|
||||||
str(appid),
|
|
||||||
mod,
|
|
||||||
]
|
|
||||||
subprocess.Popen(["/usr/bin/env", "bash", *args])
|
|
||||||
|
|
||||||
|
|
||||||
def get_needs_update(
|
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])
|
proc = subprocess.run([*client_args, *params])
|
||||||
return proc.returncode
|
return proc.returncode
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
def find_user_id(path: Path) -> str | None:
|
||||||
resolved_path = path / "config" / "loginusers.vdf"
|
resolved_path = path / "config" / "loginusers.vdf"
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ HEX_ORANGE = "#FFAC1C"
|
|||||||
CARET_DOWN = "go-down-symbolic"
|
CARET_DOWN = "go-down-symbolic"
|
||||||
CARET_UP = "go-up-symbolic"
|
CARET_UP = "go-up-symbolic"
|
||||||
CLIPBOARD = "edit-copy-symbolic"
|
CLIPBOARD = "edit-copy-symbolic"
|
||||||
FOLDER = "folder-symbolic"
|
|
||||||
ERROR = "dialog-error-symbolic"
|
ERROR = "dialog-error-symbolic"
|
||||||
HELP_BUBBLE = "help-about-symbolic"
|
HELP_BUBBLE = "help-about-symbolic"
|
||||||
INPUT_KEYBOARD = "input-keyboard-symbolic"
|
INPUT_KEYBOARD = "input-keyboard-symbolic"
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ class Preferences(EnumWithAttrs):
|
|||||||
|
|
||||||
|
|
||||||
class NotebookPage(EnumWithAttrs):
|
class NotebookPage(EnumWithAttrs):
|
||||||
OFFLINE = {"crumbs": strings.crumbs.offline, "statusbar": True}
|
|
||||||
CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": True}
|
CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": True}
|
||||||
DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False}
|
DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False}
|
||||||
HELP = {"crumbs": strings.crumbs._help, "statusbar": True}
|
HELP = {"crumbs": strings.crumbs._help, "statusbar": True}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository.Gtk import ListStore # noqa E402
|
from gi.repository.Gtk import ListStore # noqa E402
|
||||||
from gi.repository import Gdk, GObject # noqa E402
|
from gi.repository import Gdk, GObject # noqa E402
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.const.enum import NotebookPage, ServerTab
|
from dzgui.const.enum import NotebookPage, ServerTab
|
||||||
from dzgui.model.model_factory import FastInsertListStore
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: if servers fail to load, may leave dangling widgets waiting for a signal
|
# TODO: if servers fail to load, may leave dangling widgets waiting for a signal
|
||||||
@@ -154,7 +154,3 @@ class Emitter(GObject.GObject):
|
|||||||
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
|
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
|
||||||
def lan_page_initialized(self) -> None:
|
def lan_page_initialized(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object, str))
|
|
||||||
def custom_mods_loaded(self, store: "FastInsertListStore", folder: str) -> None:
|
|
||||||
pass
|
|
||||||
+10
-35
@@ -20,12 +20,13 @@ from dzgui.controllers.emitter import Emitter
|
|||||||
from dzgui.managers.config import ConfigManager
|
from dzgui.managers.config import ConfigManager
|
||||||
from dzgui.managers.connection import ConnectionManager
|
from dzgui.managers.connection import ConnectionManager
|
||||||
from dzgui.managers.contextmenu import ContextMenuManager
|
from dzgui.managers.contextmenu import ContextMenuManager
|
||||||
|
from dzgui.managers.mods import ModManager
|
||||||
from dzgui.managers.notes import NoteManager
|
from dzgui.managers.notes import NoteManager
|
||||||
from dzgui.model.servers import ServerModelManager
|
from dzgui.model.servers import ServerModelManager
|
||||||
from dzgui.util.diag import write_diagnostic
|
from dzgui.util.diag import write_diagnostic
|
||||||
from dzgui.util.format import format_player_count
|
from dzgui.util.format import format_player_count
|
||||||
from dzgui.util.open_links import open_user_workshop, open_workshop_page
|
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
|
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -36,7 +37,6 @@ from gi.repository import Gtk, Gdk, GLib, GObject # noqa E402
|
|||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pathlib import Path
|
|
||||||
from dzgui.api.servers import Record
|
from dzgui.api.servers import Record
|
||||||
from dzgui.const.enum import ServerTab
|
from dzgui.const.enum import ServerTab
|
||||||
from dzgui.managers.connection import Prerequisites
|
from dzgui.managers.connection import Prerequisites
|
||||||
@@ -47,13 +47,12 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.views.components.filter_panel import FilterPanel
|
from dzgui.views.components.filter_panel import FilterPanel
|
||||||
from dzgui.views.components.right_panel import RightPanel
|
from dzgui.views.components.right_panel import RightPanel
|
||||||
from dzgui.views.components.statusbar import Statusbar
|
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.options import Options
|
||||||
from dzgui.views.pages.preconnect import PreConnectionAssistant
|
from dzgui.views.pages.preconnect import PreConnectionAssistant
|
||||||
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, OfflineModTreeView
|
from dzgui.views.trees.tree_mods import ModTreeView
|
||||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
|
||||||
|
|
||||||
@@ -73,7 +72,6 @@ class AppNavigation:
|
|||||||
filters: "FilterPanel"
|
filters: "FilterPanel"
|
||||||
preconnect: "PreConnectionAssistant"
|
preconnect: "PreConnectionAssistant"
|
||||||
options: "Options"
|
options: "Options"
|
||||||
offline_loader: "OfflineLoader"
|
|
||||||
|
|
||||||
|
|
||||||
class Controller(GObject.GObject):
|
class Controller(GObject.GObject):
|
||||||
@@ -220,29 +218,19 @@ 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.mediator.modtreeview.load_mods()
|
self.mod_man = ModManager(self)
|
||||||
|
|
||||||
def uncolorize_mods(self) -> None:
|
def uncolorize_mods(self) -> None:
|
||||||
mod_man = self.mediator.modtreeview.get_mod_man()
|
self.mod_man.uncolorize_mods()
|
||||||
mod_man.uncolorize_mods()
|
|
||||||
|
|
||||||
def highlight_stale(self) -> None:
|
def highlight_stale(self) -> None:
|
||||||
mod_man = self.mediator.modtreeview.get_mod_man()
|
self.mod_man.highlight_stale()
|
||||||
mod_man.highlight_stale()
|
|
||||||
|
|
||||||
def toggle_mod_selection(self, state: bool) -> None:
|
def toggle_mod_selection(self, state: bool) -> None:
|
||||||
mod_man = self.mediator.modtreeview.get_mod_man()
|
self.mod_man.toggle_mod_selection(state)
|
||||||
mod_man.toggle_mod_selection(state)
|
|
||||||
|
|
||||||
def delete_mods(
|
def delete_mods(self) -> None:
|
||||||
self, treeview: Union["ModTreeView", "OfflineModTreeView", None] = None
|
self.mod_man.delete_mods()
|
||||||
) -> 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()
|
||||||
@@ -269,8 +257,7 @@ class Controller(GObject.GObject):
|
|||||||
dialog.run()
|
dialog.run()
|
||||||
|
|
||||||
def select_colorized(self) -> None:
|
def select_colorized(self) -> None:
|
||||||
mod_man = self.mediator.modtreeview.get_mod_man()
|
self.mod_man.select_colorized()
|
||||||
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()
|
||||||
@@ -301,12 +288,6 @@ class Controller(GObject.GObject):
|
|||||||
dialog = ExceptionDialog(self, str(e))
|
dialog = ExceptionDialog(self, str(e))
|
||||||
dialog.run()
|
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:
|
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)
|
||||||
|
|
||||||
@@ -552,9 +533,3 @@ class Controller(GObject.GObject):
|
|||||||
self.clear_cancel_event()
|
self.clear_cancel_event()
|
||||||
return True
|
return True
|
||||||
return False
|
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)
|
|
||||||
+1
-2
@@ -6,7 +6,6 @@ from pathlib import Path
|
|||||||
from dzgui.config.query import lookup
|
from dzgui.config.query import lookup
|
||||||
from dzgui.const.constants import APPID_DAYZ, APP_NAME, LIBRARYFOLDERS_PATH
|
from dzgui.const.constants import APPID_DAYZ, APP_NAME, LIBRARYFOLDERS_PATH
|
||||||
from dzgui.const.enum import Preferences
|
from dzgui.const.enum import Preferences
|
||||||
from dzgui.strings.errors import dayz_not_found
|
|
||||||
from dzgui.views.dialogs.early_alert import EarlyAlertDialog
|
from dzgui.views.dialogs.early_alert import EarlyAlertDialog
|
||||||
|
|
||||||
import dzgui.api.pefile as PeFile
|
import dzgui.api.pefile as PeFile
|
||||||
@@ -20,5 +19,5 @@ def is_dayz_installed(config: Path) -> None:
|
|||||||
PeFile.get_app_path(Path(path) / LIBRARYFOLDERS_PATH, APPID_DAYZ)
|
PeFile.get_app_path(Path(path) / LIBRARYFOLDERS_PATH, APPID_DAYZ)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
EarlyAlertDialog(dayz_not_found)
|
EarlyAlertDialog(str(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -88,6 +88,7 @@ class ConnectionManager:
|
|||||||
self.record: Record
|
self.record: Record
|
||||||
self.workshop: Path
|
self.workshop: Path
|
||||||
|
|
||||||
|
self.client: str
|
||||||
self.remote_mod_ids: list[str] = []
|
self.remote_mod_ids: list[str] = []
|
||||||
self.missing_mods: list[tuple[str, str, int, int]] = []
|
self.missing_mods: list[tuple[str, str, int, int]] = []
|
||||||
|
|
||||||
@@ -171,6 +172,7 @@ class ConnectionManager:
|
|||||||
client = self.controller.query_config(Preferences.CLIENT)
|
client = self.controller.query_config(Preferences.CLIENT)
|
||||||
running = is_steam_running(client)
|
running = is_steam_running(client)
|
||||||
steam_proc = SteamProcess(client_name, running)
|
steam_proc = SteamProcess(client_name, running)
|
||||||
|
self.client = client
|
||||||
|
|
||||||
game_mode = prefs.is_game_mode
|
game_mode = prefs.is_game_mode
|
||||||
|
|
||||||
@@ -263,11 +265,10 @@ class ConnectionManager:
|
|||||||
def _connect_steam(self, menu_only: bool) -> None:
|
def _connect_steam(self, menu_only: bool) -> None:
|
||||||
addr = f"{self.record.ip}:{self.record.gameport}"
|
addr = f"{self.record.ip}:{self.record.gameport}"
|
||||||
playername = self.controller.query_config(Preferences.NAME)
|
playername = self.controller.query_config(Preferences.NAME)
|
||||||
client = self.controller.query_config(Preferences.CLIENT)
|
|
||||||
if menu_only:
|
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:
|
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:
|
if rc != 0:
|
||||||
# TODO: log/pop the error
|
# TODO: log/pop the error
|
||||||
func = StoredFunc(self.controller.update_status)
|
func = StoredFunc(self.controller.update_status)
|
||||||
@@ -298,7 +299,8 @@ class ConnectionManager:
|
|||||||
for title, mod, stamp, size in self.missing_mods:
|
for title, mod, stamp, size in self.missing_mods:
|
||||||
if self.controller.is_cancel_pending():
|
if self.controller.is_cancel_pending():
|
||||||
return
|
return
|
||||||
enqueue_mod(mod, self.appid)
|
enqueue_mod(self.client, mod, self.appid)
|
||||||
|
# NOTE: prevents rate limiting
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
if raise_window is True:
|
if raise_window is True:
|
||||||
|
|||||||
@@ -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, OfflineModTreeView
|
from dzgui.views.trees.tree_mods import ModTreeView
|
||||||
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,13 +27,7 @@ if TYPE_CHECKING:
|
|||||||
class ContextMenuManager:
|
class ContextMenuManager:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
treeview: (
|
treeview: LogTreeView | ModTreeView | ServerTreeView | ServerModTreeView,
|
||||||
LogTreeView
|
|
||||||
| ModTreeView
|
|
||||||
| ServerTreeView
|
|
||||||
| ServerModTreeView
|
|
||||||
| OfflineModTreeView
|
|
||||||
),
|
|
||||||
controller: "Controller",
|
controller: "Controller",
|
||||||
) -> None:
|
) -> None:
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
@@ -78,10 +72,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, OfflineModTreeView)):
|
if isinstance(self.treeview, ModTreeView):
|
||||||
match action:
|
match action:
|
||||||
case ContextMenu.DELETE_MOD:
|
case ContextMenu.DELETE_MOD:
|
||||||
self.controller.delete_mods(self.treeview)
|
self.controller.delete_mods()
|
||||||
case ContextMenu.OPEN_WORKSHOP:
|
case ContextMenu.OPEN_WORKSHOP:
|
||||||
self.open_mod_page()
|
self.open_mod_page()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import logging
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import Any, 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, tree: Gtk.TreeView, controller: "Controller") -> None:
|
def __init__(self, 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 load_mods(self) -> None:
|
def _get_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()
|
||||||
@@ -61,12 +61,12 @@ class ModManager:
|
|||||||
self.thread_man.set_cleanup_func(func)
|
self.thread_man.set_cleanup_func(func)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.store = ModelFactory().make_mod_store()
|
func = StoredFunc(self._on_mods_loaded, mods)
|
||||||
self.store.extend(mods)
|
|
||||||
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) -> 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)
|
self.treeview.set_model(self.store)
|
||||||
msg = self.format_mod_statusbar()
|
msg = self.format_mod_statusbar()
|
||||||
total_mods = len(self.store)
|
total_mods = len(self.store)
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
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, StoredFunc, 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.emitter = controller.get_emitter()
|
|
||||||
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, local_mods: list[str], folder: str) -> "FastInsertListStore":
|
|
||||||
mods = get_custom_mods(Path(folder))
|
|
||||||
store = ModelFactory().make_mod_store()
|
|
||||||
store.extend(mods)
|
|
||||||
|
|
||||||
has_duplicates = False
|
|
||||||
for row in store:
|
|
||||||
if row[1] in local_mods:
|
|
||||||
row[-1]=True
|
|
||||||
has_duplicates = True
|
|
||||||
|
|
||||||
func = StoredFunc(self.emitter.emit, "custom_mods_loaded", store, folder, has_duplicates)
|
|
||||||
self.thread_man.set_cleanup_func(func)
|
|
||||||
# TODO: return store from cleanup function
|
|
||||||
|
|
||||||
def setup(
|
|
||||||
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)
|
|
||||||
@@ -165,6 +165,3 @@ 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]
|
|
||||||
@@ -3,4 +3,3 @@ api_validation_error = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
api_popover = "API key validation failed."
|
api_popover = "API key validation failed."
|
||||||
dayz_not_found = "Could not find DayZ and/or Steam: are they installed?"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
selectable_tree = "Ctrl-click to select multiple; Shift-click to select a range."
|
|
||||||
@@ -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,7 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from dzgui.util.localize import number
|
from dzgui.util.localize import number
|
||||||
from dzgui.strings.generic import selectable_tree
|
|
||||||
from dzgui.util.strings import no_mods, no_servers, workshop
|
from dzgui.util.strings import no_mods, no_servers, workshop
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -47,7 +46,8 @@ def format_mods(size: int, mods: int) -> str:
|
|||||||
return no_mods
|
return no_mods
|
||||||
l_size = number(size)
|
l_size = number(size)
|
||||||
plural = pluralize("mods", mods)
|
plural = pluralize("mods", mods)
|
||||||
suffix = selectable_tree
|
# TODO: strings
|
||||||
|
suffix = "Ctrl-click to select multiple; Shift-click to select a range."
|
||||||
return f"Found {mods:n} {plural} taking up {l_size} MiB. {suffix}"
|
return f"Found {mods:n} {plural} taking up {l_size} MiB. {suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ connect = "Connect"
|
|||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
server_mod_cols = ["Mod", "ID", "Installed"]
|
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"]
|
log_cols = ["Timestamp", "Flag", "Traceback", "Message"]
|
||||||
browser_cols = [
|
browser_cols = [
|
||||||
"Name",
|
"Name",
|
||||||
@@ -273,7 +273,6 @@ class Crumbs:
|
|||||||
thanks: str
|
thanks: str
|
||||||
developers: str
|
developers: str
|
||||||
default: str
|
default: str
|
||||||
offline: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
@@ -463,7 +462,6 @@ crumbs = Crumbs(
|
|||||||
thanks="Help > Special thanks",
|
thanks="Help > Special thanks",
|
||||||
developers="Options > Developers",
|
developers="Options > Developers",
|
||||||
default="Servers > ",
|
default="Servers > ",
|
||||||
offline="Mods > Play offline"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
no_mods = "No local mods found."
|
no_mods = "No local mods found."
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ 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.options import Options
|
from dzgui.views.pages.options import Options
|
||||||
from dzgui.views.pages.preconnect import PreConnectionAssistant
|
from dzgui.views.pages.preconnect import PreConnectionAssistant
|
||||||
from dzgui.views.pages.servers import ServerNotebook
|
from dzgui.views.pages.servers import ServerNotebook
|
||||||
@@ -112,11 +110,8 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
|
|||||||
|
|
||||||
self.connection = PreConnectionAssistant(MainController)
|
self.connection = PreConnectionAssistant(MainController)
|
||||||
|
|
||||||
self.offline = OfflineLoader(MainController)
|
|
||||||
|
|
||||||
self.pages = {
|
self.pages = {
|
||||||
self.help: NotebookPage.HELP,
|
self.help: NotebookPage.HELP,
|
||||||
self.offline: NotebookPage.OFFLINE,
|
|
||||||
self.clog: NotebookPage.CHANGELOG,
|
self.clog: NotebookPage.CHANGELOG,
|
||||||
self.keys: NotebookPage.KEYS,
|
self.keys: NotebookPage.KEYS,
|
||||||
self.settings: NotebookPage.OPTIONS,
|
self.settings: NotebookPage.OPTIONS,
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
from typing import Self
|
|
||||||
from dzgui.util import css
|
from dzgui.util import css
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -7,23 +6,16 @@ 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, heading: str = "") -> None:
|
def __init__(self, widget: Gtk.Widget, heading: str) -> None:
|
||||||
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
self.label = Gtk.Label(label=heading)
|
label = Gtk.Label(label=heading)
|
||||||
self.label.set_halign(Gtk.Align.START)
|
label.set_halign(Gtk.Align.START)
|
||||||
css.add_class(self.label, "settings-subheading")
|
css.add_class(label, "settings-subheading")
|
||||||
|
|
||||||
self.frame = Gtk.Frame(hexpand=True)
|
frame = Gtk.Frame(hexpand=True)
|
||||||
self.frame.set_label_widget(self.label)
|
frame.add(widget)
|
||||||
|
frame.set_label_widget(label)
|
||||||
|
|
||||||
self.add(self.frame)
|
self.add(frame)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def new_with_widget_and_label(cls, widget: Gtk.Widget, label: str) -> Self:
|
|
||||||
n = cls()
|
|
||||||
n.frame.add(widget)
|
|
||||||
n.label.set_label(label)
|
|
||||||
return n
|
|
||||||
@@ -25,6 +25,8 @@ class Statusbar(Gtk.Grid):
|
|||||||
self.controller.register_widget("statusbar", self)
|
self.controller.register_widget("statusbar", self)
|
||||||
self.emitter = controller.get_emitter()
|
self.emitter = controller.get_emitter()
|
||||||
|
|
||||||
|
self.prior_enum: NotebookPage
|
||||||
|
self.prior_status: str
|
||||||
self.playercount = ""
|
self.playercount = ""
|
||||||
self.statusbar = Gtk.Statusbar()
|
self.statusbar = Gtk.Statusbar()
|
||||||
|
|
||||||
@@ -69,8 +71,8 @@ class Statusbar(Gtk.Grid):
|
|||||||
NotebookPage.CHANGELOG,
|
NotebookPage.CHANGELOG,
|
||||||
NotebookPage.LOG,
|
NotebookPage.LOG,
|
||||||
NotebookPage.CONNECTION,
|
NotebookPage.CONNECTION,
|
||||||
NotebookPage.OFFLINE,
|
|
||||||
):
|
):
|
||||||
|
self.prior_enum = enum
|
||||||
self.set_by_context(enum, esc_to_return)
|
self.set_by_context(enum, esc_to_return)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -80,8 +82,9 @@ class Statusbar(Gtk.Grid):
|
|||||||
case NotebookPage.KEYS:
|
case NotebookPage.KEYS:
|
||||||
bar = question_to_return
|
bar = question_to_return
|
||||||
case _:
|
case _:
|
||||||
return
|
bar = self.prior_status
|
||||||
|
|
||||||
|
self.prior_enum = enum
|
||||||
self.set_by_context(enum, bar)
|
self.set_by_context(enum, bar)
|
||||||
|
|
||||||
def _on_server_row_changed(self, statusbar: Self) -> None:
|
def _on_server_row_changed(self, statusbar: Self) -> None:
|
||||||
@@ -97,6 +100,10 @@ class Statusbar(Gtk.Grid):
|
|||||||
context: Union["ServerTab", NotebookPage],
|
context: Union["ServerTab", NotebookPage],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.spinner.stop()
|
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
|
# FIXME: CalcDist is being called when table is not loaded
|
||||||
if dist is None:
|
if dist is None:
|
||||||
return
|
return
|
||||||
@@ -121,6 +128,8 @@ class Statusbar(Gtk.Grid):
|
|||||||
def set_by_context(
|
def set_by_context(
|
||||||
self, context: Union[NotebookPage, "ServerTab"], string: str
|
self, context: Union[NotebookPage, "ServerTab"], string: str
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if context != NotebookPage.KEYS:
|
||||||
|
self.prior_status = string
|
||||||
meta = self.statusbar.get_context_id(str(context))
|
meta = self.statusbar.get_context_id(str(context))
|
||||||
self.statusbar.push(meta, string)
|
self.statusbar.push(meta, string)
|
||||||
self.set_cache(string)
|
self.set_cache(string)
|
||||||
|
|||||||
@@ -33,28 +33,3 @@ class FilePicker(Gtk.FileChooserDialog):
|
|||||||
self.destroy()
|
self.destroy()
|
||||||
return Path(file)
|
return Path(file)
|
||||||
return None
|
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
|
|
||||||
@@ -44,8 +44,6 @@ 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 False
|
|
||||||
item = self._process_dynamic_row(row)
|
item = self._process_dynamic_row(row)
|
||||||
self.context_menu.append(item)
|
self.context_menu.append(item)
|
||||||
|
|
||||||
|
|||||||
@@ -11,36 +11,21 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.controllers.mc import Controller
|
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()
|
class Mods(NoOverlayScrolledWindow):
|
||||||
self.tree = ModTreeView(controller)
|
def __init__(self, controller: "Controller") -> None:
|
||||||
self.tree.set_vexpand(True)
|
super().__init__()
|
||||||
self.box.add(self.tree)
|
|
||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.controller.register_widget("modtreeview", self.tree)
|
|
||||||
self.emitter = controller.get_emitter()
|
self.emitter = controller.get_emitter()
|
||||||
|
self.tree = ModTreeView(controller)
|
||||||
|
|
||||||
self.offline_button = Gtk.Button(
|
self.add(self.tree)
|
||||||
label="Play offline",
|
self.controller.register_widget("modtreeview", self.tree)
|
||||||
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.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:
|
|
||||||
self.controller.open_offline()
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -1,307 +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,
|
|
||||||
FOLDER,
|
|
||||||
)
|
|
||||||
from dzgui.const.enum import NotebookPage, Preferences
|
|
||||||
from dzgui.managers.offline import OfflineManager
|
|
||||||
from dzgui.strings import generic, offline
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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: alternate class for left-aligned icons
|
|
||||||
self.button = IconTextButton(FOLDER, btn_label)
|
|
||||||
self.button.set_halign(Gtk.Align.START)
|
|
||||||
self.button.set_image_position(Gtk.PositionType.LEFT)
|
|
||||||
|
|
||||||
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_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.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.tree_vbox.hide()
|
|
||||||
|
|
||||||
def get_tree(self) -> OfflineModTreeView:
|
|
||||||
return self.tree
|
|
||||||
|
|
||||||
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:
|
|
||||||
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()
|
|
||||||
if len(rows) == 0:
|
|
||||||
status = generic.selectable_tree
|
|
||||||
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):
|
|
||||||
def __init__(self, parent: Gtk.Widget, controller: "Controller", heading: str) -> None:
|
|
||||||
super().__init__(controller, heading)
|
|
||||||
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
self.controller = controller
|
|
||||||
self.emitter = controller.get_emitter()
|
|
||||||
|
|
||||||
# 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(self.custom_hbox)
|
|
||||||
|
|
||||||
self.emitter.connect("custom_mods_loaded", self._on_custom_mods_loaded)
|
|
||||||
|
|
||||||
|
|
||||||
def _on_custom_mods_loaded(self, store: "FastInsertListStore", folder: str, has_duplicates: bool) -> None:
|
|
||||||
if len(store) == 0:
|
|
||||||
# TODO: pop error dialog area
|
|
||||||
# block button access
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.custom_hbox.set_label(folder)
|
|
||||||
self.tree.set_model(store)
|
|
||||||
self.tree_vbox.show()
|
|
||||||
|
|
||||||
if has_duplicates:
|
|
||||||
# TODO: pop relevant error
|
|
||||||
# duplicates should block button access
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _on_custom_button_clicked(self, button: Gtk.Button) -> None:
|
|
||||||
self.parent.parse_mods()
|
|
||||||
|
|
||||||
def get_mods(self) -> list[str]:
|
|
||||||
rows = self.tree.get_selection().get_selected_rows()
|
|
||||||
dirs = [str(col[1]) for col in rows]
|
|
||||||
return dirs
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
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: 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 parse_mods(self) -> None:
|
|
||||||
# TODO:
|
|
||||||
local_mods = self.local_frame.get_mods()
|
|
||||||
folder = self.controller.set_custom_folder()
|
|
||||||
if folder is not None:
|
|
||||||
# TODO:
|
|
||||||
self.offline_man.parse_custom_mods(local_mods, folder)
|
|
||||||
|
|
||||||
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> None:
|
|
||||||
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
|
|
||||||
# NOTE: there may be no local mods
|
|
||||||
# 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:
|
|
||||||
# TODO; block button access if no mods are selected
|
|
||||||
# TODO: consider hooking up to emitter and changing button state whenever mods are selected
|
|
||||||
|
|
||||||
# appid = self.radio_frame.get_appid()
|
|
||||||
# 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
|
|
||||||
@@ -187,14 +187,12 @@ 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 pair in [
|
for frame in [
|
||||||
(api_box, strings.options.api_keys),
|
HeadingFrame(api_box, strings.options.api_keys),
|
||||||
(prefs_grid, strings.options.prefs),
|
HeadingFrame(prefs_grid, strings.options.prefs),
|
||||||
(mods_grid, strings.options.mods),
|
HeadingFrame(mods_grid, strings.options.mods),
|
||||||
(version_grid, strings.options.version),
|
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)
|
grid.attach(frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ class PreConnectionAssistant(Gtk.Box):
|
|||||||
self.progress_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
self.progress_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
||||||
self.progress_box.add(self.mod_count)
|
self.progress_box.add(self.mod_count)
|
||||||
|
|
||||||
self.scrolled = Gtk.ScrolledWindow(margin_end=10)
|
self.scrolled = Gtk.ScrolledWindow()
|
||||||
self.scrolled.add(self.tree)
|
self.scrolled.add(self.tree)
|
||||||
self.scrolled.set_size_request(600, 400)
|
self.scrolled.set_size_request(600, 400)
|
||||||
|
|
||||||
@@ -166,9 +166,7 @@ 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.new_with_widget_and_label(
|
self.tree_frame = HeadingFrame(self.tree_box, preconnect.mods)
|
||||||
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)
|
||||||
@@ -177,9 +175,7 @@ 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.new_with_widget_and_label(
|
self.warning_frame = HeadingFrame(self.warning_box, preconnect.warnings)
|
||||||
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)
|
||||||
@@ -187,9 +183,7 @@ 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.new_with_widget_and_label(
|
self.error_frame = HeadingFrame(self.error_box, preconnect.errors)
|
||||||
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)
|
||||||
@@ -333,10 +327,7 @@ class PreConnectionAssistant(Gtk.Box):
|
|||||||
self.connect_last.hide()
|
self.connect_last.hide()
|
||||||
|
|
||||||
self._process_warnings(prereqs)
|
self._process_warnings(prereqs)
|
||||||
if self.tree.is_visible():
|
self.tree.grab_focus()
|
||||||
self.tree.grab_focus()
|
|
||||||
else:
|
|
||||||
self.grab_focus()
|
|
||||||
|
|
||||||
def mark_finished(self) -> None:
|
def mark_finished(self) -> None:
|
||||||
self.mod_count.set_label(preconnect.all_updated)
|
self.mod_count.set_label(preconnect.all_updated)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ 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.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
|
||||||
@@ -25,10 +24,7 @@ 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)
|
||||||
@@ -60,12 +56,6 @@ 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()
|
||||||
|
|
||||||
@@ -115,10 +105,3 @@ 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)
|
|
||||||
+1
-1
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
|||||||
authors = [
|
authors = [
|
||||||
{name = "aclist"}
|
{name = "aclist"}
|
||||||
]
|
]
|
||||||
version = "7.0.0b12"
|
version = "7.0.0b11"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
Reference in new issue
Block a user