fix: statusbar race condition WIP

This commit is contained in:
aclist 2026-01-21 12:04:42 +09:00
parent 2f9000ee80
commit 9cc1ece0d7
9 changed files with 169 additions and 125 deletions

View File

@ -1,7 +1,6 @@
import logging
import shutil
import threading
import textwrap
import traceback
from pathlib import Path
@ -17,23 +16,16 @@ from dzgui.api.mods import (
get_local_mod_path,
find_stale_mods,
_hash,
remove_stale_signatures
)
from dzgui.const.enum import (
Preferences,
Popup,
NotebookPage,
ButtonType,
ContextMenu,
RowType
remove_stale_signatures,
)
from dzgui.const.enum import Preferences, NotebookPage, ButtonType, ContextMenu, RowType
from dzgui.const.constants import (
APPID_DAYZ,
APPID_DAYZ_EXP,
HEX_RED,
WINDOW_DEFAULT_X,
WINDOW_DEFAULT_Y
WINDOW_DEFAULT_Y,
)
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.localize import number
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.views.dialogs.filepicker import FilePicker
from dzgui.views.dialogs.generic import GenericDialog
import gi
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__)
if TYPE_CHECKING:
from dzgui.const.enum import ServerTab
from dzgui.util.dist import Haversine
from dzgui.views.base import Notebook, Grid, OuterWindow
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.statusbar import Statusbar
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_menu import MenuTreeView
from dzgui.views.trees.tree_mods import ModTreeView
from dzgui.views.trees.tree_servers import ServerTreeView
class AppNavigation:
window: "OuterWindow"
grid: "Grid"
@ -80,16 +73,16 @@ class AppNavigation:
modtreeview: "ModTreeView"
menu: "MenuTreeView"
servers: "ServerNotebook"
browser: "ServerTreeView"
saved: "ServerTreeView"
recent: "ServerTreeView"
lan: "ServerTreeView"
logtreeview: "LogTreeView"
filters: "FilterPanel"
class Controller:
def __init__(self) -> None:
self.dist_cache: dict[str, "Haversine"] = {}
self.dist_cache: dict[str, "Haversine", "ServerTab"] = {}
self.crumbs_cache = ""
self.mediator = AppNavigation()
self.prefs: UserPrefs
@ -128,7 +121,8 @@ class Controller:
def terminate_process(self) -> None:
# 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:
return self.prefs
@ -165,7 +159,9 @@ class Controller:
"_on_tree_selection_changed",
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:
self.suppress_signal(
self.mediator.filters,
@ -184,7 +180,7 @@ class Controller:
else:
widget.handler_unblock_by_func(func)
# TODO: deprecated?
#self.mediator.menu.sel_blocked = state
# self.mediator.menu.sel_blocked = state
def toggle_debug_mode(self) -> None:
self.toggle_config(Preferences.DEBUG)
@ -234,14 +230,22 @@ class Controller:
def get_statusbar(self) -> str:
return self.mediator.statusbar.get_text()
def set_statusbar(self, text: str) -> None:
self.mediator.statusbar.set_text(text)
def remove_statusbar(self, context: str) -> None:
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:
# TODO: use statusbar stacks instead
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
if haversine is None:
dist = "Unknown"
@ -255,7 +259,8 @@ class Controller:
separated = number(raw)
dist = str(separated) + " km"
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()
def delete_multiple_mods(self) -> None:
@ -328,7 +333,7 @@ class Controller:
def show_developers_page(self) -> None:
self.open_page(NotebookPage.DEVELOPERS)
# 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:
self.mediator.grid.notebook.set_page_by_enum(page)
@ -386,7 +391,9 @@ class Controller:
open_workshop_page(mod, cmd)
# 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()
tree_iter = model.get_iter(tree_path)
mod = model.get(tree_iter, 2)[0]
@ -403,7 +410,7 @@ class Controller:
app_path = PeFile.get_nested_app_path(steam_path, APPID_DAYZ)
md5 = _hash(mod)
symlink = app_path / md5
symlink = app_path / md5
symlink.unlink()
shutil.rmtree(mods_path / mod)
@ -419,15 +426,13 @@ class Controller:
model = self.model_man.get_mod_store()
model.remove(it)
def update_mod_statusbar(self) -> None:
total_mods, total_size = self.calc_mod_size()
msg = format_mods(total_size, total_mods)
# TODO: combine
meta = self.mediator.statusbar.statusbar.get_context_id("Mods")
self.mediator.statusbar.statusbar.push(meta, msg)
#meta = self.mediator.statusbar.set_text(msg)
# meta = self.mediator.statusbar.statusbar.get_context_id("Mods")
# self.mediator.statusbar.statusbar.push(meta, msg)
self.mediator.statusbar.set_text(msg, "Mods")
def calc_mod_size(self) -> tuple[int, int]:
model = self.model_man.get_mod_store()
@ -437,7 +442,6 @@ class Controller:
total_size += mod[3]
return total_mods, total_size
def menu_action(self, action: ContextMenu, path: Gtk.TreePath) -> None:
match action:
# NOTE: manipulates server stores
@ -470,14 +474,12 @@ class Controller:
# Gtk.TreeModel, row-inserted/row-deleted
self.update_mod_statusbar()
remove_stale_signatures(
self.prefs.paths.config,
self.prefs.paths.version
self.prefs.paths.config, self.prefs.paths.version
)
case ContextMenu.OPEN_WORKSHOP:
self.open_mod_page(path)
def toggle_mod_selection(self, state: bool) -> None:
sel = self.mediator.modtreeview.get_selection()
if state:
@ -493,7 +495,9 @@ class Controller:
# NOTE: this model is reloaded each time as log changes
try:
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:
clean = redact_log(record)
store.append(clean)
@ -535,6 +539,7 @@ class Controller:
def dump_test_1(self) -> None:
import time
time.sleep(1)
# TODO: use model managers, etc.
self.data = (
@ -561,6 +566,7 @@ class Controller:
def dump_test_2(self) -> None:
import time
time.sleep(1)
# TODO: use model managers, etc.
self.data = (
@ -570,7 +576,7 @@ class Controller:
self.get_func_data()
self.destroy_on_idle()
def get_func_data(self):
def get_func_data(self) -> None:
def test():
# NOTE: do not insert model until main thread is idle
# TODO: grab from model manager and insert entire model
@ -580,6 +586,7 @@ class Controller:
treeview.set_loaded(True)
self.update_server_status()
treeview.grab_focus()
treeview = self.get_active_treeview()
manager = treeview.get_filter_man()
@ -591,6 +598,7 @@ class Controller:
def call_on_thread(self, func: Callable, *args) -> None:
from dzgui.views.dialogs.generic import WaitDialog
self.wait_dialog = WaitDialog(self, strings.dialog.fetching)
self.wait_dialog.show_all()
thread = threading.Thread(target=func, args=args)
@ -607,7 +615,7 @@ class Controller:
Lets calling widgets register a callback function
when spawning a threaded process
"""
self.callback = { "func": callback, "args": args }
self.callback = {"func": callback, "args": args}
def destroy_on_idle(self) -> None:
self.wait_dialog.destroy()
@ -642,7 +650,6 @@ class Controller:
dialog = ExceptionDialog(self, strings.api_error)
dialog.run()
def update_api_key(self, text: str, key: Preferences) -> None:
self.call_on_thread(self.test_api_response, text, key)
@ -688,13 +695,15 @@ class Controller:
def update_server_status(self) -> None:
# TODO: emit signal on statusbar only if page changed
treeview = self.get_active_treeview()
context = str(treeview.enum)
model = treeview.get_model()
status = format_player_count(model)
self.set_statusbar_placeholder(status)
self.set_statusbar(status)
self.mediator.statusbar.spinner.start()
if len(model) < 1:
self.mediator.statusbar.spinner.stop()
self.set_statusbar(status, context)
if len(model) >= 1:
self.mediator.statusbar.spinner.start()
# self.mediator.statusbar.spinner.stop()
def populate_model(self) -> None:
treeview = self.get_active_treeview()
@ -718,8 +727,8 @@ class Controller:
crumbs = self.mediator.servers.get_cached_label()
self.set_crumbs(crumbs)
# TODO: could emit this when treeview gains keyboard focus
#tree = self.get_active_treeview()
#tree.emit("on_distcalc_started")
# tree = self.get_active_treeview()
# tree.emit("on_distcalc_started")
def toggle_check(self, event: Gdk.EventKey) -> None | Literal[False]:
mappings = {
@ -739,7 +748,6 @@ class Controller:
if event.keyval not in mappings:
return False
index = mappings[event.keyval]
# TODO: register filter panel widget
self.mediator.filters.toggle_check(index)
def get_favorite(self) -> tuple[str, str] | tuple[None, None]:

View File

@ -44,10 +44,12 @@ args = parser.parse_args()
if TYPE_CHECKING:
from pathlib import Path
# TODO: profile load time
def make_parents(path: "Path") -> None:
path.parent.mkdir(parents=True, exist_ok=True)
def uninstall() -> None:
# TODO: uninstall data files (-u)
# -u removes state, log, freedesktop
@ -56,6 +58,7 @@ def uninstall() -> None:
# XDG_DATA_HOME/dzgui
pass
def main() -> None:
lock = lock_acquire() # noqa
@ -85,7 +88,9 @@ def main() -> None:
migrate_cols_file(XDG.columns)
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)
with open(XDG.debug, "w") as f:
f.truncate(0)
@ -126,7 +131,7 @@ def main() -> None:
local_coords,
version,
allow,
XDG
XDG,
)
print("All OK. Loading UI...")
App(prefs)

View File

@ -4,19 +4,20 @@ from math import radians, cos, sin, asin, sqrt
from typing import TYPE_CHECKING
from dzgui.util.ip import GeolocationError, get_coords
from dzgui.util.localize import number
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa E402
if TYPE_CHECKING:
from dzgui.util.ip import Coords
from dzgui.const.enum import ServerTab
from dzgui.controllers.mc import Controller
logger = logging.getLogger(__name__)
class Haversine():
class Haversine:
def __init__(self, lat1: float, lon1: float, lat2: float, lon2: float) -> None:
R = 6371 * 1000
@ -26,7 +27,7 @@ class Haversine():
lat1 = radians(lat1)
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))
self.dist = R * c
@ -37,15 +38,18 @@ class Haversine():
def as_miles(self) -> float:
return self.dist / 1609.344
class CalcDist(multiprocessing.Process):
def __init__(
self,
addr: str,
enum: "ServerTab",
result_queue: multiprocessing.Queue,
controller: "Controller",
) -> None:
super().__init__()
self.enum = enum
self.controller = controller
self.result_queue = result_queue
self.addr = addr
@ -55,11 +59,11 @@ class CalcDist(multiprocessing.Process):
cache = self.controller.get_dist_cache()
if self.addr 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
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:
prefs = self.controller.get_prefs()

View File

@ -1,13 +1,7 @@
import logging
#import multiprocessing
import signal
#import threading
import typing # noqa
import warnings
#from concurrent.futures import wait
#from concurrent.futures import ThreadPoolExecutor
from typing import TYPE_CHECKING, Literal
from dzgui.const.enum import NotebookPage
@ -37,6 +31,7 @@ if TYPE_CHECKING:
from dzgui.config.userprefs import UserPrefs
import gi
gi.require_version("Gtk", "3.0")
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
# 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
#def query_history() -> list | None:
# def query_history() -> list | None:
# history_file = MainController.get_prefs().paths.history
# try:
# with open(history_file, "r") as f:
@ -58,7 +53,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# rows = None
# return rows
#
#def process_tree_option(choice: RowType) -> None:
# def process_tree_option(choice: RowType) -> None:
# # server tables
# if command == RowType.RESOLVE_IP:
# record = treeview.get_record()
@ -91,18 +86,18 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# return
#
## TODO: belongs in model
#def str_to_record(record: str) -> Record | None:
# def str_to_record(record: str) -> Record | None:
# r = record.split(":")
# if len(r) != 3:
# return None
# return Record(r[0], int(r[1]), int(r[2]))
#
## TODO: ibid
#def record_to_str(record: Record) -> str:
# def record_to_str(record: Record) -> str:
# 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:
# record = Servers.validate_ip(response)
# proc = treeview.prepare_connection(record)
@ -114,7 +109,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# 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:
# # TODO: if response is non numeric, raise dialog
# if response.isnumeric() is False:
@ -139,6 +134,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
#
#
class OuterWindow(Gtk.Window):
def __init__(self) -> None:
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.populate_model()
def _on_delete_event(
self, window: "OuterWindow", event: Gdk.EventKey
) -> None:
def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
self.halt_proc_and_quit()
def halt_proc_and_quit(self) -> None:
@ -316,7 +310,7 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
MainController.set_crumbs(crumbs)
if status is False:
MainController.set_statusbar("")
MainController.set_statusbar("", "")
if enum is NotebookPage.SERVERS:
MainController.present_servers()
@ -351,8 +345,20 @@ class Grid(Gtk.Grid):
els = (
(self.crumb_box, self.notebook, Gtk.PositionType.TOP, 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:
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:
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:
return self.breadcrumbs.get_text()
@ -397,9 +403,7 @@ class App(Gtk.Application):
)
self.win.add_accel_group(accel)
GLib.unix_signal_add(
GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint
)
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint)
Gtk.main()
def _catch_sigint(self) -> Literal[True]:
@ -415,9 +419,11 @@ class App(Gtk.Application):
) -> None:
self.win.halt_proc_and_quit()
MainController = Controller()
# TODO: icon sanity check, log result
#theme = Gtk.IconTheme.get_default()
#icons = theme.list_icons(None)
#print("steam_tray_mono" in icons)
# theme = Gtk.IconTheme.get_default()
# icons = theme.list_icons(None)
# print("steam_tray_mono" in icons)
# FIXME:

View File

@ -93,6 +93,9 @@ class RefreshButton(IconTextButton):
self.connect("clicked", self._on_refresh_clicked)
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.show_time(True)
GLib.timeout_add_seconds(1, self.decrement)

View File

@ -26,13 +26,14 @@ class InfoEventBox(Gtk.EventBox):
self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing
) -> None:
self.icon.set_opacity(1)
self.parent.controller.set_statusbar(self.text)
self.parent.controller.set_statusbar(self.text, "Options")
def _on_leave_tooltip(
self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing
) -> None:
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:
self.text = text

View File

@ -19,6 +19,7 @@ class Statusbar(Gtk.Grid):
help_text = strings.statusbar_helptext
self.context: int
self.statusbar = Gtk.Statusbar()
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.status_right_label, self.spinner, Gtk.PositionType.RIGHT, 3, 1)
self.set_text(help_text)
self.set_text(help_text, "Help")
self.players = ""
def get_text(self) -> str:
@ -39,20 +40,41 @@ class Statusbar(Gtk.Grid):
label = area.get_children()[0]
return label.get_text()
def set_text(self, string: str) -> None:
if string is None:
return
meta = self.statusbar.get_context_id("Statusbar")
def set_text(self, string: str, context: str) -> None:
# if string is None:
# return
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
# get_context_id(ServerTab)
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:
if row is None:
formatted = ""
else:
formatted = self.format_metadata(row)
self.set_text(formatted)
self.set_text(formatted, "Help")
def append_distance(self, dist: str) -> None:
# TODO: process strings in controller

View File

@ -78,6 +78,8 @@ class ServerNotebook(Gtk.ScrolledWindow):
def _on_page_changed(self, notebook: Gtk.Notebook, child: Gtk.Widget, index: int) -> None:
if self.controller.loaded is False:
return
# self.controller.remove_statusbar("Servers")
# TODO: abstract
label = self.notebook.get_tab_label_text(child)

View File

@ -1,29 +1,24 @@
import logging
import multiprocessing
import subprocess
from typing import Self
import gi
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.const.enum import ContextMenu, ContextMenuGroup, FilterMode, ServerTab
from dzgui.api.servers import Record
from dzgui.model.filtered_model import FilteredModelManager
from dzgui.util.dist import CalcDist
from dzgui.util.keys import is_navkey
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
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__)
if TYPE_CHECKING:
@ -31,6 +26,7 @@ if TYPE_CHECKING:
# TODO: restore missing methods
class EnumeratedMenuItem(Gtk.MenuItem):
def __init__(self, enum: ContextMenu):
super().__init__(label=enum.dict["label"])
@ -38,10 +34,11 @@ class EnumeratedMenuItem(Gtk.MenuItem):
class ServerTreeView(TreeView):
__gsignals__ = {
"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())
}
def __init__(self, controller: "Controller", enum: ServerTab, menu: ContextMenuGroup) -> None:
__gsignals__ = {"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())}
def __init__(
self, controller: "Controller", enum: ServerTab, menu: ContextMenuGroup
) -> None:
super().__init__(controller)
QUEUE_CHECK_DELAY = 200
@ -181,11 +178,11 @@ class ServerTreeView(TreeView):
case Gdk.KEY_j:
if ind == len(children) - 1:
return True
menu.select_item(children[ind+1])
menu.select_item(children[ind + 1])
case Gdk.KEY_k:
if ind - 1 < 0:
return True
menu.select_item(children[ind-1])
menu.select_item(children[ind - 1])
case Gdk.KEY_g:
menu.select_item(children[0])
case Gdk.KEY_G:
@ -207,15 +204,12 @@ class ServerTreeView(TreeView):
"""
Propagate width change to other tabs
"""
title = col.get_title()
size = col.get_width()
# NOTE: get final width after drag action completes
GLib.idle_add(self.controller.propagate_column_width, col)
def terminate_process(self) -> None:
if self.current_proc and self.current_proc.is_alive():
self.current_proc.terminate()
if self.current_proc and self.current_proc.is_alive():
self.current_proc.terminate()
def _on_distcalc_started(self, treeview: Self):
record = self.get_record()
@ -223,8 +217,9 @@ class ServerTreeView(TreeView):
return
# TODO:
self.controller.mediator.statusbar.spinner.start()
ip = record.ip
self.current_proc = CalcDist(record.ip, self.queue, self.controller)
self.current_proc = CalcDist(
record.ip, self.get_enum(), self.queue, self.controller
)
self.current_proc.start()
def _check_result_queue(self) -> Literal[True]:
@ -235,13 +230,15 @@ class ServerTreeView(TreeView):
latest_result = self.queue.get()
cache = self.controller.get_dist_cache()
if latest_result:
# NOTE: if CalcDist was spawned on other tab
addr = latest_result[0]
haversine = latest_result[1]
if addr not in cache:
cache[addr] = haversine
# FIXME
self.controller.set_statusbar_dist(haversine)
self.controller.set_statusbar_dist(haversine, self.get_enum())
return True
def _on_server_keypress(
@ -308,7 +305,7 @@ class ServerTreeView(TreeView):
mod_context_items = [ContextMenu.OPEN_WORKSHOP, ContextMenu.DELETE_MOD]
# TODO: dynamic menu entries
#for row in items:
# for row in items:
# if row == ContextMenu.ADD_SERVER:
# if self.is_in_favs():
# row = ContextMenu.REMOVE_SERVER
@ -326,18 +323,14 @@ class ServerTreeView(TreeView):
if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l:
if self.is_selection_empty():
return
self.menu.popup_at_widget(
widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST
)
self.menu.popup_at_widget(widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST)
else:
self.menu.popup_at_pointer(event)
self.menu.select_first(False)
def _parent_row_activated(self,
tree: TreeView,
path: Gtk.TreePath,
column: Gtk.TreeViewColumn
) -> None:
def _parent_row_activated(
self, tree: TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn
) -> None:
# TODO: process server connection
# TODO: get record
print(self.get_value_at_index(0))