chore: normalize dialogs

This commit is contained in:
aclist committed 2026-01-12 03:44:41 +09:00
1 parent 45bd0e16ec
commit 1bc21d6083
15 files changed
+283 -282

No files matched your search

+1
View File
@@ -26,6 +26,7 @@
- Reduce padding on keys button - Reduce padding on keys button
- Boldface breadcrumbs - Boldface breadcrumbs
- Bold labels inside frames - Bold labels inside frames
- Unsticky keys when clicking sidebar
## Unreleased ## Unreleased
- Setup wizard - Setup wizard
-1
View File
@@ -14,7 +14,6 @@ class Popup(Enum):
NOTIFY = 2 NOTIFY = 2
CONFIRM = 3 CONFIRM = 3
ENTRY = 4 ENTRY = 4
RETURN = 5
MODLIST = 6 MODLIST = 6
DETAILS = 7 DETAILS = 7
QUIT = 8 QUIT = 8
+10 -17
View File
@@ -2,12 +2,14 @@ import logging
import shutil import shutil
import threading import threading
import textwrap import textwrap
import traceback
from pathlib import Path from pathlib import Path
from typing import Any, Callable, TYPE_CHECKING from typing import Any, Callable, TYPE_CHECKING
import dzgui.api.pefile as PeFile import dzgui.api.pefile as PeFile
import dzgui.util._json as JSON # noqa import dzgui.util._json as JSON # noqa
from dzgui.views.dialogs.generic import ExceptionDialog
from dzgui.api.probe import test_steam_api, test_bm_api from dzgui.api.probe import test_steam_api, test_bm_api
from dzgui.api.mods import ( from dzgui.api.mods import (
@@ -38,7 +40,7 @@ from dzgui.config import update
from dzgui.config.query import lookup from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs from dzgui.config.userprefs import UserPrefs
from dzgui.controllers.model import ModelManager from dzgui.controllers.model import ModelManager
from dzgui.util import cooldown, strings from dzgui.util import strings
from dzgui.util.diag import write_diagnostic from dzgui.util.diag import write_diagnostic
from dzgui.util._json import read_json, write_json from dzgui.util._json import read_json, write_json
from dzgui.util.localize import number from dzgui.util.localize import number
@@ -89,7 +91,6 @@ class Controller:
self.crumbs_cache = "" self.crumbs_cache = ""
self.mediator = AppNavigation() self.mediator = AppNavigation()
self.prefs: UserPrefs self.prefs: UserPrefs
self.cooldown = 0
self.model_manager = ModelManager() self.model_manager = ModelManager()
@@ -291,14 +292,18 @@ class Controller:
update.toggle_config(config, context) update.toggle_config(config, context)
except Exception as e: except Exception as e:
logger.critical(e) logger.critical(e)
self.spawn_dialog(strings.something_wrong, Popup.NOTIFY) trace = traceback.format_exc()
dialog = ExceptionDialog(self, trace)
dialog.run()
def update_config(self, key: Preferences, value: str) -> None: def update_config(self, key: Preferences, value: str) -> None:
try: try:
update.write_config(self.prefs.paths.config, key, value) update.write_config(self.prefs.paths.config, key, value)
except Exception as e: except Exception as e:
logger.critical(e) logger.critical(e)
self.spawn_dialog(strings.something_wrong, Popup.NOTIFY) trace = traceback.format_exc()
dialog = ExceptionDialog(self, trace)
dialog.run()
# TODO: suppress signals # TODO: suppress signals
# then reenable (or it spawns dialog twice) # then reenable (or it spawns dialog twice)
self.mediator.grid.notebook.settings.populate_settings() self.mediator.grid.notebook.settings.populate_settings()
@@ -308,19 +313,6 @@ class Controller:
#def present_toast(self, text: str) -> None: #def present_toast(self, text: str) -> None:
# self.mediator.window.toast.set_text_and_fade(text) # self.mediator.window.toast.set_text_and_fade(text)
def start_cooldown(self) -> None:
self.cooldown = cooldown.get_time()
def get_cooldown(self) -> None:
return self.cooldown
def manage_cooldown(self) -> bool:
if cooldown.is_elapsed(self.cooldown):
self.cooldown = cooldown.get_time()
return True
else:
return False
def open_keybindings(self) -> None: def open_keybindings(self) -> None:
notebook = self.mediator.grid.notebook notebook = self.mediator.grid.notebook
notebook.toggle_keybindings() notebook.toggle_keybindings()
@@ -329,6 +321,7 @@ class Controller:
notebook = self.mediator.grid.notebook notebook = self.mediator.grid.notebook
notebook.focus_current() notebook.focus_current()
# TODO: deprecated
def spawn_dialog(self, msg: str, mode: Popup) -> bool: def spawn_dialog(self, msg: str, mode: Popup) -> bool:
""" """
Spawns a GenericDialog transient to the OuterWindow Spawns a GenericDialog transient to the OuterWindow
+1
View File
@@ -26,6 +26,7 @@
- Reduce padding on keys button - Reduce padding on keys button
- Boldface breadcrumbs - Boldface breadcrumbs
- Bold labels inside frames - Bold labels inside frames
- Unsticky keys when clicking sidebar
## Unreleased ## Unreleased
- Setup wizard - Setup wizard
+2 -6
View File
@@ -3,14 +3,13 @@ import logging
import os import os
import sys import sys
from dzgui.api.mods import remove_stale_signatures from typing import TYPE_CHECKING
from dzgui.api.mods import remove_stale_signatures
from dzgui.config.ipdb import get_ipdb from dzgui.config.ipdb import get_ipdb
from dzgui.config.userprefs import UserPrefs from dzgui.config.userprefs import UserPrefs
from dzgui.config.xdg import get_xdg_paths, parse_filepaths from dzgui.config.xdg import get_xdg_paths, parse_filepaths
from dzgui.const.update import ALLOW_UPDATES from dzgui.const.update import ALLOW_UPDATES
from dzgui.init.coords import get_local_coords from dzgui.init.coords import get_local_coords
from dzgui.init.dayz import is_dayz_installed from dzgui.init.dayz import is_dayz_installed
from dzgui.init.flock import lock_acquire from dzgui.init.flock import lock_acquire
@@ -42,8 +41,6 @@ parser.add_argument("-u", "--uninstall", action="store_true", help=flags.uninsta
parser.add_argument("-d", "--developers", action="store_true", help=flags.developers) parser.add_argument("-d", "--developers", action="store_true", help=flags.developers)
args = parser.parse_args() args = parser.parse_args()
from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from pathlib import Path from pathlib import Path
@@ -110,7 +107,6 @@ def main() -> None:
# TODO: move into module # TODO: move into module
if has_steam_client() is False: if has_steam_client() is False:
EarlyAlertDialog(init.requires_steam) EarlyAlertDialog(init.requires_steam)
sys.exit(1)
is_dayz_installed(XDG.config) is_dayz_installed(XDG.config)
is_dayz_running() is_dayz_running()
-16
View File
@@ -1,16 +0,0 @@
from datetime import datetime
def get_time() -> int:
return int(datetime.now().timestamp())
# TODO: deprecated
#def write_time(path: Path) -> None:
# time = get_time()
# Path.write_text(str(time))
def is_elapsed(time_before: int) -> bool:
time_now = get_time()
if time_now - time_before <= 30:
return False
else:
return True
+18 -5
View File
@@ -86,9 +86,10 @@ scan_servers = "Scan LAN servers"
select_port = "Select the query port" select_port = "Select the query port"
# Errors # Errors
error_heading = "Error"
config_not_found = "DZGUI configuration file not found. Please exit and restart." config_not_found = "DZGUI configuration file not found. Please exit and restart."
cannot_acquire_lock = "DZGUI is already open." cannot_acquire_lock = "DZGUI is already open."
something_wrong = "Something went wrong." something_wrong = "Something went wrong. See the detailed error below."
steam_key_missing = "No Steam API key is set." steam_key_missing = "No Steam API key is set."
malformed_mods = "Found mods on system, but was unable to parse results." malformed_mods = "Found mods on system, but was unable to parse results."
steam_missing = "Local Steam installation is not set, possibly malformed config file." steam_missing = "Local Steam installation is not set, possibly malformed config file."
@@ -154,8 +155,6 @@ label_main_menu = "Main menu"
# buttons # buttons
ping_servers = "Ping servers" ping_servers = "Ping servers"
debug_mode = "Debug mode" debug_mode = "Debug mode"
keys_button = "Keys"
keys_tooltip = "Toggles the keybindings dialog"
debug_tooltip = ( debug_tooltip = (
"Used to perform a dry run without\n" "Used to perform a dry run without\n"
"actually connecting to a server" "actually connecting to a server"
@@ -539,6 +538,20 @@ connect_panel = ConnectPanel(
) )
distance_suffix = "Distance: calculating..." distance_suffix = "Distance: calculating..."
dialog_error = "ERROR"
refresh = "Refresh" @dataclass(slots=True, frozen=True)
refresh_tooltip = "Refresh server data" class AtomicButton:
refresh: str
refresh_tooltip: str
copy: str
keys: str
keys_tooltip: str
atomic_buttons = AtomicButton(
refresh="Refresh",
refresh_tooltip="Refresh server data",
copy="Copy",
keys="Keys",
keys_tooltip="Toggles the keybindings dialog",
)
+5 -1
View File
@@ -8,7 +8,6 @@ import signal
import subprocess import subprocess
import textwrap import textwrap
import threading import threading
import traceback
import typing # noqa import typing # noqa
import warnings import warnings
@@ -466,3 +465,8 @@ class App(Gtk.Application):
self.win.halt_proc_and_quit() self.win.halt_proc_and_quit()
MainController = Controller() MainController = Controller()
# TODO: icon sanity check, log result
#theme = Gtk.IconTheme.get_default()
#icons = theme.list_icons(None)
#print("steam_tray_mono" in icons)
+13 -7
View File
@@ -1,6 +1,6 @@
from typing import Callable, Self, TYPE_CHECKING from typing import Self, TYPE_CHECKING
from dzgui.util.strings import refresh, connect_panel from dzgui.util.strings import atomic_buttons, connect_panel
from dzgui.const.constants import ( from dzgui.const.constants import (
CLIPBOARD, CLIPBOARD,
INPUT_KEYBOARD, INPUT_KEYBOARD,
@@ -47,6 +47,8 @@ class IconTextButton(IconButton):
super().__init__(icon, margin=5) super().__init__(icon, margin=5)
self.set_label(label) self.set_label(label)
self.set_focus_on_click(False)
class LargeIconTextButton(IconButton): class LargeIconTextButton(IconButton):
def __init__(self, icon: str, label: str) -> None: def __init__(self, icon: str, label: str) -> None:
@@ -56,12 +58,14 @@ class LargeIconTextButton(IconButton):
self.set_image(LargeIcon(icon)) self.set_image(LargeIcon(icon))
class ClipboardButton(IconButton): class ClipboardButton(IconTextButton):
def __init__(self, controller: "Controller", data: str) -> None: def __init__(self, controller: "Controller", data: str) -> None:
super().__init__(CLIPBOARD) super().__init__(CLIPBOARD, atomic_buttons.copy)
self.controller = controller self.controller = controller
self.connect("clicked", self._on_button_clicked, data) self.connect("clicked", self._on_button_clicked, data)
self.set_tooltip_text("Copy IP to clipboard")
def _on_button_clicked(self, button: Self, data: str) -> None: def _on_button_clicked(self, button: Self, data: str) -> None:
self.controller.copy_clipboard(data) self.controller.copy_clipboard(data)
@@ -74,19 +78,21 @@ class WebButton(IconTextButton):
class RefreshButton(IconTextButton): class RefreshButton(IconTextButton):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(icon=REFRESH_ICON, label=refresh) super().__init__(icon=REFRESH_ICON, label=atomic_buttons.refresh)
self.set_margin_top(10) self.set_margin_top(10)
self.set_margin_start(80) self.set_margin_start(80)
self.set_margin_end(80) self.set_margin_end(80)
class KeysButton(IconTextButton): class KeysButton(IconTextButton):
def __init__(self, label: str) -> None: def __init__(self) -> None:
super().__init__(icon=INPUT_KEYBOARD, label=label) super().__init__(icon=INPUT_KEYBOARD, label=atomic_buttons.keys)
self.set_margin_top(10) self.set_margin_top(10)
self.set_margin_start(80) self.set_margin_start(80)
self.set_margin_end(80) self.set_margin_end(80)
self.set_tooltip_text(atomic_buttons.keys_tooltip)
class SteamConnectButton(LargeIconTextButton): class SteamConnectButton(LargeIconTextButton):
def __init__(self) -> None: def __init__(self) -> None:
+64 -70
View File
@@ -1,7 +1,6 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from dzgui.api.servers import validate_ip 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.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 AddButton, ClipboardButton, SteamConnectButton from dzgui.views.components.buttons import AddButton, ClipboardButton, SteamConnectButton
@@ -14,6 +13,9 @@ 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
COLS = 1
ROWS = 1
class LanPanel(Gtk.Frame): class LanPanel(Gtk.Frame):
def __init__(self, controller: "Controller") -> None: def __init__(self, controller: "Controller") -> None:
super().__init__(margin_top=10, margin_bottom=5) super().__init__(margin_top=10, margin_bottom=5)
@@ -24,26 +26,24 @@ class LanPanel(Gtk.Frame):
self.set_label_widget(label) self.set_label_widget(label)
radio1 = Gtk.RadioButton.new_with_label(None, "Default port (27016)") radio1 = Gtk.RadioButton.new_with_label(None, "Default port (27016)")
radio2 = Gtk.RadioButton.new_from_widget(radio1) radio2 = Gtk.RadioButton.new_with_label_from_widget(radio1, "Custom port")
# TODO: new_with_label_from_widget
radio2.set_label("Custom port")
self.entry = Gtk.Entry(placeholder_text="Query port", sensitive=False) self.entry = Gtk.Entry(placeholder_text="Enter the query port (1-65535)", sensitive=False, hexpand=True)
self.button = Gtk.Button(label="Scan") self.button = Gtk.Button(label="Scan")
radio1.connect("toggled", self._on_radio_toggled) radio1.connect("toggled", self._on_radio_toggled)
# TODO: use grid, more column spacing self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5)
hbox = Gtk.Box(margin=10, spacing=5, halign=Gtk.Align.START) self.grid.attach(radio1, 0, 0, COLS, ROWS)
hbox.pack_start(radio1, NO_EXPAND, NO_FILL, NO_PADDING) # TODO: expression to attach in a line
hbox.pack_start(radio2, NO_EXPAND, NO_FILL, NO_PADDING) self.grid.attach_next_to(radio2, radio1, Gtk.PositionType.RIGHT, COLS, ROWS)
hbox.pack_start(self.entry, NO_EXPAND, NO_FILL, NO_PADDING) self.grid.attach_next_to(self.entry, radio2, Gtk.PositionType.RIGHT, COLS, ROWS)
hbox.pack_start(self.button, NO_EXPAND, NO_FILL, NO_PADDING) self.grid.attach_next_to(self.button, self.entry, Gtk.PositionType.RIGHT, COLS, ROWS)
self.add(hbox) self.add(self.grid)
def _on_radio_toggled(self, button: Gtk.RadioButton) -> None: def _on_radio_toggled(self, button: Gtk.RadioButton) -> None:
for el in self.entry, self.button: self.entry.set_sensitive(not button.get_active())
el.set_sensitive(not button.get_active()) # TODO: validate custom port entry
class AddPanel(Gtk.Frame): class AddPanel(Gtk.Frame):
def __init__(self, controller: "Controller") -> None: def __init__(self, controller: "Controller") -> None:
@@ -52,14 +52,10 @@ class AddPanel(Gtk.Frame):
label = BoldLabel("Connect") label = BoldLabel("Connect")
self.set_label_widget(label) self.set_label_widget(label)
class ConnectPanel(Gtk.Box): self.add_server = AddButton()
def __init__(self, controller: "Controller") -> None: self.conn_server = SteamConnectButton()
super().__init__(orientation=Gtk.Orientation.VERTICAL) self.conn_server.set_sensitive(False)
self.add_server.set_sensitive(False)
COLS = 1
ROWS = 1
self.classname = "invalid-entry"
self.controller = controller
self.entry = Gtk.Entry( self.entry = Gtk.Entry(
placeholder_text=connect_panel.placeholder, placeholder_text=connect_panel.placeholder,
@@ -69,24 +65,6 @@ class ConnectPanel(Gtk.Box):
self.entry.connect("key-press-event", self._on_entry_keypress) self.entry.connect("key-press-event", self._on_entry_keypress)
self.entry.connect("changed", self._on_text_changed) self.entry.connect("changed", self._on_text_changed)
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()
self.fav_edit = Gtk.Button(label=connect_panel.edit)
self.add_server = AddButton()
self.conn_server = SteamConnectButton()
self.conn_server.set_sensitive(False)
self.add_server.set_sensitive(False)
if server_name is None:
self.fav_button.set_sensitive(False)
self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5) self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5)
self.grid.attach(self.entry, 0, 0, COLS, ROWS) self.grid.attach(self.entry, 0, 0, COLS, ROWS)
@@ -98,32 +76,7 @@ class ConnectPanel(Gtk.Box):
for el, sibling, pos, h_span, v_span in els: 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(el, sibling, pos, h_span, v_span)
self.lan = LanPanel(self.controller) self.add(self.grid)
self.add(self.lan)
label = BoldLabel("Favorite server")
frame = Gtk.Frame(margin_top=10, margin_bottom=5, label_widget=label)
b = ClipboardButton(self.controller, self.get_fav_ip())
b.set_focus_on_click(False)
b.set_tooltip_text("Copy IP to clipboard")
# 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)
self.add(frame)
label = BoldLabel("Connect")
frame = Gtk.Frame(margin_top=10, margin_bottom=5, label_widget=label)
frame.add(self.grid)
self.add(frame)
#self.lan.set_visible(False)
def get_fav_ip(self) -> str:
return self.fav_ip
def mark_valid(self) -> None: def mark_valid(self) -> None:
self.conn_server.set_sensitive(True) self.conn_server.set_sensitive(True)
@@ -152,12 +105,53 @@ class ConnectPanel(Gtk.Box):
else: else:
self.mark_invalid() 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: def _on_entry_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> None:
if event.keyval == Gdk.KEY_Escape: if event.keyval == Gdk.KEY_Escape:
# NOTE: unselect text # NOTE: unselect text
entry.select_region(0, 0) entry.select_region(0, 0)
self.controller.grab_active_treeview() self.controller.grab_active_treeview()
class ConnectPanel(Gtk.Box):
def __init__(self, controller: "Controller") -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.classname = "invalid-entry"
self.controller = controller
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()
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:
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)
+7 -7
View File
@@ -10,7 +10,7 @@ from dzgui.views.components.filter_panel import FilterPanel
from dzgui.views.components.mod_panel import ModSelectionPanel from dzgui.views.components.mod_panel import ModSelectionPanel
from dzgui.views.components.buttons import RefreshButton, KeysButton 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, EXPAND, FILL, INPUT_KEYBOARD, NO_PADDING
from dzgui.util.strings import refresh_tooltip, refresh, keys_button, keys_tooltip from dzgui.util.strings import atomic_buttons
if TYPE_CHECKING: if TYPE_CHECKING:
from GLib import SOURCE_REMOVE from GLib import SOURCE_REMOVE
@@ -31,14 +31,13 @@ class RightPanel(Gtk.Box):
self.refresh_button = RefreshButton() self.refresh_button = RefreshButton()
# TODO: consolidate into class # TODO: consolidate into class
self.refresh_button.set_tooltip_text(refresh_tooltip) #self.refresh_button.set_tooltip_text(refresh_tooltip)
self.refresh_button.set_focus_on_click(False) self.refresh_button.set_focus_on_click(False)
self.refresh_button.connect("clicked", self._on_refresh_clicked) self.refresh_button.connect("clicked", self._on_refresh_clicked)
self.keys = KeysButton(keys_button) self.keys = KeysButton()
self.keys.set_focus_on_click(False)
self.keys.set_tooltip_text(keys_tooltip)
self.keys.connect("clicked", self._on_keys_clicked) self.keys.connect("clicked", self._on_keys_clicked)
#self.keys.set_focus_on_click(False)
for el in self.button_vbox, self.keys, self.filters_vbox, self.refresh_button: for el in self.button_vbox, self.keys, self.filters_vbox, self.refresh_button:
self.pack_start(el, NO_EXPAND, FILL, NO_PADDING) self.pack_start(el, NO_EXPAND, FILL, NO_PADDING)
@@ -61,13 +60,14 @@ class RightPanel(Gtk.Box):
self.refresh_button.set_label(refresh) self.refresh_button.set_label(refresh)
self.refresh_button.set_sensitive(True) self.refresh_button.set_sensitive(True)
return GLib.SOURCE_REMOVE return GLib.SOURCE_REMOVE
self.refresh_button.set_label(f"{refresh} ({str(self.time)})") # TODO: abstract this formatting logic
self.refresh_button.set_label(f"{atomic_buttons.refresh} ({str(self.time)})")
return True return True
def _on_refresh_clicked(self, button: RefreshButton) -> None: def _on_refresh_clicked(self, button: RefreshButton) -> None:
# TODO: could be internal to refresh button class # TODO: could be internal to refresh button class
self.refresh_button.set_sensitive(False) self.refresh_button.set_sensitive(False)
self.refresh_button.set_label(f"{refresh} ({str(self.time)})") self.refresh_button.set_label(f"{atomic_buttons.refresh} ({str(self.time)})")
GLib.timeout_add_seconds(1, self.decrement) GLib.timeout_add_seconds(1, self.decrement)
self.controller.refresh_tree() self.controller.refresh_tree()
+13 -12
View File
@@ -1,22 +1,21 @@
import textwrap import textwrap
import sys
from typing import Self
from dzgui.util.strings import dialog_error, dialog_header
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 # noqa E402
class EarlyAlertDialog(Gtk.MessageDialog): class EarlyAlertDialog(Gtk.MessageDialog):
def __init__(self, string): def __init__(self, string: str) -> None:
# TODO: strings
super().__init__( super().__init__(
title="DZGUI - Dialog", title=dialog_header,
flags=0, text=dialog_error,
text="ERROR",
transient_for=None, transient_for=None,
buttons=Gtk.ButtonsType.OK buttons=Gtk.ButtonsType.OK
) )
ok = self.action_area.get_children()[0]
ok.set_label("OK")
ok.connect("clicked", Gtk.main_quit)
msg = textwrap.fill(string, 50) msg = textwrap.fill(string, 50)
self.format_secondary_text(msg) self.format_secondary_text(msg)
@@ -28,8 +27,10 @@ class EarlyAlertDialog(Gtk.MessageDialog):
self.set_default_size(250, 100) self.set_default_size(250, 100)
self.connect("delete-event", Gtk.main_quit) self.connect("response", self._on_response)
self.connect("destroy", Gtk.main_quit)
self.show_all() self.run()
Gtk.main() self.destroy()
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
sys.exit(1)
+149 -120
View File
@@ -4,147 +4,103 @@ from typing import Any, Literal, Self, TYPE_CHECKING
from dzgui.const.constants import NO_EXPAND, NO_FILL, EXPAND, FILL from dzgui.const.constants import NO_EXPAND, NO_FILL, EXPAND, FILL
from dzgui.const.enum import Popup, ButtonType, NotebookPage from dzgui.const.enum import Popup, ButtonType, NotebookPage
from dzgui.util import strings from dzgui.util import strings
from dzgui.views.components.buttons import ClipboardButton
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402 from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.controllers.mc import Controller from dzgui.controllers.mc import Controller
class ExceptionDialog(Gtk.MessageDialog): # TODO: reimplement as standalone dialogs
def __init__(self, controller: "Controller", trace: str): # NOTE: steam deck prints <2> if dialog title is same as window title
super().__init__( # case Popup.MODLIST:
transient_for=controller.mediator.window, # dialog_type = Gtk.MessageType.INFO
message_type=Gtk.MessageType.ERROR, # button_type = Gtk.ButtonsType.OK
buttons=Gtk.ButtonsType.OK, # header_text = strings.modlist
text="Error", # case Popup.DETAILS:
secondary_text="Something went wrong. See the detailed error below.", # dialog_type = Gtk.MessageType.INFO
title=strings.dialog_header, # button_type = Gtk.ButtonsType.OK
modal=True, # header_text = strings.server_details
) # TODO: unused
# def update_label(self, text: str) -> None:
# TODO: strings # self.format_secondary_text(text)
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): class GenericDialog(Gtk.MessageDialog):
def __init__(self, controller: "Controller", text: str, mode: Popup): def __init__(self,
match mode: controller: "Controller",
case Popup.WAIT: text: str,
dialog_type = Gtk.MessageType.INFO mtype: Gtk.MessageType,
button_type = Gtk.ButtonsType.NONE buttons: Gtk.ButtonsType,
header_text = strings.wait secondary: str
case Popup.NOTIFY | Popup.RETURN | Popup.QUIT: ) -> None:
dialog_type = Gtk.MessageType.INFO super().__init__(
button_type = Gtk.ButtonsType.OK
header_text = strings.notice
case Popup.CONFIRM:
dialog_type = Gtk.MessageType.QUESTION
button_type = Gtk.ButtonsType.OK_CANCEL
header_text = strings.confirmation
case Popup.ENTRY:
dialog_type = Gtk.MessageType.QUESTION
button_type = Gtk.ButtonsType.OK_CANCEL
header_text = strings.input_required
case Popup.MODLIST:
dialog_type = Gtk.MessageType.INFO
button_type = Gtk.ButtonsType.OK
header_text = strings.modlist
case Popup.DETAILS:
dialog_type = Gtk.MessageType.INFO
button_type = Gtk.ButtonsType.OK
header_text = strings.server_details
Gtk.MessageDialog.__init__(
self,
transient_for=controller.mediator.window, transient_for=controller.mediator.window,
message_type=dialog_type, message_type=mtype,
buttons=button_type, buttons=buttons,
text=header_text, text=text,
secondary_text=textwrap.fill(text, 50), secondary_text=secondary,
# NOTE: steam deck prints <2> if dialog title is same as window title
title=strings.dialog_header, title=strings.dialog_header,
modal=True, modal=True,
) )
self.controller = controller
if mode == Popup.WAIT:
dialogBox = self.get_content_area()
spinner = Gtk.Spinner()
dialogBox.pack_end(spinner, NO_EXPAND, NO_FILL, 0)
spinner.start()
self.connect("delete-event", self._on_dialog_delete)
if mode == Popup.RETURN:
button_label = strings.main_menu
ok = self.action_area.get_children()[0]
ok.set_label(button_label)
ok.connect("clicked", self._return_to_main_menu)
self.connect("delete-event", self._return_to_main_menu)
if mode == Popup.QUIT:
button_label = strings.exit_app
ok = self.action_area.get_children()[0]
ok.set_label(button_label)
ok.connect("clicked", self._quit)
self.connect("delete-event", self._quit)
self.set_default_response(Gtk.ResponseType.OK)
self.set_size_request(500, 0) self.set_size_request(500, 0)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_default_response(Gtk.ResponseType.OK)
self.connect("response", self._on_response)
self.action_area.set_layout(Gtk.ButtonBoxStyle.CENTER) self.action_area.set_layout(Gtk.ButtonBoxStyle.CENTER)
self.action_area.set_margin_bottom(20) self.action_area.set_margin_bottom(20)
self.outer = self.get_content_area() self.outer = self.get_content_area()
self.outer.set_margin_start(30) self.outer.set_margin_start(30)
self.outer.set_margin_end(30) self.outer.set_margin_end(30)
def _quit(self, *args: Any) -> None: def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
self.controller.save_res_and_quit() self.destroy()
class ConfirmationDialog(GenericDialog):
def __init__(self, controller: "Controller", secondary: str):
super().__init__(
controller=controller,
text=strings.confirm,
mtype=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.OK_CANCEL,
secondary=secondary,
)
class NotifyDialog(GenericDialog):
def __init__(self, controller: "Controller", secondary: str):
super().__init__(
controller=controller,
text=strings.notice,
mtype=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
secondary=secondary,
)
class WaitDialog(GenericDialog):
def __init__(self, controller: "Controller", secondary: str):
super().__init__(
controller=controller,
text=strings.wait,
mtype=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.NONE,
secondary=secondary,
)
self.connect("delete-event", self._on_dialog_delete)
content = self.get_content_area()
spinner = Gtk.Spinner()
content.pack_end(spinner, NO_EXPAND, NO_FILL, 0)
spinner.start()
self.show_all()
def _on_dialog_delete( def _on_dialog_delete(
self, response_id: Gtk.ResponseType, event: Gdk.Event self, response_id: Gtk.ResponseType, event: Gdk.Event
@@ -154,8 +110,81 @@ class GenericDialog(Gtk.MessageDialog):
""" """
return True return True
def _return_to_main_menu(self, widget: Gtk.Widget) -> None:
self.controller.open_page(NotebookPage.MAIN)
def update_label(self, text: str) -> None: class QuitDialog(GenericDialog):
self.format_secondary_text(text) def __init__(self, controller: "Controller", secondary: str):
super().__init__(
controller=controller,
text=strings.wait,
mtype=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.NONE,
secondary=secondary,
)
self.controller = controller
self.add_button(strings.exit_app, Gtk.ResponseType.OK)
self.connect("response", self._on_response)
self.connect("delete-event", self._on_response)
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
self.controller.save_res_and_quit()
class ExceptionDialog(GenericDialog):
"""
Error dialog with rich traceback.
Usage:
try:
foo()
except Exception:
trace = traceback.format_exc()
dialog = ExceptionDialog(Controller, trace)
dialog.run()
"""
def __init__(self, controller: "Controller", trace: str):
super().__init__(
controller=controller,
text=strings.error_heading,
mtype=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.NONE,
secondary=strings.something_wrong,
)
# NOTE: box expands to end of content area
scrollable = Gtk.ScrolledWindow(
propagate_natural_height=True,
max_content_height=500
)
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))
box.pack_start(textview, EXPAND, FILL, 10)
scrollable.add(box)
content = self.get_content_area()
content.set_spacing(0)
# FIXME: padding around top of content area when traceback is long
content.add(scrollable)
copy_button = ClipboardButton(controller, trace)
self.add_action_widget(copy_button, Gtk.ResponseType.NONE)
self.add_button("OK", Gtk.ResponseType.OK)
action_area = self.get_action_area()
self.show_all()
self.connect("response", self._on_response)
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
match response:
case Gtk.ResponseType.OK:
self.destroy()
case Gtk.ResponseType.NONE:
return True # type: ignore
case Gtk.ResponseType.DELETE_EVENT:
self.destroy()
-1
View File
@@ -424,7 +424,6 @@ class Options(Gtk.Box):
return box return box
def populate_settings(self) -> None: def populate_settings(self) -> None:
# TODO: controller.get_config()
prefs = self.controller.get_prefs() prefs = self.controller.get_prefs()
if prefs.paths.config.is_file() is False: if prefs.paths.config.is_file() is False:
# NOTE: in case file got deleted locally # NOTE: in case file got deleted locally
-19
View File
@@ -1,19 +0,0 @@
import pytest
import time
from dzgui.util import cooldown
def test_time_under():
time_before = cooldown.get_time()
time.sleep(1)
result = cooldown.is_elapsed(time_before)
assert result is False
@pytest.mark.slow
def test_time_over():
time_before = cooldown.get_time()
time.sleep(31)
result = cooldown.is_elapsed(time_before)
assert result is True