diff --git a/CHANGELOG.md b/CHANGELOG.md index 52eec94..6ff240a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,21 @@ - Developers page (and -d flag) - Redact API key in log table - Integrate add/connect widgets into main menu +- Favorite/connect/LAN panels integrated with server views +- Colorized IP/ID validation +- Visual icons +- Integrated server notebook +- Retain position on servers +- Propagate width changes to all tables ## Changed - Reduce padding on keys button -- Add icon to keys button -- Add icons to API buttons - Boldface breadcrumbs +- Bold labels inside frames ## Unreleased - Setup wizard +- Copy favorite server IP to clipboard +- Set favorite server from tables +- Detailed/copyable trace in critical error dialogs +- Local documentation diff --git a/dzgui/data/CHANGELOG.md b/dzgui/data/CHANGELOG.md index 52eec94..6ff240a 100644 --- a/dzgui/data/CHANGELOG.md +++ b/dzgui/data/CHANGELOG.md @@ -15,12 +15,21 @@ - Developers page (and -d flag) - Redact API key in log table - Integrate add/connect widgets into main menu +- Favorite/connect/LAN panels integrated with server views +- Colorized IP/ID validation +- Visual icons +- Integrated server notebook +- Retain position on servers +- Propagate width changes to all tables ## Changed - Reduce padding on keys button -- Add icon to keys button -- Add icons to API buttons - Boldface breadcrumbs +- Bold labels inside frames ## Unreleased - Setup wizard +- Copy favorite server IP to clipboard +- Set favorite server from tables +- Detailed/copyable trace in critical error dialogs +- Local documentation diff --git a/dzgui/views/base.py b/dzgui/views/base.py index 3188e7b..19a6094 100644 --- a/dzgui/views/base.py +++ b/dzgui/views/base.py @@ -8,6 +8,7 @@ import signal import subprocess import textwrap import threading +import traceback import typing # noqa import warnings diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index b6edada..8517a11 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -1,3 +1,5 @@ +from typing import Callable, Self, TYPE_CHECKING + from dzgui.util.strings import refresh, connect_panel from dzgui.const.constants import ( CLIPBOARD, @@ -12,6 +14,9 @@ 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 Icon(Gtk.Image): def __init__(self, name: str, l_margin=0) -> None: @@ -52,8 +57,13 @@ class LargeIconTextButton(IconButton): class ClipboardButton(IconButton): - def __init__(self) -> None: + def __init__(self, controller: "Controller", data: str) -> None: super().__init__(CLIPBOARD) + self.controller = controller + self.connect("clicked", self._on_button_clicked, data) + + def _on_button_clicked(self, button: Self, data: str) -> None: + self.controller.copy_clipboard(data) class WebButton(IconTextButton): diff --git a/dzgui/views/components/connect_panel.py b/dzgui/views/components/connect_panel.py index 4e96ab2..03055c3 100644 --- a/dzgui/views/components/connect_panel.py +++ b/dzgui/views/components/connect_panel.py @@ -4,7 +4,7 @@ from dzgui.api.servers import validate_ip from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING from dzgui.util.css import add_class, remove_class from dzgui.util.strings import connect_panel -from dzgui.views.components.buttons import AddButton, ClipboardButton, IconTextButton, SteamConnectButton, WebButton +from dzgui.views.components.buttons import AddButton, ClipboardButton, SteamConnectButton from dzgui.views.components.labels import BoldLabel import gi @@ -18,31 +18,40 @@ class LanPanel(Gtk.Frame): def __init__(self, controller: "Controller") -> None: super().__init__(margin_top=10, margin_bottom=5) - # TODO: use grid, more column spacing # TODO: hide lan panel on other tabs - hbox = Gtk.Box(margin=10, spacing=5, halign=Gtk.Align.START) # TODO: strings label = BoldLabel("LAN query port") self.set_label_widget(label) + radio1 = Gtk.RadioButton.new_with_label(None, "Default port (27016)") radio2 = Gtk.RadioButton.new_from_widget(radio1) + # TODO: new_with_label_from_widget radio2.set_label("Custom port") - self.entry = Gtk.Entry(placeholder_text="Query port") + + self.entry = Gtk.Entry(placeholder_text="Query port", sensitive=False) self.button = Gtk.Button(label="Scan") radio1.connect("toggled", self._on_radio_toggled) - #hbox.pack_start(label, NO_EXPAND, NO_FILL, NO_PADDING) + + # TODO: use grid, more column spacing + hbox = Gtk.Box(margin=10, spacing=5, halign=Gtk.Align.START) hbox.pack_start(radio1, NO_EXPAND, NO_FILL, NO_PADDING) hbox.pack_start(radio2, NO_EXPAND, NO_FILL, NO_PADDING) hbox.pack_start(self.entry, NO_EXPAND, NO_FILL, NO_PADDING) hbox.pack_start(self.button, NO_EXPAND, NO_FILL, NO_PADDING) - self.entry.set_sensitive(False) - self.button.set_sensitive(False) + self.add(hbox) def _on_radio_toggled(self, button: Gtk.RadioButton) -> None: for el in self.entry, self.button: el.set_sensitive(not button.get_active()) +class AddPanel(Gtk.Frame): + def __init__(self, controller: "Controller") -> None: + super().__init__(margin_top=10, margin_bottom=5) + + label = BoldLabel("Connect") + self.set_label_widget(label) + class ConnectPanel(Gtk.Box): def __init__(self, controller: "Controller") -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL) @@ -60,9 +69,9 @@ class ConnectPanel(Gtk.Box): self.entry.connect("key-press-event", self._on_entry_keypress) self.entry.connect("changed", self._on_text_changed) - user_fav, user_ip = self.controller.get_favorite() + user_fav, self.fav_ip = self.controller.get_favorite() - server_name = f"{user_fav} ({user_ip})" if user_fav is not None else connect_panel.no_fav + server_name = f"{user_fav} ({self.fav_ip})" if user_fav is not None else connect_panel.no_fav self.fav_label = Gtk.Label(label=server_name, track_visited_links=False, halign=Gtk.Align.START, hexpand=True) scrollable_label = Gtk.ScrolledWindow() scrollable_label.add(self.fav_label) @@ -78,18 +87,10 @@ class ConnectPanel(Gtk.Box): if server_name is None: self.fav_button.set_sensitive(False) - add_label = BoldLabel(connect_panel.add_con) - conn_label = BoldLabel(connect_panel.favorite) - self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5) - #self.grid.attach(conn_label, 0, 0, COLS, ROWS) self.grid.attach(self.entry, 0, 0, COLS, ROWS) els = ( - #(scrollable_label, conn_label, Gtk.PositionType.RIGHT, 3, ROWS), - #(self.fav_button, scrollable_label, Gtk.PositionType.RIGHT, COLS, ROWS), - #(add_label, conn_label, Gtk.PositionType.BOTTOM, COLS, ROWS), - #(self.entry, scrollable_label, Gtk.PositionType.BOTTOM, COLS, ROWS), (self.add_server, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS), (self.conn_server, self.add_server, Gtk.PositionType.RIGHT, COLS, ROWS), ) @@ -102,9 +103,11 @@ class ConnectPanel(Gtk.Box): label = BoldLabel("Favorite server") frame = Gtk.Frame(margin_top=10, margin_bottom=5, label_widget=label) - b = ClipboardButton() + b = ClipboardButton(self.controller, self.get_fav_ip()) + b.set_focus_on_click(False) b.set_tooltip_text("Copy IP to clipboard") - b.connect("clicked", self._on_ip_clicked, user_ip) + + # FIXME: height of connect buttons is not equivalent grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5) grid.attach(scrollable_label, 0, 0, 3, ROWS) grid.attach_next_to(b, scrollable_label, Gtk.PositionType.RIGHT, COLS, ROWS) @@ -117,10 +120,10 @@ class ConnectPanel(Gtk.Box): frame.add(self.grid) self.add(frame) - self.lan.set_visible(False) + #self.lan.set_visible(False) - def _on_ip_clicked(self, button: Gtk.Button, ip: str) -> None: - self.controller.copy_clipboard(ip) + def get_fav_ip(self) -> str: + return self.fav_ip def mark_valid(self) -> None: self.conn_server.set_sensitive(True) @@ -151,7 +154,6 @@ class ConnectPanel(Gtk.Box): def set_fav_label(self, text: str) -> None: # TODO: called by controller when changing fav - # TODO: href logic self.fav_label.set_text(text) def _on_entry_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> None: diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index 4874901..c657c8a 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -30,7 +30,9 @@ class RightPanel(Gtk.Box): self.sel_panel = ModSelectionPanel(controller) self.refresh_button = RefreshButton() + # TODO: consolidate into class self.refresh_button.set_tooltip_text(refresh_tooltip) + self.refresh_button.set_focus_on_click(False) self.refresh_button.connect("clicked", self._on_refresh_clicked) self.keys = KeysButton(keys_button) @@ -63,6 +65,7 @@ class RightPanel(Gtk.Box): return True def _on_refresh_clicked(self, button: RefreshButton) -> None: + # TODO: could be internal to refresh button class self.refresh_button.set_sensitive(False) self.refresh_button.set_label(f"{refresh} ({str(self.time)})") GLib.timeout_add_seconds(1, self.decrement) diff --git a/dzgui/views/dialogs/generic.py b/dzgui/views/dialogs/generic.py index d76fb19..e834e85 100644 --- a/dzgui/views/dialogs/generic.py +++ b/dzgui/views/dialogs/generic.py @@ -1,7 +1,7 @@ import textwrap -from typing import Any, Literal, TYPE_CHECKING +from typing import Any, Literal, Self, TYPE_CHECKING -from dzgui.const.constants import NO_EXPAND, NO_FILL +from dzgui.const.constants import NO_EXPAND, NO_FILL, EXPAND, FILL from dzgui.const.enum import Popup, ButtonType, NotebookPage from dzgui.util import strings @@ -13,6 +13,63 @@ from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402 if TYPE_CHECKING: from dzgui.controllers.mc import Controller +class ExceptionDialog(Gtk.MessageDialog): + def __init__(self, controller: "Controller", trace: str): + super().__init__( + transient_for=controller.mediator.window, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text="Error", + secondary_text="Something went wrong. See the detailed error below.", + title=strings.dialog_header, + modal=True, + ) + + # TODO: strings + self.set_size_request(550, 250) + + from dzgui.views.components.buttons import ClipboardButton + content = self.get_content_area() + content.set_spacing(0) + + box = Gtk.Box(hexpand=True, vexpand=True, orientation=Gtk.Orientation.VERTICAL) + textview = Gtk.TextView(wrap_mode=Gtk.WrapMode.WORD, editable=False, left_margin=10, right_margin=10) + textview.set_buffer(Gtk.TextBuffer(text=trace)) + # NOTE: box expands to end of content area + box.pack_start(textview, EXPAND, FILL, 10) + + action_area = self.get_action_area() + action_area.set_spacing(10) + action_area.set_margin_bottom(10) + action_area.set_layout(Gtk.ButtonBoxStyle.CENTER) + + but = ClipboardButton(controller, trace) + # TODO: flag to set button with text, or other class + but.set_label("Copy") + action_area.pack_start(but, True, True, 10) + # NOTE: reverse button order after insertion + action_area.set_direction(Gtk.TextDirection.RTL) + content.add(box) + + self.set_default_response(Gtk.ResponseType.OK) + self.connect("response", self._on_response) + self.show_all() + """ + usage: + from dzgui.views.dialogs.generic import ExceptionDialog + try: + a.banana() + except Exception as e: + trace = traceback.format_exc() + dialog = ExceptionDialog(MainController, trace) + dialog.run() + """ + + + def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None: + self.destroy() + + class GenericDialog(Gtk.MessageDialog): def __init__(self, controller: "Controller", text: str, mode: Popup): match mode: @@ -41,7 +98,6 @@ class GenericDialog(Gtk.MessageDialog): button_type = Gtk.ButtonsType.OK header_text = strings.server_details - # NOTE: steam deck prints <2> if dialog title is same as window title Gtk.MessageDialog.__init__( self, transient_for=controller.mediator.window, @@ -49,6 +105,7 @@ class GenericDialog(Gtk.MessageDialog): buttons=button_type, text=header_text, secondary_text=textwrap.fill(text, 50), + # NOTE: steam deck prints <2> if dialog title is same as window title title=strings.dialog_header, modal=True, )