fix: clean up signal emission

This commit is contained in:
aclist 2026-02-11 20:44:07 +09:00
parent 4946705a8b
commit 25f19cfd10
15 changed files with 116 additions and 72 deletions

View File

@ -2,7 +2,9 @@ from dataclasses import dataclass
from dzgui.config.xdg import Xdg
from dzgui.util.ip import Coords
@dataclass(slots=True, frozen=True)
# NOTE: mutable dataclass, 'use_miles' key changes on demand
@dataclass(slots=True)
class UserPrefs:
is_steam_deck: bool
is_game_mode: bool
@ -11,3 +13,4 @@ class UserPrefs:
version: str
allow_updates: bool
paths: Xdg
use_miles: bool

View File

@ -34,6 +34,7 @@ CLIPBOARD = "edit-copy-symbolic"
LIST_ADD = "list-add-symbolic"
STEAM_ICON = "steam_tray_mono"
VIEW_CONCEAL = "view-conceal-symbolic"
SEARCH_ICON = "system-search-symbolic"
VIEW_REVEAL = "view-reveal-symbolic"
INPUT_KEYBOARD = "input-keyboard-symbolic"
HELP_BUBBLE = "help-about-symbolic"

View File

@ -15,6 +15,7 @@ class Emitter(GObject.GObject):
def __init__(self) -> None:
super().__init__()
# TODO: rename request verbs
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
def request_keyword_focus(self) -> None:
"""User invoked Ctrl-f keybinding from ServerTreeView"""
@ -57,6 +58,14 @@ class Emitter(GObject.GObject):
def distcalc_started(self) -> None:
pass
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(bool,))
def mod_page_toggled(self, state: bool) -> None:
pass
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(bool,))
def lan_tab_toggled(self, state: bool) -> None:
pass
@GObject.Signal(
flags=GObject.SignalFlags.RUN_LAST,
arg_types=(

View File

@ -104,7 +104,7 @@ class Controller(GObject.GObject):
def call_on_thread(func: Callable) -> Callable:
def wrapper(*args, **kwargs):
self = args[0]
self.wait_dialog = WaitDialog(self, strings.dialog.fetching)
self.wait_dialog = WaitDialog(self, strings.dialog.filtering)
self.wait_dialog.show_all()
thread = threading.Thread(target=func, args=args)
thread.start()
@ -253,7 +253,6 @@ class Controller(GObject.GObject):
def set_statusbar_dist(self, haversine: "Haversine", enum: "ServerTab") -> None:
context = self.get_active_context()
print(context)
page = self.mediator.notebook.get_page_by_enum()
"""
NOTE: prevents race condition when server tab changed,
@ -261,7 +260,6 @@ class Controller(GObject.GObject):
"""
if page != NotebookPage.SERVERS:
self.emitter.emit("distcalc_ended" , None, context)
#self.mediator.statusbar.spinner.stop()
return
if enum != context:
self.emitter.emit("distcalc_ended" , None, context)
@ -272,8 +270,7 @@ class Controller(GObject.GObject):
if haversine is None:
dist = "Unknown"
else:
# FIXME: always opens file; cache distance pref at boot and when it changes
if self.query_config(Preferences.DIST) is True:
if self.prefs.use_miles:
raw = round(haversine.as_miles())
separated = number(raw)
dist = str(separated) + " mi"
@ -310,6 +307,9 @@ class Controller(GObject.GObject):
config = self.prefs.paths.config
try:
update.toggle_config(config, context)
# NOTE: 'use_miles' key is updated dynamically for statusbar unit
if context == Preferences.DIST:
self.prefs.use_miles = not self.prefs.use_miles
except Exception as e:
logger.critical(e)
trace = traceback.format_exc()
@ -337,9 +337,6 @@ class Controller(GObject.GObject):
notebook = self.mediator.grid.notebook
notebook.focus_current()
def toggle_mod_panel(self, state: bool) -> None:
self.mediator.grid.right_panel.sel_panel.set_visible(state)
def show_developers_page(self) -> None:
self.open_page(NotebookPage.DEVELOPERS)
@ -631,11 +628,10 @@ class Controller(GObject.GObject):
treeview.grab_focus()
self.destroy_on_idle()
if self.success is False:
# TODO: different dialogs for server tab contexts, e.g. lan timeout
# TODO: if history/favorites is empty, don't even trigger a call to dump data
dialog = ExceptionDialog(self, "API TIMEOUT")
dialog.run()
#if treeview.get_filter_man().get_model() is None:
# TODO: different dialogs for server tab contexts, e.g. lan timeout
# TODO: if history/favorites is empty, don't even trigger a call
def push_data(self, data: tuple, mode: Optional[FilterMode], success: bool) -> None:
@ -772,7 +768,7 @@ class Controller(GObject.GObject):
# FIXME: optional label/map/keyword parameter
def refilter_model(self, mode: FilterMode, label: Optional[str] = None) -> None:
tv = self.get_active_treeview()
if tv.get_model() is None:
if tv.filter_man.get_control() is None:
return
tv.set_model(None)
# TODO: deprecated in this context?

View File

@ -7,7 +7,9 @@ import warnings
from typing import TYPE_CHECKING
from dzgui.api.mods import remove_stale_signatures
from dzgui.const.enum import Preferences
from dzgui.config.ipdb import get_ipdb
from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs
from dzgui.config.xdg import get_xdg_paths, parse_filepaths
from dzgui.const.update import ALLOW_UPDATES
@ -126,6 +128,7 @@ def main() -> None:
# TODO: handle IP DB failure and use coords fallback
get_ipdb(XDG.ips)
local_coords = get_local_coords(XDG.ips)
use_miles = lookup(XDG.config, Preferences.DIST)
prefs = UserPrefs(
_is_steam_deck,
@ -135,6 +138,7 @@ def main() -> None:
version,
allow,
XDG,
use_miles,
)
print("All OK. Loading UI...")
App(prefs)

View File

@ -1,20 +1,26 @@
from typing import TYPE_CHECKING
from dzgui.util.strings import connect_panel, fav_panel, lan_panel
from dzgui.views.components.buttons import AddButton, ClipboardButton, SteamConnectButton
from dzgui.views.components.buttons import (
AddButton,
ClipboardButton,
SteamConnectButton,
)
from dzgui.views.components.entry import IpEntry, PortEntry
from dzgui.views.components.labels import BoldLabel
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk # noqa E402
from gi.repository import Gtk, Gdk # noqa E402
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
from dzgui.controllers.mc import Controller, Emitter
COLS = 1
ROWS = 1
class LanPanel(Gtk.Frame):
def __init__(self, controller: "Controller") -> None:
super().__init__(margin_top=10, margin_bottom=5)
@ -27,20 +33,26 @@ class LanPanel(Gtk.Frame):
self.set_label_widget(label)
radio1 = Gtk.RadioButton.new_with_label(None, lan_panel.default_button)
radio2 = Gtk.RadioButton.new_with_label_from_widget(radio1, lan_panel.custom_button)
radio2 = Gtk.RadioButton.new_with_label_from_widget(
radio1, lan_panel.custom_button
)
self.entry = PortEntry(controller)
self.scan = Gtk.Button(label=lan_panel.scan_button)
self.entry.connect("string_validated", self._on_port_validated)
self.emitter.connect("request_lan_entry_focus", lambda _: self.entry.grab_focus())
self.emitter.connect(
"request_lan_entry_focus", lambda _: self.entry.grab_focus()
)
radio1.connect("toggled", self._on_radio_toggled)
self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5)
self.grid.attach(radio1, 0, 0, COLS, ROWS)
self.grid.attach_next_to(radio2, radio1, Gtk.PositionType.RIGHT, COLS, ROWS)
self.grid.attach_next_to(self.entry, radio2, Gtk.PositionType.RIGHT, COLS, ROWS)
self.grid.attach_next_to(self.scan, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS)
self.grid.attach_next_to(
self.scan, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS
)
self.add(self.grid)
@ -52,6 +64,7 @@ class LanPanel(Gtk.Frame):
def _on_port_validated(self, entry: Gtk.Entry, state: bool) -> None:
self.scan.set_sensitive(state)
class FavPanel(Gtk.Frame):
def __init__(self, controller: "Controller") -> None:
super().__init__(margin_top=10, margin_bottom=5)
@ -63,8 +76,17 @@ class FavPanel(Gtk.Frame):
# TODO: improve upon this, cache fav ip when changed globally
user_fav, self.fav_ip = self.controller.get_favorite()
server_name = f"{user_fav} ({self.fav_ip})" if user_fav is not None else connect_panel.no_fav
self.fav_label = Gtk.Label(label=server_name, track_visited_links=False, halign=Gtk.Align.START, hexpand=True)
server_name = (
f"{user_fav} ({self.fav_ip})"
if user_fav is not None
else connect_panel.no_fav
)
self.fav_label = Gtk.Label(
label=server_name,
track_visited_links=False,
halign=Gtk.Align.START,
hexpand=True,
)
# NOTE: disable vscrollbar to prevent layout jumping behavior
scrollable_label = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER)
scrollable_label.add(self.fav_label)
@ -108,12 +130,18 @@ class AddPanel(Gtk.Frame):
self.entry = IpEntry(controller)
self.entry.connect("string_validated", self._on_ip_validated)
self.emitter.connect("request_ip_entry_focus", lambda _: self.entry.grab_focus())
self.emitter.connect(
"request_ip_entry_focus", lambda _: self.entry.grab_focus()
)
self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5)
self.grid.attach(self.entry, 0, 0, COLS, ROWS)
self.grid.attach_next_to(self.add_server, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS)
self.grid.attach_next_to(self.conn_server, self.add_server, Gtk.PositionType.RIGHT, COLS, ROWS)
self.grid.attach_next_to(
self.add_server, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS
)
self.grid.attach_next_to(
self.conn_server, self.add_server, Gtk.PositionType.RIGHT, COLS, ROWS
)
self.add(self.grid)
@ -127,6 +155,8 @@ class ConnectPanel(Gtk.Box):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.controller = controller
emitter = self.controller.get_emitter()
emitter.connect("lan_tab_toggled", self._on_lan_tab_toggled)
self.lan = LanPanel(controller)
self.fav = FavPanel(controller)
@ -134,3 +164,6 @@ class ConnectPanel(Gtk.Box):
for el in self.lan, self.fav, self.add_panel:
self.add(el)
def _on_lan_tab_toggled(self, emitter: "Emitter", state: bool) -> None:
self.lan.set_visible(state)

View File

@ -2,7 +2,7 @@ import logging
from typing import Literal, TYPE_CHECKING
from dzgui.const.enum import FilterMode
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING, SEARCH_ICON
from dzgui.util import strings
from dzgui.util.margins import set_surrounding_margins
from dzgui.views.components.labels import BoldLabel
@ -42,7 +42,7 @@ class FilterPanel(Gtk.Box):
self.controller.register_widget("filters", self)
self.emitter = self.controller.get_emitter()
map_store = self.controller.get_map_store()
self.map_store = self.controller.get_map_store()
self.checks = []
self.maps_hr = []
@ -86,8 +86,13 @@ class FilterPanel(Gtk.Box):
self.keyword_entry = Gtk.Entry()
# TODO :strings
self.keyword_entry.set_placeholder_text("Filter by keyword")
self.keyword_entry.connect("activate", self._on_keyword_enter)
self.keyword_entry.connect("activate", self._on_keyword_activated)
self.keyword_entry.connect("key-press-event", self._on_keyword_keypress)
self.keyword_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, SEARCH_ICON
)
self.keyword_entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, False)
# entry.connect("icon-release", self._on_icon_release)
self.emitter.connect(
"request_keyword_focus", lambda _: self.keyword_entry.grab_focus()
@ -103,7 +108,7 @@ class FilterPanel(Gtk.Box):
completion.connect("match_selected", self._on_completer_match)
renderer_text = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
self.maps_combo = Gtk.ComboBox.new_with_model_and_entry(map_store)
self.maps_combo = Gtk.ComboBox.new_with_model_and_entry(self.map_store)
self.maps_combo.set_entry_text_column(0)
self.maps_entry = self.maps_combo.get_child()
@ -166,7 +171,7 @@ class FilterPanel(Gtk.Box):
If entry is exact match for value in liststore,
trigger map change function
"""
for i in enumerate(map_store): # type: ignore
for i in enumerate(self.map_store): # type: ignore
if text == i[1][0]:
self.maps_combo.set_active(i[0])
self._on_map_changed(self.maps_combo)
@ -174,7 +179,7 @@ class FilterPanel(Gtk.Box):
GLib.idle_add(self.restore_focus_to_treeview)
"""
This is a workaround for widget.grab_remove()
Sets cursor position to SOL when unfocusing
Sets cursor position to start of line when unfocusing
"""
text = self.maps_entry.get_text()
self.maps_entry.set_position(len(text))
@ -192,9 +197,8 @@ class FilterPanel(Gtk.Box):
def _on_map_completion(self, entry, editable):
text = entry.get_text()
completion = entry.get_completion()
map_store = self.controller.get_map_store()
if len(text) >= completion.get_minimum_key_length():
completion.set_model(map_store)
completion.set_model(self.map_store)
def restore_focus_to_treeview(self) -> Literal[False]:
view = self.controller.get_active_treeview()
@ -232,7 +236,7 @@ class FilterPanel(Gtk.Box):
def get_keyword_filter(self) -> str:
return self.keyword_filter
def _on_keyword_enter(self, entry: Gtk.Entry) -> None:
def _on_keyword_activated(self, entry: Gtk.Entry) -> None:
# TODO:
self.controller.mediator.window.set_keep_below(False)
keyword = entry.get_text().lower()

View File

@ -11,7 +11,7 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa E402
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
from dzgui.controllers.mc import Controller, Emitter
class EnumeratedModButton(Gtk.Button):
@ -28,6 +28,8 @@ class ModSelectionPanel(Gtk.Box):
super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL, margin_top=15)
self.controller = controller
emitter = controller.get_emitter()
emitter.connect("mod_page_toggled", self._on_mod_page_toggled)
header = BoldLabel(mod_panel.header)
@ -58,6 +60,9 @@ class ModSelectionPanel(Gtk.Box):
for el in header, self.main_panel, self.stale_panel:
self.pack_start(el, NO_EXPAND, FILL, NO_PADDING)
def _on_mod_page_toggled(self, emitter: "Emitter", state: bool) -> None:
self.set_visible(state)
def after_colorize(self) -> None:
self.controller.unselect_all_mods()
self.swap_sensitive(True)

View File

@ -29,6 +29,7 @@ class RightPanel(Gtk.Box):
self.button_vbox = ButtonBox(controller)
self.filters_vbox = FilterPanel(controller)
self.sel_panel = ModSelectionPanel(controller)
emitter.connect("servers_loaded", self.toggle_refresh_button)

View File

@ -49,25 +49,10 @@ class Statusbar(Gtk.Grid):
"switch-page", self._on_notebook_page_changed
)
# TODO:
self.emitter.connect("distcalc_started", self._on_distcalc_started)
self.emitter.connect("distcalc_ended", self._on_distcalc_ended)
self.emitter.connect("servers_loaded", self._on_servers_loaded)
# self.connect("distcalc_ended", self._on_distcalc_ended)
# @GObject.Signal(
# flags=GObject.SignalFlags.RUN_LAST,
# arg_types=(
# object,
# object,
# ),
# )
# def distcalc_ended(
# self, dist: Union[str, None], context: Union["ServerTab", NotebookPage]
# ) -> None:
# pass
def _on_notebook_page_changed(
self, notebook: "Notebook", child: Gtk.Widget, index: int
) -> None:
@ -95,7 +80,8 @@ class Statusbar(Gtk.Grid):
case NotebookPage.HELP:
bar = self.controller.get_help_row()
case NotebookPage.SERVERS:
# self.emit("server_page_changed", ServerTab.BROWSER)
context = self.controller.get_active_context()
self.emitter.emit("servers_loaded", context)
return
case NotebookPage.KEYS:
bar = question_to_return

View File

@ -5,6 +5,7 @@ from dzgui.const.enum import NotebookPage
from dzgui.util.css import add_class
from dzgui.util.strings import developers
from dzgui.views.components.labels import BoldLabel
from dzgui.views.trees.tree_base import TreeView
import gi # noqa E402
gi.require_version("Gtk", "3.0")
@ -61,7 +62,6 @@ class Developers(Gtk.ScrolledWindow):
self.controller.open_page(NotebookPage.OPTIONS)
def _make_tree(self, prefs: Union["Xdg", "UserPrefs"]) -> Gtk.TreeView:
from dzgui.views.trees.tree_base import TreeView
view = TreeView(self.controller)
renderer = Gtk.CellRendererText()
for i, col in enumerate(developers.columns):

View File

@ -15,6 +15,7 @@ class Mods(Gtk.ScrolledWindow):
super().__init__()
self.controller = controller
self.emitter = controller.get_emitter()
self.tree = ModTreeView(controller)
self.add(self.tree)
@ -23,12 +24,11 @@ class Mods(Gtk.ScrolledWindow):
self.connect("map", self._on_map)
self.connect("unmap", self._on_unmap)
# TODO: use emitter
def _on_unmap(self, widget: Self) -> None:
self.controller.toggle_mod_panel(False)
self.emitter.emit("mod_page_toggled", False)
def _on_map(self, widget: Self) -> None:
self.controller.toggle_mod_panel(True)
self.emitter.emit("mod_page_toggled", True)
def grab_content_area(self) -> None:
self.tree.grab_focus()

View File

@ -59,8 +59,10 @@ class ServerNotebook(Gtk.ScrolledWindow):
def _on_map(self, widget: Self) -> None:
self.emitter.emit("server_page_toggled", True)
print("page changed, starting queue checker")
self.get_active_treeview().start_timeout()
# FIXME: only applies to server notebook, not atomic page
# this can be delegated to the map/unmap signal of the ServerTreeView itself
# print("page changed, starting queue checker")
# self.get_active_treeview().start_timeout()
def _on_unmap(self, widget: Self) -> None:
self.emitter.emit("server_page_toggled", False)

View File

@ -125,7 +125,6 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
self._on_tree_selection_changed(selection)
def _on_tree_selection_changed(self, selection: Gtk.TreeSelection) -> None:
print("selection changed")
self.emit("generic_treesel_changed", selection)
def toggle_selection(self, state: bool) -> None:

View File

@ -1,7 +1,7 @@
import logging
import multiprocessing
import threading
from typing import Any
from typing import Any, Optional
from warnings import deprecated
from dzgui.views.mixins.context_mixin import ContextMixin
@ -50,7 +50,8 @@ class ServerTreeView(ContextMixin, TreeView):
self.set_fixed_height_mode(True)
self.set_headers_visible(True)
self.queue_id: int = 0
self.queue_id: int
self.handler_id: int
self.current_proc = None
self.queue = multiprocessing.Queue()
@ -108,10 +109,6 @@ class ServerTreeView(ContextMixin, TreeView):
self.connect("key-press-event", self.present_menu)
self.connect("button-press-event", self.present_menu)
# TODO: simplify this
self.emitter.connect("statusbar_loaded", self.start_distcalc)
# self.emitter.connect("distcalc_started", self._on_distcalc_started)
def _get_ping(
self,
column: Gtk.TreeViewColumn,
@ -146,8 +143,6 @@ class ServerTreeView(ContextMixin, TreeView):
pass
def start_timeout(self) -> None:
if self.queue_id:
GLib.Source.remove(self.queue_id)
self.queue_id = GLib.timeout_add(QUEUE_CHECK_DELAY, self._check_result_queue)
def get_filter_man(self) -> FilteredModelManager:
@ -180,12 +175,19 @@ class ServerTreeView(ContextMixin, TreeView):
def _on_map(self, a) -> None:
if self.get_enum() is ServerTab.LAN:
# TODO: use emitter here
self.controller.mediator.grid.conpan.lan.set_visible(True)
self.emitter.emit("lan_tab_toggled", True)
# FIXME: only if the tab is active
self.handler_id = self.emitter.connect("statusbar_loaded", self.start_distcalc)
self.start_timeout()
self.start_distcalc()
def _on_unmap(self, a) -> None:
# NOTE: remove queue checker for this tab
GLib.Source.remove(self.queue_id)
self.emitter.disconnect(self.handler_id)
if self.get_enum() is ServerTab.LAN:
self.controller.mediator.grid.conpan.lan.set_visible(False)
self.emitter.emit("lan_tab_toggled", False)
def set_query_func(self, func: Callable) -> None:
self.query_func = func
@ -206,8 +208,6 @@ class ServerTreeView(ContextMixin, TreeView):
if self.current_proc and self.current_proc.is_alive():
self.current_proc.terminate()
from typing import Optional
def start_distcalc(self, emitter: Optional["Emitter"] = None):
self.terminate_process()
self.emitter.emit("distcalc_started")
@ -230,7 +230,6 @@ class ServerTreeView(ContextMixin, TreeView):
self.current_proc.start()
def _check_result_queue(self) -> Literal[True]:
# TODO: delegate to controller
latest_result = None
while not self.queue.empty():
latest_result = self.queue.get()
@ -242,6 +241,7 @@ class ServerTreeView(ContextMixin, TreeView):
haversine = latest_result[1]
if addr not in cache:
cache[addr] = haversine
# TODO: should be emitting a statusbar signal here instead?
self.controller.set_statusbar_dist(haversine, self.get_enum())
return True
@ -251,6 +251,7 @@ class ServerTreeView(ContextMixin, TreeView):
if event.state is Gdk.ModifierType.CONTROL_MASK:
match event.keyval:
case Gdk.KEY_r:
# TODO: unimplemented
self.refresh_player_count()
case Gdk.KEY_f:
self.emitter.emit("request_keyword_focus")
@ -260,7 +261,7 @@ class ServerTreeView(ContextMixin, TreeView):
self.emitter.emit("request_ip_entry_focus")
case Gdk.KEY_p:
if self.enum is ServerTab.LAN:
self.emitter.emit("request_lan_entry_focus", self.enum)
self.emitter.emit("request_lan_entry_focus")
case Gdk.KEY_c:
record = self.get_record()
self.controller.copy_ip(record)