mirror of
https://github.com/aclist/dztui.git
synced 2026-08-27 02:07:18 +02:00
feat: dynamic port validation
This commit is contained in:
parent
b119569cf5
commit
fdb6b342f7
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user