diff --git a/CHANGELOG.md b/CHANGELOG.md index 90af44b..888263e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - Early load alerts button - Preconnect dialog - Preconnect warnings/failsafes like filesize +- Save filters per server context between sessions ## Changed diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 49f9e49..609fa8a 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -1,14 +1,14 @@ # TODO: link to here from sphinx docs +# Installing dev dependencies +uv pip install -e .[dev] + # Setting up precommit hooks pre-commit install # Fetching submodules git submodule update --recursive --init -# Installing dev dependencies -uv pip install -e .[dev] - # Building documentation sphinx-build -M html source build -a diff --git a/docs/source/kb.rst b/docs/source/kb.rst index 54f0830..2fed949 100644 --- a/docs/source/kb.rst +++ b/docs/source/kb.rst @@ -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. -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. .. _DZG-005: diff --git a/dzgui/api/servers.py b/dzgui/api/servers.py index 490fbda..c73ff67 100644 --- a/dzgui/api/servers.py +++ b/dzgui/api/servers.py @@ -472,6 +472,7 @@ def query_by_ip(addr: str) -> A2SInfo: return A2SInfo(Record("0", 0, 0), None) return query_by_record(record, update_gameport=True) + def query_by_id(server_id: int, key: str) -> A2SInfo: """ Used with numeric Battlemetrics IDs diff --git a/dzgui/config/xdg.py b/dzgui/config/xdg.py index 137d143..2748c8f 100644 --- a/dzgui/config/xdg.py +++ b/dzgui/config/xdg.py @@ -17,6 +17,7 @@ class Xdg: system: Path debug: Path ips: Path + filters: Path def is_writeable(path_str: str) -> bool: @@ -63,9 +64,6 @@ def get_xdg_paths() -> dict: def parse_filepaths(xdg: dict) -> Xdg: - # cache = xdg["XDG_CACHE_HOME"] - # data = xdg["XDG_DATA_HOME"] - config = xdg["XDG_CONFIG_HOME"] state = xdg["XDG_STATE_HOME"] @@ -80,6 +78,7 @@ def parse_filepaths(xdg: dict) -> Xdg: history = state / "dzg.history" versions = state / "dzg.versions" ips = state / "ips.csv" + filters = state / "dzg.filters.json" return Xdg( config, @@ -91,4 +90,5 @@ def parse_filepaths(xdg: dict) -> Xdg: system, debug, ips, + filters ) diff --git a/dzgui/controllers/emitter.py b/dzgui/controllers/emitter.py index a089351..d22d585 100644 --- a/dzgui/controllers/emitter.py +++ b/dzgui/controllers/emitter.py @@ -12,6 +12,7 @@ if TYPE_CHECKING: # TODO: if servers fail to load, may leave dangling widgets waiting for a signal + class Emitter(GObject.GObject): def __init__(self) -> None: super().__init__() @@ -150,3 +151,6 @@ class Emitter(GObject.GObject): def keyword_set(self, keyword: str) -> None: pass + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) + def lan_page_initialized(self) -> None: + pass diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index 5de87dc..e8befc8 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -260,6 +260,21 @@ class Controller(GObject.GObject): def select_colorized(self) -> None: 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": """Each ServerTreeView has an atomic FilterManager""" return self.get_active_treeview().get_filter_man() @@ -303,8 +318,21 @@ class Controller(GObject.GObject): self.emitter.emit("servers_loaded", tv.get_enum()) return 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() + 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"]: return self.dist_cache diff --git a/dzgui/data/CHANGELOG.md b/dzgui/data/CHANGELOG.md index 90af44b..888263e 100644 --- a/dzgui/data/CHANGELOG.md +++ b/dzgui/data/CHANGELOG.md @@ -34,6 +34,7 @@ - Early load alerts button - Preconnect dialog - Preconnect warnings/failsafes like filesize +- Save filters per server context between sessions ## Changed diff --git a/dzgui/managers/config.py b/dzgui/managers/config.py index 12cb23e..e550e19 100644 --- a/dzgui/managers/config.py +++ b/dzgui/managers/config.py @@ -159,6 +159,9 @@ class ConfigManager: def save_res_and_quit(self, tv: "ServerTreeView", window: "OuterWindow") -> None: columns = tv.get_columns() + filters = self.controller.get_all_tree_filters() + filters_file = self.prefs.paths.filters + columns_file = self.prefs.paths.columns try: data = read_json(columns_file) @@ -173,6 +176,7 @@ class ConfigManager: try: write_json(data, columns_file) + write_json(filters, filters_file) except Exception as e: logger.critical(e) @@ -216,3 +220,12 @@ class ConfigManager: h = WINDOW_DEFAULT_Y logger.info(f"Using default window 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 diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index 7876f60..232b5cd 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -73,6 +73,7 @@ class ServerModelManager: self._dump_history() case ServerTab.LAN: # NOTE: LAN tab is only loaded on demand + self.emitter.emit("lan_page_initialized") pass case _: pass @@ -123,7 +124,7 @@ class ServerModelManager: parsed = Servers.parse_json(servers) 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: self.first_iteration = True @@ -137,6 +138,11 @@ class ServerModelManager: for future in as_completed(futures): try: 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 res = future.result(timeout=LAN_TIMEOUT) if res is None: diff --git a/dzgui/views/components/filter_panel.py b/dzgui/views/components/filter_panel.py index eba6735..26f4974 100644 --- a/dzgui/views/components/filter_panel.py +++ b/dzgui/views/components/filter_panel.py @@ -68,6 +68,7 @@ class ButtonGrid(Gtk.Grid): ) def reload_filters(self) -> None: + # TODO: may be superfluous checkboxes = self.checks self.block_toggles(True) filters = self.controller.get_enabled_filters() @@ -330,3 +331,8 @@ class FilterPanel(Gtk.Box): self.block_map_change_propagation = False return 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]) diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index efea8e6..2055190 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -52,6 +52,7 @@ class RightPanel(Gtk.Box): emitter = controller.get_emitter() emitter.connect("servers_loaded", self._on_servers_loaded) 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.filters_vbox = FilterPanel(controller) @@ -152,10 +153,15 @@ class RightPanel(Gtk.Box): return 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: # TODO: similar logic on notebook page change 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 revert() -> Literal[False]: diff --git a/dzgui/views/components/statusbar.py b/dzgui/views/components/statusbar.py index ae4544f..e45ad65 100644 --- a/dzgui/views/components/statusbar.py +++ b/dzgui/views/components/statusbar.py @@ -111,9 +111,9 @@ class Statusbar(Gtk.Grid): context: Union["ServerTab", NotebookPage], ) -> None: 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: - self.set_by_context(context, "") + return else: pretty = self.append_distance(dist) self.set_by_context(context, pretty) diff --git a/dzgui/views/mixins/mods_mixin.py b/dzgui/views/mixins/mods_mixin.py index d3fce76..6cc7da8 100644 --- a/dzgui/views/mixins/mods_mixin.py +++ b/dzgui/views/mixins/mods_mixin.py @@ -11,5 +11,5 @@ class ModsMixin: self, tree: TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn ) -> 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 diff --git a/pyproject.toml b/pyproject.toml index d2bbd2b..adcd6a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux" authors = [ {name = "aclist"} ] -version = "7.0.0b3" +version = "7.0.0b4" license = "GPL-3.0-or-later" license-files = ["LICENSE"] readme = "README.md"