fix: statusbar race condition WIP

This commit is contained in:
aclist committed 2026-01-21 12:04:42 +09:00
1 parent 2f9000ee80
commit 9cc1ece0d7
9 files changed
+163 -119

No files matched your search

+52 -44
View File
@@ -1,7 +1,6 @@
import logging import logging
import shutil import shutil
import threading import threading
import textwrap
import traceback import traceback
from pathlib import Path from pathlib import Path
@@ -17,23 +16,16 @@ from dzgui.api.mods import (
get_local_mod_path, get_local_mod_path,
find_stale_mods, find_stale_mods,
_hash, _hash,
remove_stale_signatures remove_stale_signatures,
)
from dzgui.const.enum import (
Preferences,
Popup,
NotebookPage,
ButtonType,
ContextMenu,
RowType
) )
from dzgui.const.enum import Preferences, NotebookPage, ButtonType, ContextMenu, RowType
from dzgui.const.constants import ( from dzgui.const.constants import (
APPID_DAYZ, APPID_DAYZ,
APPID_DAYZ_EXP, APPID_DAYZ_EXP,
HEX_RED, HEX_RED,
WINDOW_DEFAULT_X, WINDOW_DEFAULT_X,
WINDOW_DEFAULT_Y WINDOW_DEFAULT_Y,
) )
from dzgui.config import update from dzgui.config import update
@@ -45,19 +37,20 @@ from dzgui.util.diag import write_diagnostic
from dzgui.util._json import read_json, write_json from dzgui.util._json import read_json, write_json
from dzgui.util.localize import number from dzgui.util.localize import number
from dzgui.util.open_links import open_workshop_page, open_user_workshop from dzgui.util.open_links import open_workshop_page, open_user_workshop
from dzgui.util.format import format_mods, format_player_count, pluralize from dzgui.util.format import format_mods, format_player_count
from dzgui.util.redact import redact_log from dzgui.util.redact import redact_log
from dzgui.views.dialogs.filepicker import FilePicker from dzgui.views.dialogs.filepicker import FilePicker
from dzgui.views.dialogs.generic import GenericDialog
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GLib, GObject # noqa E402 from gi.repository import Gtk, Gdk, GLib, GObject # noqa E402
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.const.enum import ServerTab
from dzgui.util.dist import Haversine from dzgui.util.dist import Haversine
from dzgui.views.base import Notebook, Grid, OuterWindow from dzgui.views.base import Notebook, Grid, OuterWindow
from dzgui.views.components.buttonbox import ContextualButton from dzgui.views.components.buttonbox import ContextualButton
@@ -65,12 +58,12 @@ if TYPE_CHECKING:
from dzgui.views.components.right_panel import RightPanel from dzgui.views.components.right_panel import RightPanel
from dzgui.views.components.statusbar import Statusbar from dzgui.views.components.statusbar import Statusbar
from dzgui.views.pages.servers import ServerNotebook from dzgui.views.pages.servers import ServerNotebook
from dzgui.views.trees.tree_base import TreeView
from dzgui.views.trees.tree_log import LogTreeView from dzgui.views.trees.tree_log import LogTreeView
from dzgui.views.trees.tree_menu import MenuTreeView from dzgui.views.trees.tree_menu import MenuTreeView
from dzgui.views.trees.tree_mods import ModTreeView from dzgui.views.trees.tree_mods import ModTreeView
from dzgui.views.trees.tree_servers import ServerTreeView from dzgui.views.trees.tree_servers import ServerTreeView
class AppNavigation: class AppNavigation:
window: "OuterWindow" window: "OuterWindow"
grid: "Grid" grid: "Grid"
@@ -80,16 +73,16 @@ class AppNavigation:
modtreeview: "ModTreeView" modtreeview: "ModTreeView"
menu: "MenuTreeView" menu: "MenuTreeView"
servers: "ServerNotebook" servers: "ServerNotebook"
browser: "ServerTreeView"
saved: "ServerTreeView" saved: "ServerTreeView"
recent: "ServerTreeView" recent: "ServerTreeView"
lan: "ServerTreeView" lan: "ServerTreeView"
logtreeview: "LogTreeView" logtreeview: "LogTreeView"
filters: "FilterPanel" filters: "FilterPanel"
class Controller: class Controller:
def __init__(self) -> None: def __init__(self) -> None:
self.dist_cache: dict[str, "Haversine"] = {} self.dist_cache: dict[str, "Haversine", "ServerTab"] = {}
self.crumbs_cache = "" self.crumbs_cache = ""
self.mediator = AppNavigation() self.mediator = AppNavigation()
self.prefs: UserPrefs self.prefs: UserPrefs
@@ -128,7 +121,8 @@ class Controller:
def terminate_process(self) -> None: def terminate_process(self) -> None:
# TODO: only used by server table multiprocessing queue # TODO: only used by server table multiprocessing queue
self.mediator.notebook.servers.browser.terminate_process() tv = self.get_active_treeview()
tv.terminate_process()
def get_prefs(self) -> UserPrefs: def get_prefs(self) -> UserPrefs:
return self.prefs return self.prefs
@@ -165,7 +159,9 @@ class Controller:
"_on_tree_selection_changed", "_on_tree_selection_changed",
state, state,
) )
self.suppress_signal(self.mediator.menu, self.mediator.menu, "_on_keypress", state) self.suppress_signal(
self.mediator.menu, self.mediator.menu, "_on_keypress", state
)
for check in self.mediator.filters.checks: for check in self.mediator.filters.checks:
self.suppress_signal( self.suppress_signal(
self.mediator.filters, self.mediator.filters,
@@ -184,7 +180,7 @@ class Controller:
else: else:
widget.handler_unblock_by_func(func) widget.handler_unblock_by_func(func)
# TODO: deprecated? # TODO: deprecated?
#self.mediator.menu.sel_blocked = state # self.mediator.menu.sel_blocked = state
def toggle_debug_mode(self) -> None: def toggle_debug_mode(self) -> None:
self.toggle_config(Preferences.DEBUG) self.toggle_config(Preferences.DEBUG)
@@ -234,14 +230,22 @@ class Controller:
def get_statusbar(self) -> str: def get_statusbar(self) -> str:
return self.mediator.statusbar.get_text() return self.mediator.statusbar.get_text()
def set_statusbar(self, text: str) -> None: def remove_statusbar(self, context: str) -> None:
self.mediator.statusbar.set_text(text) c = self.mediator.statusbar.statusbar.get_context_id(context)
self.mediator.statusbar.statusbar.remove_all(c)
def set_statusbar(self, text: str, context: str) -> None:
self.mediator.statusbar.set_text(text, context)
def set_statusbar_placeholder(self, text: str) -> None: def set_statusbar_placeholder(self, text: str) -> None:
# TODO: use statusbar stacks instead # TODO: use statusbar stacks instead
self.statusbar_placeholder = text self.statusbar_placeholder = text
def set_statusbar_dist(self, haversine: "Haversine") -> None: def set_statusbar_dist(self, haversine: "Haversine", enum: "ServerTab") -> None:
tv = self.get_active_treeview()
if tv.get_enum() != enum:
self.mediator.statusbar.spinner.stop()
return
dist: str dist: str
if haversine is None: if haversine is None:
dist = "Unknown" dist = "Unknown"
@@ -255,7 +259,8 @@ class Controller:
separated = number(raw) separated = number(raw)
dist = str(separated) + " km" dist = str(separated) + " km"
text = self.statusbar_placeholder text = self.statusbar_placeholder
self.set_statusbar(f"{text} | Distance: {dist}") # TODO: abstract
self.set_statusbar(f"{text} | Distance: {dist}", "Servers")
self.mediator.statusbar.spinner.stop() self.mediator.statusbar.spinner.stop()
def delete_multiple_mods(self) -> None: def delete_multiple_mods(self) -> None:
@@ -328,7 +333,7 @@ class Controller:
def show_developers_page(self) -> None: def show_developers_page(self) -> None:
self.open_page(NotebookPage.DEVELOPERS) self.open_page(NotebookPage.DEVELOPERS)
# TODO: put cursor on first row # TODO: put cursor on first row
#self.mediator.developers.focus_first_row() # self.mediator.developers.focus_first_row()
def open_page(self, page: NotebookPage) -> None: def open_page(self, page: NotebookPage) -> None:
self.mediator.grid.notebook.set_page_by_enum(page) self.mediator.grid.notebook.set_page_by_enum(page)
@@ -386,7 +391,9 @@ class Controller:
open_workshop_page(mod, cmd) open_workshop_page(mod, cmd)
# TODO: put in model manager (dedicated manager for mod store) # TODO: put in model manager (dedicated manager for mod store)
def get_mod_from_tree_path(self, tree_path: Gtk.TreePath) -> tuple[str, Gtk.TreeIter]: def get_mod_from_tree_path(
self, tree_path: Gtk.TreePath
) -> tuple[str, Gtk.TreeIter]:
model = self.model_man.get_mod_store() model = self.model_man.get_mod_store()
tree_iter = model.get_iter(tree_path) tree_iter = model.get_iter(tree_path)
mod = model.get(tree_iter, 2)[0] mod = model.get(tree_iter, 2)[0]
@@ -419,15 +426,13 @@ class Controller:
model = self.model_man.get_mod_store() model = self.model_man.get_mod_store()
model.remove(it) model.remove(it)
def update_mod_statusbar(self) -> None: def update_mod_statusbar(self) -> None:
total_mods, total_size = self.calc_mod_size() total_mods, total_size = self.calc_mod_size()
msg = format_mods(total_size, total_mods) msg = format_mods(total_size, total_mods)
# TODO: combine # TODO: combine
meta = self.mediator.statusbar.statusbar.get_context_id("Mods") # meta = self.mediator.statusbar.statusbar.get_context_id("Mods")
self.mediator.statusbar.statusbar.push(meta, msg) # self.mediator.statusbar.statusbar.push(meta, msg)
#meta = self.mediator.statusbar.set_text(msg) self.mediator.statusbar.set_text(msg, "Mods")
def calc_mod_size(self) -> tuple[int, int]: def calc_mod_size(self) -> tuple[int, int]:
model = self.model_man.get_mod_store() model = self.model_man.get_mod_store()
@@ -437,7 +442,6 @@ class Controller:
total_size += mod[3] total_size += mod[3]
return total_mods, total_size return total_mods, total_size
def menu_action(self, action: ContextMenu, path: Gtk.TreePath) -> None: def menu_action(self, action: ContextMenu, path: Gtk.TreePath) -> None:
match action: match action:
# NOTE: manipulates server stores # NOTE: manipulates server stores
@@ -470,14 +474,12 @@ class Controller:
# Gtk.TreeModel, row-inserted/row-deleted # Gtk.TreeModel, row-inserted/row-deleted
self.update_mod_statusbar() self.update_mod_statusbar()
remove_stale_signatures( remove_stale_signatures(
self.prefs.paths.config, self.prefs.paths.config, self.prefs.paths.version
self.prefs.paths.version
) )
case ContextMenu.OPEN_WORKSHOP: case ContextMenu.OPEN_WORKSHOP:
self.open_mod_page(path) self.open_mod_page(path)
def toggle_mod_selection(self, state: bool) -> None: def toggle_mod_selection(self, state: bool) -> None:
sel = self.mediator.modtreeview.get_selection() sel = self.mediator.modtreeview.get_selection()
if state: if state:
@@ -493,7 +495,9 @@ class Controller:
# NOTE: this model is reloaded each time as log changes # NOTE: this model is reloaded each time as log changes
try: try:
with open(log, "r") as f: with open(log, "r") as f:
lines = [line.split(strings.delimiter) for line in f.read().splitlines()] lines = [
line.split(strings.delimiter) for line in f.read().splitlines()
]
for record in lines: for record in lines:
clean = redact_log(record) clean = redact_log(record)
store.append(clean) store.append(clean)
@@ -535,6 +539,7 @@ class Controller:
def dump_test_1(self) -> None: def dump_test_1(self) -> None:
import time import time
time.sleep(1) time.sleep(1)
# TODO: use model managers, etc. # TODO: use model managers, etc.
self.data = ( self.data = (
@@ -561,6 +566,7 @@ class Controller:
def dump_test_2(self) -> None: def dump_test_2(self) -> None:
import time import time
time.sleep(1) time.sleep(1)
# TODO: use model managers, etc. # TODO: use model managers, etc.
self.data = ( self.data = (
@@ -570,7 +576,7 @@ class Controller:
self.get_func_data() self.get_func_data()
self.destroy_on_idle() self.destroy_on_idle()
def get_func_data(self): def get_func_data(self) -> None:
def test(): def test():
# NOTE: do not insert model until main thread is idle # NOTE: do not insert model until main thread is idle
# TODO: grab from model manager and insert entire model # TODO: grab from model manager and insert entire model
@@ -580,6 +586,7 @@ class Controller:
treeview.set_loaded(True) treeview.set_loaded(True)
self.update_server_status() self.update_server_status()
treeview.grab_focus() treeview.grab_focus()
treeview = self.get_active_treeview() treeview = self.get_active_treeview()
manager = treeview.get_filter_man() manager = treeview.get_filter_man()
@@ -591,6 +598,7 @@ class Controller:
def call_on_thread(self, func: Callable, *args) -> None: def call_on_thread(self, func: Callable, *args) -> None:
from dzgui.views.dialogs.generic import WaitDialog from dzgui.views.dialogs.generic import WaitDialog
self.wait_dialog = WaitDialog(self, strings.dialog.fetching) self.wait_dialog = WaitDialog(self, strings.dialog.fetching)
self.wait_dialog.show_all() self.wait_dialog.show_all()
thread = threading.Thread(target=func, args=args) thread = threading.Thread(target=func, args=args)
@@ -607,7 +615,7 @@ class Controller:
Lets calling widgets register a callback function Lets calling widgets register a callback function
when spawning a threaded process when spawning a threaded process
""" """
self.callback = { "func": callback, "args": args } self.callback = {"func": callback, "args": args}
def destroy_on_idle(self) -> None: def destroy_on_idle(self) -> None:
self.wait_dialog.destroy() self.wait_dialog.destroy()
@@ -642,7 +650,6 @@ class Controller:
dialog = ExceptionDialog(self, strings.api_error) dialog = ExceptionDialog(self, strings.api_error)
dialog.run() dialog.run()
def update_api_key(self, text: str, key: Preferences) -> None: def update_api_key(self, text: str, key: Preferences) -> None:
self.call_on_thread(self.test_api_response, text, key) self.call_on_thread(self.test_api_response, text, key)
@@ -688,13 +695,15 @@ class Controller:
def update_server_status(self) -> None: def update_server_status(self) -> None:
# TODO: emit signal on statusbar only if page changed # TODO: emit signal on statusbar only if page changed
treeview = self.get_active_treeview() treeview = self.get_active_treeview()
context = str(treeview.enum)
model = treeview.get_model() model = treeview.get_model()
status = format_player_count(model) status = format_player_count(model)
self.set_statusbar_placeholder(status) self.set_statusbar_placeholder(status)
self.set_statusbar(status)
self.set_statusbar(status, context)
if len(model) >= 1:
self.mediator.statusbar.spinner.start() self.mediator.statusbar.spinner.start()
if len(model) < 1: # self.mediator.statusbar.spinner.stop()
self.mediator.statusbar.spinner.stop()
def populate_model(self) -> None: def populate_model(self) -> None:
treeview = self.get_active_treeview() treeview = self.get_active_treeview()
@@ -718,8 +727,8 @@ class Controller:
crumbs = self.mediator.servers.get_cached_label() crumbs = self.mediator.servers.get_cached_label()
self.set_crumbs(crumbs) self.set_crumbs(crumbs)
# TODO: could emit this when treeview gains keyboard focus # TODO: could emit this when treeview gains keyboard focus
#tree = self.get_active_treeview() # tree = self.get_active_treeview()
#tree.emit("on_distcalc_started") # tree.emit("on_distcalc_started")
def toggle_check(self, event: Gdk.EventKey) -> None | Literal[False]: def toggle_check(self, event: Gdk.EventKey) -> None | Literal[False]:
mappings = { mappings = {
@@ -739,7 +748,6 @@ class Controller:
if event.keyval not in mappings: if event.keyval not in mappings:
return False return False
index = mappings[event.keyval] index = mappings[event.keyval]
# TODO: register filter panel widget
self.mediator.filters.toggle_check(index) self.mediator.filters.toggle_check(index)
def get_favorite(self) -> tuple[str, str] | tuple[None, None]: def get_favorite(self) -> tuple[str, str] | tuple[None, None]:
+7 -2
View File
@@ -44,10 +44,12 @@ args = parser.parse_args()
if TYPE_CHECKING: if TYPE_CHECKING:
from pathlib import Path from pathlib import Path
# TODO: profile load time # TODO: profile load time
def make_parents(path: "Path") -> None: def make_parents(path: "Path") -> None:
path.parent.mkdir(parents=True, exist_ok=True) path.parent.mkdir(parents=True, exist_ok=True)
def uninstall() -> None: def uninstall() -> None:
# TODO: uninstall data files (-u) # TODO: uninstall data files (-u)
# -u removes state, log, freedesktop # -u removes state, log, freedesktop
@@ -56,6 +58,7 @@ def uninstall() -> None:
# XDG_DATA_HOME/dzgui # XDG_DATA_HOME/dzgui
pass pass
def main() -> None: def main() -> None:
lock = lock_acquire() # noqa lock = lock_acquire() # noqa
@@ -85,7 +88,9 @@ def main() -> None:
migrate_cols_file(XDG.columns) migrate_cols_file(XDG.columns)
copy_state_files(xdg_paths["XDG_STATE_HOME"]) copy_state_files(xdg_paths["XDG_STATE_HOME"])
_format = "%(asctime)s␞%(levelname)s␞%(filename)s::%(funcName)s::%(lineno)s␞%(message)s" _format = (
"%(asctime)s␞%(levelname)s␞%(filename)s::%(funcName)s::%(lineno)s␞%(message)s"
)
logging.basicConfig(filename=XDG.debug, format=_format, level=logging.DEBUG) logging.basicConfig(filename=XDG.debug, format=_format, level=logging.DEBUG)
with open(XDG.debug, "w") as f: with open(XDG.debug, "w") as f:
f.truncate(0) f.truncate(0)
@@ -126,7 +131,7 @@ def main() -> None:
local_coords, local_coords,
version, version,
allow, allow,
XDG XDG,
) )
print("All OK. Loading UI...") print("All OK. Loading UI...")
App(prefs) App(prefs)
+10 -6
View File
@@ -4,19 +4,20 @@ from math import radians, cos, sin, asin, sqrt
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from dzgui.util.ip import GeolocationError, get_coords from dzgui.util.ip import GeolocationError, get_coords
from dzgui.util.localize import number
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa E402 from gi.repository import Gtk # noqa E402
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.util.ip import Coords from dzgui.const.enum import ServerTab
from dzgui.controllers.mc import Controller from dzgui.controllers.mc import Controller
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Haversine():
class Haversine:
def __init__(self, lat1: float, lon1: float, lat2: float, lon2: float) -> None: def __init__(self, lat1: float, lon1: float, lat2: float, lon2: float) -> None:
R = 6371 * 1000 R = 6371 * 1000
@@ -26,7 +27,7 @@ class Haversine():
lat1 = radians(lat1) lat1 = radians(lat1)
lat2 = radians(lat2) lat2 = radians(lat2)
a = sin(dLat/2) ** 2 + cos(lat1) * cos(lat2) * sin(dLon/2) ** 2 a = sin(dLat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dLon / 2) ** 2
c = 2 * asin(sqrt(a)) c = 2 * asin(sqrt(a))
self.dist = R * c self.dist = R * c
@@ -37,15 +38,18 @@ class Haversine():
def as_miles(self) -> float: def as_miles(self) -> float:
return self.dist / 1609.344 return self.dist / 1609.344
class CalcDist(multiprocessing.Process): class CalcDist(multiprocessing.Process):
def __init__( def __init__(
self, self,
addr: str, addr: str,
enum: "ServerTab",
result_queue: multiprocessing.Queue, result_queue: multiprocessing.Queue,
controller: "Controller", controller: "Controller",
) -> None: ) -> None:
super().__init__() super().__init__()
self.enum = enum
self.controller = controller self.controller = controller
self.result_queue = result_queue self.result_queue = result_queue
self.addr = addr self.addr = addr
@@ -55,11 +59,11 @@ class CalcDist(multiprocessing.Process):
cache = self.controller.get_dist_cache() cache = self.controller.get_dist_cache()
if self.addr in cache: if self.addr in cache:
logger.info(f"Address '{self.addr}' already in cache") logger.info(f"Address '{self.addr}' already in cache")
self.result_queue.put([self.addr, cache[self.addr]]) self.result_queue.put([self.addr, cache[self.addr], self.enum])
return return
dist = self.compare(self.ip) dist = self.compare(self.ip)
self.result_queue.put([self.addr, dist]) self.result_queue.put([self.addr, dist, self.enum])
def compare(self, remote: str) -> int | None: def compare(self, remote: str) -> int | None:
prefs = self.controller.get_prefs() prefs = self.controller.get_prefs()
+32 -26
View File
@@ -1,13 +1,7 @@
import logging import logging
#import multiprocessing
import signal import signal
#import threading
import typing # noqa
import warnings import warnings
#from concurrent.futures import wait
#from concurrent.futures import ThreadPoolExecutor
from typing import TYPE_CHECKING, Literal from typing import TYPE_CHECKING, Literal
from dzgui.const.enum import NotebookPage from dzgui.const.enum import NotebookPage
@@ -37,6 +31,7 @@ if TYPE_CHECKING:
from dzgui.config.userprefs import UserPrefs from dzgui.config.userprefs import UserPrefs
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402 from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402
@@ -46,10 +41,10 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# TODO: move to controller # TODO: move to controller
# kilometer cache; note, user may change measurement it partway through, flush cache # kilometer cache; note, user may change measurement it partway through, flush cache
#cache: dict[str, int] = {} # cache: dict[str, int] = {}
## TODO: move to configs/servers ## TODO: move to configs/servers
#def query_history() -> list | None: # def query_history() -> list | None:
# history_file = MainController.get_prefs().paths.history # history_file = MainController.get_prefs().paths.history
# try: # try:
# with open(history_file, "r") as f: # with open(history_file, "r") as f:
@@ -58,7 +53,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# rows = None # rows = None
# return rows # return rows
# #
#def process_tree_option(choice: RowType) -> None: # def process_tree_option(choice: RowType) -> None:
# # server tables # # server tables
# if command == RowType.RESOLVE_IP: # if command == RowType.RESOLVE_IP:
# record = treeview.get_record() # record = treeview.get_record()
@@ -91,18 +86,18 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# return # return
# #
## TODO: belongs in model ## TODO: belongs in model
#def str_to_record(record: str) -> Record | None: # def str_to_record(record: str) -> Record | None:
# r = record.split(":") # r = record.split(":")
# if len(r) != 3: # if len(r) != 3:
# return None # return None
# return Record(r[0], int(r[1]), int(r[2])) # return Record(r[0], int(r[1]), int(r[2]))
# #
## TODO: ibid ## TODO: ibid
#def record_to_str(record: Record) -> str: # def record_to_str(record: Record) -> str:
# return f"{record.ip}:{record.gameport}:{record.qport}" # return f"{record.ip}:{record.gameport}:{record.qport}"
# #
# #
#def connect_by_ip(enum: RowType, response: str) -> None: # def connect_by_ip(enum: RowType, response: str) -> None:
# def _prep(response: str) -> None: # def _prep(response: str) -> None:
# record = Servers.validate_ip(response) # record = Servers.validate_ip(response)
# proc = treeview.prepare_connection(record) # proc = treeview.prepare_connection(record)
@@ -114,7 +109,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# return # return
# #
# #
#def connect_by_id(enum: RowType, uid: str, key: str) -> None: # def connect_by_id(enum: RowType, uid: str, key: str) -> None:
# def _prep(key: str, response: str) -> None: # def _prep(key: str, response: str) -> None:
# # TODO: if response is non numeric, raise dialog # # TODO: if response is non numeric, raise dialog
# if response.isnumeric() is False: # if response.isnumeric() is False:
@@ -139,6 +134,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# #
# #
class OuterWindow(Gtk.Window): class OuterWindow(Gtk.Window):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER) super().__init__(title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER)
@@ -168,9 +164,7 @@ class OuterWindow(Gtk.Window):
MainController.loaded = True MainController.loaded = True
MainController.populate_model() MainController.populate_model()
def _on_delete_event( def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
self, window: "OuterWindow", event: Gdk.EventKey
) -> None:
self.halt_proc_and_quit() self.halt_proc_and_quit()
def halt_proc_and_quit(self) -> None: def halt_proc_and_quit(self) -> None:
@@ -316,7 +310,7 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
MainController.set_crumbs(crumbs) MainController.set_crumbs(crumbs)
if status is False: if status is False:
MainController.set_statusbar("") MainController.set_statusbar("", "")
if enum is NotebookPage.SERVERS: if enum is NotebookPage.SERVERS:
MainController.present_servers() MainController.present_servers()
@@ -351,8 +345,20 @@ class Grid(Gtk.Grid):
els = ( els = (
(self.crumb_box, self.notebook, Gtk.PositionType.TOP, MAX_COLS, SINGLE_ROW), (self.crumb_box, self.notebook, Gtk.PositionType.TOP, MAX_COLS, SINGLE_ROW),
(self.conpan, self.notebook, Gtk.PositionType.BOTTOM, MAX_COLS, SINGLE_ROW), (self.conpan, self.notebook, Gtk.PositionType.BOTTOM, MAX_COLS, SINGLE_ROW),
(self.statusbar, self.conpan, Gtk.PositionType.BOTTOM, MAX_COLS, SINGLE_ROW), (
(self.right_panel, self.notebook, Gtk.PositionType.RIGHT, SINGLE_COL, MAX_ROWS), self.statusbar,
self.conpan,
Gtk.PositionType.BOTTOM,
MAX_COLS,
SINGLE_ROW,
),
(
self.right_panel,
self.notebook,
Gtk.PositionType.RIGHT,
SINGLE_COL,
MAX_ROWS,
),
) )
for el, sibling, pos, h_span, v_span in els: for el, sibling, pos, h_span, v_span in els:
self.attach_next_to(el, sibling, pos, h_span, v_span) self.attach_next_to(el, sibling, pos, h_span, v_span)
@@ -372,7 +378,7 @@ class Grid(Gtk.Grid):
def toggle_refresh_button(self, state: bool) -> None: def toggle_refresh_button(self, state: bool) -> None:
self.right_panel.refresh_button.set_visible(state) self.right_panel.refresh_button.set_visible(state)
# TODO make this method internal to Statusbar # TODO: make this method internal to Statusbar
def get_breadcrumbs(self) -> str: def get_breadcrumbs(self) -> str:
return self.breadcrumbs.get_text() return self.breadcrumbs.get_text()
@@ -397,9 +403,7 @@ class App(Gtk.Application):
) )
self.win.add_accel_group(accel) self.win.add_accel_group(accel)
GLib.unix_signal_add( GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint)
GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint
)
Gtk.main() Gtk.main()
def _catch_sigint(self) -> Literal[True]: def _catch_sigint(self) -> Literal[True]:
@@ -415,9 +419,11 @@ class App(Gtk.Application):
) -> None: ) -> None:
self.win.halt_proc_and_quit() self.win.halt_proc_and_quit()
MainController = Controller() MainController = Controller()
# TODO: icon sanity check, log result # TODO: icon sanity check, log result
#theme = Gtk.IconTheme.get_default() # theme = Gtk.IconTheme.get_default()
#icons = theme.list_icons(None) # icons = theme.list_icons(None)
#print("steam_tray_mono" in icons) # print("steam_tray_mono" in icons)
# FIXME:
+3
View File
@@ -93,6 +93,9 @@ class RefreshButton(IconTextButton):
self.connect("clicked", self._on_refresh_clicked) self.connect("clicked", self._on_refresh_clicked)
def _on_refresh_clicked(self, button: Self) -> None: def _on_refresh_clicked(self, button: Self) -> None:
# TODO: get page enum
print(self.controller.mediator.notebook.get_page_by_enum())
# if .MODS, .LOG, .OPTIONS, reload in place
self.set_sensitive(False) self.set_sensitive(False)
self.show_time(True) self.show_time(True)
GLib.timeout_add_seconds(1, self.decrement) GLib.timeout_add_seconds(1, self.decrement)
+3 -2
View File
@@ -26,13 +26,14 @@ class InfoEventBox(Gtk.EventBox):
self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing
) -> None: ) -> None:
self.icon.set_opacity(1) self.icon.set_opacity(1)
self.parent.controller.set_statusbar(self.text) self.parent.controller.set_statusbar(self.text, "Options")
def _on_leave_tooltip( def _on_leave_tooltip(
self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing
) -> None: ) -> None:
self.icon.set_opacity(0.8) self.icon.set_opacity(0.8)
self.parent.controller.set_statusbar("") #self.parent.controller.set_statusbar("", "Options")
self.parent.controller.remove_statusbar("Options")
def set_text(self, text: str) -> None: def set_text(self, text: str) -> None:
self.text = text self.text = text
+28 -6
View File
@@ -19,6 +19,7 @@ class Statusbar(Gtk.Grid):
help_text = strings.statusbar_helptext help_text = strings.statusbar_helptext
self.context: int
self.statusbar = Gtk.Statusbar() self.statusbar = Gtk.Statusbar()
self.spinner = Gtk.Spinner() self.spinner = Gtk.Spinner()
@@ -31,7 +32,7 @@ class Statusbar(Gtk.Grid):
self.attach_next_to(self.spinner, self.statusbar, Gtk.PositionType.RIGHT, 3, 1) self.attach_next_to(self.spinner, self.statusbar, Gtk.PositionType.RIGHT, 3, 1)
self.attach_next_to(self.status_right_label, self.spinner, Gtk.PositionType.RIGHT, 3, 1) self.attach_next_to(self.status_right_label, self.spinner, Gtk.PositionType.RIGHT, 3, 1)
self.set_text(help_text) self.set_text(help_text, "Help")
self.players = "" self.players = ""
def get_text(self) -> str: def get_text(self) -> str:
@@ -39,20 +40,41 @@ class Statusbar(Gtk.Grid):
label = area.get_children()[0] label = area.get_children()[0]
return label.get_text() return label.get_text()
def set_text(self, string: str) -> None: def set_text(self, string: str, context: str) -> None:
if string is None: # if string is None:
return # return
meta = self.statusbar.get_context_id("Statusbar") meta = self.statusbar.get_context_id(context)
#tv = self.controller.get_active_treeview()
#cur_context = tv.get_enum()
#cid = self.get_context_by_enum(cur_context)
#if cid != meta:
# print("requested: ", meta)
# print("current: ", cid)
# return
# TODO: substacks # TODO: substacks
# get_context_id(ServerTab) # get_context_id(ServerTab)
self.statusbar.push(meta, string) self.statusbar.push(meta, string)
#self.set_context(meta)
# TODO: type checking
# def get_context_by_enum(self, context: "ServerTab") -> int:
# cid = self.statusbar.get_context_id(str(context))
# # TODO: substacks
# return cid
#
# def get_context(self) -> int:
# return self.context
#
# def set_context(self, context: int) -> None:
# self.context = context
def refresh(self, row: "RowType") -> None: def refresh(self, row: "RowType") -> None:
if row is None: if row is None:
formatted = "" formatted = ""
else: else:
formatted = self.format_metadata(row) formatted = self.format_metadata(row)
self.set_text(formatted) self.set_text(formatted, "Help")
def append_distance(self, dist: str) -> None: def append_distance(self, dist: str) -> None:
# TODO: process strings in controller # TODO: process strings in controller
+2
View File
@@ -78,6 +78,8 @@ class ServerNotebook(Gtk.ScrolledWindow):
def _on_page_changed(self, notebook: Gtk.Notebook, child: Gtk.Widget, index: int) -> None: def _on_page_changed(self, notebook: Gtk.Notebook, child: Gtk.Widget, index: int) -> None:
if self.controller.loaded is False: if self.controller.loaded is False:
return return
# self.controller.remove_statusbar("Servers")
# TODO: abstract # TODO: abstract
label = self.notebook.get_tab_label_text(child) label = self.notebook.get_tab_label_text(child)
+26 -33
View File
@@ -1,29 +1,24 @@
import logging import logging
import multiprocessing import multiprocessing
import subprocess
from typing import Self from typing import Self
import gi from dzgui.const.enum import ContextMenu, ContextMenuGroup, FilterMode, ServerTab
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
from dzgui.const.enum import (
ContextMenu,
ContextMenuGroup,
RowType,
)
from dzgui.const.constants import UDP_PORT
from dzgui.const.enum import FilterMode, ServerTab
from dzgui.api.servers import Record from dzgui.api.servers import Record
from dzgui.model.filtered_model import FilteredModelManager from dzgui.model.filtered_model import FilteredModelManager
from dzgui.util.dist import CalcDist from dzgui.util.dist import CalcDist
from dzgui.util.keys import is_navkey from dzgui.util.keys import is_navkey
from dzgui.util import strings from dzgui.util import strings
from typing import Any, Callable, Literal, TYPE_CHECKING from typing import Callable, Literal, TYPE_CHECKING
from dzgui.views.trees.tree_base import TreeView from dzgui.views.trees.tree_base import TreeView
import dzgui.util._json as JSON # noqa import dzgui.util._json as JSON # noqa
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -31,6 +26,7 @@ if TYPE_CHECKING:
# TODO: restore missing methods # TODO: restore missing methods
class EnumeratedMenuItem(Gtk.MenuItem): class EnumeratedMenuItem(Gtk.MenuItem):
def __init__(self, enum: ContextMenu): def __init__(self, enum: ContextMenu):
super().__init__(label=enum.dict["label"]) super().__init__(label=enum.dict["label"])
@@ -38,10 +34,11 @@ class EnumeratedMenuItem(Gtk.MenuItem):
class ServerTreeView(TreeView): class ServerTreeView(TreeView):
__gsignals__ = { __gsignals__ = {"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())}
"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())
} def __init__(
def __init__(self, controller: "Controller", enum: ServerTab, menu: ContextMenuGroup) -> None: self, controller: "Controller", enum: ServerTab, menu: ContextMenuGroup
) -> None:
super().__init__(controller) super().__init__(controller)
QUEUE_CHECK_DELAY = 200 QUEUE_CHECK_DELAY = 200
@@ -181,11 +178,11 @@ class ServerTreeView(TreeView):
case Gdk.KEY_j: case Gdk.KEY_j:
if ind == len(children) - 1: if ind == len(children) - 1:
return True return True
menu.select_item(children[ind+1]) menu.select_item(children[ind + 1])
case Gdk.KEY_k: case Gdk.KEY_k:
if ind - 1 < 0: if ind - 1 < 0:
return True return True
menu.select_item(children[ind-1]) menu.select_item(children[ind - 1])
case Gdk.KEY_g: case Gdk.KEY_g:
menu.select_item(children[0]) menu.select_item(children[0])
case Gdk.KEY_G: case Gdk.KEY_G:
@@ -207,9 +204,6 @@ class ServerTreeView(TreeView):
""" """
Propagate width change to other tabs Propagate width change to other tabs
""" """
title = col.get_title()
size = col.get_width()
# NOTE: get final width after drag action completes # NOTE: get final width after drag action completes
GLib.idle_add(self.controller.propagate_column_width, col) GLib.idle_add(self.controller.propagate_column_width, col)
@@ -223,8 +217,9 @@ class ServerTreeView(TreeView):
return return
# TODO: # TODO:
self.controller.mediator.statusbar.spinner.start() self.controller.mediator.statusbar.spinner.start()
ip = record.ip self.current_proc = CalcDist(
self.current_proc = CalcDist(record.ip, self.queue, self.controller) record.ip, self.get_enum(), self.queue, self.controller
)
self.current_proc.start() self.current_proc.start()
def _check_result_queue(self) -> Literal[True]: def _check_result_queue(self) -> Literal[True]:
@@ -235,13 +230,15 @@ class ServerTreeView(TreeView):
latest_result = self.queue.get() latest_result = self.queue.get()
cache = self.controller.get_dist_cache() cache = self.controller.get_dist_cache()
if latest_result: if latest_result:
# NOTE: if CalcDist was spawned on other tab
addr = latest_result[0] addr = latest_result[0]
haversine = latest_result[1] haversine = latest_result[1]
if addr not in cache: if addr not in cache:
cache[addr] = haversine cache[addr] = haversine
# FIXME # FIXME
self.controller.set_statusbar_dist(haversine) self.controller.set_statusbar_dist(haversine, self.get_enum())
return True return True
def _on_server_keypress( def _on_server_keypress(
@@ -308,7 +305,7 @@ class ServerTreeView(TreeView):
mod_context_items = [ContextMenu.OPEN_WORKSHOP, ContextMenu.DELETE_MOD] mod_context_items = [ContextMenu.OPEN_WORKSHOP, ContextMenu.DELETE_MOD]
# TODO: dynamic menu entries # TODO: dynamic menu entries
#for row in items: # for row in items:
# if row == ContextMenu.ADD_SERVER: # if row == ContextMenu.ADD_SERVER:
# if self.is_in_favs(): # if self.is_in_favs():
# row = ContextMenu.REMOVE_SERVER # row = ContextMenu.REMOVE_SERVER
@@ -326,17 +323,13 @@ class ServerTreeView(TreeView):
if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l: if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l:
if self.is_selection_empty(): if self.is_selection_empty():
return return
self.menu.popup_at_widget( self.menu.popup_at_widget(widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST)
widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST
)
else: else:
self.menu.popup_at_pointer(event) self.menu.popup_at_pointer(event)
self.menu.select_first(False) self.menu.select_first(False)
def _parent_row_activated(self, def _parent_row_activated(
tree: TreeView, self, tree: TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn
path: Gtk.TreePath,
column: Gtk.TreeViewColumn
) -> None: ) -> None:
# TODO: process server connection # TODO: process server connection
# TODO: get record # TODO: get record