From 18ba851b7e7c6200fbf70ceedc6f01a2845bfed4 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 23 May 2026 22:23:29 +0900 Subject: [PATCH 01/12] chore: drop unused code --- scripts/release.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/release.py b/scripts/release.py index da90073..f48da6a 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -28,7 +28,6 @@ env["PYAPP_PROJECT_NAME"] = appname env["PYAPP_EXEC_SPEC"] = entrypoint env["PYAPP_PROJECT_PATH"] = wheel env["PYAPP_PASS_LOCATION"] = "true" -# env["PYAPP_PYTHON_VERSION"] = "3.13" # NOTE: explicitly install all dependencies into distribution env["PYAPP_SKIP_INSTALL"] = "true" From e715e6da1cc4c2a85a96abf9edd76dee8650e0f2 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 23 May 2026 22:23:44 +0900 Subject: [PATCH 02/12] fix: use LC_ALL var in test --- tests/test_locale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_locale.py b/tests/test_locale.py index 85ebeb1..53a3f59 100644 --- a/tests/test_locale.py +++ b/tests/test_locale.py @@ -2,14 +2,14 @@ from dzgui.util import localize def test_de(monkeypatch): - monkeypatch.setenv("LC_CTYPE", "de_DE.UTF-8") + monkeypatch.setenv("LC_ALL", "de_DE.UTF-8") localize.set_locale() assert localize.number(9900) == "9.900" assert localize.number(99.88) == "99,88" def test_en(monkeypatch): - monkeypatch.setenv("LC_CTYPE", "en_US.UTF-8") + monkeypatch.setenv("LC_ALL", "en_US.UTF-8") localize.set_locale() assert localize.number(9900) == "9,900" assert localize.number(99.88) == "99.88" @@ -22,7 +22,7 @@ def test_none(monkeypatch): def test_empty(monkeypatch): - monkeypatch.setenv("LC_CTYPE", "") + monkeypatch.setenv("LC_ALL", "") localize.set_locale() assert localize.number(9900) == "9,900" assert localize.number(99.88) == "99.88" From 86ba8d91878daaa565a188d02e1e2512505a6ba0 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sun, 24 May 2026 01:34:16 +0900 Subject: [PATCH 03/12] chore: move update logic to manager --- dzgui/managers/update.py | 60 +++++++++++++++++++++++++++ dzgui/views/components/right_panel.py | 49 +++------------------- 2 files changed, 66 insertions(+), 43 deletions(-) create mode 100644 dzgui/managers/update.py diff --git a/dzgui/managers/update.py b/dzgui/managers/update.py new file mode 100644 index 0000000..62b543b --- /dev/null +++ b/dzgui/managers/update.py @@ -0,0 +1,60 @@ +import logging +import requests +import shutil + +from dzgui.const.constants import ( + TMP_EXE, + TMP_PATH, + TMP_TARBALL, + ) +from dzgui.strings import dialogs +from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GLib # noqa E402 + +if TYPE_CHECKING: + from dzgui.controllers.mc import Controller + +logger = logging.getLogger(APP_NAME) + +class UpdateManager: + def __init__(self) -> None: + + self.thread_man = ThreadingManager() + + @call_on_thread(dialogs.fetching_update) + def _update_version(self, exe_path: str, url: str) -> None: + try: + res = requests.get(url) + if res.status_code == 200: + with open(TMP_TARBALL, "wb") as file: + file.write(res.content) + with tarfile.open(TMP_TARBALL) as tar: + tar.extractall(TMP_PATH) + + shutil.move(TMP_EXE, exe_path) + + proc = subprocess.run([exe_path, "self", "restore"]) + if proc.returncode == 0: + func = StoredFunc(self._on_update_success) + self.thread_man.set_cleanup_func(func, destroy_first=True) + else: + msg = dialogs.failed_to_update + func = StoredFunc(self._on_update_failure, msg) + self.thread_man.set_cleanup_func(func, destroy_first=True) + except Exception as e: + func = StoredFunc(self._on_update_failure, e) + self.thread_man.set_cleanup_func(func, destroy_first=True) + logger.warning(e) + + def _on_update_success(self) -> None: + msg = dialogs.update_success + dialog = QuitDialog(self.controller, msg) + dialog.run() + + def _on_update_failure(self, msg: str) -> None: + dialog = ExceptionDialog(self.controller, msg) + dialog.run() diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index 2055190..244eee8 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -19,7 +19,7 @@ from dzgui.const.constants import ( ) from dzgui.const.update import ALLOW_UPDATES from dzgui.const.enum import ServerTab -from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager +from dzgui.managers.update import UpdateManager from dzgui.strings import dialogs from dzgui.util.clip import copy_clipboard from dzgui.views.components.buttonbox import ButtonBox @@ -45,7 +45,6 @@ class RightPanel(Gtk.Box): def __init__(self, controller: "Controller"): super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL) - self.thread_man = ThreadingManager(controller) self.controller = controller self.controller.register_widget("right_panel", self) @@ -96,54 +95,18 @@ class RightPanel(Gtk.Box): valign=Gtk.Align.END, spacing=10, ) - if ALLOW_UPDATES and update is not None: + exe_path = os.getenv("PYAPP") + if exe_path and update is not None: self.update_button.set_halign(Gtk.Align.END) self.gutter_box.add(self.update_button) self.update_button.connect( - "clicked", self._on_update_button_clicked, update + "clicked", self._on_update_button_clicked, exe_path, update ) self.gutter_box.add(eb) self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING) - def _on_update_success(self) -> None: - msg = dialogs.update_success - dialog = QuitDialog(self.controller, msg) - dialog.run() - - def _on_update_failure(self, msg: str) -> None: - dialog = ExceptionDialog(self.controller, msg) - dialog.run() - - @call_on_thread(dialogs.fetching_update) - def _on_update_button_clicked(self, button: Gtk.Button, url: str) -> None: - try: - res = requests.get(url) - if res.status_code == 200: - with open(TMP_TARBALL, "wb") as file: - file.write(res.content) - with tarfile.open(TMP_TARBALL) as tar: - tar.extractall(TMP_PATH) - - exe_path = os.getenv("PYAPP") - if exe_path is None: - msg = dialogs.failed_to_update - func = StoredFunc(self._on_update_failure, msg) - self.thread_man.set_cleanup_func(func, destroy_first=True) - return - shutil.move(TMP_EXE, exe_path) - - proc = subprocess.run([exe_path, "self", "restore"]) - if proc.returncode == 0: - func = StoredFunc(self._on_update_success) - self.thread_man.set_cleanup_func(func, destroy_first=True) - else: - msg = dialogs.failed_to_update - func = StoredFunc(self._on_update_failure, msg) - self.thread_man.set_cleanup_func(func, destroy_first=True) - except Exception as e: - func = StoredFunc(self._on_update_failure, e) - self.thread_man.set_cleanup_func(func, destroy_first=True) - logger.warning(e) + def _on_update_button_clicked(self, button: Gtk.Button, exe_path: str, url: str) -> None: + UpdateManager().update_version() def _on_server_page_changed( self, emitter: "Emitter", page: "ServerTreeView" From 05fd95cd8ee6aafe6bf53e81a40daa50cabc57f5 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sun, 24 May 2026 01:34:22 +0900 Subject: [PATCH 04/12] chore: bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index adcd6a2..5f81c07 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.0b4" +version = "7.0.0b5" license = "GPL-3.0-or-later" license-files = ["LICENSE"] readme = "README.md" From 59959ae3a1922d020d2b9bf3790928895ae66990 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sun, 24 May 2026 01:49:08 +0900 Subject: [PATCH 05/12] chore: clear typehinting errors --- dzgui/managers/update.py | 17 ++++++++++++----- dzgui/views/components/right_panel.py | 12 +----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/dzgui/managers/update.py b/dzgui/managers/update.py index 62b543b..abe6818 100644 --- a/dzgui/managers/update.py +++ b/dzgui/managers/update.py @@ -1,14 +1,19 @@ import logging import requests import shutil +import subprocess +import tarfile +from typing import TYPE_CHECKING from dzgui.const.constants import ( + APP_NAME, TMP_EXE, TMP_PATH, TMP_TARBALL, - ) +) from dzgui.strings import dialogs from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager +from dzgui.views.dialogs.generic import ExceptionDialog, QuitDialog import gi @@ -20,13 +25,15 @@ if TYPE_CHECKING: logger = logging.getLogger(APP_NAME) -class UpdateManager: - def __init__(self) -> None: - self.thread_man = ThreadingManager() +class UpdateManager: + def __init__(self, controller: "Controller") -> None: + + self.thread_man = ThreadingManager(controller) + self.controller = controller @call_on_thread(dialogs.fetching_update) - def _update_version(self, exe_path: str, url: str) -> None: + def update_version(self, exe_path: str, url: str) -> None: try: res = requests.get(url) if res.status_code == 200: diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index 244eee8..48b385b 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -1,9 +1,5 @@ import logging import os -import requests -import shutil -import subprocess -import tarfile from typing import Literal, TYPE_CHECKING @@ -13,20 +9,14 @@ from dzgui.const.constants import ( NO_FILL, FILL, NO_PADDING, - TMP_EXE, - TMP_PATH, - TMP_TARBALL, ) -from dzgui.const.update import ALLOW_UPDATES from dzgui.const.enum import ServerTab from dzgui.managers.update import UpdateManager -from dzgui.strings import dialogs from dzgui.util.clip import copy_clipboard from dzgui.views.components.buttonbox import ButtonBox from dzgui.views.components.filter_panel import FilterPanel from dzgui.views.components.mod_panel import ModSelectionPanel from dzgui.views.components.buttons import IconTextButton, RefreshButton, KeysButton -from dzgui.views.dialogs.generic import ExceptionDialog, QuitDialog import gi @@ -106,7 +96,7 @@ class RightPanel(Gtk.Box): self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING) def _on_update_button_clicked(self, button: Gtk.Button, exe_path: str, url: str) -> None: - UpdateManager().update_version() + UpdateManager(self.controller).update_version() def _on_server_page_changed( self, emitter: "Emitter", page: "ServerTreeView" From 6ec77f65427ae1b7dd506f33817c55ab8064f376 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sun, 24 May 2026 01:53:16 +0900 Subject: [PATCH 06/12] fix: pass update args to manager --- dzgui/views/components/right_panel.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index 48b385b..24da0f4 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -95,8 +95,10 @@ class RightPanel(Gtk.Box): self.gutter_box.add(eb) self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING) - def _on_update_button_clicked(self, button: Gtk.Button, exe_path: str, url: str) -> None: - UpdateManager(self.controller).update_version() + def _on_update_button_clicked( + self, button: Gtk.Button, exe_path: str, url: str + ) -> None: + UpdateManager(self.controller).update_version(exe_path, url) def _on_server_page_changed( self, emitter: "Emitter", page: "ServerTreeView" From 26ee560cb4c08b55add0f06b8c6a3e08d18e12db Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sun, 24 May 2026 18:56:41 +0900 Subject: [PATCH 07/12] chore: test build version after export --- dzgui/views/components/right_panel.py | 2 +- scripts/release.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index 24da0f4..bea007c 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -110,7 +110,7 @@ class RightPanel(Gtk.Box): def _on_lan_page_init(self, emitter: "Emitter") -> None: self.filters_vbox.set_sensitive(False) - self.refresh_button.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 diff --git a/scripts/release.py b/scripts/release.py index f48da6a..ae34d65 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -45,3 +45,9 @@ if proc.returncode == 0: with tarfile.open(tarpath, "w:gz") as tar: tar.add(release_exe, arcname=appname) print(f"Wrote tarfile to '{tarpath}'") + +proc = subprocess.run([release_exe, "-v"], capture_output=True, text=True) +assert proc.stdout.rstrip() == version + +release_exe.unlink() +Path(wheel).unlink() From 0426d50d3ec97b75b9b89f3df0dd941f1df41d3a Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 25 May 2026 00:03:21 +0900 Subject: [PATCH 08/12] fix: refresh increment ov refresh decrement resetting when changing tabs --- dzgui/controllers/mc.py | 13 ++----------- dzgui/model/servers.py | 12 +++++++----- dzgui/views/components/buttons.py | 17 ++++++++++++----- dzgui/views/components/right_panel.py | 6 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index e8befc8..0628502 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -84,7 +84,6 @@ class Controller(GObject.GObject): self.emitter = Emitter() self.emitter.connect("map_selection_changed", self._on_map_selection_changed) self.emitter.connect("check_toggled", self._on_check_toggled) - self.emitter.connect("servers_loaded_init", self._on_servers_loaded_init) # NOTE: suppress requests until entire UI is loaded self.loaded = False @@ -321,6 +320,7 @@ class Controller(GObject.GObject): self.mediator.filters.button_grid.block_toggles(True) self.populate_filter_prefs() self.mediator.filters.button_grid.block_toggles(False) + self.emitter.emit("lan_page_initialized") ServerModelManager(self, tv).load() def populate_filter_prefs(self) -> None: @@ -347,16 +347,7 @@ class Controller(GObject.GObject): filter_man = self.get_filter_man() return filter_man.get_filters() - # TODO: rename - def _on_servers_loaded_init(self, emitter: "Emitter") -> None: - """Triggered after servers load but prior to maps loading""" - # FIXME: wipe maps store when changing tabs if model is none - # e.g. select recent, toggle map, then select lan -> not wiped - tv = self.get_active_treeview() - if tv.loaded is False: - return - store = self.get_map_store() - self.emitter.emit("load_maps", store) + # FIXME: wipe maps store when changing tabs if model is none def has_server_model(self) -> bool: treeview = self.get_active_treeview() diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index 232b5cd..db9cac7 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -243,9 +243,11 @@ class ServerModelManager: filter_man = self.tv.get_filter_man() maps = self._get_new_maps() filter_man.set_unique_maps(maps) + store = filter_man.get_map_store() self.first_iteration = False - self.emitter.emit("servers_loaded_init") + self.emitter.emit("load_maps", store) + # self.emitter.emit("servers_loaded_init") def add_to_history(self, record: dict[str, Any]) -> None: proxy_man = self._get_proxy_man() @@ -378,24 +380,24 @@ class ServerModelManager: # NOTE: maps are set outside of thread because it triggers map changed signals maps = self._get_new_maps() filter_man.set_unique_maps(maps) + store = filter_man.get_map_store() + self.emitter.emit("load_maps", store) self.first_iteration = False - self.emitter.emit("servers_loaded_init") self.emitter.emit("saved_servers_changed") def _update_maps(self) -> None: filter_man = self.tv.get_filter_man() filter_man.set_unique_maps(self._get_new_maps()) - self.emitter.emit("servers_loaded_init") + store = filter_man.get_map_store() + self.emitter.emit("load_maps", store) self.first_iteration = False def _cleanup_on_success(self) -> None: proxy = self._get_proxy_man().get_proxy_model() self.tv.set_model(proxy) - # TODO: servers_loaded vs servers_reloaded self.emitter.emit("servers_loaded", self.enum) - if self.first_iteration: self._update_maps() diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index e15ab5b..92147e1 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -94,9 +94,11 @@ class RefreshButton(IconTextButton): icon=REFRESH_ICON, label=atomic_buttons.refresh, ) + self.controller = controller emitter = self.controller.get_emitter() self.loading = False + self.clicked = False self.time = 30 @@ -110,17 +112,21 @@ class RefreshButton(IconTextButton): def _on_refresh_clicked(self, button: Self) -> None: """Spawned in a thread""" - self.loading = True + self.clicked = True self.controller.refresh_tree() # TODO: get server tab enum # if LAN tab, reload existing entries in place def start_decrement(self, emitter: "Emitter", tab: "ServerTab") -> None: + if self.clicked is False: + return if self.loading: - self.set_sensitive(False) - self.loading = False - self.show_time(True) - GLib.timeout_add_seconds(1, self.decrement) + return + self.set_sensitive(False) + self.loading = True + self.clicked = False + self.show_time(True) + GLib.timeout_add_seconds(1, self.decrement) def decrement(self) -> bool: self.time -= 1 @@ -128,6 +134,7 @@ class RefreshButton(IconTextButton): self.time = 30 self.show_time(False) self.set_sensitive(True) + self.loading = False return False self.show_time(True) return True diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index bea007c..d8d1d21 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -107,16 +107,16 @@ class RightPanel(Gtk.Box): if page.loaded is True: return self.filters_vbox.set_sensitive(False) + # TODO: unless it is lan page + #self.refresh_button.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() - for el in self.filters_vbox, self.refresh_button: - el.set_sensitive(state) + self.filters_vbox.set_sensitive(state) def _on_version_clicked(self, widget: Gtk.EventBox, event: Gdk.EventButton) -> None: def revert() -> Literal[False]: From 72024c5d4327f0fb87ccccf93b38d084a9d0cc46 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 25 May 2026 00:03:21 +0900 Subject: [PATCH 09/12] fix: refresh increment overwriting when changing tabs --- dzgui/controllers/mc.py | 13 ++----------- dzgui/model/servers.py | 12 +++++++----- dzgui/views/components/buttons.py | 17 ++++++++++++----- dzgui/views/components/right_panel.py | 6 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index e8befc8..0628502 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -84,7 +84,6 @@ class Controller(GObject.GObject): self.emitter = Emitter() self.emitter.connect("map_selection_changed", self._on_map_selection_changed) self.emitter.connect("check_toggled", self._on_check_toggled) - self.emitter.connect("servers_loaded_init", self._on_servers_loaded_init) # NOTE: suppress requests until entire UI is loaded self.loaded = False @@ -321,6 +320,7 @@ class Controller(GObject.GObject): self.mediator.filters.button_grid.block_toggles(True) self.populate_filter_prefs() self.mediator.filters.button_grid.block_toggles(False) + self.emitter.emit("lan_page_initialized") ServerModelManager(self, tv).load() def populate_filter_prefs(self) -> None: @@ -347,16 +347,7 @@ class Controller(GObject.GObject): filter_man = self.get_filter_man() return filter_man.get_filters() - # TODO: rename - def _on_servers_loaded_init(self, emitter: "Emitter") -> None: - """Triggered after servers load but prior to maps loading""" - # FIXME: wipe maps store when changing tabs if model is none - # e.g. select recent, toggle map, then select lan -> not wiped - tv = self.get_active_treeview() - if tv.loaded is False: - return - store = self.get_map_store() - self.emitter.emit("load_maps", store) + # FIXME: wipe maps store when changing tabs if model is none def has_server_model(self) -> bool: treeview = self.get_active_treeview() diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index 232b5cd..db9cac7 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -243,9 +243,11 @@ class ServerModelManager: filter_man = self.tv.get_filter_man() maps = self._get_new_maps() filter_man.set_unique_maps(maps) + store = filter_man.get_map_store() self.first_iteration = False - self.emitter.emit("servers_loaded_init") + self.emitter.emit("load_maps", store) + # self.emitter.emit("servers_loaded_init") def add_to_history(self, record: dict[str, Any]) -> None: proxy_man = self._get_proxy_man() @@ -378,24 +380,24 @@ class ServerModelManager: # NOTE: maps are set outside of thread because it triggers map changed signals maps = self._get_new_maps() filter_man.set_unique_maps(maps) + store = filter_man.get_map_store() + self.emitter.emit("load_maps", store) self.first_iteration = False - self.emitter.emit("servers_loaded_init") self.emitter.emit("saved_servers_changed") def _update_maps(self) -> None: filter_man = self.tv.get_filter_man() filter_man.set_unique_maps(self._get_new_maps()) - self.emitter.emit("servers_loaded_init") + store = filter_man.get_map_store() + self.emitter.emit("load_maps", store) self.first_iteration = False def _cleanup_on_success(self) -> None: proxy = self._get_proxy_man().get_proxy_model() self.tv.set_model(proxy) - # TODO: servers_loaded vs servers_reloaded self.emitter.emit("servers_loaded", self.enum) - if self.first_iteration: self._update_maps() diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index e15ab5b..92147e1 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -94,9 +94,11 @@ class RefreshButton(IconTextButton): icon=REFRESH_ICON, label=atomic_buttons.refresh, ) + self.controller = controller emitter = self.controller.get_emitter() self.loading = False + self.clicked = False self.time = 30 @@ -110,17 +112,21 @@ class RefreshButton(IconTextButton): def _on_refresh_clicked(self, button: Self) -> None: """Spawned in a thread""" - self.loading = True + self.clicked = True self.controller.refresh_tree() # TODO: get server tab enum # if LAN tab, reload existing entries in place def start_decrement(self, emitter: "Emitter", tab: "ServerTab") -> None: + if self.clicked is False: + return if self.loading: - self.set_sensitive(False) - self.loading = False - self.show_time(True) - GLib.timeout_add_seconds(1, self.decrement) + return + self.set_sensitive(False) + self.loading = True + self.clicked = False + self.show_time(True) + GLib.timeout_add_seconds(1, self.decrement) def decrement(self) -> bool: self.time -= 1 @@ -128,6 +134,7 @@ class RefreshButton(IconTextButton): self.time = 30 self.show_time(False) self.set_sensitive(True) + self.loading = False return False self.show_time(True) return True diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index bea007c..d8d1d21 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -107,16 +107,16 @@ class RightPanel(Gtk.Box): if page.loaded is True: return self.filters_vbox.set_sensitive(False) + # TODO: unless it is lan page + #self.refresh_button.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() - for el in self.filters_vbox, self.refresh_button: - el.set_sensitive(state) + self.filters_vbox.set_sensitive(state) def _on_version_clicked(self, widget: Gtk.EventBox, event: Gdk.EventButton) -> None: def revert() -> Literal[False]: From ba7a2b7ab428d57a24b96c214f41390c7358a701 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 25 May 2026 15:11:21 +0900 Subject: [PATCH 10/12] fix: fallback for missing entries in columns file --- dzgui/init/migrate.py | 11 ++++++++--- dzgui/views/trees/tree_servers.py | 6 ++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dzgui/init/migrate.py b/dzgui/init/migrate.py index 55af183..0ab7518 100644 --- a/dzgui/init/migrate.py +++ b/dzgui/init/migrate.py @@ -18,15 +18,20 @@ def has_new_config(config: Path) -> bool: def migrate_cols_file(res: Path) -> None: - # NOTE: dzg.columns.json is API 7 spec + # NOTE: filename "dzg.columns.json" is API 7 spec old_res = Path.home() / LEGACY_COLS_PATH if old_res.is_file(): j = read_json(old_res) cols = j["cols"] + # NOTE: implies prior conversion if "View" in cols: return - cols["View"] = cols.pop("Perspective") - cols["Max"] = cols.pop("Maximum") + # NOTE: user may not have changed these widths in API 6 + try: + cols["View"] = cols.pop("Perspective") + cols["Max"] = cols.pop("Maximum") + except Exception: + pass write_json(j, res) diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index c1174fe..ac2b2e3 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -72,6 +72,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore logger.critical(e) valid_json = False + # NOTE: reasonable defaults for long columns width_map = { "Name": 800, "Map": 300, @@ -96,8 +97,9 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore column.set_fixed_width(saved_size) column.set_expand(True) else: - w = width_map[column_title] - column.set_fixed_width(w) + if column_title in width_map: + w = width_map[column_title] + column.set_fixed_width(w) if column_title == "Ping": column.set_cell_data_func(renderer, self._get_ping) From 77505db621d8a62532bf5c99e29c31a41f0a5403 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 25 May 2026 15:13:08 +0900 Subject: [PATCH 11/12] fix: collision with native method name --- dzgui/views/components/buttons.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index 92147e1..c4b3bf2 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -98,7 +98,7 @@ class RefreshButton(IconTextButton): self.controller = controller emitter = self.controller.get_emitter() self.loading = False - self.clicked = False + self.is_clicked = False self.time = 30 @@ -112,19 +112,19 @@ class RefreshButton(IconTextButton): def _on_refresh_clicked(self, button: Self) -> None: """Spawned in a thread""" - self.clicked = True + self.is_clicked = True self.controller.refresh_tree() # TODO: get server tab enum # if LAN tab, reload existing entries in place def start_decrement(self, emitter: "Emitter", tab: "ServerTab") -> None: - if self.clicked is False: + if self.is_clicked is False: return if self.loading: return self.set_sensitive(False) self.loading = True - self.clicked = False + self.is_clicked = False self.show_time(True) GLib.timeout_add_seconds(1, self.decrement) From f849cbc183610dbb22999c1f0ca8e31e2bc23f79 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 25 May 2026 16:07:44 +0900 Subject: [PATCH 12/12] chore: update tests --- dzgui/init/migrate.py | 30 ++++++++++++++++++------------ tests/fixtures/columns_1 | 14 ++++++++++++++ tests/fixtures/columns_2 | 12 ++++++++++++ tests/fixtures/columns_3 | 13 +++++++++++++ tests/test_columns.py | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 tests/fixtures/columns_1 create mode 100644 tests/fixtures/columns_2 create mode 100644 tests/fixtures/columns_3 create mode 100644 tests/test_columns.py diff --git a/dzgui/init/migrate.py b/dzgui/init/migrate.py index 0ab7518..9414b4c 100644 --- a/dzgui/init/migrate.py +++ b/dzgui/init/migrate.py @@ -17,22 +17,28 @@ def has_new_config(config: Path) -> bool: return config.exists() +def convert_cols_file(res: Path) -> dict[str, int] | None: + j = read_json(res) + cols = j["cols"] + # NOTE: implies prior conversion + if "View" in cols: + return None + # NOTE: user may not have changed these widths in API 6 + try: + cols["View"] = cols.pop("Perspective") + cols["Max"] = cols.pop("Maximum") + except Exception: + pass + return j + + def migrate_cols_file(res: Path) -> None: # NOTE: filename "dzg.columns.json" is API 7 spec old_res = Path.home() / LEGACY_COLS_PATH if old_res.is_file(): - j = read_json(old_res) - cols = j["cols"] - # NOTE: implies prior conversion - if "View" in cols: - return - # NOTE: user may not have changed these widths in API 6 - try: - cols["View"] = cols.pop("Perspective") - cols["Max"] = cols.pop("Maximum") - except Exception: - pass - write_json(j, res) + j = convert_cols_file(old_res) + if j is not None: + write_json(j, res) def copy_state_files(state_path: Path) -> None: diff --git a/tests/fixtures/columns_1 b/tests/fixtures/columns_1 new file mode 100644 index 0000000..f4f6ad2 --- /dev/null +++ b/tests/fixtures/columns_1 @@ -0,0 +1,14 @@ +{ + "cols": { + "Name": 1, + "Map": 1, + "Gametime": 1, + "Players": 1, + "Queue": 1, + "IP": 1, + "Qport": 1, + "Ping": 1, + "Maximum": 1, + "Perspective": 1 + } +} diff --git a/tests/fixtures/columns_2 b/tests/fixtures/columns_2 new file mode 100644 index 0000000..5f42780 --- /dev/null +++ b/tests/fixtures/columns_2 @@ -0,0 +1,12 @@ +{ + "cols": { + "Name": 398, + "Map": 181, + "Gametime": 151, + "Players": 119, + "Queue": 96, + "IP": 247, + "Qport": 94, + "Ping": 253 + } +} diff --git a/tests/fixtures/columns_3 b/tests/fixtures/columns_3 new file mode 100644 index 0000000..15a7f9c --- /dev/null +++ b/tests/fixtures/columns_3 @@ -0,0 +1,13 @@ +{ + "cols": { + "Name": 1, + "Map": 1, + "Gametime": 1, + "Players": 1, + "Queue": 1, + "IP": 1, + "Qport": 1, + "Ping": 1, + "View": 1 + } +} diff --git a/tests/test_columns.py b/tests/test_columns.py new file mode 100644 index 0000000..e0a06f6 --- /dev/null +++ b/tests/test_columns.py @@ -0,0 +1,33 @@ +import pytest + +from dzgui.init.migrate import convert_cols_file +from tests.fixtures import fixture_path + +@pytest.fixture +def columns_with_perspective(): + return fixture_path("columns_1") + +@pytest.fixture +def columns_without_perspective(): + return fixture_path("columns_2") + +@pytest.fixture +def columns_with_view(): + return fixture_path("columns_3") + +@pytest.mark.config +def test_columns_with_perspective(columns_with_perspective): + j = convert_cols_file(columns_with_perspective) + assert "View" in j["cols"] + assert "Max" in j["cols"] + +@pytest.mark.config +def test_columns_without_perspective(columns_without_perspective): + j = convert_cols_file(columns_without_perspective) + assert "View" not in j["cols"] + assert "Max" not in j["cols"] + +@pytest.mark.config +def test_columns_with_view(columns_with_view): + j = convert_cols_file(columns_with_view) + assert j is None