mirror of
https://github.com/aclist/dztui.git
synced 2026-08-27 10:17:31 +02:00
feat: reimplement details dialog
This commit is contained in:
parent
ae291eff41
commit
4022b4c6b6
@ -234,6 +234,7 @@ class Ping:
|
||||
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class Details:
|
||||
name: Union[str, None]
|
||||
data: Union[list, None]
|
||||
description: str
|
||||
success: bool
|
||||
@ -272,22 +273,25 @@ def get_prereqs(ip: str, qport: int) -> Prereqs:
|
||||
return Prereqs(is_password, gameport, appid, version)
|
||||
|
||||
|
||||
def details(ip: str, qport: int) -> Details:
|
||||
def details(record: Record) -> Details:
|
||||
ip = record.ip
|
||||
qport = record.qport
|
||||
default_str = strings.none_provided
|
||||
|
||||
try:
|
||||
info = a2s.info((ip, qport))
|
||||
name = info.server_name
|
||||
except TimeoutError:
|
||||
return Details(None, default_str, False)
|
||||
return Details(None, None, default_str, False)
|
||||
try:
|
||||
rules = dayzquery.dayz_rules((ip, int(qport)))
|
||||
rules = dayzquery.dayz_rules((ip, qport))
|
||||
except TimeoutError:
|
||||
return Details(None, default_str, False)
|
||||
return Details(None, None, default_str, False)
|
||||
|
||||
try:
|
||||
keywords = info.keywords.split(",")
|
||||
except AttributeError:
|
||||
return Details(None, default_str, False)
|
||||
return Details(None, None, default_str, False)
|
||||
|
||||
battleye = strings.disabled
|
||||
if "battleye" in keywords:
|
||||
@ -362,7 +366,7 @@ def details(ip: str, qport: int) -> Details:
|
||||
["Version", version],
|
||||
]
|
||||
|
||||
return Details(rows, description, True)
|
||||
return Details(name, rows, description, True)
|
||||
|
||||
|
||||
def ping(iteration: int, addr: list, qport: int, ping: int) -> Ping:
|
||||
|
||||
@ -449,6 +449,7 @@ class Controller(GObject.GObject):
|
||||
model[path][4] = True
|
||||
self.emitter.emit("mods_highlighted")
|
||||
|
||||
# TODO: delegate to mod manager
|
||||
@call_on_thread(strings.dialog.working)
|
||||
def highlight_stale(self) -> None:
|
||||
# TODO: set progress bar for number of mods
|
||||
@ -609,4 +610,8 @@ class Controller(GObject.GObject):
|
||||
ConnectionManager(self).connect_by_ip(addr)
|
||||
|
||||
def connect_by_record(self, record: "Record") -> None:
|
||||
# NOTE: calls on thread
|
||||
ConnectionManager(self).connect_by_record(record)
|
||||
|
||||
def get_details(self, record: "Record") -> None:
|
||||
ConnectionManager(self).query_details(record)
|
||||
|
||||
@ -2,9 +2,9 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import dzgui.api.servers as Servers
|
||||
from dzgui.managers.thread_man import call_on_thread, StoredFunc, ThreadingManager
|
||||
from dzgui.util.strings import api_warn_msg, dialog
|
||||
from dzgui.util.strings import api_warn_msg, dialog, server_timeout
|
||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||
|
||||
from dzgui.views.dialogs.server_details import ServerDetailsDialog
|
||||
|
||||
import gi
|
||||
|
||||
@ -25,28 +25,57 @@ class ConnectionManager:
|
||||
def connect_by_id(self, addr: str, key: str) -> None:
|
||||
res = Servers.query_by_id(addr, key)
|
||||
if res is None:
|
||||
self.thread_man.set_cleanup_func(StoredFunc(self._connection_failure))
|
||||
self.thread_man.set_cleanup_func(
|
||||
StoredFunc(self._server_timeout), destroy_first=True
|
||||
)
|
||||
|
||||
print("DEBUG")
|
||||
print(res)
|
||||
|
||||
@call_on_thread(dialog.querying)
|
||||
def connect_by_ip(self, addr: str) -> None:
|
||||
res = Servers.query_by_ip(addr)
|
||||
if res is None:
|
||||
self.thread_man.set_cleanup_func(StoredFunc(self._connection_failure))
|
||||
self.thread_man.set_cleanup_func(
|
||||
StoredFunc(self._server_timeout), destroy_first=True
|
||||
)
|
||||
|
||||
print("DEBUG")
|
||||
print(res)
|
||||
|
||||
@call_on_thread(dialog.querying)
|
||||
def connect_by_record(self, record: Servers.Record) -> None:
|
||||
res = Servers.query_by_record(record)
|
||||
if res is None:
|
||||
self.thread_man.set_cleanup_func(StoredFunc(self._connection_failure))
|
||||
self.thread_man.set_cleanup_func(
|
||||
StoredFunc(self._server_timeout), destroy_first=True
|
||||
)
|
||||
|
||||
# TODO: add to history if successful
|
||||
print("DEBUG")
|
||||
print(res)
|
||||
|
||||
def _connection_failure(self) -> None:
|
||||
# TODO: more explicit warning message, not necessarily API failure?
|
||||
dialog = ExceptionDialog(self.controller, api_warn_msg)
|
||||
@call_on_thread(dialog.querying)
|
||||
def query_details(self, record: Servers.Record) -> None:
|
||||
details = Servers.details(record)
|
||||
if details.success is False:
|
||||
self.thread_man.set_cleanup_func(
|
||||
StoredFunc(self._server_timeout), destroy_first=True
|
||||
)
|
||||
return
|
||||
self.thread_man.set_cleanup_func(
|
||||
StoredFunc(self._present_details_dialog, details), destroy_first=True
|
||||
)
|
||||
|
||||
def _present_details_dialog(self, details: Servers.Details) -> None:
|
||||
dialog = ServerDetailsDialog(self.controller, details)
|
||||
dialog.run()
|
||||
|
||||
def _server_timeout(self) -> None:
|
||||
dialog = ExceptionDialog(self.controller, server_timeout)
|
||||
dialog.run()
|
||||
|
||||
# def _connection_failure(self) -> None:
|
||||
# # FIXME: returns api warning, but this is more likely a localized server issue
|
||||
# dialog = ExceptionDialog(self.controller, api_warn_msg)
|
||||
# dialog.run()
|
||||
|
||||
@ -3,7 +3,6 @@ import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.const.enum import ContextMenu, Preferences
|
||||
from dzgui.managers.thread_man import ThreadingManager
|
||||
from dzgui.util import strings
|
||||
from dzgui.util.clip import copy_clipboard
|
||||
from dzgui.util.open_links import open_workshop_page
|
||||
@ -29,7 +28,6 @@ class ContextMenuManager:
|
||||
) -> None:
|
||||
self.controller = controller
|
||||
self.treeview = treeview
|
||||
self.thread_man = ThreadingManager(parent=controller)
|
||||
|
||||
def process(self, action: ContextMenu) -> None:
|
||||
match action:
|
||||
@ -80,8 +78,8 @@ class ContextMenuManager:
|
||||
# reverse of ADD_SERVER
|
||||
pass
|
||||
case ContextMenu.SHOW_DETAILS:
|
||||
print("DEBUG: unimplemented")
|
||||
pass
|
||||
record = self.treeview.get_record()
|
||||
self.controller.get_details(record)
|
||||
case ContextMenu.SHOW_MODS:
|
||||
print("DEBUG: unimplemented")
|
||||
pass
|
||||
|
||||
@ -50,7 +50,9 @@ class ThreadingManager:
|
||||
self.parent = parent
|
||||
self.jobs = 1
|
||||
self.cleanup_func = None
|
||||
self.alternate_statusbar = None
|
||||
self.destroy_first = False
|
||||
|
||||
# self.alternate_statusbar = None
|
||||
|
||||
def call_on_thread(self, dialog_str: str, func: StoredFunc) -> None:
|
||||
def callback() -> None:
|
||||
@ -72,30 +74,39 @@ class ThreadingManager:
|
||||
GLib.idle_add(lambda: self.wait_dialog.increment(text))
|
||||
|
||||
# TODO: this should not be delegated here, set in cleanup func
|
||||
def set_alternate_statusbar(self, msg: str) -> None:
|
||||
self.alternate_statusbar = msg
|
||||
# def set_alternate_statusbar(self, msg: str) -> None:
|
||||
# self.alternate_statusbar = msg
|
||||
|
||||
def get_alternate_statusbar(self) -> Optional[str]:
|
||||
return self.alternate_statusbar
|
||||
# def get_alternate_statusbar(self) -> Optional[str]:
|
||||
# return self.alternate_statusbar
|
||||
|
||||
def set_cleanup_func(self, func: StoredFunc) -> None:
|
||||
def set_cleanup_func(self, func: StoredFunc, destroy_first: bool = False) -> None:
|
||||
if type(func) not in (StoredFunc, type(None)):
|
||||
msg = f"Callback function '{func}' is not of type StoredFunc or None"
|
||||
logger.critical(msg)
|
||||
raise TypeError(msg)
|
||||
self.destroy_first = destroy_first
|
||||
self.cleanup_func = func
|
||||
|
||||
def get_cleanup_func(self) -> StoredFunc:
|
||||
return self.cleanup_func
|
||||
|
||||
def _d(self) -> None:
|
||||
self.wait_dialog.hide()
|
||||
return False
|
||||
# def _d(self) -> None:
|
||||
# self.wait_dialog.hide()
|
||||
# return False
|
||||
|
||||
def _destroy_on_idle(self) -> GLib.SOURCE_REMOVE:
|
||||
if self.destroy_first:
|
||||
self.wait_dialog.destroy()
|
||||
|
||||
func = self.get_cleanup_func()
|
||||
if func is not None:
|
||||
func.call()
|
||||
self.set_cleanup_func(None)
|
||||
self.wait_dialog.destroy()
|
||||
if not self.destroy_first:
|
||||
self.wait_dialog.destroy()
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
def get_wait_dialog(self) -> WaitDialog:
|
||||
return self.wait_dialog
|
||||
|
||||
@ -320,7 +320,7 @@ class ServerModelManager:
|
||||
# TODO: disable map, keyword, and filter widgets if model is None
|
||||
# -> signal driven (servers_empty, servers_failed_to_load)
|
||||
|
||||
# NOTE: used by refresh action
|
||||
# NOTE: used by refresh button action
|
||||
if not self.preserve_on_fail:
|
||||
self.tv.set_model(None)
|
||||
filter_man = self.tv.get_filter_man()
|
||||
@ -330,6 +330,8 @@ class ServerModelManager:
|
||||
# TODO: distinguish signals, e.g. "servers_failed_to_load", "servers_loaded_empty"
|
||||
# customize statusbar and dialog accordingly
|
||||
self.emitter.emit("servers_loaded", self.enum)
|
||||
# TODO: destroy wait dialog first
|
||||
# see threadman.set_cleanup_func(_, destroy_first=True)
|
||||
if show_dialog:
|
||||
dialog = ExceptionDialog(self.controller, api_warn_msg)
|
||||
dialog.run()
|
||||
|
||||
@ -102,7 +102,7 @@ build_corrupted = (
|
||||
api_warn_msg = """No servers returned. Please wait and try again.
|
||||
If this issue persists, your API key may be defunct or your network is blocking requests.
|
||||
"""
|
||||
server_timeout = "Timed out when querying server, check IP or try again later"
|
||||
server_timeout = "Timed out when querying server, check IP or try again later."
|
||||
server_error = (
|
||||
"Error while contacting server, possibly timed out. Please wait and try again."
|
||||
)
|
||||
|
||||
@ -19,13 +19,6 @@ if TYPE_CHECKING:
|
||||
# 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
|
||||
# TODO: unused
|
||||
# def update_label(self, text: str) -> None:
|
||||
# self.format_secondary_text(text)
|
||||
|
||||
|
||||
class GenericDialog(Gtk.MessageDialog):
|
||||
|
||||
@ -1,20 +1,36 @@
|
||||
import gi
|
||||
|
||||
import dzgui.api.servers as Servers
|
||||
from dzgui.const.constants import EXPAND, FILL, NO_PADDING
|
||||
from dzgui.const.enum import Popup
|
||||
from dzgui.util import css
|
||||
from dzgui.util import strings
|
||||
from dzgui.util.format import format_hyperlinks
|
||||
from dzgui.views.dialogs.generic import GenericDialog
|
||||
|
||||
from typing import Self, TYPE_CHECKING
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
||||
|
||||
from dzgui.const.constants import EXPAND, FILL, NO_PADDING
|
||||
from dzgui.const.enum import Popup
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.api.servers import Details
|
||||
|
||||
|
||||
class ServerDetailsDialog(GenericDialog):
|
||||
def __init__(self, server_name: str, ip: str, qport: int):
|
||||
super().__init__(server_name, Popup.DETAILS)
|
||||
def __init__(self, controller, details: "Details"):
|
||||
super().__init__(
|
||||
controller=controller,
|
||||
text=details.name,
|
||||
buttons=Gtk.ButtonsType.OK,
|
||||
mtype=Gtk.MessageType.INFO,
|
||||
secondary=strings.server_details,
|
||||
)
|
||||
|
||||
dialog_box = self.get_content_area()
|
||||
self.set_default_response(Gtk.ResponseType.OK)
|
||||
self.set_size_request(800, 700)
|
||||
|
||||
self.ip = ip.split(":")[0]
|
||||
self.qport = qport
|
||||
self.store = Gtk.ListStore(str, str, Pango.Weight)
|
||||
|
||||
self.view = Gtk.TreeView(
|
||||
@ -24,13 +40,12 @@ class ServerDetailsDialog(GenericDialog):
|
||||
fixed_height_mode=True,
|
||||
)
|
||||
self.view.connect("row-activated", self._on_row_activated)
|
||||
self.view.connect("key-press-event", self._on_keypress)
|
||||
|
||||
for i, column_title in enumerate(["Item", "Details"]):
|
||||
renderer = Gtk.CellRendererText(xalign=0)
|
||||
if i == 0:
|
||||
column = Gtk.TreeViewColumn(
|
||||
column_title, renderer, text=i, weight=2
|
||||
)
|
||||
column = Gtk.TreeViewColumn(column_title, renderer, text=i, weight=2)
|
||||
else:
|
||||
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
|
||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||
@ -43,15 +58,12 @@ class ServerDetailsDialog(GenericDialog):
|
||||
scrollable_tree.add(self.view)
|
||||
scrollable_tree.set_size_request(700, 200)
|
||||
|
||||
# TODO: center header text
|
||||
scrollable_message = Gtk.ScrolledWindow()
|
||||
desc = Gtk.Label(label=strings.server_message, valign=Gtk.Align.START)
|
||||
css.add_class(desc, "details-heading")
|
||||
box = Gtk.Box(
|
||||
orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER
|
||||
)
|
||||
self.description = Gtk.Label(
|
||||
justify=Gtk.Justification.CENTER, wrap=True
|
||||
)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER)
|
||||
self.description = Gtk.Label(justify=Gtk.Justification.CENTER, wrap=True)
|
||||
sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
sep.set_margin_bottom(10)
|
||||
for el in desc, sep, self.description:
|
||||
@ -61,12 +73,24 @@ class ServerDetailsDialog(GenericDialog):
|
||||
dialog_box.pack_start(scrollable_tree, EXPAND, FILL, NO_PADDING)
|
||||
dialog_box.pack_start(scrollable_message, EXPAND, FILL, NO_PADDING)
|
||||
|
||||
self.wait_dialog = GenericDialog(strings.details, Popup.WAIT)
|
||||
self.wait_dialog.show_all()
|
||||
thread = threading.Thread(
|
||||
target=self._background, args=(self.wait_dialog, ip, qport)
|
||||
)
|
||||
thread.start()
|
||||
for row in details.data:
|
||||
self.store.append(row + [Pango.Weight.BOLD])
|
||||
self.view.set_model(self.store)
|
||||
text = details.description
|
||||
text = format_hyperlinks(text)
|
||||
self.description.set_markup(text)
|
||||
|
||||
self.connect("response", self._on_response)
|
||||
self.show_all()
|
||||
|
||||
def _on_keypress(self, view: Gtk.TreeView, event: Gdk.EventKey) -> None:
|
||||
if event.keyval == Gdk.KEY_Escape:
|
||||
self.destroy()
|
||||
return True
|
||||
return False
|
||||
|
||||
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
||||
self.destroy()
|
||||
|
||||
def _on_row_activated(
|
||||
self,
|
||||
@ -75,29 +99,3 @@ class ServerDetailsDialog(GenericDialog):
|
||||
col: Gtk.TreeViewColumn,
|
||||
) -> None:
|
||||
self.destroy()
|
||||
|
||||
def _load(self) -> None:
|
||||
if self.wait_dialog:
|
||||
self.wait_dialog.destroy()
|
||||
if self.success is False:
|
||||
AppNav.window.spawn_dialog(strings.server_error, Popup.NOTIFY)
|
||||
return
|
||||
self.show_all()
|
||||
self.run()
|
||||
self.destroy()
|
||||
|
||||
def _background(
|
||||
self, dialog: "GenericDialog", ip: str, qport: int
|
||||
) -> None:
|
||||
response = Servers.details(self.ip, self.qport)
|
||||
if response.success:
|
||||
for row in response.data:
|
||||
self.store.append(row + [Pango.Weight.BOLD])
|
||||
self.view.set_model(self.store)
|
||||
|
||||
text = response.description
|
||||
text = format_hyperlinks(text)
|
||||
self.description.set_markup(text)
|
||||
|
||||
self.success = response.success
|
||||
GLib.idle_add(self._load)
|
||||
|
||||
@ -63,7 +63,6 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
||||
tv = self.get_active_treeview()
|
||||
state = False if tv.get_model() is None else True
|
||||
tv.set_headers_visible(state)
|
||||
# tv.set_headers_clickable(state)
|
||||
tv.set_loaded(True)
|
||||
tv.grab_focus()
|
||||
|
||||
@ -151,9 +150,6 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
||||
tab, col, "_on_col_width_changed", False
|
||||
)
|
||||
|
||||
# def get_tabs(self) -> tuple:
|
||||
# return (self.browser, self.saved, self.recent, self.lan)
|
||||
|
||||
def get_browser(self) -> ServerTreeView:
|
||||
return self.browser
|
||||
|
||||
|
||||
@ -283,7 +283,8 @@ class ServerTreeView(ContextMixin, TreeView):
|
||||
def _parent_row_activated(
|
||||
self, tree: TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn
|
||||
) -> None:
|
||||
print(self.get_value_at_index(0))
|
||||
record = self.get_record()
|
||||
self.controller.connect_by_record(record)
|
||||
|
||||
def _parent_selection_changed(self, base_class: TreeView, sel: Gtk.TreeSelection):
|
||||
if self.loaded is False:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user