fix: dynamic statusbar WIP

This commit is contained in:
aclist 2026-01-22 12:01:18 +09:00
parent 9cc1ece0d7
commit 934ff91f05
9 changed files with 189 additions and 122 deletions

View File

@ -212,6 +212,7 @@ class Controller:
except Exception as e:
logger.critical(e)
logger.info("Normal user exit")
if self.mediator.window.props.is_maximized:
Gtk.main_quit()
return
@ -232,20 +233,30 @@ class Controller:
def remove_statusbar(self, context: str) -> None:
c = self.mediator.statusbar.statusbar.get_context_id(context)
self.mediator.statusbar.statusbar.remove_all(c)
self.mediator.statusbar.statusbar.pop(c)
def set_statusbar(self, text: str, context: str) -> None:
self.mediator.statusbar.set_text(text, context)
def set_statusbar(self, text: str, context: str) -> int:
msg_id = self.mediator.statusbar.set_text(text, context)
return msg_id
def set_statusbar_placeholder(self, text: str) -> None:
# TODO: use statusbar stacks instead
self.statusbar_placeholder = text
def set_statusbar_dist(self, haversine: "Haversine", enum: "ServerTab") -> None:
# NOTE: prevent race condition where page was changed before distance was written
if self.mediator.notebook.get_page_by_enum() is not NotebookPage.SERVERS:
cid = self.mediator.statusbar.statusbar.get_context_id(str(enum))
self.mediator.statusbar.statusbar.pop(cid)
self.mediator.statusbar.spinner.stop()
return
tv = self.get_active_treeview()
if tv.get_enum() != enum:
self.mediator.statusbar.spinner.stop()
return
# NOTE: user may have changed km/mi toggle, so recalculate
dist: str
if haversine is None:
dist = "Unknown"
@ -285,7 +296,7 @@ class Controller:
mod.append(None)
model.append(mod)
self.update_mod_statusbar()
# self.update_mod_statusbar()
def toggle_config(self, context: Preferences) -> None:
config = self.prefs.paths.config
@ -342,7 +353,6 @@ class Controller:
# TODO: consolidate methods with set_page_by_enum
match button.context:
case ButtonType.EXIT:
logger.info("Normal user exit")
self.save_res_and_quit()
return
case ButtonType.OPTIONS:
@ -352,11 +362,12 @@ class Controller:
except Exception:
return
case ButtonType.MODS:
# TODO: reload using refresh button, rather than on demand
self.load_mods()
case ButtonType.HELP:
self.mediator.statusbar.refresh(RowType.CHANGELOG)
pass
case ButtonType.SERVERS:
# TODO: drop after fixing crumbs signal
self.mediator.notebook.set_page_by_enum(button.opens)
return
@ -364,6 +375,13 @@ class Controller:
# TODO: set crumbs by signal
self.set_crumbs(button.get_label())
def get_help_text(self) -> str:
tv = self.mediator.menu
model = self.get_help_store()
tree_iter = tv.get_focused_row_iter()
value = model.get_value(tree_iter, 0)
return value
def open_user_workshop(self, uid: str) -> None:
# NOTE: uid may contain leading zeroes, not a real integer
client = self.query_config(Preferences.CLIENT)
@ -426,13 +444,10 @@ class Controller:
model = self.model_man.get_mod_store()
model.remove(it)
def update_mod_statusbar(self) -> None:
def format_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)
self.mediator.statusbar.set_text(msg, "Mods")
return msg
def calc_mod_size(self) -> tuple[int, int]:
model = self.model_man.get_mod_store()
@ -698,12 +713,24 @@ class Controller:
context = str(treeview.enum)
model = treeview.get_model()
status = format_player_count(model)
self.set_statusbar_placeholder(status)
self.set_statusbar_placeholder(status)
self.set_statusbar(status, context)
if len(model) >= 1:
self.mediator.statusbar.spinner.start()
# self.mediator.statusbar.spinner.stop()
#if len(model) >= 1:
# self.mediator.statusbar.spinner.start()
def start_spinner(self) -> None:
# TODO: use signal instead
self.mediator.statusbar.start_spinner()
def notify_statusbar_of_notebook_change(self) -> None:
enum = self.mediator.notebook.get_page_by_enum()
self.mediator.statusbar.emit("notebook_page_changed", enum)
def notify_statusbar(self) -> None:
tv = self.get_active_treeview()
enum = tv.get_enum()
self.mediator.statusbar.emit("server_page_changed", enum)
def populate_model(self) -> None:
treeview = self.get_active_treeview()
@ -722,13 +749,13 @@ class Controller:
def present_servers(self) -> None:
# TODO: abstract
# TODO: signal for crumbs
self.update_server_status()
# TODO: signal for crumbs
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 = {

View File

@ -186,6 +186,7 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
self.prior_page: NotebookPage
self.prior_status: str
self.is_return = False
MainController.register_widget("notebook", self)
@ -253,8 +254,8 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
self.set_page_by_enum(self.prior_page)
self.settings.unblock_text_entry()
return
self.is_return = True
self.set_page_by_enum(self.prior_page)
MainController.set_statusbar(self.prior_status)
def get_page_by_enum(self) -> NotebookPage | None:
for k, v in self.indexes.items():
@ -267,7 +268,7 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
if cur_page == NotebookPage.KEYS:
self.return_prior()
else:
self.prior_status = MainController.get_statusbar()
# self.prior_status = MainController.get_statusbar()
self.set_page_by_enum(NotebookPage.KEYS)
def focus_current(self) -> None:
@ -280,11 +281,7 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
return
w = widget.get_children()[0]
try:
w.focus_first_row()
w.grab_focus()
except Exception:
w.grab_focus()
w.grab_focus()
def get_page(self) -> Gtk.Widget | None:
ind = self.get_current_page()
@ -304,13 +301,19 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
self, notebook: "Notebook", page: Gtk.Widget, page_num: int
) -> None:
enum = self.get_page_by_enum()
# TODO: crumbs signal
if enum is not None:
crumbs = enum.dict["crumbs"]
status = enum.dict["statusbar"]
MainController.set_crumbs(crumbs)
if status is False:
MainController.set_statusbar("", "")
if self.is_return is True:
MainController.mediator.statusbar.emit(
"notebook_page_returned", self.prior_page
)
else:
MainController.mediator.statusbar.emit("notebook_page_changed", enum)
self.is_return = False
if enum is NotebookPage.SERVERS:
MainController.present_servers()

View File

@ -1,16 +1,26 @@
from typing import TYPE_CHECKING
from warnings import deprecated
from typing import Self, Union, TYPE_CHECKING
from dzgui.const.enum import RowType, Preferences
from dzgui.const.enum import NotebookPage, RowType
from dzgui.util import strings
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa E402
from gi.repository import Gtk, GObject # noqa E402
if TYPE_CHECKING:
from dzgui.const.enum import ServerTab
from dzgui.controllers.mc import Controller
class Statusbar(Gtk.Grid):
__gsignals__ = {
"server_page_changed": (GObject.SignalFlags.RUN_FIRST, None, (object,)),
"notebook_page_changed": (GObject.SignalFlags.RUN_FIRST, None, (object,)),
"notebook_page_returned": (GObject.SignalFlags.RUN_FIRST, None, (object,)),
}
def __init__(self, controller: "Controller") -> None:
super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
@ -26,48 +36,79 @@ class Statusbar(Gtk.Grid):
self.spinner.start()
version = self.controller.get_prefs().version
self.status_right_label = Gtk.Label(label=version, hexpand=True, halign=Gtk.Align.END)
self.status_right_label = Gtk.Label(
label=version, hexpand=True, halign=Gtk.Align.END
)
self.attach(self.statusbar, 0, 0, 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, "Help")
self.players = ""
self.connect("server_page_changed", self._on_server_page_changed)
self.connect("notebook_page_changed", self._on_notebook_page_changed)
self.connect("notebook_page_returned", self._on_notebook_page_returned)
def _on_notebook_page_changed(
self, statusbar: Self, context: "NotebookPage"
) -> None:
status = context.dict["statusbar"]
bar = ""
if status is False:
self.set_by_context(context, "")
return
match context:
case NotebookPage.MODS:
bar = self.controller.format_mod_statusbar()
case NotebookPage.HELP:
bar = self.controller.get_help_text()
self.set_by_context(context, bar)
def _on_notebook_page_returned(
self, statusbar: Self, prior_context: "NotebookPage"
) -> None:
self.pop(prior_context)
def _on_server_page_changed(self, statusbar: Self, context: "ServerTab") -> None:
self.pop(context)
def start_spinner(self) -> None:
self.spinner.start()
def stop_spinner(self) -> None:
self.spinner.stop()
def pop(self, context: Union["ServerTab", "NotebookPage"]) -> None:
cid = self.statusbar.get_context_id(str(context))
self.statusbar.pop(cid)
def get_text(self) -> str:
area = self.statusbar.get_message_area()
label = area.get_children()[0]
return label.get_text()
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)
def set_by_context(
self, context: Union[NotebookPage, "ServerTab"], string: str
) -> None:
meta = self.statusbar.get_context_id(str(context))
self.statusbar.push(meta, string)
#self.set_context(meta)
self.set_cache(string)
def get_cache(self) -> str:
return self.cache
# 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 set_cache(self, string: str) -> None:
self.cache = string
def set_text(self, string: str, context: str) -> None:
meta = self.statusbar.get_context_id(context)
self.statusbar.push(meta, string)
self.set_cache(string)
def refresh(self, row: "RowType") -> None:
if row is None:
@ -76,14 +117,7 @@ class Statusbar(Gtk.Grid):
formatted = self.format_metadata(row)
self.set_text(formatted, "Help")
def append_distance(self, dist: str) -> None:
# TODO: process strings in controller
if dist == strings.unknown:
dist = f"| Distance: {dist}"
else:
dist = f"| Distance: {dist}"
self.set_text(self.players + dist)
@deprecated("use controller")
def format_metadata(self, row: "RowType") -> str:
prefix = row.dict["tooltip"]

View File

@ -2,12 +2,14 @@ from typing import TYPE_CHECKING
from dzgui.views.trees.tree_menu import MenuTreeView
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa E402
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
class Help(Gtk.ScrolledWindow):
def __init__(self, controller: "Controller") -> None:
super().__init__()
@ -15,7 +17,8 @@ class Help(Gtk.ScrolledWindow):
self.add(self.treeview)
self.controller = controller
self.controller.register_widget("menu", self.treeview)
self.treeview.focus_first_row()
def get_treeview(self) -> MenuTreeView:
return self.treeview

View File

@ -2,12 +2,14 @@ from typing import Self, TYPE_CHECKING
from dzgui.views.trees.tree_mods import ModTreeView
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk # noqa
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
class Mods(Gtk.ScrolledWindow):
def __init__(self, controller: "Controller") -> None:
super().__init__()

View File

@ -7,6 +7,7 @@ from dzgui.views.trees.tree_servers import ServerTreeView
from dzgui.util.strings import server_labels
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GLib # noqa E402
@ -15,6 +16,7 @@ logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
class ServerNotebook(Gtk.ScrolledWindow):
def __init__(self, controller: "Controller"):
super().__init__()
@ -24,9 +26,13 @@ class ServerNotebook(Gtk.ScrolledWindow):
self.controller.register_widget("servers", self)
self.notebook = Gtk.Notebook(show_tabs=True)
self.browser = ServerTreeView(controller, ServerTab.BROWSER, ContextMenuGroup.SERVER_BROWSER)
self.browser = ServerTreeView(
controller, ServerTab.BROWSER, ContextMenuGroup.SERVER_BROWSER
)
self.saved = ServerTreeView(controller, ServerTab.SAVED, ContextMenuGroup.SAVED)
self.recent = ServerTreeView(controller, ServerTab.RECENT, ContextMenuGroup.RECENT)
self.recent = ServerTreeView(
controller, ServerTab.RECENT, ContextMenuGroup.RECENT
)
self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN)
self.browser.set_query_func(self.controller.dump_test_1)
@ -50,6 +56,7 @@ class ServerNotebook(Gtk.ScrolledWindow):
self.connect("map", self._on_map)
self.connect("unmap", self._on_unmap)
# TODO: strings
self.set_crumbs("Server browser")
def _on_map(self, widget: Self) -> None:
@ -75,43 +82,28 @@ class ServerNotebook(Gtk.ScrolledWindow):
self.controller.set_crumbs(string)
self.set_cached_label(string)
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:
return
# self.controller.remove_statusbar("Servers")
# TODO :signals
# emit signal to crumbs
# emit signal to statusbar
# TODO: abstract
label = self.notebook.get_tab_label_text(child)
if label is None:
return
# TODO: strings
text = label.strip("*")
self.notebook.set_tab_label_text(child, text)
self.set_crumbs(text)
self.controller.present_servers()
self.controller.populate_model()
# TODO: start with lan panel hidden
# FIXME: grid conpan is not set up at init
# TODO: refresh button only usable if LAN has servers?
# or just remove it
#if self.get_active_treeview() is self.lan:
# self.controller.mediator.grid.conpan.lan.set_visible(True)
#else:
# self.controller.mediator.grid.conpan.lan.set_visible(False)
# TODO: put in controller
# def query_test2(self) -> None:
# # TODO: should trigger first page action after entire UI is loaded
# self.controller.dump_test_1()
#
# def query_test(self) -> None:
# data = (["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False])
# return data
def set_cached_label(self, label: str) -> None:
self.tab_cache = label
@ -146,9 +138,13 @@ class ServerNotebook(Gtk.ScrolledWindow):
continue
for col in tab.get_columns():
if col.get_title() == title:
self.controller.suppress_signal(tab, col, "_on_col_width_changed", True)
self.controller.suppress_signal(
tab, col, "_on_col_width_changed", True
)
col.set_fixed_width(width)
self.controller.suppress_signal(tab, col, "_on_col_width_changed", False)
self.controller.suppress_signal(
tab, col, "_on_col_width_changed", False
)
def get_tabs(self) -> tuple:
return (self.browser, self.saved, self.recent, self.lan)

View File

@ -6,6 +6,7 @@ from dzgui.util.open_links import open_link_by_rowtype
from dzgui.views.trees.tree_base import TreeView
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
@ -14,11 +15,13 @@ logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
class MenuTreeView(TreeView):
"""
Simple Gtk.ListStore representation of main
menu options
"""
def __init__(self, controller: "Controller") -> None:
super().__init__(controller)
@ -34,15 +37,15 @@ class MenuTreeView(TreeView):
self.selected_row = self.get_selection()
self.controller.register_widget("menu", self)
self.set_row_separator_func(self._separate)
self.connect("generic_row_activated", self._parent_row_activated)
self.connect("generic_treesel_changed", self._parent_selection_changed)
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:
row_type = self.get_value_at_index(1)
match row_type:
@ -73,9 +76,7 @@ class MenuTreeView(TreeView):
open_link_by_rowtype(row_type)
def _parent_selection_changed(
self,
base_class: TreeView,
sel: Gtk.TreeSelection
self, base_class: TreeView, sel: Gtk.TreeSelection
) -> None:
row = self.get_value_at_index(1)
if row == "":

View File

@ -7,9 +7,10 @@ from dzgui.views.mixins.context_mixin import ContextMixin
from dzgui.views.mixins.mods_mixin import ModsMixin
from dzgui.const.enum import (
ContextMenuGroup,
)
)
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
@ -19,6 +20,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
class ModTreeView(ModsMixin, ContextMixin, TreeView):
def __init__(self, controller: "Controller") -> None:
super().__init__(controller, menu=ContextMenuGroup.MOD)
@ -32,13 +34,9 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
for i, column_title in enumerate(strings.mod_cols):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(
column_title, renderer, text=i, foreground=4
)
column = Gtk.TreeViewColumn(column_title, renderer, text=i, foreground=4)
if i == 3:
column.set_cell_data_func(
renderer, self._format_float, func_data=None
)
column.set_cell_data_func(renderer, self._format_float, func_data=None)
if column_title == "Mod":
column.set_fixed_width(500)
else:
@ -57,11 +55,10 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
self.connect("button-press-event", self._on_mods_button_press)
self.connect("key-press-event", self._on_mods_keypress)
self.focus_first_row()
self.s = self.get_selection().get_selected_rows()
def _on_mods_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
#if event.keyval is Gdk.KEY_space:
# if event.keyval is Gdk.KEY_space:
# it = self.get_focused_row_iter()
# self.get_selection().select_iter(it)
# path = self.get_focused_row_path()
@ -70,10 +67,7 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
# TODO: parse keys
self.present_menu(widget, event)
def _on_mods_button_press(self,
widget: Gtk.Widget,
event: Gdk.EventButton
) -> None:
def _on_mods_button_press(self, widget: Gtk.Widget, event: Gdk.EventButton) -> None:
if event.button == 3:
self.present_menu(widget, event)

View File

@ -150,7 +150,8 @@ class ServerTreeView(TreeView):
# this can probably be dropped in favor of statusbar cache system
# meta = self.controller.mediator.statusbar.statusbar.get_context_id("Mods")
# self.controller.mediator.statusbar.statusbar.pop(meta)
self.emit("on_distcalc_started")
pass
# self.emit("on_distcalc_started")
def get_enum(self) -> None:
return self.enum
@ -215,8 +216,17 @@ class ServerTreeView(TreeView):
record = self.get_record()
if record is None:
return
# TODO:
self.controller.mediator.statusbar.spinner.start()
cache = self.controller.get_dist_cache()
if record.ip in cache:
haversine = cache[record.ip]
self.controller.set_statusbar_dist(haversine, None)
return
if len(self.get_model()) > 1:
self.controller.start_spinner()
self.current_proc = CalcDist(
record.ip, self.get_enum(), self.queue, self.controller
)
@ -232,12 +242,10 @@ class ServerTreeView(TreeView):
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.get_enum())
return True
@ -253,7 +261,6 @@ class ServerTreeView(TreeView):
case Gdk.KEY_r:
self.refresh_player_count()
case Gdk.KEY_f:
# TODO: register filter panel instead of mediating thru right panel
self.controller.mediator.filters.keyword_entry.grab_focus()
case Gdk.KEY_m:
self.controller.mediator.filters.maps_entry.grab_focus()
@ -344,7 +351,7 @@ class ServerTreeView(TreeView):
qport = self.get_value_at_index(8)
return f"{addr}:{qport}"
def get_record(self) -> dict | None:
def get_record(self) -> Record | None:
# TODO: delegate to controller
select = self.get_selection()
sels = select.get_selected_rows()