diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index 515f315..2fbe2d8 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -694,7 +694,8 @@ class Controller: if len(fav) < 1: return None, None ip = str(self.query_config(Preferences.FAV_SRV)) - return fav, ip + addr = ip.split(":") + return fav, f"{addr[0]}:{addr[2]}" def get_dist_cache(self) -> dict[str, "Haversine"]: return self.dist_cache diff --git a/dzgui/util/strings.py b/dzgui/util/strings.py index 7594aac..a72bd6e 100644 --- a/dzgui/util/strings.py +++ b/dzgui/util/strings.py @@ -512,31 +512,55 @@ server_labels = ServerLabels( class ConnectPanel: connect: str add: str - edit: str - favorite: str add_con: str placeholder: str entry_tooltip: str add_tooltip: str connect_tooltip: str - no_fav: str connect_panel = ConnectPanel( connect="Connect", add="Add", - edit="Edit", - favorite="Favorite server", add_con="Add/connect", placeholder="Enter IP or Battlemetrics ID", entry_tooltip=( - "- IP: Format as IP:Query port\ne.g. 192.168.1.1:27016\n" + "- IP: format as IP:Query port\ne.g. 192.168.1.1:27016\n" "- Battlemetrics: numeric server ID\ne.g. 123456" ), add_tooltip="Add to my servers", connect_tooltip="Connect to this server", +) + +@dataclass(slots=True, frozen=True) +class FavPanel: + heading: str + no_fav: str + +fav_panel = FavPanel( + heading="Favorite server", no_fav="None set. Right click a server and select 'Set favorite' to set.", ) +@dataclass(slots=True, frozen=True) +class LanPanel: + heading: str + default_button: str + custom_button: str + scan_button: str + placeholder: str + entry_tooltip: str + +lan_panel = LanPanel( + heading="LAN query port", + default_button="Default port (27016)", + custom_button="Custom port", + scan_button="Scan", + placeholder="Enter the query port (1-65535)", + entry_tooltip="Specify the port to search for DayZ servers on the local network" +) + + + distance_suffix = "Distance: calculating..." dialog_error = "ERROR" diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index 76064ad..6a997c6 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -1,4 +1,4 @@ -from typing import Self, TYPE_CHECKING +from typing import Literal, Self, TYPE_CHECKING from dzgui.util.strings import atomic_buttons, connect_panel from dzgui.const.constants import ( @@ -12,22 +12,24 @@ from dzgui.const.constants import ( import gi gi.require_version("Gtk", "3.0") -from gi.repository import Gtk # noqa E402 +from gi.repository import Gtk, GLib # noqa E402 if TYPE_CHECKING: + from GLib import SOURCE_REMOVE from dzgui.controllers.mc import Controller class Icon(Gtk.Image): - def __init__(self, name: str, l_margin=0) -> None: + def __init__(self, name: str, l_margin: int = 0) -> None: super().__init__(icon_name=name, icon_size=Gtk.IconSize.BUTTON, margin_start=l_margin, ypad=2, ) + class LargeIcon(Gtk.Image): - def __init__(self, name: str, l_margin=5) -> None: + def __init__(self, name: str, l_margin: int = 5) -> None: super().__init__(icon_name=name, icon_size=Gtk.IconSize.LARGE_TOOLBAR, margin_start=l_margin @@ -40,6 +42,7 @@ class IconButton(Gtk.Button): self.icon = Icon(icon, l_margin=margin) self.set_image(self.icon) self.set_image_position(Gtk.PositionType.RIGHT) + self.set_focus_on_click(False) class IconTextButton(IconButton): @@ -47,13 +50,10 @@ class IconTextButton(IconButton): super().__init__(icon, margin=5) self.set_label(label) - self.set_focus_on_click(False) - class LargeIconTextButton(IconButton): def __init__(self, icon: str, label: str) -> None: super().__init__(icon) - self.set_label(label) self.set_image(LargeIcon(icon)) @@ -77,22 +77,60 @@ class WebButton(IconTextButton): class RefreshButton(IconTextButton): - def __init__(self) -> None: - super().__init__(icon=REFRESH_ICON, label=atomic_buttons.refresh) + def __init__(self, controller: "Controller") -> None: + super().__init__( + icon=REFRESH_ICON, + label=atomic_buttons.refresh, + ) + self.controller = controller + self.time = 30 + self.set_margin_top(10) self.set_margin_start(80) self.set_margin_end(80) + self.set_tooltip_text(atomic_buttons.refresh_tooltip) + + self.connect("clicked", self._on_refresh_clicked) + + def _on_refresh_clicked(self, button: Self) -> None: + self.set_sensitive(False) + self.show_time(True) + GLib.timeout_add_seconds(1, self.decrement) + self.controller.refresh_tree() + + def decrement(self) -> "SOURCE_REMOVE" | Literal[True]: + self.time -= 1 + if self.time == 0: + self.time = 30 + self.show_time(False) + self.set_sensitive(True) + return GLib.SOURCE_REMOVE + self.show_time(True) + return True + + def show_time(self, state: bool) -> None: + if state is True: + self.set_label(f"{atomic_buttons.refresh} ({str(self.time)})") + else: + self.set_label(atomic_buttons.refresh) class KeysButton(IconTextButton): - def __init__(self) -> None: + def __init__(self, controller: "Controller") -> None: super().__init__(icon=INPUT_KEYBOARD, label=atomic_buttons.keys) + + self.controller = controller self.set_margin_top(10) self.set_margin_start(80) self.set_margin_end(80) self.set_tooltip_text(atomic_buttons.keys_tooltip) + self.connect("clicked", self._on_keys_clicked) + + def _on_keys_clicked(self, button: Gtk.Button) -> None: + self.controller.open_keybindings() + class SteamConnectButton(LargeIconTextButton): def __init__(self) -> None: diff --git a/dzgui/views/components/connect_panel.py b/dzgui/views/components/connect_panel.py index c5610ea..efdb26d 100644 --- a/dzgui/views/components/connect_panel.py +++ b/dzgui/views/components/connect_panel.py @@ -2,8 +2,9 @@ from typing import TYPE_CHECKING from dzgui.api.servers import validate_ip from dzgui.util.css import add_class, remove_class -from dzgui.util.strings import connect_panel +from dzgui.util.strings import connect_panel, fav_panel, lan_panel from dzgui.views.components.buttons import AddButton, ClipboardButton, SteamConnectButton +from dzgui.views.components.entry import IpEntry, PortEntry from dzgui.views.components.labels import BoldLabel import gi @@ -21,29 +22,70 @@ class LanPanel(Gtk.Frame): super().__init__(margin_top=10, margin_bottom=5) # TODO: hide lan panel on other tabs - # TODO: strings - label = BoldLabel("LAN query port") + label = BoldLabel(lan_panel.heading) self.set_label_widget(label) - radio1 = Gtk.RadioButton.new_with_label(None, "Default port (27016)") - radio2 = Gtk.RadioButton.new_with_label_from_widget(radio1, "Custom port") + radio1 = Gtk.RadioButton.new_with_label(None, lan_panel.default_button) + radio2 = Gtk.RadioButton.new_with_label_from_widget(radio1, lan_panel.custom_button) - self.entry = Gtk.Entry(placeholder_text="Enter the query port (1-65535)", sensitive=False, hexpand=True) - self.button = Gtk.Button(label="Scan") + self.entry = PortEntry(controller) + self.scan = Gtk.Button(label=lan_panel.scan_button) + + self.entry.connect("string_validated", self._on_port_validated) radio1.connect("toggled", self._on_radio_toggled) self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5) self.grid.attach(radio1, 0, 0, COLS, ROWS) - # TODO: expression to attach in a line self.grid.attach_next_to(radio2, radio1, Gtk.PositionType.RIGHT, COLS, ROWS) self.grid.attach_next_to(self.entry, radio2, Gtk.PositionType.RIGHT, COLS, ROWS) - self.grid.attach_next_to(self.button, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS) + self.grid.attach_next_to(self.scan, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS) self.add(self.grid) def _on_radio_toggled(self, button: Gtk.RadioButton) -> None: self.entry.set_sensitive(not button.get_active()) - # TODO: validate custom port entry + if len(self.entry.get_text()) < 1: + self.scan.set_sensitive(False) + + def _on_port_validated(self, entry: Gtk.Entry, state: bool) -> None: + self.scan.set_sensitive(state) + +class FavPanel(Gtk.Frame): + def __init__(self, controller: "Controller") -> None: + super().__init__(margin_top=10, margin_bottom=5) + + label = BoldLabel(fav_panel.heading) + self.set_label_widget(label) + + self.controller = controller + + # TODO: improve upon this, cache fav ip when changed globally + user_fav, self.fav_ip = self.controller.get_favorite() + 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) + + self.fav_button = SteamConnectButton() + if server_name is None: + self.fav_button.set_sensitive(False) + copy = ClipboardButton(self.controller, self.get_fav_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(copy, scrollable_label, Gtk.PositionType.RIGHT, COLS, ROWS) + grid.attach_next_to(self.fav_button, copy, Gtk.PositionType.RIGHT, COLS, ROWS) + + self.add(grid) + + def get_fav_ip(self) -> str: + return self.fav_ip + + def set_fav_label(self, text: str) -> None: + # TODO: called by controller when changing fav + self.fav_label.set_text(text) + class AddPanel(Gtk.Frame): def __init__(self, controller: "Controller") -> None: @@ -60,59 +102,19 @@ class AddPanel(Gtk.Frame): self.conn_server.set_sensitive(False) self.add_server.set_sensitive(False) - self.entry = Gtk.Entry( - placeholder_text=connect_panel.placeholder, - hexpand=True, - tooltip_text=connect_panel.entry_tooltip - ) - self.entry.connect("key-press-event", self._on_entry_keypress) - self.entry.connect("changed", self._on_text_changed) + self.entry = IpEntry(controller) + self.entry.connect("string_validated", self._on_ip_validated) self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5) self.grid.attach(self.entry, 0, 0, COLS, ROWS) - - els = ( - (self.add_server, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS), - (self.conn_server, self.add_server, Gtk.PositionType.RIGHT, COLS, ROWS), - ) - - for el, sibling, pos, h_span, v_span in els: - self.grid.attach_next_to(el, sibling, pos, h_span, v_span) + self.grid.attach_next_to(self.add_server, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS) + self.grid.attach_next_to(self.conn_server, self.add_server, Gtk.PositionType.RIGHT, COLS, ROWS) self.add(self.grid) - def mark_valid(self) -> None: - self.conn_server.set_sensitive(True) - self.add_server.set_sensitive(True) - remove_class(self.entry, self.classname) - - def mark_invalid(self) -> None: - self.conn_server.set_sensitive(False) - self.add_server.set_sensitive(False) - add_class(self.entry, self.classname) - - def _on_text_changed(self, entry: Gtk.Entry) -> None: - # TODO: on submission, strip whitespace and newlines - text = entry.get_text() - if len(text) < 1: - self.conn_server.set_sensitive(False) - self.add_server.set_sensitive(False) - remove_class(entry, self.classname) - return - try: - validate_ip(text) - self.mark_valid() - except Exception: - if text.isdigit(): - self.mark_valid() - else: - self.mark_invalid() - - def _on_entry_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> None: - if event.keyval == Gdk.KEY_Escape: - # NOTE: unselect text - entry.select_region(0, 0) - self.controller.grab_active_treeview() + def _on_ip_validated(self, entry: Gtk.Entry, state: bool) -> None: + self.conn_server.set_sensitive(state) + self.add_server.set_sensitive(state) class ConnectPanel(Gtk.Box): @@ -121,39 +123,9 @@ class ConnectPanel(Gtk.Box): self.controller = controller - user_fav, self.fav_ip = self.controller.get_favorite() + self.lan = LanPanel(controller) + self.fav = FavPanel(controller) + self.add_panel = AddPanel(controller) - 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) - - self.fav_button = SteamConnectButton() - self.fav_edit = Gtk.Button(label=connect_panel.edit) - if server_name is None: - self.fav_button.set_sensitive(False) - - self.lan = LanPanel(self.controller) - self.add_panel = AddPanel(self.controller) - - label = BoldLabel("Favorite server") - frame = Gtk.Frame(margin_top=10, margin_bottom=5, label_widget=label) - b = ClipboardButton(self.controller, self.get_fav_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) - grid.attach_next_to(self.fav_button, b, Gtk.PositionType.RIGHT, COLS, ROWS) - frame.add(grid) - - for el in self.lan, frame, self.add_panel: + for el in self.lan, self.fav, self.add_panel: self.add(el) - - def get_fav_ip(self) -> str: - return self.fav_ip - - def set_fav_label(self, text: str) -> None: - # TODO: called by controller when changing fav - self.fav_label.set_text(text) - diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index b994ee0..00e26e3 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -1,27 +1,23 @@ from typing import Literal, TYPE_CHECKING -import gi # noqa E402 -gi.require_version("Gtk", "3.0") -from gi.repository import Gtk, Gdk, GLib - -from dzgui.const.enum import Preferences from dzgui.views.components.buttonbox import ButtonBox from dzgui.views.components.filter_panel import FilterPanel from dzgui.views.components.mod_panel import ModSelectionPanel from dzgui.views.components.buttons import RefreshButton, KeysButton -from dzgui.const.constants import NO_EXPAND, NO_FILL, EXPAND, FILL, INPUT_KEYBOARD, NO_PADDING +from dzgui.const.constants import NO_EXPAND, NO_FILL, FILL, NO_PADDING from dzgui.util.strings import atomic_buttons +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GLib # noqa E402 + if TYPE_CHECKING: from GLib import SOURCE_REMOVE -# TODO: refactor depends on ServerTreeView class RightPanel(Gtk.Box): def __init__(self, controller): super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL) - self.time = 30 - self.controller = controller self.controller.register_widget("right_panel", self) @@ -29,15 +25,8 @@ class RightPanel(Gtk.Box): self.filters_vbox = FilterPanel(controller) 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() - self.keys.connect("clicked", self._on_keys_clicked) - #self.keys.set_focus_on_click(False) + self.refresh_button = RefreshButton(controller) + self.keys = KeysButton(controller) for el in self.button_vbox, self.keys, self.filters_vbox, self.refresh_button: self.pack_start(el, NO_EXPAND, FILL, NO_PADDING) @@ -48,29 +37,11 @@ class RightPanel(Gtk.Box): def reinit_maps(self, rows: list) -> None: self.controller.reinit_map_store() # TODO: communicate with controller - #controller.clear_map_store() + #self.controller.clear_map_store() #map_store.append(["All maps"]) self.selected = "All maps" self.filters_vbox.set_unique_maps(rows) - def decrement(self) -> "SOURCE_REMOVE" | Literal[True]: - self.time -= 1 - if self.time == 0: - self.time = 30 - self.refresh_button.set_label(refresh) - self.refresh_button.set_sensitive(True) - return GLib.SOURCE_REMOVE - # TODO: abstract this formatting logic - self.refresh_button.set_label(f"{atomic_buttons.refresh} ({str(self.time)})") - 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"{atomic_buttons.refresh} ({str(self.time)})") - GLib.timeout_add_seconds(1, self.decrement) - self.controller.refresh_tree() - # TODO: reference #def _on_ping_clicked(self, button: Gtk.Button) -> None: # block_signals() @@ -103,8 +74,5 @@ class RightPanel(Gtk.Box): # thread = threading.Thread(target=_update_pings, args=()) # thread.start() - def _on_keys_clicked(self, button: Gtk.Button) -> None: - self.controller.open_keybindings() - def focus_button_box(self) -> None: self.button_vbox.buttons[0].grab_focus() diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index 4975cac..800d016 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -211,7 +211,8 @@ class ServerTreeView(TreeView): case Gdk.KEY_m: self.controller.mediator.grid.right_panel.filters_vbox.maps_entry.grab_focus() case Gdk.KEY_i: - self.controller.mediator.grid.conpan.entry.grab_focus() + # TODO: + self.controller.mediator.grid.conpan.add_panel.entry.grab_focus() else: match event.keyval: case Gdk.KEY_l | Gdk.KEY_Right: