mirror of
https://github.com/aclist/dztui.git
synced 2026-10-30 14:42:54 +01:00
Compare commits
60
Commits
7.0.0b20
...
5532e4e267
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5532e4e267 | ||
|
|
120703e702 | ||
|
|
b31896387a | ||
|
|
e5bb25cd60 | ||
|
|
e59b7e01fd | ||
|
|
7994bf988c | ||
|
|
90b1c875df | ||
|
|
b61e806009 | ||
|
|
b46d0614c9 | ||
|
|
c73864d545 | ||
|
|
314fed559c | ||
|
|
1c346a6c3c | ||
|
|
65e11ddd03 | ||
|
|
a1c0592c7d | ||
|
|
22c82677cb | ||
|
|
c5b41346ca | ||
|
|
c2fc9b2cca | ||
|
|
ba9d6e1d3b | ||
|
|
be6ab2c9cb | ||
|
|
2413dd1f14 | ||
|
|
9d8d37e110 | ||
|
|
fc28d419bf | ||
|
|
095af395f9 | ||
|
|
4ada06d305 | ||
|
|
5a0ab92985 | ||
|
|
f2501871ee | ||
|
|
cf70182733 | ||
|
|
42feeae8d0 | ||
|
|
28cd6b8c45 | ||
|
|
81e327fb35 | ||
|
|
37e247d1d9 | ||
|
|
286e3f2d80 | ||
|
|
2ef27a55d8 | ||
|
|
d0ac09ffe5 | ||
|
|
c0015c05ec | ||
|
|
682b5bb4c0 | ||
|
|
3551877192 | ||
|
|
b22aba8593 | ||
|
|
f85f1ef2a5 | ||
|
|
010d7984b6 | ||
|
|
70e1e9e740 | ||
|
|
734af7cf22 | ||
|
|
d8f4977ffe | ||
|
|
c9fd00f938 | ||
|
|
f4db1484d8 | ||
|
|
99ef9a31b9 | ||
|
|
4c392dc571 | ||
|
|
155ffea15d | ||
|
|
57d231331d | ||
|
|
d5fe3c35dc | ||
|
|
3cf547e903 | ||
|
|
bd0bfcb03d | ||
|
|
4f6c583294 | ||
|
|
5e5a6b4eaf | ||
|
|
ff59d6dc35 | ||
|
|
1c154a709d | ||
|
|
fb47564618 | ||
|
|
30e309442b | ||
|
|
3e4ed95908 | ||
|
|
93edbac5b0 |
No files matched your search
+5
-1
@@ -139,6 +139,9 @@ def get_remote_signatures(mods: list[str]) -> list[tuple[str, str, int, int]]:
|
|||||||
j = r.json()
|
j = r.json()
|
||||||
rows = j["response"]["publishedfiledetails"]
|
rows = j["response"]["publishedfiledetails"]
|
||||||
for row in rows:
|
for row in rows:
|
||||||
|
# NOTE: not a valid published file
|
||||||
|
if row["result"] == 9:
|
||||||
|
continue
|
||||||
title = str(row["title"])
|
title = str(row["title"])
|
||||||
_id = str(row["publishedfileid"])
|
_id = str(row["publishedfileid"])
|
||||||
time = int(row["time_updated"])
|
time = int(row["time_updated"])
|
||||||
@@ -205,9 +208,11 @@ def launch_offline(
|
|||||||
proc = subprocess.run([*client_args, *params])
|
proc = subprocess.run([*client_args, *params])
|
||||||
return proc.returncode
|
return proc.returncode
|
||||||
|
|
||||||
|
|
||||||
def find_loginusers(path: Path) -> Path:
|
def find_loginusers(path: Path) -> Path:
|
||||||
return path / "config" / "loginusers.vdf"
|
return path / "config" / "loginusers.vdf"
|
||||||
|
|
||||||
|
|
||||||
def find_user_id(path: Path) -> str | None:
|
def find_user_id(path: Path) -> str | None:
|
||||||
resolved_path = find_loginusers(path)
|
resolved_path = find_loginusers(path)
|
||||||
try:
|
try:
|
||||||
@@ -226,7 +231,6 @@ def find_user_id(path: Path) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def find_user_id_32(path: Path) -> int:
|
def find_user_id_32(path: Path) -> int:
|
||||||
uid = find_user_id(path)
|
uid = find_user_id(path)
|
||||||
if uid is None:
|
if uid is None:
|
||||||
|
|||||||
+2
-1
@@ -18,10 +18,10 @@ from dzgui.init.flock import lock_acquire
|
|||||||
from dzgui.init.prereqs import has_steam_client
|
from dzgui.init.prereqs import has_steam_client
|
||||||
from dzgui.strings import boot
|
from dzgui.strings import boot
|
||||||
|
|
||||||
# from dzgui.util.map_count import get_map_count
|
|
||||||
from dzgui.util.deck import is_steam_deck, is_game_mode
|
from dzgui.util.deck import is_steam_deck, is_game_mode
|
||||||
from dzgui.util.dirs import make_parents
|
from dzgui.util.dirs import make_parents
|
||||||
from dzgui.util.localize import set_locale
|
from dzgui.util.localize import set_locale
|
||||||
|
from dzgui.util.map_count import test_map_count
|
||||||
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
|
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
|
||||||
from dzgui.util.strings import init
|
from dzgui.util.strings import init
|
||||||
|
|
||||||
@@ -119,6 +119,7 @@ def load_gui(version: str, is_debug: bool) -> None:
|
|||||||
del os.environ["GTK_IM_MODULE"]
|
del os.environ["GTK_IM_MODULE"]
|
||||||
|
|
||||||
if has_new_config(XDG.config) is False:
|
if has_new_config(XDG.config) is False:
|
||||||
|
test_map_count()
|
||||||
migrate_cols_file(XDG.columns)
|
migrate_cols_file(XDG.columns)
|
||||||
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
||||||
# TODO: add logging inside wizard
|
# TODO: add logging inside wizard
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ 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"
|
||||||
|
CLOSE = "window-close-symbolic"
|
||||||
FOLDER = "folder-symbolic"
|
FOLDER = "folder-symbolic"
|
||||||
EDIT_DELETE = "edit-delete-symbolic"
|
EDIT_DELETE = "edit-delete-symbolic"
|
||||||
ERROR = "dialog-error-symbolic"
|
ERROR = "dialog-error-symbolic"
|
||||||
|
|||||||
@@ -402,6 +402,9 @@ class Controller(GObject.GObject):
|
|||||||
def get_menu(self) -> "MenuTreeView":
|
def get_menu(self) -> "MenuTreeView":
|
||||||
return self.mediator.menu
|
return self.mediator.menu
|
||||||
|
|
||||||
|
def unset_fav(self) -> None:
|
||||||
|
self.config_man.unset_fav()
|
||||||
|
|
||||||
def has_favorites(self) -> bool:
|
def has_favorites(self) -> bool:
|
||||||
favs = self.config_man.get_favorites()
|
favs = self.config_man.get_favorites()
|
||||||
if len(favs) < 1:
|
if len(favs) < 1:
|
||||||
@@ -459,6 +462,9 @@ class Controller(GObject.GObject):
|
|||||||
open_workshop_page(mod, cmd)
|
open_workshop_page(mod, cmd)
|
||||||
|
|
||||||
def has_note(self) -> bool:
|
def has_note(self) -> bool:
|
||||||
|
# TODO: get record string, store in memory
|
||||||
|
# get note by record, get is in favs, etc. store as a block
|
||||||
|
# this is only used by context mixin
|
||||||
note = self.get_note()
|
note = self.get_note()
|
||||||
if len(note) > 0:
|
if len(note) > 0:
|
||||||
return True
|
return True
|
||||||
@@ -473,11 +479,13 @@ class Controller(GObject.GObject):
|
|||||||
return self.notes_man.get_note(record)
|
return self.notes_man.get_note(record)
|
||||||
|
|
||||||
def add_note(self, note: str) -> None:
|
def add_note(self, note: str) -> None:
|
||||||
|
# TODO: record should be cached upon creation of dialog
|
||||||
tv = self.get_active_treeview()
|
tv = self.get_active_treeview()
|
||||||
record = tv.get_record_string()
|
record = tv.get_record_string()
|
||||||
self.notes_man.add_note(record, note)
|
self.notes_man.add_note(record, note)
|
||||||
|
|
||||||
def delete_note(self) -> None:
|
def delete_note(self) -> None:
|
||||||
|
# TODO: record should be cached upon creation of dialog
|
||||||
tv = self.get_active_treeview()
|
tv = self.get_active_treeview()
|
||||||
record = tv.get_record_string()
|
record = tv.get_record_string()
|
||||||
self.notes_man.delete_note(record)
|
self.notes_man.delete_note(record)
|
||||||
|
|||||||
+8
-2
@@ -5,12 +5,14 @@ import warnings
|
|||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.init.libgi import test_libgi_missing
|
from dzgui.init.libgi import test_libgi_missing
|
||||||
from dzgui.init.prefix import get_version
|
from dzgui.init.prefix import get_version
|
||||||
|
from dzgui.util.map_count import set_map_count
|
||||||
from dzgui.util.strings import flags
|
from dzgui.util.strings import flags
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=flags.description)
|
parser = argparse.ArgumentParser(description=flags.description)
|
||||||
parser.add_argument("-v", "--version", action="store_true", help=flags.version)
|
|
||||||
parser.add_argument("-u", "--uninstall", action="store_true", help=flags.uninstall)
|
|
||||||
parser.add_argument("-d", "--debug", action="store_true", help=flags.debug)
|
parser.add_argument("-d", "--debug", action="store_true", help=flags.debug)
|
||||||
|
parser.add_argument("-m", "--map", action="store_true", help=flags.map_count)
|
||||||
|
parser.add_argument("-u", "--uninstall", action="store_true", help=flags.uninstall)
|
||||||
|
parser.add_argument("-v", "--version", action="store_true", help=flags.version)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ def uninstall() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
# TODO: isolate single flags
|
||||||
if args.version is True:
|
if args.version is True:
|
||||||
print(get_version())
|
print(get_version())
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -32,6 +35,9 @@ def main() -> None:
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
if args.debug is True:
|
if args.debug is True:
|
||||||
warnings.filterwarnings("default", category=DeprecationWarning)
|
warnings.filterwarnings("default", category=DeprecationWarning)
|
||||||
|
if args.map is True:
|
||||||
|
set_map_count()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
version = get_version()
|
version = get_version()
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,13 @@ class ConfigManager:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def unset_fav(self) -> None:
|
||||||
|
try:
|
||||||
|
self.write_config(Preferences.FAV_LBL, "")
|
||||||
|
self.write_config(Preferences.FAV_SRV, "")
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
def write_config(self, key: Preferences, value: str) -> None:
|
def write_config(self, key: Preferences, value: str) -> None:
|
||||||
try:
|
try:
|
||||||
real_key = self.enum_to_key(key)
|
real_key = self.enum_to_key(key)
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class Prerequisites:
|
|||||||
mods: list[list[str]]
|
mods: list[list[str]]
|
||||||
game_mode: bool
|
game_mode: bool
|
||||||
is_last_server: bool
|
is_last_server: bool
|
||||||
|
invalid_mods: list[tuple[str, str]]
|
||||||
allows_downloads: tuple[bool, str]
|
allows_downloads: tuple[bool, str]
|
||||||
|
|
||||||
|
|
||||||
@@ -191,6 +192,9 @@ class ConnectionManager:
|
|||||||
|
|
||||||
is_last = self.is_last_server()
|
is_last = self.is_last_server()
|
||||||
|
|
||||||
|
# TODO: strings
|
||||||
|
invalid_mods = [(mod[0], mod[1]) for mod in remote_mods if mod[2] == "Invalid mod"]
|
||||||
|
|
||||||
prereqs = Prerequisites(
|
prereqs = Prerequisites(
|
||||||
name=info.server_name,
|
name=info.server_name,
|
||||||
appid=info.game_id,
|
appid=info.game_id,
|
||||||
@@ -206,6 +210,7 @@ class ConnectionManager:
|
|||||||
mods=remote_mods,
|
mods=remote_mods,
|
||||||
game_mode=game_mode,
|
game_mode=game_mode,
|
||||||
is_last_server=is_last,
|
is_last_server=is_last,
|
||||||
|
invalid_mods=invalid_mods,
|
||||||
allows_downloads=allows_downloads,
|
allows_downloads=allows_downloads,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -261,6 +266,11 @@ class ConnectionManager:
|
|||||||
for mod in alpha_mods:
|
for mod in alpha_mods:
|
||||||
if any(mod[1] in tuple for tuple in missing_mods):
|
if any(mod[1] in tuple for tuple in missing_mods):
|
||||||
mod[2] = resync
|
mod[2] = resync
|
||||||
|
for mod in alpha_mods:
|
||||||
|
# NOTE: if the mod is neither synched or out of date, it is a malformed mod
|
||||||
|
if mod[2] == "":
|
||||||
|
# TODO: strings
|
||||||
|
mod[2] = "Invalid mod"
|
||||||
|
|
||||||
return alpha_mods, missing_mods
|
return alpha_mods, missing_mods
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ class FilterManager:
|
|||||||
strings.filter_3pp: True,
|
strings.filter_3pp: True,
|
||||||
strings.filter_night: True,
|
strings.filter_night: True,
|
||||||
strings.filter_full: show_full,
|
strings.filter_full: show_full,
|
||||||
strings.filter_lowpop: True,
|
|
||||||
strings.filter_nonascii: False,
|
|
||||||
strings.filter_duplicate: False,
|
|
||||||
strings.filter_official: True,
|
strings.filter_official: True,
|
||||||
|
strings.filter_nonascii: False,
|
||||||
|
strings.filter_lowpop: True,
|
||||||
strings.filter_unofficial: True,
|
strings.filter_unofficial: True,
|
||||||
|
strings.filter_duplicate: False,
|
||||||
strings.filter_modded: True,
|
strings.filter_modded: True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
exit_msg = (
|
||||||
|
"System map count is not high enough to run DayZ. "
|
||||||
|
"Please exit and run 'dzgui -m' to update map count."
|
||||||
|
)
|
||||||
|
failed_to_parse = (
|
||||||
|
"Failed to parse system map count.\n"
|
||||||
|
"This usually indicates that systemd is not installed."
|
||||||
|
)
|
||||||
|
failed_to_update = (
|
||||||
|
"Failed to update system map count.\n"
|
||||||
|
"Please report the issue upstream and provide the following traceback.\n\n"
|
||||||
|
"{0}"
|
||||||
|
)
|
||||||
|
meets_minimum = "System map count of {0} already meets the minimum."
|
||||||
|
prompt = "Updated map count will be written to the file {0}."
|
||||||
|
user_exit = "User exit"
|
||||||
@@ -54,8 +54,6 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
|
|||||||
cpu = get_cpu_model()
|
cpu = get_cpu_model()
|
||||||
version = get_version()
|
version = get_version()
|
||||||
|
|
||||||
debug = lookup(config, Preferences.DEBUG)
|
|
||||||
install = lookup(config, Preferences.INSTALL)
|
|
||||||
default = lookup(config, Preferences.DEFAULT)
|
default = lookup(config, Preferences.DEFAULT)
|
||||||
|
|
||||||
steam_path = Path(default)
|
steam_path = Path(default)
|
||||||
@@ -79,8 +77,6 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
|
|||||||
Kernel: {kernel}
|
Kernel: {kernel}
|
||||||
CPU: {cpu}
|
CPU: {cpu}
|
||||||
|
|
||||||
Debug: {debug}
|
|
||||||
Auto-install: {install}
|
|
||||||
Steam path: {steam_redacted}
|
Steam path: {steam_redacted}
|
||||||
Workshop path: {workshop_redacted}
|
Workshop path: {workshop_redacted}
|
||||||
|
|
||||||
|
|||||||
+59
-16
@@ -1,28 +1,71 @@
|
|||||||
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import traceback
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dzgui.const.constants import VM_FILE, MIN_COUNT
|
from dzgui.const.constants import APP_NAME, VM_FILE, MIN_COUNT
|
||||||
|
from dzgui.util.bash import concat_bash_args
|
||||||
|
from dzgui.strings import map_count
|
||||||
|
from dzgui.views.dialogs.early_alert import EarlyIgnoreDialog
|
||||||
|
|
||||||
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
def is_map_count_valid(count: int | None) -> bool:
|
||||||
|
if count is None:
|
||||||
|
# NOTE: permit if count was unreadable
|
||||||
|
return True
|
||||||
|
return count >= MIN_COUNT
|
||||||
|
|
||||||
|
|
||||||
def get_map_count() -> int | None:
|
def get_map_count() -> int | None:
|
||||||
path = Path(VM_FILE)
|
path = Path(VM_FILE)
|
||||||
if path.is_file() is False:
|
try:
|
||||||
|
count = int(path.read_text())
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(e)
|
||||||
return None
|
return None
|
||||||
count = int(path.read_text())
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
# TODO: unfinished
|
|
||||||
#if count < MIN_COUNT:
|
|
||||||
# print("needs sudo escalation")
|
|
||||||
# # pop prompt
|
|
||||||
# # get response
|
|
||||||
# set_map_count()
|
|
||||||
#return count
|
|
||||||
|
|
||||||
# TODO: unfinished
|
def test_map_count() -> None:
|
||||||
|
count = get_map_count()
|
||||||
|
if is_map_count_valid(count):
|
||||||
|
return
|
||||||
|
msg = map_count.exit_msg
|
||||||
|
EarlyIgnoreDialog(msg)
|
||||||
|
|
||||||
|
|
||||||
def set_map_count() -> None:
|
def set_map_count() -> None:
|
||||||
|
count = get_map_count()
|
||||||
|
if count is None:
|
||||||
|
print(map_count.failed_to_parse)
|
||||||
|
return
|
||||||
|
if is_map_count_valid(count):
|
||||||
|
msg = map_count.meets_minimum.format(count)
|
||||||
|
print(msg)
|
||||||
|
return
|
||||||
|
|
||||||
conf = "/etc/sysctl.d/dayz.conf"
|
conf = "/etc/sysctl.d/dayz.conf"
|
||||||
count = f"vm.max_map_count={MIN_COUNT}"
|
value = f"vm.max_map_count={MIN_COUNT}"
|
||||||
with open(conf, "w") as f:
|
try:
|
||||||
f.write(count)
|
msg = map_count.prompt.format(conf)
|
||||||
# TODO: use concat_bash_args()
|
print(msg)
|
||||||
subprocess.run(["/usr/bin/sudo", "sysctl", "-p", conf])
|
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||||
|
tmp = f.name
|
||||||
|
Path(tmp).write_text(value)
|
||||||
|
|
||||||
|
mv_cmd = f"sudo mv {tmp} {conf}"
|
||||||
|
reload_cmd = f"sudo sysctl -p {conf}"
|
||||||
|
for cmd in mv_cmd, reload_cmd:
|
||||||
|
args = concat_bash_args(cmd)
|
||||||
|
subprocess.run([*args])
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(e)
|
||||||
|
trace = traceback.format_exc()
|
||||||
|
print(map_count.failed_to_update.format(trace))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print(map_count.user_exit)
|
||||||
|
sys.exit(0)
|
||||||
@@ -327,8 +327,8 @@ mod_panel = ModPanelStrings(
|
|||||||
unhighlight_stale_tooltip="Clears highlight from stale mods",
|
unhighlight_stale_tooltip="Clears highlight from stale mods",
|
||||||
highlight_stale="Highlight stale",
|
highlight_stale="Highlight stale",
|
||||||
highlight_stale_tooltip=(
|
highlight_stale_tooltip=(
|
||||||
"Shows locally-installed mods which are not used by any server "
|
"Shows locally-installed mods which are not \n"
|
||||||
"in your Saved Servers"
|
"used by any server in your Saved Servers"
|
||||||
),
|
),
|
||||||
unsub_selected="Unsubscribe selected",
|
unsub_selected="Unsubscribe selected",
|
||||||
unsub_selected_tooltip="Unsubscribes from selected mods",
|
unsub_selected_tooltip="Unsubscribes from selected mods",
|
||||||
@@ -467,13 +467,15 @@ class Flags:
|
|||||||
version: str
|
version: str
|
||||||
uninstall: str
|
uninstall: str
|
||||||
debug: str
|
debug: str
|
||||||
|
map_count: str
|
||||||
|
|
||||||
|
|
||||||
flags = Flags(
|
flags = Flags(
|
||||||
description="DayZ server browser and mod manager",
|
description="DayZ server browser and mod manager",
|
||||||
version="Print version information",
|
version="print version information",
|
||||||
uninstall="Uninstall data files (use prior to 'pip uninstall')",
|
uninstall="clean up state/config files",
|
||||||
debug="Enables developer debugging features",
|
debug="enable developer debugging features",
|
||||||
|
map_count="check and update system map count value",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -541,9 +543,7 @@ connect_panel = ConnectPanel(
|
|||||||
add="Add",
|
add="Add",
|
||||||
add_con="Add/connect",
|
add_con="Add/connect",
|
||||||
placeholder="Enter IP (IP:Query port)",
|
placeholder="Enter IP (IP:Query port)",
|
||||||
entry_tooltip=(
|
entry_tooltip=("- IP: format as IP:Query port\ne.g. 192.168.1.1:27016"),
|
||||||
"- IP: format as IP:Query port\ne.g. 192.168.1.1:27016"
|
|
||||||
),
|
|
||||||
add_tooltip="Add to Saved Servers",
|
add_tooltip="Add to Saved Servers",
|
||||||
connect_tooltip="Connect to this server",
|
connect_tooltip="Connect to this server",
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-1
@@ -54,7 +54,10 @@ class OuterWindow(Gtk.Window):
|
|||||||
MainController.register_widget("window", self)
|
MainController.register_widget("window", self)
|
||||||
|
|
||||||
# NOTE: steam deck taskbar may occlude elements
|
# NOTE: steam deck taskbar may occlude elements
|
||||||
if MainController.get_prefs().is_steam_deck is False:
|
is_deck = MainController.get_prefs().is_steam_deck
|
||||||
|
if is_deck:
|
||||||
|
self.maximize()
|
||||||
|
else:
|
||||||
self.set_titlebar(self.hb)
|
self.set_titlebar(self.hb)
|
||||||
|
|
||||||
self.connect("delete-event", self._on_delete_event)
|
self.connect("delete-event", self._on_delete_event)
|
||||||
|
|||||||
@@ -23,3 +23,10 @@ class HBox(GenericBox):
|
|||||||
class VBox(GenericBox):
|
class VBox(GenericBox):
|
||||||
def __init__(self, spacing: int = 0) -> None:
|
def __init__(self, spacing: int = 0) -> None:
|
||||||
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=spacing)
|
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=spacing)
|
||||||
|
|
||||||
|
class ShortHBox(HBox):
|
||||||
|
def __init__(self, widget: Gtk.Widget) -> None:
|
||||||
|
super().__init__(spacing=5)
|
||||||
|
|
||||||
|
self.set_halign(Gtk.Align.START)
|
||||||
|
self.pack_start(widget, expand=False, fill=False, padding=0)
|
||||||
@@ -10,6 +10,7 @@ from dzgui.util.strings import (
|
|||||||
)
|
)
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
CLIPBOARD,
|
CLIPBOARD,
|
||||||
|
CLOSE,
|
||||||
INPUT_KEYBOARD,
|
INPUT_KEYBOARD,
|
||||||
LIST_ADD,
|
LIST_ADD,
|
||||||
REFRESH_ICON,
|
REFRESH_ICON,
|
||||||
@@ -128,6 +129,14 @@ class CopyIpButton(ClipboardButton):
|
|||||||
self.set_tooltip_text("Copy IP to clipboard")
|
self.set_tooltip_text("Copy IP to clipboard")
|
||||||
|
|
||||||
|
|
||||||
|
class CloseButton(IconTextButton):
|
||||||
|
def __init__(self, label: str) -> None:
|
||||||
|
super().__init__(icon=CLOSE, label=label)
|
||||||
|
|
||||||
|
# TODO: strings
|
||||||
|
self.set_tooltip_text("Unset this server as favorite")
|
||||||
|
|
||||||
|
|
||||||
class WebButton(IconTextButton):
|
class WebButton(IconTextButton):
|
||||||
def __init__(self, label: str) -> None:
|
def __init__(self, label: str) -> None:
|
||||||
super().__init__(icon=WEB_BROWSER, label=label)
|
super().__init__(icon=WEB_BROWSER, label=label)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from dzgui.strings import connect_panel
|
|||||||
from dzgui.util.keys import is_ctrl_mask
|
from dzgui.util.keys import is_ctrl_mask
|
||||||
from dzgui.views.components.buttons import (
|
from dzgui.views.components.buttons import (
|
||||||
AddButton,
|
AddButton,
|
||||||
|
CloseButton,
|
||||||
CopyIpButton,
|
CopyIpButton,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -187,17 +188,27 @@ class FavPanel(Gtk.Frame):
|
|||||||
self.fav_button = SteamConnectButton()
|
self.fav_button = SteamConnectButton()
|
||||||
self.fav_button.connect("clicked", self._on_connect_clicked)
|
self.fav_button.connect("clicked", self._on_connect_clicked)
|
||||||
self.copy_button = CopyIpButton(self.controller, self.get_fav_ip)
|
self.copy_button = CopyIpButton(self.controller, self.get_fav_ip)
|
||||||
|
|
||||||
|
# TODO: strings
|
||||||
|
self.unset_button = CloseButton("Unset")
|
||||||
|
self.unset_button.connect("clicked", self._on_unset_clicked)
|
||||||
|
|
||||||
if favorite is None:
|
if favorite is None:
|
||||||
self.toggle_buttons(False)
|
self.toggle_buttons(False)
|
||||||
|
|
||||||
# NOTE: disable vscrollbar to prevent layout jumping behavior
|
# NOTE: disable vscrollbar to prevent layout jumping behavior
|
||||||
scrollable_label = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER)
|
self.scrollable_label = Gtk.ScrolledWindow(
|
||||||
scrollable_label.add(self.fav_label)
|
vscrollbar_policy=Gtk.PolicyType.NEVER, overlay_scrolling=False
|
||||||
|
)
|
||||||
|
self.scrollable_label.add(self.fav_label)
|
||||||
|
|
||||||
grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5)
|
grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5)
|
||||||
grid.attach(scrollable_label, 0, 0, 3, ROWS)
|
grid.attach(self.scrollable_label, 0, 0, 3, ROWS)
|
||||||
grid.attach_next_to(
|
grid.attach_next_to(
|
||||||
self.copy_button, scrollable_label, Gtk.PositionType.RIGHT, COLS, ROWS
|
self.unset_button, self.scrollable_label, Gtk.PositionType.RIGHT, COLS, ROWS
|
||||||
|
)
|
||||||
|
grid.attach_next_to(
|
||||||
|
self.copy_button, self.unset_button, Gtk.PositionType.RIGHT, COLS, ROWS
|
||||||
)
|
)
|
||||||
grid.attach_next_to(
|
grid.attach_next_to(
|
||||||
self.fav_button, self.copy_button, Gtk.PositionType.RIGHT, COLS, ROWS
|
self.fav_button, self.copy_button, Gtk.PositionType.RIGHT, COLS, ROWS
|
||||||
@@ -205,8 +216,16 @@ class FavPanel(Gtk.Frame):
|
|||||||
|
|
||||||
self.add(grid)
|
self.add(grid)
|
||||||
|
|
||||||
|
def _on_unset_clicked(self, button: Gtk.Button) -> None:
|
||||||
|
try:
|
||||||
|
self.controller.unset_fav()
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
self.fav_label.set_text(connect_panel.favs_empty)
|
||||||
|
self.toggle_buttons(False)
|
||||||
|
|
||||||
def toggle_buttons(self, state: bool) -> None:
|
def toggle_buttons(self, state: bool) -> None:
|
||||||
for button in self.fav_button, self.copy_button:
|
for button in self.fav_button, self.copy_button, self.unset_button:
|
||||||
button.set_sensitive(state)
|
button.set_sensitive(state)
|
||||||
|
|
||||||
def _on_connect_clicked(self, button: Gtk.Button) -> None:
|
def _on_connect_clicked(self, button: Gtk.Button) -> None:
|
||||||
|
|||||||
@@ -33,15 +33,15 @@ class ButtonGrid(Gtk.Grid):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
halign=Gtk.Align.CENTER, column_spacing=5, column_homogeneous=True
|
halign=Gtk.Align.CENTER, column_spacing=5, column_homogeneous=True
|
||||||
)
|
)
|
||||||
row = 1
|
|
||||||
col = 0
|
|
||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.emitter = controller.get_emitter()
|
self.emitter = controller.get_emitter()
|
||||||
|
|
||||||
self.checks: list[Gtk.CheckButton] = []
|
self.checks: list[Gtk.CheckButton] = []
|
||||||
|
|
||||||
# TODO: use enumerated checks
|
flowbox = Gtk.FlowBox(
|
||||||
|
halign=Gtk.Align.CENTER, min_children_per_line=3, max_children_per_line=3
|
||||||
|
)
|
||||||
|
|
||||||
for check in defaults.keys():
|
for check in defaults.keys():
|
||||||
checkbox = Gtk.CheckButton(label=check)
|
checkbox = Gtk.CheckButton(label=check)
|
||||||
label = checkbox.get_child()
|
label = checkbox.get_child()
|
||||||
@@ -51,14 +51,12 @@ class ButtonGrid(Gtk.Grid):
|
|||||||
if defaults[check]:
|
if defaults[check]:
|
||||||
checkbox.set_active(True)
|
checkbox.set_active(True)
|
||||||
|
|
||||||
col = col + 1
|
|
||||||
if col > 3:
|
|
||||||
row += 1
|
|
||||||
col = 1
|
|
||||||
self.attach(checkbox, col, row, 1, 1)
|
|
||||||
checkbox.connect("toggled", self._on_check_toggled)
|
checkbox.connect("toggled", self._on_check_toggled)
|
||||||
|
flowbox.add(checkbox)
|
||||||
self.checks.append(checkbox)
|
self.checks.append(checkbox)
|
||||||
|
|
||||||
|
self.add(flowbox)
|
||||||
|
|
||||||
def block_toggles(self, state: bool) -> None:
|
def block_toggles(self, state: bool) -> None:
|
||||||
for check in self.checks:
|
for check in self.checks:
|
||||||
self.controller.suppress_signal(
|
self.controller.suppress_signal(
|
||||||
|
|||||||
@@ -10,21 +10,26 @@ gi.require_version("Gtk", "3.0")
|
|||||||
from gi.repository import Gtk # noqa E402
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
|
||||||
class EarlyAlertDialog(Gtk.MessageDialog):
|
class AbortDialog(Gtk.MessageDialog):
|
||||||
def __init__(self, string: str) -> None:
|
def __init__(self, string: str, buttons: Gtk.ButtonsType) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
title=dialog_header,
|
title=dialog_header,
|
||||||
text=dialog_error,
|
text=dialog_error,
|
||||||
transient_for=None,
|
transient_for=None,
|
||||||
buttons=Gtk.ButtonsType.OK,
|
buttons=buttons,
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = textwrap.fill(string, 50)
|
msg = textwrap.fill(string, 50)
|
||||||
self.format_secondary_text(msg)
|
self.format_secondary_text(msg)
|
||||||
|
|
||||||
|
ma = self.get_message_area()
|
||||||
|
label = ma.get_children()[1] # type: ignore
|
||||||
|
label.set_justify(Gtk.Justification.CENTER)
|
||||||
|
|
||||||
aa = self.get_action_area()
|
aa = self.get_action_area()
|
||||||
aa.set_margin_bottom(20)
|
aa.set_margin_bottom(20)
|
||||||
# self.action_area.set_margin_bottom(20)
|
aa.set_layout(Gtk.ButtonBoxStyle.CENTER) # type: ignore
|
||||||
|
|
||||||
self.outer = self.get_content_area()
|
self.outer = self.get_content_area()
|
||||||
self.outer.set_margin_start(30)
|
self.outer.set_margin_start(30)
|
||||||
self.outer.set_margin_end(30)
|
self.outer.set_margin_end(30)
|
||||||
@@ -32,26 +37,30 @@ class EarlyAlertDialog(Gtk.MessageDialog):
|
|||||||
self.set_default_size(250, 100)
|
self.set_default_size(250, 100)
|
||||||
|
|
||||||
abort = self.get_widget_for_response(Gtk.ResponseType.OK)
|
abort = self.get_widget_for_response(Gtk.ResponseType.OK)
|
||||||
|
ignore = self.get_widget_for_response(Gtk.ResponseType.CANCEL)
|
||||||
if abort is not None and hasattr(abort, "set_label"):
|
if abort is not None and hasattr(abort, "set_label"):
|
||||||
abort.set_label("Exit")
|
abort.set_label("Exit")
|
||||||
|
if ignore is not None and hasattr(ignore, "set_label"):
|
||||||
|
ignore.set_label("Ignore")
|
||||||
|
|
||||||
self.connect("response", self._on_response)
|
self.connect("response", self._on_response)
|
||||||
|
|
||||||
self.run()
|
self.run()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
||||||
match response:
|
match response:
|
||||||
case Gtk.ResponseType.OK:
|
case Gtk.ResponseType.OK | Gtk.ResponseType.DELETE_EVENT:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
case Gtk.ResponseType.CANCEL:
|
case Gtk.ResponseType.CANCEL:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
class EarlyIgnoreDialog(EarlyAlertDialog):
|
class EarlyAlertDialog(AbortDialog):
|
||||||
def __init__(self, string: str) -> None:
|
def __init__(self, string: str) -> None:
|
||||||
super().__init__(string=string)
|
super().__init__(string=string, buttons=Gtk.ButtonsType.OK)
|
||||||
|
|
||||||
# TODO: reverse order
|
|
||||||
self.add_button("Ignore", Gtk.ResponseType.CANCEL)
|
|
||||||
|
|
||||||
# TODO: if exit, sys.exit(1), else pass
|
class EarlyIgnoreDialog(AbortDialog):
|
||||||
|
def __init__(self, string: str) -> None:
|
||||||
|
super().__init__(string=string, buttons=Gtk.ButtonsType.OK_CANCEL)
|
||||||
@@ -107,7 +107,7 @@ class WaitDialog(GenericDialog):
|
|||||||
self.cur_job = 1
|
self.cur_job = 1
|
||||||
|
|
||||||
self.cancel = Gtk.Button(label="Cancel", halign=Gtk.Align.CENTER)
|
self.cancel = Gtk.Button(label="Cancel", halign=Gtk.Align.CENTER)
|
||||||
self.cancel.connect("clicked", lambda _: self.controller.set_cancel_event())
|
self.cancel.connect("clicked", self._on_cancel_clicked)
|
||||||
|
|
||||||
self.connect("delete-event", lambda widget, event: True)
|
self.connect("delete-event", lambda widget, event: True)
|
||||||
content = self.get_content_area()
|
content = self.get_content_area()
|
||||||
@@ -138,6 +138,11 @@ class WaitDialog(GenericDialog):
|
|||||||
self.prog.set_fraction(fraction)
|
self.prog.set_fraction(fraction)
|
||||||
self.cur_job += 1
|
self.cur_job += 1
|
||||||
|
|
||||||
|
def _on_cancel_clicked(self, button: Gtk.Button) -> None:
|
||||||
|
# TODO: strings
|
||||||
|
GLib.idle_add(self.update_text, "Caught cancel signal, cleaning up")
|
||||||
|
self.controller.set_cancel_event()
|
||||||
|
|
||||||
def show_cancel(self, state: bool) -> None:
|
def show_cancel(self, state: bool) -> None:
|
||||||
self.cancel.set_visible(state)
|
self.cancel.set_visible(state)
|
||||||
|
|
||||||
@@ -156,6 +161,7 @@ class QuitDialog(GenericDialog):
|
|||||||
self.add_button(strings.exit_app, Gtk.ResponseType.OK)
|
self.add_button(strings.exit_app, Gtk.ResponseType.OK)
|
||||||
|
|
||||||
self.connect("response", self._on_response)
|
self.connect("response", self._on_response)
|
||||||
|
# TODO: superfluous with the above
|
||||||
self.connect("delete-event", self._on_response)
|
self.connect("delete-event", self._on_response)
|
||||||
|
|
||||||
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import html
|
||||||
from typing import Self, TYPE_CHECKING
|
from typing import Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import EXPAND, FILL
|
from dzgui.const.constants import EXPAND, FILL
|
||||||
@@ -65,6 +66,7 @@ class ServerDialog(GenericDialog):
|
|||||||
|
|
||||||
class ServerDetailsDialog(ServerDialog):
|
class ServerDetailsDialog(ServerDialog):
|
||||||
def __init__(self, controller: "Controller", details: "Details"):
|
def __init__(self, controller: "Controller", details: "Details"):
|
||||||
|
# TODO: server name should also be packed in details struct
|
||||||
name = controller.get_server_name()
|
name = controller.get_server_name()
|
||||||
super().__init__(controller, strings.server_details, name, menu=None)
|
super().__init__(controller, strings.server_details, name, menu=None)
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ class ServerDetailsDialog(ServerDialog):
|
|||||||
self.view.set_model(self.store)
|
self.view.set_model(self.store)
|
||||||
text = details.description
|
text = details.description
|
||||||
text = format_hyperlinks(text)
|
text = format_hyperlinks(text)
|
||||||
self.description.set_markup(text)
|
self.description.set_markup(html.escape(text))
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,11 @@ class CursorMixin:
|
|||||||
end = len(model) - 1
|
end = len(model) - 1
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
cur_row = self.get_focused_row_index() # type: ignore
|
|
||||||
|
try:
|
||||||
|
cur_row = self.get_focused_row_index() # type: ignore
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
if position == CursorPosition.DOWN:
|
if position == CursorPosition.DOWN:
|
||||||
if cur_row == end:
|
if cur_row == end:
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ class Changelog(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ig
|
|||||||
|
|
||||||
# TODO: should long text be wrapped?
|
# TODO: should long text be wrapped?
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5, margin_top=10)
|
self.box = Gtk.Box(
|
||||||
|
orientation=Gtk.Orientation.VERTICAL, spacing=5, margin_top=10
|
||||||
|
)
|
||||||
self.add(self.box)
|
self.add(self.box)
|
||||||
|
|
||||||
self.connect("key-press-event", self._on_keypress)
|
self.connect("key-press-event", self._on_keypress)
|
||||||
@@ -55,7 +57,6 @@ class Changelog(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ig
|
|||||||
return version, date
|
return version, date
|
||||||
raise ValueError("Changelog parse error")
|
raise ValueError("Changelog parse error")
|
||||||
|
|
||||||
|
|
||||||
def _generate_nodes(self, releases: list[tuple[str, list[str]]]) -> None:
|
def _generate_nodes(self, releases: list[tuple[str, list[str]]]) -> None:
|
||||||
for header, changes in releases:
|
for header, changes in releases:
|
||||||
for ind in (0, -1):
|
for ind in (0, -1):
|
||||||
@@ -64,8 +65,12 @@ class Changelog(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ig
|
|||||||
text = "\n".join(changes)
|
text = "\n".join(changes)
|
||||||
formatted = format_pango(text)
|
formatted = format_pango(text)
|
||||||
|
|
||||||
label = Gtk.Label(valign=Gtk.Align.START, margin=15, halign=Gtk.Align.START)
|
container = Gtk.Box(
|
||||||
|
valign=Gtk.Align.START, halign=Gtk.Align.START
|
||||||
|
)
|
||||||
|
label = Gtk.Label()
|
||||||
label.set_markup(formatted)
|
label.set_markup(formatted)
|
||||||
|
container.add(label)
|
||||||
|
|
||||||
version, date = self._parse_version(header)
|
version, date = self._parse_version(header)
|
||||||
button_text = f"{version} ({date})"
|
button_text = f"{version} ({date})"
|
||||||
@@ -73,9 +78,20 @@ class Changelog(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ig
|
|||||||
|
|
||||||
expander = Gtk.Expander()
|
expander = Gtk.Expander()
|
||||||
expander.set_label_widget(button)
|
expander.set_label_widget(button)
|
||||||
expander.add(label)
|
expander.add(container)
|
||||||
|
expander.connect("activate", self._on_expand, container)
|
||||||
self.box.add(expander)
|
self.box.add(expander)
|
||||||
|
|
||||||
|
def _on_expand(self, expander: Gtk.Box, container: Gtk.Box) -> None:
|
||||||
|
"""
|
||||||
|
Wayland/some themes do not expect widgets to have a size allocation until expanded.
|
||||||
|
Trying to set margins a priori may cause negative allocation,
|
||||||
|
as the margins are subtracted from zero. In effect, the widget is not realized
|
||||||
|
until it is expanded for the first time.
|
||||||
|
"""
|
||||||
|
container.set_margin_start(15)
|
||||||
|
container.set_margin_top(15)
|
||||||
|
container.set_margin_bottom(15)
|
||||||
|
|
||||||
def _parse(self, changelog: str) -> list[tuple[str, list[str]]]:
|
def _parse(self, changelog: str) -> list[tuple[str, list[str]]]:
|
||||||
release = ""
|
release = ""
|
||||||
|
|||||||
@@ -103,4 +103,4 @@ class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
return grid
|
return grid
|
||||||
|
|
||||||
def grab_content_area(self) -> None:
|
def grab_content_area(self) -> None:
|
||||||
self.box.grab_focus()
|
self.grab_focus()
|
||||||
@@ -521,3 +521,6 @@ class OfflineLoader(Gtk.Box):
|
|||||||
self.mission_frame.present_folder_changed(mission)
|
self.mission_frame.present_folder_changed(mission)
|
||||||
return
|
return
|
||||||
self.offline_man.launch(appid, mission, local_mods, custom_folder, custom_mods)
|
self.offline_man.launch(appid, mission, local_mods, custom_folder, custom_mods)
|
||||||
|
|
||||||
|
def grab_content_area(self) -> None:
|
||||||
|
self.local_frame.tree.grab_focus()
|
||||||
@@ -20,11 +20,14 @@ from dzgui.const.enum import Preferences, ServerTab
|
|||||||
from dzgui.strings import errors, options
|
from dzgui.strings import errors, options
|
||||||
from dzgui.util import strings, css, open_links
|
from dzgui.util import strings, css, open_links
|
||||||
|
|
||||||
|
from dzgui.views.components.box import ShortHBox, VBox
|
||||||
|
from dzgui.views.components.eventbox import InfoEventBox
|
||||||
from dzgui.views.components.labels import LeftLabel
|
from dzgui.views.components.labels import LeftLabel
|
||||||
from dzgui.views.components.buttons import WebButton
|
from dzgui.views.components.buttons import WebButton
|
||||||
from dzgui.views.components.frame import HeadingFrame
|
from dzgui.views.components.frame import HeadingFrame
|
||||||
from dzgui.views.components.misc import ClientCombo
|
from dzgui.views.components.misc import ClientCombo
|
||||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||||
|
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
||||||
|
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -37,17 +40,40 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.controllers.emitter import Emitter
|
from dzgui.controllers.emitter import Emitter
|
||||||
|
|
||||||
|
|
||||||
class ShortHBox(Gtk.Box):
|
class ToggleField(Gtk.Box):
|
||||||
def __init__(self, widget: Gtk.Widget) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
controller: "Controller",
|
||||||
|
first_option: str,
|
||||||
|
second_option: str,
|
||||||
|
context: Preferences,
|
||||||
|
) -> None:
|
||||||
super().__init__(spacing=5, halign=Gtk.Align.START)
|
super().__init__(spacing=5, halign=Gtk.Align.START)
|
||||||
|
|
||||||
self.pack_start(widget, NO_EXPAND, NO_FILL, NO_PADDING)
|
self.controller = controller
|
||||||
|
self.context = context
|
||||||
|
self.radio1 = Gtk.RadioButton.new_with_label(None, first_option)
|
||||||
|
self.radio2 = Gtk.RadioButton.new_from_widget(self.radio1)
|
||||||
|
self.radio2.set_label(second_option)
|
||||||
|
self.pack_start(self.radio1, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
|
self.pack_start(self.radio2, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
|
|
||||||
|
def set_suboption_active(self, state: bool) -> None:
|
||||||
|
# NOTE: defer connection of signal until after state is set
|
||||||
|
self.radio2.set_active(state)
|
||||||
|
self.radio1.connect("toggled", self._on_radio_toggled, self.context)
|
||||||
|
|
||||||
|
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
|
||||||
|
self.controller.toggle_config(context)
|
||||||
|
|
||||||
|
def set_sensitive(self, state: bool) -> None:
|
||||||
|
for el in self.radio1, self.radio2:
|
||||||
|
el.set_sensitive(state)
|
||||||
|
|
||||||
|
|
||||||
class Options(Gtk.Box):
|
class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||||
def __init__(self, controller: "Controller"):
|
def __init__(self, controller: "Controller"):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
orientation=Gtk.Orientation.VERTICAL,
|
|
||||||
margin_start=10,
|
margin_start=10,
|
||||||
margin_end=10,
|
margin_end=10,
|
||||||
)
|
)
|
||||||
@@ -60,11 +86,6 @@ class Options(Gtk.Box):
|
|||||||
self.DEFAULT_WIDTH = 1
|
self.DEFAULT_WIDTH = 1
|
||||||
self.DEFAULT_HEIGHT = 1
|
self.DEFAULT_HEIGHT = 1
|
||||||
|
|
||||||
label = Gtk.Label(label=strings.options.header)
|
|
||||||
label.set_halign(Gtk.Align.CENTER)
|
|
||||||
css.add_class(label, "page-heading")
|
|
||||||
self.add(label)
|
|
||||||
|
|
||||||
self.steam_entry: Gtk.Entry
|
self.steam_entry: Gtk.Entry
|
||||||
|
|
||||||
self.steam = WebButton(label=strings.options.steam_web)
|
self.steam = WebButton(label=strings.options.steam_web)
|
||||||
@@ -84,19 +105,26 @@ class Options(Gtk.Box):
|
|||||||
# TODO: make submit field a standalone class
|
# TODO: make submit field a standalone class
|
||||||
self.player_box.get_children()[0].set_width_chars(30) # type: ignore
|
self.player_box.get_children()[0].set_width_chars(30) # type: ignore
|
||||||
|
|
||||||
self.fullscreen_toggle = self.make_binary_radio(
|
self.fullscreen_toggle = ToggleField(
|
||||||
|
self.controller,
|
||||||
strings.options.last_used,
|
strings.options.last_used,
|
||||||
strings.options.always_fs,
|
strings.options.always_fs,
|
||||||
Preferences.WINDOW,
|
Preferences.WINDOW,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: strings
|
||||||
|
eb = InfoEventBox(
|
||||||
|
"This option is not available on Steam Deck.", self.controller
|
||||||
|
)
|
||||||
|
self.fullscreen_toggle.add(eb)
|
||||||
|
|
||||||
self.client_combo = ClientCombo()
|
self.client_combo = ClientCombo()
|
||||||
self.client_combo.connect("changed", self._on_client_changed)
|
self.client_combo.connect("changed", self._on_client_changed)
|
||||||
|
|
||||||
client_hbox = ShortHBox(self.client_combo)
|
client_hbox = ShortHBox(self.client_combo)
|
||||||
|
|
||||||
self.distance_toggle = self.make_binary_radio(
|
self.distance_toggle = ToggleField(
|
||||||
strings.options.km, strings.options.mi, Preferences.DIST
|
self.controller, strings.options.km, strings.options.mi, Preferences.DIST
|
||||||
)
|
)
|
||||||
|
|
||||||
combo_store = Gtk.ListStore(str, object)
|
combo_store = Gtk.ListStore(str, object)
|
||||||
@@ -165,9 +193,16 @@ class Options(Gtk.Box):
|
|||||||
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
|
||||||
|
|
||||||
self.scrollable = Gtk.ScrolledWindow(vexpand=True)
|
label = Gtk.Label(label=strings.options.header)
|
||||||
self.scrollable.add(grid)
|
label.set_halign(Gtk.Align.CENTER)
|
||||||
self.add(self.scrollable)
|
css.add_class(label, "page-heading")
|
||||||
|
|
||||||
|
box = VBox()
|
||||||
|
box.add(label)
|
||||||
|
box.add(grid)
|
||||||
|
self.add(box)
|
||||||
|
|
||||||
|
self.connect("key-press-event", self._on_keypress)
|
||||||
|
|
||||||
def get_client_name(self) -> str:
|
def get_client_name(self) -> str:
|
||||||
model = self.client_combo.get_model()
|
model = self.client_combo.get_model()
|
||||||
@@ -290,14 +325,6 @@ class Options(Gtk.Box):
|
|||||||
real_cmd = combo.get_model()[_iter][1]
|
real_cmd = combo.get_model()[_iter][1]
|
||||||
self.controller.update_config(Preferences.CLIENT, real_cmd)
|
self.controller.update_config(Preferences.CLIENT, real_cmd)
|
||||||
|
|
||||||
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
|
|
||||||
try:
|
|
||||||
self.controller.toggle_config(context)
|
|
||||||
except Exception:
|
|
||||||
button.handler_block_by_func(self._on_radio_toggled)
|
|
||||||
self.populate_settings()
|
|
||||||
button.handler_unblock_by_func(self._on_radio_toggled)
|
|
||||||
|
|
||||||
def _is_valid_text(self, text: str, context: Preferences) -> bool:
|
def _is_valid_text(self, text: str, context: Preferences) -> bool:
|
||||||
if text.isspace():
|
if text.isspace():
|
||||||
return False
|
return False
|
||||||
@@ -341,23 +368,6 @@ class Options(Gtk.Box):
|
|||||||
state = self._is_valid_text(text, context)
|
state = self._is_valid_text(text, context)
|
||||||
button.set_sensitive(state)
|
button.set_sensitive(state)
|
||||||
|
|
||||||
def make_binary_radio(
|
|
||||||
self,
|
|
||||||
first_option: str,
|
|
||||||
second_option: str,
|
|
||||||
context: Preferences,
|
|
||||||
) -> Gtk.Box:
|
|
||||||
|
|
||||||
hbox = Gtk.Box(spacing=5, halign=Gtk.Align.START)
|
|
||||||
radio1 = Gtk.RadioButton.new_with_label(None, first_option)
|
|
||||||
radio2 = Gtk.RadioButton.new_from_widget(radio1)
|
|
||||||
radio2.set_label(second_option)
|
|
||||||
radio1.connect("toggled", self._on_radio_toggled, context)
|
|
||||||
hbox.pack_start(radio1, NO_EXPAND, NO_FILL, NO_PADDING)
|
|
||||||
hbox.pack_start(radio2, NO_EXPAND, NO_FILL, NO_PADDING)
|
|
||||||
|
|
||||||
return hbox
|
|
||||||
|
|
||||||
def populate_settings(self) -> None:
|
def populate_settings(self) -> None:
|
||||||
prefs = self.controller.get_prefs()
|
prefs = self.controller.get_prefs()
|
||||||
# NOTE: re-check in case file was removed by user between runs
|
# NOTE: re-check in case file was removed by user between runs
|
||||||
@@ -383,14 +393,15 @@ class Options(Gtk.Box):
|
|||||||
if hasattr(p, "set_text"):
|
if hasattr(p, "set_text"):
|
||||||
p.set_text(name)
|
p.set_text(name)
|
||||||
|
|
||||||
# NOTE: suppress toggle signal until radios are built
|
fs = config["fullscreen"]
|
||||||
self._suppress_toggles(True)
|
miles = config["use_miles"]
|
||||||
for el, conf_state in [
|
|
||||||
(self.fullscreen_toggle, config["fullscreen"]),
|
if prefs.is_steam_deck:
|
||||||
(self.distance_toggle, config["use_miles"]),
|
self.fullscreen_toggle.set_sensitive(False)
|
||||||
]:
|
fs = True
|
||||||
el.get_children()[conf_state].set_active(True)
|
|
||||||
self._suppress_toggles(False)
|
self.fullscreen_toggle.set_suboption_active(fs)
|
||||||
|
self.distance_toggle.set_suboption_active(miles)
|
||||||
|
|
||||||
# NOTE: disable buttons if no text is set
|
# NOTE: disable buttons if no text is set
|
||||||
for field in (
|
for field in (
|
||||||
@@ -442,4 +453,4 @@ class Options(Gtk.Box):
|
|||||||
widget.set_visibility(state)
|
widget.set_visibility(state)
|
||||||
|
|
||||||
def grab_content_area(self) -> None:
|
def grab_content_area(self) -> None:
|
||||||
return
|
self.grab_focus()
|
||||||
@@ -239,6 +239,15 @@ class PreConnectionAssistant(Gtk.Box):
|
|||||||
)
|
)
|
||||||
|
|
||||||
"""Errors"""
|
"""Errors"""
|
||||||
|
if len(prereqs.invalid_mods) > 0:
|
||||||
|
pairs = [": ".join(sub) for sub in prereqs.invalid_mods]
|
||||||
|
lines = "\n".join(pairs)
|
||||||
|
msg = (
|
||||||
|
"Server has invalid mods that are not recognized by Steam.\n"
|
||||||
|
"Contact the server owner and include these mod IDs in your report:\n"
|
||||||
|
f"{lines}"
|
||||||
|
)
|
||||||
|
errors.append(msg)
|
||||||
if prereqs.binary_missing:
|
if prereqs.binary_missing:
|
||||||
errors.append(
|
errors.append(
|
||||||
f"Remote server is running the build '{prereqs.build}', but it is not installed.\n{resync_msg}"
|
f"Remote server is running the build '{prereqs.build}', but it is not installed.\n{resync_msg}"
|
||||||
@@ -275,10 +284,10 @@ class PreConnectionAssistant(Gtk.Box):
|
|||||||
allows_dl, running_app = prereqs.allows_downloads
|
allows_dl, running_app = prereqs.allows_downloads
|
||||||
if len(prereqs.mods) > 0 and allows_dl is False:
|
if len(prereqs.mods) > 0 and allows_dl is False:
|
||||||
msg = (
|
msg = (
|
||||||
f"The game '{running_app}' is currently running in Steam, but background downloads are not enabled.\n"
|
f"The app '{running_app}' is currently running in Steam, but background downloads are not enabled.\n"
|
||||||
"Either stop the game first, or update your global Steam settings or the game's local settings.\n"
|
"Either stop the game first, or update your global Steam settings or the game's local settings.\n"
|
||||||
"Otherwise, mods may be queued for download but never update."
|
"Otherwise, mods may be queued for download but never update."
|
||||||
)
|
)
|
||||||
warnings.append(msg)
|
warnings.append(msg)
|
||||||
|
|
||||||
self.add_warnings(warnings)
|
self.add_warnings(warnings)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
|||||||
authors = [
|
authors = [
|
||||||
{name = "aclist"}
|
{name = "aclist"}
|
||||||
]
|
]
|
||||||
version = "7.0.0b20"
|
version = "7.0.0b21"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from dzgui.api.servers import Details
|
||||||
|
from dzgui.views.dialogs.servers import ServerDetailsDialog
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
|
||||||
|
class MockController:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.window = Gtk.Window()
|
||||||
|
|
||||||
|
def get_window(self) -> Gtk.Window:
|
||||||
|
return self.window
|
||||||
|
|
||||||
|
def get_server_name(self) -> str:
|
||||||
|
return "My server"
|
||||||
|
|
||||||
|
def get_emitter(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def test_html_escaping() -> None:
|
||||||
|
text = "<Server description> Game & mods"
|
||||||
|
details = Details([["0", "1"]], text, True)
|
||||||
|
controller = MockController()
|
||||||
|
dialog = ServerDetailsDialog(controller, details)
|
||||||
|
|
||||||
|
assert dialog.description.get_text() == text
|
||||||
Reference in new issue
Block a user