mirror of
https://github.com/aclist/dztui.git
synced 2026-11-10 20:10:38 +01:00
Compare commits
123
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63316a01a9 | ||
|
|
0729273503 | ||
|
|
91d164d666 | ||
|
|
0d7f022c43 | ||
|
|
f76c400697 | ||
|
|
7ae5880890 | ||
|
|
afbced228d | ||
|
|
f5d7bf713d | ||
|
|
c1630adee1 | ||
|
|
88f6b84bea | ||
|
|
134dcb6f0b | ||
|
|
cc0354c881 | ||
|
|
f2d21be347 | ||
|
|
f65fa153cc | ||
|
|
8aa7328d8f | ||
|
|
bb04854cd7 | ||
|
|
5014b2a467 | ||
|
|
d020295aa3 | ||
|
|
1879347831 | ||
|
|
5a1c098af7 | ||
|
|
3d346dfb07 | ||
|
|
cb03db6032 | ||
|
|
c69022f74c | ||
|
|
7f0bccad64 | ||
|
|
d6b5c7bbb4 | ||
|
|
f0a07cbbba | ||
|
|
30fa762780 | ||
|
|
807a530550 | ||
|
|
f092cfdf4a | ||
|
|
d5c597ab03 | ||
|
|
06c85fdfd8 | ||
|
|
9b948271e1 | ||
|
|
7322970c54 | ||
|
|
504a2a724c | ||
|
|
c66511b005 | ||
|
|
0c4026daf8 | ||
|
|
e03c6905c6 | ||
|
|
8fb3462f35 | ||
|
|
bfe9b9d147 | ||
|
|
9f36569fd4 | ||
|
|
da18b41136 | ||
|
|
f092207c61 | ||
|
|
c94e4e73db | ||
|
|
8da264686a | ||
|
|
598973104c | ||
|
|
15d4b71350 | ||
|
|
16d3df618a | ||
|
|
4e5fadf928 | ||
|
|
3dc37561be | ||
|
|
13786591b3 | ||
|
|
9eb1342877 | ||
|
|
3f29484895 | ||
|
|
22dd8fc67f | ||
|
|
fa2f86b03e | ||
|
|
c8fe6ec056 | ||
|
|
6901e5456e | ||
|
|
49298d9943 | ||
|
|
72e3d95808 | ||
|
|
a42c9d4389 | ||
|
|
cc708c64b2 | ||
|
|
03fa707305 | ||
|
|
2479cbd41b | ||
|
|
370edbe01b | ||
|
|
0c41654645 | ||
|
|
903f8fc31d | ||
|
|
6b583f68d6 | ||
|
|
3b3ccd0de6 | ||
|
|
70aacaa866 | ||
|
|
7582ab4d3b | ||
|
|
225a2a148e | ||
|
|
9117655ebd | ||
|
|
fdf92dca40 | ||
|
|
a97766855d | ||
|
|
3097701e98 | ||
|
|
895e08d228 | ||
|
|
08a6f698a5 | ||
|
|
78be8c93b7 | ||
|
|
8a2889d15d | ||
|
|
1a33f71716 | ||
|
|
ae13dc9a89 | ||
|
|
d5706f969c | ||
|
|
790e34b20c | ||
|
|
35230ae03f | ||
|
|
596d3a59ac | ||
|
|
e7cef215c6 | ||
|
|
12daf041ff | ||
|
|
90ef6cbdbb | ||
|
|
2cc26b6573 | ||
|
|
0ec1c157f4 | ||
|
|
0cf6439538 | ||
|
|
e3c1ef59ee | ||
|
|
78144e9127 | ||
|
|
33b8dfba70 | ||
|
|
abf0060cac | ||
|
|
95daa9163b | ||
|
|
ff068e5396 | ||
|
|
80680b86b0 | ||
|
|
a4ac357c8f | ||
|
|
728b8b8242 | ||
|
|
3b51392d39 | ||
|
|
159e2da355 | ||
|
|
b83a333cea | ||
|
|
46117ecdda | ||
|
|
30119fec46 | ||
|
|
ac01818549 | ||
|
|
d486712a4c | ||
|
|
d116702145 | ||
|
|
5bee3614bd | ||
|
|
c73b07991f | ||
|
|
199cd07982 | ||
|
|
e3d62c6749 | ||
|
|
5134bf02fb | ||
|
|
f97be9352d | ||
|
|
b092a809f4 | ||
|
|
2cc7710807 | ||
|
|
5fb10d9758 | ||
|
|
100cb43afc | ||
|
|
7e2a585812 | ||
|
|
c1181435b3 | ||
|
|
22a7814ce4 | ||
|
|
b030da5c65 | ||
|
|
3c0acdd068 | ||
|
|
141f96fb9f |
No files matched your search
@@ -53,6 +53,10 @@
|
|||||||
- Expand/collapse changelog versions
|
- Expand/collapse changelog versions
|
||||||
- Support local light/dark mode changes on system theme
|
- Support local light/dark mode changes on system theme
|
||||||
- Server time calculator
|
- Server time calculator
|
||||||
|
- Add "Unmodded" filter to servers
|
||||||
|
- List timestamp of last DayZ client update
|
||||||
|
- Validate mods via Mod page to pull in updates
|
||||||
|
- Support for Nasdara (Badlands) DLC
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Conform to PEP 440 versioning for beta versions
|
- Conform to PEP 440 versioning for beta versions
|
||||||
@@ -74,6 +78,7 @@
|
|||||||
- Enable LAN page Empty/Full filters on first run of app
|
- Enable LAN page Empty/Full filters on first run of app
|
||||||
- Propagate subscribed mods to Steam client
|
- Propagate subscribed mods to Steam client
|
||||||
- Make j/k keys in context menus follow same wraparound logic as down arrow/up arrow
|
- Make j/k keys in context menus follow same wraparound logic as down arrow/up arrow
|
||||||
|
- Use SQLite DB for geolocation records
|
||||||
|
|
||||||
### Dropped
|
### Dropped
|
||||||
- Battlemetrics API support
|
- Battlemetrics API support
|
||||||
@@ -92,6 +97,8 @@
|
|||||||
- Moved dialogs out of threads
|
- Moved dialogs out of threads
|
||||||
- History table not being in lock-step with state file
|
- History table not being in lock-step with state file
|
||||||
- Current keyword filter not being applied when refreshing table
|
- Current keyword filter not being applied when refreshing table
|
||||||
|
- Steam Flatpak cannot run DZGUI as a non-Steam game
|
||||||
|
- Collisions between native Steam and Steam Flatpak clients
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
- Load offline mods
|
- Load offline mods
|
||||||
|
|||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
# History of the project
|
||||||
|
This file collects notable milestones and events related to DZGUI, mainly around DZGUI as a framework and the history of development.
|
||||||
|
A chronological summary of features can be found in CHANGELOG.md.
|
||||||
|
|
||||||
|
## 2021
|
||||||
|
### 2021-12-03: DayZ adds support for Battleye over Proton
|
||||||
|
DayZ was among a handful of games with whitelisted anti-cheat support under Valve's emerging WINE-based Proton compatibility layer.
|
||||||
|
While adding anti-cheat compatibility at a technical level, this change did not actually allow the client to connect to servers running complex modlists.
|
||||||
|
|
||||||
|
Development of the precursor to DZGUI began at around this time.
|
||||||
|
|
||||||
|
### 2021-12-12: initial version of DZTUI is created
|
||||||
|
DZTUI, a Bash script and terminal user interface, was the precursor to DZGUI.
|
||||||
|
This was an initial attempt at bootstrapping the Steam client to handle mods correctly when launching DayZ.
|
||||||
|
Due to user interest, features continued to be added. This early tool is archived under the 'dztui' branch.
|
||||||
|
|
||||||
|
## 2022
|
||||||
|
### 2022-05-09: addition of zenity GUI and project name change (DZGUI)
|
||||||
|
With the introduction of the Steam Deck, more users wanted a graphical user interface
|
||||||
|
to interact with DZTUI. Being that DZTUI was purely a Bash script, a method of
|
||||||
|
drawing menus was needed, so the "zenity" package was used to create prefabricated dialogs.
|
||||||
|
While this met the minimum requirement to show prompts and selectable options, no rich customization was possible.
|
||||||
|
|
||||||
|
The introduction of zenity as a dependency marked the shift from "DZTUI" to "DZGUI".
|
||||||
|
|
||||||
|
Throughout 2022, the limits of zenity for DZGUI's purposes were tested, particularly in the context of SteamOS.
|
||||||
|
This led to the discovery of a number of bugs in zenity, GTK, and SteamOS, which were reported up to their respective channels.
|
||||||
|
While this was improving the SteamOS user experience, it started to become clear that a fully custom UI was needed.
|
||||||
|
|
||||||
|
This barebones version of DZGUI sufficed for some time, with the Bash script and some standalone Python modules doing the heavy lifting,
|
||||||
|
and this period served to surface a number of bugs and interesting interactions with the Steam client.
|
||||||
|
|
||||||
|
## 2023
|
||||||
|
### 2023-11-23: Python 3.10 dependency
|
||||||
|
Prior to this time, DZGUI open-endedly allowed you to use any Python version for its modules, without any significant handling for
|
||||||
|
the idiosyncrasies of different versions. A minimum requirement of Python 3.10 was thus instituted to normalize expected behavior.
|
||||||
|
|
||||||
|
Throughout 2023, various features related to server browsing were added, such as filters and more granular search parameters.
|
||||||
|
|
||||||
|
There were still a lot of unknowns at this time, so things were being resolved through trial and error.
|
||||||
|
|
||||||
|
## 2024
|
||||||
|
### 2024-01-31: DZGUI 5 API introduces GTK UI
|
||||||
|
Using zenity as a simple GUI had exposed some bugs and limitations in that package, particularly when trying to push huge lists of server records
|
||||||
|
into tables and sorting them in a performant manner.
|
||||||
|
|
||||||
|
Around this time it became clear that a more robust UI framework was needed. While keeping the original Bash script and Python modules for
|
||||||
|
background processing, PyGObject (GTK) was added to the project in order to ship a custom UI that could be designed from the ground up.
|
||||||
|
|
||||||
|
However, it should be noted that at this point, the DZGUI logic itself was still a (increasingly large) Bash script to which a GTK UI had been stitched on.
|
||||||
|
These were totally separate components, with UI actions on the GTK side triggering calls to Bash functions in an overly complex helper file.
|
||||||
|
The UI was basically a thin client around the old Bash logic, but this was still a far cry from an integrated application with proper signals and dedicated components.
|
||||||
|
|
||||||
|
Effectively, DZGUI 5 through 6 were a stepping stone that set the groundwork to go from DZGUI 4 (pure Bash script) to
|
||||||
|
DZGUI 7 (standalone Python application). Today, little of DZGUI 5's original logic remains outside of the basic UI grid.
|
||||||
|
|
||||||
|
### 2024-10-15: Frostline DLC released, widespread parsing problems
|
||||||
|
DayZ's first DLC introduced a new format for server responses that packed extra metadata into the A2S_RULES format.
|
||||||
|
Upon its release, all third-party DayZ launchers and server listing web sites were unable to parse the mod information being returned from Frostline servers.
|
||||||
|
|
||||||
|
For a period of two weeks, this remained unsolved and various attempts were made to understand the updated specification.
|
||||||
|
|
||||||
|
### 2024-10-30: DZGUI hotfix adds support for Frostline servers
|
||||||
|
DZGUI was the first third-party tool to fix parsing for Frostline servers and restore functionality.
|
||||||
|
This was a small milestone, because it meant DZGUI was evolving from a "dumb" launcher to a featureful tool
|
||||||
|
touching upon and improving all aspects of the end-to-end DayZ experience.
|
||||||
|
|
||||||
|
Particularly around this time, the sheer diversity of needs of modders, server operators, and players started to become clear.
|
||||||
|
Users had highly specific and diverse use cases for setting up their game environment, and requested a number of features to support that on
|
||||||
|
the DZGUI side.
|
||||||
|
|
||||||
|
## 2025
|
||||||
|
### 2025-01-17: DZGUI 6 API
|
||||||
|
With DZGUI 6, the difficult decision to enforce a Python 3.13 dependency was made.
|
||||||
|
While the previous dependency was Python 3.10+, it was deemed better to develop against and target identical versions.
|
||||||
|
|
||||||
|
However, due to the vagaries of managing Python environments on end-user systems, this change added unnecessary complexity and frustration.
|
||||||
|
Users had to properly set up virtual environments, install the relevant dependencies, and create a wrapper script to run this version, whereas
|
||||||
|
previous versions had essentially been point and click.
|
||||||
|
|
||||||
|
### 2025-10-23: DZGUI 7 API and the "Ship of Theseus"
|
||||||
|
It had become clear that the situation above was untenable and the legacy Bash script was unmaintainable given the project's enlarged scope.
|
||||||
|
The lack of a robust testing or typehinting framework made it difficult to diagnose rare bugs.
|
||||||
|
|
||||||
|
Shipping a standalone, turnkey application with zero or minimal dependencies was the most urgent priority.
|
||||||
|
DZGUI was still carrying around small bits and pieces of the old Bash script logic, so the decision was made to
|
||||||
|
refactor the application into a pure Python + GTK tool. This would effectively be a total rewrite of all logic,
|
||||||
|
only preserving the most minimal skeleton of the original UI layout as a reference.
|
||||||
|
|
||||||
|
Over the course of a year, a wholesale "Ship of Theseus" rewrite took place.
|
||||||
|
Nearly every component of DZGUI 6, both API and UI side, was taken apart and replaced.
|
||||||
|
|
||||||
|
A soft alpha and beta phase were used to iterate on this concept further. DZGUI 7 was increasingly recommended to users
|
||||||
|
as the preferred choice. This allowed for keeping the established userbase on the (relatively) stable DZGUI 6 spec, while building up
|
||||||
|
the new spec and making it available to more users as features were elaborated.
|
||||||
|
|
||||||
|
Through late 2025, several LLM-generated applications that recycled significant parts of DZGUI's code without the applicable GPL license were found.
|
||||||
|
The maintainers of those projects were advised to excise the offending segments or apply the necessary GPL licenses.
|
||||||
|
|
||||||
|
## 2026
|
||||||
|
### 2026-05-17: Switch to standalone releases
|
||||||
|
The DZGUI 7 beta starts shipping standalone glibc builds with an integrated setup wizard and installer.
|
||||||
|
|
||||||
|
DZGUI 6 had relied on "over the air" updates delivered via either a "stable" or "testing" branch
|
||||||
|
(archived respectively under the "dzgui" and "testing" branches). Effectively, the script would bootstrap itself and
|
||||||
|
fetch raw files directly from the repository. This was prone to problems because updates were monolithic, not giving
|
||||||
|
an easy escape hatch if the user needed to rollback in the event of a regression.
|
||||||
|
|
||||||
|
This was changed to atomic, tagged releases. In addition to updating directly through DZGUI, these prebuilt releases can be
|
||||||
|
downloaded manually as needed, and rollbacks are available for all prior versions under the DZGUI 7 specification.
|
||||||
|
|
||||||
|
### 2026-10-27: 500 tickets closed
|
||||||
|
The milestone of 500 tickets/PRs being closed was reached.
|
||||||
|
|
||||||
|
2026 was by far DZGUI's most intensive year by commits, perhaps suggesting the scope of the rewrite:
|
||||||
|
|
||||||
|
- 2021: 16
|
||||||
|
- 2022: 434
|
||||||
|
- 2023: 322
|
||||||
|
- 2024: 347
|
||||||
|
- 2025: 426
|
||||||
|
- 2026: 1300
|
||||||
|
|
||||||
|
DZGUI 7 is scheduled to release as a stable version within the year.
|
||||||
@@ -17,7 +17,7 @@ DZGUI follows a specific set of guidelines, enumerated below:
|
|||||||
- Provide a free and open-source tool under GPL
|
- Provide a free and open-source tool under GPL
|
||||||
- Do not use undocumented, third-party servers to aggregate data; no central "DZGUI" server
|
- Do not use undocumented, third-party servers to aggregate data; no central "DZGUI" server
|
||||||
- All connections go to Steam and DayZ servers directly; the tool should function in perpetuity without intermediary endpoints
|
- All connections go to Steam and DayZ servers directly; the tool should function in perpetuity without intermediary endpoints
|
||||||
- Target Linux desktop and Steam deck specifically; not a cross-platform tool
|
- Target Linux desktop and Steam Deck specifically; not a cross-platform tool
|
||||||
|
|
||||||
## Setup and usage
|
## Setup and usage
|
||||||
|
|
||||||
@@ -37,6 +37,8 @@ Users wishing to review the licenses to these components can inspect the `LICENS
|
|||||||
|
|
||||||
Steam and the Steam logo are trademarks and/or registered trademarks of Valve Corporation in the U.S. and/or other countries.
|
Steam and the Steam logo are trademarks and/or registered trademarks of Valve Corporation in the U.S. and/or other countries.
|
||||||
|
|
||||||
|
DayZ is a trademark and brand of Bohemia Interactive a.s.
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
DZGUI is beta-quality software and is provided as-is.
|
DZGUI is beta-quality software and is provided as-is.
|
||||||
+30
-4
@@ -1,15 +1,18 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, TYPE_CHECKING
|
||||||
from warnings import deprecated
|
from warnings import deprecated
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
|
|
||||||
class ACF:
|
class ACF:
|
||||||
def __init__(self, file: Path) -> None:
|
def __init__(self, file: Path) -> None:
|
||||||
@@ -42,7 +45,7 @@ class ACF:
|
|||||||
def dequote(cls, s: str) -> str:
|
def dequote(cls, s: str) -> str:
|
||||||
return s.rstrip('"').lstrip('"')
|
return s.rstrip('"').lstrip('"')
|
||||||
|
|
||||||
def parse(self, lines: Iterator[list[str]]) -> dict[str, str]:
|
def parse(self, lines: "Iterator[list[str]]") -> dict[str, str]:
|
||||||
acf: dict[str, Any] = {}
|
acf: dict[str, Any] = {}
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
@@ -69,6 +72,28 @@ class ACF:
|
|||||||
except StopIteration:
|
except StopIteration:
|
||||||
return acf
|
return acf
|
||||||
|
|
||||||
|
def search_key(self, key: str) -> str:
|
||||||
|
return str(self.dict["AppState"][key])
|
||||||
|
|
||||||
|
def date_to_str(self, epoch: int) -> str | None:
|
||||||
|
if epoch == 0:
|
||||||
|
return None
|
||||||
|
return datetime.fromtimestamp(epoch).isoformat(" ")
|
||||||
|
|
||||||
|
def get_date_by_key(self, key: str) -> str | None:
|
||||||
|
try:
|
||||||
|
stamp = int(self.search_key(key))
|
||||||
|
return self.date_to_str(stamp)
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_last_updated(self) -> str | None:
|
||||||
|
return self.get_date_by_key("LastUpdated")
|
||||||
|
|
||||||
|
def get_last_played(self) -> str | None:
|
||||||
|
return self.get_date_by_key("LastPlayed")
|
||||||
|
|
||||||
def get_allows_downloads(self) -> int | None:
|
def get_allows_downloads(self) -> int | None:
|
||||||
try:
|
try:
|
||||||
# TODO: consider coercing strs to ints on initial import
|
# TODO: consider coercing strs to ints on initial import
|
||||||
@@ -86,7 +111,8 @@ class WorkshopACF(ACF):
|
|||||||
def __init__(self, file: Path) -> None:
|
def __init__(self, file: Path) -> None:
|
||||||
super().__init__(file)
|
super().__init__(file)
|
||||||
|
|
||||||
def unpack(self, d: dict, lines: list[Any] = []) -> str:
|
def unpack(self, d: dict[str, Any], lines: list[Any] = []) -> str:
|
||||||
|
self.indent: int
|
||||||
t1 = "AppWorkshop"
|
t1 = "AppWorkshop"
|
||||||
t2 = ("WorkshopItemsInstalled", "WorkshopItemDetails")
|
t2 = ("WorkshopItemsInstalled", "WorkshopItemDetails")
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
|
|||||||
+1
-13
@@ -24,14 +24,6 @@ from typing import Any
|
|||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ModMeta:
|
|
||||||
protocol: str
|
|
||||||
published_id: str
|
|
||||||
name: str
|
|
||||||
timestamp: str
|
|
||||||
|
|
||||||
|
|
||||||
def get_local_mod_ids(steam_path: Path) -> list[int]:
|
def get_local_mod_ids(steam_path: Path) -> list[int]:
|
||||||
workshop_path = get_local_mod_path(steam_path)
|
workshop_path = get_local_mod_path(steam_path)
|
||||||
mods = get_local_mods(workshop_path)
|
mods = get_local_mods(workshop_path)
|
||||||
@@ -114,10 +106,6 @@ def get_delimited_mods(steam_path: Path) -> list[Any]:
|
|||||||
return parse_mods(mods)
|
return parse_mods(mods)
|
||||||
|
|
||||||
|
|
||||||
def get_missing_mods(local: list, remote: list) -> list:
|
|
||||||
return [mod for mod in remote if mod not in local]
|
|
||||||
|
|
||||||
|
|
||||||
def _hash(uid: str, use_custom: bool = False) -> str:
|
def _hash(uid: str, use_custom: bool = False) -> str:
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
md5.update(uid.encode("ascii"))
|
md5.update(uid.encode("ascii"))
|
||||||
@@ -152,7 +140,7 @@ def remove_stale_signatures(
|
|||||||
|
|
||||||
|
|
||||||
def find_stale_mods(config: Path) -> list[int]:
|
def find_stale_mods(config: Path) -> list[int]:
|
||||||
def push_record(rec: str) -> list:
|
def push_record(rec: str) -> list[int]:
|
||||||
record = fqip_to_record(rec)
|
record = fqip_to_record(rec)
|
||||||
if record is None:
|
if record is None:
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from typing import Any, Optional, TYPE_CHECKING, Union
|
|||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, REQUEST_TIMEOUT
|
from dzgui.const.constants import APP_NAME, REQUEST_TIMEOUT
|
||||||
from dzgui.const.endpoints import STEAM_SERVERS
|
from dzgui.const.endpoints import STEAM_SERVERS
|
||||||
|
from dzgui.model.proxy_model import ServerRow
|
||||||
from dzgui.util.format import format_exception
|
from dzgui.util.format import format_exception
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ def get_netmask() -> str:
|
|||||||
|
|
||||||
|
|
||||||
# TODO: rename
|
# TODO: rename
|
||||||
def test_ip(suffix: int, port: int, event: threading.Event) -> dict | None:
|
def test_ip(suffix: int, port: int, event: threading.Event) -> dict[str, Any] | None:
|
||||||
if event.is_set():
|
if event.is_set():
|
||||||
return None
|
return None
|
||||||
netmask = get_netmask()
|
netmask = get_netmask()
|
||||||
@@ -139,7 +140,9 @@ def sanitize(name: str) -> str:
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
def parse_json(json: list) -> list:
|
def parse_json(
|
||||||
|
json: list[dict[str, Any]],
|
||||||
|
) -> list[ServerRow]:
|
||||||
"""
|
"""
|
||||||
Server metadata is underspecified and server operators
|
Server metadata is underspecified and server operators
|
||||||
tend to insert random garbage in the headers. In case
|
tend to insert random garbage in the headers. In case
|
||||||
@@ -224,7 +227,7 @@ def parse_json(json: list) -> list:
|
|||||||
mapname = row["map"].lower()
|
mapname = row["map"].lower()
|
||||||
players = row["players"]
|
players = row["players"]
|
||||||
max_players = row["max_players"]
|
max_players = row["max_players"]
|
||||||
raw = (
|
raw = ServerRow(
|
||||||
name,
|
name,
|
||||||
mapname,
|
mapname,
|
||||||
view,
|
view,
|
||||||
@@ -507,14 +510,14 @@ def record_to_fqip(record: Record) -> str:
|
|||||||
return f"{record.ip}:{record.gameport}:{record.qport}"
|
return f"{record.ip}:{record.gameport}:{record.qport}"
|
||||||
|
|
||||||
|
|
||||||
def response_to_fqip(res: dict) -> str:
|
def response_to_fqip(res: dict[str, Any]) -> str:
|
||||||
ip = res["addr"].split(":")[0]
|
ip = res["addr"].split(":")[0]
|
||||||
gameport = res["gameport"]
|
gameport = res["gameport"]
|
||||||
qport = res["addr"].split(":")[1]
|
qport = res["addr"].split(":")[1]
|
||||||
return f"{ip}:{gameport}:{qport}"
|
return f"{ip}:{gameport}:{qport}"
|
||||||
|
|
||||||
|
|
||||||
def response_to_record(res: dict) -> Record:
|
def response_to_record(res: dict[str, Any]) -> Record:
|
||||||
ip = res["addr"].split(":")[0]
|
ip = res["addr"].split(":")[0]
|
||||||
gameport = res["gameport"]
|
gameport = res["gameport"]
|
||||||
qport = res["addr"].split(":")[1]
|
qport = res["addr"].split(":")[1]
|
||||||
|
|||||||
@@ -173,11 +173,12 @@ class Shortcuts:
|
|||||||
self.save_shortcuts()
|
self.save_shortcuts()
|
||||||
|
|
||||||
def save_shortcuts(self) -> None:
|
def save_shortcuts(self) -> None:
|
||||||
try:
|
if self.shortcuts_path.is_file():
|
||||||
backup = self.shortcuts_path.with_suffix(".vdf.bak")
|
try:
|
||||||
shutil.copy(self.shortcuts_path, backup)
|
backup = self.shortcuts_path.with_suffix(".vdf.bak")
|
||||||
except Exception as e:
|
shutil.copy(self.shortcuts_path, backup)
|
||||||
logger.critical(e)
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
try:
|
try:
|
||||||
with open(self.shortcuts_path, "wb") as f:
|
with open(self.shortcuts_path, "wb") as f:
|
||||||
vdf.binary_dump(self.shortcuts, f)
|
vdf.binary_dump(self.shortcuts, f)
|
||||||
|
|||||||
+39
-14
@@ -12,8 +12,9 @@ from typing import Any, Generator, Union
|
|||||||
from warnings import deprecated
|
from warnings import deprecated
|
||||||
|
|
||||||
from dzgui.api.acf import ACF
|
from dzgui.api.acf import ACF
|
||||||
|
from dzgui.const.endpoints import VALIDATE
|
||||||
from dzgui.init.prereqs import has_any_client
|
from dzgui.init.prereqs import has_any_client
|
||||||
from dzgui.init.proc import has_cmd
|
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
APPID_DAYZ,
|
APPID_DAYZ,
|
||||||
APPID_DAYZ_EXP,
|
APPID_DAYZ_EXP,
|
||||||
@@ -23,19 +24,17 @@ from dzgui.const.constants import (
|
|||||||
DEBIAN_STEAM_PATH,
|
DEBIAN_STEAM_PATH,
|
||||||
DEFAULT_STEAM_PATH,
|
DEFAULT_STEAM_PATH,
|
||||||
DOWNLOADS_PATH,
|
DOWNLOADS_PATH,
|
||||||
FLATPAK_CMD,
|
|
||||||
FLATPAK_STEAM_PATH,
|
FLATPAK_STEAM_PATH,
|
||||||
LIBRARYFOLDERS_PATH,
|
LIBRARYFOLDERS_PATH,
|
||||||
REQUEST_TIMEOUT,
|
REQUEST_TIMEOUT,
|
||||||
STEAM_CMD,
|
|
||||||
UBUNTU_STEAM_PATH,
|
UBUNTU_STEAM_PATH,
|
||||||
VDF_PATH,
|
VDF_PATH,
|
||||||
)
|
)
|
||||||
from dzgui.const.endpoints import (
|
from dzgui.const.endpoints import (
|
||||||
APP_DETAILS,
|
STEAM_APP_DETAILS,
|
||||||
SUB_ENDPOINT,
|
|
||||||
STEAM_PUBLISHED_FILES,
|
STEAM_PUBLISHED_FILES,
|
||||||
UNSUB_ENDPOINT,
|
STEAM_SUB,
|
||||||
|
STEAM_UNSUB,
|
||||||
)
|
)
|
||||||
from dzgui.strings import wizard
|
from dzgui.strings import wizard
|
||||||
from dzgui.strings.errors import api_validation_error, workshop_endpoint_error
|
from dzgui.strings.errors import api_validation_error, workshop_endpoint_error
|
||||||
@@ -85,9 +84,9 @@ def get_steam_paths() -> list[tuple[Path, str]]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
for path in DEFAULT_PATH, FLATPAK_PATH, UBUNTU_PATH, DEBIAN_PATH:
|
for path in DEFAULT_PATH, FLATPAK_PATH, UBUNTU_PATH, DEBIAN_PATH:
|
||||||
if path is FLATPAK_PATH and has_cmd(FLATPAK_CMD) is False:
|
if path is FLATPAK_PATH and has_flatpak_client() is False:
|
||||||
continue
|
continue
|
||||||
if path is not FLATPAK_PATH and has_cmd(STEAM_CMD) is False:
|
if path is not FLATPAK_PATH and has_steam_client() is False:
|
||||||
continue
|
continue
|
||||||
if path.joinpath(VDF_PATH).is_file():
|
if path.joinpath(VDF_PATH).is_file():
|
||||||
paths.append((path, human[path]))
|
paths.append((path, human[path]))
|
||||||
@@ -311,17 +310,16 @@ def update_workshop(key: str, mod: int, endpoint: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def subscribe(key: str, mod: int) -> None:
|
def subscribe(key: str, mod: int) -> None:
|
||||||
update_workshop(key, mod, SUB_ENDPOINT)
|
update_workshop(key, mod, STEAM_SUB)
|
||||||
|
|
||||||
|
|
||||||
def unsubscribe(key: str, mod: int) -> None:
|
def unsubscribe(key: str, mod: int) -> None:
|
||||||
update_workshop(key, mod, UNSUB_ENDPOINT)
|
update_workshop(key, mod, STEAM_UNSUB)
|
||||||
|
|
||||||
|
|
||||||
@deprecated("Use subscribe()")
|
def validate_mod(client: str, mod: str) -> None:
|
||||||
def enqueue_mod(client: str, mod: str, appid: int) -> None:
|
|
||||||
client_args = concat_bash_args(client)
|
client_args = concat_bash_args(client)
|
||||||
subprocess.Popen([*client_args, "+workshop_download_item", str(appid), mod])
|
subprocess.Popen([*client_args, "+workshop_download_item", str(APPID_DAYZ), mod])
|
||||||
|
|
||||||
|
|
||||||
@deprecated("Cf. https://github.com/ValveSoftware/steam-for-linux/issues/9672")
|
@deprecated("Cf. https://github.com/ValveSoftware/steam-for-linux/issues/9672")
|
||||||
@@ -391,6 +389,26 @@ def get_running_app() -> int | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_updated(path: Path, appid: int) -> str | None:
|
||||||
|
root_path = get_app_path(path, appid)
|
||||||
|
acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf")
|
||||||
|
try:
|
||||||
|
return ACF(acf).get_last_updated()
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_played(path: Path, appid: int) -> str | None:
|
||||||
|
root_path = get_app_path(path, appid)
|
||||||
|
acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf")
|
||||||
|
try:
|
||||||
|
return ACF(acf).get_last_played()
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_app_allows_downloads(path: Path, appid: int) -> bool:
|
def get_app_allows_downloads(path: Path, appid: int) -> bool:
|
||||||
try:
|
try:
|
||||||
root_path = get_app_path(path, appid)
|
root_path = get_app_path(path, appid)
|
||||||
@@ -494,7 +512,7 @@ def get_app_path(folders_path: Path, appid: int) -> Path:
|
|||||||
|
|
||||||
def get_app_name(appid: int) -> str | None:
|
def get_app_name(appid: int) -> str | None:
|
||||||
payload = {"appids": [appid]}
|
payload = {"appids": [appid]}
|
||||||
res = requests.get(APP_DETAILS, params=payload)
|
res = requests.get(STEAM_APP_DETAILS, params=payload)
|
||||||
if res.status_code != 200:
|
if res.status_code != 200:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@@ -502,3 +520,10 @@ def get_app_name(appid: int) -> str | None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(e)
|
logger.debug(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("Currently unused")
|
||||||
|
def validate_dayz(client: str) -> None:
|
||||||
|
client_args = concat_bash_args(client)
|
||||||
|
uri = VALIDATE + str(APPID_DAYZ)
|
||||||
|
subprocess.Popen([*client_args, uri])
|
||||||
+11
-11
@@ -7,7 +7,12 @@ from pathlib import Path
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, FLATPAK_ROOT_PATH, ICONS_PATH
|
from dzgui.const.constants import (
|
||||||
|
APP_NAME,
|
||||||
|
APP_NAME_LOWER,
|
||||||
|
FLATPAK_ROOT_PATH,
|
||||||
|
ICONS_PATH,
|
||||||
|
)
|
||||||
from dzgui.const.enum import Preferences
|
from dzgui.const.enum import Preferences
|
||||||
from dzgui.config.query import lookup
|
from dzgui.config.query import lookup
|
||||||
from dzgui.config.userprefs import UserPrefs
|
from dzgui.config.userprefs import UserPrefs
|
||||||
@@ -17,7 +22,6 @@ from dzgui.init.migrate import (
|
|||||||
copy_state_files,
|
copy_state_files,
|
||||||
migrate_cols_file,
|
migrate_cols_file,
|
||||||
)
|
)
|
||||||
from dzgui.init.flock import lock_acquire
|
|
||||||
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
||||||
from dzgui.strings import boot
|
from dzgui.strings import boot
|
||||||
|
|
||||||
@@ -103,7 +107,8 @@ def load_steam_icon() -> None:
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk # noqa
|
from gi.repository import Gtk # noqa
|
||||||
|
|
||||||
try:
|
try:
|
||||||
traversable = resources.files(APP_NAME_LOWER).joinpath(ICONS_PATH)
|
traversable = resources.files(APP_NAME_LOWER).joinpath(ICONS_PATH)
|
||||||
icons = Path(str(traversable))
|
icons = Path(str(traversable))
|
||||||
@@ -115,9 +120,7 @@ def load_steam_icon() -> None:
|
|||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
|
|
||||||
|
|
||||||
def load_gui(version: str, is_debug: bool) -> None:
|
def build_gui(version: str, is_debug: bool) -> App | None:
|
||||||
lock = lock_acquire() # noqa
|
|
||||||
|
|
||||||
set_locale()
|
set_locale()
|
||||||
# NOTE: consider aborting this check if steam deck
|
# NOTE: consider aborting this check if steam deck
|
||||||
xdg_paths = get_xdg_paths()
|
xdg_paths = get_xdg_paths()
|
||||||
@@ -145,16 +148,13 @@ def load_gui(version: str, is_debug: bool) -> None:
|
|||||||
wizard = SetupWizard(_is_steam_deck, XDG, xdg_paths)
|
wizard = SetupWizard(_is_steam_deck, XDG, xdg_paths)
|
||||||
|
|
||||||
if not wizard.is_setup_complete():
|
if not wizard.is_setup_complete():
|
||||||
return
|
return None
|
||||||
|
|
||||||
setup_logger(XDG.debug)
|
setup_logger(XDG.debug)
|
||||||
with open(XDG.debug, "w") as f:
|
with open(XDG.debug, "w") as f:
|
||||||
f.truncate(0)
|
f.truncate(0)
|
||||||
|
|
||||||
if _is_steam_deck is False:
|
if _is_steam_deck is False:
|
||||||
# TODO: sudo escalation dialog
|
|
||||||
# count = get_map_count()
|
|
||||||
# TODO: move into module
|
|
||||||
if FLATPAK_ROOT_PATH in lookup(XDG.config, Preferences.DEFAULT):
|
if FLATPAK_ROOT_PATH in lookup(XDG.config, Preferences.DEFAULT):
|
||||||
has_client = has_flatpak_client()
|
has_client = has_flatpak_client()
|
||||||
else:
|
else:
|
||||||
@@ -180,4 +180,4 @@ def load_gui(version: str, is_debug: bool) -> None:
|
|||||||
print(boot.all_ok)
|
print(boot.all_ok)
|
||||||
load_steam_icon()
|
load_steam_icon()
|
||||||
app = App(prefs)
|
app = App(prefs)
|
||||||
app.run(None)
|
return app
|
||||||
+102
-3
@@ -3,6 +3,9 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import shutil
|
import shutil
|
||||||
|
import socket
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -22,15 +25,16 @@ def find_download_url(url: str) -> str | None:
|
|||||||
return str(urls[0])
|
return str(urls[0])
|
||||||
|
|
||||||
|
|
||||||
def get_ipdb(ips_path: Path) -> None:
|
def get_ipdb(db_path: Path) -> None:
|
||||||
try:
|
try:
|
||||||
url = find_download_url(DB_IP)
|
url = find_download_url(DB_IP)
|
||||||
if url is None:
|
if url is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
ips_path = db_path.with_suffix(".csv")
|
||||||
|
month_file = db_path.parent / ".month"
|
||||||
date = find_date(url)
|
date = find_date(url)
|
||||||
month_file = ips_path.parent / ".month"
|
if db_path.exists() and month_file.exists():
|
||||||
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}")
|
||||||
@@ -45,6 +49,9 @@ def get_ipdb(ips_path: Path) -> None:
|
|||||||
logger.info("Stripping IPv6 records")
|
logger.info("Stripping IPv6 records")
|
||||||
strip_ipv6(ips_path)
|
strip_ipv6(ips_path)
|
||||||
|
|
||||||
|
create_table(db_path, ips_path)
|
||||||
|
ips_path.unlink()
|
||||||
|
|
||||||
with open(month_file, "w") as f:
|
with open(month_file, "w") as f:
|
||||||
f.write(date)
|
f.write(date)
|
||||||
logger.info(f"Wrote {date} to {month_file}")
|
logger.info(f"Wrote {date} to {month_file}")
|
||||||
@@ -108,3 +115,95 @@ def strip_ipv6(path: Path) -> None:
|
|||||||
out.write(merged)
|
out.write(merged)
|
||||||
path.unlink()
|
path.unlink()
|
||||||
alt_path.rename(path)
|
alt_path.rename(path)
|
||||||
|
|
||||||
|
|
||||||
|
def ip_to_int(ip: str) -> int:
|
||||||
|
return int.from_bytes(socket.inet_aton(ip), "big")
|
||||||
|
|
||||||
|
|
||||||
|
def marshal_rows(ip_file: Path) -> list[tuple[int, int, str, str]]:
|
||||||
|
YIELD = 0.003
|
||||||
|
SLEEP = 0.001
|
||||||
|
with open(ip_file, "r") as f:
|
||||||
|
rows: list[tuple[int, int, str, str]] = []
|
||||||
|
# NOTE: yield to GIL periodically to reduce load on spinner
|
||||||
|
last_yield = time.monotonic()
|
||||||
|
for row in f:
|
||||||
|
start, end, lat, lon = row.rstrip("\n").split(",")
|
||||||
|
int_start = ip_to_int(start)
|
||||||
|
int_end = ip_to_int(end)
|
||||||
|
rows.append((int_start, int_end, lat, lon))
|
||||||
|
|
||||||
|
now = time.monotonic()
|
||||||
|
if (now - last_yield) >= YIELD:
|
||||||
|
time.sleep(SLEEP)
|
||||||
|
last_yield = time.monotonic()
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def create_table(db_file: Path, ip_file: Path) -> None:
|
||||||
|
create = """
|
||||||
|
CREATE TABLE IF NOT EXISTS ips (
|
||||||
|
start_range INTEGER PRIMARY KEY,
|
||||||
|
end_range INTEGER,
|
||||||
|
latitude REAL,
|
||||||
|
longitude REAL
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
"""
|
||||||
|
insert = """
|
||||||
|
INSERT INTO ips
|
||||||
|
(start_range, end_range, latitude, longitude)
|
||||||
|
values(?, ?, ?, ?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if db_file.exists():
|
||||||
|
db_file.unlink()
|
||||||
|
|
||||||
|
conn = sqlite3.connect(":memory:")
|
||||||
|
c = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
c.execute(create)
|
||||||
|
rows = marshal_rows(ip_file)
|
||||||
|
c.executemany(insert, rows)
|
||||||
|
logger.info(f"Inserted {len(rows)} IP records into database")
|
||||||
|
conn.commit()
|
||||||
|
with sqlite3.connect(db_file) as dest:
|
||||||
|
conn.backup(dest)
|
||||||
|
logger.info(f"Serialized IP records to database: '{db_file}'")
|
||||||
|
conn.close()
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
|
||||||
|
|
||||||
|
def read_from_table(db_path: Path, ip: str) -> tuple[float, float]:
|
||||||
|
if not db_path.exists():
|
||||||
|
raise FileNotFoundError(f"No file found at the location '{db_path}")
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
c = conn.cursor()
|
||||||
|
|
||||||
|
int_ip = ip_to_int(ip)
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT end_range, latitude, longitude
|
||||||
|
FROM ips
|
||||||
|
WHERE start_range <= ?
|
||||||
|
ORDER BY start_range DESC
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
index = """
|
||||||
|
CREATE INDEX IF NOT EXISTS start_index
|
||||||
|
ON ips(start_range);
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
c.execute(index)
|
||||||
|
conn.commit()
|
||||||
|
c.execute(query, (int_ip,))
|
||||||
|
row = c.fetchone()
|
||||||
|
end, lat, lon = row
|
||||||
|
conn.close()
|
||||||
|
return lat, lon
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
raise e
|
||||||
@@ -5,6 +5,7 @@ from dzgui.util._json import read_json
|
|||||||
|
|
||||||
from dzgui.const.enum import Preferences
|
from dzgui.const.enum import Preferences
|
||||||
|
|
||||||
|
|
||||||
class ConfigFileError(Exception):
|
class ConfigFileError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -39,4 +40,4 @@ def get_favorites(path: Path) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def enum_to_key(enum: Preferences) -> str:
|
def enum_to_key(enum: Preferences) -> str:
|
||||||
return str(enum.dict["key"])
|
return str(enum.key)
|
||||||
+5
-5
@@ -22,7 +22,7 @@ class Xdg:
|
|||||||
version: Path
|
version: Path
|
||||||
system: Path
|
system: Path
|
||||||
debug: Path
|
debug: Path
|
||||||
ips: Path
|
db: Path
|
||||||
filters: Path
|
filters: Path
|
||||||
shortcut: Path
|
shortcut: Path
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ def is_writeable(path_str: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_xdg_paths() -> dict:
|
def get_xdg_paths() -> dict[str, Path]:
|
||||||
home = Path.home()
|
home = Path.home()
|
||||||
xdg_paths = {
|
xdg_paths = {
|
||||||
"XDG_CONFIG_HOME": home / ".config",
|
"XDG_CONFIG_HOME": home / ".config",
|
||||||
@@ -73,7 +73,7 @@ def get_xdg_paths() -> dict:
|
|||||||
return resolved_paths
|
return resolved_paths
|
||||||
|
|
||||||
|
|
||||||
def parse_filepaths(xdg: dict) -> Xdg:
|
def parse_filepaths(xdg: dict[str, Path]) -> Xdg:
|
||||||
config = xdg["XDG_CONFIG_HOME"]
|
config = xdg["XDG_CONFIG_HOME"]
|
||||||
state = xdg["XDG_STATE_HOME"]
|
state = xdg["XDG_STATE_HOME"]
|
||||||
share = xdg["XDG_DATA_HOME"]
|
share = xdg["XDG_DATA_HOME"]
|
||||||
@@ -88,7 +88,7 @@ def parse_filepaths(xdg: dict) -> Xdg:
|
|||||||
resolution = state / "dzg.res.json"
|
resolution = state / "dzg.res.json"
|
||||||
history = state / "dzg.history"
|
history = state / "dzg.history"
|
||||||
versions = state / "dzg.versions"
|
versions = state / "dzg.versions"
|
||||||
ips = state / "ips.csv"
|
db = state / "ips.db"
|
||||||
filters = state / "dzg.filters.json"
|
filters = state / "dzg.filters.json"
|
||||||
|
|
||||||
shortcut = share / APP_NAME_LOWER
|
shortcut = share / APP_NAME_LOWER
|
||||||
@@ -102,7 +102,7 @@ def parse_filepaths(xdg: dict) -> Xdg:
|
|||||||
versions,
|
versions,
|
||||||
system,
|
system,
|
||||||
debug,
|
debug,
|
||||||
ips,
|
db,
|
||||||
filters,
|
filters,
|
||||||
shortcut,
|
shortcut,
|
||||||
)
|
)
|
||||||
@@ -37,37 +37,8 @@ HEX_GREEN = "#32CD32"
|
|||||||
HEX_RED = "#FF0000"
|
HEX_RED = "#FF0000"
|
||||||
HEX_ORANGE = "#FFAC1C"
|
HEX_ORANGE = "#FFAC1C"
|
||||||
|
|
||||||
# TODO: normalize icon names
|
|
||||||
CALCULATOR = "accessories-calculator-symbolic"
|
|
||||||
CARET_DOWN = "go-down-symbolic"
|
|
||||||
CARET_UP = "go-up-symbolic"
|
|
||||||
CLIPBOARD = "edit-copy-symbolic"
|
|
||||||
CLOSE = "window-close-symbolic"
|
|
||||||
FOLDER = "folder-symbolic"
|
|
||||||
EDIT_DELETE = "edit-delete-symbolic"
|
|
||||||
ERROR = "dialog-error-symbolic"
|
|
||||||
HELP_BUBBLE = "help-about-symbolic"
|
|
||||||
INPUT_KEYBOARD = "input-keyboard-symbolic"
|
|
||||||
LIST_ADD = "list-add-symbolic"
|
|
||||||
REFRESH_ICON = "view-refresh-symbolic"
|
|
||||||
SEARCH_ICON = "system-search-symbolic"
|
|
||||||
STEAM_ICON = "steam-tray-symbolic"
|
|
||||||
VIEW_CONCEAL = "view-conceal-symbolic"
|
|
||||||
VIEW_REVEAL = "view-reveal-symbolic"
|
|
||||||
WARNING = "dialog-warning-symbolic"
|
|
||||||
WEB_BROWSER = "web-browser-symbolic"
|
|
||||||
DAY_ICON = "weather-clear-symbolic"
|
|
||||||
NIGHT_ICON = "weather-clear-night-symbolic"
|
|
||||||
|
|
||||||
SEPARATOR = "SEPARATOR"
|
SEPARATOR = "SEPARATOR"
|
||||||
|
|
||||||
# TODO: drop
|
|
||||||
NO_PADDING = 0
|
|
||||||
NO_EXPAND = False
|
|
||||||
NO_FILL = False
|
|
||||||
EXPAND = True
|
|
||||||
FILL = True
|
|
||||||
|
|
||||||
SCROLL_INCREMENT = 50
|
SCROLL_INCREMENT = 50
|
||||||
WINDOW_DEFAULT_X = 1400
|
WINDOW_DEFAULT_X = 1400
|
||||||
WINDOW_DEFAULT_Y = 800
|
WINDOW_DEFAULT_Y = 800
|
||||||
@@ -96,6 +67,7 @@ LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
|
|||||||
TMP_PATH = "/tmp"
|
TMP_PATH = "/tmp"
|
||||||
TMP_TARBALL = "/tmp/dzgui.tar.gz"
|
TMP_TARBALL = "/tmp/dzgui.tar.gz"
|
||||||
TMP_EXE = "/tmp/dzgui/dzgui"
|
TMP_EXE = "/tmp/dzgui/dzgui"
|
||||||
|
LOCK_FILE = "/tmp/dzgui.flock"
|
||||||
|
|
||||||
DAYZ_COMMUNITY_ROOT = "Missions"
|
DAYZ_COMMUNITY_ROOT = "Missions"
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ STEAM_PUBLISHED_FILES = (
|
|||||||
"https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1"
|
"https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1"
|
||||||
)
|
)
|
||||||
STEAM_SERVERS = "https://api.steampowered.com/IGameServersService/GetServerList/v1"
|
STEAM_SERVERS = "https://api.steampowered.com/IGameServersService/GetServerList/v1"
|
||||||
SUB_ENDPOINT = "https://api.steampowered.com/IPublishedFileService/Subscribe/v1"
|
STEAM_SUB = "https://api.steampowered.com/IPublishedFileService/Subscribe/v1"
|
||||||
UNSUB_ENDPOINT = "https://api.steampowered.com/IPublishedFileService/Unsubscribe/v1"
|
STEAM_UNSUB = "https://api.steampowered.com/IPublishedFileService/Unsubscribe/v1"
|
||||||
APP_DETAILS = "https://store.steampowered.com/api/appdetails?"
|
STEAM_APP_DETAILS = "https://store.steampowered.com/api/appdetails?"
|
||||||
|
|
||||||
GITHUB = "https://github.com/aclist"
|
GITHUB = "https://github.com/aclist"
|
||||||
GITHUB_RELEASES = "https://api.github.com/repos/aclist/dztui/releases/latest"
|
GITHUB_RELEASES = "https://api.github.com/repos/aclist/dztui/releases/latest"
|
||||||
@@ -18,6 +18,7 @@ COORDS_API = "http://ip-api.com/json"
|
|||||||
# steam URIs
|
# steam URIs
|
||||||
WORKSHOP = "steam://url/CommunityFilePage/"
|
WORKSHOP = "steam://url/CommunityFilePage/"
|
||||||
OPEN_URL = "steam://openurl/"
|
OPEN_URL = "steam://openurl/"
|
||||||
|
VALIDATE = "steam://validate/"
|
||||||
|
|
||||||
# user facing
|
# user facing
|
||||||
GITHUB_PAGES = "https://aclist.github.io/dzgui/index"
|
GITHUB_PAGES = "https://aclist.github.io/dzgui/index"
|
||||||
|
|||||||
+224
-201
@@ -1,7 +1,9 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Self
|
|
||||||
|
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
|
from dzgui.strings import rows
|
||||||
|
|
||||||
|
|
||||||
class Port(Enum):
|
class Port(Enum):
|
||||||
@@ -46,124 +48,61 @@ class FilterMode(Enum):
|
|||||||
TOGGLE_ON = 3
|
TOGGLE_ON = 3
|
||||||
|
|
||||||
|
|
||||||
class EnumWithAttrs(Enum):
|
@dataclass(slots=True, frozen=True)
|
||||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self:
|
class ModButtonMeta:
|
||||||
value = len(cls.__members__) + 1
|
label: str
|
||||||
obj = object.__new__(cls)
|
tooltip: str
|
||||||
obj._value_ = value
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __init__(self, d: dict):
|
|
||||||
self.dict = d
|
|
||||||
|
|
||||||
|
|
||||||
class Preferences(EnumWithAttrs):
|
@dataclass(slots=True, frozen=True)
|
||||||
STEAM = {
|
class ButtonTypeMeta:
|
||||||
"key": "steam_api",
|
label: str
|
||||||
}
|
tooltip: str
|
||||||
CLIENT = {
|
opens: NotebookPage | None
|
||||||
"key": "client",
|
|
||||||
}
|
|
||||||
WINDOW = {
|
|
||||||
"key": "fullscreen",
|
|
||||||
}
|
|
||||||
DIST = {
|
|
||||||
"key": "use_miles",
|
|
||||||
}
|
|
||||||
NAME = {
|
|
||||||
"key": "name",
|
|
||||||
}
|
|
||||||
DEFAULT = {
|
|
||||||
"key": "default_steam_path",
|
|
||||||
}
|
|
||||||
FAV_LBL = {
|
|
||||||
"key": "fav_label",
|
|
||||||
}
|
|
||||||
FAV_SRV = {
|
|
||||||
"key": "fav_server",
|
|
||||||
}
|
|
||||||
IP_LIST = {
|
|
||||||
"key": "ip_list",
|
|
||||||
}
|
|
||||||
START_TAB = {
|
|
||||||
"key": "start_tab",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NotebookPage(EnumWithAttrs):
|
@dataclass(slots=True, frozen=True)
|
||||||
OFFLINE = {"crumbs": strings.crumbs.offline, "statusbar": True}
|
class ConfigKey:
|
||||||
CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": True}
|
key: str
|
||||||
DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False}
|
|
||||||
HELP = {"crumbs": strings.crumbs._help, "statusbar": True}
|
|
||||||
KEYS = {"crumbs": strings.crumbs.keys, "statusbar": True}
|
|
||||||
LOG = {"crumbs": strings.crumbs.log, "statusbar": True}
|
|
||||||
MODS = {"crumbs": strings.crumbs.mods, "statusbar": True}
|
|
||||||
OPTIONS = {"crumbs": strings.crumbs.options, "statusbar": False}
|
|
||||||
SERVERS = {"crumbs": strings.crumbs.servers, "statusbar": True}
|
|
||||||
THANKS = {"crumbs": strings.crumbs.thanks, "statusbar": True}
|
|
||||||
CONNECTION = {"crumbs": "Connect", "statusbar": True}
|
|
||||||
|
|
||||||
|
|
||||||
class RowType(EnumWithAttrs):
|
@dataclass(slots=True, frozen=True)
|
||||||
# TODO: strings
|
class NotebookMeta:
|
||||||
CHANGELOG = {
|
crumbs: str
|
||||||
"label": "View changelog",
|
has_statusbar: bool
|
||||||
"tooltip": "Opens the DZGUI changelog",
|
|
||||||
}
|
|
||||||
SHOW_LOG = {
|
|
||||||
"label": "Show debug log",
|
|
||||||
"tooltip": "Read the DZGUI log generated since startup",
|
|
||||||
}
|
|
||||||
SEPARATOR = {
|
|
||||||
"label": "SEPARATOR",
|
|
||||||
"tooltip": "",
|
|
||||||
}
|
|
||||||
DUMP_LOG = {
|
|
||||||
"label": "Output system info to log file",
|
|
||||||
"tooltip": "Dump diagnostic data for troubleshooting",
|
|
||||||
}
|
|
||||||
DOCS = {
|
|
||||||
"label": "Documentation/help files (GitHub) ⧉",
|
|
||||||
"tooltip": "Open the DZGUI documentation in a browser",
|
|
||||||
}
|
|
||||||
DOCS_FALLBACK = {
|
|
||||||
"label": "Documentation/help files (Codeberg mirror) ⧉",
|
|
||||||
"tooltip": "Open the DZGUI documentation in a browser",
|
|
||||||
}
|
|
||||||
BUGS = {
|
|
||||||
"label": "Report a bug (GitHub) ⧉",
|
|
||||||
"tooltip": "Open the DZGUI issue tracker in a browser",
|
|
||||||
}
|
|
||||||
FORUM = {
|
|
||||||
"label": "DZGUI Subreddit ⧉",
|
|
||||||
"tooltip": "Open the DZGUI discussion forum in a browser",
|
|
||||||
}
|
|
||||||
SPONSOR = {
|
|
||||||
"label": "Sponsor (GitHub) ⧉",
|
|
||||||
"tooltip": "Sponsor development of the DZGUI project",
|
|
||||||
}
|
|
||||||
THANKS = {
|
|
||||||
"label": "Special thanks",
|
|
||||||
"tooltip": "A list of contributors, testers, and sponsors",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: rename to ContextMenuItem
|
@dataclass(slots=True, frozen=True)
|
||||||
class ContextMenu(EnumWithAttrs):
|
class RowMeta:
|
||||||
ADD_NOTE = {"label": strings.add_note}
|
label: str
|
||||||
ADD_SERVER = {"label": strings.add}
|
tooltip: str
|
||||||
CONNECT = {"label": strings.connect}
|
|
||||||
COPY_LOG_CLIPBOARD = {"label": strings.copy_log}
|
|
||||||
COPY_SERVER_IP = {"label": strings.copy_ip}
|
@dataclass(slots=True, frozen=True)
|
||||||
COPY_SERVER_NAME = {"label": strings.copy_name}
|
class ContextMenuMeta:
|
||||||
UNSUB_MOD = {"label": strings.unsub_mod}
|
label: str
|
||||||
OPEN_WORKSHOP = {"label": strings.open_workshop}
|
|
||||||
REFRESH_PLAYERS = {"label": strings.refresh_players}
|
|
||||||
REMOVE_HISTORY = {"label": strings.remove_history}
|
class ContextMenuItem(Enum):
|
||||||
REMOVE_SERVER = {"label": strings.remove}
|
ADD_NOTE = ContextMenuMeta(strings.add_note)
|
||||||
SET_FAV = {"label": strings.add_fav}
|
ADD_SERVER = ContextMenuMeta(strings.add)
|
||||||
SHOW_DETAILS = {"label": strings.show_details}
|
CONNECT = ContextMenuMeta(strings.connect)
|
||||||
SHOW_MODS = {"label": strings.show_mods}
|
COPY_LOG_CLIPBOARD = ContextMenuMeta(strings.copy_log)
|
||||||
|
COPY_SERVER_IP = ContextMenuMeta(strings.copy_ip)
|
||||||
|
COPY_SERVER_NAME = ContextMenuMeta(strings.copy_name)
|
||||||
|
UNSUB_MOD = ContextMenuMeta(strings.unsub_mod)
|
||||||
|
VALIDATE_MOD = ContextMenuMeta(strings.validate_mod)
|
||||||
|
OPEN_WORKSHOP = ContextMenuMeta(strings.open_workshop)
|
||||||
|
REFRESH_PLAYERS = ContextMenuMeta(strings.refresh_players)
|
||||||
|
REMOVE_HISTORY = ContextMenuMeta(strings.remove_history)
|
||||||
|
REMOVE_SERVER = ContextMenuMeta(strings.remove)
|
||||||
|
SET_FAV = ContextMenuMeta(strings.add_fav)
|
||||||
|
SHOW_DETAILS = ContextMenuMeta(strings.show_details)
|
||||||
|
SHOW_MODS = ContextMenuMeta(strings.show_mods)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
return self.value.label
|
||||||
|
|
||||||
|
|
||||||
class ContextMenuGroup(Enum):
|
class ContextMenuGroup(Enum):
|
||||||
@@ -171,109 +110,193 @@ class ContextMenuGroup(Enum):
|
|||||||
Groupings of context menu items
|
Groupings of context menu items
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SERVER_MOD = (ContextMenu.OPEN_WORKSHOP,)
|
SERVER_MOD = (ContextMenuItem.OPEN_WORKSHOP,)
|
||||||
MOD = (ContextMenu.OPEN_WORKSHOP, ContextMenu.UNSUB_MOD)
|
MOD = (
|
||||||
|
ContextMenuItem.OPEN_WORKSHOP,
|
||||||
|
ContextMenuItem.UNSUB_MOD,
|
||||||
|
ContextMenuItem.VALIDATE_MOD,
|
||||||
|
)
|
||||||
MOD_OFFLINE = (None,)
|
MOD_OFFLINE = (None,)
|
||||||
LOG = (ContextMenu.COPY_LOG_CLIPBOARD,)
|
LOG = (ContextMenuItem.COPY_LOG_CLIPBOARD,)
|
||||||
SERVER_BROWSER = (
|
SERVER_BROWSER = (
|
||||||
ContextMenu.CONNECT,
|
ContextMenuItem.CONNECT,
|
||||||
ContextMenu.ADD_SERVER,
|
ContextMenuItem.ADD_SERVER,
|
||||||
ContextMenu.SET_FAV,
|
ContextMenuItem.SET_FAV,
|
||||||
ContextMenu.COPY_SERVER_NAME,
|
ContextMenuItem.COPY_SERVER_NAME,
|
||||||
ContextMenu.COPY_SERVER_IP,
|
ContextMenuItem.COPY_SERVER_IP,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenuItem.ADD_NOTE,
|
||||||
ContextMenu.SHOW_MODS,
|
ContextMenuItem.SHOW_MODS,
|
||||||
ContextMenu.SHOW_DETAILS,
|
ContextMenuItem.SHOW_DETAILS,
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenuItem.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
SCAN_LAN = (
|
SCAN_LAN = (
|
||||||
ContextMenu.CONNECT,
|
ContextMenuItem.CONNECT,
|
||||||
ContextMenu.SET_FAV,
|
ContextMenuItem.SET_FAV,
|
||||||
ContextMenu.COPY_SERVER_NAME,
|
ContextMenuItem.COPY_SERVER_NAME,
|
||||||
ContextMenu.COPY_SERVER_IP,
|
ContextMenuItem.COPY_SERVER_IP,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenuItem.ADD_NOTE,
|
||||||
ContextMenu.SHOW_MODS,
|
ContextMenuItem.SHOW_MODS,
|
||||||
ContextMenu.SHOW_DETAILS,
|
ContextMenuItem.SHOW_DETAILS,
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenuItem.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
SAVED = (
|
SAVED = (
|
||||||
ContextMenu.CONNECT,
|
ContextMenuItem.CONNECT,
|
||||||
ContextMenu.SET_FAV,
|
ContextMenuItem.SET_FAV,
|
||||||
ContextMenu.REMOVE_SERVER,
|
ContextMenuItem.REMOVE_SERVER,
|
||||||
ContextMenu.COPY_SERVER_NAME,
|
ContextMenuItem.COPY_SERVER_NAME,
|
||||||
ContextMenu.COPY_SERVER_IP,
|
ContextMenuItem.COPY_SERVER_IP,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenuItem.ADD_NOTE,
|
||||||
ContextMenu.SHOW_MODS,
|
ContextMenuItem.SHOW_MODS,
|
||||||
ContextMenu.SHOW_DETAILS,
|
ContextMenuItem.SHOW_DETAILS,
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenuItem.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
RECENT = (
|
RECENT = (
|
||||||
ContextMenu.CONNECT,
|
ContextMenuItem.CONNECT,
|
||||||
ContextMenu.ADD_SERVER,
|
ContextMenuItem.ADD_SERVER,
|
||||||
ContextMenu.SET_FAV,
|
ContextMenuItem.SET_FAV,
|
||||||
ContextMenu.REMOVE_HISTORY,
|
ContextMenuItem.REMOVE_HISTORY,
|
||||||
ContextMenu.COPY_SERVER_NAME,
|
ContextMenuItem.COPY_SERVER_NAME,
|
||||||
ContextMenu.COPY_SERVER_IP,
|
ContextMenuItem.COPY_SERVER_IP,
|
||||||
ContextMenu.ADD_NOTE,
|
ContextMenuItem.ADD_NOTE,
|
||||||
ContextMenu.SHOW_MODS,
|
ContextMenuItem.SHOW_MODS,
|
||||||
ContextMenu.SHOW_DETAILS,
|
ContextMenuItem.SHOW_DETAILS,
|
||||||
ContextMenu.REFRESH_PLAYERS,
|
ContextMenuItem.REFRESH_PLAYERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModButton(EnumWithAttrs):
|
class Preferences(Enum):
|
||||||
SELECT_ALL = {
|
STEAM = ConfigKey("steam_api")
|
||||||
"label": strings.mod_panel.select_all,
|
CLIENT = ConfigKey("client")
|
||||||
"tooltip": strings.mod_panel.select_all_tooltip,
|
WINDOW = ConfigKey("fullscreen")
|
||||||
}
|
DIST = ConfigKey("use_miles")
|
||||||
UNSELECT_ALL = {
|
NAME = ConfigKey("name")
|
||||||
"label": strings.mod_panel.unselect_all,
|
DEFAULT = ConfigKey("default_steam_path")
|
||||||
"tooltip": strings.mod_panel.unselect_all_tooltip,
|
FAV_LBL = ConfigKey("fav_label")
|
||||||
}
|
FAV_SRV = ConfigKey("fav_server")
|
||||||
HIGHLIGHT_STALE = {
|
IP_LIST = ConfigKey("ip_list")
|
||||||
"label": strings.mod_panel.highlight_stale,
|
START_TAB = ConfigKey("start_tab")
|
||||||
"tooltip": strings.mod_panel.highlight_stale_tooltip,
|
|
||||||
}
|
@property
|
||||||
UNHIGHLIGHT_STALE = {
|
def key(self) -> str:
|
||||||
"label": strings.mod_panel.unhighlight_stale,
|
return self.value.key
|
||||||
"tooltip": strings.mod_panel.unhighlight_stale_tooltip,
|
|
||||||
}
|
|
||||||
UNSUB_SELECTED = {
|
|
||||||
"label": strings.mod_panel.unsub_selected,
|
|
||||||
"tooltip": strings.mod_panel.unsub_selected_tooltip,
|
|
||||||
}
|
|
||||||
SELECT_STALE = {
|
|
||||||
"label": strings.mod_panel.select_stale,
|
|
||||||
"tooltip": strings.mod_panel.select_stale_tooltip,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ButtonType(EnumWithAttrs):
|
class NotebookPage(Enum):
|
||||||
SERVERS = {
|
OFFLINE = NotebookMeta(strings.crumbs.offline, True)
|
||||||
"label": strings.buttons.servers_label,
|
CHANGELOG = NotebookMeta(strings.crumbs.changelog, True)
|
||||||
"tooltip": strings.buttons.servers_tooltip,
|
DEVELOPERS = NotebookMeta(strings.crumbs.developers, False)
|
||||||
"opens": NotebookPage.SERVERS,
|
HELP = NotebookMeta(strings.crumbs._help, True)
|
||||||
}
|
KEYS = NotebookMeta(strings.crumbs.keys, True)
|
||||||
MODS = {
|
LOG = NotebookMeta(strings.crumbs.log, True)
|
||||||
"label": strings.buttons.mods_label,
|
MODS = NotebookMeta(strings.crumbs.mods, True)
|
||||||
"tooltip": strings.buttons.mods_tooltip,
|
OPTIONS = NotebookMeta(strings.crumbs.options, False)
|
||||||
"opens": NotebookPage.MODS,
|
SERVERS = NotebookMeta(strings.crumbs.servers, True)
|
||||||
}
|
THANKS = NotebookMeta(strings.crumbs.thanks, True)
|
||||||
OPTIONS = {
|
CONNECTION = NotebookMeta(strings.crumbs.connect, True)
|
||||||
"label": strings.buttons.options_label,
|
|
||||||
"tooltip": strings.buttons.options_tooltip,
|
@property
|
||||||
"opens": NotebookPage.OPTIONS,
|
def crumbs(self) -> str:
|
||||||
}
|
return self.value.crumbs
|
||||||
HELP = {
|
|
||||||
"label": strings.buttons.help_label,
|
@property
|
||||||
"tooltip": strings.buttons.help_tooltip,
|
def has_statusbar(self) -> bool:
|
||||||
"opens": NotebookPage.HELP,
|
return self.value.has_statusbar
|
||||||
}
|
|
||||||
EXIT = {
|
|
||||||
"label": strings.buttons.exit_label,
|
class RowType(Enum):
|
||||||
"tooltip": strings.buttons.exit_tooltip,
|
CHANGELOG = RowMeta(rows.changelog_label, rows.changelog_status)
|
||||||
"opens": None,
|
SHOW_LOG = RowMeta(rows.log_label, rows.log_status)
|
||||||
}
|
SEPARATOR = RowMeta(rows.separator_label, rows.separator_status)
|
||||||
|
DUMP_LOG = RowMeta(rows.system_log_label, rows.system_log_status)
|
||||||
|
DOCS = RowMeta(rows.docs_label, rows.docs_status)
|
||||||
|
DOCS_FALLBACK = RowMeta(rows.docs_fallback_label, rows.docs_fallback_status)
|
||||||
|
BUGS = RowMeta(rows.bugs_label, rows.bugs_status)
|
||||||
|
FORUM = RowMeta(rows.forum_label, rows.forum_status)
|
||||||
|
SPONSOR = RowMeta(rows.sponsor_label, rows.sponsor_status)
|
||||||
|
THANKS = RowMeta(rows.thanks_label, rows.thanks_status)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
return self.value.label
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tooltip(self) -> str:
|
||||||
|
return self.value.tooltip
|
||||||
|
|
||||||
|
|
||||||
|
class ModButton(Enum):
|
||||||
|
SELECT_ALL = ModButtonMeta(
|
||||||
|
strings.mod_panel.select_all, strings.mod_panel.select_all_tooltip
|
||||||
|
)
|
||||||
|
UNSELECT_ALL = ModButtonMeta(
|
||||||
|
strings.mod_panel.unselect_all, strings.mod_panel.unselect_all_tooltip
|
||||||
|
)
|
||||||
|
HIGHLIGHT_STALE = ModButtonMeta(
|
||||||
|
strings.mod_panel.highlight_stale, strings.mod_panel.highlight_stale_tooltip
|
||||||
|
)
|
||||||
|
UNHIGHLIGHT_STALE = ModButtonMeta(
|
||||||
|
strings.mod_panel.unhighlight_stale, strings.mod_panel.unhighlight_stale_tooltip
|
||||||
|
)
|
||||||
|
UNSUB_SELECTED = ModButtonMeta(
|
||||||
|
strings.mod_panel.unsub_selected,
|
||||||
|
strings.mod_panel.unsub_selected_tooltip,
|
||||||
|
)
|
||||||
|
SELECT_STALE = ModButtonMeta(
|
||||||
|
strings.mod_panel.select_stale,
|
||||||
|
strings.mod_panel.select_stale_tooltip,
|
||||||
|
)
|
||||||
|
VALIDATE = ModButtonMeta(
|
||||||
|
strings.mod_panel.validate,
|
||||||
|
strings.mod_panel.validate_tooltip,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
return self.value.label
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tooltip(self) -> str:
|
||||||
|
return self.value.tooltip
|
||||||
|
|
||||||
|
|
||||||
|
class ButtonType(Enum):
|
||||||
|
SERVERS = ButtonTypeMeta(
|
||||||
|
strings.buttons.servers_label,
|
||||||
|
strings.buttons.servers_tooltip,
|
||||||
|
NotebookPage.SERVERS,
|
||||||
|
)
|
||||||
|
MODS = ButtonTypeMeta(
|
||||||
|
strings.buttons.mods_label,
|
||||||
|
strings.buttons.mods_tooltip,
|
||||||
|
NotebookPage.MODS,
|
||||||
|
)
|
||||||
|
OPTIONS = ButtonTypeMeta(
|
||||||
|
strings.buttons.options_label,
|
||||||
|
strings.buttons.options_tooltip,
|
||||||
|
NotebookPage.OPTIONS,
|
||||||
|
)
|
||||||
|
HELP = ButtonTypeMeta(
|
||||||
|
strings.buttons.help_label,
|
||||||
|
strings.buttons.help_tooltip,
|
||||||
|
NotebookPage.HELP,
|
||||||
|
)
|
||||||
|
EXIT = ButtonTypeMeta(
|
||||||
|
strings.buttons.exit_label,
|
||||||
|
strings.buttons.exit_tooltip,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
return self.value.label
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tooltip(self) -> str:
|
||||||
|
return self.value.tooltip
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opens(self) -> NotebookPage | None:
|
||||||
|
return self.value.opens
|
||||||
|
|
||||||
|
|
||||||
HELP_MENU_ROWS = (
|
HELP_MENU_ROWS = (
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
ICON_CALCULATOR = "accessories-calculator-symbolic"
|
||||||
|
ICON_CARET_DOWN = "go-down-symbolic"
|
||||||
|
ICON_CARET_UP = "go-up-symbolic"
|
||||||
|
ICON_CLIPBOARD = "edit-copy-symbolic"
|
||||||
|
ICON_CLOSE = "window-close-symbolic"
|
||||||
|
ICON_DAY = "weather-clear-symbolic"
|
||||||
|
ICON_EDIT_DELETE = "edit-delete-symbolic"
|
||||||
|
ICON_ERROR = "dialog-error-symbolic"
|
||||||
|
ICON_FOLDER = "folder-symbolic"
|
||||||
|
ICON_HELP_BUBBLE = "help-about-symbolic"
|
||||||
|
ICON_INPUT_KEYBOARD = "input-keyboard-symbolic"
|
||||||
|
ICON_LIST_ADD = "list-add-symbolic"
|
||||||
|
ICON_NIGHT = "weather-clear-night-symbolic"
|
||||||
|
ICON_REFRESH = "view-refresh-symbolic"
|
||||||
|
ICON_SEARCH = "system-search-symbolic"
|
||||||
|
ICON_STEAM = "steam-tray-symbolic"
|
||||||
|
ICON_VIEW_CONCEAL = "view-conceal-symbolic"
|
||||||
|
ICON_VIEW_REVEAL = "view-reveal-symbolic"
|
||||||
|
ICON_WARNING = "dialog-warning-symbolic"
|
||||||
|
ICON_WEB_BROWSER = "web-browser-symbolic"
|
||||||
@@ -3,8 +3,8 @@ from typing import TYPE_CHECKING
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository.Gtk import ListStore # noqa E402
|
from gi.repository.Gtk import ListStore
|
||||||
from gi.repository import Gdk, GObject # noqa E402
|
from gi.repository import GObject
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.const.enum import NotebookPage, ServerTab
|
from dzgui.const.enum import NotebookPage, ServerTab
|
||||||
@@ -111,6 +111,10 @@ class Emitter(GObject.GObject):
|
|||||||
def check_toggled(self, label: str, state: bool) -> None:
|
def check_toggled(self, label: str, state: bool) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
|
||||||
|
def servers_timed_out(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object,))
|
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object,))
|
||||||
def servers_loaded(self, tag: "ServerTab") -> None:
|
def servers_loaded(self, tag: "ServerTab") -> None:
|
||||||
pass
|
pass
|
||||||
|
|||||||
+22
-11
@@ -8,7 +8,7 @@ import dzgui.util._json as JSON # noqa
|
|||||||
|
|
||||||
from dzgui.const.enum import (
|
from dzgui.const.enum import (
|
||||||
ButtonType,
|
ButtonType,
|
||||||
ContextMenu,
|
ContextMenuItem,
|
||||||
FilterMode,
|
FilterMode,
|
||||||
NotebookPage,
|
NotebookPage,
|
||||||
Preferences,
|
Preferences,
|
||||||
@@ -32,7 +32,7 @@ from dzgui.views.dialogs.generic import ExceptionDialog
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk, GLib, GObject # noqa E402
|
from gi.repository import Gtk, Gdk, GLib, GObject
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ class AppNavigation:
|
|||||||
|
|
||||||
class Controller(GObject.GObject):
|
class Controller(GObject.GObject):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.dist_cache: dict[str, "Haversine"] = {}
|
self.dist_cache: dict[str, "Haversine" | None] = {}
|
||||||
self.mediator = AppNavigation()
|
self.mediator = AppNavigation()
|
||||||
|
|
||||||
self.prefs: UserPrefs
|
self.prefs: UserPrefs
|
||||||
@@ -102,6 +102,16 @@ class Controller(GObject.GObject):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.critical(f"{attr} is not a valid AppNavigation attribute.")
|
logger.critical(f"{attr} is not a valid AppNavigation attribute.")
|
||||||
|
|
||||||
|
def validate_mods(
|
||||||
|
self, treeview: Union["ModTreeView", "OfflineModTreeView", None] = None
|
||||||
|
) -> None:
|
||||||
|
if treeview is None:
|
||||||
|
view = self.mediator.modtreeview
|
||||||
|
else:
|
||||||
|
view = treeview
|
||||||
|
mod_man = view.get_mod_man()
|
||||||
|
mod_man.validate_mods()
|
||||||
|
|
||||||
def get_prefs(self) -> UserPrefs:
|
def get_prefs(self) -> UserPrefs:
|
||||||
return self.prefs
|
return self.prefs
|
||||||
|
|
||||||
@@ -186,7 +196,9 @@ class Controller(GObject.GObject):
|
|||||||
def show_developers_page(self) -> None:
|
def show_developers_page(self) -> None:
|
||||||
self.open_page(NotebookPage.DEVELOPERS)
|
self.open_page(NotebookPage.DEVELOPERS)
|
||||||
|
|
||||||
def open_page(self, page: NotebookPage) -> None:
|
def open_page(self, page: NotebookPage | None) -> None:
|
||||||
|
if page is None:
|
||||||
|
return
|
||||||
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:
|
||||||
@@ -252,7 +264,7 @@ class Controller(GObject.GObject):
|
|||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
return
|
return
|
||||||
|
|
||||||
def menu_action(self, action: ContextMenu, tree: "ServerTreeView") -> None:
|
def menu_action(self, action: ContextMenuItem, tree: "ServerTreeView") -> None:
|
||||||
context_man = ContextMenuManager(tree, self)
|
context_man = ContextMenuManager(tree, self)
|
||||||
context_man.process(action)
|
context_man.process(action)
|
||||||
|
|
||||||
@@ -264,7 +276,7 @@ class Controller(GObject.GObject):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
dialog = ExceptionDialog(self, str(e))
|
dialog = ExceptionDialog(self, str(e))
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def select_colorized(self) -> None:
|
def select_colorized(self) -> None:
|
||||||
mod_man = self.mediator.modtreeview.get_mod_man()
|
mod_man = self.mediator.modtreeview.get_mod_man()
|
||||||
@@ -298,7 +310,7 @@ class Controller(GObject.GObject):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
dialog = ExceptionDialog(self, str(e))
|
dialog = ExceptionDialog(self, str(e))
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def update_steam_api_key(self, text: str) -> None:
|
def update_steam_api_key(self, text: str) -> None:
|
||||||
self.config_man.update_steam_api_key(text)
|
self.config_man.update_steam_api_key(text)
|
||||||
@@ -345,22 +357,21 @@ class Controller(GObject.GObject):
|
|||||||
filter_man.set_filter(filt, state)
|
filter_man.set_filter(filt, state)
|
||||||
self.mediator.filters.set_filters(filters)
|
self.mediator.filters.set_filters(filters)
|
||||||
|
|
||||||
def get_dist_cache(self) -> dict[str, "Haversine"]:
|
def get_dist_cache(self) -> dict[str, Union["Haversine", None]]:
|
||||||
return self.dist_cache
|
return self.dist_cache
|
||||||
|
|
||||||
def set_dist_cache(self, addr: str, haversine: "Haversine") -> None:
|
def set_dist_cache(self, addr: str, haversine: Union["Haversine", None]) -> None:
|
||||||
self.dist_cache[addr] = haversine
|
self.dist_cache[addr] = haversine
|
||||||
|
|
||||||
def get_map_store(self) -> Gtk.ListStore:
|
def get_map_store(self) -> Gtk.ListStore:
|
||||||
filter_man = self.get_filter_man()
|
filter_man = self.get_filter_man()
|
||||||
return filter_man.get_map_store()
|
return filter_man.get_map_store()
|
||||||
|
|
||||||
def get_enabled_filters(self) -> dict:
|
def get_enabled_filters(self) -> dict[str, bool]:
|
||||||
filter_man = self.get_filter_man()
|
filter_man = self.get_filter_man()
|
||||||
return filter_man.get_filters()
|
return filter_man.get_filters()
|
||||||
|
|
||||||
# FIXME: wipe maps store when changing tabs if model is none
|
# FIXME: wipe maps store when changing tabs if model is none
|
||||||
|
|
||||||
def has_server_model(self) -> bool:
|
def has_server_model(self) -> bool:
|
||||||
treeview = self.get_active_treeview()
|
treeview = self.get_active_treeview()
|
||||||
proxy_man = treeview.get_proxy_man()
|
proxy_man = treeview.get_proxy_man()
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 361 KiB |
@@ -10,10 +10,10 @@ if TYPE_CHECKING:
|
|||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
def get_local_coords(path: "Path") -> Coords | None:
|
def get_local_coords(db_path: "Path") -> Coords | None:
|
||||||
try:
|
try:
|
||||||
my_ip = get_local_ip()
|
my_ip = get_local_ip()
|
||||||
return get_coords(path, my_ip)
|
return get_coords(db_path, my_ip)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(e)
|
logger.warn(e)
|
||||||
return None
|
return None
|
||||||
+3
-2
@@ -5,8 +5,9 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
|
|
||||||
from dzgui.views.dialogs.early_alert import EarlyAlertDialog
|
from dzgui.const.constants import LOCK_FILE
|
||||||
from dzgui.util.strings import cannot_acquire_lock
|
from dzgui.util.strings import cannot_acquire_lock
|
||||||
|
from dzgui.views.dialogs.early_alert import EarlyAlertDialog
|
||||||
|
|
||||||
|
|
||||||
def lock_release(lock: TextIO) -> None:
|
def lock_release(lock: TextIO) -> None:
|
||||||
@@ -15,7 +16,7 @@ def lock_release(lock: TextIO) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def lock_acquire() -> TextIO:
|
def lock_acquire() -> TextIO:
|
||||||
lockfile = Path("/tmp/dzgui.flock")
|
lockfile = Path(LOCK_FILE)
|
||||||
lock = open(lockfile, "w")
|
lock = open(lockfile, "w")
|
||||||
try:
|
try:
|
||||||
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, FLATPAK_CMD, STEAM_CMD
|
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, FLATPAK_CMD, STEAM_CMD
|
||||||
|
from dzgui.init.proc import is_sandboxed
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
def has_flatpak_client() -> bool:
|
def has_flatpak_client() -> bool:
|
||||||
|
if is_sandboxed():
|
||||||
|
return True
|
||||||
try:
|
try:
|
||||||
res = subprocess.run(
|
res = subprocess.run(
|
||||||
[FLATPAK_CMD, "list", "--columns=application"],
|
[FLATPAK_CMD, "list", "--columns=application"],
|
||||||
|
|||||||
+1
-1
Submodule dzgui/lib/dayzquery updated: 07483b88ed...fe413df8e8.
+8
-2
@@ -42,6 +42,12 @@ def main() -> None:
|
|||||||
test_libgi_missing()
|
test_libgi_missing()
|
||||||
|
|
||||||
# NOTE: only import ligbirepository modules after the above check
|
# NOTE: only import ligbirepository modules after the above check
|
||||||
from dzgui.app_init import load_gui
|
from dzgui.app_init import build_gui
|
||||||
|
from dzgui.init.flock import lock_acquire
|
||||||
|
|
||||||
load_gui(version, args.debug)
|
lock = lock_acquire() # noqa
|
||||||
|
|
||||||
|
app = build_gui(version, args.debug)
|
||||||
|
if app is None:
|
||||||
|
sys.exit(0)
|
||||||
|
app.run(None)
|
||||||
@@ -124,7 +124,7 @@ class ConfigManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def enum_to_key(self, enum: Preferences) -> str:
|
def enum_to_key(self, enum: Preferences) -> str:
|
||||||
return str(enum.dict["key"])
|
return str(enum.key)
|
||||||
|
|
||||||
def toggle_config(self, key: Preferences) -> None:
|
def toggle_config(self, key: Preferences) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -162,7 +162,7 @@ class ConfigManager:
|
|||||||
trace = traceback.format_exc()
|
trace = traceback.format_exc()
|
||||||
dialog = ExceptionDialog(self.controller, trace)
|
dialog = ExceptionDialog(self.controller, trace)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def save_res_and_quit(self, tv: "ServerTreeView", window: "OuterWindow") -> None:
|
def save_res_and_quit(self, tv: "ServerTreeView", window: "OuterWindow") -> None:
|
||||||
columns = tv.get_columns()
|
columns = tv.get_columns()
|
||||||
|
|||||||
@@ -48,18 +48,13 @@ from dzgui.strings.dialogs import (
|
|||||||
waiting_for_directories,
|
waiting_for_directories,
|
||||||
)
|
)
|
||||||
from dzgui.strings import kb
|
from dzgui.strings import kb
|
||||||
from dzgui.strings.server_mods import checkmark, resync
|
from dzgui.strings.server_mods import checkmark, resync, invalid_mod
|
||||||
from dzgui.util.format import format_exception, format_mib
|
from dzgui.util.format import format_exception, format_mib
|
||||||
from dzgui.util.strings import dialog, server_timeout
|
from dzgui.util.strings import dialog, server_timeout
|
||||||
from dzgui.util.symlink import rebuild_symlinks
|
from dzgui.util.symlink import rebuild_symlinks
|
||||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||||
from dzgui.views.dialogs.servers import ServerDetailsDialog, ServerModDialog
|
from dzgui.views.dialogs.servers import ServerDetailsDialog, ServerModDialog
|
||||||
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
from gi.repository import Gtk, GLib # noqa E402
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.api.servers import A2SInfo, Record
|
from dzgui.api.servers import A2SInfo, Record
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -197,9 +192,8 @@ class ConnectionManager:
|
|||||||
|
|
||||||
is_last = self.is_last_server()
|
is_last = self.is_last_server()
|
||||||
|
|
||||||
# TODO: strings
|
|
||||||
invalid_mods = [
|
invalid_mods = [
|
||||||
(mod[0], mod[1]) for mod in remote_mods if mod[2] == "Invalid mod"
|
(mod[0], mod[1]) for mod in remote_mods if mod[2] == invalid_mod
|
||||||
]
|
]
|
||||||
|
|
||||||
prereqs = Prerequisites(
|
prereqs = Prerequisites(
|
||||||
@@ -277,8 +271,7 @@ class ConnectionManager:
|
|||||||
for mod in alpha_mods:
|
for mod in alpha_mods:
|
||||||
# NOTE: if the mod is neither synched or out of date, it is a malformed mod
|
# NOTE: if the mod is neither synched or out of date, it is a malformed mod
|
||||||
if mod[2] == "":
|
if mod[2] == "":
|
||||||
# TODO: strings
|
mod[2] = invalid_mod
|
||||||
mod[2] = "Invalid mod"
|
|
||||||
|
|
||||||
return alpha_mods, missing_mods
|
return alpha_mods, missing_mods
|
||||||
|
|
||||||
@@ -298,22 +291,22 @@ class ConnectionManager:
|
|||||||
|
|
||||||
def _present_modlist_dialog(self, mods: list[list[str]]) -> None:
|
def _present_modlist_dialog(self, mods: list[list[str]]) -> None:
|
||||||
dialog = ServerModDialog(self.controller, mods)
|
dialog = ServerModDialog(self.controller, mods)
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def _present_details_dialog(self, details: Servers.Details) -> None:
|
def _present_details_dialog(self, details: Servers.Details) -> None:
|
||||||
dialog = ServerDetailsDialog(self.controller, details)
|
dialog = ServerDetailsDialog(self.controller, details)
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def _workshop_timeout(self, msg: str) -> None:
|
def _workshop_timeout(self, msg: str) -> None:
|
||||||
dialog = ExceptionDialog(self.controller, msg)
|
dialog = ExceptionDialog(self.controller, msg)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def _server_timeout(self) -> None:
|
def _server_timeout(self) -> None:
|
||||||
dialog = ExceptionDialog(self.controller, server_timeout)
|
dialog = ExceptionDialog(self.controller, server_timeout)
|
||||||
dialog.set_details_buffer(kb.DZG_006)
|
dialog.set_details_buffer(kb.DZG_006)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def get_debug_args(self) -> str:
|
def get_debug_args(self) -> str:
|
||||||
addr = f"{self.record.ip}:{self.record.gameport}"
|
addr = f"{self.record.ip}:{self.record.gameport}"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import logging
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.const.enum import ContextMenu, Preferences
|
from dzgui.const.enum import ContextMenuItem, Preferences
|
||||||
from dzgui.util.clip import copy_clipboard
|
from dzgui.util.clip import copy_clipboard
|
||||||
from dzgui.util.open_links import open_workshop_page
|
from dzgui.util.open_links import open_workshop_page
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class ContextMenuManager:
|
|||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.treeview = treeview
|
self.treeview = treeview
|
||||||
|
|
||||||
def process(self, action: ContextMenu) -> None:
|
def process(self, action: ContextMenuItem) -> None:
|
||||||
if isinstance(self.treeview, ServerTreeView):
|
if isinstance(self.treeview, ServerTreeView):
|
||||||
record = self.treeview.get_record()
|
record = self.treeview.get_record()
|
||||||
if record is None:
|
if record is None:
|
||||||
@@ -42,14 +42,14 @@ class ContextMenuManager:
|
|||||||
|
|
||||||
match action:
|
match action:
|
||||||
# UNTHREADED
|
# UNTHREADED
|
||||||
case ContextMenu.ADD_NOTE:
|
case ContextMenuItem.ADD_NOTE:
|
||||||
dialog = NoteDialog(self.controller)
|
dialog = NoteDialog(self.controller)
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
case ContextMenu.COPY_SERVER_IP:
|
case ContextMenuItem.COPY_SERVER_IP:
|
||||||
self.copy_server_ip()
|
self.copy_server_ip()
|
||||||
case ContextMenu.COPY_SERVER_NAME:
|
case ContextMenuItem.COPY_SERVER_NAME:
|
||||||
self.copy_server_name()
|
self.copy_server_name()
|
||||||
case ContextMenu.SET_FAV:
|
case ContextMenuItem.SET_FAV:
|
||||||
name = self.treeview.get_value_at_index(0)
|
name = self.treeview.get_value_at_index(0)
|
||||||
# NOTE: fully qualified ip
|
# NOTE: fully qualified ip
|
||||||
fqip = self.treeview.get_record_string()
|
fqip = self.treeview.get_record_string()
|
||||||
@@ -58,36 +58,40 @@ class ContextMenuManager:
|
|||||||
self.controller.set_fav(name, fqip, simple)
|
self.controller.set_fav(name, fqip, simple)
|
||||||
|
|
||||||
# THREADED
|
# THREADED
|
||||||
case ContextMenu.ADD_SERVER:
|
case ContextMenuItem.ADD_SERVER:
|
||||||
self.controller.add_by_record(record)
|
self.controller.add_by_record(record)
|
||||||
case ContextMenu.CONNECT:
|
case ContextMenuItem.CONNECT:
|
||||||
self.controller.connect_by_record(record)
|
self.controller.connect_by_record(record)
|
||||||
case ContextMenu.REFRESH_PLAYERS:
|
case ContextMenuItem.REFRESH_PLAYERS:
|
||||||
self.controller.refresh_players(record)
|
self.controller.refresh_players(record)
|
||||||
case ContextMenu.REMOVE_HISTORY:
|
case ContextMenuItem.REMOVE_HISTORY:
|
||||||
self.controller.remove_from_history(record)
|
self.controller.remove_from_history(record)
|
||||||
case ContextMenu.REMOVE_SERVER:
|
case ContextMenuItem.REMOVE_SERVER:
|
||||||
self.controller.remove_by_record(record)
|
self.controller.remove_by_record(record)
|
||||||
case ContextMenu.SHOW_DETAILS:
|
case ContextMenuItem.SHOW_DETAILS:
|
||||||
self.controller.get_details(record)
|
self.controller.get_details(record)
|
||||||
case ContextMenu.SHOW_MODS:
|
case ContextMenuItem.SHOW_MODS:
|
||||||
self.controller.get_modlist(record)
|
self.controller.get_modlist(record)
|
||||||
|
|
||||||
if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
|
if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)):
|
||||||
match action:
|
match action:
|
||||||
case ContextMenu.UNSUB_MOD:
|
case ContextMenuItem.OPEN_WORKSHOP:
|
||||||
self.controller.unsub_mods(self.treeview)
|
|
||||||
case ContextMenu.OPEN_WORKSHOP:
|
|
||||||
self.open_mod_page()
|
self.open_mod_page()
|
||||||
|
case ContextMenuItem.UNSUB_MOD:
|
||||||
|
self.controller.unsub_mods(self.treeview)
|
||||||
|
case ContextMenuItem.VALIDATE_MOD:
|
||||||
|
self.controller.validate_mods(self.treeview)
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
if isinstance(self.treeview, LogTreeView):
|
if isinstance(self.treeview, LogTreeView):
|
||||||
match action:
|
match action:
|
||||||
case ContextMenu.COPY_LOG_CLIPBOARD:
|
case ContextMenuItem.COPY_LOG_CLIPBOARD:
|
||||||
self.copy_log()
|
self.copy_log()
|
||||||
|
|
||||||
if isinstance(self.treeview, ServerModTreeView):
|
if isinstance(self.treeview, ServerModTreeView):
|
||||||
match action:
|
match action:
|
||||||
case ContextMenu.OPEN_WORKSHOP:
|
case ContextMenuItem.OPEN_WORKSHOP:
|
||||||
self.open_mod_page()
|
self.open_mod_page()
|
||||||
|
|
||||||
def copy_server_ip(self) -> None:
|
def copy_server_ip(self) -> None:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from dzgui.model.model_factory import ModelFactory
|
from dzgui.model.model_factory import ModelFactory
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
from dzgui.util.strings import all_maps
|
from dzgui.strings.filters import all_maps
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.model.model_factory import FastInsertListStore
|
from dzgui.model.model_factory import FastInsertListStore
|
||||||
@@ -51,7 +51,6 @@ class FilterManager:
|
|||||||
self.active_map = (0, all_maps)
|
self.active_map = (0, all_maps)
|
||||||
self.prior_map = all_maps
|
self.prior_map = all_maps
|
||||||
|
|
||||||
self.filters: list
|
|
||||||
self.enabled_filters = dict(self.default_filters)
|
self.enabled_filters = dict(self.default_filters)
|
||||||
|
|
||||||
def get_tooltips(self) -> dict[str, str]:
|
def get_tooltips(self) -> dict[str, str]:
|
||||||
@@ -75,11 +74,11 @@ class FilterManager:
|
|||||||
def set_active_keyword(self, word: str) -> None:
|
def set_active_keyword(self, word: str) -> None:
|
||||||
self.active_keyword = word
|
self.active_keyword = word
|
||||||
|
|
||||||
def get_default_filters(self) -> dict:
|
def get_default_filters(self) -> dict[str, bool]:
|
||||||
"""Deep copy of defaults"""
|
"""Deep copy of defaults"""
|
||||||
return dict(self.default_filters)
|
return dict(self.default_filters)
|
||||||
|
|
||||||
def get_filters(self) -> dict:
|
def get_filters(self) -> dict[str, bool]:
|
||||||
return self.enabled_filters
|
return self.enabled_filters
|
||||||
|
|
||||||
def set_filter(self, label: str, state: bool) -> None:
|
def set_filter(self, label: str, state: bool) -> None:
|
||||||
@@ -106,17 +105,11 @@ class FilterManager:
|
|||||||
|
|
||||||
self.reinit_map_store()
|
self.reinit_map_store()
|
||||||
maps.sort()
|
maps.sort()
|
||||||
for m in maps:
|
clean = [m for m in maps if m != all_maps]
|
||||||
# TODO: strings
|
for m in clean:
|
||||||
if m == "All maps":
|
|
||||||
continue
|
|
||||||
self.append_map([m])
|
self.append_map([m])
|
||||||
|
|
||||||
# TODO: currently unused
|
def get_all_filters(self) -> tuple[str, ...]:
|
||||||
def get_unique_maps(self) -> list[str]:
|
|
||||||
return [row[0] for row in self.map_store if row[0] != "All maps"]
|
|
||||||
|
|
||||||
def get_all_filters(self) -> tuple:
|
|
||||||
map_name = self.get_active_map_name()
|
map_name = self.get_active_map_name()
|
||||||
enabled = self.get_filters()
|
enabled = self.get_filters()
|
||||||
kw = self.get_active_keyword()
|
kw = self.get_active_keyword()
|
||||||
|
|||||||
+28
-4
@@ -4,7 +4,7 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.api.steam import unsubscribe
|
from dzgui.api.steam import unsubscribe, validate_mod
|
||||||
from dzgui.api.mods import (
|
from dzgui.api.mods import (
|
||||||
get_delimited_mods,
|
get_delimited_mods,
|
||||||
find_stale_mods,
|
find_stale_mods,
|
||||||
@@ -30,7 +30,7 @@ import dzgui.api.pefile as PeFile
|
|||||||
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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -85,7 +85,7 @@ class ModManager:
|
|||||||
total_mods = len(self.store)
|
total_mods = len(self.store)
|
||||||
self.emitter.emit("mods_updated", msg, total_mods)
|
self.emitter.emit("mods_updated", msg, total_mods)
|
||||||
|
|
||||||
def unsub_mods(self) -> None:
|
def get_selected_mods(self) -> list[tuple[str, Gtk.TreeIter]]:
|
||||||
sel = self.treeview.get_selection()
|
sel = self.treeview.get_selection()
|
||||||
model, pathlist = sel.get_selected_rows()
|
model, pathlist = sel.get_selected_rows()
|
||||||
# NOTE: reverse when multiple selection
|
# NOTE: reverse when multiple selection
|
||||||
@@ -96,6 +96,15 @@ class ModManager:
|
|||||||
continue
|
continue
|
||||||
mod, _iter = res
|
mod, _iter = res
|
||||||
mods.append((mod, _iter))
|
mods.append((mod, _iter))
|
||||||
|
return mods
|
||||||
|
|
||||||
|
def validate_mods(self) -> None:
|
||||||
|
mods = self.get_selected_mods()
|
||||||
|
self.thread_man.set_job_count(len(mods))
|
||||||
|
self.validate_all_mods(mods)
|
||||||
|
|
||||||
|
def unsub_mods(self) -> None:
|
||||||
|
mods = self.get_selected_mods()
|
||||||
self.thread_man.set_job_count(len(mods))
|
self.thread_man.set_job_count(len(mods))
|
||||||
self.unsub_all_mods(mods)
|
self.unsub_all_mods(mods)
|
||||||
|
|
||||||
@@ -109,6 +118,21 @@ class ModManager:
|
|||||||
mod = model.get_value(tree_iter, 2)
|
mod = model.get_value(tree_iter, 2)
|
||||||
return mod, tree_iter
|
return mod, tree_iter
|
||||||
|
|
||||||
|
def validate_atomic_mod(self, mod: str) -> None:
|
||||||
|
# NOTE: propagation to dayz exp will happen during
|
||||||
|
# subsequent symlink phase on connect
|
||||||
|
client = self.controller.query_config(Preferences.CLIENT)
|
||||||
|
validate_mod(client, mod)
|
||||||
|
|
||||||
|
@call_on_thread(dialogs.validating_mods)
|
||||||
|
def validate_all_mods(self, mods: list[tuple[str, Gtk.TreeIter]]) -> None:
|
||||||
|
for mod, _iter in mods:
|
||||||
|
self.validate_atomic_mod(mod)
|
||||||
|
self.thread_man.increment_dialog()
|
||||||
|
time.sleep(API_RATE_LIMIT)
|
||||||
|
func = StoredFunc(lambda: None)
|
||||||
|
self.thread_man.set_cleanup_func(func)
|
||||||
|
|
||||||
@call_on_thread(dialogs.deleting_mods)
|
@call_on_thread(dialogs.deleting_mods)
|
||||||
def unsub_all_mods(self, mods: list[tuple[str, Gtk.TreeIter]]) -> None:
|
def unsub_all_mods(self, mods: list[tuple[str, Gtk.TreeIter]]) -> None:
|
||||||
for mod, _iter in mods:
|
for mod, _iter in mods:
|
||||||
@@ -197,7 +221,7 @@ class ModManager:
|
|||||||
total_size += mod[3]
|
total_size += mod[3]
|
||||||
return total_mods, total_size
|
return total_mods, total_size
|
||||||
|
|
||||||
def _on_stale_mods_found(self, stale_mods: list) -> None:
|
def _on_stale_mods_found(self, stale_mods: list[int]) -> None:
|
||||||
"""Manipulates attached ListStore in the main event loop"""
|
"""Manipulates attached ListStore in the main event loop"""
|
||||||
model = self.treeview.get_model()
|
model = self.treeview.get_model()
|
||||||
if model is None:
|
if model is None:
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class OfflineManager:
|
|||||||
is_valid = is_mission(folder)
|
is_valid = is_mission(folder)
|
||||||
self.emitter.emit("custom_mission_loaded", str(folder), is_valid)
|
self.emitter.emit("custom_mission_loaded", str(folder), is_valid)
|
||||||
|
|
||||||
def find_custom_mods(self, callback: Callable) -> None:
|
def find_custom_mods(self, callback: Callable[[], None]) -> None:
|
||||||
folder = self.open_folderpicker(dialogs.custom_mod_dialog)
|
folder = self.open_folderpicker(dialogs.custom_mod_dialog)
|
||||||
window = self.controller.get_window()
|
window = self.controller.get_window()
|
||||||
window.set_sensitive(False)
|
window.set_sensitive(False)
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ if TYPE_CHECKING:
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib # noqa E402
|
from gi.repository import GLib
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
def call_on_thread(
|
def call_on_thread(
|
||||||
dialog_str: str, show_dialog: bool = True, show_cancel: bool = False
|
dialog_str: str, show_dialog: bool = True, show_cancel: bool = False
|
||||||
) -> Callable:
|
) -> Callable[..., Any]:
|
||||||
def decorator(func: Callable) -> Callable:
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args: Any, **kwargs: Any) -> None:
|
def wrapper(*args: Any, **kwargs: Any) -> None:
|
||||||
self = args[0]
|
self = args[0]
|
||||||
@@ -42,7 +42,7 @@ def call_on_thread(
|
|||||||
|
|
||||||
|
|
||||||
class StoredFunc:
|
class StoredFunc:
|
||||||
def __init__(self, func: Callable, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> None:
|
||||||
sig = inspect.signature(func)
|
sig = inspect.signature(func)
|
||||||
self.func = func
|
self.func = func
|
||||||
self.bindings = sig.bind(*args, **kwargs)
|
self.bindings = sig.bind(*args, **kwargs)
|
||||||
|
|||||||
@@ -15,11 +15,6 @@ from dzgui.strings import dialogs
|
|||||||
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
||||||
from dzgui.views.dialogs.generic import ExceptionDialog, QuitDialog
|
from dzgui.views.dialogs.generic import ExceptionDialog, QuitDialog
|
||||||
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
from gi.repository import Gtk, GLib # noqa E402
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
@@ -60,9 +55,9 @@ class UpdateManager:
|
|||||||
def _on_update_success(self) -> None:
|
def _on_update_success(self) -> None:
|
||||||
msg = dialogs.update_success
|
msg = dialogs.update_success
|
||||||
dialog = QuitDialog(self.controller, msg)
|
dialog = QuitDialog(self.controller, msg)
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def _on_update_failure(self, msg: str) -> None:
|
def _on_update_failure(self, msg: str) -> None:
|
||||||
dialog = ExceptionDialog(self.controller, msg)
|
dialog = ExceptionDialog(self.controller, msg)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
@@ -7,11 +7,11 @@ from dzgui.util.strings import delimiter
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository.Gtk import ListStore, TreeIter # noqa E402
|
from gi.repository.Gtk import ListStore, TreeIter
|
||||||
from gi.repository import GObject # noqa E402
|
from gi.repository import GObject
|
||||||
|
|
||||||
GTYPE_TO_PYTHON = {
|
GTYPE_TO_PYTHON = {
|
||||||
GObject.type_from_name(GObject.type_name(ptype)): ptype
|
GObject.type_from_name(GObject.type_name(ptype)): ptype # type: ignore[no-untyped-call]
|
||||||
for ptype in (int, float, str, bool, object)
|
for ptype in (int, float, str, bool, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ class ModelFactory:
|
|||||||
|
|
||||||
def make_help_store(self) -> FastInsertListStore:
|
def make_help_store(self) -> FastInsertListStore:
|
||||||
store = self.new_model_from_class(MenuCols)
|
store = self.new_model_from_class(MenuCols)
|
||||||
rows = [[row.dict["label"], row] for row in HELP_MENU_ROWS]
|
rows = [[row.label, row] for row in HELP_MENU_ROWS]
|
||||||
store.extend(rows)
|
store.extend(rows)
|
||||||
return store
|
return store
|
||||||
|
|
||||||
|
|||||||
+168
-80
@@ -1,10 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from typing import Any, TYPE_CHECKING, Union
|
from dataclasses import dataclass, fields
|
||||||
|
from operator import attrgetter
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.enum import FilterMode
|
from dzgui.const.enum import FilterMode
|
||||||
from dzgui.model.model_factory import ModelFactory
|
from dzgui.model.model_factory import ModelFactory
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
|
from dzgui.strings import filters as str_filters
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.managers.filter import FilterManager
|
from dzgui.managers.filter import FilterManager
|
||||||
@@ -16,6 +19,117 @@ DAY_REG = r"([0][6-9]|[1][0-7])"
|
|||||||
NIGHT_REG = r"([0][0-5]|[1][8-9]|[2][0-3])"
|
NIGHT_REG = r"([0][0-5]|[1][8-9]|[2][0-3])"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True, frozen=True)
|
||||||
|
class ServerRow:
|
||||||
|
name: str
|
||||||
|
mapname: str
|
||||||
|
view: str
|
||||||
|
time: str
|
||||||
|
players: int
|
||||||
|
max_players: int
|
||||||
|
queue: int
|
||||||
|
ip: str
|
||||||
|
qport: int
|
||||||
|
ping: str
|
||||||
|
provider: str
|
||||||
|
modded: bool
|
||||||
|
|
||||||
|
# NOTE: construct getter once for fast lookup in lieu of field introspection
|
||||||
|
_to_tuple = attrgetter(
|
||||||
|
"name",
|
||||||
|
"mapname",
|
||||||
|
"view",
|
||||||
|
"time",
|
||||||
|
"players",
|
||||||
|
"max_players",
|
||||||
|
"queue",
|
||||||
|
"ip",
|
||||||
|
"qport",
|
||||||
|
"ping",
|
||||||
|
"provider",
|
||||||
|
"modded",
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: tests for marshalled row insertion
|
||||||
|
@classmethod
|
||||||
|
def marshal_rows(
|
||||||
|
cls, rows: list["ServerRow"]
|
||||||
|
) -> list[tuple[str, str, str, str, int, int, int, str, int, str, str, bool]]:
|
||||||
|
"""Marshals data before passing into FastInsertListStore's extend() method"""
|
||||||
|
getter = cls._to_tuple
|
||||||
|
return [getter(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
class FiltrationManager:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
|
||||||
|
self.filter_map = {
|
||||||
|
strings.filter_3pp: self.filter_3pp,
|
||||||
|
strings.filter_1pp: self.filter_1pp,
|
||||||
|
strings.filter_official: self.filter_official,
|
||||||
|
strings.filter_unofficial: self.filter_unofficial,
|
||||||
|
strings.filter_empty: self.filter_empty,
|
||||||
|
strings.filter_full: self.filter_full,
|
||||||
|
strings.filter_duplicate: self.filter_duplicate,
|
||||||
|
strings.filter_day: self.filter_day,
|
||||||
|
strings.filter_night: self.filter_night,
|
||||||
|
strings.filter_nonascii: self.filter_nonascii,
|
||||||
|
strings.filter_lowpop: self.filter_lowpop,
|
||||||
|
strings.filter_modded: self.filter_modded,
|
||||||
|
strings.filter_unmodded: self.filter_unmodded,
|
||||||
|
}
|
||||||
|
|
||||||
|
def filter_3pp(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.view != strings.filter_3pp]
|
||||||
|
|
||||||
|
def filter_1pp(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.view != strings.filter_1pp]
|
||||||
|
|
||||||
|
def filter_official(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.provider != strings.filter_official]
|
||||||
|
|
||||||
|
def filter_unofficial(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.provider != strings.filter_unofficial]
|
||||||
|
|
||||||
|
def filter_empty(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.players != 0]
|
||||||
|
|
||||||
|
def filter_full(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.players != row.max_players]
|
||||||
|
|
||||||
|
def filter_duplicate(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
seen = set()
|
||||||
|
final = []
|
||||||
|
for row in rows:
|
||||||
|
if row.name not in seen:
|
||||||
|
seen.add(row.name)
|
||||||
|
final.append(row)
|
||||||
|
rows = final
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def filter_day(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if not re.match(DAY_REG, row.time)]
|
||||||
|
|
||||||
|
def filter_night(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if not re.match(NIGHT_REG, row.time)]
|
||||||
|
|
||||||
|
def filter_nonascii(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.name.isascii()]
|
||||||
|
|
||||||
|
def filter_lowpop(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if (row.players / row.max_players * 100) > 30]
|
||||||
|
|
||||||
|
def filter_modded(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if not row.modded]
|
||||||
|
|
||||||
|
def filter_unmodded(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
return [row for row in rows if row.modded]
|
||||||
|
|
||||||
|
def filter(self, filter_type: str, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
|
func = self.filter_map[filter_type]
|
||||||
|
return func(rows)
|
||||||
|
|
||||||
|
|
||||||
class ProxyModelManager:
|
class ProxyModelManager:
|
||||||
"""
|
"""
|
||||||
Manages access to cached FastInsertListStore resources and
|
Manages access to cached FastInsertListStore resources and
|
||||||
@@ -30,20 +144,16 @@ class ProxyModelManager:
|
|||||||
|
|
||||||
def __init__(self, filter_man: "FilterManager") -> None:
|
def __init__(self, filter_man: "FilterManager") -> None:
|
||||||
self.filter_cache: dict[
|
self.filter_cache: dict[
|
||||||
tuple[str],
|
tuple[str, ...],
|
||||||
list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]],
|
list[ServerRow],
|
||||||
] = {}
|
] = {}
|
||||||
|
|
||||||
self.proxy_model: "FastInsertListStore"
|
self.proxy_model: "FastInsertListStore"
|
||||||
self.filter_man = filter_man
|
self.filter_man = filter_man
|
||||||
|
self.filtration_manager = FiltrationManager()
|
||||||
|
|
||||||
self.control_model: (
|
self.control_model: list[ServerRow] | None = None
|
||||||
list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]]
|
self.filtered: list[ServerRow] = []
|
||||||
| None
|
|
||||||
) = None
|
|
||||||
self.filtered: list[
|
|
||||||
tuple[str, str, str, str, int, int, int, str, int, int, str, bool]
|
|
||||||
] = []
|
|
||||||
self.success = True
|
self.success = True
|
||||||
|
|
||||||
def has_control_model(self) -> bool:
|
def has_control_model(self) -> bool:
|
||||||
@@ -51,12 +161,7 @@ class ProxyModelManager:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def append_row(self, row: list) -> None:
|
def append_row_to_control(self, row: ServerRow) -> None:
|
||||||
self.proxy_model.append(row)
|
|
||||||
|
|
||||||
def append_row_to_control(
|
|
||||||
self, row: tuple[str, str, str, str, int, int, int, str, int, int, str, bool]
|
|
||||||
) -> None:
|
|
||||||
if self.control_model is None:
|
if self.control_model is None:
|
||||||
raise AttributeError("Trying to add rows to a non-existent model")
|
raise AttributeError("Trying to add rows to a non-existent model")
|
||||||
self.control_model.append(row)
|
self.control_model.append(row)
|
||||||
@@ -64,22 +169,24 @@ class ProxyModelManager:
|
|||||||
|
|
||||||
def update_playercount(self, playercount: "NewPlayerCount") -> None:
|
def update_playercount(self, playercount: "NewPlayerCount") -> None:
|
||||||
treeiter = playercount.treeiter
|
treeiter = playercount.treeiter
|
||||||
self.proxy_model[treeiter][4] = playercount.players
|
field = [f.name for f in fields(ServerRow)]
|
||||||
self.proxy_model[treeiter][6] = playercount.queue
|
index_players = field.index("players")
|
||||||
|
index_queue = field.index("queue")
|
||||||
|
self.proxy_model[treeiter][index_players] = playercount.players
|
||||||
|
self.proxy_model[treeiter][index_queue] = playercount.queue
|
||||||
|
|
||||||
# TODO: use dataclass for record rows
|
|
||||||
def append_row_to_history(
|
def append_row_to_history(
|
||||||
self,
|
self,
|
||||||
history: tuple[str, str, str, str, int, int, int, str, int, int, str, bool],
|
history: ServerRow,
|
||||||
) -> None:
|
) -> None:
|
||||||
addr = history[7]
|
addr = history.ip
|
||||||
qport = history[8]
|
qport = history.qport
|
||||||
|
|
||||||
if self.control_model is None:
|
if self.control_model is None:
|
||||||
raise AttributeError("Trying to append row to empty model")
|
raise AttributeError("Trying to append row to empty model")
|
||||||
found = False
|
found = False
|
||||||
for i, row in enumerate(self.control_model):
|
for i, row in enumerate(self.control_model):
|
||||||
if addr == row[7] and qport == row[8]:
|
if addr == row.ip and qport == row.qport:
|
||||||
item = self.control_model.pop(i)
|
item = self.control_model.pop(i)
|
||||||
self.control_model.append(item)
|
self.control_model.append(item)
|
||||||
found = True
|
found = True
|
||||||
@@ -88,6 +195,7 @@ class ProxyModelManager:
|
|||||||
if found is False:
|
if found is False:
|
||||||
self.control_model.append(history)
|
self.control_model.append(history)
|
||||||
|
|
||||||
|
# NOTE: truncate history to 10 records max
|
||||||
if len(self.control_model) == 11:
|
if len(self.control_model) == 11:
|
||||||
del self.control_model[0]
|
del self.control_model[0]
|
||||||
|
|
||||||
@@ -99,7 +207,7 @@ class ProxyModelManager:
|
|||||||
addr = f"{record.ip}:{record.gameport}"
|
addr = f"{record.ip}:{record.gameport}"
|
||||||
qport = record.qport
|
qport = record.qport
|
||||||
for row in self.control_model:
|
for row in self.control_model:
|
||||||
if addr == row[7] and qport == row[8]:
|
if addr == row.ip and qport == row.qport:
|
||||||
self.control_model.remove(row)
|
self.control_model.remove(row)
|
||||||
break
|
break
|
||||||
self.wipe_cache()
|
self.wipe_cache()
|
||||||
@@ -108,19 +216,22 @@ class ProxyModelManager:
|
|||||||
def clear_proxy_model(self) -> None:
|
def clear_proxy_model(self) -> None:
|
||||||
self.proxy_model.clear()
|
self.proxy_model.clear()
|
||||||
|
|
||||||
def get_proxy_model(self) -> Union["FastInsertListStore", None]:
|
def get_proxy_model(self) -> "FastInsertListStore | None":
|
||||||
if hasattr(self, "proxy_model"):
|
if hasattr(self, "proxy_model"):
|
||||||
return self.proxy_model
|
return self.proxy_model
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def filter(
|
def filter(
|
||||||
self, mode: FilterMode, skip_cache: bool = False
|
self, mode: FilterMode, skip_cache: bool = False
|
||||||
) -> Union["FastInsertListStore", None]:
|
) -> "FastInsertListStore | None":
|
||||||
"""
|
"""
|
||||||
Native Gtk.TreeView.refilter() method was not performant enough
|
Native Gtk.TreeView.refilter() method was not performant enough
|
||||||
when running in the main loop with 40k+ records
|
when running in the main loop with 40k+ records
|
||||||
|
|
||||||
skip_cache: used when updating a record on Saved Servers/History and forcing a refilter
|
skip_cache: used when updating a record on Saved Servers/History and forcing a refilter
|
||||||
|
|
||||||
|
NOTE: data must be marshalled from ServerRow format before constructing list stores
|
||||||
|
The "clone" is the proxy model (list of tuples) actually fed into Gtk.TreeView
|
||||||
"""
|
"""
|
||||||
# TODO: filter cache: return a dataclass object with clearly enumerated map, keyword, and filter values
|
# TODO: filter cache: return a dataclass object with clearly enumerated map, keyword, and filter values
|
||||||
# instead of just a serial list of strings
|
# instead of just a serial list of strings
|
||||||
@@ -129,8 +240,9 @@ class ProxyModelManager:
|
|||||||
if skip_cache is False:
|
if skip_cache is False:
|
||||||
if filters in self.filter_cache:
|
if filters in self.filter_cache:
|
||||||
cached_rows = self.filter_cache[filters]
|
cached_rows = self.filter_cache[filters]
|
||||||
|
marshalled_rows = ServerRow.marshal_rows(cached_rows)
|
||||||
clone = ModelFactory().make_server_store()
|
clone = ModelFactory().make_server_store()
|
||||||
clone.extend(cached_rows)
|
clone.extend(marshalled_rows)
|
||||||
self.set_proxy_model(clone)
|
self.set_proxy_model(clone)
|
||||||
self.set_filtered(cached_rows)
|
self.set_filtered(cached_rows)
|
||||||
return None
|
return None
|
||||||
@@ -149,17 +261,18 @@ class ProxyModelManager:
|
|||||||
clone = ModelFactory().make_server_store()
|
clone = ModelFactory().make_server_store()
|
||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
rows = self.sort_rows(rows)
|
rows = self.sort_rows(rows)
|
||||||
clone.extend(rows)
|
marshalled_rows = ServerRow.marshal_rows(rows)
|
||||||
|
clone.extend(marshalled_rows)
|
||||||
|
|
||||||
self.set_cache(filters, rows)
|
self.set_cache(filters, rows)
|
||||||
self.set_proxy_model(clone)
|
self.set_proxy_model(clone)
|
||||||
return clone
|
return clone
|
||||||
|
|
||||||
def sort_rows(self, rows: list) -> list:
|
def sort_rows(self, rows: list[ServerRow]) -> list[ServerRow]:
|
||||||
rows.sort(key=lambda x: re.sub(r"[^A-Za-z0-9]+", "", x[0].lower()))
|
rows.sort(key=lambda x: re.sub(r"[^A-Za-z0-9]+", "", x.name.lower()))
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def filter_initial(self, filters: tuple) -> list:
|
def filter_initial(self, filters: tuple[str, ...]) -> list[ServerRow]:
|
||||||
"""
|
"""
|
||||||
Simply culls the control model of any disabled filters
|
Simply culls the control model of any disabled filters
|
||||||
"""
|
"""
|
||||||
@@ -168,20 +281,20 @@ class ProxyModelManager:
|
|||||||
self.set_filtered(self.filter_toggle_off(filters, f))
|
self.set_filtered(self.filter_toggle_off(filters, f))
|
||||||
return self.filtered
|
return self.filtered
|
||||||
|
|
||||||
def filter_map(self, filters: tuple) -> list:
|
def filter_map(self, filters: tuple[str, ...]) -> list[ServerRow]:
|
||||||
"""
|
"""
|
||||||
Multi-filtration for any context starts by narrowing by map
|
Multi-filtration for any context starts by narrowing by map
|
||||||
"""
|
"""
|
||||||
rows = self.filtered
|
rows = self.filtered
|
||||||
sel_map = self.filter_man.get_active_map_name()
|
sel_map = self.filter_man.get_active_map_name()
|
||||||
|
|
||||||
if sel_map == strings.all_maps:
|
if sel_map == str_filters.all_maps:
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
rows = [row for row in rows if row[1] == sel_map]
|
rows = [row for row in rows if row.mapname == sel_map]
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def filter_keyword(self, filters: tuple) -> list:
|
def filter_keyword(self, filters: tuple[str, ...]) -> list[ServerRow]:
|
||||||
keyword = self.filter_man.get_active_keyword()
|
keyword = self.filter_man.get_active_keyword()
|
||||||
rows = self.filtered
|
rows = self.filtered
|
||||||
|
|
||||||
@@ -191,13 +304,15 @@ class ProxyModelManager:
|
|||||||
filtered = [
|
filtered = [
|
||||||
row
|
row
|
||||||
for row in rows
|
for row in rows
|
||||||
if keyword in row[0].lower()
|
if keyword in row.name.lower()
|
||||||
or keyword in row[1].lower()
|
or keyword in row.mapname.lower()
|
||||||
or keyword in row[7].lower()
|
or keyword in row.ip.lower()
|
||||||
]
|
]
|
||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
def filter_toggle_off(self, filters: tuple, filter_type: str) -> list:
|
def filter_toggle_off(
|
||||||
|
self, filters: tuple[str, ...], filter_type: str
|
||||||
|
) -> list[ServerRow]:
|
||||||
"""
|
"""
|
||||||
Sub-filtration of the current model
|
Sub-filtration of the current model
|
||||||
"""
|
"""
|
||||||
@@ -211,43 +326,11 @@ class ProxyModelManager:
|
|||||||
self.set_filtered(None)
|
self.set_filtered(None)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
rows = self.filtered
|
# TODO: pack filter type enum in filter checkbox, rather than stringwise comparison
|
||||||
match filter_type:
|
rows = self.filtration_manager.filter(filter_type, self.filtered)
|
||||||
case strings.filter_3pp:
|
|
||||||
rows = [row for row in rows if row[2] != strings.filter_3pp]
|
|
||||||
case strings.filter_1pp:
|
|
||||||
rows = [row for row in rows if row[2] != strings.filter_1pp]
|
|
||||||
case strings.filter_official:
|
|
||||||
rows = [row for row in rows if row[10] != strings.filter_official]
|
|
||||||
case strings.filter_unofficial:
|
|
||||||
rows = [row for row in rows if row[10] != strings.filter_unofficial]
|
|
||||||
case strings.filter_empty:
|
|
||||||
rows = [row for row in rows if row[4] != 0]
|
|
||||||
case strings.filter_full:
|
|
||||||
rows = [row for row in rows if row[4] != row[5]]
|
|
||||||
case strings.filter_duplicate:
|
|
||||||
seen = set()
|
|
||||||
final = []
|
|
||||||
for row in rows:
|
|
||||||
if row[0] not in seen:
|
|
||||||
seen.add(row[0])
|
|
||||||
final.append(row)
|
|
||||||
rows = final
|
|
||||||
case strings.filter_day:
|
|
||||||
rows = [row for row in rows if not re.match(DAY_REG, row[3])]
|
|
||||||
case strings.filter_night:
|
|
||||||
rows = [row for row in rows if not re.match(NIGHT_REG, row[3])]
|
|
||||||
case strings.filter_nonascii:
|
|
||||||
rows = [row for row in rows if row[0].isascii()]
|
|
||||||
case strings.filter_lowpop:
|
|
||||||
rows = [row for row in rows if (row[4] / row[5] * 100) > 30]
|
|
||||||
case strings.filter_modded:
|
|
||||||
rows = [row for row in rows if not row[11]]
|
|
||||||
case strings.filter_unmodded:
|
|
||||||
rows = [row for row in rows if row[11]]
|
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def filter_toggle_on(self, filters: tuple[str]) -> list:
|
def filter_toggle_on(self, filters: tuple[str, ...]) -> list[ServerRow]:
|
||||||
"""Effectively applies all filters"""
|
"""Effectively applies all filters"""
|
||||||
self.set_filtered(self.control_model)
|
self.set_filtered(self.control_model)
|
||||||
self.set_filtered(self.filter_map(filters))
|
self.set_filtered(self.filter_map(filters))
|
||||||
@@ -258,31 +341,36 @@ class ProxyModelManager:
|
|||||||
self.set_filtered(self.filter_toggle_off(filters, f))
|
self.set_filtered(self.filter_toggle_off(filters, f))
|
||||||
return self.filtered
|
return self.filtered
|
||||||
|
|
||||||
def set_cache(self, filters: tuple[str], filtered_rows: list[tuple]) -> None:
|
def set_cache(
|
||||||
|
self, filters: tuple[str, ...], filtered_rows: list[ServerRow]
|
||||||
|
) -> None:
|
||||||
self.filter_cache[filters] = filtered_rows
|
self.filter_cache[filters] = filtered_rows
|
||||||
|
|
||||||
def set_filtered(self, rows: list | None) -> None:
|
def set_filtered(self, rows: list[ServerRow] | None) -> None:
|
||||||
if rows is None:
|
if rows is None:
|
||||||
self.filtered = []
|
self.filtered = []
|
||||||
else:
|
else:
|
||||||
self.filtered = rows
|
self.filtered = rows
|
||||||
|
|
||||||
def get_filtered(self) -> list:
|
def get_filtered(self) -> list[ServerRow]:
|
||||||
return self.filtered
|
return self.filtered
|
||||||
|
|
||||||
def set_proxy_model(self, model: "FastInsertListStore") -> None:
|
def set_proxy_model(self, model: "FastInsertListStore") -> None:
|
||||||
"""
|
"""
|
||||||
FastInsertListStore representation of the raw model after filtration
|
FastInsertListStore representation of the raw model after filtration
|
||||||
|
|
||||||
|
This attribute is saved from within a thread so that it later be retrieved
|
||||||
|
and safely packed into the TreeView in the main loop, cf. get_proxy_model()
|
||||||
"""
|
"""
|
||||||
self.proxy_model = model
|
self.proxy_model = model
|
||||||
|
|
||||||
def set_control(self, rows: list) -> None:
|
def set_control(self, rows: list[ServerRow]) -> None:
|
||||||
"""
|
"""
|
||||||
Raw data model prior to filtration
|
Raw data model prior to filtration
|
||||||
"""
|
"""
|
||||||
self.control_model = rows
|
self.control_model = rows
|
||||||
|
|
||||||
def get_control(self) -> list[Any]:
|
def get_control(self) -> list[ServerRow]:
|
||||||
if self.control_model is None:
|
if self.control_model is None:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"Expected a populated control model, but it is Nonetype"
|
"Expected a populated control model, but it is Nonetype"
|
||||||
@@ -293,7 +381,7 @@ class ProxyModelManager:
|
|||||||
self.filtered = []
|
self.filtered = []
|
||||||
self.filter_cache = {}
|
self.filter_cache = {}
|
||||||
|
|
||||||
def push(self, data: list[Any]) -> None:
|
def push(self, data: list[ServerRow]) -> None:
|
||||||
self.wipe_cache()
|
self.wipe_cache()
|
||||||
self.set_control(data)
|
self.set_control(data)
|
||||||
cur_keyword = self.filter_man.get_active_keyword()
|
cur_keyword = self.filter_man.get_active_keyword()
|
||||||
|
|||||||
+41
-19
@@ -21,12 +21,12 @@ from dzgui.views.dialogs.generic import ExceptionDialog
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib # noqa E402
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.api.servers import A2SInfo, Record
|
from dzgui.api.servers import A2SInfo, Record
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
from dzgui.model.proxy_model import ProxyModelManager
|
from dzgui.model.proxy_model import ProxyModelManager, ServerRow
|
||||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
@@ -92,6 +92,7 @@ class ServerModelManager:
|
|||||||
job = Servers.query_api
|
job = Servers.query_api
|
||||||
params = Servers.params
|
params = Servers.params
|
||||||
servers = []
|
servers = []
|
||||||
|
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor() as executor:
|
||||||
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
@@ -200,26 +201,47 @@ class ServerModelManager:
|
|||||||
|
|
||||||
@call_on_thread(dialog.querying)
|
@call_on_thread(dialog.querying)
|
||||||
def add_by_ip(self, addr: str) -> None:
|
def add_by_ip(self, addr: str) -> None:
|
||||||
res = Servers.query_by_ip(addr)
|
self.preserve_on_fail = True
|
||||||
self._parse_single_record(res)
|
try:
|
||||||
|
res = Servers.query_by_ip(addr)
|
||||||
|
self._parse_single_record(res)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
self.thread_man.set_cleanup_func(
|
||||||
|
StoredFunc(self._cleanup_on_failure), destroy_first=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
@call_on_thread(dialog.querying)
|
@call_on_thread(dialog.querying)
|
||||||
def add_by_record(self, record: Servers.Record) -> None:
|
def add_by_record(self, record: Servers.Record) -> None:
|
||||||
|
self.preserve_on_fail = True
|
||||||
# NOTE: rationale: a raw Record as shown in server browser may resolve to a different IP
|
# NOTE: rationale: a raw Record as shown in server browser may resolve to a different IP
|
||||||
res = Servers.query_by_record(record)
|
try:
|
||||||
self._parse_single_record(res)
|
res = Servers.query_by_record(record)
|
||||||
|
self._parse_single_record(res)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
self.thread_man.set_cleanup_func(
|
||||||
|
StoredFunc(self._cleanup_on_failure), destroy_first=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
@call_on_thread(dialog.querying)
|
@call_on_thread(dialog.querying)
|
||||||
def remove_by_record(self, record: Servers.Record) -> None:
|
def remove_by_record(self, record: Servers.Record) -> None:
|
||||||
res = Servers.query_by_record(record)
|
self.preserve_on_fail = True
|
||||||
self._parse_single_record(res, delete=True)
|
try:
|
||||||
|
res = Servers.query_by_record(record)
|
||||||
|
self._parse_single_record(res, delete=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
self.thread_man.set_cleanup_func(
|
||||||
|
StoredFunc(self._cleanup_on_failure), destroy_first=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
def update_history(
|
def update_history(
|
||||||
self,
|
self,
|
||||||
rows: (
|
rows: list["ServerRow"] | None = None,
|
||||||
list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]]
|
|
||||||
| None
|
|
||||||
) = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
if rows is not None:
|
if rows is not None:
|
||||||
@@ -242,7 +264,6 @@ class ServerModelManager:
|
|||||||
self.first_iteration = False
|
self.first_iteration = False
|
||||||
self.emitter.emit("load_maps", store)
|
self.emitter.emit("load_maps", store)
|
||||||
|
|
||||||
# TODO: dataclass for record rows; check for other dict annotations
|
|
||||||
def add_to_history(self, data: tuple[dict[str, Any], "Record"]) -> None:
|
def add_to_history(self, data: tuple[dict[str, Any], "Record"]) -> None:
|
||||||
row, record = data
|
row, record = data
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
@@ -279,7 +300,6 @@ class ServerModelManager:
|
|||||||
proxy_man.update_playercount(self.playercount)
|
proxy_man.update_playercount(self.playercount)
|
||||||
|
|
||||||
def _parse_single_record(self, response: "A2SInfo", delete: bool = False) -> None:
|
def _parse_single_record(self, response: "A2SInfo", delete: bool = False) -> None:
|
||||||
self.preserve_on_fail = True
|
|
||||||
try:
|
try:
|
||||||
row = response.as_row()
|
row = response.as_row()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -408,12 +428,14 @@ class ServerModelManager:
|
|||||||
if show_dialog:
|
if show_dialog:
|
||||||
dialog = ExceptionDialog(self.controller, dialogs.load_error_lan)
|
dialog = ExceptionDialog(self.controller, dialogs.load_error_lan)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def _cleanup_on_failure(self, show_dialog: bool = True) -> None:
|
def _cleanup_on_failure(self, show_dialog: bool = True) -> None:
|
||||||
# TODO: disable map, keyword, and filter widgets if model is None
|
# TODO: disable map, keyword, and filter widgets if model is None
|
||||||
# signal driven (servers_empty, servers_failed_to_load)
|
# signal driven (servers_empty, servers_failed_to_load)
|
||||||
|
|
||||||
|
self.emitter.emit("servers_timed_out")
|
||||||
|
|
||||||
if self.preserve_on_fail is False:
|
if self.preserve_on_fail is False:
|
||||||
self.tv.set_model(None)
|
self.tv.set_model(None)
|
||||||
filter_man = self.tv.get_filter_man()
|
filter_man = self.tv.get_filter_man()
|
||||||
@@ -425,15 +447,15 @@ class ServerModelManager:
|
|||||||
dialog = ExceptionDialog(self.controller, server_timeout)
|
dialog = ExceptionDialog(self.controller, server_timeout)
|
||||||
dialog.set_details_buffer(kb.DZG_006)
|
dialog.set_details_buffer(kb.DZG_006)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def _push_data(self, data: list[Any]) -> None:
|
def _push_data(self, data: list["ServerRow"]) -> None:
|
||||||
self._get_proxy_man().push(data)
|
self._get_proxy_man().push(data)
|
||||||
self._sort_unique_maps(data)
|
self._sort_unique_maps(data)
|
||||||
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_success))
|
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_success))
|
||||||
|
|
||||||
def _sort_unique_maps(self, data: list) -> None:
|
def _sort_unique_maps(self, data: list[Any]) -> None:
|
||||||
u_maps = set([row[1] for row in data])
|
u_maps = set([row.mapname for row in data])
|
||||||
self._set_new_maps(sorted(u_maps))
|
self._set_new_maps(sorted(u_maps))
|
||||||
|
|
||||||
def _set_new_maps(self, maps: list[str]) -> None:
|
def _set_new_maps(self, maps: list[str]) -> None:
|
||||||
|
|||||||
@@ -1,2 +1,16 @@
|
|||||||
|
clipboard_button = "Copy to clipboard"
|
||||||
|
clipboard_ip_button = "Copy IP to clipboard"
|
||||||
|
|
||||||
|
dock_button_collapse = "Collapse the connection panel"
|
||||||
|
dock_button_expand = "Expand the connection panel"
|
||||||
|
|
||||||
workshop = "Steam Workshop"
|
workshop = "Steam Workshop"
|
||||||
workshop_tooltip = "Open your Workshop subscriptions"
|
workshop_tooltip = "Open your Workshop subscriptions"
|
||||||
|
|
||||||
|
version_notifier = "Copied!"
|
||||||
|
version_tooltip = "Click to copy to clipboard"
|
||||||
|
|
||||||
|
update_button = "Updates available"
|
||||||
|
|
||||||
|
unset_button_label = "Unset"
|
||||||
|
unset_button_tooltip = "Unset this server as favorite"
|
||||||
@@ -9,6 +9,7 @@ update_success = "Updated successfully. Please exit and relaunch."
|
|||||||
load_error_lan = "Failed to find any servers on your network.\nCheck the server query port or your firewall settings."
|
load_error_lan = "Failed to find any servers on your network.\nCheck the server query port or your firewall settings."
|
||||||
fetching_mods = "Fetching mod metadata"
|
fetching_mods = "Fetching mod metadata"
|
||||||
deleting_mods = "Unsubscribing mods"
|
deleting_mods = "Unsubscribing mods"
|
||||||
|
validating_mods = "Validating mods"
|
||||||
scanning_mods = "Scanning mods"
|
scanning_mods = "Scanning mods"
|
||||||
parsing_mods = "Parsing mods"
|
parsing_mods = "Parsing mods"
|
||||||
|
|
||||||
@@ -28,3 +29,5 @@ toggle_calc = "Toggle calculator"
|
|||||||
|
|
||||||
server_details = "Server details"
|
server_details = "Server details"
|
||||||
server_message = "Server message"
|
server_message = "Server message"
|
||||||
|
|
||||||
|
caught_cancel_signal = "Caught cancel signal, cleaning up"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
all_maps = "All maps"
|
||||||
|
filters_heading = "Filters"
|
||||||
|
keyword_placeholder = "Filter by keyword"
|
||||||
|
map_placeholder = "Filter by map"
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
tab_changed_label = "*"
|
||||||
selectable_tree = "Ctrl-click to select multiple; Shift-click to select a range."
|
selectable_tree = "Ctrl-click to select multiple; Shift-click to select a range."
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
dialog_title = "Add a short note/reminder"
|
||||||
|
dialog_subtitle = "Limit 30 chars"
|
||||||
|
delete_button = "Delete note"
|
||||||
|
ok_button = "Update note"
|
||||||
|
current_note = "Current note: '{0}'"
|
||||||
@@ -16,7 +16,9 @@ lan_combo = "LAN"
|
|||||||
fullscreen_eventbox = "This option is not available on Steam Deck."
|
fullscreen_eventbox = "This option is not available on Steam Deck."
|
||||||
save_button = "Save"
|
save_button = "Save"
|
||||||
|
|
||||||
|
api_placeholder = "Enter API key"
|
||||||
api_failed = "API validation failed"
|
api_failed = "API validation failed"
|
||||||
|
api_validate = "Validate"
|
||||||
|
|
||||||
flatpak_label = "Flatpak client"
|
flatpak_label = "Flatpak client"
|
||||||
flatpak_standard = "Standard"
|
flatpak_standard = "Standard"
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
changelog_label = "View changelog"
|
||||||
|
changelog_status = "Opens the DZGUI changelog"
|
||||||
|
|
||||||
|
log_label = "Show debug log"
|
||||||
|
log_status = "Read the DZGUI log generated since startup"
|
||||||
|
|
||||||
|
separator_label = "SEPARATOR"
|
||||||
|
separator_status = ""
|
||||||
|
|
||||||
|
system_log_label = "Output system info to log file"
|
||||||
|
system_log_status = "Dump diagnostic data for troubleshooting"
|
||||||
|
|
||||||
|
docs_label = "Documentation/help files (GitHub) ⧉"
|
||||||
|
docs_status = "Open the DZGUI documentation in a browser"
|
||||||
|
|
||||||
|
docs_fallback_label = "Documentation/help files (Codeberg mirror) ⧉"
|
||||||
|
docs_fallback_status = "Open the DZGUI documentation in a browser"
|
||||||
|
|
||||||
|
bugs_label = "Report a bug (GitHub) ⧉"
|
||||||
|
bugs_status = "Open the DZGUI issue tracker in a browser"
|
||||||
|
|
||||||
|
forum_label = "DZGUI Subreddit ⧉"
|
||||||
|
forum_status = "Open the DZGUI discussion forum in a browser"
|
||||||
|
|
||||||
|
sponsor_label = "Sponsor (GitHub) ⧉"
|
||||||
|
sponsor_status = "Sponsor development of the DZGUI project"
|
||||||
|
|
||||||
|
thanks_label = "Special thanks"
|
||||||
|
thanks_status = "A list of contributors, testers, and sponsors"
|
||||||
@@ -3,4 +3,5 @@ mod_id = "ID"
|
|||||||
up_to_date = "Up to date"
|
up_to_date = "Up to date"
|
||||||
modlist = "Modlist"
|
modlist = "Modlist"
|
||||||
checkmark = "✓"
|
checkmark = "✓"
|
||||||
resync="⟳"
|
resync = "⟳"
|
||||||
|
invalid_mod = "Invalid mod"
|
||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
gi.require_version("Gdk", "3.0")
|
from gi.repository import Gtk, Gdk
|
||||||
from gi.repository import Gtk, Gdk # noqa E402
|
|
||||||
|
|
||||||
|
|
||||||
def copy_clipboard(text: str) -> None:
|
def copy_clipboard(text: str) -> None:
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ from dzgui.const.constants import APP_NAME_LOWER, CSS_PATH
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk # noqa E402
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
|
|
||||||
def add_class(widget: Gtk.Widget, label: str) -> None:
|
def add_class(widget: Gtk.Widget, label: str) -> None:
|
||||||
|
|||||||
+6
-11
@@ -11,6 +11,7 @@ from dzgui.const.enum import Preferences
|
|||||||
from dzgui.config.query import lookup
|
from dzgui.config.query import lookup
|
||||||
from dzgui.init.prefix import get_version
|
from dzgui.init.prefix import get_version
|
||||||
from dzgui.util.redact import redact_home
|
from dzgui.util.redact import redact_home
|
||||||
|
from dzgui.util.strings import unknown
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
@@ -27,28 +28,22 @@ def get_cpu_model() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def print_servers(servers: list[str]) -> str:
|
def print_servers(servers: list[str]) -> str:
|
||||||
s = ""
|
return "\n".join(servers)
|
||||||
for server in servers:
|
|
||||||
s += server + "\n"
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def print_mods(mods: list[int]) -> str:
|
def print_mods(mods: list[int]) -> str:
|
||||||
s = ""
|
return "\n".join(map(str, mods))
|
||||||
for mod in mods:
|
|
||||||
s += str(mod) + "\n"
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def write_diagnostic(config: Path, outfile: Path) -> None:
|
def write_diagnostic(config: Path, outfile: Path) -> None:
|
||||||
date = datetime.now().isoformat()
|
date = datetime.now().isoformat()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# FIXME: ID_LIKE not available on CachyOS
|
# NOTE: ID_LIKE not available on CachyOS
|
||||||
distro = platform.freedesktop_os_release()["ID_LIKE"]
|
distro = platform.freedesktop_os_release()["ID_LIKE"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(e)
|
logger.warn(e)
|
||||||
distro = "Unknown"
|
distro = unknown
|
||||||
|
|
||||||
kernel = os.uname().release
|
kernel = os.uname().release
|
||||||
cpu = get_cpu_model()
|
cpu = get_cpu_model()
|
||||||
@@ -68,7 +63,6 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
|
|||||||
servers = lookup(config, Preferences.IP_LIST)
|
servers = lookup(config, Preferences.IP_LIST)
|
||||||
servers_pretty = print_servers(servers)
|
servers_pretty = print_servers(servers)
|
||||||
|
|
||||||
# FIXME: extraneous newlines in lists of mods
|
|
||||||
template = f"""\
|
template = f"""\
|
||||||
{APP_NAME} version {version}
|
{APP_NAME} version {version}
|
||||||
Date: {date}
|
Date: {date}
|
||||||
@@ -82,6 +76,7 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
|
|||||||
|
|
||||||
Mods:
|
Mods:
|
||||||
{mods_pretty}
|
{mods_pretty}
|
||||||
|
|
||||||
Servers:
|
Servers:
|
||||||
{servers_pretty}
|
{servers_pretty}
|
||||||
"""
|
"""
|
||||||
|
|||||||
+5
-9
@@ -7,11 +7,6 @@ from dzgui.const.constants import APP_NAME
|
|||||||
from dzgui.util.ip import get_coords
|
from dzgui.util.ip import get_coords
|
||||||
from dzgui.util.localize import number
|
from dzgui.util.localize import number
|
||||||
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
from gi.repository import Gtk # noqa E402
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from dzgui.const.enum import ServerTab
|
from dzgui.const.enum import ServerTab
|
||||||
@@ -59,7 +54,7 @@ class CalcDist:
|
|||||||
self,
|
self,
|
||||||
addr: str,
|
addr: str,
|
||||||
enum: "ServerTab",
|
enum: "ServerTab",
|
||||||
result_queue: "Queue",
|
result_queue: "Queue[tuple[str, Haversine | None, ServerTab]]",
|
||||||
controller: "Controller",
|
controller: "Controller",
|
||||||
cache: dict[str, Haversine],
|
cache: dict[str, Haversine],
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -72,7 +67,7 @@ class CalcDist:
|
|||||||
self.ip = self.addr
|
self.ip = self.addr
|
||||||
|
|
||||||
dist = self.compare(self.ip)
|
dist = self.compare(self.ip)
|
||||||
self.result_queue.put([self.addr, dist, self.enum])
|
self.result_queue.put((self.addr, dist, self.enum))
|
||||||
|
|
||||||
def compare(self, remote_ip: str) -> Haversine | None:
|
def compare(self, remote_ip: str) -> Haversine | None:
|
||||||
prefs = self.controller.get_prefs()
|
prefs = self.controller.get_prefs()
|
||||||
@@ -80,8 +75,9 @@ class CalcDist:
|
|||||||
if local is None:
|
if local is None:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
remote = get_coords(prefs.paths.ips, remote_ip)
|
remote = get_coords(prefs.paths.db, remote_ip)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
haversine = Haversine(local.lat, local.lon, remote.lat, remote.lon)
|
haversine = Haversine(local.lat, local.lon, remote.lat, remote.lon)
|
||||||
|
|||||||
+11
-3
@@ -1,12 +1,16 @@
|
|||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, GTK_APP_ID
|
from typing import TYPE_CHECKING
|
||||||
|
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, GTK_APP_ID, TMP_PATH
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
def apply_overrides(xdg: dict[str, str]) -> None:
|
|
||||||
|
def apply_overrides(xdg: dict[str, "Path"]) -> None:
|
||||||
try:
|
try:
|
||||||
# NOTE: D-Bus registration required for sandbox
|
# NOTE: D-Bus registration required for sandbox
|
||||||
args = [
|
args = [
|
||||||
@@ -18,7 +22,11 @@ def apply_overrides(xdg: dict[str, str]) -> None:
|
|||||||
]
|
]
|
||||||
subprocess.run([*args], capture_output=True)
|
subprocess.run([*args], capture_output=True)
|
||||||
|
|
||||||
for path in xdg.values():
|
paths: list[str] = []
|
||||||
|
paths.extend([str(path) for path in xdg.values()])
|
||||||
|
paths.append(TMP_PATH)
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
args = [
|
args = [
|
||||||
"flatpak",
|
"flatpak",
|
||||||
"--user",
|
"--user",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from dzgui.util.localize import number
|
from dzgui.util.localize import number
|
||||||
from dzgui.strings.generic import selectable_tree
|
from dzgui.strings.generic import selectable_tree
|
||||||
@@ -7,7 +8,7 @@ from dzgui.util.strings import no_mods, no_servers, workshop
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
def format_pango(text: str) -> str:
|
def format_pango(text: str) -> str:
|
||||||
@@ -60,7 +61,7 @@ def format_server_mods(mods: int) -> str:
|
|||||||
return f"Found {mods:n} {plural}. {workshop}"
|
return f"Found {mods:n} {plural}. {workshop}"
|
||||||
|
|
||||||
|
|
||||||
def format_player_count(model: Gtk.TreeModel | None, control: list) -> str:
|
def format_player_count(model: Gtk.TreeModel | None, control: list[Any]) -> str:
|
||||||
players = 0
|
players = 0
|
||||||
hits: int
|
hits: int
|
||||||
status: str
|
status: str
|
||||||
|
|||||||
+15
-46
@@ -1,4 +1,5 @@
|
|||||||
import a2s
|
import a2s
|
||||||
|
from ipaddress import ip_address
|
||||||
import requests
|
import requests
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -7,6 +8,7 @@ from dataclasses import dataclass
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.api.servers import Record
|
from dzgui.api.servers import Record
|
||||||
|
from dzgui.config.ipdb import read_from_table
|
||||||
from dzgui.const.constants import REQUEST_TIMEOUT
|
from dzgui.const.constants import REQUEST_TIMEOUT
|
||||||
from dzgui.const.endpoints import IP_ECHO
|
from dzgui.const.endpoints import IP_ECHO
|
||||||
|
|
||||||
@@ -29,58 +31,25 @@ class ReservedIPError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def ip2list(ip: str) -> list:
|
def ip2list(ip: str) -> list[int]:
|
||||||
return [int(digit) for digit in ip.split(".")]
|
return [int(digit) for digit in ip.split(".")]
|
||||||
|
|
||||||
|
|
||||||
# TODO: tests
|
def is_usable(ip: str) -> bool:
|
||||||
def is_reserved(digits: list) -> bool:
|
address = ip_address(ip)
|
||||||
reserved = [0, 10, 127]
|
if address.is_reserved or address.is_private:
|
||||||
|
return False
|
||||||
if digits[0] in reserved:
|
return True
|
||||||
return True
|
|
||||||
if digits[0] == 192 and digits[1] == 168:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_coords(ips: "Path", ip: str) -> Coords:
|
def get_coords(db_path: "Path", ip: str) -> Coords:
|
||||||
split = ip2list(ip)
|
if not is_usable(ip):
|
||||||
|
|
||||||
if is_reserved(split):
|
|
||||||
raise ReservedIPError(f"{ip} is a reserved IP address.")
|
raise ReservedIPError(f"{ip} is a reserved IP address.")
|
||||||
|
try:
|
||||||
prefix = f"^{split[0]}.{split[1]}."
|
lat, lon = read_from_table(db_path, ip)
|
||||||
|
return Coords(lat, lon)
|
||||||
if shutil.which("rg") is not None:
|
except Exception:
|
||||||
args = ["/usr/bin/rg", prefix, str(ips)]
|
raise GeolocationError("No matching records found")
|
||||||
else:
|
|
||||||
args = ["/usr/bin/grep", "-E", prefix, str(ips)]
|
|
||||||
proc = subprocess.run(args, capture_output=True, text=True)
|
|
||||||
|
|
||||||
if proc.returncode != 0:
|
|
||||||
raise GeolocationError("Failed to split records")
|
|
||||||
|
|
||||||
ip_list = proc.stdout.splitlines()
|
|
||||||
|
|
||||||
for address in ip_list:
|
|
||||||
fields = address.split(",")
|
|
||||||
upper = fields[1]
|
|
||||||
|
|
||||||
upper_digits = ip2list(upper)
|
|
||||||
if ip == upper:
|
|
||||||
return Coords(float(fields[-2]), float(fields[-1]))
|
|
||||||
|
|
||||||
if split[2] > upper_digits[2]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if split[2] == upper_digits[2]:
|
|
||||||
if split[3] > upper_digits[3]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if split[2] <= upper_digits[2]:
|
|
||||||
return Coords(float(fields[-2]), float(fields[-1]))
|
|
||||||
raise GeolocationError("No matching records found")
|
|
||||||
|
|
||||||
|
|
||||||
def get_local_ip() -> str:
|
def get_local_ip() -> str:
|
||||||
|
|||||||
+4
-1
@@ -1,6 +1,8 @@
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gdk # noqa E402
|
from gi.repository import Gdk
|
||||||
|
|
||||||
|
|
||||||
def is_navkey(key: int) -> bool:
|
def is_navkey(key: int) -> bool:
|
||||||
nav_keys = [
|
nav_keys = [
|
||||||
@@ -18,6 +20,7 @@ def is_navkey(key: int) -> bool:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_ctrl_mask(event: Gdk.EventKey) -> bool:
|
def is_ctrl_mask(event: Gdk.EventKey) -> bool:
|
||||||
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
def set_surrounding_margins(widget: Gtk.Widget, margin: int) -> None:
|
def set_surrounding_margins(widget: Gtk.Widget, margin: int) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import cast, TYPE_CHECKING
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gdk, GLib
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
from dzgui.views.base import OuterWindow
|
||||||
|
from dzgui.views.mixins.context_mixin import ContextMixin
|
||||||
|
from gi.repository import GdkPixbuf
|
||||||
|
|
||||||
|
# TODO: randomize/mock PII distance values
|
||||||
|
|
||||||
|
|
||||||
|
class IdleScreenshot:
|
||||||
|
def __init__(
|
||||||
|
self, controller: "Controller", window: "OuterWindow", scrot_path: Path
|
||||||
|
) -> None:
|
||||||
|
self.window = window
|
||||||
|
self.controller = controller
|
||||||
|
self.scrot_path = scrot_path
|
||||||
|
|
||||||
|
clock = window.get_frame_clock()
|
||||||
|
if clock is None:
|
||||||
|
raise TypeError("Widget not realized, FrameClock not initialized")
|
||||||
|
clock.connect("after-paint", self.callback)
|
||||||
|
|
||||||
|
def callback(self, clock: Gdk.FrameClock) -> None:
|
||||||
|
GLib.timeout_add(1000, self.check_idle)
|
||||||
|
|
||||||
|
def check_idle(self) -> bool:
|
||||||
|
tv = self.controller.get_active_treeview()
|
||||||
|
loaded = tv.is_loaded()
|
||||||
|
if loaded is True:
|
||||||
|
self.emit_key_press(tv)
|
||||||
|
GLib.timeout_add(1000, self.save_and_quit)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def emit_key_press(self, widget: "ContextMixin") -> None:
|
||||||
|
window = widget.get_parent_window()
|
||||||
|
if window is None:
|
||||||
|
raise TypeError(f"Failed to find a valid parent window for {widget}")
|
||||||
|
event = Gdk.EventKey.new(Gdk.EventType.KEY_PRESS)
|
||||||
|
# NOTE: GTK introspection returns Gdk.Event type
|
||||||
|
event_key = cast(Gdk.EventKey, event)
|
||||||
|
event_key.keyval = Gdk.KEY_l
|
||||||
|
event_key.state = Gdk.ModifierType.CONTROL_MASK
|
||||||
|
event_key.window = window
|
||||||
|
event_key.time = Gdk.CURRENT_TIME
|
||||||
|
event_key.send_event = True
|
||||||
|
# TODO: suppress errors from synthetic key events
|
||||||
|
widget.present_menu(widget, event)
|
||||||
|
|
||||||
|
def save_and_quit(self) -> None:
|
||||||
|
self.save_pixbuf()
|
||||||
|
self.controller.save_res_and_quit()
|
||||||
|
|
||||||
|
def save_pixbuf(self) -> None:
|
||||||
|
pixbuf = self.capture_application()
|
||||||
|
if pixbuf:
|
||||||
|
pixbuf.savev(str(self.scrot_path), "png", [], [])
|
||||||
|
|
||||||
|
def capture_application(self) -> "GdkPixbuf.Pixbuf":
|
||||||
|
# NOTE: composited context menu over X11 window
|
||||||
|
root = Gdk.get_default_root_window()
|
||||||
|
|
||||||
|
xwindow = self.window.get_window()
|
||||||
|
if xwindow is None:
|
||||||
|
raise ValueError("Failed to get a valid X11 window")
|
||||||
|
index, x, y = xwindow.get_origin()
|
||||||
|
width, height = self.window.get_size()
|
||||||
|
|
||||||
|
application = Gdk.pixbuf_get_from_window(root, x, y, width, height)
|
||||||
|
if application is None:
|
||||||
|
raise ValueError("Failed to get a valid GdkPixbuf")
|
||||||
|
return application
|
||||||
@@ -31,6 +31,7 @@ show_details = "Show server details"
|
|||||||
refresh_players = "Refresh player count"
|
refresh_players = "Refresh player count"
|
||||||
open_workshop = "Open in Steam Workshop"
|
open_workshop = "Open in Steam Workshop"
|
||||||
unsub_mod = "Unsubscribe mod"
|
unsub_mod = "Unsubscribe mod"
|
||||||
|
validate_mod = "Validate mod"
|
||||||
copy_name = "Copy name to clipboard"
|
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"
|
||||||
@@ -117,6 +118,7 @@ servers = {
|
|||||||
"0": "toggle filter 10",
|
"0": "toggle filter 10",
|
||||||
"Minus": "toggle filter 11",
|
"Minus": "toggle filter 11",
|
||||||
"Backslash": "toggle filter 12",
|
"Backslash": "toggle filter 12",
|
||||||
|
"Apostrophe": "toggle filter 13",
|
||||||
}
|
}
|
||||||
vim = {
|
vim = {
|
||||||
"j": "Move down a row/scroll up",
|
"j": "Move down a row/scroll up",
|
||||||
@@ -158,9 +160,6 @@ filter_unofficial = "Unofficial"
|
|||||||
filter_modded = "Modded"
|
filter_modded = "Modded"
|
||||||
filter_unmodded = "Unmodded"
|
filter_unmodded = "Unmodded"
|
||||||
|
|
||||||
# maps
|
|
||||||
all_maps = "All maps"
|
|
||||||
|
|
||||||
# state
|
# state
|
||||||
unknown = "Unknown"
|
unknown = "Unknown"
|
||||||
none_provided = "None provided"
|
none_provided = "None provided"
|
||||||
@@ -200,6 +199,8 @@ class ModPanelStrings:
|
|||||||
clear_highlights: str
|
clear_highlights: str
|
||||||
select_stale: str
|
select_stale: str
|
||||||
select_stale_tooltip: str
|
select_stale_tooltip: str
|
||||||
|
validate: str
|
||||||
|
validate_tooltip: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
@@ -249,6 +250,7 @@ class Crumbs:
|
|||||||
developers: str
|
developers: str
|
||||||
default: str
|
default: str
|
||||||
offline: str
|
offline: str
|
||||||
|
connect: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
@@ -319,6 +321,8 @@ mod_panel = ModPanelStrings(
|
|||||||
clear_highlights="Clears highlights and reverts the table to a default state",
|
clear_highlights="Clears highlights and reverts the table to a default state",
|
||||||
select_stale="Select stale",
|
select_stale="Select stale",
|
||||||
select_stale_tooltip="Only selects highlighted stale mods",
|
select_stale_tooltip="Only selects highlighted stale mods",
|
||||||
|
validate="Validate selected",
|
||||||
|
validate_tooltip="Validates selected mods and updates them if necessary",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -434,6 +438,7 @@ crumbs = Crumbs(
|
|||||||
developers="Options > Developers",
|
developers="Options > Developers",
|
||||||
default="Servers > ",
|
default="Servers > ",
|
||||||
offline="Mods > Play offline",
|
offline="Mods > Play offline",
|
||||||
|
connect="Connect",
|
||||||
)
|
)
|
||||||
|
|
||||||
no_mods = "No local mods found."
|
no_mods = "No local mods found."
|
||||||
|
|||||||
+21
-3
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Literal, Self
|
from typing import Any, Literal, Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
@@ -38,7 +38,7 @@ import gi
|
|||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
gi.require_version("GLibUnix", "2.0")
|
gi.require_version("GLibUnix", "2.0")
|
||||||
from gi.repository import Gtk, GLib, GLibUnix, Gdk # type: ignore # noqa E402
|
from gi.repository import Gtk, GLib, GLibUnix, Gdk # type: ignore
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.config.userprefs import UserPrefs
|
from dzgui.config.userprefs import UserPrefs
|
||||||
@@ -47,7 +47,7 @@ logger = logging.getLogger(APP_NAME)
|
|||||||
|
|
||||||
|
|
||||||
class OuterWindow(Gtk.ApplicationWindow):
|
class OuterWindow(Gtk.ApplicationWindow):
|
||||||
def __init__(self, app: Gtk.Application) -> None:
|
def __init__(self, app: "App") -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
application=app, title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER
|
application=app, title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER
|
||||||
)
|
)
|
||||||
@@ -78,6 +78,15 @@ class OuterWindow(Gtk.ApplicationWindow):
|
|||||||
MainController.loaded = True
|
MainController.loaded = True
|
||||||
MainController.populate_model(MainController.get_active_treeview())
|
MainController.populate_model(MainController.get_active_treeview())
|
||||||
|
|
||||||
|
try:
|
||||||
|
from dzgui.util.save_screenshot import IdleScreenshot
|
||||||
|
|
||||||
|
scrot = app.get_automation_param("screenshot_path")
|
||||||
|
self.fullscreen()
|
||||||
|
IdleScreenshot(MainController, self, scrot)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
|
def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
|
||||||
self.halt_proc_and_quit()
|
self.halt_proc_and_quit()
|
||||||
|
|
||||||
@@ -291,6 +300,14 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore
|
|||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self.connect("activate", self._on_activate)
|
self.connect("activate", self._on_activate)
|
||||||
|
|
||||||
|
self.automation_params: dict[str, Any] = {}
|
||||||
|
|
||||||
|
def get_automation_param(self, key: str) -> Any:
|
||||||
|
return self.automation_params[key]
|
||||||
|
|
||||||
|
def set_automation_param(self, key: str, value: Any) -> None:
|
||||||
|
self.automation_params[key] = value
|
||||||
|
|
||||||
def _on_activate(self, app: Self) -> None:
|
def _on_activate(self, app: Self) -> None:
|
||||||
self.win = OuterWindow(app)
|
self.win = OuterWindow(app)
|
||||||
|
|
||||||
@@ -331,4 +348,5 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore
|
|||||||
) -> None:
|
) -> None:
|
||||||
self.win.halt_proc_and_quit()
|
self.win.halt_proc_and_quit()
|
||||||
|
|
||||||
|
|
||||||
MainController = Controller()
|
MainController = Controller()
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, NO_EXPAND, NO_FILL, NO_PADDING
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.const.enum import ButtonType
|
from dzgui.const.enum import ButtonType
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk # noqa E402
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
@@ -19,7 +19,11 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class ContextualButton(Gtk.Button):
|
class ContextualButton(Gtk.Button):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, label: str, opens: "NotebookPage", tooltip: str, context: ButtonType
|
self,
|
||||||
|
label: str,
|
||||||
|
opens: "NotebookPage | None",
|
||||||
|
tooltip: str,
|
||||||
|
context: ButtonType,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
label=label,
|
label=label,
|
||||||
@@ -51,9 +55,9 @@ class ButtonBox(Gtk.Box):
|
|||||||
|
|
||||||
for side_button in ButtonType:
|
for side_button in ButtonType:
|
||||||
button = ContextualButton(
|
button = ContextualButton(
|
||||||
label=side_button.dict["label"],
|
label=side_button.label,
|
||||||
opens=side_button.dict["opens"],
|
opens=side_button.opens,
|
||||||
tooltip=side_button.dict["tooltip"],
|
tooltip=side_button.tooltip,
|
||||||
context=side_button,
|
context=side_button,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,7 +67,7 @@ class ButtonBox(Gtk.Box):
|
|||||||
|
|
||||||
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, NO_PADDING)
|
self.pack_start(button, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
def _focus_first_button(self, emitter: "Emitter") -> None:
|
def _focus_first_button(self, emitter: "Emitter") -> None:
|
||||||
self.buttons[0].grab_focus()
|
self.buttons[0].grab_focus()
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
from typing import Callable, Literal, Self, TYPE_CHECKING, Union
|
from typing import Callable, Literal, Self, TYPE_CHECKING, Union
|
||||||
|
|
||||||
from dzgui.util.clip import copy_clipboard
|
from dzgui.util.clip import copy_clipboard
|
||||||
from dzgui.util.format import pluralize
|
|
||||||
from dzgui.strings import buttons
|
from dzgui.strings import buttons
|
||||||
from dzgui.util.strings import (
|
from dzgui.util.strings import (
|
||||||
alert_button_tooltip,
|
|
||||||
atomic_buttons,
|
atomic_buttons,
|
||||||
)
|
)
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.icons import (
|
||||||
CLIPBOARD,
|
ICON_CLIPBOARD,
|
||||||
CLOSE,
|
ICON_CLOSE,
|
||||||
INPUT_KEYBOARD,
|
ICON_INPUT_KEYBOARD,
|
||||||
LIST_ADD,
|
ICON_LIST_ADD,
|
||||||
REFRESH_ICON,
|
ICON_REFRESH,
|
||||||
STEAM_ICON,
|
ICON_STEAM,
|
||||||
WARNING,
|
ICON_WEB_BROWSER,
|
||||||
WEB_BROWSER,
|
|
||||||
)
|
)
|
||||||
from dzgui.strings import connect_panel
|
from dzgui.strings import connect_panel
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk, GLib # noqa E402
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
@@ -64,7 +61,7 @@ class IconButton(Gtk.Button):
|
|||||||
self.icon = Icon(icon, margin_start=margin_start, margin_end=margin_end)
|
self.icon = Icon(icon, margin_start=margin_start, margin_end=margin_end)
|
||||||
self.set_image(self.icon)
|
self.set_image(self.icon)
|
||||||
self.set_image_position(position)
|
self.set_image_position(position)
|
||||||
self.set_focus_on_click(False)
|
self.set_focus_on_click(False) # type: ignore[no-untyped-call]
|
||||||
self.set_always_show_image(True)
|
self.set_always_show_image(True)
|
||||||
|
|
||||||
def swap_icon(self, icon: str) -> None:
|
def swap_icon(self, icon: str) -> None:
|
||||||
@@ -101,15 +98,13 @@ class LargeIconTextButton(IconButton):
|
|||||||
|
|
||||||
|
|
||||||
class ClipboardButton(IconTextButton):
|
class ClipboardButton(IconTextButton):
|
||||||
def __init__(self, controller: Union["Controller", None], func: Callable) -> None:
|
def __init__(self, func: Callable[[], str]) -> None:
|
||||||
super().__init__(CLIPBOARD, atomic_buttons.copy)
|
super().__init__(ICON_CLIPBOARD, atomic_buttons.copy)
|
||||||
self.controller = controller
|
|
||||||
self.connect("clicked", self._on_button_clicked, func)
|
self.connect("clicked", self._on_button_clicked, func)
|
||||||
|
|
||||||
# TODO: strings
|
self.set_tooltip_text(buttons.clipboard_button)
|
||||||
self.set_tooltip_text("Copy to clipboard")
|
|
||||||
|
|
||||||
def _on_button_clicked(self, button: Self, func: Callable) -> None:
|
def _on_button_clicked(self, button: Self, func: Callable[[], str]) -> None:
|
||||||
def revert() -> Literal[False]:
|
def revert() -> Literal[False]:
|
||||||
self.revert_icon()
|
self.revert_icon()
|
||||||
return False
|
return False
|
||||||
@@ -120,33 +115,30 @@ class ClipboardButton(IconTextButton):
|
|||||||
GLib.timeout_add(600, revert)
|
GLib.timeout_add(600, revert)
|
||||||
|
|
||||||
|
|
||||||
# TODO: determine when controller would be passed to this button or drop
|
|
||||||
class CopyIpButton(ClipboardButton):
|
class CopyIpButton(ClipboardButton):
|
||||||
def __init__(self, controller: Union["Controller", None], func: Callable) -> None:
|
def __init__(self, func: Callable[[], str]) -> None:
|
||||||
super().__init__(controller, func)
|
super().__init__(func)
|
||||||
|
|
||||||
# TODO: strings
|
self.set_tooltip_text(buttons.clipboard_ip_button)
|
||||||
self.set_tooltip_text("Copy IP to clipboard")
|
|
||||||
|
|
||||||
|
|
||||||
class CloseButton(IconTextButton):
|
class CloseButton(IconTextButton):
|
||||||
def __init__(self, label: str) -> None:
|
def __init__(self, label: str) -> None:
|
||||||
super().__init__(icon=CLOSE, label=label)
|
super().__init__(icon=ICON_CLOSE, label=label)
|
||||||
|
|
||||||
# TODO: strings
|
self.set_tooltip_text(buttons.unset_button_tooltip)
|
||||||
self.set_tooltip_text("Unset this server as favorite")
|
|
||||||
|
|
||||||
|
|
||||||
class WebButton(IconTextButton):
|
class WebButton(IconTextButton):
|
||||||
def __init__(self, label: str) -> None:
|
def __init__(self, label: str) -> None:
|
||||||
super().__init__(icon=WEB_BROWSER, label=label)
|
super().__init__(icon=ICON_WEB_BROWSER, label=label)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RefreshButton(IconTextButton):
|
class RefreshButton(IconTextButton):
|
||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
icon=REFRESH_ICON,
|
icon=ICON_REFRESH,
|
||||||
label=atomic_buttons.refresh,
|
label=atomic_buttons.refresh,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -163,18 +155,24 @@ class RefreshButton(IconTextButton):
|
|||||||
self.set_tooltip_text(atomic_buttons.refresh_tooltip)
|
self.set_tooltip_text(atomic_buttons.refresh_tooltip)
|
||||||
|
|
||||||
self.connect("clicked", self._on_refresh_clicked)
|
self.connect("clicked", self._on_refresh_clicked)
|
||||||
emitter.connect("servers_loaded", self.start_decrement)
|
emitter.connect("servers_loaded", self._on_load)
|
||||||
|
emitter.connect("servers_timed_out", self._on_timeout)
|
||||||
|
|
||||||
|
def _on_timeout(self, emitter: "Emitter") -> None:
|
||||||
|
self.is_clicked = True
|
||||||
|
self.start_decrement()
|
||||||
|
|
||||||
|
def _on_load(self, emitter: "Emitter", tab: "ServerTab") -> None:
|
||||||
|
self.start_decrement()
|
||||||
|
|
||||||
def _on_refresh_clicked(self, button: Self) -> None:
|
def _on_refresh_clicked(self, button: Self) -> None:
|
||||||
"""Spawned in a thread"""
|
"""Spawned in a thread"""
|
||||||
if self.controller.get_active_treeview().is_loaded() is False:
|
|
||||||
return
|
|
||||||
self.is_clicked = True
|
self.is_clicked = True
|
||||||
self.controller.refresh_tree()
|
self.controller.refresh_tree()
|
||||||
# TODO: get server tab enum
|
# TODO: get server tab enum
|
||||||
# if LAN tab, reload existing entries in place
|
# if LAN tab, reload existing entries in place
|
||||||
|
|
||||||
def start_decrement(self, emitter: "Emitter", tab: "ServerTab") -> None:
|
def start_decrement(self) -> None:
|
||||||
if self.is_clicked is False:
|
if self.is_clicked is False:
|
||||||
return
|
return
|
||||||
if self.loading:
|
if self.loading:
|
||||||
@@ -205,7 +203,7 @@ class RefreshButton(IconTextButton):
|
|||||||
|
|
||||||
class KeysButton(IconTextButton):
|
class KeysButton(IconTextButton):
|
||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
super().__init__(icon=INPUT_KEYBOARD, label=atomic_buttons.keys)
|
super().__init__(icon=ICON_INPUT_KEYBOARD, label=atomic_buttons.keys)
|
||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.set_margin_top(10)
|
self.set_margin_top(10)
|
||||||
@@ -222,7 +220,7 @@ class KeysButton(IconTextButton):
|
|||||||
|
|
||||||
class SteamConnectButton(LargeIconTextButton):
|
class SteamConnectButton(LargeIconTextButton):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(icon=STEAM_ICON, label=connect_panel.connect_button)
|
super().__init__(icon=ICON_STEAM, label=connect_panel.connect_button)
|
||||||
self.set_tooltip_text(connect_panel.connect_tooltip)
|
self.set_tooltip_text(connect_panel.connect_tooltip)
|
||||||
|
|
||||||
|
|
||||||
@@ -242,33 +240,10 @@ class SteamWorkshopButton(SteamTextButton):
|
|||||||
|
|
||||||
class AddButton(IconTextButton):
|
class AddButton(IconTextButton):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(icon=LIST_ADD, label=connect_panel.add_button)
|
super().__init__(icon=ICON_LIST_ADD, label=connect_panel.add_button)
|
||||||
self.set_tooltip_text(connect_panel.add_tooltip)
|
self.set_tooltip_text(connect_panel.add_tooltip)
|
||||||
|
|
||||||
|
|
||||||
class LoggerAlertsButton(IconTextButton):
|
|
||||||
def __init__(self, warnings: int, errors: int) -> None:
|
|
||||||
warnings_text = ""
|
|
||||||
errors_text = ""
|
|
||||||
separator = ""
|
|
||||||
if warnings > 0:
|
|
||||||
warnings_plural = pluralize("warnings", warnings)
|
|
||||||
warnings_text = f"{warnings} {warnings_plural}"
|
|
||||||
if errors > 0:
|
|
||||||
errors_plural = pluralize("errors", errors)
|
|
||||||
errors_text = f" {errors} {errors_plural}"
|
|
||||||
if warnings + errors > 1:
|
|
||||||
separator = ","
|
|
||||||
concat = warnings_text + separator + errors_text
|
|
||||||
super().__init__(
|
|
||||||
icon=WARNING,
|
|
||||||
label=f"Loaded with{concat}",
|
|
||||||
)
|
|
||||||
self.set_halign(Gtk.Align.END)
|
|
||||||
self.set_hexpand(True)
|
|
||||||
self.set_tooltip_text(alert_button_tooltip)
|
|
||||||
|
|
||||||
|
|
||||||
class SpinnerButton(Gtk.Button):
|
class SpinnerButton(Gtk.Button):
|
||||||
def __init__(self, label: str) -> None:
|
def __init__(self, label: str) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
from typing import Self, TYPE_CHECKING
|
from typing import Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import CARET_DOWN, CARET_UP, UDP_PORT
|
from dzgui.const.icons import ICON_CARET_DOWN, ICON_CARET_UP
|
||||||
|
from dzgui.const.constants import UDP_PORT
|
||||||
from dzgui.model.servers import ServerModelManager
|
from dzgui.model.servers import ServerModelManager
|
||||||
from dzgui.strings import connect_panel
|
from dzgui.strings import connect_panel
|
||||||
|
from dzgui.strings.buttons import (
|
||||||
|
dock_button_collapse,
|
||||||
|
dock_button_expand,
|
||||||
|
unset_button_label,
|
||||||
|
)
|
||||||
from dzgui.util.keys import is_ctrl_mask
|
from dzgui.util.keys import is_ctrl_mask
|
||||||
from dzgui.views.components.buttons import (
|
from dzgui.views.components.buttons import (
|
||||||
AddButton,
|
AddButton,
|
||||||
@@ -18,7 +24,7 @@ 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, Gdk # noqa E402
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -30,27 +36,24 @@ ROWS = 1
|
|||||||
|
|
||||||
class DockButton(IconButton):
|
class DockButton(IconButton):
|
||||||
def __init__(self, child: Gtk.Widget) -> None:
|
def __init__(self, child: Gtk.Widget) -> None:
|
||||||
super().__init__(CARET_DOWN)
|
super().__init__(ICON_CARET_DOWN)
|
||||||
|
|
||||||
self.expanded = True
|
self.expanded = True
|
||||||
self.child = child
|
self.child = child
|
||||||
self.connect("clicked", self._on_click)
|
self.connect("clicked", self._on_click)
|
||||||
|
|
||||||
self.collapse_text = "Collapse the connection panel"
|
|
||||||
self.expand_text = "Expand the connection panel"
|
|
||||||
|
|
||||||
self.set_margin_top(10)
|
self.set_margin_top(10)
|
||||||
self.set_tooltip_text(self.collapse_text)
|
self.set_tooltip_text(dock_button_collapse)
|
||||||
|
|
||||||
def collapse(self) -> None:
|
def collapse(self) -> None:
|
||||||
self.set_image(Icon(CARET_UP))
|
self.set_image(Icon(ICON_CARET_UP))
|
||||||
self.child.hide()
|
self.child.hide()
|
||||||
self.set_tooltip_text(self.expand_text)
|
self.set_tooltip_text(dock_button_expand)
|
||||||
|
|
||||||
def expand(self) -> None:
|
def expand(self) -> None:
|
||||||
self.set_image(Icon(CARET_DOWN))
|
self.set_image(Icon(ICON_CARET_DOWN))
|
||||||
self.child.show()
|
self.child.show()
|
||||||
self.set_tooltip_text(self.collapse_text)
|
self.set_tooltip_text(dock_button_collapse)
|
||||||
|
|
||||||
def _on_click(self, button: Self) -> None:
|
def _on_click(self, button: Self) -> None:
|
||||||
if self.expanded:
|
if self.expanded:
|
||||||
@@ -81,7 +84,6 @@ class LanPanel(Gtk.Frame):
|
|||||||
self.scan = Gtk.Button(label=connect_panel.lan_scan_button)
|
self.scan = Gtk.Button(label=connect_panel.lan_scan_button)
|
||||||
self.scan.connect("clicked", self._on_scan_clicked)
|
self.scan.connect("clicked", self._on_scan_clicked)
|
||||||
|
|
||||||
# TODO: strings
|
|
||||||
self.early_abort = Gtk.CheckButton(label=connect_panel.lan_checkbox)
|
self.early_abort = Gtk.CheckButton(label=connect_panel.lan_checkbox)
|
||||||
self.early_abort.set_active(True)
|
self.early_abort.set_active(True)
|
||||||
self.early_abort.set_tooltip_text(connect_panel.lan_abort_tooltip)
|
self.early_abort.set_tooltip_text(connect_panel.lan_abort_tooltip)
|
||||||
@@ -187,10 +189,9 @@ class FavPanel(Gtk.Frame):
|
|||||||
|
|
||||||
self.fav_button = SteamConnectButton()
|
self.fav_button = SteamConnectButton()
|
||||||
self.fav_button.connect("clicked", self._on_connect_clicked)
|
self.fav_button.connect("clicked", self._on_connect_clicked)
|
||||||
self.copy_button = CopyIpButton(self.controller, self.get_fav_ip)
|
self.copy_button = CopyIpButton(self.get_fav_ip)
|
||||||
|
|
||||||
# TODO: strings
|
self.unset_button = CloseButton(unset_button_label)
|
||||||
self.unset_button = CloseButton("Unset")
|
|
||||||
self.unset_button.connect("clicked", self._on_unset_clicked)
|
self.unset_button.connect("clicked", self._on_unset_clicked)
|
||||||
|
|
||||||
if favorite is None:
|
if favorite is None:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from dzgui.util.format import embolden
|
|||||||
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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -43,7 +43,7 @@ class Breadcrumbs(Gtk.Label):
|
|||||||
label = page.get_current_tab_text()
|
label = page.get_current_tab_text()
|
||||||
self.set_server_crumbs(label)
|
self.set_server_crumbs(label)
|
||||||
else:
|
else:
|
||||||
crumbs = enum.dict["crumbs"]
|
crumbs = enum.crumbs
|
||||||
self.set_crumbs(crumbs)
|
self.set_crumbs(crumbs)
|
||||||
|
|
||||||
def set_server_crumbs(self, suffix: str) -> None:
|
def set_server_crumbs(self, suffix: str) -> None:
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
from typing import Any, Callable, TYPE_CHECKING
|
from typing import Any, Callable, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.api.servers import validate_ip
|
from dzgui.api.servers import validate_ip
|
||||||
from dzgui.const.constants import VIEW_CONCEAL, VIEW_REVEAL
|
from dzgui.const.icons import ICON_VIEW_CONCEAL, ICON_VIEW_REVEAL
|
||||||
from dzgui.util.css import add_class, remove_class
|
from dzgui.util.css import add_class, remove_class
|
||||||
from dzgui.strings import connect_panel
|
from dzgui.strings import connect_panel
|
||||||
|
from dzgui.strings.options import api_placeholder, api_validate
|
||||||
from dzgui.strings.errors import api_popover
|
from dzgui.strings.errors import api_popover
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk, GObject # noqa E402
|
from gi.repository import Gtk, Gdk, GObject
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -38,7 +39,7 @@ class ValidatedEntry(Gtk.Entry):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
controller: "Controller",
|
controller: "Controller",
|
||||||
func: Callable,
|
func: Callable[[str], bool],
|
||||||
placeholder_text: str = "",
|
placeholder_text: str = "",
|
||||||
tooltip_text: str = "",
|
tooltip_text: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -46,7 +47,6 @@ class ValidatedEntry(Gtk.Entry):
|
|||||||
hexpand=True, placeholder_text=placeholder_text, tooltip_text=tooltip_text
|
hexpand=True, placeholder_text=placeholder_text, tooltip_text=tooltip_text
|
||||||
)
|
)
|
||||||
|
|
||||||
self.func: Callable
|
|
||||||
self.set_validation_func(func)
|
self.set_validation_func(func)
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ class ValidatedEntry(Gtk.Entry):
|
|||||||
self.emit("string_validated", False)
|
self.emit("string_validated", False)
|
||||||
remove_class(self, self.classname)
|
remove_class(self, self.classname)
|
||||||
|
|
||||||
def set_validation_func(self, func: Callable) -> None:
|
def set_validation_func(self, func: Callable[[str], bool]) -> None:
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
def insert_icon(self) -> None:
|
def insert_icon(self) -> None:
|
||||||
@@ -141,21 +141,19 @@ class APIEntry(Gtk.Box):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
||||||
|
|
||||||
self.func: Callable | None = None
|
self.func: Callable[[str], None] | None = None
|
||||||
|
|
||||||
# TODO: strings
|
|
||||||
self.entry = Gtk.Entry(
|
self.entry = Gtk.Entry(
|
||||||
width_chars=60, hexpand=True, placeholder_text="Enter API key"
|
width_chars=60, hexpand=True, placeholder_text=api_placeholder
|
||||||
)
|
)
|
||||||
self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, VIEW_REVEAL)
|
self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, ICON_VIEW_REVEAL)
|
||||||
self.entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
|
self.entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
|
||||||
self.entry.set_visibility(False)
|
self.entry.set_visibility(False)
|
||||||
self.entry.connect("changed", self._on_text_changed)
|
self.entry.connect("changed", self._on_text_changed)
|
||||||
self.entry.connect("icon-release", self._on_icon_release)
|
self.entry.connect("icon-release", self._on_icon_release)
|
||||||
self.entry.connect("activate", self._on_field_activated)
|
self.entry.connect("activate", self._on_field_activated)
|
||||||
|
|
||||||
# TODO: strings
|
self.submit = Gtk.Button(label=api_validate)
|
||||||
self.submit = Gtk.Button(label="Validate")
|
|
||||||
self.submit.set_sensitive(False)
|
self.submit.set_sensitive(False)
|
||||||
self.submit.connect("clicked", self._on_submit)
|
self.submit.connect("clicked", self._on_submit)
|
||||||
|
|
||||||
@@ -206,9 +204,9 @@ class APIEntry(Gtk.Box):
|
|||||||
) -> None:
|
) -> None:
|
||||||
visible = widget.get_visibility()
|
visible = widget.get_visibility()
|
||||||
if visible:
|
if visible:
|
||||||
icon, state = VIEW_REVEAL, False
|
icon, state = ICON_VIEW_REVEAL, False
|
||||||
else:
|
else:
|
||||||
icon, state = VIEW_CONCEAL, True
|
icon, state = ICON_VIEW_CONCEAL, True
|
||||||
widget.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
widget.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
||||||
widget.set_visibility(state)
|
widget.set_visibility(state)
|
||||||
|
|
||||||
@@ -222,7 +220,7 @@ class APIEntry(Gtk.Box):
|
|||||||
res = self.func(text)
|
res = self.func(text)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def set_validation_func(self, func: Callable | None) -> None:
|
def set_validation_func(self, func: Callable[[str], None] | None) -> None:
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
def disable_button(self) -> None:
|
def disable_button(self) -> None:
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.enum import NotebookPage
|
from dzgui.const.enum import NotebookPage
|
||||||
from dzgui.const.constants import HELP_BUBBLE
|
from dzgui.const.icons import ICON_HELP_BUBBLE
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk # noqa E402
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -20,7 +20,7 @@ class InfoEventBox(Gtk.EventBox):
|
|||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
self.icon = Gtk.Image.new_from_icon_name(
|
self.icon = Gtk.Image.new_from_icon_name(
|
||||||
HELP_BUBBLE, Gtk.IconSize.LARGE_TOOLBAR
|
ICON_HELP_BUBBLE, Gtk.IconSize.LARGE_TOOLBAR
|
||||||
)
|
)
|
||||||
self.icon.set_opacity(0.8)
|
self.icon.set_opacity(0.8)
|
||||||
box = Gtk.Box()
|
box = Gtk.Box()
|
||||||
|
|||||||
@@ -3,21 +3,22 @@ from typing import Literal, TYPE_CHECKING
|
|||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
EXPAND,
|
|
||||||
NO_EXPAND,
|
|
||||||
NO_FILL,
|
|
||||||
NO_PADDING,
|
|
||||||
SEARCH_ICON,
|
|
||||||
)
|
)
|
||||||
|
from dzgui.const.icons import ICON_SEARCH
|
||||||
from dzgui.util.format import format_exception
|
from dzgui.util.format import format_exception
|
||||||
from dzgui.util.strings import all_maps
|
from dzgui.strings.filters import (
|
||||||
|
all_maps,
|
||||||
|
filters_heading,
|
||||||
|
keyword_placeholder,
|
||||||
|
map_placeholder,
|
||||||
|
)
|
||||||
from dzgui.views.components.maps_combo import MapsCombo
|
from dzgui.views.components.maps_combo import MapsCombo
|
||||||
from dzgui.views.components.labels import BoldLabel
|
from dzgui.views.components.labels import BoldLabel
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk, Pango, GLib # noqa E402
|
from gi.repository import Gtk, Gdk, Pango, GLib
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ class ButtonGrid(Gtk.Grid):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
controller: "Controller",
|
controller: "Controller",
|
||||||
defaults: dict[str, str],
|
defaults: dict[str, bool],
|
||||||
tooltips: dict[str, str],
|
tooltips: dict[str, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -42,7 +43,10 @@ class ButtonGrid(Gtk.Grid):
|
|||||||
self.checks: list[Gtk.CheckButton] = []
|
self.checks: list[Gtk.CheckButton] = []
|
||||||
|
|
||||||
flowbox = Gtk.FlowBox(
|
flowbox = Gtk.FlowBox(
|
||||||
halign=Gtk.Align.CENTER, min_children_per_line=3, max_children_per_line=3, selection_mode=Gtk.SelectionMode.NONE
|
halign=Gtk.Align.CENTER,
|
||||||
|
min_children_per_line=3,
|
||||||
|
max_children_per_line=3,
|
||||||
|
selection_mode=Gtk.SelectionMode.NONE,
|
||||||
)
|
)
|
||||||
|
|
||||||
for check in defaults.keys():
|
for check in defaults.keys():
|
||||||
@@ -93,14 +97,13 @@ class ButtonGrid(Gtk.Grid):
|
|||||||
logger.info(f"User toggled button '{label}' to {state}")
|
logger.info(f"User toggled button '{label}' to {state}")
|
||||||
self.emitter.emit("check_toggled", label, state)
|
self.emitter.emit("check_toggled", label, state)
|
||||||
|
|
||||||
def get_checkboxes(self) -> list:
|
def get_checkboxes(self) -> list[Gtk.CheckButton]:
|
||||||
return self.checks
|
return self.checks
|
||||||
|
|
||||||
|
|
||||||
class KeywordEntry(Gtk.Entry):
|
class KeywordEntry(Gtk.Entry):
|
||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
# TODO :strings
|
super().__init__(placeholder_text=keyword_placeholder)
|
||||||
super().__init__(placeholder_text="Filter by keyword")
|
|
||||||
|
|
||||||
self.keyword = ""
|
self.keyword = ""
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
@@ -109,7 +112,7 @@ class KeywordEntry(Gtk.Entry):
|
|||||||
self.connect("key-press-event", self._on_keypress)
|
self.connect("key-press-event", self._on_keypress)
|
||||||
self.connect("icon-release", lambda *args: self.activate())
|
self.connect("icon-release", lambda *args: self.activate())
|
||||||
|
|
||||||
self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, SEARCH_ICON)
|
self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, ICON_SEARCH)
|
||||||
self.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
|
self.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
|
||||||
|
|
||||||
self.emitter.connect("load_maps", self._on_maps_loaded)
|
self.emitter.connect("load_maps", self._on_maps_loaded)
|
||||||
@@ -174,8 +177,7 @@ class FilterPanel(Gtk.Box):
|
|||||||
self.keyword_entry = KeywordEntry(self.controller)
|
self.keyword_entry = KeywordEntry(self.controller)
|
||||||
self.button_grid = ButtonGrid(self.controller, defaults, tooltips)
|
self.button_grid = ButtonGrid(self.controller, defaults, tooltips)
|
||||||
|
|
||||||
# TODO: strings
|
self.filters_label = BoldLabel(filters_heading)
|
||||||
self.filters_label = BoldLabel("Filters")
|
|
||||||
|
|
||||||
self.emitter.connect(
|
self.emitter.connect(
|
||||||
"request_keyword_focus", lambda _: self.keyword_entry.grab_focus()
|
"request_keyword_focus", lambda _: self.keyword_entry.grab_focus()
|
||||||
@@ -195,13 +197,12 @@ class FilterPanel(Gtk.Box):
|
|||||||
|
|
||||||
self.maps_entry: Gtk.Entry = self.maps_combo.get_child() # type: ignore
|
self.maps_entry: Gtk.Entry = self.maps_combo.get_child() # type: ignore
|
||||||
self.maps_entry.set_completion(completion)
|
self.maps_entry.set_completion(completion)
|
||||||
# TODO: strings
|
self.maps_entry.set_placeholder_text(map_placeholder)
|
||||||
self.maps_entry.set_placeholder_text("Filter by map")
|
|
||||||
self.maps_entry.connect("changed", self._on_map_completion, True)
|
self.maps_entry.connect("changed", self._on_map_completion, True)
|
||||||
self.maps_entry.connect("key-press-event", self._on_map_entry_keypress)
|
self.maps_entry.connect("key-press-event", self._on_map_entry_keypress)
|
||||||
self.maps_entry.connect("activate", self._on_maps_activated)
|
self.maps_entry.connect("activate", self._on_maps_activated)
|
||||||
|
|
||||||
self.maps_combo.pack_start(renderer_text, EXPAND)
|
self.maps_combo.pack_start(renderer_text, expand=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)
|
||||||
|
|
||||||
@@ -211,7 +212,7 @@ class FilterPanel(Gtk.Box):
|
|||||||
self.maps_combo,
|
self.maps_combo,
|
||||||
self.button_grid,
|
self.button_grid,
|
||||||
):
|
):
|
||||||
self.pack_start(el, NO_EXPAND, NO_FILL, NO_PADDING)
|
self.pack_start(el, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
def _on_maps_activated(self, entry: Gtk.Entry) -> None:
|
def _on_maps_activated(self, entry: Gtk.Entry) -> None:
|
||||||
text = entry.get_text()
|
text = entry.get_text()
|
||||||
@@ -305,6 +306,7 @@ class FilterPanel(Gtk.Box):
|
|||||||
Gdk.KEY_0: 9,
|
Gdk.KEY_0: 9,
|
||||||
Gdk.KEY_minus: 10,
|
Gdk.KEY_minus: 10,
|
||||||
Gdk.KEY_backslash: 11,
|
Gdk.KEY_backslash: 11,
|
||||||
|
Gdk.KEY_apostrophe: 12,
|
||||||
}
|
}
|
||||||
if keyval not in mappings:
|
if keyval not in mappings:
|
||||||
return False
|
return False
|
||||||
@@ -313,6 +315,8 @@ class FilterPanel(Gtk.Box):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def toggle_check(self, digit: int) -> None:
|
def toggle_check(self, digit: int) -> None:
|
||||||
|
if not self.button_grid.is_sensitive():
|
||||||
|
return
|
||||||
checks = self.button_grid.get_checkboxes()
|
checks = self.button_grid.get_checkboxes()
|
||||||
check = checks[digit]
|
check = checks[digit]
|
||||||
state = check.get_active()
|
state = check.get_active()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
class Icon(Gtk.Image):
|
class Icon(Gtk.Image):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from dzgui.util.format import embolden
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
class BoldLabel(Gtk.Label):
|
class BoldLabel(Gtk.Label):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib # noqa E402
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
|
||||||
class MapsCombo(Gtk.EntryCompletion):
|
class MapsCombo(Gtk.EntryCompletion):
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from typing import Self
|
from typing import Self
|
||||||
from warnings import deprecated
|
from warnings import deprecated
|
||||||
|
|
||||||
from dzgui.const.constants import ERROR, FLATPAK_RUN_CMD, FLATPAK_SANDBOX, STEAM_CMD
|
from dzgui.const.constants import FLATPAK_RUN_CMD, FLATPAK_SANDBOX, STEAM_CMD
|
||||||
|
from dzgui.const.icons import ICON_ERROR
|
||||||
from dzgui.init.prereqs import has_steam_client, has_flatpak_client
|
from dzgui.init.prereqs import has_steam_client, has_flatpak_client
|
||||||
from dzgui.strings import options
|
from dzgui.strings import options
|
||||||
from dzgui.views.components.box import HBox
|
from dzgui.views.components.box import HBox
|
||||||
@@ -73,7 +74,7 @@ class ErrorPopover(Gtk.Popover):
|
|||||||
|
|
||||||
self.hbox = HBox()
|
self.hbox = HBox()
|
||||||
self.label = Gtk.Label(label="", margin_start=10, margin_end=10)
|
self.label = Gtk.Label(label="", margin_start=10, margin_end=10)
|
||||||
error_icon = Icon(ERROR, margin_start=10)
|
error_icon = Icon(ICON_ERROR, margin_start=10)
|
||||||
self.hbox.extend([error_icon, self.label])
|
self.hbox.extend([error_icon, self.label])
|
||||||
self.add(self.hbox)
|
self.add(self.hbox)
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
from typing import Self, TYPE_CHECKING
|
from typing import Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.enum import ModButton
|
from dzgui.const.enum import ModButton
|
||||||
from dzgui.const.constants import NO_EXPAND, FILL, NO_PADDING
|
|
||||||
from dzgui.util.strings import mod_panel
|
from dzgui.util.strings import mod_panel
|
||||||
from dzgui.views.components.labels import BoldLabel
|
from dzgui.views.components.labels import BoldLabel
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk # noqa E402
|
from gi.repository import Gtk
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -20,8 +19,8 @@ class EnumeratedModButton(Gtk.Button):
|
|||||||
super().__init__(margin_start=10, margin_end=10, focus_on_click=False)
|
super().__init__(margin_start=10, margin_end=10, focus_on_click=False)
|
||||||
|
|
||||||
self.enum = enum
|
self.enum = enum
|
||||||
self.set_label(enum.dict["label"])
|
self.set_label(enum.label)
|
||||||
self.set_tooltip_text(enum.dict["tooltip"])
|
self.set_tooltip_text(enum.tooltip)
|
||||||
|
|
||||||
|
|
||||||
class ModPanelButton(EnumeratedModButton):
|
class ModPanelButton(EnumeratedModButton):
|
||||||
@@ -50,11 +49,12 @@ class ModSelectionPanel(Gtk.Box):
|
|||||||
ModButton.SELECT_ALL,
|
ModButton.SELECT_ALL,
|
||||||
ModButton.UNSELECT_ALL,
|
ModButton.UNSELECT_ALL,
|
||||||
ModButton.UNSUB_SELECTED,
|
ModButton.UNSUB_SELECTED,
|
||||||
|
ModButton.VALIDATE,
|
||||||
)
|
)
|
||||||
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, NO_PADDING)
|
self.main_panel.pack_start(b, expand=False, fill=True, padding=0)
|
||||||
|
|
||||||
self.stale_panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
self.stale_panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
self.highlight_stale_button = ModPanelButton(ModButton.HIGHLIGHT_STALE)
|
self.highlight_stale_button = ModPanelButton(ModButton.HIGHLIGHT_STALE)
|
||||||
@@ -66,11 +66,11 @@ class ModSelectionPanel(Gtk.Box):
|
|||||||
self.unhighlight_stale_button,
|
self.unhighlight_stale_button,
|
||||||
self.select_stale_button,
|
self.select_stale_button,
|
||||||
):
|
):
|
||||||
self.stale_panel.pack_start(b, NO_EXPAND, FILL, NO_PADDING)
|
self.stale_panel.pack_start(b, expand=False, fill=True, padding=0)
|
||||||
b.connect("clicked", self._on_button_clicked)
|
b.connect("clicked", self._on_button_clicked)
|
||||||
|
|
||||||
for el in header, self.main_panel, self.stale_panel:
|
for el in header, self.main_panel, self.stale_panel:
|
||||||
self.pack_start(el, NO_EXPAND, FILL, NO_PADDING)
|
self.pack_start(el, expand=False, fill=True, padding=0)
|
||||||
|
|
||||||
self.connect("map", self._reinit_button)
|
self.connect("map", self._reinit_button)
|
||||||
|
|
||||||
@@ -114,3 +114,5 @@ class ModSelectionPanel(Gtk.Box):
|
|||||||
self.swap_sensitive(False)
|
self.swap_sensitive(False)
|
||||||
case ModButton.SELECT_STALE:
|
case ModButton.SELECT_STALE:
|
||||||
self.controller.select_colorized()
|
self.controller.select_colorized()
|
||||||
|
case ModButton.VALIDATE:
|
||||||
|
self.controller.validate_mods()
|
||||||
@@ -6,13 +6,10 @@ from typing import Literal, TYPE_CHECKING
|
|||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
NO_EXPAND,
|
|
||||||
NO_FILL,
|
|
||||||
FILL,
|
|
||||||
NO_PADDING,
|
|
||||||
)
|
)
|
||||||
from dzgui.const.enum import ServerTab
|
from dzgui.const.enum import ServerTab
|
||||||
from dzgui.managers.update import UpdateManager
|
from dzgui.managers.update import UpdateManager
|
||||||
|
from dzgui.strings.buttons import update_button, version_notifier, version_tooltip
|
||||||
from dzgui.util.clip import copy_clipboard
|
from dzgui.util.clip import copy_clipboard
|
||||||
from dzgui.util.file import is_writeable
|
from dzgui.util.file import is_writeable
|
||||||
from dzgui.views.components.buttonbox import ButtonBox
|
from dzgui.views.components.buttonbox import ButtonBox
|
||||||
@@ -23,7 +20,7 @@ from dzgui.views.components.buttons import IconTextButton, RefreshButton, KeysBu
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk, GLib # noqa E402
|
from gi.repository import Gtk, Gdk, GLib
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -65,20 +62,19 @@ class RightPanel(Gtk.Box):
|
|||||||
vexpand=True,
|
vexpand=True,
|
||||||
halign=Gtk.Align.END,
|
halign=Gtk.Align.END,
|
||||||
valign=Gtk.Align.END,
|
valign=Gtk.Align.END,
|
||||||
# TODO: strings
|
tooltip_text=version_tooltip,
|
||||||
tooltip_text="Click to copy to clipboard",
|
|
||||||
)
|
)
|
||||||
eb = Gtk.EventBox(halign=Gtk.Align.END, valign=Gtk.Align.END)
|
eb = Gtk.EventBox(halign=Gtk.Align.END, valign=Gtk.Align.END)
|
||||||
eb.add(self.version_label)
|
eb.add(self.version_label)
|
||||||
eb.connect("button-press-event", self._on_version_clicked)
|
eb.connect("button-press-event", self._on_version_clicked)
|
||||||
|
|
||||||
for el in self.button_vbox, self.keys, self.filters_vbox, self.refresh_button:
|
for el in self.button_vbox, self.keys, self.filters_vbox, self.refresh_button:
|
||||||
self.pack_start(el, NO_EXPAND, FILL, NO_PADDING)
|
self.pack_start(el, expand=False, fill=True, padding=0)
|
||||||
|
|
||||||
self.pack_start(self.sel_panel, NO_EXPAND, NO_FILL, NO_PADDING)
|
self.pack_start(self.sel_panel, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
self.update_button = IconTextButton(
|
self.update_button = IconTextButton(
|
||||||
"dialog-information-symbolic", label="Updates available"
|
"dialog-information-symbolic", label=update_button
|
||||||
)
|
)
|
||||||
|
|
||||||
self.gutter_box = Gtk.Box(
|
self.gutter_box = Gtk.Box(
|
||||||
@@ -96,7 +92,7 @@ class RightPanel(Gtk.Box):
|
|||||||
"clicked", self._on_update_button_clicked, exe_path, update
|
"clicked", self._on_update_button_clicked, exe_path, update
|
||||||
)
|
)
|
||||||
self.gutter_box.add(eb)
|
self.gutter_box.add(eb)
|
||||||
self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING)
|
self.pack_start(self.gutter_box, expand=False, fill=True, padding=0)
|
||||||
|
|
||||||
def _on_update_button_clicked(
|
def _on_update_button_clicked(
|
||||||
self, button: Gtk.Button, exe_path: str, url: str
|
self, button: Gtk.Button, exe_path: str, url: str
|
||||||
@@ -131,7 +127,6 @@ class RightPanel(Gtk.Box):
|
|||||||
return
|
return
|
||||||
self.copying = True
|
self.copying = True
|
||||||
version = self.version_label.get_text()
|
version = self.version_label.get_text()
|
||||||
# TODO: strings
|
self.version_label.set_text(version_notifier)
|
||||||
self.version_label.set_text("Copied!")
|
|
||||||
copy_clipboard(version)
|
copy_clipboard(version)
|
||||||
GLib.timeout_add_seconds(1, revert)
|
GLib.timeout_add_seconds(1, revert)
|
||||||
@@ -2,12 +2,11 @@ from typing import Self, Union, TYPE_CHECKING
|
|||||||
|
|
||||||
from dzgui.const.enum import NotebookPage, ServerTab
|
from dzgui.const.enum import NotebookPage, ServerTab
|
||||||
from dzgui.util.strings import esc_to_return, question_to_return
|
from dzgui.util.strings import esc_to_return, question_to_return
|
||||||
from dzgui.views.components.buttons import LoggerAlertsButton
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.const.enum import ServerTab
|
from dzgui.const.enum import ServerTab
|
||||||
@@ -47,9 +46,6 @@ class Statusbar(Gtk.Grid):
|
|||||||
self.emitter.connect("servers_loaded", self._on_servers_loaded)
|
self.emitter.connect("servers_loaded", self._on_servers_loaded)
|
||||||
self.emitter.connect("mods_updated", self._on_mods_updated)
|
self.emitter.connect("mods_updated", self._on_mods_updated)
|
||||||
|
|
||||||
def _on_alerts_clicked(self, button: LoggerAlertsButton) -> None:
|
|
||||||
self.controller.populate_log()
|
|
||||||
|
|
||||||
def _on_mods_updated(self, emitter: "Emitter", msg: str, mods: int) -> None:
|
def _on_mods_updated(self, emitter: "Emitter", msg: str, mods: int) -> None:
|
||||||
self.set_by_context(NotebookPage.MODS, msg)
|
self.set_by_context(NotebookPage.MODS, msg)
|
||||||
|
|
||||||
@@ -60,7 +56,7 @@ class Statusbar(Gtk.Grid):
|
|||||||
return
|
return
|
||||||
|
|
||||||
enum = notebook.get_page_by_enum()
|
enum = notebook.get_page_by_enum()
|
||||||
show_statusbar = enum.dict["statusbar"]
|
show_statusbar = enum.has_statusbar
|
||||||
|
|
||||||
if show_statusbar is False:
|
if show_statusbar is False:
|
||||||
self.set_by_context(enum, "")
|
self.set_by_context(enum, "")
|
||||||
@@ -152,5 +148,5 @@ class Statusbar(Gtk.Grid):
|
|||||||
row = tree.get_value_at_index(1)
|
row = tree.get_value_at_index(1)
|
||||||
if self.controller.loaded is False:
|
if self.controller.loaded is False:
|
||||||
return
|
return
|
||||||
tooltip = row.dict["tooltip"]
|
tooltip = row.tooltip
|
||||||
self.set_by_context(NotebookPage.HELP, tooltip)
|
self.set_by_context(NotebookPage.HELP, tooltip)
|
||||||
@@ -2,8 +2,9 @@ from typing import Literal
|
|||||||
from dzgui.util import css
|
from dzgui.util import css
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib # noqa E402
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
|
|
||||||
class Toast(Gtk.EventBox):
|
class Toast(Gtk.EventBox):
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from dzgui.const.constants import HEX_GREEN, HEX_RED
|
|||||||
from dzgui.init.coords import get_local_coords
|
from dzgui.init.coords import get_local_coords
|
||||||
from dzgui.init.dayz import is_dayz_installed
|
from dzgui.init.dayz import is_dayz_installed
|
||||||
from dzgui.init.update import check_updates
|
from dzgui.init.update import check_updates
|
||||||
from dzgui.const.constants import EXPAND, FILL
|
|
||||||
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
||||||
from dzgui.strings import preboot, dialogs
|
from dzgui.strings import preboot, dialogs
|
||||||
from dzgui.util.format import format_exception
|
from dzgui.util.format import format_exception
|
||||||
@@ -105,7 +104,7 @@ class BootDialog(ColorAwareApp, Gtk.Dialog): # type: ignore
|
|||||||
self.error_label = Gtk.Label(margin_start=15, margin_end=15)
|
self.error_label = Gtk.Label(margin_start=15, margin_end=15)
|
||||||
|
|
||||||
# TODO: abstract class
|
# TODO: abstract class
|
||||||
self.copy_button = ClipboardButton(None, lambda: self.error_label.get_text())
|
self.copy_button = ClipboardButton(self.error_label.get_text)
|
||||||
self.button_hbox = Gtk.Box(
|
self.button_hbox = Gtk.Box(
|
||||||
orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.CENTER, spacing=10
|
orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.CENTER, spacing=10
|
||||||
)
|
)
|
||||||
@@ -118,8 +117,8 @@ class BootDialog(ColorAwareApp, Gtk.Dialog): # type: ignore
|
|||||||
self.error_box.add(self.button_hbox)
|
self.error_box.add(self.button_hbox)
|
||||||
|
|
||||||
self.content = self.get_content_area()
|
self.content = self.get_content_area()
|
||||||
self.content.pack_start(self.scrollable_tree, EXPAND, FILL, 0)
|
self.content.pack_start(self.scrollable_tree, expand=True, fill=True, padding=0)
|
||||||
self.content.pack_start(self.error_box, EXPAND, FILL, 0)
|
self.content.pack_start(self.error_box, expand=True, fill=True, padding=0)
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
||||||
self.error_box.hide()
|
self.error_box.hide()
|
||||||
@@ -132,9 +131,9 @@ class BootDialog(ColorAwareApp, Gtk.Dialog): # type: ignore
|
|||||||
preboot.signatures,
|
preboot.signatures,
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
(StoredFunc(get_ipdb, self.xdg.ips), preboot.geo, False),
|
(StoredFunc(get_ipdb, self.xdg.db), preboot.geo, False),
|
||||||
# TODO: handle IP DB failure and use coords fallback
|
# TODO: handle IP DB failure and use coords fallback
|
||||||
(StoredFunc(get_local_coords, self.xdg.ips), preboot.coords, True),
|
(StoredFunc(get_local_coords, self.xdg.db), preboot.coords, True),
|
||||||
(StoredFunc(check_updates, self.version), preboot.updates, True),
|
(StoredFunc(check_updates, self.version), preboot.updates, True),
|
||||||
]
|
]
|
||||||
self.results: list[Any] = []
|
self.results: list[Any] = []
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ from dataclasses import dataclass
|
|||||||
from datetime import datetime, time, timedelta
|
from datetime import datetime, time, timedelta
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from dzgui.const.constants import DAY_ICON, NIGHT_ICON
|
from dzgui.const.icons import ICON_DAY, ICON_NIGHT
|
||||||
from dzgui.strings import calc
|
from dzgui.strings import calc
|
||||||
from dzgui.views.components.icon import Icon
|
from dzgui.views.components.icon import Icon
|
||||||
from dzgui.views.components.labels import BoldLabel
|
from dzgui.views.components.labels import BoldLabel
|
||||||
from dzgui.views.components.box import VBox, HBox
|
from dzgui.views.components.box import VBox, HBox
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib, GObject # type: ignore # noqa E402
|
from gi.repository import Gtk, GLib, GObject
|
||||||
|
|
||||||
DAY_START_TIME = time(hour=6, minute=0, second=0)
|
DAY_START_TIME = time(hour=6, minute=0, second=0)
|
||||||
DAY_END_TIME = time(hour=18, minute=0, second=0)
|
DAY_END_TIME = time(hour=18, minute=0, second=0)
|
||||||
@@ -385,7 +385,7 @@ class TimePickerFrame(Gtk.Frame):
|
|||||||
h1 = BoldLabel(calc.heading_server_time)
|
h1 = BoldLabel(calc.heading_server_time)
|
||||||
h2 = BoldLabel(calc.heading_target_time)
|
h2 = BoldLabel(calc.heading_target_time)
|
||||||
|
|
||||||
self.icon_indicator = Icon(DAY_ICON)
|
self.icon_indicator = Icon(ICON_DAY)
|
||||||
self.update_server_icon()
|
self.update_server_icon()
|
||||||
|
|
||||||
server_time_box = HBox(10)
|
server_time_box = HBox(10)
|
||||||
@@ -411,7 +411,7 @@ class TimePickerFrame(Gtk.Frame):
|
|||||||
self.add(flowbox)
|
self.add(flowbox)
|
||||||
|
|
||||||
def update_server_icon(self) -> None:
|
def update_server_icon(self) -> None:
|
||||||
icon = DAY_ICON if self.server_time.is_day() else NIGHT_ICON
|
icon = ICON_DAY if self.server_time.is_day() else ICON_NIGHT
|
||||||
self.icon_indicator.set_icon_name(icon)
|
self.icon_indicator.set_icon_name(icon)
|
||||||
|
|
||||||
def _on_server_clock_increment(self, emitter: Emitter, time_now: datetime) -> None:
|
def _on_server_clock_increment(self, emitter: Emitter, time_now: datetime) -> None:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from dzgui.views.mixins.colorscheme import ColorAwareApp
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
class AbortDialog(ColorAwareApp, Gtk.MessageDialog): # type: ignore
|
class AbortDialog(ColorAwareApp, Gtk.MessageDialog): # type: ignore
|
||||||
@@ -46,7 +46,7 @@ class AbortDialog(ColorAwareApp, Gtk.MessageDialog): # type: ignore
|
|||||||
|
|
||||||
self.connect("response", self._on_response)
|
self.connect("response", self._on_response)
|
||||||
|
|
||||||
self.run()
|
self.run() # type: ignore[no-untyped-call]
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ from pathlib import Path
|
|||||||
|
|
||||||
from dzgui.util.strings import picker
|
from dzgui.util.strings import picker
|
||||||
|
|
||||||
import gi # noqa E402
|
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
|
||||||
|
|
||||||
|
|
||||||
class FilePicker(Gtk.FileChooserDialog):
|
class FilePicker(Gtk.FileChooserDialog):
|
||||||
@@ -16,14 +16,14 @@ class FilePicker(Gtk.FileChooserDialog):
|
|||||||
transient_for=parent,
|
transient_for=parent,
|
||||||
resizable=True,
|
resizable=True,
|
||||||
)
|
)
|
||||||
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL)
|
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL) # type: ignore[no-untyped-call]
|
||||||
self.add_buttons("_Save", Gtk.ResponseType.OK)
|
self.add_buttons("_Save", Gtk.ResponseType.OK) # type: ignore[no-untyped-call]
|
||||||
self.set_default_response(Gtk.ResponseType.OK)
|
self.set_default_response(Gtk.ResponseType.OK)
|
||||||
|
|
||||||
self.set_current_name(picker.placeholder)
|
self.set_current_name(picker.placeholder)
|
||||||
|
|
||||||
def pick_file(self) -> Path | None:
|
def pick_file(self) -> Path | None:
|
||||||
res = self.run()
|
res = self.run() # type: ignore[no-untyped-call]
|
||||||
if res in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
if res in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||||
self.destroy()
|
self.destroy()
|
||||||
return None
|
return None
|
||||||
@@ -43,12 +43,12 @@ class FolderPicker(Gtk.FileChooserDialog):
|
|||||||
parent=parent,
|
parent=parent,
|
||||||
resizable=True,
|
resizable=True,
|
||||||
)
|
)
|
||||||
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL)
|
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL) # type: ignore[no-untyped-call]
|
||||||
self.add_buttons("_OK", Gtk.ResponseType.OK)
|
self.add_buttons("_OK", Gtk.ResponseType.OK) # type: ignore[no-untyped-call]
|
||||||
self.set_default_response(Gtk.ResponseType.OK)
|
self.set_default_response(Gtk.ResponseType.OK)
|
||||||
|
|
||||||
def pick_folder(self) -> Path | None:
|
def pick_folder(self) -> Path | None:
|
||||||
res = self.run()
|
res = self.run() # type: ignore[no-untyped-call]
|
||||||
if res in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
if res in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||||
self.destroy()
|
self.destroy()
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from typing import Literal, Self, TYPE_CHECKING
|
from typing import Literal, Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING, EXPAND, FILL
|
|
||||||
from dzgui.strings import dialogs
|
from dzgui.strings import dialogs
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
from dzgui.views.components.buttons import ClipboardButton
|
from dzgui.views.components.buttons import ClipboardButton
|
||||||
@@ -8,7 +7,7 @@ from dzgui.views.components.buttons import ClipboardButton
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -46,7 +45,7 @@ class GenericDialog(Gtk.MessageDialog):
|
|||||||
|
|
||||||
def pack(self, widget: Gtk.Widget) -> None:
|
def pack(self, widget: Gtk.Widget) -> None:
|
||||||
content = self.get_content_area()
|
content = self.get_content_area()
|
||||||
content.pack_start(widget, EXPAND, FILL, NO_PADDING)
|
content.pack_start(widget, expand=True, fill=True, padding=0)
|
||||||
|
|
||||||
|
|
||||||
class ConfirmationDialog(GenericDialog):
|
class ConfirmationDialog(GenericDialog):
|
||||||
@@ -121,13 +120,13 @@ class WaitDialog(GenericDialog):
|
|||||||
spinner_box.add(spinner)
|
spinner_box.add(spinner)
|
||||||
|
|
||||||
if self.jobs > 1:
|
if self.jobs > 1:
|
||||||
content.pack_start(self.prog, NO_EXPAND, NO_FILL, 0)
|
content.pack_start(self.prog, expand=False, fill=False, padding=0)
|
||||||
else:
|
else:
|
||||||
content.pack_start(spinner_box, NO_EXPAND, NO_FILL, 0)
|
content.pack_start(spinner_box, expand=False, fill=False, padding=0)
|
||||||
spinner.start()
|
spinner.start()
|
||||||
|
|
||||||
if show_cancel is True:
|
if show_cancel is True:
|
||||||
content.pack_start(self.cancel, NO_EXPAND, NO_FILL, 0)
|
content.pack_start(self.cancel, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
def update_text(self, msg: str) -> None:
|
def update_text(self, msg: str) -> None:
|
||||||
self.format_secondary_text(msg)
|
self.format_secondary_text(msg)
|
||||||
@@ -140,8 +139,7 @@ class WaitDialog(GenericDialog):
|
|||||||
self.cur_job += 1
|
self.cur_job += 1
|
||||||
|
|
||||||
def _on_cancel_clicked(self, button: Gtk.Button) -> None:
|
def _on_cancel_clicked(self, button: Gtk.Button) -> None:
|
||||||
# TODO: strings
|
GLib.idle_add(self.update_text, dialogs.caught_cancel_signal)
|
||||||
GLib.idle_add(self.update_text, "Caught cancel signal, cleaning up")
|
|
||||||
self.controller.set_cancel_event()
|
self.controller.set_cancel_event()
|
||||||
|
|
||||||
def show_cancel(self, state: bool) -> None:
|
def show_cancel(self, state: bool) -> None:
|
||||||
@@ -187,7 +185,7 @@ class TextBufferDialog(GenericDialog):
|
|||||||
secondary=secondary,
|
secondary=secondary,
|
||||||
)
|
)
|
||||||
self.text = text
|
self.text = text
|
||||||
copy_button = ClipboardButton(controller, self.get_text)
|
copy_button = ClipboardButton(self.get_text)
|
||||||
self.add_action_widget(copy_button, Gtk.ResponseType.NONE)
|
self.add_action_widget(copy_button, Gtk.ResponseType.NONE)
|
||||||
self.add_button("OK", Gtk.ResponseType.OK)
|
self.add_button("OK", Gtk.ResponseType.OK)
|
||||||
self.connect("response", self._on_response)
|
self.connect("response", self._on_response)
|
||||||
@@ -272,7 +270,7 @@ class ExceptionDialog(TextBufferDialog):
|
|||||||
bottom_margin=10,
|
bottom_margin=10,
|
||||||
)
|
)
|
||||||
textview.set_buffer(Gtk.TextBuffer(text=trace))
|
textview.set_buffer(Gtk.TextBuffer(text=trace))
|
||||||
box.pack_start(textview, EXPAND, FILL, 0)
|
box.pack_start(textview, expand=True, fill=True, padding=0)
|
||||||
|
|
||||||
self.error_details = Gtk.ScrolledWindow(
|
self.error_details = Gtk.ScrolledWindow(
|
||||||
overlay_scrolling=False,
|
overlay_scrolling=False,
|
||||||
@@ -296,7 +294,7 @@ class ExceptionDialog(TextBufferDialog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
details_textview.set_buffer(self.details_buffer)
|
details_textview.set_buffer(self.details_buffer)
|
||||||
details_box.pack_start(details_textview, EXPAND, FILL, 10)
|
details_box.pack_start(details_textview, expand=True, fill=True, padding=10)
|
||||||
self.error_details.add(details_box)
|
self.error_details.add(details_box)
|
||||||
|
|
||||||
self.error_notebook = Gtk.Notebook(show_tabs=False, margin_bottom=15)
|
self.error_notebook = Gtk.Notebook(show_tabs=False, margin_bottom=15)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import textwrap
|
import textwrap
|
||||||
from typing import Self, TYPE_CHECKING
|
from typing import Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import NO_EXPAND, NO_FILL
|
|
||||||
from dzgui.util.strings import notice
|
from dzgui.util.strings import notice
|
||||||
from dzgui.views.dialogs.generic import GenericDialog
|
from dzgui.views.dialogs.generic import GenericDialog
|
||||||
from dzgui.views.components.buttons import SteamTextButton
|
from dzgui.views.components.buttons import SteamTextButton
|
||||||
@@ -9,7 +8,7 @@ from dzgui.views.components.buttons import SteamTextButton
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -42,7 +41,7 @@ class WorkshopLinkDialog(GenericDialog):
|
|||||||
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, uid)
|
button.connect("clicked", self._on_button_clicked, uid)
|
||||||
self.dialogBox.pack_end(button, NO_EXPAND, NO_FILL, 0)
|
self.dialogBox.pack_end(button, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
self.connect("response", self._on_dialog_response)
|
self.connect("response", self._on_dialog_response)
|
||||||
|
|||||||
+11
-19
@@ -1,12 +1,13 @@
|
|||||||
from typing import Self, TYPE_CHECKING
|
from typing import Self, TYPE_CHECKING
|
||||||
|
|
||||||
|
from dzgui.strings import notes
|
||||||
from dzgui.views.components.entry import ValidatedEntry
|
from dzgui.views.components.entry import ValidatedEntry
|
||||||
from dzgui.views.dialogs.generic import GenericDialog
|
from dzgui.views.dialogs.generic import GenericDialog
|
||||||
|
|
||||||
import gi # noqa E402
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gdk, Gtk # noqa E402
|
from gi.repository import Gtk
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
@@ -14,23 +15,20 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class NoteDialog(GenericDialog):
|
class NoteDialog(GenericDialog):
|
||||||
def __init__(self, controller: "Controller"):
|
def __init__(self, controller: "Controller"):
|
||||||
# TODO: add to strings
|
|
||||||
title = "Add a short note/reminder."
|
|
||||||
secondary = "Limit 30 chars"
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
controller=controller,
|
controller=controller,
|
||||||
text=title,
|
text=notes.dialog_title,
|
||||||
buttons=Gtk.ButtonsType.NONE,
|
buttons=Gtk.ButtonsType.NONE,
|
||||||
mtype=Gtk.MessageType.INFO,
|
mtype=Gtk.MessageType.INFO,
|
||||||
secondary=secondary,
|
secondary=notes.dialog_subtitle,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
delete_button = Gtk.Button(label="Delete note")
|
delete_button = Gtk.Button(label=notes.delete_button)
|
||||||
delete_button.set_sensitive(False)
|
delete_button.set_sensitive(False)
|
||||||
self.add_action_widget(delete_button, Gtk.ResponseType.CANCEL)
|
self.add_action_widget(delete_button, Gtk.ResponseType.CANCEL)
|
||||||
self.ok = self.add_button("Update note", Gtk.ResponseType.OK)
|
self.ok = self.add_button(notes.ok_button, Gtk.ResponseType.OK)
|
||||||
self.ok.grab_default()
|
self.ok.grab_default()
|
||||||
|
|
||||||
self.entry = ValidatedEntry(controller, self.validate)
|
self.entry = ValidatedEntry(controller, self.validate)
|
||||||
@@ -42,7 +40,7 @@ class NoteDialog(GenericDialog):
|
|||||||
self.entry.set_text(note)
|
self.entry.set_text(note)
|
||||||
if len(note) > 0:
|
if len(note) > 0:
|
||||||
delete_button.set_sensitive(True)
|
delete_button.set_sensitive(True)
|
||||||
self.format_secondary_text(f"Current note: '{note}'")
|
self.format_secondary_text(notes.current_note.format(note))
|
||||||
elif len(note) == 0:
|
elif len(note) == 0:
|
||||||
self.ok.set_sensitive(False)
|
self.ok.set_sensitive(False)
|
||||||
|
|
||||||
@@ -55,9 +53,6 @@ class NoteDialog(GenericDialog):
|
|||||||
self.entry.connect("changed", self._on_text_changed)
|
self.entry.connect("changed", self._on_text_changed)
|
||||||
|
|
||||||
def _on_text_changed(self, entry: Gtk.Entry) -> None:
|
def _on_text_changed(self, entry: Gtk.Entry) -> None:
|
||||||
"""
|
|
||||||
Called in conjunction with signal callback in the parent class
|
|
||||||
"""
|
|
||||||
if len(entry.get_text()) == 0:
|
if len(entry.get_text()) == 0:
|
||||||
self.ok.set_sensitive(False)
|
self.ok.set_sensitive(False)
|
||||||
|
|
||||||
@@ -71,14 +66,11 @@ class NoteDialog(GenericDialog):
|
|||||||
|
|
||||||
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
|
||||||
match response:
|
match response:
|
||||||
case Gtk.ResponseType.DELETE_EVENT:
|
|
||||||
self.destroy()
|
|
||||||
case Gtk.ResponseType.OK:
|
case Gtk.ResponseType.OK:
|
||||||
note = self.entry.get_text()
|
note = self.entry.get_text()
|
||||||
self.controller.add_note(note)
|
self.controller.add_note(note)
|
||||||
self.destroy()
|
|
||||||
case Gtk.ResponseType.CANCEL:
|
case Gtk.ResponseType.CANCEL:
|
||||||
self.controller.delete_note()
|
self.controller.delete_note()
|
||||||
self.destroy()
|
case _:
|
||||||
case Gtk.ResponseType.CANCEL | Gtk.ResponseType.CLOSE:
|
pass
|
||||||
self.destroy()
|
self.destroy()
|
||||||
@@ -2,7 +2,7 @@ import gi
|
|||||||
import html
|
import html
|
||||||
from typing import Self, TYPE_CHECKING
|
from typing import Self, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import CALCULATOR
|
from dzgui.const.icons import ICON_CALCULATOR
|
||||||
from dzgui.const.enum import ContextMenuGroup
|
from dzgui.const.enum import ContextMenuGroup
|
||||||
from dzgui.model.model_factory import ModelFactory
|
from dzgui.model.model_factory import ModelFactory
|
||||||
from dzgui.util import css
|
from dzgui.util import css
|
||||||
@@ -110,7 +110,7 @@ class ServerDetailsDialog(ServerDialog):
|
|||||||
text = format_hyperlinks(text)
|
text = format_hyperlinks(text)
|
||||||
self.description.set_markup(html.escape(text))
|
self.description.set_markup(html.escape(text))
|
||||||
|
|
||||||
calc_button = IconTextButton(CALCULATOR, dialogs.toggle_calc)
|
calc_button = IconTextButton(ICON_CALCULATOR, dialogs.toggle_calc)
|
||||||
calc_button.connect("clicked", self._on_calc_pressed)
|
calc_button.connect("clicked", self._on_calc_pressed)
|
||||||
calc_button.set_halign(Gtk.Align.END)
|
calc_button.set_halign(Gtk.Align.END)
|
||||||
calc_button.set_valign(Gtk.Align.START)
|
calc_button.set_valign(Gtk.Align.START)
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ from dzgui.views.mixins.colorscheme import ColorAwareApp
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib, Pango # noqa E402
|
from gi.repository import Gtk, GLib, Pango
|
||||||
|
|
||||||
|
|
||||||
class Assistant(Gtk.Assistant):
|
class Assistant(Gtk.Assistant):
|
||||||
def __init__(self, is_deck: bool, paths: dict[str, str]):
|
def __init__(self, is_deck: bool, paths: dict[str, Path]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if is_deck:
|
if is_deck:
|
||||||
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
|
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
|
||||||
@@ -63,7 +63,7 @@ class Assistant(Gtk.Assistant):
|
|||||||
|
|
||||||
|
|
||||||
class Window(Gtk.Window):
|
class Window(Gtk.Window):
|
||||||
def __init__(self, is_deck: bool, paths: dict[str, str]) -> None:
|
def __init__(self, is_deck: bool, paths: dict[str, Path]) -> None:
|
||||||
super().__init__(title=APP_NAME, icon_name=APP_NAME)
|
super().__init__(title=APP_NAME, icon_name=APP_NAME)
|
||||||
self.assistant = Assistant(is_deck, paths)
|
self.assistant = Assistant(is_deck, paths)
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ class CheckboxWithPath(CheckboxWithLabel):
|
|||||||
|
|
||||||
|
|
||||||
class UninstallPage(ScrolledWizardPage):
|
class UninstallPage(ScrolledWizardPage):
|
||||||
def __init__(self, paths: dict[str, str], pyapp: str | None):
|
def __init__(self, paths: dict[str, Path], pyapp: str | None):
|
||||||
super().__init__(heading=uninstall.pg1_heading, description=uninstall.pg1_blurb)
|
super().__init__(heading=uninstall.pg1_heading, description=uninstall.pg1_blurb)
|
||||||
|
|
||||||
self.pyapp = pyapp
|
self.pyapp = pyapp
|
||||||
@@ -219,7 +219,7 @@ class UninstallPage(ScrolledWizardPage):
|
|||||||
|
|
||||||
|
|
||||||
class UninstallWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
class UninstallWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
||||||
def __init__(self, is_deck: bool, paths: dict[str, str]) -> None:
|
def __init__(self, is_deck: bool, paths: dict[str, Path]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
config = Path(paths["XDG_CONFIG_HOME"])
|
config = Path(paths["XDG_CONFIG_HOME"])
|
||||||
if not config.is_dir() or not config.joinpath("config.json").is_file():
|
if not config.is_dir() or not config.joinpath("config.json").is_file():
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from dzgui.const.constants import (
|
|||||||
HERO_PATH,
|
HERO_PATH,
|
||||||
FLATPAK_ROOT_PATH,
|
FLATPAK_ROOT_PATH,
|
||||||
LEGACY_CONFIG_PATH,
|
LEGACY_CONFIG_PATH,
|
||||||
|
TMP_PATH,
|
||||||
)
|
)
|
||||||
from dzgui.const.boilerplate import config_boilerplate
|
from dzgui.const.boilerplate import config_boilerplate
|
||||||
from dzgui.const.endpoints import STEAM_API_SETUP
|
from dzgui.const.endpoints import STEAM_API_SETUP
|
||||||
@@ -34,7 +35,7 @@ from dzgui.views.trees.tree_simple import SimpleTree
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gdk, Gtk, GLib, GObject, GdkPixbuf # noqa E402
|
from gi.repository import Gtk, GLib, GObject, GdkPixbuf
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.config.xdg import Xdg
|
from dzgui.config.xdg import Xdg
|
||||||
@@ -167,7 +168,12 @@ class NotificationFrame(Gtk.Frame):
|
|||||||
|
|
||||||
class APIValidationPage(EnumeratedWizardPage):
|
class APIValidationPage(EnumeratedWizardPage):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, enum: PageNum, heading: str, description: str, link: str, func: Callable
|
self,
|
||||||
|
enum: PageNum,
|
||||||
|
heading: str,
|
||||||
|
description: str,
|
||||||
|
link: str,
|
||||||
|
func: Callable[[str], None],
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
enum=enum,
|
enum=enum,
|
||||||
@@ -420,7 +426,7 @@ class PreferencesPage(EnumeratedWizardPage):
|
|||||||
|
|
||||||
|
|
||||||
class FlatpakOverridePage(EnumeratedWizardPage):
|
class FlatpakOverridePage(EnumeratedWizardPage):
|
||||||
def __init__(self, xdg_paths: dict[str, str]) -> None:
|
def __init__(self, xdg_paths: dict[str, Path]) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
enum=PageNum.FLATPAK,
|
enum=PageNum.FLATPAK,
|
||||||
heading=wizard.heading_flatpak,
|
heading=wizard.heading_flatpak,
|
||||||
@@ -432,6 +438,8 @@ class FlatpakOverridePage(EnumeratedWizardPage):
|
|||||||
store = Gtk.ListStore(str)
|
store = Gtk.ListStore(str)
|
||||||
for path in xdg_paths.values():
|
for path in xdg_paths.values():
|
||||||
store.append((str(path),))
|
store.append((str(path),))
|
||||||
|
store.append((TMP_PATH,))
|
||||||
|
|
||||||
tree = SimpleTree(store, wizard.tree_column)
|
tree = SimpleTree(store, wizard.tree_column)
|
||||||
tree.set_halign(Gtk.Align.CENTER)
|
tree.set_halign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
@@ -468,7 +476,7 @@ class CompletionPage(EnumeratedWizardPage):
|
|||||||
|
|
||||||
|
|
||||||
class Assistant(Gtk.Assistant):
|
class Assistant(Gtk.Assistant):
|
||||||
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]):
|
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, Path]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if is_deck:
|
if is_deck:
|
||||||
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
|
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
|
||||||
@@ -773,7 +781,7 @@ class SteamPathPage(EnumeratedWizardPage):
|
|||||||
|
|
||||||
|
|
||||||
class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
||||||
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]) -> None:
|
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, Path]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
GLib.set_prgname(APP_NAME)
|
GLib.set_prgname(APP_NAME)
|
||||||
self.win = Window(is_deck, XDG, xdg_paths)
|
self.win = Window(is_deck, XDG, xdg_paths)
|
||||||
@@ -784,7 +792,7 @@ class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
|||||||
|
|
||||||
|
|
||||||
class Window(Gtk.Window):
|
class Window(Gtk.Window):
|
||||||
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]) -> None:
|
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, Path]) -> None:
|
||||||
super().__init__(title=APP_NAME, icon_name=APP_NAME)
|
super().__init__(title=APP_NAME, icon_name=APP_NAME)
|
||||||
self.assistant = Assistant(is_deck, XDG, xdg_paths)
|
self.assistant = Assistant(is_deck, XDG, xdg_paths)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.const.enum import ContextMenu
|
from dzgui.const.enum import ContextMenuItem
|
||||||
from dzgui.util.keys import is_ctrl_mask, is_navkey
|
from dzgui.util.keys import is_ctrl_mask, is_navkey
|
||||||
from dzgui.util.strings import edit_note
|
from dzgui.util.strings import edit_note
|
||||||
from dzgui.views.trees.tree_base import TreeView
|
from dzgui.views.trees.tree_base import TreeView
|
||||||
@@ -9,17 +9,13 @@ from dzgui.views.trees.tree_base import TreeView
|
|||||||
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, Gdk
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
class ContextMixin(TreeView):
|
class ContextMixin(TreeView):
|
||||||
def present_menu(
|
def present_menu(self, widget: Gtk.Widget, event: Gdk.Event) -> bool:
|
||||||
self,
|
|
||||||
widget: Gtk.Widget,
|
|
||||||
event: Gdk.EventButton | Gdk.EventKey,
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
if self.menu is None:
|
if self.menu is None:
|
||||||
return False
|
return False
|
||||||
@@ -28,7 +24,7 @@ class ContextMixin(TreeView):
|
|||||||
|
|
||||||
match event.type:
|
match event.type:
|
||||||
case Gdk.EventType.BUTTON_PRESS:
|
case Gdk.EventType.BUTTON_PRESS:
|
||||||
if event.button != 3:
|
if event.button != Gdk.BUTTON_SECONDARY: # type: ignore[comparison-overlap]
|
||||||
return False
|
return False
|
||||||
self._process_button_event(event) # type: ignore
|
self._process_button_event(event) # type: ignore
|
||||||
case Gdk.EventType.KEY_PRESS:
|
case Gdk.EventType.KEY_PRESS:
|
||||||
@@ -62,17 +58,17 @@ class ContextMixin(TreeView):
|
|||||||
self.context_menu.select_first(False)
|
self.context_menu.select_first(False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _process_dynamic_row(self, row: ContextMenu) -> Gtk.MenuItem:
|
def _process_dynamic_row(self, row: ContextMenuItem) -> Gtk.MenuItem:
|
||||||
if row == ContextMenu.ADD_SERVER and self.is_in_favs(): # type: ignore
|
if row == ContextMenuItem.ADD_SERVER and self.is_in_favs(): # type: ignore
|
||||||
row = ContextMenu.REMOVE_SERVER
|
row = ContextMenuItem.REMOVE_SERVER
|
||||||
|
|
||||||
item = Gtk.MenuItem(label=row.dict["label"])
|
item = Gtk.MenuItem(label=row.label)
|
||||||
item.connect("activate", self._on_menu_click, row)
|
item.connect("activate", self._on_menu_click, row)
|
||||||
|
|
||||||
if row == ContextMenu.SHOW_MODS:
|
if row == ContextMenuItem.SHOW_MODS:
|
||||||
item.set_sensitive(self.is_modded()) # type: ignore
|
item.set_sensitive(self.is_modded()) # type: ignore
|
||||||
|
|
||||||
if row == ContextMenu.ADD_NOTE:
|
if row == ContextMenuItem.ADD_NOTE:
|
||||||
if self.controller.has_note():
|
if self.controller.has_note():
|
||||||
item.set_label(edit_note)
|
item.set_label(edit_note)
|
||||||
|
|
||||||
@@ -96,7 +92,7 @@ class ContextMixin(TreeView):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _on_menu_click(self, widget: Gtk.MenuItem, enum: ContextMenu) -> None:
|
def _on_menu_click(self, widget: Gtk.MenuItem, enum: ContextMenuItem) -> None:
|
||||||
self.controller.menu_action(enum, self) # type: ignore
|
self.controller.menu_action(enum, self) # type: ignore
|
||||||
|
|
||||||
def _on_key(self, menu: Gtk.Menu, event: Gdk.EventKey) -> bool | None:
|
def _on_key(self, menu: Gtk.Menu, event: Gdk.EventKey) -> bool | None:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from dzgui.util.keys import is_ctrl_mask
|
|||||||
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, Gdk
|
||||||
|
|
||||||
|
|
||||||
class CursorMixin:
|
class CursorMixin:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from dzgui.views.trees.tree_base import TreeView
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
class ModsMixin:
|
class ModsMixin:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ 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
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ from dzgui.util.strings import developers
|
|||||||
from dzgui.views.components.labels import BoldLabel
|
from dzgui.views.components.labels import BoldLabel
|
||||||
from dzgui.views.trees.tree_base import TreeView
|
from dzgui.views.trees.tree_base import TreeView
|
||||||
|
|
||||||
import gi # noqa E402
|
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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from dzgui.views.trees.tree_menu import MenuTreeView
|
|||||||
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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ 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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: refactor into autopopulated ListStores
|
||||||
class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||||
"""
|
"""
|
||||||
Notebook page holding a prearranged grid
|
Notebook page holding a prearranged grid
|
||||||
@@ -35,7 +36,7 @@ class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
|
|
||||||
self.connect("key-press-event", self._on_keypress)
|
self.connect("key-press-event", self._on_keypress)
|
||||||
|
|
||||||
def build_keys(self, items: list) -> Gtk.Grid:
|
def build_keys(self, items: list[dict[str, str]]) -> Gtk.Grid:
|
||||||
grid = Gtk.Grid(row_spacing=10, column_homogeneous=True)
|
grid = Gtk.Grid(row_spacing=10, column_homogeneous=True)
|
||||||
grid.set_halign(Gtk.Align.START)
|
grid.set_halign(Gtk.Align.START)
|
||||||
row, col = 1, 0
|
row, col = 1, 0
|
||||||
@@ -72,7 +73,7 @@ class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
row += 1
|
row += 1
|
||||||
return grid
|
return grid
|
||||||
|
|
||||||
def build_sidebar(self, categories: list) -> Gtk.Grid:
|
def build_sidebar(self, categories: list[str]) -> Gtk.Grid:
|
||||||
row, col = 0, 0
|
row, col = 0, 0
|
||||||
w, h = 1, 1
|
w, h = 1, 1
|
||||||
sidebar = Gtk.Grid(row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL)
|
sidebar = Gtk.Grid(row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL)
|
||||||
@@ -83,7 +84,7 @@ class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
sidebar.attach(label, col, row, w, h)
|
sidebar.attach(label, col, row, w, h)
|
||||||
return sidebar
|
return sidebar
|
||||||
|
|
||||||
def build_grid(self, items: list) -> Gtk.Grid:
|
def build_grid(self, items: list[dict[str, str]]) -> Gtk.Grid:
|
||||||
grid = Gtk.Grid(
|
grid = Gtk.Grid(
|
||||||
row_spacing=20,
|
row_spacing=20,
|
||||||
halign=Gtk.Align.CENTER,
|
halign=Gtk.Align.CENTER,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from dzgui.views.trees.tree_log import LogTreeView
|
|||||||
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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ from enum import Enum
|
|||||||
from typing import Self, TYPE_CHECKING, Union
|
from typing import Self, TYPE_CHECKING, Union
|
||||||
|
|
||||||
from dzgui.util import css
|
from dzgui.util import css
|
||||||
|
from dzgui.const.icons import ICON_EDIT_DELETE, ICON_FOLDER, ICON_WARNING
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
APPID_DAYZ,
|
APPID_DAYZ,
|
||||||
APPID_DAYZ_EXP,
|
APPID_DAYZ_EXP,
|
||||||
APPNAME_DAYZ,
|
APPNAME_DAYZ,
|
||||||
APPNAME_DAYZ_EXP_HUMAN,
|
APPNAME_DAYZ_EXP_HUMAN,
|
||||||
EDIT_DELETE,
|
|
||||||
FOLDER,
|
|
||||||
WARNING,
|
|
||||||
)
|
)
|
||||||
from dzgui.const.enum import ContextMenuGroup, NotebookPage
|
from dzgui.const.enum import ContextMenuGroup, NotebookPage
|
||||||
from dzgui.managers.offline import OfflineManager
|
from dzgui.managers.offline import OfflineManager
|
||||||
@@ -87,7 +85,7 @@ class FolderHBox(HBox):
|
|||||||
self.emitter = controller.get_emitter()
|
self.emitter = controller.get_emitter()
|
||||||
|
|
||||||
self.eb = InfoEventBox(eb_text, controller)
|
self.eb = InfoEventBox(eb_text, controller)
|
||||||
self.button = IconTextButton(FOLDER, btn_label, Gtk.PositionType.LEFT)
|
self.button = IconTextButton(ICON_FOLDER, btn_label, Gtk.PositionType.LEFT)
|
||||||
self.button.set_halign(Gtk.Align.START)
|
self.button.set_halign(Gtk.Align.START)
|
||||||
self.button.set_image_position(Gtk.PositionType.LEFT)
|
self.button.set_image_position(Gtk.PositionType.LEFT)
|
||||||
|
|
||||||
@@ -99,7 +97,7 @@ class FolderHBox(HBox):
|
|||||||
self.label = Gtk.Label()
|
self.label = Gtk.Label()
|
||||||
self.scrolled_label.add(self.label)
|
self.scrolled_label.add(self.label)
|
||||||
|
|
||||||
self.unset_button = IconTextButton(EDIT_DELETE, offline.unset_button)
|
self.unset_button = IconTextButton(ICON_EDIT_DELETE, offline.unset_button)
|
||||||
self.unset_button.connect("clicked", self._on_unset_clicked)
|
self.unset_button.connect("clicked", self._on_unset_clicked)
|
||||||
|
|
||||||
self.spinner = Gtk.Spinner()
|
self.spinner = Gtk.Spinner()
|
||||||
@@ -195,7 +193,7 @@ class ModFrame(HeadingFrame):
|
|||||||
self.tree_vbox.extend([self.scrolled, self.status])
|
self.tree_vbox.extend([self.scrolled, self.status])
|
||||||
|
|
||||||
self.no_mods = HBox(spacing=10)
|
self.no_mods = HBox(spacing=10)
|
||||||
no_mods_icon = Icon(WARNING, margin_start=10)
|
no_mods_icon = Icon(ICON_WARNING, margin_start=10)
|
||||||
no_mods_label = Gtk.Label(label=offline.no_local_mods, halign=Gtk.Align.START)
|
no_mods_label = Gtk.Label(label=offline.no_local_mods, halign=Gtk.Align.START)
|
||||||
self.no_mods.extend([no_mods_icon, no_mods_label])
|
self.no_mods.extend([no_mods_icon, no_mods_label])
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, TYPE_CHECKING
|
from typing import Any, Callable, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.api import pefile as PeFile
|
from dzgui.api import pefile as PeFile
|
||||||
|
from dzgui.api.steam import get_last_updated
|
||||||
|
|
||||||
from dzgui.config import query
|
from dzgui.config import query
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
@@ -12,12 +13,8 @@ from dzgui.const.constants import (
|
|||||||
FLATPAK_RUN_CMD,
|
FLATPAK_RUN_CMD,
|
||||||
FLATPAK_SANDBOX,
|
FLATPAK_SANDBOX,
|
||||||
FLATPAK_STEAM_PATH,
|
FLATPAK_STEAM_PATH,
|
||||||
NO_EXPAND,
|
|
||||||
NO_FILL,
|
|
||||||
NO_PADDING,
|
|
||||||
VIEW_CONCEAL,
|
|
||||||
VIEW_REVEAL,
|
|
||||||
)
|
)
|
||||||
|
from dzgui.const.icons import ICON_VIEW_CONCEAL, ICON_VIEW_REVEAL
|
||||||
from dzgui.const.endpoints import STEAM_API_SETUP
|
from dzgui.const.endpoints import STEAM_API_SETUP
|
||||||
from dzgui.const.enum import Preferences, ServerTab
|
from dzgui.const.enum import Preferences, ServerTab
|
||||||
from dzgui.strings import options
|
from dzgui.strings import options
|
||||||
@@ -54,7 +51,7 @@ class SubmitField(Gtk.Box):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
||||||
|
|
||||||
self.callback: Callable
|
self.callback: Callable[[], None]
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
@@ -66,7 +63,7 @@ class SubmitField(Gtk.Box):
|
|||||||
# TODO: audit for all applicable callbacks
|
# TODO: audit for all applicable callbacks
|
||||||
if private:
|
if private:
|
||||||
self.entry.set_icon_from_icon_name(
|
self.entry.set_icon_from_icon_name(
|
||||||
Gtk.EntryIconPosition.SECONDARY, VIEW_REVEAL
|
Gtk.EntryIconPosition.SECONDARY, ICON_VIEW_REVEAL
|
||||||
)
|
)
|
||||||
self.entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
|
self.entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
|
||||||
self.entry.connect("icon-release", self._on_icon_release)
|
self.entry.connect("icon-release", self._on_icon_release)
|
||||||
@@ -130,9 +127,9 @@ class SubmitField(Gtk.Box):
|
|||||||
) -> None:
|
) -> None:
|
||||||
visible = widget.get_visibility()
|
visible = widget.get_visibility()
|
||||||
if visible:
|
if visible:
|
||||||
icon, state = VIEW_REVEAL, False
|
icon, state = ICON_VIEW_REVEAL, False
|
||||||
else:
|
else:
|
||||||
icon, state = VIEW_CONCEAL, True
|
icon, state = ICON_VIEW_CONCEAL, True
|
||||||
widget.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
widget.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
||||||
widget.set_visibility(state)
|
widget.set_visibility(state)
|
||||||
|
|
||||||
@@ -142,7 +139,7 @@ class SubmitField(Gtk.Box):
|
|||||||
return
|
return
|
||||||
self.save_option()
|
self.save_option()
|
||||||
|
|
||||||
def set_callback(self, callback: Callable) -> None:
|
def set_callback(self, callback: Callable[[], None]) -> None:
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|
||||||
def save_option(self) -> None:
|
def save_option(self) -> None:
|
||||||
@@ -209,7 +206,7 @@ class NameSubmitField(SubmitField):
|
|||||||
Preferences.NAME,
|
Preferences.NAME,
|
||||||
private=False,
|
private=False,
|
||||||
)
|
)
|
||||||
self.entry.set_width_chars(30) # type: ignore
|
self.entry.set_width_chars(30)
|
||||||
self.set_callback(self.save_player_name)
|
self.set_callback(self.save_player_name)
|
||||||
|
|
||||||
def save_player_name(self) -> None:
|
def save_player_name(self) -> None:
|
||||||
@@ -233,8 +230,8 @@ class ToggleField(Gtk.Box):
|
|||||||
self.radio1 = Gtk.RadioButton.new_with_label(None, first_option)
|
self.radio1 = Gtk.RadioButton.new_with_label(None, first_option)
|
||||||
self.radio2 = Gtk.RadioButton.new_from_widget(self.radio1)
|
self.radio2 = Gtk.RadioButton.new_from_widget(self.radio1)
|
||||||
self.radio2.set_label(second_option)
|
self.radio2.set_label(second_option)
|
||||||
self.pack_start(self.radio1, NO_EXPAND, NO_FILL, NO_PADDING)
|
self.pack_start(self.radio1, expand=False, fill=False, padding=0)
|
||||||
self.pack_start(self.radio2, NO_EXPAND, NO_FILL, NO_PADDING)
|
self.pack_start(self.radio2, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
def set_suboption_active(self, state: bool) -> None:
|
def set_suboption_active(self, state: bool) -> None:
|
||||||
# NOTE: defer connection of signal until after state is set
|
# NOTE: defer connection of signal until after state is set
|
||||||
@@ -340,8 +337,10 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
[LeftLabel(options.flatpak_label), self.flatpak_toggle],
|
[LeftLabel(options.flatpak_label), self.flatpak_toggle],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.dayz_version_label = Gtk.Label(label=strings.null)
|
self.dayz_version_label = Gtk.Label(label=strings.null, halign=Gtk.Align.START)
|
||||||
self.dayz_exp_version_label = Gtk.Label(label=strings.null)
|
self.dayz_exp_version_label = Gtk.Label(
|
||||||
|
label=strings.null, halign=Gtk.Align.START
|
||||||
|
)
|
||||||
|
|
||||||
version_rows = [
|
version_rows = [
|
||||||
[LeftLabel(APPNAME_DAYZ), self.dayz_version_label],
|
[LeftLabel(APPNAME_DAYZ), self.dayz_version_label],
|
||||||
@@ -403,7 +402,8 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
def _on_link_button_clicked(self, button: Gtk.Button, url: str) -> None:
|
def _on_link_button_clicked(self, button: Gtk.Button, url: str) -> None:
|
||||||
open_links.open_link_by_url(url)
|
open_links.open_link_by_url(url)
|
||||||
|
|
||||||
def _make_grid(self, rows: list) -> Gtk.Grid:
|
# TODO: use FlowBox
|
||||||
|
def _make_grid(self, rows: list[list[Any]]) -> Gtk.Grid:
|
||||||
grid = Gtk.Grid(
|
grid = Gtk.Grid(
|
||||||
orientation=Gtk.Orientation.VERTICAL,
|
orientation=Gtk.Orientation.VERTICAL,
|
||||||
column_spacing=10,
|
column_spacing=10,
|
||||||
@@ -436,7 +436,7 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
if prefs.paths.config.is_file() is False:
|
if prefs.paths.config.is_file() is False:
|
||||||
dialog = ExceptionDialog(self.controller, strings.config_not_found)
|
dialog = ExceptionDialog(self.controller, strings.config_not_found)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
dialog.run()
|
dialog.run() # type: ignore[no-untyped-call]
|
||||||
raise OSError(f"Config file '{prefs.paths.config}' not found")
|
raise OSError(f"Config file '{prefs.paths.config}' not found")
|
||||||
|
|
||||||
config = query.get_config(prefs.paths.config)
|
config = query.get_config(prefs.paths.config)
|
||||||
@@ -467,10 +467,18 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
dayz_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ)
|
dayz_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ)
|
||||||
if dayz_version is None:
|
if dayz_version is None:
|
||||||
dayz_version = strings.null
|
dayz_version = strings.null
|
||||||
|
else:
|
||||||
|
last_updated = get_last_updated(steam_path, APPID_DAYZ)
|
||||||
|
if last_updated is not None:
|
||||||
|
dayz_version += f" (updated at {last_updated})"
|
||||||
|
|
||||||
dayz_exp_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP)
|
dayz_exp_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP)
|
||||||
if dayz_exp_version is None:
|
if dayz_exp_version is None:
|
||||||
dayz_exp_version = strings.null
|
dayz_exp_version = strings.null
|
||||||
|
else:
|
||||||
|
last_updated = get_last_updated(steam_path, APPID_DAYZ_EXP)
|
||||||
|
if last_updated is not None:
|
||||||
|
dayz_exp_version += f" (updated at {last_updated})"
|
||||||
|
|
||||||
self.dayz_version_label.set_text(dayz_version)
|
self.dayz_version_label.set_text(dayz_version)
|
||||||
self.dayz_exp_version_label.set_text(dayz_exp_version)
|
self.dayz_exp_version_label.set_text(dayz_exp_version)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Self, Sequence, TYPE_CHECKING
|
from typing import Self, Sequence, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.icons import (
|
||||||
ERROR,
|
ICON_ERROR,
|
||||||
WARNING,
|
ICON_WARNING,
|
||||||
)
|
)
|
||||||
from dzgui.const.enum import NotebookPage
|
from dzgui.const.enum import NotebookPage
|
||||||
from dzgui.util.css import add_class
|
from dzgui.util.css import add_class
|
||||||
@@ -18,7 +18,7 @@ from dzgui.views.trees.tree_server_mods import ServerModTreeView
|
|||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gdk, Gtk # noqa E402
|
from gi.repository import Gdk, Gtk
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -166,7 +166,7 @@ class PreConnectionAssistant(Gtk.Box):
|
|||||||
|
|
||||||
# TODO: abstract into components
|
# TODO: abstract into components
|
||||||
self.warning_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
self.warning_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
self.warning_tree = MaskedTree(WARNING)
|
self.warning_tree = MaskedTree(ICON_WARNING)
|
||||||
self.warning_placeholder = Placeholder(preconnect.placeholder_no_warnings)
|
self.warning_placeholder = Placeholder(preconnect.placeholder_no_warnings)
|
||||||
self.warning_box.add(self.warning_tree)
|
self.warning_box.add(self.warning_tree)
|
||||||
self.warning_box.add(self.warning_placeholder)
|
self.warning_box.add(self.warning_placeholder)
|
||||||
@@ -175,7 +175,7 @@ class PreConnectionAssistant(Gtk.Box):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
self.error_tree = MaskedTree(ERROR)
|
self.error_tree = MaskedTree(ICON_ERROR)
|
||||||
self.error_placeholder = Placeholder(preconnect.no_errors)
|
self.error_placeholder = Placeholder(preconnect.no_errors)
|
||||||
self.error_box.add(self.error_tree)
|
self.error_box.add(self.error_tree)
|
||||||
self.error_box.add(self.error_placeholder)
|
self.error_box.add(self.error_placeholder)
|
||||||
@@ -231,7 +231,7 @@ class PreConnectionAssistant(Gtk.Box):
|
|||||||
args = self.controller.get_debug_args()
|
args = self.controller.get_debug_args()
|
||||||
d = DebugDialog(self.controller, args)
|
d = DebugDialog(self.controller, args)
|
||||||
d.show_all()
|
d.show_all()
|
||||||
d.run()
|
d.run() # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
def _on_ok_clicked(self, button: Gtk.Button) -> None:
|
def _on_ok_clicked(self, button: Gtk.Button) -> None:
|
||||||
self.controller.update_and_connect()
|
self.controller.update_and_connect()
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ from typing import Self, TYPE_CHECKING
|
|||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.const.enum import ContextMenuGroup, ServerTab
|
from dzgui.const.enum import ContextMenuGroup, ServerTab
|
||||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
from dzgui.strings.generic import tab_changed_label
|
||||||
from dzgui.util.strings import server_labels
|
from dzgui.util.strings import server_labels
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, Gdk, GLib # noqa E402
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
@@ -147,8 +148,7 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
|||||||
|
|
||||||
label = self.notebook.get_tab_label_text(child)
|
label = self.notebook.get_tab_label_text(child)
|
||||||
if label is not None:
|
if label is not None:
|
||||||
# TODO: strings
|
text = label.strip(tab_changed_label)
|
||||||
text = label.strip("*")
|
|
||||||
self.notebook.set_tab_label_text(child, text)
|
self.notebook.set_tab_label_text(child, text)
|
||||||
|
|
||||||
tree = child.get_tree()
|
tree = child.get_tree()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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
|
||||||
|
|
||||||
# TODO: wrap and center long values
|
# TODO: wrap and center long values
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|||||||
@@ -79,11 +79,6 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
|||||||
index = path.get_indices()[0]
|
index = path.get_indices()[0]
|
||||||
return index
|
return index
|
||||||
|
|
||||||
def get_selected_records(self) -> list:
|
|
||||||
sel = self.get_selection()
|
|
||||||
model, rows = sel.get_selected_rows()
|
|
||||||
return [model[row] for row in rows]
|
|
||||||
|
|
||||||
def _on_keypress(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> bool:
|
def _on_keypress(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> bool:
|
||||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
|
|
||||||
@@ -108,14 +103,9 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
|||||||
|
|
||||||
def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
|
def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
|
||||||
"""
|
"""
|
||||||
Suppresses spamming on keydown
|
Adds debounce to key press/release events to prevent spamming
|
||||||
TODO: explain this better
|
when rapidly navigating through records
|
||||||
"""
|
"""
|
||||||
# TODO: multisel
|
|
||||||
# if event.keyval is Gdk.KEY_space:
|
|
||||||
# it = self.get_focused_row_iter()
|
|
||||||
# self.get_selection().select_iter(it)
|
|
||||||
# return True
|
|
||||||
|
|
||||||
model = self.get_model()
|
model = self.get_model()
|
||||||
if model is None:
|
if model is None:
|
||||||
@@ -150,9 +140,11 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
|||||||
self.set_cursor(path)
|
self.set_cursor(path)
|
||||||
|
|
||||||
def get_value_at_index(self, index: int) -> Any:
|
def get_value_at_index(self, index: int) -> Any:
|
||||||
(model, pathlist) = self.get_model_and_pathlist()
|
model, pathlist = self.get_model_and_pathlist()
|
||||||
if len(pathlist) < 1:
|
if len(pathlist) < 1:
|
||||||
return ""
|
return ""
|
||||||
|
if model is None:
|
||||||
|
return ""
|
||||||
path = pathlist[0]
|
path = pathlist[0]
|
||||||
tree_iter = model.get_iter(path)
|
tree_iter = model.get_iter(path)
|
||||||
value = model.get_value(tree_iter, index)
|
value = model.get_value(tree_iter, index)
|
||||||
@@ -166,20 +158,16 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
|||||||
sel = self.get_selection()
|
sel = self.get_selection()
|
||||||
self._on_tree_selection_changed(sel)
|
self._on_tree_selection_changed(sel)
|
||||||
|
|
||||||
def get_model_and_pathlist(self) -> tuple:
|
def get_model_and_pathlist(
|
||||||
|
self,
|
||||||
|
) -> tuple[Gtk.TreeModel | None, list[Gtk.TreePath]]:
|
||||||
select = self.get_selection()
|
select = self.get_selection()
|
||||||
sels = select.get_selected_rows()
|
sels = select.get_selected_rows()
|
||||||
(model, pathlist) = sels
|
model: Gtk.TreeModel | None
|
||||||
|
pathlist: list[Gtk.TreePath]
|
||||||
|
model, pathlist = sels
|
||||||
return (model, pathlist)
|
return (model, pathlist)
|
||||||
|
|
||||||
# @deprecated("Currently unused")
|
|
||||||
# def get_mpath(self) -> Optional[Gtk.TreePath]:
|
|
||||||
# (model, pathlist) = self.get_model_and_pathlist()
|
|
||||||
# if len(pathlist) < 1:
|
|
||||||
# return None
|
|
||||||
# path = pathlist[0]
|
|
||||||
# return path
|
|
||||||
|
|
||||||
def _on_row_activated(
|
def _on_row_activated(
|
||||||
self,
|
self,
|
||||||
treeview: Gtk.TreeView,
|
treeview: Gtk.TreeView,
|
||||||
@@ -194,15 +182,6 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# @deprecated("unused")
|
|
||||||
# def get_selected_row(self) -> Optional[Gtk.TreeModelRow]:
|
|
||||||
# ind = self.get_selected_row_index()
|
|
||||||
# model = self.get_model()
|
|
||||||
# if model is None:
|
|
||||||
# return None
|
|
||||||
# row = model[ind]
|
|
||||||
# return row
|
|
||||||
|
|
||||||
def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any:
|
def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any:
|
||||||
model = self.get_model()
|
model = self.get_model()
|
||||||
if model is None:
|
if model is None:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from dzgui.views.mixins.context_mixin import ContextMixin
|
|||||||
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, Gdk
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
@@ -77,4 +77,4 @@ class MenuTreeView(TreeView):
|
|||||||
raise ValueError("Trying to call a method on non-existent model")
|
raise ValueError("Trying to call a method on non-existent model")
|
||||||
_iter = self.get_focused_row_iter()
|
_iter = self.get_focused_row_iter()
|
||||||
rowtype = model.get_value(_iter, 1)
|
rowtype = model.get_value(_iter, 1)
|
||||||
return str(rowtype.dict["tooltip"])
|
return str(rowtype.tooltip)
|
||||||
@@ -7,7 +7,7 @@ from warnings import deprecated
|
|||||||
|
|
||||||
from dzgui.api.servers import ping, Record
|
from dzgui.api.servers import ping, Record
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME
|
||||||
from dzgui.const.enum import ContextMenu, ContextMenuGroup, ServerTab
|
from dzgui.const.enum import ContextMenuItem, ContextMenuGroup, ServerTab
|
||||||
from dzgui.managers.filter import FilterManager
|
from dzgui.managers.filter import FilterManager
|
||||||
from dzgui.model.proxy_model import ProxyModelManager
|
from dzgui.model.proxy_model import ProxyModelManager
|
||||||
from dzgui.util import strings
|
from dzgui.util import strings
|
||||||
@@ -32,6 +32,7 @@ if TYPE_CHECKING:
|
|||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
from dzgui.controllers.emitter import Emitter
|
from dzgui.controllers.emitter import Emitter
|
||||||
from dzgui.model.model_factory import FastInsertListStore
|
from dzgui.model.model_factory import FastInsertListStore
|
||||||
|
from dzgui.util.dist import Haversine
|
||||||
|
|
||||||
QUEUE_CHECK_DELAY = 200
|
QUEUE_CHECK_DELAY = 200
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
|
|
||||||
self.queue_id: int
|
self.queue_id: int
|
||||||
self.handler_id: int
|
self.handler_id: int
|
||||||
self.queue: Queue = Queue()
|
self.queue: Queue[tuple[str, "Haversine" | None, ServerTab]] = Queue()
|
||||||
|
|
||||||
self.seen_cache: list[Record] = []
|
self.seen_cache: list[Record] = []
|
||||||
|
|
||||||
@@ -250,9 +251,9 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
if self.enum is ServerTab.LAN:
|
if self.enum is ServerTab.LAN:
|
||||||
self.emitter.emit("request_custom_port_focus")
|
self.emitter.emit("request_custom_port_focus")
|
||||||
case Gdk.KEY_c:
|
case Gdk.KEY_c:
|
||||||
self.controller.menu_action(ContextMenu.COPY_SERVER_IP, self)
|
self.controller.menu_action(ContextMenuItem.COPY_SERVER_IP, self)
|
||||||
case Gdk.KEY_r:
|
case Gdk.KEY_r:
|
||||||
self.controller.menu_action(ContextMenu.REFRESH_PLAYERS, self)
|
self.controller.menu_action(ContextMenuItem.REFRESH_PLAYERS, self)
|
||||||
else:
|
else:
|
||||||
match event.keyval:
|
match event.keyval:
|
||||||
case Gdk.KEY_l | Gdk.KEY_Right:
|
case Gdk.KEY_l | Gdk.KEY_Right:
|
||||||
@@ -406,11 +407,15 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
path = model.get_path(it)
|
path = model.get_path(it)
|
||||||
row_index = path.get_indices()[0]
|
row_index = path.get_indices()[0]
|
||||||
try:
|
try:
|
||||||
start, end = self.get_visible_range()
|
start, end = self.get_visible_range() # type: ignore[no-untyped-call]
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
if row_index >= start[0] <= end[0]:
|
if row_index >= start[0] <= end[0]:
|
||||||
# NOTE: fetch raw data rows
|
# NOTE: fetch raw data rows
|
||||||
real_model = self.proxy_man.get_control()
|
real_model = self.proxy_man.get_control()
|
||||||
value = real_model[row_index][col_index]
|
from dataclasses import fields
|
||||||
|
from dzgui.model.proxy_model import ServerRow
|
||||||
|
key = fields(ServerRow)[col_index].name
|
||||||
|
|
||||||
|
value = getattr(real_model[row_index], key)
|
||||||
cell.set_property("text", str(value))
|
cell.set_property("text", str(value))
|
||||||
+3
-2
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
|||||||
authors = [
|
authors = [
|
||||||
{name = "aclist"}
|
{name = "aclist"}
|
||||||
]
|
]
|
||||||
version = "7.0.0b28"
|
version = "7.0.0b30"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -78,6 +78,7 @@ line-length = 88
|
|||||||
indent-width = 4
|
indent-width = 4
|
||||||
extend-exclude = ["*lib/a2s"]
|
extend-exclude = ["*lib/a2s"]
|
||||||
lint.unfixable = ["F401", "I001"]
|
lint.unfixable = ["F401", "I001"]
|
||||||
|
lint.ignore = ["E402"]
|
||||||
|
|
||||||
[tool.ruff.format]
|
[tool.ruff.format]
|
||||||
quote-style = "double"
|
quote-style = "double"
|
||||||
@@ -104,7 +105,7 @@ python-a2s = { path = "./dzgui/lib/a2s", editable = true }
|
|||||||
dayzquery = { path = "./dzgui/lib/dayzquery", editable = true }
|
dayzquery = { path = "./dzgui/lib/dayzquery", editable = true }
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3,Soup2" } }
|
config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3" } }
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
markers = [
|
markers = [
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from pathlib import Path
|
from shared import get_git_root
|
||||||
|
|
||||||
BOILERPLATE = """
|
BOILERPLATE = """
|
||||||
This file includes the combined licenses to the following components:
|
This file includes the combined licenses to the following components:
|
||||||
@@ -40,7 +40,7 @@ class CombinedLicense:
|
|||||||
|
|
||||||
def concat_license() -> str:
|
def concat_license() -> str:
|
||||||
combined = CombinedLicense()
|
combined = CombinedLicense()
|
||||||
root = Path(__file__).resolve().parents[1]
|
root = get_git_root()
|
||||||
licenses = root.joinpath("docs/licenses")
|
licenses = root.joinpath("docs/licenses")
|
||||||
|
|
||||||
main_license = root.joinpath(LICENSE_FILE)
|
main_license = root.joinpath(LICENSE_FILE)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
par = Path(__file__).parent.parent
|
from shared import get_git_root
|
||||||
clog = par / "CHANGELOG.md"
|
|
||||||
target = par / "dzgui/data/CHANGELOG.md"
|
root = get_git_root()
|
||||||
|
clog = root.joinpath("CHANGELOG.md")
|
||||||
|
target = root.joinpath("dzgui/data/CHANGELOG.md")
|
||||||
|
|
||||||
shutil.copy(clog, target)
|
shutil.copy(clog, target)
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user