Compare commits

..

8 Commits

Author SHA1 Message Date
aclist
d2643d08e9
Merge pull request #296 from aclist/fix/empty-statusbar
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
fix: statusbar being wiped when calcdist fails on empty row
2026-05-21 21:07:23 +09:00
aclist
6274f62c93 chore: remove print statement
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-05-21 21:06:40 +09:00
aclist
24609a1c71 docs: update changelog 2026-05-21 20:58:32 +09:00
aclist
1b344c1260 chore: clear typehinting errors
Some checks are pending
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
2026-05-21 20:55:01 +09:00
aclist
e72e09eec1 feat: save filters on exit 2026-05-21 20:42:24 +09:00
aclist
00e78b1228 fix: set filter panel to insensitive on lan load 2026-05-21 20:42:00 +09:00
aclist
efddd61ea1 chore: bump version 2026-05-21 01:49:55 +09:00
aclist
d006908bbc fix: statusbar being wiped when calcdist fails on empty row 2026-05-21 01:47:05 +09:00
15 changed files with 79 additions and 13 deletions

View File

@ -34,6 +34,7 @@
- Early load alerts button - Early load alerts button
- Preconnect dialog - Preconnect dialog
- Preconnect warnings/failsafes like filesize - Preconnect warnings/failsafes like filesize
- Save filters per server context between sessions
## Changed ## Changed

View File

@ -1,14 +1,14 @@
# TODO: link to here from sphinx docs # TODO: link to here from sphinx docs
# Installing dev dependencies
uv pip install -e .[dev]
# Setting up precommit hooks # Setting up precommit hooks
pre-commit install pre-commit install
# Fetching submodules # Fetching submodules
git submodule update --recursive --init git submodule update --recursive --init
# Installing dev dependencies
uv pip install -e .[dev]
# Building documentation # Building documentation
sphinx-build -M html source build -a sphinx-build -M html source build -a

View File

@ -34,7 +34,7 @@ DZG-004: On Steam Deck, some mods in the Workshop show a black screen when DZGUI
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
This is a bug in the Steam client that is being tracked at Valve's Steam for Linux issue tracker here: https://github.com/ValveSoftware/steam-for-linux/issues/9598. This is a bug in the Steam client that is being tracked at Valve's Steam for Linux issue tracker here: https://github.com/ValveSoftware/steam-for-linux/issues/9598.
To resolve this issue, manually intervene in the Steam client by selecting a different context (e.g., Store, Library), waiting for it to load, then navigating back to the Workshop context. This should To resolve this issue, manually intervene in the Steam client by selecting a different context (e.g., Store, Library), waiting for it to load, then navigating back to the Workshop context. This should
clear the blockage and allow the contents to render. clear the blockage and allow the contents to render.
.. _DZG-005: .. _DZG-005:

View File

@ -472,6 +472,7 @@ def query_by_ip(addr: str) -> A2SInfo:
return A2SInfo(Record("0", 0, 0), None) return A2SInfo(Record("0", 0, 0), None)
return query_by_record(record, update_gameport=True) return query_by_record(record, update_gameport=True)
def query_by_id(server_id: int, key: str) -> A2SInfo: def query_by_id(server_id: int, key: str) -> A2SInfo:
""" """
Used with numeric Battlemetrics IDs Used with numeric Battlemetrics IDs

View File

@ -17,6 +17,7 @@ class Xdg:
system: Path system: Path
debug: Path debug: Path
ips: Path ips: Path
filters: Path
def is_writeable(path_str: str) -> bool: def is_writeable(path_str: str) -> bool:
@ -63,9 +64,6 @@ def get_xdg_paths() -> dict:
def parse_filepaths(xdg: dict) -> Xdg: def parse_filepaths(xdg: dict) -> Xdg:
# cache = xdg["XDG_CACHE_HOME"]
# data = xdg["XDG_DATA_HOME"]
config = xdg["XDG_CONFIG_HOME"] config = xdg["XDG_CONFIG_HOME"]
state = xdg["XDG_STATE_HOME"] state = xdg["XDG_STATE_HOME"]
@ -80,6 +78,7 @@ def parse_filepaths(xdg: dict) -> Xdg:
history = state / "dzg.history" history = state / "dzg.history"
versions = state / "dzg.versions" versions = state / "dzg.versions"
ips = state / "ips.csv" ips = state / "ips.csv"
filters = state / "dzg.filters.json"
return Xdg( return Xdg(
config, config,
@ -91,4 +90,5 @@ def parse_filepaths(xdg: dict) -> Xdg:
system, system,
debug, debug,
ips, ips,
filters
) )

View File

@ -12,6 +12,7 @@ if TYPE_CHECKING:
# 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
class Emitter(GObject.GObject): class Emitter(GObject.GObject):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
@ -150,3 +151,6 @@ class Emitter(GObject.GObject):
def keyword_set(self, keyword: str) -> None: def keyword_set(self, keyword: str) -> None:
pass pass
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
def lan_page_initialized(self) -> None:
pass

View File

@ -260,6 +260,21 @@ class Controller(GObject.GObject):
def select_colorized(self) -> None: def select_colorized(self) -> None:
self.mod_man.select_colorized() self.mod_man.select_colorized()
def get_all_tree_filters(self) -> dict[str, dict[str, bool]]:
servers = self.get_servers()
all_filters: dict[str, dict[str, bool]] = {}
trees = (
servers.get_browser(),
servers.get_saved(),
servers.get_recent(),
servers.get_lan(),
)
for tree in trees:
enum = str(tree.get_enum())
filters = tree.get_filter_man().get_filters()
all_filters[enum] = filters
return all_filters
def get_filter_man(self) -> "FilterManager": def get_filter_man(self) -> "FilterManager":
"""Each ServerTreeView has an atomic FilterManager""" """Each ServerTreeView has an atomic FilterManager"""
return self.get_active_treeview().get_filter_man() return self.get_active_treeview().get_filter_man()
@ -303,8 +318,21 @@ class Controller(GObject.GObject):
self.emitter.emit("servers_loaded", tv.get_enum()) self.emitter.emit("servers_loaded", tv.get_enum())
return return
self.mediator.statusbar.set_by_context(tv.get_enum(), "") self.mediator.statusbar.set_by_context(tv.get_enum(), "")
self.mediator.filters.button_grid.block_toggles(True)
self.populate_filter_prefs()
self.mediator.filters.button_grid.block_toggles(False)
ServerModelManager(self, tv).load() ServerModelManager(self, tv).load()
def populate_filter_prefs(self) -> None:
tv = self.get_active_treeview()
filters = self.config_man.get_filters(tv)
if filters is None:
return
filter_man = self.get_filter_man()
for filt, state in filters.items():
filter_man.set_filter(filt, state)
self.mediator.filters.set_filters(filters)
def get_dist_cache(self) -> dict[str, "Haversine"]: def get_dist_cache(self) -> dict[str, "Haversine"]:
return self.dist_cache return self.dist_cache

View File

@ -34,6 +34,7 @@
- Early load alerts button - Early load alerts button
- Preconnect dialog - Preconnect dialog
- Preconnect warnings/failsafes like filesize - Preconnect warnings/failsafes like filesize
- Save filters per server context between sessions
## Changed ## Changed

View File

@ -159,6 +159,9 @@ class ConfigManager:
def save_res_and_quit(self, tv: "ServerTreeView", window: "OuterWindow") -> None: def save_res_and_quit(self, tv: "ServerTreeView", window: "OuterWindow") -> None:
columns = tv.get_columns() columns = tv.get_columns()
filters = self.controller.get_all_tree_filters()
filters_file = self.prefs.paths.filters
columns_file = self.prefs.paths.columns columns_file = self.prefs.paths.columns
try: try:
data = read_json(columns_file) data = read_json(columns_file)
@ -173,6 +176,7 @@ class ConfigManager:
try: try:
write_json(data, columns_file) write_json(data, columns_file)
write_json(filters, filters_file)
except Exception as e: except Exception as e:
logger.critical(e) logger.critical(e)
@ -216,3 +220,12 @@ class ConfigManager:
h = WINDOW_DEFAULT_Y h = WINDOW_DEFAULT_Y
logger.info(f"Using default window size {w},{h}") logger.info(f"Using default window size {w},{h}")
window.set_default_size(w, h) window.set_default_size(w, h)
def get_filters(self, tv: "ServerTreeView") -> Any | None:
filters = self.prefs.paths.filters
try:
enum = tv.get_enum()
filters = read_json(filters)
return filters[str(enum)]
except Exception:
return None

View File

@ -73,6 +73,7 @@ class ServerModelManager:
self._dump_history() self._dump_history()
case ServerTab.LAN: case ServerTab.LAN:
# NOTE: LAN tab is only loaded on demand # NOTE: LAN tab is only loaded on demand
self.emitter.emit("lan_page_initialized")
pass pass
case _: case _:
pass pass
@ -123,7 +124,7 @@ class ServerModelManager:
parsed = Servers.parse_json(servers) parsed = Servers.parse_json(servers)
self._push_data(parsed) self._push_data(parsed)
@call_on_thread(dialog.scanning) @call_on_thread(dialog.scanning, show_cancel=True)
def dump_lan(self, port: int, early_abort: bool) -> None: def dump_lan(self, port: int, early_abort: bool) -> None:
self.first_iteration = True self.first_iteration = True
@ -137,6 +138,11 @@ class ServerModelManager:
for future in as_completed(futures): for future in as_completed(futures):
try: try:
if self.controller.get_exit_event().is_set(): if self.controller.get_exit_event().is_set():
event.set()
return
if self.controller.get_cancel_event().is_set():
event.set()
self.controller.clear_cancel_event()
return return
res = future.result(timeout=LAN_TIMEOUT) res = future.result(timeout=LAN_TIMEOUT)
if res is None: if res is None:

View File

@ -68,6 +68,7 @@ class ButtonGrid(Gtk.Grid):
) )
def reload_filters(self) -> None: def reload_filters(self) -> None:
# TODO: may be superfluous
checkboxes = self.checks checkboxes = self.checks
self.block_toggles(True) self.block_toggles(True)
filters = self.controller.get_enabled_filters() filters = self.controller.get_enabled_filters()
@ -330,3 +331,8 @@ class FilterPanel(Gtk.Box):
self.block_map_change_propagation = False self.block_map_change_propagation = False
return return
self.emitter.emit("map_selection_changed", name) self.emitter.emit("map_selection_changed", name)
def set_filters(self, filters: dict[str, bool]) -> None:
for check in self.button_grid.checks:
label = check.get_label()
check.set_active(filters[label])

View File

@ -52,6 +52,7 @@ class RightPanel(Gtk.Box):
emitter = controller.get_emitter() emitter = controller.get_emitter()
emitter.connect("servers_loaded", self._on_servers_loaded) emitter.connect("servers_loaded", self._on_servers_loaded)
emitter.connect("server_page_changed", self._on_server_page_changed) emitter.connect("server_page_changed", self._on_server_page_changed)
emitter.connect("lan_page_initialized", self._on_lan_page_init)
self.button_vbox = ButtonBox(controller) self.button_vbox = ButtonBox(controller)
self.filters_vbox = FilterPanel(controller) self.filters_vbox = FilterPanel(controller)
@ -152,10 +153,15 @@ class RightPanel(Gtk.Box):
return return
self.filters_vbox.set_sensitive(False) self.filters_vbox.set_sensitive(False)
def _on_lan_page_init(self, emitter: "Emitter") -> None:
self.filters_vbox.set_sensitive(False)
self.refresh_button.set_sensitive(False)
def _on_servers_loaded(self, emitter: "Emitter", context: "ServerTab") -> None: def _on_servers_loaded(self, emitter: "Emitter", context: "ServerTab") -> None:
# TODO: similar logic on notebook page change # TODO: similar logic on notebook page change
state = self.controller.has_server_model() state = self.controller.has_server_model()
self.filters_vbox.set_sensitive(state) for el in self.filters_vbox, self.refresh_button:
el.set_sensitive(state)
def _on_version_clicked(self, widget: Gtk.EventBox, event: Gdk.EventButton) -> None: def _on_version_clicked(self, widget: Gtk.EventBox, event: Gdk.EventButton) -> None:
def revert() -> Literal[False]: def revert() -> Literal[False]:

View File

@ -111,9 +111,9 @@ class Statusbar(Gtk.Grid):
context: Union["ServerTab", NotebookPage], context: Union["ServerTab", NotebookPage],
) -> None: ) -> None:
self.spinner.stop() self.spinner.stop()
# TODO: 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:
self.set_by_context(context, "") return
else: else:
pretty = self.append_distance(dist) pretty = self.append_distance(dist)
self.set_by_context(context, pretty) self.set_by_context(context, pretty)

View File

@ -11,5 +11,5 @@ class ModsMixin:
self, tree: TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn self, tree: TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn
) -> None: ) -> None:
mod = self.get_value_at_index(2) mod = self.get_value_at_index(2) # type: ignore
self.controller.open_workshop_page(mod) # type: ignore self.controller.open_workshop_page(mod) # type: ignore

View File

@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [ authors = [
{name = "aclist"} {name = "aclist"}
] ]
version = "7.0.0b3" version = "7.0.0b4"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
license-files = ["LICENSE"] license-files = ["LICENSE"]
readme = "README.md" readme = "README.md"