import logging from typing import Self, TYPE_CHECKING from dzgui.const.constants import APP_NAME from dzgui.const.enum import ContextMenuGroup, ServerTab 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 logger = logging.getLogger(APP_NAME) if TYPE_CHECKING: from dzgui.controllers.mc import Controller from dzgui.controllers.emitter import Emitter class ScrollableTree(Gtk.ScrolledWindow): def __init__(self, tree: "ServerTreeView") -> None: super().__init__(overlay_scrolling=False) self.tree = tree self.add(tree) def get_tree(self) -> "ServerTreeView": return self.tree class ServerNotebook(Gtk.ScrolledWindow): def __init__(self, controller: "Controller"): super().__init__() self.controller = controller self.emitter = self.controller.get_emitter() self.controller.register_widget("servers", self) self.notebook = Gtk.Notebook(show_tabs=True) 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.lan = ServerTreeView( controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN, show_empty=True, show_full=True, ) tabs = [ (self.browser, server_labels.browser), (self.saved, server_labels.saved), (self.recent, server_labels.recent), (self.lan, server_labels.lan), ] for tree, label in tabs: scrolled = ScrollableTree(tree) self.notebook.append_page(scrolled, Gtk.Label(label=label)) self.add(self.notebook) self.notebook.connect_after("switch-page", self._on_page_changed) self.connect("key-press-event", self._on_keypress) self.connect("map", self._on_map) self.connect("unmap", self._on_unmap) self.emitter.connect("servers_loaded", self._on_servers_loaded) self.emitter.connect("saved_servers_changed", self._on_saved_servers_changed) def _on_saved_servers_changed(self, emitter: "Emitter") -> None: # TODO: can be dropped/consolidated? saved = 1 cur_page = self.notebook.get_current_page() if cur_page == saved: return page = self.notebook.get_nth_page(saved) if not page: return label = self.notebook.get_tab_label(page) if label is None: return if hasattr(label, "set_text"): label.set_text(f"{server_labels.saved}*") def _on_servers_loaded(self, emitter: "Emitter", tab: "ServerTab") -> None: # NOTE: workaround for GTK bug where fullscreen causes headers to vanish when model is None # TODO: this should be internal to servers page tv = self.get_active_treeview() if tv is None: return state = False if tv.get_model() is None else True tv.set_headers_visible(state) tv.set_loaded(True) tv.focus_first_row() tv.grab_focus() def _on_map(self, widget: Self) -> None: self.emitter.emit("server_page_toggled", True) def _on_unmap(self, widget: Self) -> None: self.emitter.emit("server_page_toggled", False) def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> bool: # NOTE: abort if modifier mask is active if event.state != 0: return False match event.keyval: case Gdk.KEY_n: self.notebook.next_page() self.grab_content_area() return True case Gdk.KEY_p: self.notebook.prev_page() self.grab_content_area() return True case _: return False def grab_content_area(self) -> None: tv = self.get_active_treeview() if tv is not None: tv.grab_focus() def get_current_tab_text(self) -> str: ind = self.notebook.get_current_page() child = self.notebook.get_nth_page(ind) if child is None: return "" text = self.notebook.get_tab_label_text(child) if text is None: return "" return text def _on_page_changed( self, notebook: Gtk.Notebook, child: ScrollableTree, index: int ) -> None: if self.controller.loaded is False: return label = self.notebook.get_tab_label_text(child) if label is not None: # TODO: strings text = label.strip("*") self.notebook.set_tab_label_text(child, text) tree = child.get_tree() self.emitter.emit("server_page_changed", tree) # NOTE: spawns a thread tv = self.get_active_treeview() if tv is not None: self.controller.populate_model(tv) def get_active_treeview(self) -> ServerTreeView: index = self.notebook.get_current_page() scrollable = self.notebook.get_nth_page(index) if scrollable is not None and hasattr(scrollable, "get_children"): tv = scrollable.get_children()[0] if isinstance(tv, ServerTreeView): return tv raise ValueError("No treeview set") def update_tab_widths(self, col: Gtk.TreeViewColumn) -> None: # TODO: may cause pixel offsets when application is maximized width = col.get_width() title = col.get_title() for tab in (self.browser, self.saved, self.recent, self.lan): if tab == self.get_active_treeview(): continue for col in tab.get_columns(): if col.get_title() == title: 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 ) def get_browser(self) -> ServerTreeView: return self.browser def get_saved(self) -> ServerTreeView: return self.saved def get_recent(self) -> ServerTreeView: return self.recent def get_lan(self) -> ServerTreeView: return self.lan