chore: abstract ThreadManager class (WIP)

This commit is contained in:
aclist 2026-02-21 17:44:11 +09:00
parent 42544c6dcc
commit 62da949465
13 changed files with 177 additions and 127 deletions

View File

@ -212,9 +212,9 @@ class ContextMenu(EnumWithAttrs):
ADD_NOTE = {"label": strings.add_note}
ADD_SERVER = {"label": strings.add}
CONNECT = {"label": strings.connect}
COPY_CLIPBOARD = {"label": strings.copy_ip}
COPY_LOG_CLIPBOARD = {"label": strings.copy_log}
COPY_NAME = {"label": strings.copy_name}
COPY_SERVER_IP = {"label": strings.copy_ip}
COPY_SERVER_NAME = {"label": strings.copy_name}
DELETE_MOD = {"label": strings.delete_mod}
OPEN_WORKSHOP = {"label": strings.open_workshop}
REFRESH_PLAYERS = {"label": strings.refresh_players}
@ -236,8 +236,8 @@ class ContextMenuGroup(Enum):
ContextMenu.CONNECT,
ContextMenu.ADD_SERVER,
ContextMenu.SET_FAV,
ContextMenu.COPY_NAME,
ContextMenu.COPY_CLIPBOARD,
ContextMenu.COPY_SERVER_NAME,
ContextMenu.COPY_SERVER_IP,
ContextMenu.ADD_NOTE,
ContextMenu.SHOW_MODS,
ContextMenu.SHOW_DETAILS,
@ -246,8 +246,8 @@ class ContextMenuGroup(Enum):
SCAN_LAN = (
ContextMenu.CONNECT,
ContextMenu.SET_FAV,
ContextMenu.COPY_NAME,
ContextMenu.COPY_CLIPBOARD,
ContextMenu.COPY_SERVER_NAME,
ContextMenu.COPY_SERVER_IP,
ContextMenu.ADD_NOTE,
ContextMenu.SHOW_MODS,
ContextMenu.SHOW_DETAILS,
@ -257,8 +257,8 @@ class ContextMenuGroup(Enum):
ContextMenu.CONNECT,
ContextMenu.SET_FAV,
ContextMenu.REMOVE_SERVER,
ContextMenu.COPY_NAME,
ContextMenu.COPY_CLIPBOARD,
ContextMenu.COPY_SERVER_NAME,
ContextMenu.COPY_SERVER_IP,
ContextMenu.ADD_NOTE,
ContextMenu.SHOW_MODS,
ContextMenu.SHOW_DETAILS,
@ -269,8 +269,8 @@ class ContextMenuGroup(Enum):
ContextMenu.ADD_SERVER,
ContextMenu.SET_FAV,
ContextMenu.REMOVE_HISTORY,
ContextMenu.COPY_NAME,
ContextMenu.COPY_CLIPBOARD,
ContextMenu.COPY_SERVER_NAME,
ContextMenu.COPY_SERVER_IP,
ContextMenu.ADD_NOTE,
ContextMenu.SHOW_MODS,
ContextMenu.SHOW_DETAILS,

View File

@ -43,6 +43,7 @@ from dzgui.config import update
from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs
from dzgui.controllers.emitter import Emitter
from dzgui.model.contextmenu import ContextMenuManager
from dzgui.model.filtered_model import FilteredModelManager
from dzgui.model.misc_model import ModelManager
from dzgui.util import strings
@ -462,6 +463,7 @@ class Controller(GObject.GObject):
def query_ip_id(self, addr: str) -> None:
# NOTE: Battlemetrics
# TODO: stores full ip, gameport, qport in file as canonical record
if addr.isdigit():
config = self.prefs.paths.config
resolved = map_id_to_record(config, addr)
@ -478,24 +480,36 @@ class Controller(GObject.GObject):
res = self.query_ip_id(addr)
def cleanup_on_insert(self) -> None:
print(self.get_cleanup_func())
treeview = self.get_active_treeview()
# TODO; update control model in filtered_model
model = treeview.get_model().append(self.insert_record[0])
# FIXME: grab filterman outside of thread
treeview = self.mediator.servers.saved
filter_man = treeview.get_filter_man()
model = filter_man.get_control()
print(len(model))
# FIXME: refiltration should occur in thread
model.append(self.insert_record[0])
filtered = filter_man.filter(FilterMode.INITIAL)
treeview.set_model(filter_man.get_model())
# TODO: update statusbar
context = self.get_active_context()
# TODO: adding a row may update available maps
# TODO: if all filters are already applied, strange behavior may occur
# need to insert and update all according logic per current filters
self.emitter.emit("servers_loaded", context)
# model = treeview.get_model().append(self.insert_record[0])
@call_on_thread("querying address")
def add_by_id_or_ip(self, addr: str) -> None:
res = self.query_ip_id(addr)
self.insert_record = Servers.parse_json([res])
#if res is None:
# if res is None:
# # TODO: set cleanupfunc with dialog
# return
# TODO: add into saved servers file
# TODO: update saved servers model
# if current focus is not save servers, update label
# TODO: add ip here
self.set_cleanup_func(StoredFunc(lambda: self.cleanup_on_insert()))
# TODO: saved servers might not be loaded yet, in which case
# should just add to server file
self.set_cleanup_func(StoredFunc(self.cleanup_on_insert))
def dump_api(self) -> None:
key = self.query_config(Preferences.STEAM)
@ -540,53 +554,53 @@ class Controller(GObject.GObject):
client = self.query_config(Preferences.CLIENT)
open_user_workshop(uid, client)
def copy_log(self, paths: list[Gtk.TreePath]) -> str:
if len(paths) < 1:
return ""
final = []
for path in paths:
record = self.log_store[path]
r = [el for el in record]
concat = strings.delimiter.join(r)
final.append(concat)
text = "\n".join(final)
return text
# def copy_log(self, paths: list[Gtk.TreePath]) -> str:
# if len(paths) < 1:
# return ""
# final = []
# for path in paths:
# record = self.log_store[path]
# r = [el for el in record]
# concat = strings.delimiter.join(r)
# final.append(concat)
# text = "\n".join(final)
# return text
def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any:
treeview = self.get_active_treeview()
model = treeview.get_model()
if model is None:
return None
value = model[path][index]
return value
# def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any:
# treeview = self.get_active_treeview()
# model = treeview.get_model()
# if model is None:
# return None
# value = model[path][index]
# return value
def copy_name(self, path: Gtk.TreePath) -> None:
name = self.get_col_value_by_path_index(path, 0)
if name is None:
return
self.copy_clipboard(name)
# def copy_name(self, path: Gtk.TreePath) -> None:
# name = self.get_col_value_by_path_index(path, 0)
# if name is None:
# return
# self.copy_clipboard(name)
def copy_ip(self, path: Gtk.TreePath) -> None:
treeview = self.get_active_treeview()
record = treeview.get_record()
self.copy_clipboard(f"{record.ip}:{record.qport}")
# def copy_ip(self, path: Gtk.TreePath) -> None:
# treeview = self.get_active_treeview()
# record = treeview.get_record()
# self.copy_clipboard(f"{record.ip}:{record.qport}")
def copy_clipboard(self, text: str) -> None:
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.clipboard.set_text(text, -1)
# def copy_clipboard(self, text: str) -> None:
# self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
# self.clipboard.set_text(text, -1)
def open_mod_page(self, path: Gtk.TreePath) -> None:
mod, it = self.model_man.get_mod_from_tree_path(path)
cmd = self.query_config(Preferences.CLIENT)
open_workshop_page(mod, cmd)
# def open_mod_page(self, path: Gtk.TreePath) -> None:
# mod, it = self.get_mod_from_tree_path(path)
# cmd = self.query_config(Preferences.CLIENT)
# open_workshop_page(mod, cmd)
def get_mod_from_tree_path(
self, tree_path: Gtk.TreePath
) -> tuple[str, Gtk.TreeIter]:
model = self.get_mod_store()
tree_iter = model.get_iter(tree_path)
mod = model.get(tree_iter, 2)[0]
return mod, tree_iter
# def get_mod_from_tree_path(
# self, tree_path: Gtk.TreePath
# ) -> tuple[str, Gtk.TreeIter]:
# model = self.get_mod_store()
# tree_iter = model.get_iter(tree_path)
# mod = model.get(tree_iter, 2)[0]
# return mod, tree_iter
def delete_single_mod_cleanup(self, _iter: Gtk.TreeIter) -> None:
self.get_mod_store().remove(_iter)
@ -653,61 +667,65 @@ class Controller(GObject.GObject):
simple_ip = treeview.get_simplified_ip()
self.emitter.emit("fav_server_changed", name, simple_ip)
def menu_action(self, action: ContextMenu, path: Gtk.TreePath) -> None:
match action:
# NOTE: manipulates server stores and caches
# add to saved servers model verbatim and sort in place
# update tab with !
# update config file with IP
# TODO: needs threading
case ContextMenu.ADD_SERVER:
pass
def menu_action(self, action: ContextMenu, tree: Gtk.TreeView) -> None:
# TODO: pass prefs into contextman
context_man = ContextMenuManager(tree, self)
context_man.process(action)
# context_man.process(action)
# match action:
# # NOTE: manipulates server stores and caches
# # add to saved servers model verbatim and sort in place
# # update tab with !
# # update config file with IP
# # TODO: needs threading
# case ContextMenu.ADD_SERVER:
# pass
# spawn edit dialog and update cache, notes file
case ContextMenu.ADD_NOTE:
pass
# # spawn edit dialog and update cache, notes file
# case ContextMenu.ADD_NOTE:
# pass
case ContextMenu.COPY_CLIPBOARD:
self.copy_ip(path)
# case ContextMenu.COPY_CLIPBOARD:
# self.copy_ip(path)
case ContextMenu.COPY_NAME:
self.copy_name(path)
# case ContextMenu.COPY_NAME:
# self.copy_name(path)
case ContextMenu.DELETE_MOD:
self.delete_single_mod(path)
# TODO: connect to emitter automatically
# Gtk.TreeModel, row-inserted/row-deleted
# updates statusbar
# FIXME: signal should instead be emitted off of treeview when rows added/inserted
# self.update_mod_statusbar()
# case ContextMenu.DELETE_MOD:
# self.delete_single_mod(path)
# # TODO: connect to emitter automatically
# # Gtk.TreeModel, row-inserted/row-deleted
# # updates statusbar
# # FIXME: signal should instead be emitted off of treeview when rows added/inserted
# # self.update_mod_statusbar()
case ContextMenu.REFRESH_PLAYERS:
# get record
# call a2s on thread
pass
# case ContextMenu.REFRESH_PLAYERS:
# # get record
# # call a2s on thread
# pass
# update history model, update tab label, pop off of queue, write new list into file
# see dq.py
case ContextMenu.REMOVE_HISTORY:
pass
# # update history model, update tab label, pop off of queue, write new list into file
# # see dq.py
# case ContextMenu.REMOVE_HISTORY:
# pass
# reverse of ADD_SERVER
case ContextMenu.REMOVE_SERVER:
pass
# # reverse of ADD_SERVER
# case ContextMenu.REMOVE_SERVER:
# pass
case ContextMenu.SET_FAV:
self.set_fav()
# case ContextMenu.SET_FAV:
# self.set_fav()
# NOTE: spawns dedicated dialogs
# TODO: needs threading
case ContextMenu.SHOW_DETAILS:
pass
# TODO: needs threading
case ContextMenu.SHOW_MODS:
pass
# # NOTE: spawns dedicated dialogs
# # TODO: needs threading
# case ContextMenu.SHOW_DETAILS:
# pass
# # TODO: needs threading
# case ContextMenu.SHOW_MODS:
# pass
case ContextMenu.OPEN_WORKSHOP:
self.open_mod_page(path)
# case ContextMenu.OPEN_WORKSHOP:
# self.open_mod_page(path)
def toggle_mod_selection(self, state: bool) -> None:
sel = self.mediator.modtreeview.get_selection()
@ -715,6 +733,7 @@ class Controller(GObject.GObject):
sel.select_all()
else:
sel.unselect_all()
# TODO: signal
self.mediator.modtreeview.grab_focus()
def populate_log(self) -> None:
@ -786,7 +805,8 @@ class Controller(GObject.GObject):
# CHORE: test if treeview's sort method inserts row at the correct index
# inserting a row serializes file on disk, updates control model for that tab, and
# reapplies filters to ephemeral model; since filters are applied, in-situ insertion might not be necessary
self.to_insert.connect("row-inserted", lambda *args: print("row inserted into model"))
# TODO: will be inserted out of order
# self.to_insert.connect("row-inserted", lambda *args: print("row inserted into model"))
# TODO: signals or other approach to deferring map
# model insertion after thread closes
@ -1088,3 +1108,19 @@ class Controller(GObject.GObject):
def has_server_model(self) -> bool:
treeview = self.get_active_treeview()
return treeview.get_model() is not None
def hide_widgets_on_init(self) -> None:
self.mediator.grid.conpan.set_lan_visible(False)
self.mediator.grid.right_panel.sel_panel.hide()
def get_notebook(self) -> "Notebook":
return self.mediator.notebook
def get_servers(self) -> "Notebook":
return self.mediator.servers.notebook
def get_window(self) -> "OuterWindow":
return self.mediator.window
def get_menu(self) -> "MenuTreeView":
return self.mediator.menu

View File

@ -157,12 +157,12 @@ class OuterWindow(Gtk.Window):
# TODO: first run
MainController.open_page(NotebookPage.SERVERS)
MainController.mediator.grid.conpan.lan.set_visible(False)
self.grid.right_panel.sel_panel.hide()
MainController.hide_widgets_on_init()
# TODO: POC, trigger page change here
MainController.loaded = True
# TODO: debug flag
if MainController.get_prefs().is_debug:
return
MainController.populate_model()
@ -171,7 +171,7 @@ class OuterWindow(Gtk.Window):
self.halt_proc_and_quit()
def halt_proc_and_quit(self) -> None:
#MainController.terminate_process()
# MainController.terminate_process()
MainController.save_res_and_quit()

View File

@ -187,7 +187,6 @@ class AddPanel(Gtk.Frame):
def _on_add_clicked(self, button: Gtk.Button) -> None:
self._submit_query()
def _on_ip_validated(self, entry: Gtk.Entry, state: bool) -> None:
self.conn_server.set_sensitive(state)
self.add_server.set_sensitive(state)
@ -208,5 +207,8 @@ 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:
def set_lan_visible(self, state: bool) -> None:
self.lan.set_visible(state)
def _on_lan_tab_toggled(self, emitter: "Emitter", state: bool) -> None:
self.set_lan_visible(state)

View File

@ -21,8 +21,8 @@ class Breadcrumbs(Gtk.Label):
crumbs = embolden(label_main_menu)
self.set_markup(crumbs)
notebook = controller.mediator.notebook
servers = controller.mediator.servers.notebook
notebook = controller.get_notebook()
servers = controller.get_servers()
notebook.connect_after("switch-page", self._on_notebook_page_changed)
servers.connect_after("switch-page", self._on_server_tab_changed)

View File

@ -109,7 +109,7 @@ class KeywordEntry(Gtk.Entry):
def _on_activated(self, entry: Gtk.Entry) -> None:
# TODO: investigate this method
self.controller.mediator.window.set_keep_below(False)
# self.controller.mediator.window.set_keep_below(False)
keyword = entry.get_text().lower()
if keyword == self.controller.get_keyword():

View File

@ -71,7 +71,5 @@ class RightPanel(Gtk.Box):
# unblock_signals()
# temp_model = self.AppNav.treeview.get_model()
# treeview.set_model(None)
# treeview.wait_dialog = GenericDialog("Pinging servers", Popup.WAIT)
# treeview.wait_dialog.show_all()
# thread = threading.Thread(target=_update_pings, args=())
# thread.start()

View File

@ -42,10 +42,8 @@ class Statusbar(Gtk.Grid):
self.players = ""
controller.mediator.menu.connect(
"generic_treesel_changed", self._help_row_changed
)
controller.mediator.notebook.connect_after(
controller.get_menu().connect("generic_treesel_changed", self._help_row_changed)
controller.get_notebook().connect_after(
"switch-page", self._on_notebook_page_changed
)
@ -83,9 +81,9 @@ class Statusbar(Gtk.Grid):
case NotebookPage.HELP:
bar = self.controller.get_help_row()
# TODO: drop
#case NotebookPage.MODS:
# case NotebookPage.MODS:
# return
#case NotebookPage.SERVERS:
# case NotebookPage.SERVERS:
# context = self.controller.get_active_context()
# # self.emitter.emit("servers_loaded", context)
# return

View File

@ -39,7 +39,7 @@ class GenericDialog(Gtk.MessageDialog):
secondary: str,
) -> None:
super().__init__(
transient_for=controller.mediator.window,
transient_for=controller.get_window(),
message_type=mtype,
buttons=buttons,
text=text,
@ -141,6 +141,7 @@ class WaitDialog(GenericDialog):
class QuitDialog(GenericDialog):
def __init__(self, controller: "Controller", secondary: str):
# FIXME: do not pass controller to every dialog
super().__init__(
controller=controller,
text=strings.wait,
@ -156,6 +157,7 @@ class QuitDialog(GenericDialog):
self.connect("delete-event", self._on_response)
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
# TODO: use a signal?
self.controller.save_res_and_quit()

View File

@ -86,8 +86,9 @@ class ContextMixin(TreeView):
clipboard = self.controller.copy_log(records)
self.controller.copy_clipboard(clipboard)
else:
path = self.get_focused_row_path()
self.controller.menu_action(enum, path)
#path = self.get_focused_row_path()
#self.controller.menu_action(enum, path)
self.controller.menu_action(enum, self)
def _on_key(self, menu: Gtk.Menu, event: Gdk.EventKey) -> bool | None:
if not is_navkey(event.keyval):

View File

@ -1,6 +1,6 @@
import logging
from typing import Optional, TYPE_CHECKING
from typing import Any, Optional, TYPE_CHECKING
from warnings import deprecated
from dzgui.const.constants import SEPARATOR
@ -188,3 +188,10 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
return None
row = model[ind]
return row
def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any:
model = self.get_model()
if model is None:
return None
value = model[path][index]
return value

View File

@ -63,6 +63,14 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
def _on_mod_page_loaded(self, emitter: "Emitter") -> None:
self.set_cursor(0)
def get_selected_mod(self) -> str:
path = self.get_focused_row_path()
model = self.get_model()
tree_iter = model.get_iter(path)
mod = model.get(tree_iter, 2)[0]
return mod
# return mod, tree_iter
# def _on_mods_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
# # TODO: multiselect
# # if event.keyval is Gdk.KEY_space:
@ -88,7 +96,6 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
it: Gtk.TreeIter,
data: Any,
) -> Any:
# https://docs.huihoo.com/pygtk/2.0-tutorial/sec-CellRenderers.html
val = model[it][3]
formatted = localize.number(val)
cell.set_property("text", formatted)

View File

@ -1,5 +1,4 @@
import logging
import multiprocessing
import threading
from typing import Any, Optional, Self
from warnings import deprecated