mirror of
https://github.com/aclist/dztui.git
synced 2026-10-30 22:52:42 +01:00
Merge pull request #30 from aclist/feat/server-notebook
feat/server notebook
This commit is contained in:
46 files changed
+1348
-929
No files matched your search
+1
-1
@@ -14,6 +14,7 @@
|
|||||||
- Open filepicker when generating system log
|
- Open filepicker when generating system log
|
||||||
- Developers page (and -d flag)
|
- Developers page (and -d flag)
|
||||||
- Redact API key in log table
|
- Redact API key in log table
|
||||||
|
- Integrate add/connect widgets into main menu
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
- Reduce padding on keys button
|
- Reduce padding on keys button
|
||||||
@@ -23,4 +24,3 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
- Setup wizard
|
- Setup wizard
|
||||||
- Integrate add/connect widgets into main menu
|
|
||||||
@@ -27,11 +27,11 @@ def get_ipdb(ips_path: Path) -> None:
|
|||||||
|
|
||||||
date = find_date(url)
|
date = find_date(url)
|
||||||
month_file = ips_path.parent / ".month"
|
month_file = ips_path.parent / ".month"
|
||||||
if month_file.exists() is True:
|
if ips_path.exists() and month_file.exists():
|
||||||
old_date = month_file.read_text().rstrip("\n")
|
old_date = month_file.read_text().rstrip("\n")
|
||||||
if old_date == date:
|
if old_date == date:
|
||||||
logger.info(f"IP DB date matches: {date}")
|
logger.info(f"IP DB date matches: {date}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: log additional output
|
# TODO: log additional output
|
||||||
logger.info(f"Fetching IPDB for {date} from {url}")
|
logger.info(f"Fetching IPDB for {date} from {url}")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from dzgui.util.ip import Coords
|
|||||||
class UserPrefs:
|
class UserPrefs:
|
||||||
is_steam_deck: bool
|
is_steam_deck: bool
|
||||||
is_game_mode: bool
|
is_game_mode: bool
|
||||||
|
is_developer: bool
|
||||||
coords: Coords | None
|
coords: Coords | None
|
||||||
version: str
|
version: str
|
||||||
allow_updates: bool
|
allow_updates: bool
|
||||||
|
|||||||
@@ -34,10 +34,12 @@ VIEW_CONCEAL = "view-conceal-symbolic"
|
|||||||
VIEW_REVEAL = "view-reveal-symbolic"
|
VIEW_REVEAL = "view-reveal-symbolic"
|
||||||
INPUT_KEYBOARD = "input-keyboard-symbolic"
|
INPUT_KEYBOARD = "input-keyboard-symbolic"
|
||||||
HELP_BUBBLE = "help-about-symbolic"
|
HELP_BUBBLE = "help-about-symbolic"
|
||||||
|
REFRESH_ICON = "view-refresh-symbolic"
|
||||||
WEB_BROWSER = "web-browser-symbolic"
|
WEB_BROWSER = "web-browser-symbolic"
|
||||||
|
|
||||||
SEPARATOR = "SEPARATOR"
|
SEPARATOR = "SEPARATOR"
|
||||||
|
|
||||||
|
NO_PADDING = 0
|
||||||
NO_EXPAND = False
|
NO_EXPAND = False
|
||||||
NO_FILL = False
|
NO_FILL = False
|
||||||
EXPAND = True
|
EXPAND = True
|
||||||
|
|||||||
+39
-128
@@ -77,18 +77,15 @@ class Preferences(EnumWithAttrs):
|
|||||||
|
|
||||||
|
|
||||||
class NotebookPage(EnumWithAttrs):
|
class NotebookPage(EnumWithAttrs):
|
||||||
CHANGELOG = {"crumbs": strings.crumbs.changelog,}
|
CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": False}
|
||||||
HISTORY = {"crumbs": strings.crumbs.history,}
|
KEYS = {"crumbs": strings.crumbs.keys, "statusbar": False}
|
||||||
KEYS = {"crumbs": strings.crumbs.keys,}
|
LOG = {"crumbs": strings.crumbs.log, "statusbar": False}
|
||||||
LAN = {"crumbs": strings.crumbs.lan,}
|
HELP = {"crumbs": strings.crumbs._help, "statusbar": True}
|
||||||
LOG = {"crumbs": strings.crumbs.log,}
|
MODS = {"crumbs": strings.crumbs.mods, "statusbar": True}
|
||||||
MAIN = {"crumbs": strings.crumbs.main,}
|
OPTIONS = {"crumbs": strings.crumbs.options, "statusbar": False}
|
||||||
MODS = {"crumbs": strings.crumbs.mods,}
|
SERVERS = {"crumbs": strings.crumbs.servers, "statusbar": True}
|
||||||
MY_SERVERS = {"crumbs": strings.crumbs.my_servers,}
|
THANKS = {"crumbs": strings.crumbs.thanks, "statusbar": False}
|
||||||
OPTIONS = {"crumbs": strings.crumbs.options,}
|
DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False}
|
||||||
SERVERS = {"crumbs": strings.crumbs.servers,}
|
|
||||||
THANKS = {"crumbs": strings.crumbs.thanks,}
|
|
||||||
DEVELOPERS = {"crumbs": strings.crumbs.developers,}
|
|
||||||
|
|
||||||
|
|
||||||
class RowType(EnumWithAttrs):
|
class RowType(EnumWithAttrs):
|
||||||
@@ -300,6 +297,7 @@ class RowType(EnumWithAttrs):
|
|||||||
# TODO: rename to ContextItem
|
# TODO: rename to ContextItem
|
||||||
class ContextMenu(EnumWithAttrs):
|
class ContextMenu(EnumWithAttrs):
|
||||||
ADD_SERVER = {"label": strings.add}
|
ADD_SERVER = {"label": strings.add}
|
||||||
|
ADD_FAV = {"label": strings.add_fav}
|
||||||
REMOVE_SERVER = {"label": strings.remove}
|
REMOVE_SERVER = {"label": strings.remove}
|
||||||
COPY_NAME = {"label": strings.copy_name}
|
COPY_NAME = {"label": strings.copy_name}
|
||||||
COPY_CLIPBOARD = {"label": strings.copy_ip}
|
COPY_CLIPBOARD = {"label": strings.copy_ip}
|
||||||
@@ -311,6 +309,7 @@ class ContextMenu(EnumWithAttrs):
|
|||||||
OPEN_WORKSHOP = {"label": strings.open_workshop}
|
OPEN_WORKSHOP = {"label": strings.open_workshop}
|
||||||
DELETE_MOD = {"label": strings.delete_mod}
|
DELETE_MOD = {"label": strings.delete_mod}
|
||||||
COPY_LOG_CLIPBOARD = {"label": strings.copy_log}
|
COPY_LOG_CLIPBOARD = {"label": strings.copy_log}
|
||||||
|
CONNECT = {"label": strings.connect}
|
||||||
|
|
||||||
|
|
||||||
class ContextMenuGroup(Enum):
|
class ContextMenuGroup(Enum):
|
||||||
@@ -324,7 +323,9 @@ class ContextMenuGroup(Enum):
|
|||||||
ContextMenu.COPY_LOG_CLIPBOARD,
|
ContextMenu.COPY_LOG_CLIPBOARD,
|
||||||
)
|
)
|
||||||
SERVER_BROWSER = (
|
SERVER_BROWSER = (
|
||||||
|
ContextMenu.CONNECT,
|
||||||
ContextMenu.ADD_SERVER,
|
ContextMenu.ADD_SERVER,
|
||||||
|
ContextMenu.ADD_FAV,
|
||||||
ContextMenu.COPY_NAME,
|
ContextMenu.COPY_NAME,
|
||||||
ContextMenu.COPY_CLIPBOARD,
|
ContextMenu.COPY_CLIPBOARD,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenu.ADD_NOTE,
|
||||||
@@ -333,6 +334,8 @@ class ContextMenuGroup(Enum):
|
|||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenu.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
SCAN_LAN = (
|
SCAN_LAN = (
|
||||||
|
ContextMenu.CONNECT,
|
||||||
|
ContextMenu.ADD_FAV,
|
||||||
ContextMenu.COPY_NAME,
|
ContextMenu.COPY_NAME,
|
||||||
ContextMenu.COPY_CLIPBOARD,
|
ContextMenu.COPY_CLIPBOARD,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenu.ADD_NOTE,
|
||||||
@@ -341,6 +344,8 @@ class ContextMenuGroup(Enum):
|
|||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenu.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
SAVED = (
|
SAVED = (
|
||||||
|
ContextMenu.CONNECT,
|
||||||
|
ContextMenu.ADD_FAV,
|
||||||
ContextMenu.REMOVE_SERVER,
|
ContextMenu.REMOVE_SERVER,
|
||||||
ContextMenu.COPY_NAME,
|
ContextMenu.COPY_NAME,
|
||||||
ContextMenu.COPY_CLIPBOARD,
|
ContextMenu.COPY_CLIPBOARD,
|
||||||
@@ -350,7 +355,9 @@ class ContextMenuGroup(Enum):
|
|||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenu.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
RECENT = (
|
RECENT = (
|
||||||
|
ContextMenu.CONNECT,
|
||||||
ContextMenu.ADD_SERVER,
|
ContextMenu.ADD_SERVER,
|
||||||
|
ContextMenu.ADD_FAV,
|
||||||
ContextMenu.REMOVE_HISTORY,
|
ContextMenu.REMOVE_HISTORY,
|
||||||
ContextMenu.COPY_NAME,
|
ContextMenu.COPY_NAME,
|
||||||
ContextMenu.COPY_CLIPBOARD,
|
ContextMenu.COPY_CLIPBOARD,
|
||||||
@@ -360,117 +367,6 @@ class ContextMenuGroup(Enum):
|
|||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenu.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MAIN_MENU_ROWS = (
|
|
||||||
RowType.SERVER_BROWSER,
|
|
||||||
RowType.SAVED_SERVERS,
|
|
||||||
RowType.RECENT_SERVERS,
|
|
||||||
RowType.SCAN_LAN,
|
|
||||||
RowType.SEPARATOR,
|
|
||||||
RowType.QUICK_CONNECT,
|
|
||||||
RowType.CONN_BY_IP,
|
|
||||||
RowType.CONN_BY_ID,
|
|
||||||
RowType.SEPARATOR,
|
|
||||||
RowType.ADD_BY_IP,
|
|
||||||
RowType.ADD_BY_ID,
|
|
||||||
RowType.CHNG_FAV,
|
|
||||||
)
|
|
||||||
HELP_MENU_ROWS = (
|
|
||||||
RowType.CHANGELOG,
|
|
||||||
RowType.SHOW_LOG,
|
|
||||||
RowType.DUMP_LOG,
|
|
||||||
RowType.SEPARATOR,
|
|
||||||
RowType.DOCS,
|
|
||||||
RowType.DOCS_FALLBACK,
|
|
||||||
RowType.BUGS,
|
|
||||||
RowType.FORUM,
|
|
||||||
RowType.SPONSOR,
|
|
||||||
RowType.SEPARATOR,
|
|
||||||
RowType.THANKS,
|
|
||||||
)
|
|
||||||
|
|
||||||
#class WindowContext(EnumWithAttrs):
|
|
||||||
# # TODO: deprecated
|
|
||||||
# @classmethod
|
|
||||||
# def row2con(cls, row: RowType) -> "WindowContext":
|
|
||||||
# m = WindowContext.MAIN_MENU
|
|
||||||
# for member in cls:
|
|
||||||
# if row in member.dict["rows"]:
|
|
||||||
# m = member
|
|
||||||
# elif row in member.dict["called_by"]:
|
|
||||||
# m = member
|
|
||||||
# else:
|
|
||||||
# continue
|
|
||||||
# return m
|
|
||||||
#
|
|
||||||
# # outer menu pages
|
|
||||||
# # TODO: some deprecated keys in here
|
|
||||||
# #MAIN_MENU = {
|
|
||||||
# # RowType.SERVER_BROWSER,
|
|
||||||
# # RowType.SAVED_SERVERS,
|
|
||||||
# # RowType.RECENT_SERVERS,
|
|
||||||
# # RowType.SCAN_LAN,
|
|
||||||
# # RowType.SEPARATOR,
|
|
||||||
# # RowType.QUICK_CONNECT,
|
|
||||||
# # RowType.CONN_BY_IP,
|
|
||||||
# # RowType.CONN_BY_ID,
|
|
||||||
# # RowType.SEPARATOR,
|
|
||||||
# # RowType.ADD_BY_IP,
|
|
||||||
# # RowType.ADD_BY_ID,
|
|
||||||
# # RowType.CHNG_FAV,
|
|
||||||
# # }
|
|
||||||
# #MODS = {
|
|
||||||
# # "label": "Mods",
|
|
||||||
# # "rows": [],
|
|
||||||
# # "called_by": [],
|
|
||||||
# #}
|
|
||||||
# HELP = {
|
|
||||||
# "label": "Help",
|
|
||||||
# "rows": [
|
|
||||||
# RowType.CHANGELOG,
|
|
||||||
# RowType.SHOW_LOG,
|
|
||||||
# RowType.DUMP_LOG,
|
|
||||||
# RowType.SEPARATOR,
|
|
||||||
# RowType.DOCS,
|
|
||||||
# RowType.DOCS_FALLBACK,
|
|
||||||
# RowType.BUGS,
|
|
||||||
# RowType.FORUM,
|
|
||||||
# RowType.SPONSOR,
|
|
||||||
# RowType.SEPARATOR,
|
|
||||||
# RowType.THANKS,
|
|
||||||
# ],
|
|
||||||
# "called_by": [],
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# ## inner server contexts
|
|
||||||
# #TABLE_API = {
|
|
||||||
# # "label": "",
|
|
||||||
# # "rows": [],
|
|
||||||
# # "called_by": [RowType.SERVER_BROWSER],
|
|
||||||
# #}
|
|
||||||
# #TABLE_SERVER = {
|
|
||||||
# # "label": "",
|
|
||||||
# # "rows": [],
|
|
||||||
# # "called_by": [
|
|
||||||
# # RowType.SAVED_SERVERS,
|
|
||||||
# # RowType.RECENT_SERVERS,
|
|
||||||
# # RowType.SCAN_LAN,
|
|
||||||
# # ],
|
|
||||||
# #}
|
|
||||||
# #TABLE_MODS = {
|
|
||||||
# # "label": "",
|
|
||||||
# # "rows": [],
|
|
||||||
# # "called_by": [
|
|
||||||
# # RowType.LIST_MODS,
|
|
||||||
# # ],
|
|
||||||
# #}
|
|
||||||
# #TABLE_LOG = {
|
|
||||||
# # "label": "",
|
|
||||||
# # "rows": [],
|
|
||||||
# # "called_by": [RowType.SHOW_LOG],
|
|
||||||
# #}
|
|
||||||
|
|
||||||
|
|
||||||
class ModButton(EnumWithAttrs):
|
class ModButton(EnumWithAttrs):
|
||||||
SELECT_ALL = {
|
SELECT_ALL = {
|
||||||
"label": strings.mod_panel.select_all,
|
"label": strings.mod_panel.select_all,
|
||||||
@@ -499,10 +395,10 @@ class ModButton(EnumWithAttrs):
|
|||||||
|
|
||||||
|
|
||||||
class ButtonType(EnumWithAttrs):
|
class ButtonType(EnumWithAttrs):
|
||||||
MAIN_MENU = {
|
SERVERS = {
|
||||||
"label": strings.buttons.main_label,
|
"label": strings.buttons.servers_label,
|
||||||
"tooltip": strings.buttons.main_tooltip,
|
"tooltip": strings.buttons.servers_tooltip,
|
||||||
"opens": NotebookPage.MAIN,
|
"opens": NotebookPage.SERVERS,
|
||||||
}
|
}
|
||||||
MODS = {
|
MODS = {
|
||||||
"label": strings.buttons.mods_label,
|
"label": strings.buttons.mods_label,
|
||||||
@@ -517,10 +413,25 @@ class ButtonType(EnumWithAttrs):
|
|||||||
HELP = {
|
HELP = {
|
||||||
"label": strings.buttons.help_label,
|
"label": strings.buttons.help_label,
|
||||||
"tooltip": strings.buttons.help_tooltip,
|
"tooltip": strings.buttons.help_tooltip,
|
||||||
"opens": NotebookPage.MAIN,
|
"opens": NotebookPage.HELP,
|
||||||
}
|
}
|
||||||
EXIT = {
|
EXIT = {
|
||||||
"label": strings.buttons.exit_label,
|
"label": strings.buttons.exit_label,
|
||||||
"tooltip": strings.buttons.exit_tooltip,
|
"tooltip": strings.buttons.exit_tooltip,
|
||||||
"opens": None,
|
"opens": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HELP_MENU_ROWS = (
|
||||||
|
RowType.CHANGELOG,
|
||||||
|
RowType.SHOW_LOG,
|
||||||
|
RowType.DUMP_LOG,
|
||||||
|
RowType.SEPARATOR,
|
||||||
|
RowType.DOCS,
|
||||||
|
RowType.DOCS_FALLBACK,
|
||||||
|
RowType.BUGS,
|
||||||
|
RowType.FORUM,
|
||||||
|
RowType.SPONSOR,
|
||||||
|
RowType.SEPARATOR,
|
||||||
|
RowType.THANKS,
|
||||||
|
)
|
||||||
+181
-48
@@ -7,6 +7,8 @@ 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
|
||||||
|
|
||||||
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 (
|
||||||
get_delimited_mods,
|
get_delimited_mods,
|
||||||
@@ -21,6 +23,7 @@ from dzgui.const.enum import (
|
|||||||
NotebookPage,
|
NotebookPage,
|
||||||
ButtonType,
|
ButtonType,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
|
RowType
|
||||||
)
|
)
|
||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
@@ -38,8 +41,8 @@ from dzgui.controllers.model import ModelManager
|
|||||||
from dzgui.util.diag import write_diagnostic
|
from dzgui.util.diag import write_diagnostic
|
||||||
from dzgui.util import cooldown, strings
|
from dzgui.util import cooldown, strings
|
||||||
from dzgui.util._json import read_json, write_json
|
from dzgui.util._json import read_json, write_json
|
||||||
from dzgui.util.open_links import open_workshop_page
|
from dzgui.util.open_links import open_workshop_page, open_user_workshop
|
||||||
from dzgui.util.format import format_mods
|
from dzgui.util.format import format_mods, format_player_count, pluralize
|
||||||
from dzgui.util.redact import redact_log
|
from dzgui.util.redact import redact_log
|
||||||
|
|
||||||
from dzgui.views.dialogs.filepicker import FilePicker
|
from dzgui.views.dialogs.filepicker import FilePicker
|
||||||
@@ -53,34 +56,66 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.views.components.buttonbox import ContextualButton
|
from dzgui.views.components.buttonbox import ContextualButton
|
||||||
from dzgui.views.base import AppNavigation
|
|
||||||
from dzgui.const.enum import ContextMenu, RowType
|
|
||||||
from dzgui.views.base import OuterWindow
|
from dzgui.views.base import OuterWindow
|
||||||
|
from dzgui.views.trees.tree_base import TreeView
|
||||||
|
from dzgui.views.trees.tree_mods import ModTreeView
|
||||||
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
from dzgui.views.trees.tree_log import LogTreeView
|
||||||
|
from dzgui.views.base import Notebook, Grid, OuterWindow
|
||||||
|
from dzgui.views.components.statusbar import Statusbar
|
||||||
|
from dzgui.views.components.right_panel import RightPanel
|
||||||
|
from dzgui.views.pages.servers import ServerNotebook
|
||||||
|
|
||||||
|
class AppNavigation:
|
||||||
|
window: "OuterWindow"
|
||||||
|
grid: "Grid"
|
||||||
|
right_panel: "RightPanel"
|
||||||
|
statusbar: "Statusbar"
|
||||||
|
notebook: "Notebook"
|
||||||
|
modtreeview: "ModTreeView"
|
||||||
|
menu: "MenuTreeView"
|
||||||
|
servers: "ServerNotebook"
|
||||||
|
browser: "ServerTreeView"
|
||||||
|
saved: "ServerTreeView"
|
||||||
|
recent: "ServerTreeView"
|
||||||
|
lan: "ServerTreeView"
|
||||||
|
modtreeview: "ModTreeView"
|
||||||
|
logtreeview: "LogTreeView"
|
||||||
|
|
||||||
|
# TODO: most server contexts can be dropped from this struct
|
||||||
|
|
||||||
class Controller:
|
class Controller:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.is_developer: bool
|
self.crumbs_cache = ""
|
||||||
self.mediator: AppNavigation
|
self.mediator = AppNavigation()
|
||||||
self.prefs: UserPrefs
|
self.prefs: UserPrefs
|
||||||
self.cooldown = 0
|
self.cooldown = 0
|
||||||
|
|
||||||
self.model_manager = ModelManager()
|
self.model_manager = ModelManager()
|
||||||
|
|
||||||
def set_help_menu_crumbs(self) -> None:
|
def register_widget(self, attr: str, widget: Gtk.Widget) -> None:
|
||||||
# TODO: going to be deprecated after server notebook is added
|
try:
|
||||||
tip = self.mediator.treeview.get_model()[0][0]
|
setattr(self.mediator, attr, widget)
|
||||||
store = self.model_manager.get_help_store()[0][0]
|
except AttributeError:
|
||||||
if tip == store:
|
logger.critical(f"{attr} is not a valid AppNavigation attribute.")
|
||||||
self.set_crumbs("Help")
|
|
||||||
|
|
||||||
def set_crumbs(self, crumbs: str) -> None:
|
def set_crumbs(self, text: str) -> None:
|
||||||
self.mediator.grid.set_breadcrumbs(crumbs)
|
self.mediator.grid.set_breadcrumbs(text)
|
||||||
|
|
||||||
def get_crumbs(self) -> str:
|
def get_crumbs(self) -> str:
|
||||||
return self.mediator.grid.get_breadcrumbs()
|
return self.mediator.grid.get_breadcrumbs()
|
||||||
|
|
||||||
def get_row_store(self) -> Gtk.ListStore:
|
def get_server_store(self) -> Gtk.ListStore:
|
||||||
return self.model_manager.get_row_store()
|
return self.model_manager.get_server_store()
|
||||||
|
|
||||||
|
def get_saved_store(self) -> Gtk.ListStore:
|
||||||
|
return self.model_manager.get_saved_store()
|
||||||
|
|
||||||
|
def get_recent_store(self) -> Gtk.ListStore:
|
||||||
|
return self.model_manager.get_recent_store()
|
||||||
|
|
||||||
|
def get_lan_store(self) -> Gtk.ListStore:
|
||||||
|
return self.model_manager.get_lan_store()
|
||||||
|
|
||||||
def get_help_store(self) -> Gtk.ListStore:
|
def get_help_store(self) -> Gtk.ListStore:
|
||||||
return self.model_manager.get_help_store()
|
return self.model_manager.get_help_store()
|
||||||
@@ -99,7 +134,7 @@ class Controller:
|
|||||||
|
|
||||||
def terminate_process(self) -> None:
|
def terminate_process(self) -> None:
|
||||||
# TODO: only used by server table multiprocessing queue
|
# TODO: only used by server table multiprocessing queue
|
||||||
self.mediator.notebook.servers.terminate_process()
|
self.mediator.notebook.servers.browser.terminate_process()
|
||||||
|
|
||||||
def get_prefs(self) -> UserPrefs:
|
def get_prefs(self) -> UserPrefs:
|
||||||
return self.prefs
|
return self.prefs
|
||||||
@@ -120,12 +155,6 @@ class Controller:
|
|||||||
def append_map(self, map_row: list) -> None:
|
def append_map(self, map_row: list) -> None:
|
||||||
self.model_manager.append_map(map_row)
|
self.model_manager.append_map(map_row)
|
||||||
|
|
||||||
def set_mediator(self, mediator: "AppNavigation") -> None:
|
|
||||||
self.mediator = mediator
|
|
||||||
|
|
||||||
def get_mediator(self) -> "AppNavigation":
|
|
||||||
return self.mediator
|
|
||||||
|
|
||||||
def unblock_signals(self) -> None:
|
def unblock_signals(self) -> None:
|
||||||
self.block_signals(False)
|
self.block_signals(False)
|
||||||
|
|
||||||
@@ -137,12 +166,12 @@ class Controller:
|
|||||||
state,
|
state,
|
||||||
)
|
)
|
||||||
self.suppress_signal(
|
self.suppress_signal(
|
||||||
self.mediator.treeview,
|
self.mediator.menu,
|
||||||
self.mediator.treeview.selected_row,
|
self.mediator.menu.selected_row,
|
||||||
"_on_tree_selection_changed",
|
"_on_tree_selection_changed",
|
||||||
state,
|
state,
|
||||||
)
|
)
|
||||||
self.suppress_signal(self.mediator.treeview, self.mediator.treeview, "_on_keypress", state)
|
self.suppress_signal(self.mediator.menu, self.mediator.menu, "_on_keypress", state)
|
||||||
for check in self.mediator.grid.right_panel.filters_vbox.checks:
|
for check in self.mediator.grid.right_panel.filters_vbox.checks:
|
||||||
self.suppress_signal(
|
self.suppress_signal(
|
||||||
self.mediator.grid.right_panel.filters_vbox,
|
self.mediator.grid.right_panel.filters_vbox,
|
||||||
@@ -160,12 +189,39 @@ class Controller:
|
|||||||
widget.handler_block_by_func(func)
|
widget.handler_block_by_func(func)
|
||||||
else:
|
else:
|
||||||
widget.handler_unblock_by_func(func)
|
widget.handler_unblock_by_func(func)
|
||||||
self.mediator.treeview.sel_blocked = state
|
# TODO: deprecated?
|
||||||
|
#self.mediator.menu.sel_blocked = state
|
||||||
|
|
||||||
def toggle_debug_mode(self) -> None:
|
def toggle_debug_mode(self) -> None:
|
||||||
self.toggle_config(Preferences.DEBUG)
|
self.toggle_config(Preferences.DEBUG)
|
||||||
|
|
||||||
|
def get_active_treeview(self) -> "TreeView":
|
||||||
|
return self.mediator.notebook.servers.get_active_treeview()
|
||||||
|
|
||||||
|
def grab_active_treeview(self) -> None:
|
||||||
|
self.get_active_treeview().grab_focus()
|
||||||
|
|
||||||
def save_res_and_quit(self, *args: Any) -> None:
|
def save_res_and_quit(self, *args: Any) -> None:
|
||||||
|
treeview = self.get_active_treeview()
|
||||||
|
columns = treeview.get_columns()
|
||||||
|
|
||||||
|
columns_file = self.prefs.paths.columns
|
||||||
|
try:
|
||||||
|
data = JSON.read_json(columns_file)
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
data = {"cols": {}}
|
||||||
|
|
||||||
|
for column in columns:
|
||||||
|
title = column.get_title()
|
||||||
|
size = column.get_width()
|
||||||
|
data["cols"][title] = size
|
||||||
|
|
||||||
|
try:
|
||||||
|
JSON.write_json(data, columns_file)
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
|
||||||
if self.mediator.window.props.is_maximized:
|
if self.mediator.window.props.is_maximized:
|
||||||
Gtk.main_quit()
|
Gtk.main_quit()
|
||||||
return
|
return
|
||||||
@@ -181,13 +237,16 @@ class Controller:
|
|||||||
|
|
||||||
Gtk.main_quit()
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
def get_statusbar(self) -> str:
|
||||||
|
return self.mediator.grid.statusbar.get_text()
|
||||||
|
|
||||||
def set_statusbar(self, text: str) -> None:
|
def set_statusbar(self, text: str) -> None:
|
||||||
self.mediator.grid.statusbar.set_text(text)
|
self.mediator.grid.statusbar.set_text(text)
|
||||||
|
|
||||||
def delete_multiple_mods(self) -> None:
|
def delete_multiple_mods(self) -> None:
|
||||||
sel = self.mediator.modtreeview.get_selection()
|
sel = self.mediator.modtreeview.get_selection()
|
||||||
model, pathlist = sel.get_selected_rows()
|
model, pathlist = sel.get_selected_rows()
|
||||||
# NOTE: reverse when multiple
|
# NOTE: reverse when multiple selection
|
||||||
for path in reversed(pathlist):
|
for path in reversed(pathlist):
|
||||||
self.delete_single_mod(path)
|
self.delete_single_mod(path)
|
||||||
|
|
||||||
@@ -222,17 +281,20 @@ class Controller:
|
|||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
self.spawn_dialog(strings.something_wrong, Popup.NOTIFY)
|
self.spawn_dialog(strings.something_wrong, Popup.NOTIFY)
|
||||||
# TODO: suppress signals
|
# TODO: suppress signals
|
||||||
# then reenable (or it spawns twice)
|
# then reenable (or it spawns dialog twice)
|
||||||
self.mediator.grid.notebook.settings.populate_settings()
|
self.mediator.grid.notebook.settings.populate_settings()
|
||||||
return
|
return
|
||||||
|
|
||||||
# NOTE: disabled for now
|
# NOTE: disabled for now
|
||||||
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:
|
def start_cooldown(self) -> None:
|
||||||
self.cooldown = cooldown.get_time()
|
self.cooldown = cooldown.get_time()
|
||||||
|
|
||||||
|
def get_cooldown(self) -> None:
|
||||||
|
return self.cooldown
|
||||||
|
|
||||||
def manage_cooldown(self) -> bool:
|
def manage_cooldown(self) -> bool:
|
||||||
if cooldown.is_elapsed(self.cooldown):
|
if cooldown.is_elapsed(self.cooldown):
|
||||||
self.cooldown = cooldown.get_time()
|
self.cooldown = cooldown.get_time()
|
||||||
@@ -267,16 +329,24 @@ class Controller:
|
|||||||
def set_statusbar_by_row(self, row: "RowType") -> None:
|
def set_statusbar_by_row(self, row: "RowType") -> None:
|
||||||
self.mediator.grid.statusbar.refresh(row)
|
self.mediator.grid.statusbar.refresh(row)
|
||||||
|
|
||||||
|
def toggle_server_panels(self, state: bool) -> None:
|
||||||
|
self.mediator.grid.toggle_filter_panel(state)
|
||||||
|
self.mediator.grid.toggle_connect_panel(state)
|
||||||
|
self.mediator.grid.toggle_refresh_button(state)
|
||||||
|
|
||||||
def toggle_mod_panel(self, state: bool) -> None:
|
def toggle_mod_panel(self, state: bool) -> None:
|
||||||
self.mediator.grid.sel_panel.set_visible(state)
|
self.mediator.grid.right_panel.sel_panel.set_visible(state)
|
||||||
|
|
||||||
def show_developers_page(self) -> None:
|
def show_developers_page(self) -> None:
|
||||||
self.open_page(NotebookPage.DEVELOPERS)
|
self.open_page(NotebookPage.DEVELOPERS)
|
||||||
|
# TODO: put cursor on first row
|
||||||
|
#self.mediator.developers.focus_first_row()
|
||||||
|
|
||||||
def open_page(self, page: NotebookPage) -> None:
|
def open_page(self, page: NotebookPage) -> None:
|
||||||
self.mediator.grid.notebook.set_page_by_enum(page)
|
self.mediator.grid.notebook.set_page_by_enum(page)
|
||||||
|
|
||||||
def open_page_by_button(self, button: "ContextualButton") -> None:
|
def open_page_by_button(self, button: "ContextualButton") -> None:
|
||||||
|
# TODO: consolidate methods with set_page_by_enum
|
||||||
match button.context:
|
match button.context:
|
||||||
case ButtonType.EXIT:
|
case ButtonType.EXIT:
|
||||||
logger.info("Normal user exit")
|
logger.info("Normal user exit")
|
||||||
@@ -287,21 +357,21 @@ class Controller:
|
|||||||
case ButtonType.MODS:
|
case ButtonType.MODS:
|
||||||
self.load_mods()
|
self.load_mods()
|
||||||
case ButtonType.HELP:
|
case ButtonType.HELP:
|
||||||
help_store = self.model_manager.get_help_store()
|
self.mediator.grid.statusbar.refresh(RowType.CHANGELOG)
|
||||||
self.mediator.treeview.set_model(help_store)
|
pass
|
||||||
case ButtonType.MAIN_MENU:
|
case ButtonType.SERVERS:
|
||||||
# TODO: going to be deprecated after server notebook is added
|
self.mediator.notebook.set_page_by_enum(button.opens)
|
||||||
row_store = self.model_manager.get_row_store()
|
# TODO: use cache
|
||||||
self.mediator.treeview.set_model(row_store)
|
self.update_server_status()
|
||||||
|
return
|
||||||
|
|
||||||
self.mediator.grid.notebook.set_page_by_enum(button.opens)
|
self.mediator.notebook.set_page_by_enum(button.opens)
|
||||||
self.set_crumbs(button.get_label())
|
self.set_crumbs(button.get_label())
|
||||||
|
|
||||||
# TODO: deprecated?
|
def open_user_workshop(self, uid: str) -> None:
|
||||||
def open_self_workshop(self, uid: str) -> None:
|
|
||||||
# NOTE: uid may contain leading zeroes, not a real integer
|
# NOTE: uid may contain leading zeroes, not a real integer
|
||||||
client = self.query_config(Preferences.CLIENT)
|
client = self.query_config(Preferences.CLIENT)
|
||||||
open_workshop_page(uid, client)
|
open_user_workshop(uid, client)
|
||||||
|
|
||||||
def copy_log(self, paths: list[Gtk.TreePath]) -> str:
|
def copy_log(self, paths: list[Gtk.TreePath]) -> str:
|
||||||
if len(paths) < 1:
|
if len(paths) < 1:
|
||||||
@@ -364,7 +434,6 @@ class Controller:
|
|||||||
msg = format_mods(total_size, total_mods)
|
msg = format_mods(total_size, total_mods)
|
||||||
self.mediator.grid.statusbar.set_text(msg)
|
self.mediator.grid.statusbar.set_text(msg)
|
||||||
|
|
||||||
|
|
||||||
def calc_mod_size(self) -> tuple[int, int]:
|
def calc_mod_size(self) -> tuple[int, int]:
|
||||||
model = self.model_manager.get_mod_store()
|
model = self.model_manager.get_mod_store()
|
||||||
total_mods = len(model)
|
total_mods = len(model)
|
||||||
@@ -376,10 +445,12 @@ class Controller:
|
|||||||
|
|
||||||
def menu_action(self, action: ContextMenu, path: Gtk.TreePath) -> None:
|
def menu_action(self, action: ContextMenu, path: Gtk.TreePath) -> None:
|
||||||
match action:
|
match action:
|
||||||
# NOTE: manipulates server store
|
# NOTE: manipulates server stores
|
||||||
# TODO: unimplemented
|
# TODO: unimplemented
|
||||||
case ContextMenu.ADD_SERVER:
|
case ContextMenu.ADD_SERVER:
|
||||||
pass
|
pass
|
||||||
|
case ContextMenu.ADD_FAV:
|
||||||
|
pass
|
||||||
case ContextMenu.ADD_NOTE:
|
case ContextMenu.ADD_NOTE:
|
||||||
pass
|
pass
|
||||||
case ContextMenu.COPY_CLIPBOARD:
|
case ContextMenu.COPY_CLIPBOARD:
|
||||||
@@ -539,8 +610,70 @@ class Controller:
|
|||||||
logger.info(f"Using default window size {w},{h}")
|
logger.info(f"Using default window size {w},{h}")
|
||||||
window.set_default_size(w, h)
|
window.set_default_size(w, h)
|
||||||
|
|
||||||
def set_developer_mode(self, mode: bool) -> None:
|
def update_server_status(self) -> None:
|
||||||
self.is_developer = mode
|
treeview = self.mediator.notebook.servers.get_active_treeview()
|
||||||
|
model = treeview.get_model()
|
||||||
|
status = format_player_count(model)
|
||||||
|
self.mediator.statusbar.set_text(status)
|
||||||
|
|
||||||
def get_developer_mode(self) -> bool:
|
def propagate_column_width(self, col: Gtk.TreeViewColumn) -> None:
|
||||||
return self.is_developer
|
GLib.idle_add(self.mediator.servers.update_tab_widths, col)
|
||||||
|
|
||||||
|
def set_crumbs_cache(self, text: str) -> None:
|
||||||
|
self.crumbs_cache = text
|
||||||
|
|
||||||
|
def get_crumbs_cache(self) -> str:
|
||||||
|
return self.crumbs_cache
|
||||||
|
|
||||||
|
def refresh_tree(self) -> None:
|
||||||
|
treeview = self.mediator.notebook.servers.get_active_treeview()
|
||||||
|
treeview.set_loaded(False)
|
||||||
|
self.populate_model()
|
||||||
|
|
||||||
|
def populate_model(self) -> None:
|
||||||
|
# TODO: always use same server model, store in servertreeview class
|
||||||
|
treeview = self.mediator.notebook.servers.get_active_treeview()
|
||||||
|
if treeview.get_loaded() is False:
|
||||||
|
new_model = self.model_manager.new_model()
|
||||||
|
# NOTE: set_query_func()
|
||||||
|
func = treeview.get_query_func()
|
||||||
|
if func is not None:
|
||||||
|
model = treeview.get_model()
|
||||||
|
model.clear()
|
||||||
|
data = func()
|
||||||
|
# TODO: threading
|
||||||
|
model.append(data)
|
||||||
|
treeview.set_loaded(True)
|
||||||
|
self.update_server_status()
|
||||||
|
treeview.grab_focus()
|
||||||
|
|
||||||
|
def focus_button_box(self) -> None:
|
||||||
|
self.mediator.right_panel.focus_button_box()
|
||||||
|
|
||||||
|
def present_servers(self) -> None:
|
||||||
|
self.grab_active_treeview()
|
||||||
|
self.update_server_status()
|
||||||
|
crumbs = self.mediator.servers.get_cached_label()
|
||||||
|
self.set_crumbs(crumbs)
|
||||||
|
|
||||||
|
def toggle_check(self, event: Gdk.EventKey) -> None:
|
||||||
|
keyname = Gdk.keyval_name(event.keyval)
|
||||||
|
if keyname.isnumeric() and int(keyname) > 0:
|
||||||
|
digit = int(keyname) - 1
|
||||||
|
self.mediator.grid.right_panel.filters_vbox.toggle_check(digit)
|
||||||
|
else:
|
||||||
|
match event.keyval:
|
||||||
|
case Gdk.KEY_0:
|
||||||
|
self.mediator.grid.right_panel.filters_vbox.toggle_check(9)
|
||||||
|
case Gdk.KEY_minus:
|
||||||
|
self.mediator.grid.right_panel.filters_vbox.toggle_check(10)
|
||||||
|
case Gdk.KEY_backslash:
|
||||||
|
self.mediator.grid.right_panel.filters_vbox.toggle_check(11)
|
||||||
|
case _:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_favorite_label(self) -> str | None:
|
||||||
|
fav = str(self.query_config(Preferences.FAV_LBL))
|
||||||
|
if len(fav) < 1:
|
||||||
|
return None
|
||||||
|
return fav
|
||||||
+23
-15
@@ -2,7 +2,7 @@ import re
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
from dzgui.const.enum import FilterMode, MAIN_MENU_ROWS, HELP_MENU_ROWS
|
from dzgui.const.enum import FilterMode, HELP_MENU_ROWS
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -69,16 +69,15 @@ class ModelManager:
|
|||||||
self.ping_cache: dict[str, int] = {}
|
self.ping_cache: dict[str, int] = {}
|
||||||
|
|
||||||
self.map_store = ListStore(str)
|
self.map_store = ListStore(str)
|
||||||
self.row_store = self.new_model_from_class(MenuCols)
|
|
||||||
self.help_store = self.new_model_from_class(MenuCols)
|
self.help_store = self.new_model_from_class(MenuCols)
|
||||||
|
|
||||||
self.mod_store = self.new_model_from_class(ModCols)
|
self.mod_store = self.new_model_from_class(ModCols)
|
||||||
self.log_store = self.new_model_from_class(LogCols)
|
self.log_store = self.new_model_from_class(LogCols)
|
||||||
self.modlist_store = self.new_model_from_class(ServerModCols)
|
self.modlist_store = self.new_model_from_class(ServerModCols)
|
||||||
|
|
||||||
for row in MAIN_MENU_ROWS:
|
self.server_store = self.new_model()
|
||||||
label = row.dict["label"]
|
self.saved_store = self.new_model()
|
||||||
self.row_store.append([label, row])
|
self.recent_store = self.new_model()
|
||||||
|
self.lan_store = self.new_model()
|
||||||
|
|
||||||
for row in HELP_MENU_ROWS:
|
for row in HELP_MENU_ROWS:
|
||||||
label = row.dict["label"]
|
label = row.dict["label"]
|
||||||
@@ -88,10 +87,22 @@ class ModelManager:
|
|||||||
self.filtered = None
|
self.filtered = None
|
||||||
self.success = True
|
self.success = True
|
||||||
|
|
||||||
def __new__(cls) -> Self:
|
#def __new__(cls) -> Self:
|
||||||
if not hasattr(cls, "instance"):
|
# if not hasattr(cls, "instance"):
|
||||||
cls.instance = super(ModelManager, cls).__new__(cls)
|
# cls.instance = super(ModelManager, cls).__new__(cls)
|
||||||
return cls.instance
|
# return cls.instance
|
||||||
|
|
||||||
|
def get_recent_store(self) -> ListStore:
|
||||||
|
return self.recent_store
|
||||||
|
|
||||||
|
def get_lan_store(self) -> ListStore:
|
||||||
|
return self.lan_store
|
||||||
|
|
||||||
|
def get_saved_store(self) -> ListStore:
|
||||||
|
return self.saved_store
|
||||||
|
|
||||||
|
def get_server_store(self) -> ListStore:
|
||||||
|
return self.server_store
|
||||||
|
|
||||||
def new_model_from_class(self, cls: type) -> ListStore:
|
def new_model_from_class(self, cls: type) -> ListStore:
|
||||||
store = ListStore(*[ftype for field, ftype in cls.__annotations__.items()])
|
store = ListStore(*[ftype for field, ftype in cls.__annotations__.items()])
|
||||||
@@ -100,8 +111,8 @@ class ModelManager:
|
|||||||
def get_map_store(self) -> ListStore:
|
def get_map_store(self) -> ListStore:
|
||||||
return self.map_store
|
return self.map_store
|
||||||
|
|
||||||
def get_row_store(self) -> ListStore:
|
#def get_row_store(self) -> ListStore:
|
||||||
return self.row_store
|
# return self.row_store
|
||||||
|
|
||||||
def get_help_store(self) -> ListStore:
|
def get_help_store(self) -> ListStore:
|
||||||
return self.help_store
|
return self.help_store
|
||||||
@@ -140,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:
|
||||||
@@ -153,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:
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
- Open filepicker when generating system log
|
- Open filepicker when generating system log
|
||||||
- Developers page (and -d flag)
|
- Developers page (and -d flag)
|
||||||
- Redact API key in log table
|
- Redact API key in log table
|
||||||
|
- Integrate add/connect widgets into main menu
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
- Reduce padding on keys button
|
- Reduce padding on keys button
|
||||||
@@ -23,4 +24,3 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
- Setup wizard
|
- Setup wizard
|
||||||
- Integrate add/connect widgets into main menu
|
|
||||||
@@ -50,4 +50,6 @@ def copy_ipdb(ips_path: Path) -> None:
|
|||||||
legacy = home / LEGACY_IPS_PATH
|
legacy = home / LEGACY_IPS_PATH
|
||||||
if ips_path == legacy:
|
if ips_path == legacy:
|
||||||
return
|
return
|
||||||
|
if legacy.is_file() is False:
|
||||||
|
return
|
||||||
shutil.copy(legacy, ips_path)
|
shutil.copy(legacy, ips_path)
|
||||||
+10
-4
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import requests
|
import requests
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -9,14 +10,19 @@ from dzgui.const.constants import APP_NAME_LOWER, REQUEST_TIMEOUT
|
|||||||
from dzgui.const.endpoints import GITHUB_RELEASES, CODEBERG_RELEASES
|
from dzgui.const.endpoints import GITHUB_RELEASES, CODEBERG_RELEASES
|
||||||
from dzgui.init.prefix import is_prefix_writeable
|
from dzgui.init.prefix import is_prefix_writeable
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def get_latest_release() -> str | None:
|
def get_latest_release() -> str | None:
|
||||||
tag = None
|
tag = None
|
||||||
for url in [GITHUB_RELEASES, CODEBERG_RELEASES]:
|
for url in [GITHUB_RELEASES, CODEBERG_RELEASES]:
|
||||||
res = requests.get(url, timeout=REQUEST_TIMEOUT)
|
try:
|
||||||
if res.status_code == 200:
|
res = requests.get(url, timeout=REQUEST_TIMEOUT)
|
||||||
tag = res.json()["tag_name"]
|
if res.status_code == 200:
|
||||||
break
|
tag = res.json()["tag_name"]
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
continue
|
||||||
return tag
|
return tag
|
||||||
|
|
||||||
def allow_updates(allow: bool) -> bool:
|
def allow_updates(allow: bool) -> bool:
|
||||||
|
|||||||
+30
-14
@@ -19,7 +19,6 @@ from dzgui.init.migrate import (
|
|||||||
migrate_cols_file,
|
migrate_cols_file,
|
||||||
migrate_legacy_conf,
|
migrate_legacy_conf,
|
||||||
copy_state_files,
|
copy_state_files,
|
||||||
copy_ipdb,
|
|
||||||
)
|
)
|
||||||
from dzgui.init.prefix import get_version
|
from dzgui.init.prefix import get_version
|
||||||
from dzgui.init.prereqs import has_steam_client
|
from dzgui.init.prereqs import has_steam_client
|
||||||
@@ -43,7 +42,23 @@ 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:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# TODO: profile load time
|
# TODO: profile load time
|
||||||
|
def make_parents(path: "Path") -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def uninstall() -> None:
|
||||||
|
# TODO: uninstall data files (-u)
|
||||||
|
# -u removes state, log, freedesktop
|
||||||
|
# XDG_SHARE_HOME/dzgui
|
||||||
|
# XDG_STATE_HOME/dzgui
|
||||||
|
# XDG_DATA_HOME/dzgui
|
||||||
|
pass
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
lock = lock_acquire() # noqa
|
lock = lock_acquire() # noqa
|
||||||
|
|
||||||
@@ -51,11 +66,9 @@ def main() -> None:
|
|||||||
print(get_version())
|
print(get_version())
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# TODO: uninstall data files (-u)
|
if args.uninstall is True:
|
||||||
# -u removes state, log, freedesktop
|
uninstall()
|
||||||
# XDG_SHARE_HOME/dzgui
|
sys.exit(0)
|
||||||
# XDG_STATE_HOME/dzgui
|
|
||||||
# XDG_DATA_HOME/dzgui
|
|
||||||
|
|
||||||
version = get_version()
|
version = get_version()
|
||||||
set_locale()
|
set_locale()
|
||||||
@@ -64,28 +77,29 @@ def main() -> None:
|
|||||||
xdg_paths = get_xdg_paths()
|
xdg_paths = get_xdg_paths()
|
||||||
XDG = parse_filepaths(xdg_paths)
|
XDG = parse_filepaths(xdg_paths)
|
||||||
|
|
||||||
|
if XDG.resolution.parent.is_dir() is False:
|
||||||
|
make_parents(XDG.resolution)
|
||||||
|
# TODO: test
|
||||||
|
if XDG.debug.is_file() is False:
|
||||||
|
make_parents(XDG.debug)
|
||||||
|
|
||||||
if has_new_config(XDG.config) is False:
|
if has_new_config(XDG.config) is False:
|
||||||
migrate_legacy_conf(XDG.config)
|
migrate_legacy_conf(XDG.config)
|
||||||
migrate_cols_file(XDG.columns)
|
migrate_cols_file(XDG.columns)
|
||||||
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
||||||
copy_ipdb(XDG.ips)
|
|
||||||
|
|
||||||
_format = "%(asctime)s␞%(levelname)s␞%(filename)s::%(funcName)s::%(lineno)s␞%(message)s"
|
_format = "%(asctime)s␞%(levelname)s␞%(filename)s::%(funcName)s::%(lineno)s␞%(message)s"
|
||||||
logging.basicConfig(filename=XDG.debug, format=_format, level=logging.DEBUG)
|
logging.basicConfig(filename=XDG.debug, format=_format, level=logging.DEBUG)
|
||||||
with open(XDG.debug, "w") as f:
|
with open(XDG.debug, "w") as f:
|
||||||
f.truncate(0)
|
f.truncate(0)
|
||||||
|
|
||||||
|
|
||||||
_is_steam_deck = is_steam_deck()
|
_is_steam_deck = is_steam_deck()
|
||||||
_is_game_mode = False
|
_is_game_mode = is_game_mode() if _is_steam_deck else False
|
||||||
if _is_steam_deck:
|
|
||||||
_is_game_mode = is_game_mode()
|
|
||||||
if _is_game_mode:
|
if _is_game_mode:
|
||||||
# NOTE: this may no longer be necessary on newer versions of SteamOS
|
# NOTE: this may no longer be necessary on newer versions of SteamOS
|
||||||
del os.environ["GTK_IM_MODULE"]
|
del os.environ["GTK_IM_MODULE"]
|
||||||
|
|
||||||
# TODO: test spamming timeout
|
# TODO: test spamming timeout
|
||||||
allow = False
|
|
||||||
allow = allow_updates(ALLOW_UPDATES)
|
allow = allow_updates(ALLOW_UPDATES)
|
||||||
if allow is True:
|
if allow is True:
|
||||||
check_updates(version)
|
check_updates(version)
|
||||||
@@ -93,6 +107,7 @@ def main() -> None:
|
|||||||
if _is_steam_deck is False:
|
if _is_steam_deck is False:
|
||||||
# TODO: sudo escalation dialog
|
# TODO: sudo escalation dialog
|
||||||
count = get_map_count()
|
count = get_map_count()
|
||||||
|
# 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)
|
sys.exit(1)
|
||||||
@@ -104,17 +119,18 @@ def main() -> None:
|
|||||||
rebuild_symlinks(XDG.config)
|
rebuild_symlinks(XDG.config)
|
||||||
remove_stale_signatures(XDG.config, XDG.version)
|
remove_stale_signatures(XDG.config, XDG.version)
|
||||||
|
|
||||||
# TODO: handle IP DB failure
|
# TODO: handle IP DB failure and use coords fallback
|
||||||
get_ipdb(XDG.ips)
|
get_ipdb(XDG.ips)
|
||||||
local_coords = get_local_coords(XDG.ips)
|
local_coords = get_local_coords(XDG.ips)
|
||||||
|
|
||||||
prefs = UserPrefs(
|
prefs = UserPrefs(
|
||||||
_is_steam_deck,
|
_is_steam_deck,
|
||||||
_is_game_mode,
|
_is_game_mode,
|
||||||
|
args.developers,
|
||||||
local_coords,
|
local_coords,
|
||||||
version,
|
version,
|
||||||
allow,
|
allow,
|
||||||
XDG
|
XDG
|
||||||
)
|
)
|
||||||
print("All OK. Loading UI...")
|
print("All OK. Loading UI...")
|
||||||
App(prefs, args.developers)
|
App(prefs)
|
||||||
@@ -11,9 +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)
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-2
@@ -1,7 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from dzgui.util.localize import number
|
from dzgui.util.localize import number
|
||||||
from dzgui.util.strings import no_mods
|
from dzgui.util.strings import no_mods, no_servers, distance_suffix
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
|
||||||
def format_pango(text: str) -> str:
|
def format_pango(text: str) -> str:
|
||||||
@@ -41,4 +45,27 @@ def format_mods(size: int, mods: int) -> str:
|
|||||||
return no_mods
|
return no_mods
|
||||||
l_size = number(size)
|
l_size = number(size)
|
||||||
plural = pluralize("mods", mods)
|
plural = pluralize("mods", mods)
|
||||||
return f"Found {mods:n} {plural} taking up {l_size} MiB"
|
# TODO: strings
|
||||||
|
suffix = "Ctrl-click to select multiple."
|
||||||
|
return f"Found {mods:n} {plural} taking up {l_size} MiB. {suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
def format_player_count(model: Gtk.TreeModel | None) -> str:
|
||||||
|
players = 0
|
||||||
|
hits: int
|
||||||
|
status: str
|
||||||
|
if model is None or len(model) == 0:
|
||||||
|
return no_servers
|
||||||
|
else:
|
||||||
|
hits = len(model)
|
||||||
|
for row in model:
|
||||||
|
players += row[4]
|
||||||
|
players_pretty = pluralize("players", players)
|
||||||
|
hits_pretty = pluralize("matches", hits)
|
||||||
|
suffix = distance_suffix
|
||||||
|
status = f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
|
||||||
|
return f"{status} | {suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
def embolden(text: str) -> str:
|
||||||
|
return f"<b>{text}</b>"
|
||||||
+2
-3
@@ -19,8 +19,7 @@ def is_navkey(key: int) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def is_ctrl_mask(event: Gdk.EventKey) -> bool:
|
def is_ctrl_mask(event: Gdk.EventKey) -> bool:
|
||||||
if event.keyval is Gdk.KEY_l \
|
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
||||||
and event.state is Gdk.ModifierType.CONTROL_MASK:
|
return True
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
+62
-16
@@ -53,8 +53,10 @@ copy_name = "Copy name to clipboard"
|
|||||||
copy_ip = "Copy IP to clipboard"
|
copy_ip = "Copy IP to clipboard"
|
||||||
copy_log = "Copy record(s) to clipboard"
|
copy_log = "Copy record(s) to clipboard"
|
||||||
add = "Add to my servers"
|
add = "Add to my servers"
|
||||||
|
add_fav = "Set as favorite"
|
||||||
remove = "Remove from my servers"
|
remove = "Remove from my servers"
|
||||||
remove_history = "Remove from history"
|
remove_history = "Remove from history"
|
||||||
|
connect = "Connect"
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
server_mod_cols = ["Mod", "ID", "Installed"]
|
server_mod_cols = ["Mod", "ID", "Installed"]
|
||||||
@@ -63,10 +65,10 @@ log_cols = ["Timestamp", "Flag", "Traceback", "Message"]
|
|||||||
browser_cols = [
|
browser_cols = [
|
||||||
"Name",
|
"Name",
|
||||||
"Map",
|
"Map",
|
||||||
"Perspective",
|
"View",
|
||||||
"Gametime",
|
"Gametime",
|
||||||
"Players",
|
"Players",
|
||||||
"Maximum",
|
"Max",
|
||||||
"Queue",
|
"Queue",
|
||||||
"IP",
|
"IP",
|
||||||
"Qport",
|
"Qport",
|
||||||
@@ -126,6 +128,7 @@ servers = {
|
|||||||
"Ctrl-f": "jump to keyword search field",
|
"Ctrl-f": "jump to keyword search field",
|
||||||
"Ctrl-m": "jump to maps field",
|
"Ctrl-m": "jump to maps field",
|
||||||
"Ctrl-d": "toggle dry run (debug) mode",
|
"Ctrl-d": "toggle dry run (debug) mode",
|
||||||
|
"Ctrl-i": "jump to IP insert field",
|
||||||
"ESC": "return to table",
|
"ESC": "return to table",
|
||||||
"1-9": "toggle filter 1-9 on/off",
|
"1-9": "toggle filter 1-9 on/off",
|
||||||
"0": "toggle filter 10",
|
"0": "toggle filter 10",
|
||||||
@@ -152,7 +155,7 @@ label_main_menu = "Main menu"
|
|||||||
ping_servers = "Ping servers"
|
ping_servers = "Ping servers"
|
||||||
debug_mode = "Debug mode"
|
debug_mode = "Debug mode"
|
||||||
keys_button = "Keys"
|
keys_button = "Keys"
|
||||||
keys_tooltip = "Opens the keybindings dialog"
|
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"
|
||||||
@@ -240,8 +243,8 @@ class Init:
|
|||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
class Button:
|
class Button:
|
||||||
main_label: str
|
servers_label: str
|
||||||
main_tooltip: str
|
servers_tooltip: str
|
||||||
mods_label: str
|
mods_label: str
|
||||||
mods_tooltip: str
|
mods_tooltip: str
|
||||||
options_label: str
|
options_label: str
|
||||||
@@ -254,13 +257,10 @@ class Button:
|
|||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
class Crumbs:
|
class Crumbs:
|
||||||
changelog: str
|
changelog: str
|
||||||
history: str
|
|
||||||
keys: str
|
keys: str
|
||||||
lan: str
|
|
||||||
log: str
|
log: str
|
||||||
main: str
|
_help: str
|
||||||
mods: str
|
mods: str
|
||||||
my_servers: str
|
|
||||||
options: str
|
options: str
|
||||||
servers: str
|
servers: str
|
||||||
thanks: str
|
thanks: str
|
||||||
@@ -426,8 +426,8 @@ dialog = Dialog(
|
|||||||
)
|
)
|
||||||
|
|
||||||
buttons = Button(
|
buttons = Button(
|
||||||
main_label="Main menu",
|
servers_label="Servers",
|
||||||
main_tooltip="Search for and connect to servers",
|
servers_tooltip="Search for and connect to servers",
|
||||||
mods_label="Mods",
|
mods_label="Mods",
|
||||||
mods_tooltip="Manage local mods",
|
mods_tooltip="Manage local mods",
|
||||||
options_label="Options",
|
options_label="Options",
|
||||||
@@ -440,21 +440,19 @@ buttons = Button(
|
|||||||
|
|
||||||
crumbs = Crumbs(
|
crumbs = Crumbs(
|
||||||
changelog="Help > Changelog",
|
changelog="Help > Changelog",
|
||||||
history="Main menu > Recent servers",
|
|
||||||
keys="Keybindings",
|
keys="Keybindings",
|
||||||
lan="Main menu > Local servers",
|
|
||||||
log="Help > Debug log",
|
log="Help > Debug log",
|
||||||
main="Main menu",
|
_help="Help",
|
||||||
mods="Mods",
|
mods="Mods",
|
||||||
my_servers="Main menu > Saved servers",
|
|
||||||
options="Options",
|
options="Options",
|
||||||
servers="Main menu > Server browser",
|
servers="Servers > Server browser",
|
||||||
thanks="Help > Special thanks",
|
thanks="Help > Special thanks",
|
||||||
developers="Options > Developers",
|
developers="Options > Developers",
|
||||||
)
|
)
|
||||||
|
|
||||||
checkmark = "✓"
|
checkmark = "✓"
|
||||||
no_mods = "No local mods found."
|
no_mods = "No local mods found."
|
||||||
|
no_servers = "No server metadata to list."
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
class Flags:
|
class Flags:
|
||||||
@@ -496,3 +494,51 @@ developers = DevelopersPage(
|
|||||||
prefs_label="Boot preferences",
|
prefs_label="Boot preferences",
|
||||||
columns=["Key", "Value"]
|
columns=["Key", "Value"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@dataclass(slots=True, frozen=True)
|
||||||
|
class ServerLabels:
|
||||||
|
browser: str
|
||||||
|
saved: str
|
||||||
|
recent: str
|
||||||
|
lan: str
|
||||||
|
|
||||||
|
server_labels = ServerLabels(
|
||||||
|
browser="Server Browser",
|
||||||
|
saved="Saved Servers",
|
||||||
|
recent="Recent",
|
||||||
|
lan="LAN",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass(slots=True, frozen=True)
|
||||||
|
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"
|
||||||
|
"- Battlemetrics: numeric server ID\ne.g. 123456"
|
||||||
|
),
|
||||||
|
add_tooltip="Add to my servers",
|
||||||
|
connect_tooltip="Connect to this server",
|
||||||
|
no_fav="None set. Right click a server and select 'Set favorite' to set.",
|
||||||
|
)
|
||||||
|
|
||||||
|
distance_suffix = "Distance: calculating..."
|
||||||
|
|
||||||
|
refresh = "Refresh"
|
||||||
|
refresh_tooltip = "Refresh server data"
|
||||||
@@ -15,14 +15,18 @@ def rebuild_symlinks(config: Path) -> None:
|
|||||||
steam_path = Path(path)
|
steam_path = Path(path)
|
||||||
|
|
||||||
dayz_path = PeFile.get_nested_app_path(steam_path, APPID_DAYZ)
|
dayz_path = PeFile.get_nested_app_path(steam_path, APPID_DAYZ)
|
||||||
|
# NOTE: unlink stale symlinks
|
||||||
for file in dayz_path.iterdir():
|
for file in dayz_path.iterdir():
|
||||||
if file.is_symlink():
|
if file.is_symlink() and file.exists() is False:
|
||||||
file.unlink()
|
file.unlink()
|
||||||
workshop = get_local_mod_path(steam_path)
|
workshop = get_local_mod_path(steam_path)
|
||||||
for uid in get_local_mod_ids(steam_path):
|
# NOTE: create symlinks for missing mods
|
||||||
uid = str(uid)
|
for mod_id in get_local_mod_ids(steam_path):
|
||||||
|
uid = str(mod_id)
|
||||||
md5sum = _hash(str(uid))
|
md5sum = _hash(str(uid))
|
||||||
Path(dayz_path / md5sum).symlink_to(workshop / uid)
|
source = Path(dayz_path / md5sum)
|
||||||
|
if source.exists() is False:
|
||||||
|
source.symlink_to(workshop / uid)
|
||||||
|
|
||||||
|
|
||||||
def clone_symlinks(config: Path) -> None:
|
def clone_symlinks(config: Path) -> None:
|
||||||
@@ -39,6 +43,7 @@ def clone_symlinks(config: Path) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# TODO: test these two operations
|
# TODO: test these two operations
|
||||||
|
# use less time intensive logic like the above
|
||||||
for file in exp_path.iterdir():
|
for file in exp_path.iterdir():
|
||||||
if file.is_symlink():
|
if file.is_symlink():
|
||||||
file.unlink()
|
file.unlink()
|
||||||
|
|||||||
+136
-337
@@ -11,75 +11,64 @@ import threading
|
|||||||
import typing # noqa
|
import typing # noqa
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from collections.abc import Callable
|
#from concurrent.futures import wait
|
||||||
from concurrent.futures import wait
|
#from concurrent.futures import ThreadPoolExecutor
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal, Any
|
from typing import TYPE_CHECKING, Literal
|
||||||
|
|
||||||
# TODO: most likely very little of this will be retained
|
from dzgui.const.enum import NotebookPage, VAdjustment
|
||||||
from dzgui.const.enum import (
|
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING
|
||||||
Preferences,
|
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER
|
||||||
Popup,
|
|
||||||
RowType,
|
|
||||||
NotebookPage,
|
|
||||||
VAdjustment,
|
|
||||||
MAIN_MENU_ROWS,
|
|
||||||
)
|
|
||||||
from dzgui.const.constants import NO_EXPAND, NO_FILL, EXPAND, FILL
|
|
||||||
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, WINDOW_DEFAULT_X, WINDOW_DEFAULT_Y
|
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
from dzgui.util import css, dist, localize, strings, ip, deck, open_links
|
from dzgui.util import css, strings
|
||||||
from dzgui.util.format import pluralize
|
from dzgui.util.format import embolden
|
||||||
|
|
||||||
# GRID ITEMS
|
# NOTEBOOK ITEMS
|
||||||
# TODO: import notebook only and add components there?
|
# TODO: import notebook only and add components there?
|
||||||
from dzgui.views.pages.changelog import Changelog
|
from dzgui.views.pages.changelog import Changelog
|
||||||
|
from dzgui.views.components.connect_panel import ConnectPanel
|
||||||
|
from dzgui.views.pages.devs import Developers
|
||||||
|
from dzgui.views.pages.help import Help
|
||||||
from dzgui.views.pages.keys import Keybindings
|
from dzgui.views.pages.keys import Keybindings
|
||||||
from dzgui.views.pages.thanks import Thanks
|
from dzgui.views.pages.log import Log
|
||||||
|
from dzgui.views.pages.mods import Mods
|
||||||
from dzgui.views.pages.options import Options
|
from dzgui.views.pages.options import Options
|
||||||
|
from dzgui.views.pages.servers import ServerNotebook
|
||||||
|
from dzgui.views.pages.thanks import Thanks
|
||||||
|
|
||||||
|
from dzgui.views.components.statusbar import Statusbar
|
||||||
from dzgui.views.components.mod_panel import ModSelectionPanel
|
from dzgui.views.components.mod_panel import ModSelectionPanel
|
||||||
from dzgui.views.components.right_panel import RightPanel
|
from dzgui.views.components.right_panel import RightPanel
|
||||||
from dzgui.views.components.toast import Toast
|
from dzgui.views.components.toast import Toast
|
||||||
|
from dzgui.views.dialogs.generic import GenericDialog
|
||||||
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
||||||
|
|
||||||
# TREES
|
# TREES
|
||||||
from dzgui.views.trees.tree_log import LogTreeView
|
|
||||||
from dzgui.views.trees.tree_menu import MenuTreeView
|
from dzgui.views.trees.tree_menu import MenuTreeView
|
||||||
|
from dzgui.views.trees.tree_log import LogTreeView
|
||||||
from dzgui.views.trees.tree_mods import ModTreeView
|
from dzgui.views.trees.tree_mods import ModTreeView
|
||||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
|
||||||
from dzgui.views.dialogs.generic import GenericDialog
|
|
||||||
|
|
||||||
# TODO: not going to be in base anymore
|
# TODO: not going to be in base anymore
|
||||||
import dzgui.util._json as JSON # noqa
|
import dzgui.util._json as JSON # noqa
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.config.userprefs import UserPrefs
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
# https://bugzilla.gnome.org/show_bug.cgi?id=708676
|
# https://bugzilla.gnome.org/show_bug.cgi?id=708676
|
||||||
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
|
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
|
||||||
|
|
||||||
# TODO: move to controller
|
# TODO: move to controller
|
||||||
cache: dict[str, int] = {}
|
# kilometer cache; note, user may change measurement it partway through, flush cache
|
||||||
|
#cache: dict[str, int] = {}
|
||||||
# TODO: move to strings.py
|
|
||||||
def format_metadata(row: RowType) -> str:
|
|
||||||
prefix = row.dict["tooltip"]
|
|
||||||
|
|
||||||
if row == RowType.QUICK_CONNECT or row == RowType.CHNG_FAV:
|
|
||||||
label = MainController.query_config(Preferences.FAV_LBL)
|
|
||||||
if len(label) < 1:
|
|
||||||
label = "unset"
|
|
||||||
return f"{prefix} ({label})"
|
|
||||||
else:
|
|
||||||
return prefix
|
|
||||||
|
|
||||||
## TODO: move to configs/servers
|
## TODO: move to configs/servers
|
||||||
#def query_history() -> list | None:
|
#def query_history() -> list | None:
|
||||||
@@ -92,14 +81,9 @@ def format_metadata(row: RowType) -> str:
|
|||||||
# 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
|
||||||
#
|
#
|
||||||
@@ -115,12 +99,12 @@ def format_metadata(row: RowType) -> str:
|
|||||||
# 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,
|
||||||
@@ -143,7 +127,7 @@ def format_metadata(row: RowType) -> str:
|
|||||||
#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(
|
||||||
@@ -167,7 +151,7 @@ def format_metadata(row: RowType) -> str:
|
|||||||
# 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(
|
||||||
@@ -184,7 +168,7 @@ def format_metadata(row: RowType) -> str:
|
|||||||
# 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
|
||||||
@@ -217,46 +201,22 @@ class OuterWindow(Gtk.Window):
|
|||||||
super().__init__(title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER)
|
super().__init__(title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER)
|
||||||
|
|
||||||
self.hb = AppHeaderBar()
|
self.hb = AppHeaderBar()
|
||||||
MainController.set_mediator(AppNav)
|
MainController.register_widget("window", self)
|
||||||
AppNav.window = self
|
|
||||||
|
|
||||||
# steam deck taskbar may occlude elements
|
# NOTE: steam deck taskbar may occlude elements
|
||||||
if MainController.get_prefs().is_steam_deck is False:
|
if MainController.get_prefs().is_steam_deck is False:
|
||||||
self.set_titlebar(self.hb)
|
self.set_titlebar(self.hb)
|
||||||
|
|
||||||
self.connect("delete-event", self._on_delete_event)
|
self.connect("delete-event", self._on_delete_event)
|
||||||
self.connect("key-press-event", self._on_keypress)
|
|
||||||
|
|
||||||
MainController.set_resolution(self)
|
|
||||||
|
|
||||||
self.grid = Grid()
|
self.grid = Grid()
|
||||||
self.toast = Toast()
|
self.add(self.grid)
|
||||||
self.overlay = Gtk.Overlay()
|
|
||||||
self.overlay.add_overlay(self.grid)
|
|
||||||
self.overlay.add_overlay(self.toast)
|
|
||||||
self.add(self.overlay)
|
|
||||||
|
|
||||||
|
MainController.set_resolution(self)
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
||||||
self.toast.set_visible(False)
|
|
||||||
self.grid.right_panel.filters_vbox.set_visible(False)
|
|
||||||
self.grid.right_panel.enable_ping_button(False)
|
|
||||||
self.grid.sel_panel.set_visible(False)
|
|
||||||
|
|
||||||
# TODO: register from treeview or notebook
|
|
||||||
AppNav.treeview = self.grid.notebook.scrollable_treelist.treeview
|
|
||||||
|
|
||||||
css.load_css()
|
css.load_css()
|
||||||
AppNav.grid.notebook.set_page_by_enum(NotebookPage.MAIN)
|
MainController.open_page(NotebookPage.SERVERS)
|
||||||
|
|
||||||
def _on_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
|
|
||||||
if event.state is Gdk.ModifierType.CONTROL_MASK \
|
|
||||||
and event.keyval is Gdk.KEY_d:
|
|
||||||
if AppNav.right_panel.filters_vbox.keyword_entry.is_focus():
|
|
||||||
return
|
|
||||||
if AppNav.right_panel.filters_vbox.maps_entry.is_focus():
|
|
||||||
return
|
|
||||||
AppNav.right_panel.toggle_debug()
|
|
||||||
|
|
||||||
def _on_delete_event(
|
def _on_delete_event(
|
||||||
self, window: "OuterWindow", event: Gdk.EventKey
|
self, window: "OuterWindow", event: Gdk.EventKey
|
||||||
@@ -268,121 +228,51 @@ class OuterWindow(Gtk.Window):
|
|||||||
MainController.save_res_and_quit()
|
MainController.save_res_and_quit()
|
||||||
|
|
||||||
|
|
||||||
# TODO: consolidate
|
|
||||||
# wrap base tree class in this
|
|
||||||
class ScrollableTree(Gtk.ScrolledWindow):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.treeview = MenuTreeView(MainController)
|
|
||||||
self.add(self.treeview)
|
|
||||||
|
|
||||||
def _set_adjustment(self, adjustment: VAdjustment) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AppHeaderBar(Gtk.HeaderBar):
|
class AppHeaderBar(Gtk.HeaderBar):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.props.title = APP_NAME
|
self.props.title = APP_NAME
|
||||||
self.set_decoration_layout(":minimize,maximize,close")
|
self.set_decoration_layout(":minimize,maximize,close")
|
||||||
self.set_show_close_button(True)
|
self.set_show_close_button(True)
|
||||||
|
|
||||||
|
|
||||||
class ScrollableNote(ScrollableMixin, Gtk.Box):
|
class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
|
||||||
def __init__(self, content_box: Gtk.Box, back_button=False):
|
|
||||||
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
|
||||||
|
|
||||||
self.scrollable = Gtk.ScrolledWindow()
|
|
||||||
self.scrollable.set_vexpand(True)
|
|
||||||
|
|
||||||
self.back_button = Gtk.Button(
|
|
||||||
label="Back", hexpand=True, halign=Gtk.Align.CENTER
|
|
||||||
)
|
|
||||||
|
|
||||||
self.gutter = Gtk.Box(
|
|
||||||
orientation=Gtk.Orientation.HORIZONTAL, valign=Gtk.Align.END
|
|
||||||
)
|
|
||||||
if back_button:
|
|
||||||
self.gutter.add(self.back_button)
|
|
||||||
self.back_button.connect("clicked", self._on_back_clicked)
|
|
||||||
|
|
||||||
self.scrollable.add(content_box)
|
|
||||||
self.add(self.scrollable)
|
|
||||||
self.add(self.gutter)
|
|
||||||
|
|
||||||
def _on_back_clicked(self, button: Gtk.Button) -> None:
|
|
||||||
AppNav.notebook.return_prior()
|
|
||||||
|
|
||||||
|
|
||||||
class Notebook(ScrollableMixin, Gtk.Notebook):
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(show_tabs=False, show_border=False)
|
super().__init__(show_tabs=False)
|
||||||
|
|
||||||
AppNav.notebook = self
|
self.prior_page: NotebookPage
|
||||||
self.prior_page: int
|
self.prior_status: str
|
||||||
|
|
||||||
# TODO: set internally
|
MainController.register_widget("notebook", self)
|
||||||
self.scrollable_treelist = ScrollableTree()
|
|
||||||
self.scrollable_treelist.set_hexpand(False)
|
|
||||||
self.scrollable_treelist.set_vexpand(True)
|
|
||||||
self.scrollable_treelist.type = None
|
|
||||||
|
|
||||||
self.change = Changelog(MainController)
|
self.help = Help(MainController)
|
||||||
self.clog = ScrollableNote(self.change, back_button=False)
|
self.clog = Changelog(MainController)
|
||||||
self.clog.scrollable.set_propagate_natural_width(False)
|
|
||||||
self.clog.type = RowType.CHANGELOG
|
|
||||||
|
|
||||||
# TODO: scrollable internally
|
|
||||||
self.keys = ScrollableNote(Keybindings())
|
|
||||||
self.keys.type = RowType.KEYBINDINGS
|
|
||||||
|
|
||||||
|
self.keys = Keybindings()
|
||||||
self.settings = Options(MainController)
|
self.settings = Options(MainController)
|
||||||
|
|
||||||
# TODO: make all treeviews internally scrollable in base class
|
self.servers = ServerNotebook(MainController)
|
||||||
# server and quad tables should have hexpand property set to True
|
self.mods = Mods(MainController)
|
||||||
# add all treeviews as page and register them to AppNav and self.indexes
|
|
||||||
# when switching to a treeview, update relevant view and just pop that page
|
|
||||||
# instead of loading/unloading the model each time
|
|
||||||
self.ssu = Gtk.ScrolledWindow()
|
|
||||||
self.servers = ServerTreeView(MainController)
|
|
||||||
self.ssu.add(self.servers)
|
|
||||||
|
|
||||||
self.quad = Gtk.ScrolledWindow()
|
self.thanks = Thanks()
|
||||||
|
self.log = Log(MainController)
|
||||||
self.scroll_mod = Gtk.ScrolledWindow()
|
self.developers = Developers(MainController)
|
||||||
# TODO: register this table
|
|
||||||
self.quad_table = ModTreeView(MainController)
|
|
||||||
AppNav.modtreeview = self.quad_table
|
|
||||||
self.scroll_mod.add(self.quad_table)
|
|
||||||
|
|
||||||
self.scroll_log = Gtk.ScrolledWindow()
|
|
||||||
self.scroll_log.set_hexpand(True)
|
|
||||||
self.log_table = LogTreeView(MainController)
|
|
||||||
AppNav.logtreeview = self.log_table
|
|
||||||
self.scroll_log.add(self.log_table)
|
|
||||||
|
|
||||||
self.thanks = ScrollableNote(Thanks(), back_button=False)
|
|
||||||
|
|
||||||
from dzgui.views.pages.devs import Developers
|
|
||||||
developers = Developers(MainController)
|
|
||||||
self.developers = ScrollableNote(developers)
|
|
||||||
|
|
||||||
self.pages = {
|
self.pages = {
|
||||||
self.scrollable_treelist: NotebookPage.MAIN,
|
self.help: NotebookPage.HELP,
|
||||||
self.clog: NotebookPage.CHANGELOG,
|
self.clog: NotebookPage.CHANGELOG,
|
||||||
self.keys: NotebookPage.KEYS,
|
self.keys: NotebookPage.KEYS,
|
||||||
self.settings: NotebookPage.OPTIONS,
|
self.settings: NotebookPage.OPTIONS,
|
||||||
self.ssu: NotebookPage.SERVERS,
|
self.servers: NotebookPage.SERVERS,
|
||||||
self.scroll_mod: NotebookPage.MODS,
|
self.mods: NotebookPage.MODS,
|
||||||
self.scroll_log: NotebookPage.LOG,
|
self.log: NotebookPage.LOG,
|
||||||
self.thanks: NotebookPage.THANKS,
|
self.thanks: NotebookPage.THANKS,
|
||||||
self.developers: NotebookPage.DEVELOPERS,
|
self.developers: NotebookPage.DEVELOPERS,
|
||||||
}
|
}
|
||||||
self.indexes = {}
|
self.indexes = {}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Note that due to historical reasons, GtkNotebook refuses to switch to a page
|
Note that due to historical reasons, Gtk.Notebook refuses to switch to a page
|
||||||
unless the child widget is visible. Therefore, it is recommended to show child
|
unless the child widget is visible. Therefore, it is recommended to show child
|
||||||
widgets before adding them to a notebook.
|
widgets before adding them to a notebook.
|
||||||
"""
|
"""
|
||||||
@@ -390,66 +280,37 @@ class Notebook(ScrollableMixin, Gtk.Notebook):
|
|||||||
page.show_all()
|
page.show_all()
|
||||||
index = self.append_page(page)
|
index = self.append_page(page)
|
||||||
enum = self.pages[page]
|
enum = self.pages[page]
|
||||||
crumbs = enum.dict["crumbs"]
|
|
||||||
self.indexes[enum] = index
|
self.indexes[enum] = index
|
||||||
|
|
||||||
self.connect_after("switch-page", self._on_page_changed)
|
self.connect_after("switch-page", self._on_page_changed)
|
||||||
self.connect("key-press-event", self._on_keypress)
|
self.connect("key-press-event", self._on_keypress)
|
||||||
|
|
||||||
def _on_mods_focus_change(self, widget: ModTreeView, event: Gdk.EventFocus) -> None:
|
|
||||||
MainController.toggle_mod_panel(event.in_)
|
|
||||||
|
|
||||||
def _on_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
|
def _on_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
|
||||||
page = self.get_page()
|
|
||||||
|
|
||||||
match event.keyval:
|
match event.keyval:
|
||||||
case Gdk.KEY_Return:
|
|
||||||
page = self.get_page()
|
|
||||||
if page:
|
|
||||||
page.back_button.clicked()
|
|
||||||
case Gdk.KEY_Right | Gdk.KEY_l:
|
case Gdk.KEY_Right | Gdk.KEY_l:
|
||||||
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
||||||
return
|
return
|
||||||
AppNav.right_panel.focus_button_box()
|
MainController.focus_button_box()
|
||||||
case Gdk.KEY_question:
|
case Gdk.KEY_question:
|
||||||
self.toggle_keybindings()
|
self.toggle_keybindings()
|
||||||
|
|
||||||
# NOTE: abort on non scrollable pages
|
|
||||||
# TODO: this should be an internal property of those pages
|
|
||||||
allowed = (NotebookPage.KEYS, NotebookPage.CHANGELOG, NotebookPage.THANKS)
|
|
||||||
if self.pages[page] not in allowed:
|
|
||||||
return
|
|
||||||
|
|
||||||
match event.keyval:
|
|
||||||
case Gdk.KEY_k | Gdk.KEY_Up:
|
|
||||||
page._set_adjustment(VAdjustment.UP)
|
|
||||||
case Gdk.KEY_Down | Gdk.KEY_j:
|
|
||||||
page._set_adjustment(VAdjustment.DOWN)
|
|
||||||
case Gdk.KEY_g:
|
|
||||||
page._set_adjustment(VAdjustment.TOP)
|
|
||||||
case Gdk.KEY_G:
|
|
||||||
page._set_adjustment(VAdjustment.BOTTOM)
|
|
||||||
|
|
||||||
def return_prior(self) -> None:
|
def return_prior(self) -> None:
|
||||||
page = self.get_nth_page(self.prior_page)
|
"""
|
||||||
# FIXME: check enum for NotebookPage.OPTIONS
|
Gtk.Notebook focuses the first input field when changing pages;
|
||||||
if hasattr(page, "steam_entry"):
|
this workaround unhighlights the selected region and makes entry
|
||||||
"""
|
fields unfocusable prior to the page 'switch-page' signal,
|
||||||
Gtk.Notebook focuses the first input field when changing pages;
|
then makes them focusable again
|
||||||
this workaround unhighlights the selected region and makes entry
|
|
||||||
fields unfocusable prior to the page 'switch-page' signal,
|
|
||||||
then makes them focusable again
|
|
||||||
|
|
||||||
Used when switching to keybindings dialog
|
Used when switching back from NotebookPage.KEYS to avoid cursor
|
||||||
"""
|
getting stuck inside text entry fields
|
||||||
entries = page.steam_entry, page.bm_entry
|
"""
|
||||||
for entry in entries:
|
if self.prior_page is NotebookPage.OPTIONS:
|
||||||
entry.set_position(-1)
|
self.settings.block_text_entry()
|
||||||
entry.set_can_focus(False)
|
self.set_page_by_enum(self.prior_page)
|
||||||
self.set_current_page(self.prior_page)
|
self.settings.unblock_text_entry()
|
||||||
for entry in entries:
|
return
|
||||||
entry.set_can_focus(True)
|
self.set_page_by_enum(self.prior_page)
|
||||||
self.set_current_page(self.prior_page)
|
MainController.set_statusbar(self.prior_status)
|
||||||
|
|
||||||
def get_page_by_enum(self) -> NotebookPage | None:
|
def get_page_by_enum(self) -> NotebookPage | None:
|
||||||
for k, v in self.indexes.items():
|
for k, v in self.indexes.items():
|
||||||
@@ -462,19 +323,24 @@ class Notebook(ScrollableMixin, Gtk.Notebook):
|
|||||||
if cur_page == NotebookPage.KEYS:
|
if cur_page == NotebookPage.KEYS:
|
||||||
self.return_prior()
|
self.return_prior()
|
||||||
else:
|
else:
|
||||||
self.prior_page = self.get_current_page()
|
self.prior_status = MainController.get_statusbar()
|
||||||
self.set_page_by_enum(NotebookPage.KEYS)
|
self.set_page_by_enum(NotebookPage.KEYS)
|
||||||
|
|
||||||
def focus_current(self) -> None:
|
def focus_current(self) -> None:
|
||||||
widget = self.get_page()
|
widget = self.get_page()
|
||||||
if widget:
|
if widget is None:
|
||||||
w = widget.get_children()[0]
|
return
|
||||||
# NOTE: if item contains treeview, focus first cell
|
|
||||||
try:
|
if widget is self.servers:
|
||||||
w.focus_first_row()
|
MainController.grab_active_treeview()
|
||||||
w.grab_focus()
|
return
|
||||||
except Exception as e:
|
|
||||||
w.grab_focus()
|
w = widget.get_children()[0]
|
||||||
|
try:
|
||||||
|
w.focus_first_row()
|
||||||
|
w.grab_focus()
|
||||||
|
except Exception as e:
|
||||||
|
w.grab_focus()
|
||||||
|
|
||||||
def get_page(self) -> Gtk.Widget | None:
|
def get_page(self) -> Gtk.Widget | None:
|
||||||
ind = self.get_current_page()
|
ind = self.get_current_page()
|
||||||
@@ -484,143 +350,87 @@ class Notebook(ScrollableMixin, Gtk.Notebook):
|
|||||||
return widget
|
return widget
|
||||||
|
|
||||||
def set_page_by_enum(self, enum: NotebookPage) -> None:
|
def set_page_by_enum(self, enum: NotebookPage) -> None:
|
||||||
self.prior_page = self.get_current_page()
|
prior = self.get_page_by_enum()
|
||||||
|
if prior is not None:
|
||||||
|
self.prior_page = prior
|
||||||
self.set_current_page(self.indexes[enum])
|
self.set_current_page(self.indexes[enum])
|
||||||
self.focus_current()
|
self.focus_current()
|
||||||
# TODO: should be an internal property of those pages
|
|
||||||
blank = [
|
|
||||||
NotebookPage.OPTIONS,
|
|
||||||
NotebookPage.THANKS,
|
|
||||||
NotebookPage.CHANGELOG,
|
|
||||||
NotebookPage.LOG
|
|
||||||
]
|
|
||||||
if enum in blank:
|
|
||||||
# TODO: delegate to controller
|
|
||||||
AppNav.grid.statusbar.refresh(None)
|
|
||||||
|
|
||||||
def _on_page_changed(
|
def _on_page_changed(
|
||||||
self, notebook: "Notebook", page: Gtk.Widget, page_num: int
|
self, notebook: "Notebook", page: Gtk.Widget, page_num: int
|
||||||
) -> None:
|
) -> None:
|
||||||
enum = self.get_page_by_enum()
|
enum = self.get_page_by_enum()
|
||||||
|
if enum is not None:
|
||||||
|
crumbs = enum.dict["crumbs"]
|
||||||
|
status = enum.dict["statusbar"]
|
||||||
|
MainController.set_crumbs(crumbs)
|
||||||
|
|
||||||
is_mods = True if enum is NotebookPage.MODS else False
|
is_mods = True if enum is NotebookPage.MODS else False
|
||||||
|
is_servers = True if enum is NotebookPage.SERVERS else False
|
||||||
|
|
||||||
MainController.toggle_mod_panel(is_mods)
|
MainController.toggle_mod_panel(is_mods)
|
||||||
|
MainController.toggle_server_panels(is_servers)
|
||||||
|
|
||||||
crumbs = enum.dict["crumbs"]
|
if status is False:
|
||||||
MainController.set_crumbs(crumbs)
|
MainController.set_statusbar("")
|
||||||
|
if enum is NotebookPage.SERVERS:
|
||||||
# TODO: going to be deprecated after server notebook is added
|
MainController.present_servers()
|
||||||
if enum is not NotebookPage.MAIN:
|
|
||||||
return
|
|
||||||
|
|
||||||
MainController.set_help_menu_crumbs()
|
|
||||||
|
|
||||||
|
|
||||||
class Statusbar(Gtk.Statusbar):
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
help_text = strings.statusbar_helptext
|
|
||||||
self.set_text(help_text)
|
|
||||||
|
|
||||||
version = MainController.get_prefs().version
|
|
||||||
self.status_right_label = Gtk.Label(label=version)
|
|
||||||
self.add(self.status_right_label)
|
|
||||||
|
|
||||||
self.players = ""
|
|
||||||
|
|
||||||
def get_text(self) -> str:
|
|
||||||
area = self.get_message_area()
|
|
||||||
label = area.get_children()[0]
|
|
||||||
return label.get_text()
|
|
||||||
|
|
||||||
def set_text(self, string: str) -> None:
|
|
||||||
if string is None:
|
|
||||||
return
|
|
||||||
meta = self.get_context_id("Statusbar")
|
|
||||||
self.push(meta, string)
|
|
||||||
|
|
||||||
def refresh(self, row: RowType) -> None:
|
|
||||||
if row is None:
|
|
||||||
formatted = ""
|
|
||||||
else:
|
|
||||||
formatted = format_metadata(row)
|
|
||||||
self.set_text(formatted)
|
|
||||||
|
|
||||||
def append_distance(self, dist: str) -> None:
|
|
||||||
# TODO: process strings in controller
|
|
||||||
if dist == strings.unknown:
|
|
||||||
dist = f"| Distance: {dist}"
|
|
||||||
else:
|
|
||||||
dist = f"| Distance: {dist}"
|
|
||||||
self.set_text(self.players + dist)
|
|
||||||
|
|
||||||
def update_server_meta(self) -> None:
|
|
||||||
model = AppNav.treeview.get_model()
|
|
||||||
if model is None:
|
|
||||||
players = 0
|
|
||||||
hits = 0
|
|
||||||
else:
|
|
||||||
hits = len(model)
|
|
||||||
players = 0
|
|
||||||
for row in model:
|
|
||||||
players += row[4]
|
|
||||||
|
|
||||||
# TODO: move to util.format
|
|
||||||
players_pretty = pluralize("players", players)
|
|
||||||
hits_pretty = pluralize("matches", hits)
|
|
||||||
formatted = (
|
|
||||||
f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
|
|
||||||
)
|
|
||||||
suffix = "| Distance: calculating..."
|
|
||||||
|
|
||||||
if players == 0:
|
|
||||||
suffix = ""
|
|
||||||
self.set_text(formatted + suffix)
|
|
||||||
self.players = formatted
|
|
||||||
|
|
||||||
|
|
||||||
class Grid(Gtk.Grid):
|
class Grid(Gtk.Grid):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__(column_homogeneous=True)
|
||||||
self.set_column_homogeneous(True)
|
|
||||||
|
|
||||||
AppNav.grid = self
|
MAX_ROWS = 3
|
||||||
|
MAX_COLS = 3
|
||||||
|
SINGLE_ROW = 1
|
||||||
|
SINGLE_COL = 1
|
||||||
|
|
||||||
# FIXME: do not pass AppNav to right panel
|
MainController.register_widget("grid", self)
|
||||||
self.right_panel = RightPanel(AppNav, MainController)
|
|
||||||
self.sel_panel = ModSelectionPanel(MainController)
|
|
||||||
self.right_panel.pack_start(self.sel_panel, NO_EXPAND, NO_FILL, 0)
|
|
||||||
|
|
||||||
self.notebook = Notebook()
|
|
||||||
self.statusbar = Statusbar()
|
|
||||||
|
|
||||||
|
self.statusbar = Statusbar(MainController)
|
||||||
|
self.right_panel = RightPanel(MainController)
|
||||||
self.breadcrumbs = Gtk.Label(halign=Gtk.Align.START)
|
self.breadcrumbs = Gtk.Label(halign=Gtk.Align.START)
|
||||||
self.set_breadcrumbs(strings.label_main_menu)
|
self.set_breadcrumbs(strings.label_main_menu)
|
||||||
|
|
||||||
self.attach(self.notebook, 0, 0, 3, 1)
|
self.notebook = Notebook()
|
||||||
self.attach_next_to(
|
self.conpan = ConnectPanel(MainController)
|
||||||
self.breadcrumbs, self.notebook, Gtk.PositionType.TOP, 3, 1
|
|
||||||
)
|
self.attach(self.notebook, 0, 0, MAX_COLS, SINGLE_ROW)
|
||||||
self.attach_next_to(
|
|
||||||
self.statusbar, self.notebook, Gtk.PositionType.BOTTOM, 3, 1
|
els = (
|
||||||
)
|
(self.breadcrumbs, self.notebook, Gtk.PositionType.TOP, MAX_COLS, SINGLE_ROW),
|
||||||
self.attach_next_to(
|
(self.conpan, self.notebook, Gtk.PositionType.BOTTOM, MAX_COLS, SINGLE_ROW),
|
||||||
self.right_panel, self.notebook, Gtk.PositionType.RIGHT, 1, 1
|
(self.statusbar, self.conpan, Gtk.PositionType.BOTTOM, MAX_COLS, SINGLE_ROW),
|
||||||
|
(self.right_panel, self.notebook, Gtk.PositionType.RIGHT, SINGLE_COL, MAX_ROWS),
|
||||||
)
|
)
|
||||||
|
for el, sibling, pos, h_span, v_span in els:
|
||||||
|
self.attach_next_to(el, sibling, pos, h_span, v_span)
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
||||||
|
def toggle_filter_panel(self, state: bool) -> None:
|
||||||
|
self.right_panel.filters_vbox.set_visible(state)
|
||||||
|
|
||||||
|
def toggle_connect_panel(self, state: bool) -> None:
|
||||||
|
self.conpan.set_visible(state)
|
||||||
|
|
||||||
|
def toggle_refresh_button(self, state: bool) -> None:
|
||||||
|
self.right_panel.refresh_button.set_visible(state)
|
||||||
|
|
||||||
|
# TODO make this method internal to Statusbar
|
||||||
def get_breadcrumbs(self) -> str:
|
def get_breadcrumbs(self) -> str:
|
||||||
return self.breadcrumbs.get_text()
|
return self.breadcrumbs.get_text()
|
||||||
|
|
||||||
def set_breadcrumbs(self, text: str) -> None:
|
def set_breadcrumbs(self, text: str) -> None:
|
||||||
self.breadcrumbs.set_markup(f"<b>{text}</b>")
|
crumbs = embolden(text)
|
||||||
|
self.breadcrumbs.set_markup(crumbs)
|
||||||
|
|
||||||
|
|
||||||
class App(Gtk.Application):
|
class App(Gtk.Application):
|
||||||
def __init__(self, prefs, is_developer) -> None:
|
def __init__(self, prefs: "UserPrefs") -> None:
|
||||||
GLib.set_prgname(APP_NAME)
|
GLib.set_prgname(APP_NAME)
|
||||||
MainController.set_prefs(prefs)
|
MainController.set_prefs(prefs)
|
||||||
MainController.set_developer_mode(is_developer)
|
|
||||||
|
|
||||||
self.win = OuterWindow()
|
self.win = OuterWindow()
|
||||||
|
|
||||||
@@ -638,8 +448,9 @@ class App(Gtk.Application):
|
|||||||
)
|
)
|
||||||
Gtk.main()
|
Gtk.main()
|
||||||
|
|
||||||
def _catch_sigint(self) -> None:
|
def _catch_sigint(self) -> Literal[True]:
|
||||||
self.win.halt_proc_and_quit()
|
self.win.halt_proc_and_quit()
|
||||||
|
return True
|
||||||
|
|
||||||
def _halt_window_subprocess(
|
def _halt_window_subprocess(
|
||||||
self,
|
self,
|
||||||
@@ -650,16 +461,4 @@ class App(Gtk.Application):
|
|||||||
) -> None:
|
) -> None:
|
||||||
self.win.halt_proc_and_quit()
|
self.win.halt_proc_and_quit()
|
||||||
|
|
||||||
|
|
||||||
class AppNavigation:
|
|
||||||
grid: Grid
|
|
||||||
window: OuterWindow
|
|
||||||
notebook: Notebook
|
|
||||||
right_panel: RightPanel
|
|
||||||
treeview: MenuTreeView
|
|
||||||
modtreeview: ModTreeView
|
|
||||||
logtreeview: LogTreeView
|
|
||||||
# TODO: add tree_log and tree_server views here
|
|
||||||
|
|
||||||
AppNav = AppNavigation()
|
|
||||||
MainController = Controller()
|
MainController = Controller()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from dzgui.const.enum import ButtonType
|
from dzgui.const.enum import ButtonType
|
||||||
from dzgui.const.constants import NO_EXPAND, NO_FILL
|
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
@@ -20,7 +20,7 @@ class ContextualButton(Gtk.Button):
|
|||||||
self.opens = opens
|
self.opens = opens
|
||||||
|
|
||||||
class ButtonBox(Gtk.Box):
|
class ButtonBox(Gtk.Box):
|
||||||
def __init__(self, controller):
|
def __init__(self, controller) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
spacing=6,
|
spacing=6,
|
||||||
margin_top=0,
|
margin_top=0,
|
||||||
@@ -29,7 +29,6 @@ class ButtonBox(Gtk.Box):
|
|||||||
orientation=Gtk.Orientation.VERTICAL,
|
orientation=Gtk.Orientation.VERTICAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.prior_button = ButtonType.MAIN_MENU
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.buttons = list()
|
self.buttons = list()
|
||||||
self.connect("key-press-event", self._on_keypress)
|
self.connect("key-press-event", self._on_keypress)
|
||||||
@@ -44,14 +43,13 @@ class ButtonBox(Gtk.Box):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# FIXME: if debug log fails to load, still opens table
|
# FIXME: if debug log fails to load, still opens table
|
||||||
|
|
||||||
size = (10, 10) if prefs.is_steam_deck else (50, 50)
|
size = (10, 10) if prefs.is_steam_deck else (50, 50)
|
||||||
x, y = size
|
x, y = size
|
||||||
button.set_size_request(x, y)
|
button.set_size_request(x, y)
|
||||||
|
|
||||||
self.buttons.append(button)
|
self.buttons.append(button)
|
||||||
button.connect("clicked", self._on_selection_button_clicked)
|
button.connect("clicked", self._on_selection_button_clicked)
|
||||||
self.pack_start(button, NO_EXPAND, NO_FILL, 0)
|
self.pack_start(button, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
|
|
||||||
|
|
||||||
def _on_selection_button_clicked(self, button: Gtk.Button) -> None:
|
def _on_selection_button_clicked(self, button: Gtk.Button) -> None:
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from dzgui.util.strings import refresh
|
||||||
|
from dzgui.views.components.icon import Icon
|
||||||
|
from dzgui.const.constants import REFRESH_ICON, WEB_BROWSER, INPUT_KEYBOARD
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
|
||||||
|
class IconButton(Gtk.Button):
|
||||||
|
def __init__(self, icon: str, margin: int = 0) -> None:
|
||||||
|
super().__init__()
|
||||||
|
i = Icon(icon, l_margin=margin)
|
||||||
|
self.set_image(i)
|
||||||
|
self.set_image_position(Gtk.PositionType.RIGHT)
|
||||||
|
|
||||||
|
class IconTextButton(IconButton):
|
||||||
|
def __init__(self, icon: str, label: str) -> None:
|
||||||
|
super().__init__(icon, margin=5)
|
||||||
|
self.set_label(label)
|
||||||
|
|
||||||
|
|
||||||
|
class WebButton(IconTextButton):
|
||||||
|
def __init__(self, label: str) -> None:
|
||||||
|
super().__init__(icon=WEB_BROWSER, label=label)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshButton(IconTextButton):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__(icon=REFRESH_ICON, label=refresh)
|
||||||
|
self.set_margin_top(10)
|
||||||
|
self.set_margin_start(80)
|
||||||
|
self.set_margin_end(80)
|
||||||
|
|
||||||
|
class KeysButton(IconTextButton):
|
||||||
|
def __init__(self, label: str) -> None:
|
||||||
|
super().__init__(icon=INPUT_KEYBOARD, label=label)
|
||||||
|
self.set_margin_top(10)
|
||||||
|
self.set_margin_start(80)
|
||||||
|
self.set_margin_end(80)
|
||||||
@@ -1,17 +1,114 @@
|
|||||||
|
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.views.components.buttons import WebButton
|
||||||
|
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
|
||||||
|
|
||||||
class ConnectPanel(Gtk.Grid):
|
if TYPE_CHECKING:
|
||||||
def __init__(self) -> None:
|
from dzgui.controllers.mc import Controller
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.label = Gtk.Label(label="Connect by IP/ID")
|
class ConnectPanel(Gtk.Frame):
|
||||||
self.label2 = Gtk.Label(label="Save server by IP/ID")
|
def __init__(self, controller: "Controller") -> None:
|
||||||
self.entry1 = Gtk.Entry()
|
super().__init__(margin_top=10, margin_bottom=5)
|
||||||
self.entry2 = Gtk.Entry()
|
|
||||||
|
|
||||||
self.attach(self.label, 0, 0, 3, 1)
|
COLS = 1
|
||||||
self.attach_next_to(self.entry1, self.label, Gtk.PositionType.BOTTOM, 3, 1)
|
ROWS = 1
|
||||||
self.attach_next_to(self.label2, self.entry1, Gtk.PositionType.BOTTOM, 3, 1)
|
self.classname = "invalid-entry"
|
||||||
self.attach_next_to(self.entry2, self.label2, Gtk.PositionType.BOTTOM, 3, 1)
|
self.controller = controller
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# TODO: get ip as well?
|
||||||
|
user_fav = self.controller.get_favorite_label()
|
||||||
|
|
||||||
|
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.add(self.fav_label)
|
||||||
|
|
||||||
|
self.fav_button = Gtk.Button(label=connect_panel.connect,
|
||||||
|
tooltip_text=connect_panel.connect_tooltip
|
||||||
|
)
|
||||||
|
self.fav_edit = Gtk.Button(label=connect_panel.edit)
|
||||||
|
self.edit_server = WebButton(label="EDIT")
|
||||||
|
|
||||||
|
self.conn_server= Gtk.Button(label=connect_panel.connect,
|
||||||
|
tooltip_text=connect_panel.connect_tooltip
|
||||||
|
)
|
||||||
|
self.add_server = Gtk.Button(label=connect_panel.add,
|
||||||
|
tooltip_text=connect_panel.add_tooltip
|
||||||
|
)
|
||||||
|
|
||||||
|
self.conn_server.set_sensitive(False)
|
||||||
|
self.add_server.set_sensitive(False)
|
||||||
|
if server_name is None:
|
||||||
|
self.fav_button.set_sensitive(False)
|
||||||
|
|
||||||
|
add_label = BoldLabel(connect_panel.add_con)
|
||||||
|
conn_label = BoldLabel(connect_panel.favorite)
|
||||||
|
|
||||||
|
self.grid = Gtk.Grid(margin=10, vexpand=False, column_spacing=15, row_spacing=5)
|
||||||
|
self.grid.attach(conn_label, 0, 0, COLS, ROWS)
|
||||||
|
|
||||||
|
els = (
|
||||||
|
(scrollable_label, conn_label, Gtk.PositionType.RIGHT, 3, ROWS),
|
||||||
|
(self.fav_button, scrollable_label, Gtk.PositionType.RIGHT, COLS, ROWS),
|
||||||
|
(add_label, conn_label, Gtk.PositionType.BOTTOM, COLS, ROWS),
|
||||||
|
(self.entry, scrollable_label, Gtk.PositionType.BOTTOM, COLS, ROWS),
|
||||||
|
(self.add_server, self.fav_button, Gtk.PositionType.BOTTOM, 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.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 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()
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
import gi # noqa E402
|
import gi # noqa E402
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk, Pango
|
from gi.repository import Gtk, Gdk, Pango, GLib
|
||||||
|
|
||||||
from typing import Literal
|
from dzgui.const.enum import FilterMode
|
||||||
|
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
from dzgui.util.margins import set_surrounding_margins
|
from dzgui.util.margins import set_surrounding_margins
|
||||||
from dzgui.const.enum import FilterMode
|
from dzgui.views.components.labels import BoldLabel
|
||||||
from dzgui.const.constants import NO_EXPAND, NO_FILL
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class FilterPanel(Gtk.Box):
|
class FilterPanel(Gtk.Box):
|
||||||
def __init__(self, appnav, controller):
|
def __init__(self, controller):
|
||||||
super().__init__(spacing=6)
|
super().__init__(spacing=6, vexpand=False)
|
||||||
|
|
||||||
# TODO: set strings in constants
|
|
||||||
self.default_filters = {
|
self.default_filters = {
|
||||||
strings.filter_1pp: True,
|
strings.filter_1pp: True,
|
||||||
strings.filter_day: True,
|
strings.filter_day: True,
|
||||||
@@ -32,17 +32,17 @@ class FilterPanel(Gtk.Box):
|
|||||||
strings.filter_modded: True,
|
strings.filter_modded: True,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.AppNav = appnav
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
map_store = self.controller.get_map_store()
|
map_store = self.controller.get_map_store()
|
||||||
|
|
||||||
self.checks = []
|
self.checks = []
|
||||||
self.maps_hr = []
|
self.maps_hr = []
|
||||||
|
|
||||||
self.enabled_filters = dict(self.default_filters)
|
self.enabled_filters = dict(self.default_filters)
|
||||||
self.keyword_filter = ""
|
self.keyword_filter: str
|
||||||
self.selected_map = strings.all_maps
|
self.selected_map: str = strings.all_maps
|
||||||
self.prior_map = strings.all_maps
|
self.prior_map: str = strings.all_maps
|
||||||
|
|
||||||
button_grid = Gtk.Grid(
|
button_grid = Gtk.Grid(
|
||||||
halign=Gtk.Align.CENTER, column_spacing=5, column_homogeneous=True
|
halign=Gtk.Align.CENTER, column_spacing=5, column_homogeneous=True
|
||||||
@@ -71,7 +71,8 @@ class FilterPanel(Gtk.Box):
|
|||||||
set_surrounding_margins(self, 10)
|
set_surrounding_margins(self, 10)
|
||||||
self.set_margin_top(1)
|
self.set_margin_top(1)
|
||||||
|
|
||||||
self.filters_label = Gtk.Label(label="Filters")
|
# TODO: strings
|
||||||
|
self.filters_label = BoldLabel("Filters")
|
||||||
|
|
||||||
self.keyword_entry = Gtk.Entry()
|
self.keyword_entry = Gtk.Entry()
|
||||||
self.keyword_entry.set_placeholder_text("Filter by keyword")
|
self.keyword_entry.set_placeholder_text("Filter by keyword")
|
||||||
@@ -96,22 +97,19 @@ class FilterPanel(Gtk.Box):
|
|||||||
self.maps_entry.connect("key-press-event", self._on_map_entry_keypress)
|
self.maps_entry.connect("key-press-event", self._on_map_entry_keypress)
|
||||||
|
|
||||||
# FIXME: only giving two params to pack_start
|
# FIXME: only giving two params to pack_start
|
||||||
|
# cf. EXPAND, NO_EXPAND
|
||||||
self.maps_combo.pack_start(renderer_text, True)
|
self.maps_combo.pack_start(renderer_text, True)
|
||||||
self.maps_combo.connect("changed", self._on_map_changed)
|
self.maps_combo.connect("changed", self._on_map_changed)
|
||||||
self.maps_combo.connect("key-press-event", self._on_combo_keypress)
|
self.maps_combo.connect("key-press-event", self._on_combo_keypress)
|
||||||
|
|
||||||
self.pack_start(self.filters_label, NO_EXPAND, NO_FILL, 0)
|
for el in self.filters_label, self.keyword_entry, self.maps_combo, button_grid:
|
||||||
self.pack_start(self.keyword_entry, NO_EXPAND, NO_FILL, 0)
|
self.pack_start(el, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
self.pack_start(self.maps_combo, NO_EXPAND, NO_FILL, 0)
|
|
||||||
|
|
||||||
self.pack_start(button_grid, NO_EXPAND, NO_FILL, 0)
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -125,19 +123,13 @@ 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 = ""
|
||||||
self.reinit_filters()
|
self.reinit_filters()
|
||||||
self.set_visible(False)
|
self.set_visible(False)
|
||||||
sel_panel = self.AppNav.grid.sel_panel
|
# TODO:
|
||||||
|
sel_panel = self.controller.mediator.grid.sel_panel
|
||||||
if sel_panel.is_visible():
|
if sel_panel.is_visible():
|
||||||
sel_panel.set_visible(False)
|
sel_panel.set_visible(False)
|
||||||
|
|
||||||
@@ -191,7 +183,8 @@ class FilterPanel(Gtk.Box):
|
|||||||
completion.set_model(map_store)
|
completion.set_model(map_store)
|
||||||
|
|
||||||
def restore_focus_to_treeview(self) -> Literal[False]:
|
def restore_focus_to_treeview(self) -> Literal[False]:
|
||||||
self.AppNav.treeview.grab_focus()
|
view = self.controller.get_active_treeview()
|
||||||
|
view.grab_focus()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _on_keyword_keypress(
|
def _on_keyword_keypress(
|
||||||
@@ -230,7 +223,8 @@ class FilterPanel(Gtk.Box):
|
|||||||
return self.keyword_filter
|
return self.keyword_filter
|
||||||
|
|
||||||
def _on_keyword_enter(self, entry: Gtk.Entry) -> None:
|
def _on_keyword_enter(self, entry: Gtk.Entry) -> None:
|
||||||
self.AppNav.window.set_keep_below(False)
|
# TODO:
|
||||||
|
self.controller.mediator.window.set_keep_below(False)
|
||||||
keyword = entry.get_text().lower()
|
keyword = entry.get_text().lower()
|
||||||
if keyword == self.keyword_filter:
|
if keyword == self.keyword_filter:
|
||||||
return
|
return
|
||||||
@@ -238,7 +232,8 @@ class FilterPanel(Gtk.Box):
|
|||||||
return
|
return
|
||||||
logger.info(f"User filtered by keyword '{keyword}'")
|
logger.info(f"User filtered by keyword '{keyword}'")
|
||||||
self.keyword_filter = keyword
|
self.keyword_filter = keyword
|
||||||
self.AppNav.treeview.filter(FilterMode.KEYWORD, keyword)
|
treeview = self.controller.get_active_treeview()
|
||||||
|
treeview.filter(FilterMode.KEYWORD, keyword)
|
||||||
|
|
||||||
def _on_button_release(self, window, button) -> Literal[True]:
|
def _on_button_release(self, window, button) -> Literal[True]:
|
||||||
return True
|
return True
|
||||||
@@ -255,8 +250,7 @@ class FilterPanel(Gtk.Box):
|
|||||||
check.set_active(not state)
|
check.set_active(not state)
|
||||||
|
|
||||||
def _on_check_toggled(self, button: Gtk.CheckButton) -> None:
|
def _on_check_toggled(self, button: Gtk.CheckButton) -> None:
|
||||||
if not self.AppNav.treeview.is_server_context(self.AppNav.treeview.view):
|
treeview = self.controller.get_active_treeview()
|
||||||
return
|
|
||||||
label = button.get_label()
|
label = button.get_label()
|
||||||
state = button.get_active()
|
state = button.get_active()
|
||||||
logger.info(f"User toggled button '{label}' to {state}")
|
logger.info(f"User toggled button '{label}' to {state}")
|
||||||
@@ -266,9 +260,10 @@ class FilterPanel(Gtk.Box):
|
|||||||
mode = FilterMode.TOGGLE_OFF
|
mode = FilterMode.TOGGLE_OFF
|
||||||
|
|
||||||
self.enabled_filters[label] = state
|
self.enabled_filters[label] = state
|
||||||
self.AppNav.treeview.filter(mode, label)
|
treeview.filter(mode, label)
|
||||||
|
|
||||||
def _on_map_changed(self, combo: Gtk.ComboBox) -> None:
|
def _on_map_changed(self, combo: Gtk.ComboBox) -> None:
|
||||||
|
treeview = self.controller.get_active_treeview()
|
||||||
old_sel = self.selected_map
|
old_sel = self.selected_map
|
||||||
model = combo.get_model()
|
model = combo.get_model()
|
||||||
tree_iter = combo.get_active_iter()
|
tree_iter = combo.get_active_iter()
|
||||||
@@ -283,4 +278,4 @@ class FilterPanel(Gtk.Box):
|
|||||||
self.prior_map = self.selected_map
|
self.prior_map = self.selected_map
|
||||||
self.selected_map = selection
|
self.selected_map = selection
|
||||||
self.maps_entry.set_text(selection)
|
self.maps_entry.set_text(selection)
|
||||||
self.AppNav.treeview.filter(FilterMode.MAP)
|
treeview.filter(FilterMode.MAP)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from dzgui.util.format import embolden
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
class BoldLabel(Gtk.Label):
|
||||||
|
def __init__(self, text: str):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
label = embolden(text)
|
||||||
|
self.set_markup(label)
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.enum import ModButton
|
from dzgui.const.enum import ModButton
|
||||||
from dzgui.const.constants import NO_EXPAND, FILL
|
from dzgui.const.constants import NO_EXPAND, FILL, NO_PADDING
|
||||||
from dzgui.util import strings
|
from dzgui.util.strings import mod_panel
|
||||||
|
from dzgui.views.components.labels import BoldLabel
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
@@ -26,32 +27,34 @@ class ModSelectionPanel(Gtk.Box):
|
|||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
|
header = BoldLabel(mod_panel.header)
|
||||||
|
|
||||||
|
self.main_panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
buttons = (
|
buttons = (
|
||||||
ModButton.SELECT_ALL,
|
ModButton.SELECT_ALL,
|
||||||
ModButton.UNSELECT_ALL,
|
ModButton.UNSELECT_ALL,
|
||||||
ModButton.HIGHLIGHT_STALE,
|
|
||||||
ModButton.DELETE_SELECTED,
|
ModButton.DELETE_SELECTED,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.header = Gtk.Label()
|
|
||||||
self.header.set_markup(f"<b>{strings.mod_panel.header}</b>")
|
|
||||||
|
|
||||||
self.main_panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
|
||||||
for button in buttons:
|
for button in buttons:
|
||||||
b = EnumeratedModButton(button)
|
b = EnumeratedModButton(button)
|
||||||
b.connect("clicked", self._on_button_clicked)
|
b.connect("clicked", self._on_button_clicked)
|
||||||
self.main_panel.pack_start(b, NO_EXPAND, FILL, 0)
|
self.main_panel.pack_start(b, NO_EXPAND, FILL, NO_PADDING)
|
||||||
|
|
||||||
self.extra_panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
self.stale_panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
for button in (ModButton.SELECT_STALE, ModButton.UNHIGHLIGHT_STALE):
|
buttons = (
|
||||||
|
ModButton.HIGHLIGHT_STALE,
|
||||||
|
ModButton.SELECT_STALE,
|
||||||
|
ModButton.UNHIGHLIGHT_STALE,
|
||||||
|
)
|
||||||
|
for button in buttons:
|
||||||
b = EnumeratedModButton(button)
|
b = EnumeratedModButton(button)
|
||||||
b.connect("clicked", self._on_button_clicked)
|
b.connect("clicked", self._on_button_clicked)
|
||||||
b.set_sensitive(False)
|
if button is not ModButton.HIGHLIGHT_STALE:
|
||||||
self.extra_panel.pack_start(b, NO_EXPAND, FILL, 0)
|
b.set_sensitive(False)
|
||||||
|
self.stale_panel.pack_start(b, NO_EXPAND, FILL, NO_PADDING)
|
||||||
|
|
||||||
self.pack_start(self.header, NO_EXPAND, FILL, 0)
|
for el in header, self.main_panel, self.stale_panel:
|
||||||
self.pack_start(self.main_panel, NO_EXPAND, FILL, 0)
|
self.pack_start(el, NO_EXPAND, FILL, NO_PADDING)
|
||||||
self.pack_start(self.extra_panel, NO_EXPAND, FILL, 0)
|
|
||||||
|
|
||||||
def after_colorize(self) -> None:
|
def after_colorize(self) -> None:
|
||||||
self.controller.unselect_all_mods()
|
self.controller.unselect_all_mods()
|
||||||
|
|||||||
@@ -1,70 +1,49 @@
|
|||||||
|
from typing import Literal, TYPE_CHECKING
|
||||||
|
|
||||||
import gi # noqa E402
|
import gi # noqa E402
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk
|
from gi.repository import Gtk, Gdk, GLib
|
||||||
|
|
||||||
from dzgui.const.enum import Preferences
|
from dzgui.const.enum import Preferences
|
||||||
from dzgui.views.components.buttonbox import ButtonBox
|
from dzgui.views.components.buttonbox import ButtonBox
|
||||||
from dzgui.views.components.filter_panel import FilterPanel
|
from dzgui.views.components.filter_panel import FilterPanel
|
||||||
|
from dzgui.views.components.mod_panel import ModSelectionPanel
|
||||||
from dzgui.views.components.icon import Icon
|
from dzgui.views.components.icon import Icon
|
||||||
from dzgui.const.constants import NO_EXPAND, NO_FILL, EXPAND, FILL, INPUT_KEYBOARD
|
from dzgui.views.components.buttons import RefreshButton, KeysButton
|
||||||
from dzgui.util import strings
|
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
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from GLib import SOURCE_REMOVE
|
||||||
|
|
||||||
# TODO: refactor depends on ServerTreeView
|
# TODO: refactor depends on ServerTreeView
|
||||||
class RightPanel(Gtk.Box):
|
class RightPanel(Gtk.Box):
|
||||||
def __init__(self, appnav, controller):
|
def __init__(self, controller):
|
||||||
super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL)
|
super().__init__(spacing=6, orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
self.AppNav = appnav
|
self.time = 30
|
||||||
self.AppNav.right_panel = self
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
self.controller.register_widget("right_panel", self)
|
||||||
|
|
||||||
self.button_vbox = ButtonBox(controller)
|
self.button_vbox = ButtonBox(controller)
|
||||||
self.filters_vbox = FilterPanel(appnav, controller)
|
self.filters_vbox = FilterPanel(controller)
|
||||||
|
self.sel_panel = ModSelectionPanel(controller)
|
||||||
|
|
||||||
for el in self.button_vbox, self.filters_vbox:
|
self.refresh_button = RefreshButton()
|
||||||
padding = 0
|
self.refresh_button.set_tooltip_text(refresh_tooltip)
|
||||||
self.pack_start(el, NO_EXPAND, NO_FILL, padding)
|
self.refresh_button.connect("clicked", self._on_refresh_clicked)
|
||||||
|
|
||||||
self.ping = Gtk.Button(
|
self.keys = KeysButton(keys_button)
|
||||||
label=strings.ping_servers,
|
self.keys.set_tooltip_text(keys_tooltip)
|
||||||
margin_top=10,
|
self.keys.connect("clicked", self._on_question_clicked)
|
||||||
margin_start=80,
|
|
||||||
margin_end=80,
|
|
||||||
tooltip_text=strings.ping_tooltip,
|
|
||||||
)
|
|
||||||
self.ping.connect("clicked", self._on_ping_clicked)
|
|
||||||
|
|
||||||
self.debug_toggle = Gtk.ToggleButton(
|
for el in self.button_vbox, self.keys, self.filters_vbox, self.refresh_button:
|
||||||
label=strings.debug_mode,
|
self.pack_start(el, NO_EXPAND, FILL, NO_PADDING)
|
||||||
margin_top=10,
|
|
||||||
margin_start=80,
|
|
||||||
margin_end=80,
|
|
||||||
tooltip_text=strings.debug_tooltip,
|
|
||||||
)
|
|
||||||
|
|
||||||
if controller.query_config(Preferences.DEBUG) == True:
|
self.pack_start(self.sel_panel, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
self.debug_toggle.set_active(True)
|
|
||||||
self.debug_toggle.connect("toggled", self._on_debug_toggled)
|
|
||||||
|
|
||||||
i = Icon(INPUT_KEYBOARD)
|
|
||||||
i.set_margin_start(5)
|
|
||||||
self.question = Gtk.Button(
|
|
||||||
label=strings.keys_button,
|
|
||||||
margin_start=80,
|
|
||||||
margin_end=80,
|
|
||||||
tooltip_text=strings.keys_tooltip,
|
|
||||||
image = i,
|
|
||||||
)
|
|
||||||
self.question.set_image_position(Gtk.PositionType.RIGHT)
|
|
||||||
self.question.connect("clicked", self._on_question_clicked)
|
|
||||||
|
|
||||||
for el in self.ping, self.debug_toggle, self.question:
|
|
||||||
padding = 0
|
|
||||||
self.pack_start(el, NO_EXPAND, FILL, padding)
|
|
||||||
|
|
||||||
def enable_ping_button(self, state: bool) -> None:
|
|
||||||
self.ping.set_visible(state)
|
|
||||||
|
|
||||||
|
# TODO: move to filter panel
|
||||||
def reinit_maps(self, rows: list) -> None:
|
def reinit_maps(self, rows: list) -> None:
|
||||||
self.controller.reinit_map_store()
|
self.controller.reinit_map_store()
|
||||||
# TODO: communicate with controller
|
# TODO: communicate with controller
|
||||||
@@ -73,53 +52,56 @@ class RightPanel(Gtk.Box):
|
|||||||
self.selected = "All maps"
|
self.selected = "All maps"
|
||||||
self.filters_vbox.set_unique_maps(rows)
|
self.filters_vbox.set_unique_maps(rows)
|
||||||
|
|
||||||
def toggle_debug(self) -> None:
|
def decrement(self) -> "SOURCE_REMOVE" | Literal[True]:
|
||||||
if type(self.AppNav.window.get_focus()) is Gtk.Entry:
|
self.time -= 1
|
||||||
return
|
if self.time == 0:
|
||||||
state = self.debug_toggle.get_active()
|
self.time = 30
|
||||||
self.debug_toggle.set_active(not state)
|
self.refresh_button.set_label(refresh)
|
||||||
|
self.refresh_button.set_sensitive(True)
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
self.refresh_button.set_label(f"{refresh} ({str(self.time)})")
|
||||||
|
return True
|
||||||
|
|
||||||
def _on_debug_toggled(self, button: Gtk.Button) -> None:
|
def _on_refresh_clicked(self, button: RefreshButton) -> None:
|
||||||
state = button.get_active()
|
self.refresh_button.set_sensitive(False)
|
||||||
grid = self.AppNav.grid
|
self.refresh_button.set_label(f"{refresh} ({str(self.time)})")
|
||||||
self.controller.toggle_debug_mode()
|
GLib.timeout_add_seconds(1, self.decrement)
|
||||||
|
self.controller.refresh_tree()
|
||||||
|
|
||||||
def _on_ping_clicked(self, button: Gtk.Button) -> None:
|
# TODO: reference
|
||||||
# TODO
|
#def _on_ping_clicked(self, button: Gtk.Button) -> None:
|
||||||
block_signals()
|
# block_signals()
|
||||||
|
# def _update_pings():
|
||||||
|
# # TODO
|
||||||
|
# rows = ModelManager.get_filtered()
|
||||||
|
# with ThreadPoolExecutor(100) as executor:
|
||||||
|
# futures = [
|
||||||
|
# executor.submit(Servers.ping, i, row)
|
||||||
|
# for i, row in enumerate(rows)
|
||||||
|
# ]
|
||||||
|
# wait(futures)
|
||||||
|
# for future in futures:
|
||||||
|
# res = future.result()
|
||||||
|
# path = Gtk.TreePath.new_from_indices([res.iteration])
|
||||||
|
# temp_model[path][9] = res.ping
|
||||||
|
# ModelManager.ping_cache[res.addr] = res.ping
|
||||||
|
# # TODO: drop/rewrite
|
||||||
|
# treeview.set_model(temp_model)
|
||||||
|
# treeview.wait_dialog.destroy()
|
||||||
|
# treeview.grab_focus()
|
||||||
|
|
||||||
def _update_pings():
|
# # TODO:
|
||||||
# TODO
|
# unblock_signals()
|
||||||
rows = ModelManager.get_filtered()
|
|
||||||
with ThreadPoolExecutor(100) as executor:
|
|
||||||
futures = [
|
|
||||||
executor.submit(Servers.ping, i, row)
|
|
||||||
for i, row in enumerate(rows)
|
|
||||||
]
|
|
||||||
wait(futures)
|
|
||||||
for future in futures:
|
|
||||||
res = future.result()
|
|
||||||
path = Gtk.TreePath.new_from_indices([res.iteration])
|
|
||||||
temp_model[path][9] = res.ping
|
|
||||||
ModelManager.ping_cache[res.addr] = res.ping
|
|
||||||
self.AppNav.treeview.set_model(temp_model)
|
|
||||||
self.AppNav.treeview.wait_dialog.destroy()
|
|
||||||
self.AppNav.treeview.enable_ping_column(True)
|
|
||||||
self.AppNav.treeview.grab_focus()
|
|
||||||
self.AppNav.right_panel.ping.set_sensitive(False)
|
|
||||||
|
|
||||||
# TODO:
|
# temp_model = self.AppNav.treeview.get_model()
|
||||||
unblock_signals()
|
# treeview.set_model(None)
|
||||||
|
# treeview.wait_dialog = GenericDialog("Pinging servers", Popup.WAIT)
|
||||||
temp_model = self.AppNav.treeview.get_model()
|
# treeview.wait_dialog.show_all()
|
||||||
self.AppNav.treeview.set_model(None)
|
# thread = threading.Thread(target=_update_pings, args=())
|
||||||
self.AppNav.treeview.wait_dialog = GenericDialog("Pinging servers", Popup.WAIT)
|
# thread.start()
|
||||||
self.AppNav.treeview.wait_dialog.show_all()
|
|
||||||
thread = threading.Thread(target=_update_pings, args=())
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def _on_question_clicked(self, button: Gtk.Button) -> None:
|
def _on_question_clicked(self, button: Gtk.Button) -> None:
|
||||||
self.AppNav.grid.notebook.toggle_keybindings()
|
self.controller.open_keybindings()
|
||||||
|
|
||||||
def focus_button_box(self) -> None:
|
def focus_button_box(self) -> None:
|
||||||
self.button_vbox.buttons[0].grab_focus()
|
self.button_vbox.buttons[0].grab_focus()
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from dzgui.const.enum import RowType, Preferences
|
||||||
|
from dzgui.util import strings
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
class Statusbar(Gtk.Statusbar):
|
||||||
|
def __init__(self, controller: "Controller") -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.controller = controller
|
||||||
|
self.controller.register_widget("statusbar", self)
|
||||||
|
|
||||||
|
help_text = strings.statusbar_helptext
|
||||||
|
self.set_text(help_text)
|
||||||
|
|
||||||
|
version = self.controller.get_prefs().version
|
||||||
|
self.status_right_label = Gtk.Label(label=version)
|
||||||
|
self.add(self.status_right_label)
|
||||||
|
|
||||||
|
self.players = ""
|
||||||
|
|
||||||
|
def get_text(self) -> str:
|
||||||
|
area = self.get_message_area()
|
||||||
|
label = area.get_children()[0]
|
||||||
|
return label.get_text()
|
||||||
|
|
||||||
|
def set_text(self, string: str) -> None:
|
||||||
|
if string is None:
|
||||||
|
return
|
||||||
|
meta = self.get_context_id("Statusbar")
|
||||||
|
self.push(meta, string)
|
||||||
|
|
||||||
|
def refresh(self, row: "RowType") -> None:
|
||||||
|
if row is None:
|
||||||
|
formatted = ""
|
||||||
|
else:
|
||||||
|
formatted = self.format_metadata(row)
|
||||||
|
self.set_text(formatted)
|
||||||
|
|
||||||
|
def append_distance(self, dist: str) -> None:
|
||||||
|
# TODO: process strings in controller
|
||||||
|
if dist == strings.unknown:
|
||||||
|
dist = f"| Distance: {dist}"
|
||||||
|
else:
|
||||||
|
dist = f"| Distance: {dist}"
|
||||||
|
self.set_text(self.players + dist)
|
||||||
|
|
||||||
|
def format_metadata(self, row: "RowType") -> str:
|
||||||
|
prefix = row.dict["tooltip"]
|
||||||
|
|
||||||
|
if row == RowType.QUICK_CONNECT or row == RowType.CHNG_FAV:
|
||||||
|
label = self.controller.query_config(Preferences.FAV_LBL)
|
||||||
|
if len(label) < 1:
|
||||||
|
label = "unset"
|
||||||
|
return f"{prefix} ({label})"
|
||||||
|
else:
|
||||||
|
return prefix
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
from dzgui.views.components.icon import Icon
|
|
||||||
from dzgui.const.constants import WEB_BROWSER
|
|
||||||
|
|
||||||
import gi
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
from gi.repository import Gtk # noqa E402
|
|
||||||
|
|
||||||
|
|
||||||
class WebButton(Gtk.Button):
|
|
||||||
def __init__(self, label: str):
|
|
||||||
super().__init__(label=label)
|
|
||||||
|
|
||||||
icon = Icon(WEB_BROWSER, l_margin=5)
|
|
||||||
self.set_image(icon)
|
|
||||||
self.set_image_position(Gtk.PositionType.RIGHT)
|
|
||||||
@@ -2,6 +2,7 @@ import gi
|
|||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
||||||
|
|
||||||
|
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING
|
||||||
from dzgui.const.enum import Popup
|
from dzgui.const.enum import Popup
|
||||||
|
|
||||||
class EntryDialog(GenericDialog):
|
class EntryDialog(GenericDialog):
|
||||||
@@ -24,14 +25,14 @@ class EntryDialog(GenericDialog):
|
|||||||
self.user_entry.set_margin_top(0)
|
self.user_entry.set_margin_top(0)
|
||||||
self.user_entry.set_size_request(250, 0)
|
self.user_entry.set_size_request(250, 0)
|
||||||
self.user_entry.set_activates_default(True)
|
self.user_entry.set_activates_default(True)
|
||||||
self.dialogBox.pack_start(self.user_entry, NO_EXPAND, NO_FILL, 0)
|
self.dialogBox.pack_start(self.user_entry, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
|
|
||||||
if link:
|
if link:
|
||||||
button = Gtk.Button(label=link)
|
button = Gtk.Button(label=link)
|
||||||
button.set_margin_start(60)
|
button.set_margin_start(60)
|
||||||
button.set_margin_end(60)
|
button.set_margin_end(60)
|
||||||
button.connect("clicked", self._on_button_clicked, button_type)
|
button.connect("clicked", self._on_button_clicked, button_type)
|
||||||
self.dialogBox.pack_end(button, NO_EXPAND, NO_FILL, 0)
|
self.dialogBox.pack_end(button, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
|
|
||||||
self.ok = self.dialog.action_area.get_children()[1]
|
self.ok = self.dialog.action_area.get_children()[1]
|
||||||
self.ok.set_sensitive(False)
|
self.ok.set_sensitive(False)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import gi
|
|||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
||||||
|
|
||||||
|
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING
|
||||||
from dzgui.util.import strings
|
from dzgui.util.import strings
|
||||||
|
|
||||||
class LanDialog(Gtk.MessageDialog):
|
class LanDialog(Gtk.MessageDialog):
|
||||||
@@ -55,7 +56,7 @@ class LanDialog(Gtk.MessageDialog):
|
|||||||
self.button_box.add(self.warn_label)
|
self.button_box.add(self.warn_label)
|
||||||
|
|
||||||
content = self.get_content_area()
|
content = self.get_content_area()
|
||||||
content.pack_start(self.button_box, NO_EXPAND, NO_FILL, 0)
|
content.pack_start(self.button_box, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
content.set_margin_start(30)
|
content.set_margin_start(30)
|
||||||
content.set_margin_end(30)
|
content.set_margin_end(30)
|
||||||
content.show_all()
|
content.show_all()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from dzgui.const.enum import Popup, Preferences
|
|||||||
from dzgui.const.constants import NO_EXPAND, NO_FILL
|
from dzgui.const.constants import NO_EXPAND, NO_FILL
|
||||||
from dzgui.util.open_links import open_user_workshop
|
from dzgui.util.open_links import open_user_workshop
|
||||||
from dzgui.views.dialogs.generic import GenericDialog
|
from dzgui.views.dialogs.generic import GenericDialog
|
||||||
from dzgui.views.components.web_button import WebButton
|
from dzgui.views.components.buttons import WebButton
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
@@ -41,8 +41,9 @@ class WorkshopLinkDialog(GenericDialog):
|
|||||||
|
|
||||||
def _on_button_clicked(self, button: Gtk.Button, uid: str) -> None:
|
def _on_button_clicked(self, button: Gtk.Button, uid: str) -> None:
|
||||||
# TODO: currently checks user id on instantiation
|
# TODO: currently checks user id on instantiation
|
||||||
client = self.controller.query_config(Preferences.CLIENT)
|
self.controller.open_user_workshop(uid)
|
||||||
open_user_workshop(uid, client)
|
#client = self.controller.query_config(Preferences.CLIENT)
|
||||||
|
#open_user_workshop(uid, client)
|
||||||
|
|
||||||
def _on_dialog_response(
|
def _on_dialog_response(
|
||||||
self, dialog: Self, resp: Gtk.ResponseType
|
self, dialog: Self, resp: Gtk.ResponseType
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import gi
|
|||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
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
|
from dzgui.const.enum import Popup
|
||||||
|
|
||||||
class ServerDetailsDialog(GenericDialog):
|
class ServerDetailsDialog(GenericDialog):
|
||||||
@@ -57,8 +58,8 @@ class ServerDetailsDialog(GenericDialog):
|
|||||||
box.add(el)
|
box.add(el)
|
||||||
scrollable_message.add(box)
|
scrollable_message.add(box)
|
||||||
|
|
||||||
dialog_box.pack_start(scrollable_tree, EXPAND, FILL, 0)
|
dialog_box.pack_start(scrollable_tree, EXPAND, FILL, NO_PADDING)
|
||||||
dialog_box.pack_start(scrollable_message, EXPAND, FILL, 0)
|
dialog_box.pack_start(scrollable_message, EXPAND, FILL, NO_PADDING)
|
||||||
|
|
||||||
self.wait_dialog = GenericDialog(strings.details, Popup.WAIT)
|
self.wait_dialog = GenericDialog(strings.details, Popup.WAIT)
|
||||||
self.wait_dialog.show_all()
|
self.wait_dialog.show_all()
|
||||||
|
|||||||
@@ -3,11 +3,22 @@ from dzgui.const.constants import SCROLL_INCREMENT
|
|||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gdk # noqa
|
from gi.repository import Gtk, Gdk # noqa
|
||||||
|
|
||||||
class ScrollableMixin:
|
class ScrollableMixin:
|
||||||
def _set_adjustment(self, adjustment = VAdjustment) -> None:
|
def _on_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
|
||||||
vadj = self.scrollable.get_vadjustment()
|
match event.keyval:
|
||||||
|
case Gdk.KEY_k | Gdk.KEY_Up:
|
||||||
|
self._set_adjustment(VAdjustment.UP)
|
||||||
|
case Gdk.KEY_Down | Gdk.KEY_j:
|
||||||
|
self._set_adjustment(VAdjustment.DOWN)
|
||||||
|
case Gdk.KEY_g:
|
||||||
|
self._set_adjustment(VAdjustment.TOP)
|
||||||
|
case Gdk.KEY_G:
|
||||||
|
self._set_adjustment(VAdjustment.BOTTOM)
|
||||||
|
|
||||||
|
def _set_adjustment(self, adjustment: VAdjustment) -> None:
|
||||||
|
vadj = self.get_vadjustment()
|
||||||
match adjustment:
|
match adjustment:
|
||||||
case VAdjustment.TOP:
|
case VAdjustment.TOP:
|
||||||
adj = vadj.get_lower()
|
adj = vadj.get_lower()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from importlib import resources
|
|||||||
|
|
||||||
from dzgui.const.constants import APP_NAME_LOWER
|
from dzgui.const.constants import APP_NAME_LOWER
|
||||||
from dzgui.util.format import format_pango
|
from dzgui.util.format import format_pango
|
||||||
|
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
@@ -16,9 +17,9 @@ logger = logging.getLogger(__name__)
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
class Changelog(Gtk.Box):
|
class Changelog(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||||
def __init__(self, controller: "Controller"):
|
def __init__(self, controller: "Controller"):
|
||||||
super().__init__()
|
super().__init__(propagate_natural_width=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
changelog = resources.read_text(APP_NAME_LOWER, "data/CHANGELOG.md")
|
changelog = resources.read_text(APP_NAME_LOWER, "data/CHANGELOG.md")
|
||||||
@@ -30,4 +31,8 @@ class Changelog(Gtk.Box):
|
|||||||
formatted = format_pango(changelog)
|
formatted = format_pango(changelog)
|
||||||
self.changelog_label = Gtk.Label(valign=Gtk.Align.START, margin=15)
|
self.changelog_label = Gtk.Label(valign=Gtk.Align.START, margin=15)
|
||||||
self.changelog_label.set_markup(formatted)
|
self.changelog_label.set_markup(formatted)
|
||||||
self.add(self.changelog_label)
|
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
self.box.add(self.changelog_label)
|
||||||
|
self.add(self.box)
|
||||||
|
|
||||||
|
self.connect("key-press-event", self._on_keypress)
|
||||||
+17
-19
@@ -2,8 +2,9 @@ from dataclasses import fields
|
|||||||
from typing import Union, TYPE_CHECKING
|
from typing import Union, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.enum import NotebookPage
|
from dzgui.const.enum import NotebookPage
|
||||||
from dzgui.util.strings import developers
|
|
||||||
from dzgui.util.css import add_class
|
from dzgui.util.css import add_class
|
||||||
|
from dzgui.util.strings import developers
|
||||||
|
from dzgui.views.components.labels import BoldLabel
|
||||||
|
|
||||||
import gi # noqa E402
|
import gi # noqa E402
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
@@ -14,19 +15,21 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.config.userprefs import UserPrefs
|
from dzgui.config.userprefs import UserPrefs
|
||||||
from dzgui.config.xdg import Xdg
|
from dzgui.config.xdg import Xdg
|
||||||
|
|
||||||
class Developers(Gtk.Box):
|
class Developers(Gtk.ScrolledWindow):
|
||||||
"""
|
"""
|
||||||
Shows TreeViews displaying contents of parsed XDG paths
|
Shows TreeViews displaying contents of parsed XDG paths
|
||||||
and user preferences
|
and user preferences
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
orientation=Gtk.Orientation.VERTICAL,
|
|
||||||
margin_start=10,
|
|
||||||
margin_end=10,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
self.box = Gtk.Box(
|
||||||
|
orientation=Gtk.Orientation.VERTICAL,
|
||||||
|
margin_start=10,
|
||||||
|
margin_end=10
|
||||||
|
)
|
||||||
|
|
||||||
heading = Gtk.Label(label=developers.header)
|
heading = Gtk.Label(label=developers.header)
|
||||||
heading.set_halign(Gtk.Align.CENTER)
|
heading.set_halign(Gtk.Align.CENTER)
|
||||||
@@ -34,11 +37,9 @@ class Developers(Gtk.Box):
|
|||||||
|
|
||||||
back_button = Gtk.Button(label="Back", halign=Gtk.Align.START)
|
back_button = Gtk.Button(label="Back", halign=Gtk.Align.START)
|
||||||
back_button.connect("clicked", self._on_back_clicked)
|
back_button.connect("clicked", self._on_back_clicked)
|
||||||
paths_label = Gtk.Label()
|
|
||||||
paths_label.set_markup(f"<b>{developers.paths_label}</b>")
|
|
||||||
|
|
||||||
prefs_label = Gtk.Label()
|
paths_label = BoldLabel(developers.paths_label)
|
||||||
prefs_label.set_markup(f"<b>{developers.prefs_label}</b>")
|
prefs_label = BoldLabel(developers.prefs_label)
|
||||||
|
|
||||||
paths_tree = self._make_tree(self.controller.prefs.paths)
|
paths_tree = self._make_tree(self.controller.prefs.paths)
|
||||||
prefs_tree = self._make_tree(self.controller.prefs)
|
prefs_tree = self._make_tree(self.controller.prefs)
|
||||||
@@ -48,23 +49,20 @@ class Developers(Gtk.Box):
|
|||||||
paths_tree.set_cursor(path)
|
paths_tree.set_cursor(path)
|
||||||
prefs_tree.set_cursor(path)
|
prefs_tree.set_cursor(path)
|
||||||
|
|
||||||
for el in [
|
for el in [paths_label, paths_tree, prefs_label, prefs_tree]:
|
||||||
paths_label,
|
|
||||||
paths_tree,
|
|
||||||
prefs_label,
|
|
||||||
prefs_tree
|
|
||||||
]:
|
|
||||||
trees_box.add(el)
|
trees_box.add(el)
|
||||||
|
|
||||||
self.add(heading)
|
for el in [heading, back_button, trees_box]:
|
||||||
self.add(back_button)
|
self.box.add(el)
|
||||||
self.add(trees_box)
|
|
||||||
|
self.add(self.box)
|
||||||
|
|
||||||
def _on_back_clicked(self, button: Gtk.Button) -> None:
|
def _on_back_clicked(self, button: Gtk.Button) -> None:
|
||||||
self.controller.open_page(NotebookPage.OPTIONS)
|
self.controller.open_page(NotebookPage.OPTIONS)
|
||||||
|
|
||||||
def _make_tree(self, prefs: Union["Xdg", "UserPrefs"]) -> Gtk.TreeView:
|
def _make_tree(self, prefs: Union["Xdg", "UserPrefs"]) -> Gtk.TreeView:
|
||||||
view = Gtk.TreeView()
|
from dzgui.views.trees.tree_base import TreeView
|
||||||
|
view = TreeView(self.controller)
|
||||||
renderer = Gtk.CellRendererText()
|
renderer = Gtk.CellRendererText()
|
||||||
for i, col in enumerate(developers.columns):
|
for i, col in enumerate(developers.columns):
|
||||||
column = Gtk.TreeViewColumn(col, renderer, text=i)
|
column = Gtk.TreeViewColumn(col, renderer, text=i)
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from dzgui.views.trees.tree_menu import MenuTreeView
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
class Help(Gtk.ScrolledWindow):
|
||||||
|
def __init__(self, controller: "Controller") -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.treeview = MenuTreeView(controller)
|
||||||
|
self.add(self.treeview)
|
||||||
|
|
||||||
|
self.controller = controller
|
||||||
|
self.controller.register_widget("menu", self.treeview)
|
||||||
|
|
||||||
|
def get_treeview(self) -> MenuTreeView:
|
||||||
|
return self.treeview
|
||||||
@@ -1,25 +1,31 @@
|
|||||||
|
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
||||||
from dzgui.util.strings import navigation, servers, vim, key_header, key_contexts
|
from dzgui.util.strings import navigation, servers, vim, key_header, key_contexts
|
||||||
from dzgui.util.css import add_class
|
from dzgui.util.css import add_class
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
class Keybindings(Gtk.Box):
|
class Keybindings(ScrollableMixin, Gtk.ScrolledWindow):
|
||||||
"""
|
"""
|
||||||
Notebook page holding a prearranged grid
|
Notebook page holding a prearranged grid
|
||||||
of keybindings and their descriptions
|
of keybindings and their descriptions
|
||||||
"""
|
"""
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
super().__init__()
|
||||||
|
|
||||||
|
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
label = Gtk.Label(label=key_header)
|
label = Gtk.Label(label=key_header)
|
||||||
add_class(label, "page-heading")
|
add_class(label, "page-heading")
|
||||||
self.add(label)
|
|
||||||
|
|
||||||
grid = self.build_grid([servers, navigation, vim])
|
grid = self.build_grid([servers, navigation, vim])
|
||||||
self.add(grid)
|
self.box.add(label)
|
||||||
|
self.box.add(grid)
|
||||||
|
self.add(self.box)
|
||||||
|
|
||||||
|
self.connect("key-press-event", self._on_keypress)
|
||||||
|
|
||||||
def build_keys(self, items: list) -> Gtk.Grid:
|
def build_keys(self, items: list) -> Gtk.Grid:
|
||||||
grid = Gtk.Grid(row_spacing=10, column_homogeneous=True)
|
grid = Gtk.Grid(row_spacing=10, column_homogeneous=True)
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from dzgui.views.trees.tree_log import LogTreeView
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
class Log(Gtk.ScrolledWindow):
|
||||||
|
def __init__(self, controller: "Controller") -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.treeview = LogTreeView(controller)
|
||||||
|
self.add(self.treeview)
|
||||||
|
|
||||||
|
self.controller = controller
|
||||||
|
self.controller.register_widget("logtreeview", self.treeview)
|
||||||
|
|
||||||
|
def get_treeview(self) -> LogTreeView:
|
||||||
|
return self.treeview
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from dzgui.views.trees.tree_mods import ModTreeView
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, Gdk # noqa
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
class Mods(Gtk.ScrolledWindow):
|
||||||
|
def __init__(self, controller: "Controller") -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.controller = controller
|
||||||
|
self.tree = ModTreeView(controller)
|
||||||
|
|
||||||
|
self.add(self.tree)
|
||||||
|
self.controller.register_widget("modtreeview", self.tree)
|
||||||
@@ -8,7 +8,7 @@ from dzgui.util import strings, css, open_links
|
|||||||
|
|
||||||
from dzgui.views.components.label import LeftLabel
|
from dzgui.views.components.label import LeftLabel
|
||||||
from dzgui.views.components.eventbox import InfoEventBox
|
from dzgui.views.components.eventbox import InfoEventBox
|
||||||
from dzgui.views.components.web_button import WebButton
|
from dzgui.views.components.buttons import WebButton
|
||||||
from dzgui.views.dialogs.link_dialog import WorkshopLinkDialog
|
from dzgui.views.dialogs.link_dialog import WorkshopLinkDialog
|
||||||
|
|
||||||
from dzgui.const.enum import Preferences, Popup
|
from dzgui.const.enum import Preferences, Popup
|
||||||
@@ -23,6 +23,7 @@ from dzgui.const.constants import (
|
|||||||
FLATPAK_SANDBOX,
|
FLATPAK_SANDBOX,
|
||||||
NO_EXPAND,
|
NO_EXPAND,
|
||||||
NO_FILL,
|
NO_FILL,
|
||||||
|
NO_PADDING,
|
||||||
STEAM_CMD,
|
STEAM_CMD,
|
||||||
VIEW_CONCEAL,
|
VIEW_CONCEAL,
|
||||||
VIEW_REVEAL,
|
VIEW_REVEAL,
|
||||||
@@ -91,7 +92,7 @@ class Options(Gtk.Box):
|
|||||||
self.client_combo.set_active(0)
|
self.client_combo.set_active(0)
|
||||||
self.client_combo.connect("changed", self._on_client_changed)
|
self.client_combo.connect("changed", self._on_client_changed)
|
||||||
hbox = Gtk.Box(spacing=5, halign=Gtk.Align.START)
|
hbox = Gtk.Box(spacing=5, halign=Gtk.Align.START)
|
||||||
hbox.pack_start(self.client_combo, NO_EXPAND, NO_FILL, 0)
|
hbox.pack_start(self.client_combo, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
|
|
||||||
self.distance_toggle = self.make_binary_radio(
|
self.distance_toggle = self.make_binary_radio(
|
||||||
strings.options.km, strings.options.mi, Preferences.DIST
|
strings.options.km, strings.options.mi, Preferences.DIST
|
||||||
@@ -167,7 +168,10 @@ class Options(Gtk.Box):
|
|||||||
|
|
||||||
developers=Gtk.Button(label="Developers", halign=Gtk.Align.START)
|
developers=Gtk.Button(label="Developers", halign=Gtk.Align.START)
|
||||||
developers.connect("clicked", self._on_developers_clicked)
|
developers.connect("clicked", self._on_developers_clicked)
|
||||||
if self.controller.get_developer_mode():
|
|
||||||
|
prefs = self.controller.get_prefs()
|
||||||
|
is_developer = prefs.is_developer
|
||||||
|
if is_developer:
|
||||||
grid.attach(
|
grid.attach(
|
||||||
developers, 1, 0, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
|
developers, 1, 0, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
|
||||||
)
|
)
|
||||||
@@ -187,6 +191,15 @@ class Options(Gtk.Box):
|
|||||||
self.scrollable.add(grid)
|
self.scrollable.add(grid)
|
||||||
self.add(self.scrollable)
|
self.add(self.scrollable)
|
||||||
|
|
||||||
|
def block_text_entry(self) -> None:
|
||||||
|
for entry in self.steam_entry, self.bm_entry:
|
||||||
|
entry.set_position(-1)
|
||||||
|
entry.set_can_focus(False)
|
||||||
|
|
||||||
|
def unblock_text_entry(self) -> None:
|
||||||
|
for entry in self.steam_entry, self.bm_entry:
|
||||||
|
entry.set_can_focus(True)
|
||||||
|
|
||||||
def _on_developers_clicked(self, button: Gtk.Button) -> None:
|
def _on_developers_clicked(self, button: Gtk.Button) -> None:
|
||||||
self.controller.show_developers_page()
|
self.controller.show_developers_page()
|
||||||
|
|
||||||
@@ -290,6 +303,7 @@ class Options(Gtk.Box):
|
|||||||
#call_on_thread(show_wait_dialog, cmd, wait_msg, "")
|
#call_on_thread(show_wait_dialog, cmd, wait_msg, "")
|
||||||
|
|
||||||
def _on_client_changed(self, combo: Gtk.ComboBoxText) -> None:
|
def _on_client_changed(self, combo: Gtk.ComboBoxText) -> None:
|
||||||
|
# TODO: use two columns or constants here, not strings
|
||||||
client = combo.get_active_text()
|
client = combo.get_active_text()
|
||||||
match client:
|
match client:
|
||||||
case "Steam":
|
case "Steam":
|
||||||
@@ -390,8 +404,8 @@ class Options(Gtk.Box):
|
|||||||
radio2 = Gtk.RadioButton.new_from_widget(radio1)
|
radio2 = Gtk.RadioButton.new_from_widget(radio1)
|
||||||
radio2.set_label(second_option)
|
radio2.set_label(second_option)
|
||||||
radio1.connect("toggled", self._on_radio_toggled, context)
|
radio1.connect("toggled", self._on_radio_toggled, context)
|
||||||
hbox.pack_start(radio1, NO_EXPAND, NO_FILL, 0)
|
hbox.pack_start(radio1, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
hbox.pack_start(radio2, NO_EXPAND, NO_FILL, 0)
|
hbox.pack_start(radio2, NO_EXPAND, NO_FILL, NO_PADDING)
|
||||||
|
|
||||||
return hbox
|
return hbox
|
||||||
|
|
||||||
@@ -410,9 +424,10 @@ 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:
|
||||||
# in case file got deleted locally
|
# NOTE: in case file got deleted locally
|
||||||
self.controller.spawn_dialog(strings.config_not_found, Popup.QUIT)
|
self.controller.spawn_dialog(strings.config_not_found, Popup.QUIT)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Self, TYPE_CHECKING
|
||||||
|
|
||||||
|
from dzgui.const.enum import ContextMenuGroup
|
||||||
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
from dzgui.util.strings import server_labels
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, Gdk, GLib # noqa E402
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
class ServerNotebook(Gtk.ScrolledWindow):
|
||||||
|
def __init__(self, controller: "Controller"):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.tab_cache = ""
|
||||||
|
self.controller = controller
|
||||||
|
self.controller.register_widget("servers", self)
|
||||||
|
self.notebook = Gtk.Notebook(show_tabs=True)
|
||||||
|
|
||||||
|
self.browser = ServerTreeView(controller)
|
||||||
|
self.saved = ServerTreeView(controller)
|
||||||
|
self.recent = ServerTreeView(controller)
|
||||||
|
self.lan = ServerTreeView(controller)
|
||||||
|
|
||||||
|
self.lan.set_query_func(self.query_test)
|
||||||
|
# TODO: set context menu on init
|
||||||
|
self.browser.set_context_menu(ContextMenuGroup.SERVER_BROWSER)
|
||||||
|
self.saved.set_context_menu(ContextMenuGroup.SAVED)
|
||||||
|
self.recent.set_context_menu(ContextMenuGroup.RECENT)
|
||||||
|
self.lan.set_context_menu(ContextMenuGroup.SCAN_LAN)
|
||||||
|
|
||||||
|
# TODO: set model manager for each tab on init
|
||||||
|
# TODO: add set_ and get_model_manager() methods
|
||||||
|
|
||||||
|
tabs = [
|
||||||
|
(self.browser, server_labels.browser, self.controller.get_server_store),
|
||||||
|
(self.saved, server_labels.saved, self.controller.get_saved_store),
|
||||||
|
(self.recent, server_labels.recent, self.controller.get_recent_store),
|
||||||
|
(self.lan, server_labels.lan, self.controller.get_lan_store),
|
||||||
|
]
|
||||||
|
|
||||||
|
for tree, label, func in tabs:
|
||||||
|
store = func()
|
||||||
|
if label == "LAN":
|
||||||
|
pass
|
||||||
|
#store.append(["BAR", "a", "a", "a", 1, 1, 1, "1:1", 0, 0, "a", False])
|
||||||
|
else:
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
|
||||||
|
tree.loaded = True
|
||||||
|
# TODO: set model on init of servertreeview
|
||||||
|
tree.set_model(store)
|
||||||
|
|
||||||
|
scrolled = Gtk.ScrolledWindow()
|
||||||
|
scrolled.add(tree)
|
||||||
|
self.notebook.append_page(scrolled, Gtk.Label(label=label))
|
||||||
|
|
||||||
|
self.add(self.notebook)
|
||||||
|
self.notebook.connect_after("switch-page", self._on_page_changed)
|
||||||
|
self.connect("key-press-event", self._on_keypress)
|
||||||
|
|
||||||
|
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> None:
|
||||||
|
|
||||||
|
match event.keyval:
|
||||||
|
case Gdk.KEY_n:
|
||||||
|
self.notebook.next_page()
|
||||||
|
case Gdk.KEY_p:
|
||||||
|
self.notebook.prev_page()
|
||||||
|
case _:
|
||||||
|
return
|
||||||
|
self.get_active_treeview().grab_focus()
|
||||||
|
|
||||||
|
|
||||||
|
def _on_page_changed(self, notebook: Gtk.Notebook, child: Gtk.Widget, index: int) -> None:
|
||||||
|
# TODO: abstract
|
||||||
|
# TODO: load on first run
|
||||||
|
label = self.notebook.get_tab_label_text(child)
|
||||||
|
if label is None:
|
||||||
|
return
|
||||||
|
# TODO: strings
|
||||||
|
text = label.strip("*")
|
||||||
|
self.notebook.set_tab_label_text(child, text)
|
||||||
|
|
||||||
|
# TODO: strings
|
||||||
|
string = f"Servers > {text}"
|
||||||
|
self.controller.set_crumbs(string)
|
||||||
|
self.tab_cache = string
|
||||||
|
|
||||||
|
child.grab_focus()
|
||||||
|
|
||||||
|
# FIXME: doesn't fire on first run
|
||||||
|
self.controller.update_server_status()
|
||||||
|
self.controller.populate_model()
|
||||||
|
|
||||||
|
# TODO: put in controller
|
||||||
|
def query_test(self) -> None:
|
||||||
|
data = (["BAR", "a", "a", "a", 1, 1, 1, "1:1", 0, 0, "a", False])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_cached_label(self) -> str:
|
||||||
|
return self.tab_cache
|
||||||
|
|
||||||
|
def get_active_treeview(self) -> ServerTreeView:
|
||||||
|
index = self.notebook.get_current_page()
|
||||||
|
scrollable = self.notebook.get_nth_page(index)
|
||||||
|
treeview = scrollable.get_children()[0]
|
||||||
|
return treeview
|
||||||
|
|
||||||
|
def add_notification(self) -> None:
|
||||||
|
saved = self.notebook.get_nth_page(1)
|
||||||
|
if saved is None:
|
||||||
|
return
|
||||||
|
text = self.notebook.get_tab_label_text(saved)
|
||||||
|
if text is None:
|
||||||
|
return
|
||||||
|
# TODO: strings
|
||||||
|
if "*" in text:
|
||||||
|
return
|
||||||
|
text += "*"
|
||||||
|
self.notebook.set_tab_label_text(saved, text)
|
||||||
|
|
||||||
|
def update_tab_widths(self, col: Gtk.TreeViewColumn) -> None:
|
||||||
|
# TODO: may cause pixel offsets when application is maximized
|
||||||
|
width = col.get_width()
|
||||||
|
title = col.get_title()
|
||||||
|
for tab in (self.browser, self.saved, self.recent, self.lan):
|
||||||
|
if tab == self.get_active_treeview():
|
||||||
|
continue
|
||||||
|
for col in tab.get_columns():
|
||||||
|
if col.get_title() == title:
|
||||||
|
self.controller.suppress_signal(tab, col, "_on_col_width_changed", True)
|
||||||
|
col.set_fixed_width(width)
|
||||||
|
self.controller.suppress_signal(tab, col, "_on_col_width_changed", False)
|
||||||
|
|
||||||
|
def get_tabs(self) -> tuple:
|
||||||
|
return (self.browser, self.saved, self.recent, self.lan)
|
||||||
+14
-14
@@ -1,34 +1,34 @@
|
|||||||
from dzgui.util.format import format_pango
|
from dzgui.util.format import format_pango
|
||||||
from dzgui.util.strings import thanks
|
from dzgui.util.strings import thanks
|
||||||
|
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
"""
|
# TODO: wrap and center long values
|
||||||
Special thanks page recognizing contributors to the project in alpha-order
|
|
||||||
"""
|
|
||||||
|
|
||||||
# TODO: make scrollable, test long values/wrapping
|
class Thanks(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||||
|
"""
|
||||||
class Thanks(Gtk.Box):
|
Special thanks page recognizing contributors to the project in alpha-order
|
||||||
|
"""
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__()
|
||||||
orientation=Gtk.Orientation.VERTICAL,
|
|
||||||
spacing=10
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
||||||
label = format_pango(thanks.header)
|
label = format_pango(thanks.header)
|
||||||
header = Gtk.Label()
|
header = Gtk.Label()
|
||||||
header.set_markup(label)
|
header.set_markup(label)
|
||||||
self.add(header)
|
|
||||||
|
|
||||||
description = Gtk.Label(label=thanks.description, justify=Gtk.Justification.CENTER)
|
description = Gtk.Label(label=thanks.description, justify=Gtk.Justification.CENTER)
|
||||||
self.add(description)
|
|
||||||
|
|
||||||
users = [f"- {user}" for user in sorted(thanks.users, key=str.lower)]
|
users = [f"- {user}" for user in sorted(thanks.users, key=str.lower)]
|
||||||
pretty_users = "\n".join(users)
|
pretty_users = "\n".join(users)
|
||||||
body = Gtk.Label()
|
body = Gtk.Label()
|
||||||
body.set_markup(pretty_users)
|
body.set_markup(pretty_users)
|
||||||
self.add(body)
|
|
||||||
|
for el in header, description, body:
|
||||||
|
self.box.add(el)
|
||||||
|
self.add(self.box)
|
||||||
|
|
||||||
|
self.connect("key-press-event", self._on_keypress)
|
||||||
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
from dzgui.const.enum import ContextMenuGroup
|
from dzgui.const.enum import ContextMenuGroup
|
||||||
|
|
||||||
class TreeView(CursorMixin, Gtk.TreeView):
|
class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
||||||
def __init__(self, controller: "Controller", menu: "ContextMenuGroup" = None) -> None:
|
def __init__(self, controller: "Controller", menu: "ContextMenuGroup" = None) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
enable_search=False,
|
enable_search=False,
|
||||||
@@ -67,6 +67,11 @@ class TreeView(CursorMixin, Gtk.TreeView):
|
|||||||
it = self.get_selection().get_selected()[1]
|
it = self.get_selection().get_selected()[1]
|
||||||
return it
|
return it
|
||||||
|
|
||||||
|
def get_focused_row_iter(self) -> Gtk.TreeIter:
|
||||||
|
path = self.get_focused_row_path()
|
||||||
|
model = self.get_model()
|
||||||
|
return model.get_iter(path)
|
||||||
|
|
||||||
def get_focused_row_path(self) -> Gtk.TreePath:
|
def get_focused_row_path(self) -> Gtk.TreePath:
|
||||||
return self.get_cursor().path
|
return self.get_cursor().path
|
||||||
|
|
||||||
@@ -84,10 +89,11 @@ class TreeView(CursorMixin, Gtk.TreeView):
|
|||||||
|
|
||||||
if is_navkey(event.keyval):
|
if is_navkey(event.keyval):
|
||||||
# TODO: investigate this
|
# TODO: investigate this
|
||||||
|
tv = self.controller.get_active_treeview()
|
||||||
if self.sel_blocked is False:
|
if self.sel_blocked is False:
|
||||||
self.controller.suppress_signal(
|
self.controller.suppress_signal(
|
||||||
self.controller.mediator.treeview,
|
tv,
|
||||||
self.controller.mediator.treeview.selected_row,
|
tv.selected_row,
|
||||||
"_on_tree_selection_changed",
|
"_on_tree_selection_changed",
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
@@ -101,6 +107,11 @@ class TreeView(CursorMixin, Gtk.TreeView):
|
|||||||
"""
|
"""
|
||||||
Suppresses spamming on keydown
|
Suppresses spamming on keydown
|
||||||
"""
|
"""
|
||||||
|
#if event.keyval is Gdk.KEY_space:
|
||||||
|
# it = self.get_focused_row_iter()
|
||||||
|
# self.get_selection().select_iter(it)
|
||||||
|
# return True
|
||||||
|
|
||||||
if is_navkey(event.keyval):
|
if is_navkey(event.keyval):
|
||||||
if self.sel_blocked is True:
|
if self.sel_blocked is True:
|
||||||
self.controller.suppress_signal(
|
self.controller.suppress_signal(
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ class LogTreeView(ContextMixin, TreeView):
|
|||||||
super().__init__(controller, menu=ContextMenuGroup.LOG)
|
super().__init__(controller, menu=ContextMenuGroup.LOG)
|
||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
self.controller.register_widget("logtreeview", self)
|
||||||
|
|
||||||
# TODO: maybe put this in init
|
# TODO: maybe put this in init
|
||||||
self.set_fixed_height_mode(True)
|
|
||||||
self.set_headers_visible(True)
|
self.set_headers_visible(True)
|
||||||
|
self.set_fixed_height_mode(True)
|
||||||
self.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
|
self.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
|
||||||
|
|
||||||
model = self.controller.get_log_store()
|
model = self.controller.get_log_store()
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ class MenuTreeView(TreeView):
|
|||||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||||
self.append_column(column)
|
self.append_column(column)
|
||||||
|
|
||||||
row_store = self.controller.get_row_store()
|
help_store = self.controller.get_help_store()
|
||||||
self.set_model(row_store)
|
self.set_model(help_store)
|
||||||
|
|
||||||
self.selected_row = self.get_selection()
|
self.selected_row = self.get_selection()
|
||||||
|
|
||||||
@@ -44,7 +44,6 @@ class MenuTreeView(TreeView):
|
|||||||
column: Gtk.TreeViewColumn
|
column: Gtk.TreeViewColumn
|
||||||
) -> None:
|
) -> None:
|
||||||
row_type = self.get_value_at_index(1)
|
row_type = self.get_value_at_index(1)
|
||||||
print(row_type)
|
|
||||||
|
|
||||||
match row_type:
|
match row_type:
|
||||||
case RowType.THANKS:
|
case RowType.THANKS:
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
|
|||||||
column.set_fixed_width(500)
|
column.set_fixed_width(500)
|
||||||
else:
|
else:
|
||||||
column.set_fixed_width(150)
|
column.set_fixed_width(150)
|
||||||
# hidden color property column
|
# NOTE: hidden color property column
|
||||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||||
column.set_sort_column_id(i)
|
column.set_sort_column_id(i)
|
||||||
if i != 4:
|
if i != 4:
|
||||||
@@ -61,6 +61,13 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
|
|||||||
self.s = self.get_selection().get_selected_rows()
|
self.s = self.get_selection().get_selected_rows()
|
||||||
|
|
||||||
def _on_mods_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
|
def _on_mods_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
|
||||||
|
#if event.keyval is Gdk.KEY_space:
|
||||||
|
# it = self.get_focused_row_iter()
|
||||||
|
# self.get_selection().select_iter(it)
|
||||||
|
# path = self.get_focused_row_path()
|
||||||
|
# self.set_cursor(path)
|
||||||
|
# return False
|
||||||
|
# TODO: parse keys
|
||||||
self.present_menu(widget, event)
|
self.present_menu(widget, event)
|
||||||
|
|
||||||
def _on_mods_button_press(self,
|
def _on_mods_button_press(self,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
+115
-112
@@ -8,6 +8,7 @@ from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
|
|||||||
|
|
||||||
from dzgui.const.enum import (
|
from dzgui.const.enum import (
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
|
ContextMenuGroup,
|
||||||
RowType,
|
RowType,
|
||||||
)
|
)
|
||||||
from dzgui.const.constants import UDP_PORT
|
from dzgui.const.constants import UDP_PORT
|
||||||
@@ -29,6 +30,12 @@ if TYPE_CHECKING:
|
|||||||
# TODO: add multiprocessing queue
|
# TODO: add multiprocessing queue
|
||||||
# TODO: fix cache
|
# TODO: fix cache
|
||||||
|
|
||||||
|
class EnumeratedMenuItem(Gtk.MenuItem):
|
||||||
|
def __init__(self, enum: ContextMenu):
|
||||||
|
super().__init__(label=enum.dict["label"])
|
||||||
|
self.enum = enum
|
||||||
|
|
||||||
|
|
||||||
class ServerTreeView(TreeView):
|
class ServerTreeView(TreeView):
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())
|
"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())
|
||||||
@@ -36,6 +43,13 @@ class ServerTreeView(TreeView):
|
|||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
super().__init__(controller)
|
super().__init__(controller)
|
||||||
|
|
||||||
|
QUEUE_CHECK_DELAY = 200
|
||||||
|
|
||||||
|
self.loaded = False
|
||||||
|
self.query_func: Callable = None
|
||||||
|
|
||||||
|
self.menu = Gtk.Menu()
|
||||||
|
self.menu.connect("key-press-event", self._on_key)
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
self.set_fixed_height_mode(True)
|
self.set_fixed_height_mode(True)
|
||||||
@@ -62,9 +76,6 @@ class ServerTreeView(TreeView):
|
|||||||
column.set_resizable(True)
|
column.set_resizable(True)
|
||||||
column.set_sort_column_id(i)
|
column.set_sort_column_id(i)
|
||||||
|
|
||||||
# TODO: use index of column instead of name
|
|
||||||
# so literal name won't matter
|
|
||||||
# needs conversion logic for old configs
|
|
||||||
if valid_json:
|
if valid_json:
|
||||||
try:
|
try:
|
||||||
saved_size = data["cols"][column_title]
|
saved_size = data["cols"][column_title]
|
||||||
@@ -78,27 +89,62 @@ class ServerTreeView(TreeView):
|
|||||||
if column_title == "Map":
|
if column_title == "Map":
|
||||||
column.set_fixed_width(300)
|
column.set_fixed_width(300)
|
||||||
|
|
||||||
|
column.connect("notify::fixed-width", self._on_col_width_changed)
|
||||||
self.append_column(column)
|
self.append_column(column)
|
||||||
|
|
||||||
# TODO: do not load model on initial init
|
|
||||||
# TODO: row_store = self.controller.get_row_store()
|
|
||||||
# TODO: test values: see below
|
|
||||||
r = Gtk.ListStore(
|
|
||||||
str, str, str, str, int, int, int, str, int, int, str, bool
|
|
||||||
)
|
|
||||||
r.append(["TEST", "a", "a", "a", 0, 0, 0, "a", 0, 0, "a", False])
|
|
||||||
self.set_model(r)
|
|
||||||
|
|
||||||
self.connect("on_distcalc_started", self._on_calclat_started)
|
self.connect("on_distcalc_started", self._on_calclat_started)
|
||||||
|
|
||||||
|
|
||||||
self.connect("button-release-event", self._on_server_button_release)
|
self.connect("button-release-event", self._on_server_button_release)
|
||||||
self.connect("key-press-event", self._on_server_keypress)
|
self.connect("key-press-event", self._on_server_keypress)
|
||||||
|
|
||||||
self.connect("generic_row_activated", self._parent_row_activated)
|
self.connect("generic_row_activated", self._parent_row_activated)
|
||||||
self.connect("generic_treesel_changed", self._parent_selection_changed)
|
self.connect("generic_treesel_changed", self._parent_selection_changed)
|
||||||
|
|
||||||
GLib.timeout_add(200, self._check_result_queue)
|
GLib.timeout_add(QUEUE_CHECK_DELAY, self._check_result_queue)
|
||||||
|
|
||||||
|
def _on_key(self, menu: Gtk.Menu, event: Gdk.EventKey) -> bool | None:
|
||||||
|
if not is_navkey(event.keyval):
|
||||||
|
return False
|
||||||
|
sel = menu.get_selected_item()
|
||||||
|
children = menu.get_children()
|
||||||
|
for i, child in enumerate(children):
|
||||||
|
if sel is child:
|
||||||
|
ind = i
|
||||||
|
break
|
||||||
|
|
||||||
|
match event.keyval:
|
||||||
|
case Gdk.KEY_j:
|
||||||
|
if ind == len(children) - 1:
|
||||||
|
return True
|
||||||
|
menu.select_item(children[ind+1])
|
||||||
|
case Gdk.KEY_k:
|
||||||
|
if ind - 1 < 0:
|
||||||
|
return True
|
||||||
|
menu.select_item(children[ind-1])
|
||||||
|
case Gdk.KEY_g:
|
||||||
|
menu.select_item(children[0])
|
||||||
|
case Gdk.KEY_G:
|
||||||
|
ind = len(children) - 1
|
||||||
|
menu.select_item(children[ind])
|
||||||
|
case _:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_query_func(self, func: Callable) -> None:
|
||||||
|
self.query_func = func
|
||||||
|
|
||||||
|
def get_query_func(self) -> Callable | None:
|
||||||
|
return self.query_func
|
||||||
|
|
||||||
|
def _on_col_width_changed(
|
||||||
|
self, col: Gtk.TreeViewColumn, width: GObject.ParamSpecInt
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Propagate width change to other tabs
|
||||||
|
"""
|
||||||
|
title = col.get_title()
|
||||||
|
size = col.get_width()
|
||||||
|
|
||||||
|
# NOTE: get final width after drag action completes
|
||||||
|
GLib.idle_add(self.controller.propagate_column_width, col)
|
||||||
|
|
||||||
def terminate_process(self) -> None:
|
def terminate_process(self) -> None:
|
||||||
if self.current_proc and self.current_proc.is_alive():
|
if self.current_proc and self.current_proc.is_alive():
|
||||||
@@ -118,7 +164,7 @@ class ServerTreeView(TreeView):
|
|||||||
addr = latest_result[0]
|
addr = latest_result[0]
|
||||||
km = latest_result[1]
|
km = latest_result[1]
|
||||||
cache[addr] = km
|
cache[addr] = km
|
||||||
# TODO: fixme
|
# FIXME
|
||||||
self.statusbar.append_distance(km)
|
self.statusbar.append_distance(km)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -134,37 +180,35 @@ class ServerTreeView(TreeView):
|
|||||||
case Gdk.KEY_r:
|
case Gdk.KEY_r:
|
||||||
self.refresh_player_count()
|
self.refresh_player_count()
|
||||||
case Gdk.KEY_f:
|
case Gdk.KEY_f:
|
||||||
if not AppNav.treeview.is_server_context(AppNav.treeview.view):
|
# TODO: register filter panel instead of mediating thru right panel
|
||||||
return True
|
self.controller.mediator.grid.right_panel.filters_vbox.keyword_entry.grab_focus()
|
||||||
AppNav.right_panel.filters_vbox.keyword_entry.grab_focus()
|
|
||||||
case Gdk.KEY_m:
|
case Gdk.KEY_m:
|
||||||
# FIXME: should no longer be relevant
|
self.controller.mediator.grid.right_panel.filters_vbox.maps_entry.grab_focus()
|
||||||
if AppNav.treeview.view == WindowContext.TABLE_MODS:
|
case Gdk.KEY_i:
|
||||||
return True
|
self.controller.mediator.grid.conpan.entry.grab_focus()
|
||||||
AppNav.right_panel.filters_vbox.maps_entry.grab_focus()
|
|
||||||
else:
|
else:
|
||||||
keyname = Gdk.keyval_name(event.keyval)
|
|
||||||
if keyname.isnumeric() and int(keyname) > 0:
|
|
||||||
digit = int(keyname) - 1
|
|
||||||
AppNav.grid.right_panel.filters_vbox.toggle_check(digit)
|
|
||||||
return False
|
|
||||||
match event.keyval:
|
match event.keyval:
|
||||||
case Gdk.KEY_l | Gdk.KEY_Right:
|
case Gdk.KEY_l | Gdk.KEY_Right:
|
||||||
#if event.state is Gdk.ModifierType.CONTROL_MASK:
|
|
||||||
# return
|
|
||||||
self.controller.mediator.right_panel.focus_button_box()
|
self.controller.mediator.right_panel.focus_button_box()
|
||||||
case Gdk.KEY_0:
|
|
||||||
grid.right_panel.filters_vbox.toggle_check(9)
|
|
||||||
case Gdk.KEY_minus:
|
|
||||||
grid.right_panel.filters_vbox.toggle_check(10)
|
|
||||||
case Gdk.KEY_backslash:
|
|
||||||
grid.right_panel.filters_vbox.toggle_check(11)
|
|
||||||
case _:
|
case _:
|
||||||
return False
|
self.controller.toggle_check(event)
|
||||||
|
|
||||||
|
def set_context_menu(self, items: ContextMenuGroup) -> None:
|
||||||
|
# TODO: if debug is on, add raw command copy to context menu
|
||||||
|
for item in items.value:
|
||||||
|
menu_item = EnumeratedMenuItem(item)
|
||||||
|
menu_item.connect("activate", self._on_menu_click)
|
||||||
|
self.menu.append(menu_item)
|
||||||
|
self.menu.show_all()
|
||||||
|
|
||||||
|
def _on_menu_click(self, item) -> None:
|
||||||
|
print(f"UNIMPLEMENTED: {item.enum}")
|
||||||
|
pass
|
||||||
|
|
||||||
def _on_server_button_release(
|
def _on_server_button_release(
|
||||||
self, widget: Gtk.Widget, event: Gdk.EventButton
|
self, widget: Gtk.Widget, event: Gdk.EventButton
|
||||||
) -> None:
|
) -> None:
|
||||||
|
# TODO: use ContextMixin
|
||||||
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
|
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -179,73 +223,23 @@ class ServerTreeView(TreeView):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.menu = Gtk.Menu()
|
|
||||||
mod_context_items = [ContextMenu.OPEN_WORKSHOP, ContextMenu.DELETE_MOD]
|
mod_context_items = [ContextMenu.OPEN_WORKSHOP, ContextMenu.DELETE_MOD]
|
||||||
# TODO: reimplement server context enums
|
|
||||||
server_context_items = {
|
|
||||||
RowType.SERVER_BROWSER: [
|
|
||||||
ContextMenu.ADD_SERVER,
|
|
||||||
ContextMenu.COPY_NAME,
|
|
||||||
ContextMenu.COPY_CLIPBOARD,
|
|
||||||
ContextMenu.ADD_NOTE,
|
|
||||||
ContextMenu.SHOW_MODS,
|
|
||||||
ContextMenu.SHOW_DETAILS,
|
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
|
||||||
],
|
|
||||||
RowType.SCAN_LAN: [
|
|
||||||
ContextMenu.COPY_NAME,
|
|
||||||
ContextMenu.COPY_CLIPBOARD,
|
|
||||||
ContextMenu.ADD_NOTE,
|
|
||||||
ContextMenu.SHOW_MODS,
|
|
||||||
ContextMenu.SHOW_DETAILS,
|
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
|
||||||
],
|
|
||||||
RowType.SAVED_SERVERS: [
|
|
||||||
ContextMenu.REMOVE_SERVER,
|
|
||||||
ContextMenu.COPY_NAME,
|
|
||||||
ContextMenu.COPY_CLIPBOARD,
|
|
||||||
ContextMenu.ADD_NOTE,
|
|
||||||
ContextMenu.SHOW_MODS,
|
|
||||||
ContextMenu.SHOW_DETAILS,
|
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
|
||||||
],
|
|
||||||
RowType.RECENT_SERVERS: [
|
|
||||||
ContextMenu.ADD_SERVER,
|
|
||||||
ContextMenu.REMOVE_HISTORY,
|
|
||||||
ContextMenu.COPY_NAME,
|
|
||||||
ContextMenu.ADD_NOTE,
|
|
||||||
ContextMenu.COPY_CLIPBOARD,
|
|
||||||
ContextMenu.SHOW_MODS,
|
|
||||||
ContextMenu.SHOW_DETAILS,
|
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: how to get current server context
|
# TODO: dynamic menu entries
|
||||||
if self.view == WindowContext.TABLE_MODS:
|
#for row in items:
|
||||||
items = mod_context_items
|
# if row == ContextMenu.ADD_SERVER:
|
||||||
elif self.subpage in server_context_items:
|
# if self.is_in_favs():
|
||||||
items = server_context_items[self.subpage]
|
# row = ContextMenu.REMOVE_SERVER
|
||||||
else:
|
# item = Gtk.MenuItem(label=row.dict["label"])
|
||||||
return
|
# item.type = row
|
||||||
|
# item.action = row.dict["action"]
|
||||||
for row in items:
|
# self.menu.append(item)
|
||||||
if row == ContextMenu.ADD_SERVER:
|
# if row == ContextMenu.SHOW_MODS:
|
||||||
if self.is_in_favs():
|
# if not self.has_mods():
|
||||||
row = ContextMenu.REMOVE_SERVER
|
# item.set_sensitive(False)
|
||||||
item = Gtk.MenuItem(label=row.dict["label"])
|
# if row == ContextMenu.ADD_NOTE:
|
||||||
item.type = row
|
# if self.get_record_string() in notes_cache:
|
||||||
item.action = row.dict["action"]
|
# item.set_label(strings.edit_note)
|
||||||
item.connect("activate", self._on_menu_click)
|
|
||||||
self.menu.append(item)
|
|
||||||
if row == ContextMenu.SHOW_MODS:
|
|
||||||
if not self.has_mods():
|
|
||||||
item.set_sensitive(False)
|
|
||||||
if row == ContextMenu.ADD_NOTE:
|
|
||||||
if self.get_record_string() in notes_cache:
|
|
||||||
item.set_label(strings.edit_note)
|
|
||||||
|
|
||||||
self.menu.show_all()
|
|
||||||
|
|
||||||
if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l:
|
if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l:
|
||||||
if self.is_selection_empty():
|
if self.is_selection_empty():
|
||||||
@@ -263,25 +257,27 @@ class ServerTreeView(TreeView):
|
|||||||
column: Gtk.TreeViewColumn
|
column: Gtk.TreeViewColumn
|
||||||
) -> None:
|
) -> None:
|
||||||
# TODO: process server connection
|
# TODO: process server connection
|
||||||
|
# TODO: get record
|
||||||
print(self.get_value_at_index(0))
|
print(self.get_value_at_index(0))
|
||||||
|
|
||||||
def _parent_selection_changed(self, base_class: TreeView, sel: Gtk.TreeSelection):
|
def _parent_selection_changed(self, base_class: TreeView, sel: Gtk.TreeSelection):
|
||||||
print(self.get_value_at_index(0))
|
|
||||||
|
|
||||||
self.terminate_process()
|
self.terminate_process()
|
||||||
record = self.get_record()
|
record = self.get_record()
|
||||||
print(record)
|
|
||||||
|
|
||||||
# TODO: ?
|
if record is None:
|
||||||
if not record:
|
|
||||||
grid.statusbar.update_server_meta()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
ip = record.ip
|
#model = self.get_model()
|
||||||
|
#if record is None:
|
||||||
|
# self.controller.update_server_status(model)
|
||||||
|
# return
|
||||||
|
|
||||||
self.emit("on_distcalc_started")
|
ip = record.ip
|
||||||
self.current_proc = CalcDist(self, record.ip, self.queue, cache)
|
# TODO
|
||||||
self.current_proc.start()
|
#self.emit("on_distcalc_started")
|
||||||
|
#self.current_proc = CalcDist(self, record.ip, self.queue, cache)
|
||||||
|
#self.current_proc.start()
|
||||||
|
|
||||||
def get_record_string(self) -> str:
|
def get_record_string(self) -> str:
|
||||||
addr = self.get_value_at_index(7)
|
addr = self.get_value_at_index(7)
|
||||||
@@ -289,6 +285,7 @@ class ServerTreeView(TreeView):
|
|||||||
return f"{addr}:{qport}"
|
return f"{addr}:{qport}"
|
||||||
|
|
||||||
def get_record(self) -> dict | None:
|
def get_record(self) -> dict | None:
|
||||||
|
# TODO: delegate to controller
|
||||||
select = self.get_selection()
|
select = self.get_selection()
|
||||||
sels = select.get_selected_rows()
|
sels = select.get_selected_rows()
|
||||||
(model, pathlist) = sels
|
(model, pathlist) = sels
|
||||||
@@ -303,3 +300,9 @@ class ServerTreeView(TreeView):
|
|||||||
ip = addr.split(":")[0]
|
ip = addr.split(":")[0]
|
||||||
gameport = int(addr.split(":")[1])
|
gameport = int(addr.split(":")[1])
|
||||||
return Record(ip, gameport, qport)
|
return Record(ip, gameport, qport)
|
||||||
|
|
||||||
|
def get_loaded(self) -> bool:
|
||||||
|
return self.loaded
|
||||||
|
|
||||||
|
def set_loaded(self, status: bool) -> None:
|
||||||
|
self.loaded = status
|
||||||
Reference in new issue
Block a user