Compare commits

...
7 Commits
Author SHA1 Message Date
aclist fc28d419bf fix: change bool state
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
2026-08-11 22:23:49 +09:00
aclist 095af395f9 feat: dynamic radio sensitivity on steam deck 2026-08-11 22:23:18 +09:00
aclist 4ada06d305 chore: test html escaping in descriptions 2026-08-11 21:44:47 +09:00
aclist 5a0ab92985 chore: add tech debt comment 2026-08-11 21:44:08 +09:00
aclist f2501871ee feat: update dialog on cancel action 2026-08-11 21:43:53 +09:00
aclist cf70182733 feat: unset fav server by button
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-08-09 23:48:17 +09:00
aclist 42feeae8d0 fix: html escape server desc 2026-08-09 21:07:37 +09:00
9 changed files with 139 additions and 43 deletions

No files matched your search

+1
View File
@@ -38,6 +38,7 @@ HEX_ORANGE = "#FFAC1C"
CARET_DOWN = "go-down-symbolic" CARET_DOWN = "go-down-symbolic"
CARET_UP = "go-up-symbolic" CARET_UP = "go-up-symbolic"
CLIPBOARD = "edit-copy-symbolic" CLIPBOARD = "edit-copy-symbolic"
CLOSE = "window-close-symbolic"
FOLDER = "folder-symbolic" FOLDER = "folder-symbolic"
EDIT_DELETE = "edit-delete-symbolic" EDIT_DELETE = "edit-delete-symbolic"
ERROR = "dialog-error-symbolic" ERROR = "dialog-error-symbolic"
+8
View File
@@ -402,6 +402,9 @@ class Controller(GObject.GObject):
def get_menu(self) -> "MenuTreeView": def get_menu(self) -> "MenuTreeView":
return self.mediator.menu return self.mediator.menu
def unset_fav(self) -> None:
self.config_man.unset_fav()
def has_favorites(self) -> bool: def has_favorites(self) -> bool:
favs = self.config_man.get_favorites() favs = self.config_man.get_favorites()
if len(favs) < 1: if len(favs) < 1:
@@ -459,6 +462,9 @@ class Controller(GObject.GObject):
open_workshop_page(mod, cmd) open_workshop_page(mod, cmd)
def has_note(self) -> bool: def has_note(self) -> bool:
# TODO: get record string, store in memory
# get note by record, get is in favs, etc. store as a block
# this is only used by context mixin
note = self.get_note() note = self.get_note()
if len(note) > 0: if len(note) > 0:
return True return True
@@ -473,11 +479,13 @@ class Controller(GObject.GObject):
return self.notes_man.get_note(record) return self.notes_man.get_note(record)
def add_note(self, note: str) -> None: def add_note(self, note: str) -> None:
# TODO: record should be cached upon creation of dialog
tv = self.get_active_treeview() tv = self.get_active_treeview()
record = tv.get_record_string() record = tv.get_record_string()
self.notes_man.add_note(record, note) self.notes_man.add_note(record, note)
def delete_note(self) -> None: def delete_note(self) -> None:
# TODO: record should be cached upon creation of dialog
tv = self.get_active_treeview() tv = self.get_active_treeview()
record = tv.get_record_string() record = tv.get_record_string()
self.notes_man.delete_note(record) self.notes_man.delete_note(record)
+7
View File
@@ -160,6 +160,13 @@ class ConfigManager:
except Exception: except Exception:
return return
def unset_fav(self) -> None:
try:
self.write_config(Preferences.FAV_LBL, "")
self.write_config(Preferences.FAV_SRV, "")
except Exception:
return
def write_config(self, key: Preferences, value: str) -> None: def write_config(self, key: Preferences, value: str) -> None:
try: try:
real_key = self.enum_to_key(key) real_key = self.enum_to_key(key)
+9
View File
@@ -10,6 +10,7 @@ from dzgui.util.strings import (
) )
from dzgui.const.constants import ( from dzgui.const.constants import (
CLIPBOARD, CLIPBOARD,
CLOSE,
INPUT_KEYBOARD, INPUT_KEYBOARD,
LIST_ADD, LIST_ADD,
REFRESH_ICON, REFRESH_ICON,
@@ -128,6 +129,14 @@ class CopyIpButton(ClipboardButton):
self.set_tooltip_text("Copy IP to clipboard") self.set_tooltip_text("Copy IP to clipboard")
class CloseButton(IconTextButton):
def __init__(self, label: str) -> None:
super().__init__(icon=CLOSE, label=label)
# TODO: strings
self.set_tooltip_text("Unset this server as favorite")
class WebButton(IconTextButton): class WebButton(IconTextButton):
def __init__(self, label: str) -> None: def __init__(self, label: str) -> None:
super().__init__(icon=WEB_BROWSER, label=label) super().__init__(icon=WEB_BROWSER, label=label)
+24 -5
View File
@@ -6,6 +6,7 @@ from dzgui.strings import connect_panel
from dzgui.util.keys import is_ctrl_mask from dzgui.util.keys import is_ctrl_mask
from dzgui.views.components.buttons import ( from dzgui.views.components.buttons import (
AddButton, AddButton,
CloseButton,
CopyIpButton, CopyIpButton,
Icon, Icon,
IconButton, IconButton,
@@ -187,17 +188,27 @@ class FavPanel(Gtk.Frame):
self.fav_button = SteamConnectButton() self.fav_button = SteamConnectButton()
self.fav_button.connect("clicked", self._on_connect_clicked) self.fav_button.connect("clicked", self._on_connect_clicked)
self.copy_button = CopyIpButton(self.controller, self.get_fav_ip) self.copy_button = CopyIpButton(self.controller, self.get_fav_ip)
# TODO: strings
self.unset_button = CloseButton("Unset")
self.unset_button.connect("clicked", self._on_unset_clicked)
if favorite is None: if favorite is None:
self.toggle_buttons(False) self.toggle_buttons(False)
# NOTE: disable vscrollbar to prevent layout jumping behavior # NOTE: disable vscrollbar to prevent layout jumping behavior
scrollable_label = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER) self.scrollable_label = Gtk.ScrolledWindow(
scrollable_label.add(self.fav_label) vscrollbar_policy=Gtk.PolicyType.NEVER, overlay_scrolling=False
)
self.scrollable_label.add(self.fav_label)
grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5) grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5)
grid.attach(scrollable_label, 0, 0, 3, ROWS) grid.attach(self.scrollable_label, 0, 0, 3, ROWS)
grid.attach_next_to( grid.attach_next_to(
self.copy_button, scrollable_label, Gtk.PositionType.RIGHT, COLS, ROWS self.unset_button, self.scrollable_label, Gtk.PositionType.RIGHT, COLS, ROWS
)
grid.attach_next_to(
self.copy_button, self.unset_button, Gtk.PositionType.RIGHT, COLS, ROWS
) )
grid.attach_next_to( grid.attach_next_to(
self.fav_button, self.copy_button, Gtk.PositionType.RIGHT, COLS, ROWS self.fav_button, self.copy_button, Gtk.PositionType.RIGHT, COLS, ROWS
@@ -205,8 +216,16 @@ class FavPanel(Gtk.Frame):
self.add(grid) self.add(grid)
def _on_unset_clicked(self, button: Gtk.Button) -> None:
try:
self.controller.unset_fav()
except Exception:
return
self.fav_label.set_text(connect_panel.favs_empty)
self.toggle_buttons(False)
def toggle_buttons(self, state: bool) -> None: def toggle_buttons(self, state: bool) -> None:
for button in self.fav_button, self.copy_button: for button in self.fav_button, self.copy_button, self.unset_button:
button.set_sensitive(state) button.set_sensitive(state)
def _on_connect_clicked(self, button: Gtk.Button) -> None: def _on_connect_clicked(self, button: Gtk.Button) -> None:
+6 -1
View File
@@ -107,7 +107,7 @@ class WaitDialog(GenericDialog):
self.cur_job = 1 self.cur_job = 1
self.cancel = Gtk.Button(label="Cancel", halign=Gtk.Align.CENTER) self.cancel = Gtk.Button(label="Cancel", halign=Gtk.Align.CENTER)
self.cancel.connect("clicked", lambda _: self.controller.set_cancel_event()) self.cancel.connect("clicked", self._on_cancel_clicked)
self.connect("delete-event", lambda widget, event: True) self.connect("delete-event", lambda widget, event: True)
content = self.get_content_area() content = self.get_content_area()
@@ -138,6 +138,11 @@ class WaitDialog(GenericDialog):
self.prog.set_fraction(fraction) self.prog.set_fraction(fraction)
self.cur_job += 1 self.cur_job += 1
def _on_cancel_clicked(self, button: Gtk.Button) -> None:
# TODO: strings
GLib.idle_add(self.update_text, "Caught cancel signal, cleaning up")
self.controller.set_cancel_event()
def show_cancel(self, state: bool) -> None: def show_cancel(self, state: bool) -> None:
self.cancel.set_visible(state) self.cancel.set_visible(state)
+3 -1
View File
@@ -1,3 +1,4 @@
import html
from typing import Self, TYPE_CHECKING from typing import Self, TYPE_CHECKING
from dzgui.const.constants import EXPAND, FILL from dzgui.const.constants import EXPAND, FILL
@@ -65,6 +66,7 @@ class ServerDialog(GenericDialog):
class ServerDetailsDialog(ServerDialog): class ServerDetailsDialog(ServerDialog):
def __init__(self, controller: "Controller", details: "Details"): def __init__(self, controller: "Controller", details: "Details"):
# TODO: server name should also be packed in details struct
name = controller.get_server_name() name = controller.get_server_name()
super().__init__(controller, strings.server_details, name, menu=None) super().__init__(controller, strings.server_details, name, menu=None)
@@ -109,7 +111,7 @@ class ServerDetailsDialog(ServerDialog):
self.view.set_model(self.store) self.view.set_model(self.store)
text = details.description text = details.description
text = format_hyperlinks(text) text = format_hyperlinks(text)
self.description.set_markup(text) self.description.set_markup(html.escape(text))
self.show_all() self.show_all()
+51 -36
View File
@@ -21,6 +21,7 @@ from dzgui.strings import errors, options
from dzgui.util import strings, css, open_links from dzgui.util import strings, css, open_links
from dzgui.views.components.box import VBox from dzgui.views.components.box import VBox
from dzgui.views.components.eventbox import InfoEventBox
from dzgui.views.components.labels import LeftLabel from dzgui.views.components.labels import LeftLabel
from dzgui.views.components.buttons import WebButton from dzgui.views.components.buttons import WebButton
from dzgui.views.components.frame import HeadingFrame from dzgui.views.components.frame import HeadingFrame
@@ -39,6 +40,37 @@ if TYPE_CHECKING:
from dzgui.controllers.emitter import Emitter from dzgui.controllers.emitter import Emitter
class ToggleField(Gtk.Box):
def __init__(
self,
controller: "Controller",
first_option: str,
second_option: str,
context: Preferences,
) -> None:
super().__init__(spacing=5, halign=Gtk.Align.START)
self.controller = controller
self.context = context
self.radio1 = Gtk.RadioButton.new_with_label(None, first_option)
self.radio2 = Gtk.RadioButton.new_from_widget(self.radio1)
self.radio2.set_label(second_option)
self.pack_start(self.radio1, NO_EXPAND, NO_FILL, NO_PADDING)
self.pack_start(self.radio2, NO_EXPAND, NO_FILL, NO_PADDING)
def set_suboption_active(self, state: bool) -> None:
# NOTE: defer connection of signal until after state is set
self.radio2.set_active(state)
self.radio1.connect("toggled", self._on_radio_toggled, self.context)
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
self.controller.toggle_config(context)
def set_sensitive(self, state: bool) -> None:
for el in self.radio1, self.radio2:
el.set_sensitive(state)
class ShortHBox(Gtk.Box): class ShortHBox(Gtk.Box):
def __init__(self, widget: Gtk.Widget) -> None: def __init__(self, widget: Gtk.Widget) -> None:
super().__init__(spacing=5, halign=Gtk.Align.START) super().__init__(spacing=5, halign=Gtk.Align.START)
@@ -80,19 +112,26 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
# TODO: make submit field a standalone class # TODO: make submit field a standalone class
self.player_box.get_children()[0].set_width_chars(30) # type: ignore self.player_box.get_children()[0].set_width_chars(30) # type: ignore
self.fullscreen_toggle = self.make_binary_radio( self.fullscreen_toggle = ToggleField(
self.controller,
strings.options.last_used, strings.options.last_used,
strings.options.always_fs, strings.options.always_fs,
Preferences.WINDOW, Preferences.WINDOW,
) )
# TODO: strings
eb = InfoEventBox(
"This option is not available on Steam Deck.", self.controller
)
self.fullscreen_toggle.add(eb)
self.client_combo = ClientCombo() self.client_combo = ClientCombo()
self.client_combo.connect("changed", self._on_client_changed) self.client_combo.connect("changed", self._on_client_changed)
client_hbox = ShortHBox(self.client_combo) client_hbox = ShortHBox(self.client_combo)
self.distance_toggle = self.make_binary_radio( self.distance_toggle = ToggleField(
strings.options.km, strings.options.mi, Preferences.DIST self.controller, strings.options.km, strings.options.mi, Preferences.DIST
) )
combo_store = Gtk.ListStore(str, object) combo_store = Gtk.ListStore(str, object)
@@ -293,14 +332,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
real_cmd = combo.get_model()[_iter][1] real_cmd = combo.get_model()[_iter][1]
self.controller.update_config(Preferences.CLIENT, real_cmd) self.controller.update_config(Preferences.CLIENT, real_cmd)
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
try:
self.controller.toggle_config(context)
except Exception:
button.handler_block_by_func(self._on_radio_toggled)
self.populate_settings()
button.handler_unblock_by_func(self._on_radio_toggled)
def _is_valid_text(self, text: str, context: Preferences) -> bool: def _is_valid_text(self, text: str, context: Preferences) -> bool:
if text.isspace(): if text.isspace():
return False return False
@@ -344,23 +375,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
state = self._is_valid_text(text, context) state = self._is_valid_text(text, context)
button.set_sensitive(state) button.set_sensitive(state)
def make_binary_radio(
self,
first_option: str,
second_option: str,
context: Preferences,
) -> Gtk.Box:
hbox = Gtk.Box(spacing=5, halign=Gtk.Align.START)
radio1 = Gtk.RadioButton.new_with_label(None, first_option)
radio2 = Gtk.RadioButton.new_from_widget(radio1)
radio2.set_label(second_option)
radio1.connect("toggled", self._on_radio_toggled, context)
hbox.pack_start(radio1, NO_EXPAND, NO_FILL, NO_PADDING)
hbox.pack_start(radio2, NO_EXPAND, NO_FILL, NO_PADDING)
return hbox
def populate_settings(self) -> None: def populate_settings(self) -> None:
prefs = self.controller.get_prefs() prefs = self.controller.get_prefs()
# NOTE: re-check in case file was removed by user between runs # NOTE: re-check in case file was removed by user between runs
@@ -386,14 +400,15 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
if hasattr(p, "set_text"): if hasattr(p, "set_text"):
p.set_text(name) p.set_text(name)
# NOTE: suppress toggle signal until radios are built fs = config["fullscreen"]
self._suppress_toggles(True) miles = config["use_miles"]
for el, conf_state in [
(self.fullscreen_toggle, config["fullscreen"]), if prefs.is_steam_deck:
(self.distance_toggle, config["use_miles"]), self.fullscreen_toggle.set_sensitive(False)
]: fs = True
el.get_children()[conf_state].set_active(True)
self._suppress_toggles(False) self.fullscreen_toggle.set_suboption_active(fs)
self.distance_toggle.set_suboption_active(miles)
# NOTE: disable buttons if no text is set # NOTE: disable buttons if no text is set
for field in ( for field in (
+30
View File
@@ -0,0 +1,30 @@
from dzgui.api.servers import Details
from dzgui.views.dialogs.servers import ServerDetailsDialog
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa E402
class MockController:
def __init__(self) -> None:
self.window = Gtk.Window()
def get_window(self) -> Gtk.Window:
return self.window
def get_server_name(self) -> str:
return "My server"
def get_emitter(self) -> None:
return None
def test_html_escaping() -> None:
text = "<Server description> Game & mods"
details = Details([["0", "1"]], text, True)
controller = MockController()
dialog = ServerDetailsDialog(controller, details)
assert dialog.description.get_text() == text