From 6fc348ea3486f5562220b785c6632510bc865d3f Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:49:06 +0900 Subject: [PATCH] fix: signal emission --- dzgui/controllers/emitter.py | 8 ++++ dzgui/controllers/mc.py | 70 ++++++++++++++++------------- dzgui/init/dayz.py | 1 + dzgui/main.py | 2 +- dzgui/views/components/statusbar.py | 17 +++---- dzgui/views/dialogs/early_alert.py | 18 +++++++- dzgui/views/dialogs/generic.py | 16 ++++++- dzgui/views/trees/tree_servers.py | 13 +++--- 8 files changed, 89 insertions(+), 56 deletions(-) diff --git a/dzgui/controllers/emitter.py b/dzgui/controllers/emitter.py index aa23bef..292f613 100644 --- a/dzgui/controllers/emitter.py +++ b/dzgui/controllers/emitter.py @@ -48,10 +48,18 @@ class Emitter(GObject.GObject): """Triggered on map/unmap signal from NotebookPage.SERVERS. Shows/hides grid panels.""" pass + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) + def statusbar_loaded(self) -> None: + pass + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) def distcalc_started(self) -> None: pass + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object,)) + def servers_loaded(self, tag: "ServerTab") -> None: + pass + @GObject.Signal( flags=GObject.SignalFlags.RUN_LAST, arg_types=( diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index 210afa4..1e4ad79 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -2,6 +2,7 @@ import logging import shutil import threading import traceback +from typing import Optional from warnings import deprecated from concurrent.futures import wait @@ -189,8 +190,6 @@ class Controller(GObject.GObject): widget.handler_block_by_func(func) else: widget.handler_unblock_by_func(func) - # TODO: deprecated? - # self.mediator.menu.sel_blocked = state def toggle_debug_mode(self) -> None: self.toggle_config(Preferences.DEBUG) @@ -377,8 +376,11 @@ class Controller(GObject.GObject): for future in futures: res = future.result() if res.status != 200 or not res.parsed: + # NOTE: this is happening in a thread, need to exit first + # TODO: set internal state and pass failure flag # TODO: pop warning dialog - self.push_data(None, FilterMode.INITIAL) + print("failed to parse/timeout error") + self.push_data(None, success=False) return j = res.json serv += j["response"]["servers"] @@ -391,7 +393,7 @@ class Controller(GObject.GObject): # TODO: ping column pass parsed = Servers.parse_json(serv) self.mediator.filters.set_unique_maps(parsed) - self.push_data(parsed, FilterMode.INITIAL) + self.push_data(parsed, FilterMode.INITIAL, success=True) #def set_unique_maps(self, records: list) -> None: # if len(records) < 1: @@ -615,10 +617,10 @@ class Controller(GObject.GObject): import time time.sleep(1) data = ( - ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], - ["BAR", "a", "a", "a", 1, 1, 1, "172.111.51.156:2302", 0, 0, "a", False], - ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], - ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 1, 1, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "172.111.51.156:2302", 1, 1, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 1, 1, "a", False], + ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 1, 1, "a", False], ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], @@ -627,39 +629,47 @@ class Controller(GObject.GObject): ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], ["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False], ) - self.push_data(data) + self.push_data(data, FilterMode.INITIAL, success=True) def cleanup(self) -> None: + # TODO: if success failed, throw popup treeview = self.get_active_treeview() context = self.get_active_context() # TODO: rename signal - self.mediator.statusbar.emit("server_page_changed", context) - # TODO: may be superfluous + self.emitter.emit("servers_loaded", context) treeview.grab_focus() self.destroy_on_idle() - if len(treeview.filter_man.get_model()) == 0: + if treeview.get_filter_man().get_model() is None: + # FIXME: this may not indicate failure, just empty results # TODO: different dialogs for server tab contexts # TODO: if history/favorites is empty, don't even trigger a call # TODO: add proper string for this dialog - dialog = ExceptionDialog(self, "API TIMEOUT") - dialog.run() + # TODO: this is a placeholder string + # TODO: only pop this is success was failure + if self.success is False: + dialog = ExceptionDialog(self, "API TIMEOUT") + dialog.run() - def push_data(self, data: tuple, mode: FilterMode) -> None: + def push_data(self, data: tuple, mode: Optional[FilterMode], success: bool) -> None: treeview = self.get_active_treeview() # context = self.get_active_context() manager = treeview.get_filter_man() + # TODO: + self.success = success - if data is None: - insert = None - else: - # TODO: consolidate into filter manager - if mode == FilterMode.INITIAL: - manager.set_control(data) - # TODO: init maps here - manager.filter(mode) - insert = manager.get_model() - treeview.set_model(insert) + if success: + if data is None: + insert = None + else: + # TODO: consolidate into filter manager + if mode == FilterMode.INITIAL: + manager.set_control(data) + # TODO: init maps here + manager.filter(mode) + # FIXME: inserting none may cause problems if this was a simple refresh action + insert = manager.get_model() + treeview.set_model(insert) treeview.set_loaded(True) GLib.idle_add(self.cleanup) @@ -775,7 +785,7 @@ class Controller(GObject.GObject): def refilter_model(self, mode: FilterMode, label: str) -> None: tv = self.get_active_treeview() # TODO: shouldn't empty model be None? - if len(tv.get_model()) == 0: + if tv.get_model() is None: return tv.set_model(None) self.set_callback(None, None) @@ -784,12 +794,12 @@ class Controller(GObject.GObject): def populate_model(self) -> None: treeview = self.get_active_treeview() if treeview.is_loaded() is True: - self.mediator.statusbar.emit("server_page_changed", treeview.get_enum()) + self.emitter.emit("servers_loaded", treeview.get_enum()) return func = treeview.get_query_func() if func is None: - self.mediator.statusbar.emit("server_page_changed", treeview.get_enum()) + self.emitter.emit("servers_loaded", treeview.get_enum()) return # TODO: on legacy version, model clearing happens in thread treeview.set_model(None) @@ -812,10 +822,6 @@ class Controller(GObject.GObject): def get_dist_cache(self) -> dict[str, "Haversine"]: return self.dist_cache - # TODO: deprecated in favor of map/unmap - def toggle_lan_panel(self, state: bool) -> None: - self.mediator.grid.conpan.set_visible(state) - # TODO: use model manager, map and keyword caches # TODO: model cache that hooks checkbox signal def get_filters(self) -> list: diff --git a/dzgui/init/dayz.py b/dzgui/init/dayz.py index 1816693..7db9e16 100644 --- a/dzgui/init/dayz.py +++ b/dzgui/init/dayz.py @@ -12,6 +12,7 @@ import dzgui.api.pefile as PeFile logger = logging.getLogger(__name__) + def is_dayz_installed(config: Path) -> None: try: path = lookup(config, Preferences.DEFAULT) diff --git a/dzgui/main.py b/dzgui/main.py index 34d3122..8b2d154 100644 --- a/dzgui/main.py +++ b/dzgui/main.py @@ -32,7 +32,7 @@ from dzgui.util.symlink import rebuild_symlinks from dzgui.util.strings import init, flags from dzgui.views.base import App -from dzgui.views.dialogs.early_alert import EarlyAlertDialog +from dzgui.views.dialogs.early_alert import EarlyAlertDialog, EarlyIgnoreDialog logger = logging.getLogger(__name__) parser = argparse.ArgumentParser(description=flags.description) diff --git a/dzgui/views/components/statusbar.py b/dzgui/views/components/statusbar.py index 95f7130..09892fe 100644 --- a/dzgui/views/components/statusbar.py +++ b/dzgui/views/components/statusbar.py @@ -49,15 +49,10 @@ class Statusbar(Gtk.Grid): ) # TODO: self.emitter.connect("distcalc_started", lambda _: self.spinner.start()) + self.emitter.connect("servers_loaded", self._on_servers_loaded) + self.connect("distcalc_ended", self._on_distcalc_ended) - self.connect("server_page_changed", self._on_server_page_changed) - - # TODO: move to emitter - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object,)) - def server_page_changed(self, tab: ServerTab) -> None: - pass - @GObject.Signal( flags=GObject.SignalFlags.RUN_LAST, arg_types=( @@ -97,7 +92,7 @@ class Statusbar(Gtk.Grid): case NotebookPage.HELP: bar = self.controller.get_help_row() case NotebookPage.SERVERS: - self.emit("server_page_changed", ServerTab.BROWSER) + # self.emit("server_page_changed", ServerTab.BROWSER) return case NotebookPage.KEYS: bar = question_to_return @@ -128,14 +123,12 @@ class Statusbar(Gtk.Grid): def append_distance(self, dist: str) -> str: return f"{self.playercount} | Distance: {dist}" - def _on_server_page_changed(self, statusbar: Self, context: "ServerTab") -> None: + def _on_servers_loaded(self, statusbar: Self, context: "ServerTab") -> None: count = self.controller.get_player_count() self.playercount = count self.set_by_context(context, count) - tree = self.controller.get_active_treeview() - # TODO: emit page change signal on emitter, treeview catches signal and calls distcalc directly - tree.emit("distcalc_started") + self.emitter.emit("statusbar_loaded") def pop(self, context: Union["ServerTab", "NotebookPage"]) -> None: cid = self.statusbar.get_context_id(str(context)) diff --git a/dzgui/views/dialogs/early_alert.py b/dzgui/views/dialogs/early_alert.py index f4de721..3d68925 100644 --- a/dzgui/views/dialogs/early_alert.py +++ b/dzgui/views/dialogs/early_alert.py @@ -27,10 +27,24 @@ class EarlyAlertDialog(Gtk.MessageDialog): self.set_default_size(250, 100) - self.connect("response", self._on_response) + abort = self.get_widget_for_response(Gtk.ResponseType.OK) + abort.set_label("Exit") + self.connect("response", self._on_response) self.run() self.destroy() def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None: - sys.exit(1) + match response: + case Gtk.ResponseType.OK: + sys.exit(1) + case Gtk.ResponseType.CANCEL: + return + +class EarlyIgnoreDialog(EarlyAlertDialog): + def __init__(self, string: str) -> None: + super().__init__(string=string) + + # TODO: reverse order + self.add_button("Ignore", Gtk.ResponseType.CANCEL) + diff --git a/dzgui/views/dialogs/generic.py b/dzgui/views/dialogs/generic.py index b93901b..ef91879 100644 --- a/dzgui/views/dialogs/generic.py +++ b/dzgui/views/dialogs/generic.py @@ -62,6 +62,7 @@ class GenericDialog(Gtk.MessageDialog): def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None: self.destroy() + return response class ConfirmationDialog(GenericDialog): @@ -74,6 +75,19 @@ class ConfirmationDialog(GenericDialog): secondary=secondary, ) +class IgnoreDialog(GenericDialog): + def __init__(self, controller: "Controller", secondary: str): + super().__init__( + controller=controller, + text=strings.confirm, + mtype=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.OK_CANCEL, + secondary=secondary, + ) + cancel = self.get_widget_for_response(Gtk.ResponseType.CANCEL) + cancel.set_label("Ignore") + + class NotifyDialog(GenericDialog): def __init__(self, controller: "Controller", secondary: str): @@ -81,7 +95,7 @@ class NotifyDialog(GenericDialog): controller=controller, text=strings.notice, mtype=Gtk.MessageType.INFO, - buttons=Gtk.ButtonsType.OK, + buttons=Gtk.ButtonsType.OK_CANCEL, secondary=secondary, ) diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index 77ea3ab..1cc87a6 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -24,6 +24,7 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: from dzgui.controllers.mc import Controller + from dzgui.controllers.emitter import Emitter class ServerTreeView(ContextMixin, TreeView): @@ -93,7 +94,6 @@ class ServerTreeView(ContextMixin, TreeView): column.connect("notify::fixed-width", self._on_col_width_changed) self.append_column(column) - self.connect("distcalc_started", self._on_distcalc_started) self.connect("key-press-event", self._on_server_keypress) self.connect("generic_row_activated", self._parent_row_activated) self.connect("generic_treesel_changed", self._parent_selection_changed) @@ -103,12 +103,10 @@ class ServerTreeView(ContextMixin, TreeView): self.connect("key-press-event", self.present_menu) self.connect("button-press-event", self.present_menu) + self.emitter.connect("statusbar_loaded", self._on_distcalc_started) + self.emitter.connect("distcalc_started", self._on_distcalc_started) GLib.timeout_add(QUEUE_CHECK_DELAY, self._check_result_queue) - @GObject.Signal(flags=GObject.SignalFlags.RUN_FIRST, arg_types=()) - def distcalc_started(self) -> None: - pass - def get_filter_man(self) -> FilteredModelManager: return self.filter_man @@ -165,12 +163,11 @@ class ServerTreeView(ContextMixin, TreeView): if self.current_proc and self.current_proc.is_alive(): self.current_proc.terminate() - def _on_distcalc_started(self, treeview: Self): + def _on_distcalc_started(self, emitter: "Emitter"): record = self.get_record() if record is None: return - self.emitter.emit("distcalc_started") cache = self.controller.get_dist_cache() if record.ip in cache: @@ -259,7 +256,7 @@ class ServerTreeView(ContextMixin, TreeView): def _parent_selection_changed(self, base_class: TreeView, sel: Gtk.TreeSelection): self.terminate_process() - self.emit("distcalc_started") + self.emitter.emit("distcalc_started") def get_record_string(self) -> str: addr = self.get_value_at_index(7)