feat: live input validation

This commit is contained in:
aclist committed 2026-01-02 06:32:32 +09:00
1 parent 8a1c5bea6c
commit 3c82b71f76
8 files changed
+71 -38

No files matched your search

-3
View File
@@ -151,11 +151,9 @@ class ModelManager:
if prior_map == "All maps": if prior_map == "All maps":
rows = self.filter_map(filters) rows = self.filter_map(filters)
else: else:
AppNav.right_panel.ping.set_sensitive(True)
rows = self.filter_toggle_on(filters, *args) rows = self.filter_toggle_on(filters, *args)
case FilterMode.KEYWORD: case FilterMode.KEYWORD:
AppNav.right_panel.ping.set_sensitive(True)
rows = self.filter_toggle_on(filters, *args) rows = self.filter_toggle_on(filters, *args)
case FilterMode.TOGGLE_OFF: case FilterMode.TOGGLE_OFF:
@@ -164,7 +162,6 @@ class ModelManager:
rows = self.filtered rows = self.filtered
case FilterMode.TOGGLE_ON: case FilterMode.TOGGLE_ON:
AppNav.right_panel.ping.set_sensitive(True)
rows = self.filter_toggle_on(filters, *args) rows = self.filter_toggle_on(filters, *args)
if mode is not FilterMode.INITIAL: if mode is not FilterMode.INITIAL:
+8 -1
View File
@@ -11,11 +11,18 @@ def add_class(widget: Gtk.Widget, label: str) -> None:
context = widget.get_style_context() context = widget.get_style_context()
context.add_class(label) context.add_class(label)
# TODO: def remove_class()
def remove_class(widget: Gtk.Widget, label: str) -> None:
context = widget.get_style_context()
context.remove_class(label)
def load_css() -> None: def load_css() -> None:
# TODO: consider storing this in a data file # TODO: consider storing this in a data file
css = """ css = """
.invalid-entry {
border-color: red;
}
.frame { .frame {
border: 0px; border: 0px;
} }
+6 -11
View File
@@ -80,14 +80,9 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# return rows # return rows
# #
#def process_tree_option(choice: RowType) -> None: #def process_tree_option(choice: RowType) -> None:
# context = AppNav.treeview.view
# command = choice
# cmd_string = command.dict["label"]
# logger.info(f"Parsing tree option '{command}' for the context '{context}'")
#
# # server tables # # server tables
# if command == RowType.RESOLVE_IP: # if command == RowType.RESOLVE_IP:
# record = AppNav.treeview.get_record() # record = treeview.get_record()
# wait_msg = command.dict["wait_msg"] # wait_msg = command.dict["wait_msg"]
# show_wait_dialog = True # show_wait_dialog = True
# #
@@ -103,12 +98,12 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# if command == RowType.QUICK_CONNECT: # if command == RowType.QUICK_CONNECT:
# record = MainController.query_config(Preferences.FAV_SRV) # record = MainController.query_config(Preferences.FAV_SRV)
# if record == "": # if record == "":
# AppNav.window.spawn_dialog("No favorite server currently set", Popup.NOTIFY) # spawn_dialog("No favorite server currently set", Popup.NOTIFY)
# return # return
# #
# record = str_to_record(record) # record = str_to_record(record)
# thread_new_with_dialog( # thread_new_with_dialog(
# AppNav.treeview.prepare_connection, # treeview.prepare_connection,
# parse_shell_output, # parse_shell_output,
# "Querying server", # "Querying server",
# command, # command,
@@ -131,7 +126,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
#def connect_by_ip(enum: RowType, response: str) -> None: #def connect_by_ip(enum: RowType, response: str) -> None:
# def _prep(response: str) -> None: # def _prep(response: str) -> None:
# record = Servers.validate_ip(response) # record = Servers.validate_ip(response)
# proc = AppNav.treeview.prepare_connection(record) # proc = treeview.prepare_connection(record)
# return proc # return proc
# #
# thread_new_with_dialog( # thread_new_with_dialog(
@@ -155,7 +150,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# logger.critical(e) # logger.critical(e)
# # raise dialog # # raise dialog
# return # return
# # proc = AppNav.treeview.prepare_connection(record) # # proc = treeview.prepare_connection(record)
# # return proc # # return proc
# #
# #thread_new_with_dialog( # #thread_new_with_dialog(
@@ -172,7 +167,7 @@ warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
# if enum == RowType.CONN_BY_ID: # if enum == RowType.CONN_BY_ID:
# key = MainController.query_config(Preferences.BM) # key = MainController.query_config(Preferences.BM)
# if len(key) == 0: # if len(key) == 0:
# AppNav.window.spawn_dialog( # spawn_dialog(
# "No Battlemetrics API key is set; see Options", Popup.NOTIFY # "No Battlemetrics API key is set; see Options", Popup.NOTIFY
# ) # )
# return # return
+44 -3
View File
@@ -1,12 +1,14 @@
from typing import TYPE_CHECKING 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
from dzgui.views.components.buttons import WebButton from dzgui.views.components.buttons import WebButton
from dzgui.views.components.labels import BoldLabel from dzgui.views.components.labels import BoldLabel
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa E402 from gi.repository import Gtk, Gdk # noqa E402
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.controllers.mc import Controller from dzgui.controllers.mc import Controller
@@ -17,6 +19,7 @@ class ConnectPanel(Gtk.Frame):
COLS = 1 COLS = 1
ROWS = 1 ROWS = 1
self.classname = "invalid-entry"
self.controller = controller self.controller = controller
self.entry = Gtk.Entry( self.entry = Gtk.Entry(
@@ -24,14 +27,16 @@ class ConnectPanel(Gtk.Frame):
hexpand=True, hexpand=True,
tooltip_text=connect_panel.entry_tooltip tooltip_text=connect_panel.entry_tooltip
) )
self.entry.connect("key-press-event", self._on_entry_keypress)
self.entry.connect("changed", self._on_text_changed)
# TODO: get ip as well? # TODO: get ip as well?
user_fav = self.controller.get_favorite_label() user_fav = self.controller.get_favorite_label()
server_name = user_fav if user_fav is not None else connect_panel.no_fav server_name = user_fav if user_fav is not None else connect_panel.no_fav
self.fav_label = Gtk.Label(label=server_name, halign=Gtk.Align.START)
scrollable_label = Gtk.ScrolledWindow() scrollable_label = Gtk.ScrolledWindow()
label = Gtk.Label(label=server_name, halign=Gtk.Align.START) scrollable_label.add(self.fav_label)
scrollable_label.add(label)
self.fav_button = Gtk.Button(label=connect_panel.connect, self.fav_button = Gtk.Button(label=connect_panel.connect,
tooltip_text=connect_panel.connect_tooltip tooltip_text=connect_panel.connect_tooltip
@@ -70,3 +75,39 @@ class ConnectPanel(Gtk.Frame):
self.grid.attach_next_to(el, sibling, pos, h_span, v_span) self.grid.attach_next_to(el, sibling, pos, h_span, v_span)
self.add(self.grid) 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:
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 as e:
if text.isdigit():
self.mark_valid()
else:
self.mark_invalid()
def set_fav_label(self, text: str) -> None:
# TODO: called by controller when changing fav
self.fav_label.set_text(text)
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()
+2 -10
View File
@@ -108,9 +108,8 @@ class FilterPanel(Gtk.Box):
def set_unique_maps(self, maps: list) -> None: def set_unique_maps(self, maps: list) -> None:
if len(maps) < 1: if len(maps) < 1:
return return
# FIXME: clear typehints u_maps = set([row[1] for row in maps])
u_maps = set([row[1] for row in maps]) # type: ignore u_maps = sorted(u_maps)
u_maps = sorted(u_maps) # type: ignore
for m in u_maps: for m in u_maps:
self.controller.append_map([m]) self.controller.append_map([m])
self.maps_hr.append(m) self.maps_hr.append(m)
@@ -124,13 +123,6 @@ class FilterPanel(Gtk.Box):
filters.append(k) filters.append(k)
return tuple(filters) return tuple(filters)
# used on personal/local server lists
def enable_all_filters(self) -> None:
for check in self.checks:
check.set_active(True)
for k in self.enabled_filters:
self.enabled_filters[k] = True
def reinit_panel(self) -> None: def reinit_panel(self) -> None:
self.keyword_entry.set_text("") self.keyword_entry.set_text("")
self.keyword_filter = "" self.keyword_filter = ""
+7 -8
View File
@@ -52,10 +52,9 @@ class RightPanel(Gtk.Box):
def _on_refresh_clicked(self, button: RefreshButton) -> None: def _on_refresh_clicked(self, button: RefreshButton) -> None:
self.controller.refresh_tree() self.controller.refresh_tree()
# TODO: reference
#def _on_ping_clicked(self, button: Gtk.Button) -> None: #def _on_ping_clicked(self, button: Gtk.Button) -> None:
# # TODO
# block_signals() # block_signals()
# def _update_pings(): # def _update_pings():
# # TODO # # TODO
# rows = ModelManager.get_filtered() # rows = ModelManager.get_filtered()
@@ -71,17 +70,17 @@ class RightPanel(Gtk.Box):
# temp_model[path][9] = res.ping # temp_model[path][9] = res.ping
# ModelManager.ping_cache[res.addr] = res.ping # ModelManager.ping_cache[res.addr] = res.ping
# # TODO: drop/rewrite # # TODO: drop/rewrite
# self.AppNav.treeview.set_model(temp_model) # treeview.set_model(temp_model)
# self.AppNav.treeview.wait_dialog.destroy() # treeview.wait_dialog.destroy()
# self.AppNav.treeview.grab_focus() # treeview.grab_focus()
# # TODO: # # TODO:
# unblock_signals() # unblock_signals()
# temp_model = self.AppNav.treeview.get_model() # temp_model = self.AppNav.treeview.get_model()
# self.AppNav.treeview.set_model(None) # treeview.set_model(None)
# self.AppNav.treeview.wait_dialog = GenericDialog("Pinging servers", Popup.WAIT) # treeview.wait_dialog = GenericDialog("Pinging servers", Popup.WAIT)
# self.AppNav.treeview.wait_dialog.show_all() # treeview.wait_dialog.show_all()
# thread = threading.Thread(target=_update_pings, args=()) # thread = threading.Thread(target=_update_pings, args=())
# thread.start() # thread.start()
+2 -2
View File
@@ -76,14 +76,14 @@ class ModDialog(GenericDialog):
# dialog.destroy() # dialog.destroy()
# # TODO: natively implemented # # TODO: natively implemented
# #if data.returncode == 1: # #if data.returncode == 1:
# # AppNav.window.spawn_dialog(strings.server_error, Popup.NOTIFY) # # spawn_dialog(strings.server_error, Popup.NOTIFY)
# # return # # return
# self.show_all() # self.show_all()
# self.set_markup(f"Modlist ({mod_count} mods)") # self.set_markup(f"Modlist ({mod_count} mods)")
# self.run() # self.run()
# self.destroy() # self.destroy()
# record = AppNav.treeview.get_record() # record = treeview.get_record()
# if not record: # if not record:
# return # return
+2
View File
@@ -184,6 +184,8 @@ class ServerTreeView(TreeView):
self.controller.mediator.grid.right_panel.filters_vbox.keyword_entry.grab_focus() self.controller.mediator.grid.right_panel.filters_vbox.keyword_entry.grab_focus()
case Gdk.KEY_m: case Gdk.KEY_m:
self.controller.mediator.grid.right_panel.filters_vbox.maps_entry.grab_focus() self.controller.mediator.grid.right_panel.filters_vbox.maps_entry.grab_focus()
case Gdk.KEY_i:
self.controller.mediator.grid.conpan.entry.grab_focus()
else: else:
match event.keyval: match event.keyval:
case Gdk.KEY_l | Gdk.KEY_Right: case Gdk.KEY_l | Gdk.KEY_Right: