mirror of
https://github.com/aclist/dztui.git
synced 2026-11-10 20:10:38 +01:00
Compare commits
142
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
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 | ||
|
|
25ae2052e4 | ||
|
|
1b1777117f | ||
|
|
3e6bd95285 | ||
|
|
f4be9a31e9 | ||
|
|
a6282e4401 | ||
|
|
a9ae5f59b5 | ||
|
|
cdec81071d | ||
|
|
97b5c0e6ee | ||
|
|
67c19fb90d | ||
|
|
d9d3f747bb | ||
|
|
1440871819 | ||
|
|
761988d8c2 | ||
|
|
97ee2b7307 | ||
|
|
ff6edb6181 | ||
|
|
4c22263fb5 | ||
|
|
ab5637ae3b | ||
|
|
682532a867 | ||
|
|
d577ba0970 | ||
|
|
c2993ad289 | ||
|
|
edb87893f9 | ||
|
|
519e6c354d | ||
|
|
01b3babdf5 | ||
|
|
141f96fb9f | ||
|
|
e286f02342 | ||
|
|
9f3c39fdd6 | ||
|
|
0359aacd54 | ||
|
|
80e127889e | ||
|
|
da98b1d5b1 | ||
|
|
33869158de |
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
|
||||||
|
|
||||||
@@ -35,6 +35,10 @@ Geolocation records from [DB-IP](https://db-ip.com) under [CC 4.0 license](https
|
|||||||
Executable versions of DZGUI published as release binaries ship with various runtime dependencies and the Python interpreter built in.
|
Executable versions of DZGUI published as release binaries ship with various runtime dependencies and the Python interpreter built in.
|
||||||
Users wishing to review the licenses to these components can inspect the `LICENSE` file located in the release tarball.
|
Users wishing to review the licenses to these components can inspect the `LICENSE` file located in the release tarball.
|
||||||
|
|
||||||
|
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 []
|
||||||
|
|||||||
@@ -111,7 +111,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 +139,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[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]]:
|
||||||
"""
|
"""
|
||||||
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
|
||||||
@@ -507,14 +509,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]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from dzgui.api.steam import find_user_id_32
|
from dzgui.api.steam import find_user_id_32
|
||||||
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, GTK_APP_ID, IMAGES_PATH
|
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, IMAGES_PATH
|
||||||
from dzgui.util.dirs import copy_dzgui_to_xdg_data, make_parents
|
from dzgui.util.dirs import copy_dzgui_to_xdg_data, make_parents
|
||||||
from dzgui.util.strings import unknown
|
from dzgui.util.strings import unknown
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ class Shortcuts:
|
|||||||
NEW_ENTRY["DevkitGameID"] = ""
|
NEW_ENTRY["DevkitGameID"] = ""
|
||||||
NEW_ENTRY["DevkitOverrideAppID"] = 0
|
NEW_ENTRY["DevkitOverrideAppID"] = 0
|
||||||
NEW_ENTRY["LastPlayTime"] = 0
|
NEW_ENTRY["LastPlayTime"] = 0
|
||||||
NEW_ENTRY["FlatpakAppID"] = GTK_APP_ID
|
NEW_ENTRY["FlatpakAppID"] = ""
|
||||||
|
|
||||||
NEW_ENTRY["sortas"] = ""
|
NEW_ENTRY["sortas"] = ""
|
||||||
NEW_ENTRY["tags"] = {}
|
NEW_ENTRY["tags"] = {}
|
||||||
@@ -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)
|
||||||
|
|||||||
+43
-10
@@ -12,7 +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.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,
|
||||||
@@ -24,15 +26,15 @@ from dzgui.const.constants import (
|
|||||||
DOWNLOADS_PATH,
|
DOWNLOADS_PATH,
|
||||||
FLATPAK_STEAM_PATH,
|
FLATPAK_STEAM_PATH,
|
||||||
LIBRARYFOLDERS_PATH,
|
LIBRARYFOLDERS_PATH,
|
||||||
UBUNTU_STEAM_PATH,
|
|
||||||
REQUEST_TIMEOUT,
|
REQUEST_TIMEOUT,
|
||||||
|
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
|
||||||
@@ -82,6 +84,10 @@ 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_flatpak_client() is False:
|
||||||
|
continue
|
||||||
|
if path is not FLATPAK_PATH and has_steam_client() is False:
|
||||||
|
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]))
|
||||||
return paths
|
return paths
|
||||||
@@ -198,6 +204,7 @@ def load_to_menu(client: str, appid: int, name: str, mods: list[str]) -> int:
|
|||||||
"""Loads to the menu screen with the selected mods; used for AFK/pre-joining"""
|
"""Loads to the menu screen with the selected mods; used for AFK/pre-joining"""
|
||||||
concat = concat_mods(mods)
|
concat = concat_mods(mods)
|
||||||
client_args = concat_bash_args(client)
|
client_args = concat_bash_args(client)
|
||||||
|
logger.debug(f"Setting client arguments to: {client_args}")
|
||||||
params = [
|
params = [
|
||||||
"-applaunch",
|
"-applaunch",
|
||||||
str(appid),
|
str(appid),
|
||||||
@@ -303,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")
|
||||||
@@ -383,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)
|
||||||
@@ -486,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:
|
||||||
@@ -494,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])
|
||||||
+25
-26
@@ -2,12 +2,17 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
from importlib import resources
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from warnings import deprecated
|
|
||||||
|
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, STEAM_ICON, STEAM_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,8 +22,7 @@ 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_any_client
|
|
||||||
from dzgui.strings import boot
|
from dzgui.strings import boot
|
||||||
|
|
||||||
from dzgui.util.deck import is_steam_deck, is_game_mode
|
from dzgui.util.deck import is_steam_deck, is_game_mode
|
||||||
@@ -26,7 +30,7 @@ from dzgui.util.dirs import make_parents
|
|||||||
from dzgui.util.localize import set_locale
|
from dzgui.util.localize import set_locale
|
||||||
from dzgui.util.map_count import test_map_count
|
from dzgui.util.map_count import test_map_count
|
||||||
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
|
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
|
||||||
from dzgui.util.strings import init, steam_icon_missing
|
from dzgui.util.strings import init
|
||||||
|
|
||||||
from dzgui.views.base import App
|
from dzgui.views.base import App
|
||||||
from dzgui.views.dialogs.boot import BootWindow
|
from dzgui.views.dialogs.boot import BootWindow
|
||||||
@@ -99,31 +103,24 @@ def copy_bare_configs(config: "Path", resolution: "Path") -> tuple[bool, bool]:
|
|||||||
return (config_changed, state_changed)
|
return (config_changed, state_changed)
|
||||||
|
|
||||||
|
|
||||||
@deprecated("Currently unused")
|
def load_steam_icon() -> None:
|
||||||
def load_steam_icon(config: Path) -> 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
|
||||||
|
|
||||||
default = lookup(config, Preferences.DEFAULT)
|
|
||||||
icon_path = Path(default).joinpath(STEAM_ICONS_PATH)
|
|
||||||
try:
|
try:
|
||||||
|
traversable = resources.files(APP_NAME_LOWER).joinpath(ICONS_PATH)
|
||||||
|
icons = Path(str(traversable))
|
||||||
|
|
||||||
theme = Gtk.IconTheme.get_default()
|
theme = Gtk.IconTheme.get_default()
|
||||||
paths = theme.get_search_path()
|
paths = theme.get_search_path()
|
||||||
theme.set_search_path(paths + [str(icon_path)])
|
theme.set_search_path(paths + [str(icons)])
|
||||||
paths = theme.get_search_path()
|
|
||||||
icons = theme.list_icons(None)
|
|
||||||
if STEAM_ICON not in icons:
|
|
||||||
logger.warning(steam_icon_missing)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
logger.critical(e)
|
||||||
logger.warning(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()
|
||||||
@@ -148,20 +145,21 @@ def load_gui(version: str, is_debug: bool) -> None:
|
|||||||
migrate_cols_file(XDG.columns)
|
migrate_cols_file(XDG.columns)
|
||||||
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
||||||
# TODO: add logging inside wizard
|
# TODO: add logging inside wizard
|
||||||
wizard = SetupWizard(_is_steam_deck, XDG)
|
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
|
if FLATPAK_ROOT_PATH in lookup(XDG.config, Preferences.DEFAULT):
|
||||||
# count = get_map_count()
|
has_client = has_flatpak_client()
|
||||||
# TODO: move into module
|
else:
|
||||||
if has_any_client() is False:
|
has_client = has_steam_client()
|
||||||
|
if has_client is False:
|
||||||
EarlyAlertDialog(init.requires_steam)
|
EarlyAlertDialog(init.requires_steam)
|
||||||
|
|
||||||
bootwin = BootWindow(XDG, version)
|
bootwin = BootWindow(XDG, version)
|
||||||
@@ -180,5 +178,6 @@ def load_gui(version: str, is_debug: bool) -> None:
|
|||||||
use_miles=use_miles,
|
use_miles=use_miles,
|
||||||
)
|
)
|
||||||
print(boot.all_ok)
|
print(boot.all_ok)
|
||||||
|
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)
|
||||||
+15
-7
@@ -3,7 +3,13 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME_LOWER, DEBUG_LOG, SYSTEM_LOG
|
from dzgui.const.constants import (
|
||||||
|
APP_NAME_LOWER,
|
||||||
|
DEBUG_LOG,
|
||||||
|
FLATPAK_ROOT_PATH,
|
||||||
|
SYSTEM_LOG,
|
||||||
|
)
|
||||||
|
from dzgui.init.proc import is_sandboxed
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -16,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
|
||||||
|
|
||||||
@@ -45,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",
|
||||||
@@ -53,10 +59,12 @@ def get_xdg_paths() -> dict:
|
|||||||
"XDG_DATA_HOME": home / ".local" / "share",
|
"XDG_DATA_HOME": home / ".local" / "share",
|
||||||
"XDG_CACHE_HOME": home / ".cache",
|
"XDG_CACHE_HOME": home / ".cache",
|
||||||
}
|
}
|
||||||
|
|
||||||
resolved_paths = {}
|
resolved_paths = {}
|
||||||
for path in xdg_paths:
|
for path in xdg_paths:
|
||||||
real_path = os.environ.get(path)
|
real_path = os.environ.get(path)
|
||||||
|
if real_path is not None and is_sandboxed():
|
||||||
|
search_string = FLATPAK_ROOT_PATH + "/"
|
||||||
|
real_path = real_path.replace(search_string, "")
|
||||||
if real_path is not None and is_writeable(real_path):
|
if real_path is not None and is_writeable(real_path):
|
||||||
new_path = Path(real_path)
|
new_path = Path(real_path)
|
||||||
else:
|
else:
|
||||||
@@ -65,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"]
|
||||||
@@ -80,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
|
||||||
@@ -94,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_mono"
|
|
||||||
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
|
||||||
@@ -80,6 +51,7 @@ DEBUG_LOG = f"{APP_NAME}_DEBUG.LOG"
|
|||||||
SYSTEM_LOG = f"{APP_NAME}_SYSTEM.LOG"
|
SYSTEM_LOG = f"{APP_NAME}_SYSTEM.LOG"
|
||||||
CHANGELOG_PATH = "data/CHANGELOG.md"
|
CHANGELOG_PATH = "data/CHANGELOG.md"
|
||||||
IMAGES_PATH = "data/images"
|
IMAGES_PATH = "data/images"
|
||||||
|
ICONS_PATH = "data/images/gtk"
|
||||||
|
|
||||||
HERO_PATH = "data/images/_hero.png"
|
HERO_PATH = "data/images/_hero.png"
|
||||||
CSS_PATH = "data/app.css"
|
CSS_PATH = "data/app.css"
|
||||||
@@ -89,13 +61,13 @@ FLATPAK_ROOT_PATH = ".var/app/com.valvesoftware.Steam"
|
|||||||
FLATPAK_STEAM_PATH = FLATPAK_ROOT_PATH + "/data/Steam"
|
FLATPAK_STEAM_PATH = FLATPAK_ROOT_PATH + "/data/Steam"
|
||||||
UBUNTU_STEAM_PATH = ".steam/steam"
|
UBUNTU_STEAM_PATH = ".steam/steam"
|
||||||
DEBIAN_STEAM_PATH = ".steam/debian-installation"
|
DEBIAN_STEAM_PATH = ".steam/debian-installation"
|
||||||
STEAM_ICONS_PATH = "public"
|
|
||||||
|
|
||||||
LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
|
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 |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.3.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M568 320C568 457 456.8 568 319.6 568C205.8 568 110 491.7 80.6 387.6L175.8 426.9C182.2 459 210.7 483.3 244.7 483.3C283.9 483.3 316.6 450.9 314.9 409.8L399.4 349.6C451.5 350.9 495.2 308.7 495.2 256.1C495.2 204.5 453.2 162.6 401.5 162.6C349.8 162.6 307.8 204.6 307.8 256.1L307.8 257.3L248.6 343C233.1 342.1 217.9 346.4 205.1 355.1L72 300.1C82.2 172.4 189.1 72 319.6 72C456.8 72 568 183 568 320zM227.7 448.3L197.2 435.7C202.8 447.3 212.5 456.5 224.4 461.5C251.3 472.7 282.2 459.9 293.4 433.1C298.8 420.1 298.9 405.8 293.5 392.8C288.1 379.8 278 369.6 265 364.2C252.1 358.8 238.3 359 226.1 363.6L257.6 376.6C277.4 384.8 286.8 407.5 278.5 427.3C270.2 447.2 247.5 456.5 227.7 448.3zM401.5 193.8C435.9 193.8 463.8 221.7 463.8 256.1C463.8 290.5 435.9 318.4 401.5 318.4C367.1 318.4 339.2 290.5 339.2 256.1C339.2 221.7 367.1 193.8 401.5 193.8zM401.6 302.8C427.4 302.8 448.4 281.8 448.4 256C448.4 230.2 427.4 209.2 401.6 209.2C375.8 209.2 354.8 230.2 354.8 256C354.8 281.8 375.8 302.8 401.6 302.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 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"],
|
||||||
|
|||||||
+10
-3
@@ -1,7 +1,8 @@
|
|||||||
import psutil
|
|
||||||
import subprocess
|
|
||||||
import shutil
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import psutil
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
@@ -26,7 +27,13 @@ def is_steam_running(cmd: str) -> bool:
|
|||||||
raise TypeError("Not a valid Steam client selection")
|
raise TypeError("Not a valid Steam client selection")
|
||||||
|
|
||||||
|
|
||||||
|
def is_sandboxed() -> bool:
|
||||||
|
return bool(os.environ.get("FLATPAK_ID"))
|
||||||
|
|
||||||
|
|
||||||
def is_flatpak_steam_running() -> bool:
|
def is_flatpak_steam_running() -> bool:
|
||||||
|
if is_sandboxed():
|
||||||
|
return True
|
||||||
if has_cmd(FLATPAK_CMD) is False:
|
if has_cmd(FLATPAK_CMD) is False:
|
||||||
return False
|
return False
|
||||||
proc = subprocess.check_output(
|
proc = subprocess.check_output(
|
||||||
|
|||||||
+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
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any, TYPE_CHECKING, Union
|
from typing import Any, TYPE_CHECKING, Union
|
||||||
|
|
||||||
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 +18,22 @@ 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
|
||||||
|
|
||||||
|
|
||||||
class ProxyModelManager:
|
class ProxyModelManager:
|
||||||
"""
|
"""
|
||||||
Manages access to cached FastInsertListStore resources and
|
Manages access to cached FastInsertListStore resources and
|
||||||
@@ -30,7 +48,7 @@ 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[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]],
|
||||||
] = {}
|
] = {}
|
||||||
|
|
||||||
@@ -51,9 +69,6 @@ class ProxyModelManager:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def append_row(self, row: list) -> None:
|
|
||||||
self.proxy_model.append(row)
|
|
||||||
|
|
||||||
def append_row_to_control(
|
def append_row_to_control(
|
||||||
self, row: tuple[str, str, str, str, int, int, int, str, int, int, str, bool]
|
self, row: tuple[str, str, str, str, int, int, int, str, int, int, str, bool]
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -67,7 +82,6 @@ class ProxyModelManager:
|
|||||||
self.proxy_model[treeiter][4] = playercount.players
|
self.proxy_model[treeiter][4] = playercount.players
|
||||||
self.proxy_model[treeiter][6] = playercount.queue
|
self.proxy_model[treeiter][6] = 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: tuple[str, str, str, str, int, int, int, str, int, int, str, bool],
|
||||||
@@ -175,7 +189,7 @@ class ProxyModelManager:
|
|||||||
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[1] == sel_map]
|
||||||
@@ -247,7 +261,7 @@ class ProxyModelManager:
|
|||||||
rows = [row for row in rows if row[11]]
|
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:
|
||||||
"""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,7 +272,7 @@ 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[tuple]) -> 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 | None) -> None:
|
||||||
|
|||||||
+37
-11
@@ -21,7 +21,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, 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
|
||||||
@@ -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,19 +201,43 @@ 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,
|
||||||
@@ -279,7 +304,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 +432,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,14 +451,14 @@ 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[Any]) -> 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[1] for row in data])
|
||||||
self._set_new_maps(sorted(u_maps))
|
self._set_new_maps(sorted(u_maps))
|
||||||
|
|
||||||
|
|||||||
@@ -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,4 +16,13 @@ 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_standard = "Standard"
|
||||||
|
flatpak_container = "Containerized (flatpak-spawn --host)"
|
||||||
|
flatpak_info = (
|
||||||
|
"Containerized mode is used on certain distributions. May have side effects."
|
||||||
|
)
|
||||||
@@ -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"
|
||||||
+16
-2
@@ -3,6 +3,8 @@ title_intro = "Welcome"
|
|||||||
blurb_intro = """
|
blurb_intro = """
|
||||||
This wizard will help you set up some common config options before launching the application.
|
This wizard will help you set up some common config options before launching the application.
|
||||||
"""
|
"""
|
||||||
|
error_sandbox = "Setup cannot be run from inside Flatpak sandbox.\nPlease launch the application directly."
|
||||||
|
welcome = "Welcome to {0}!"
|
||||||
|
|
||||||
### SteamPathPage
|
### SteamPathPage
|
||||||
heading_steam_path = "Steam path"
|
heading_steam_path = "Steam path"
|
||||||
@@ -17,7 +19,7 @@ desc_flatpak_path = (
|
|||||||
desc_ubuntu_path = "This is the default Steam path on <b>Ubuntu-based systems</b>."
|
desc_ubuntu_path = "This is the default Steam path on <b>Ubuntu-based systems</b>."
|
||||||
desc_debian_path = "This is the default Steam path on <b>Debian-based systems</b>."
|
desc_debian_path = "This is the default Steam path on <b>Debian-based systems</b>."
|
||||||
no_valid_paths = (
|
no_valid_paths = (
|
||||||
"No valid Steam paths found on system. Please install Steam to continue."
|
"No valid Steam path or Steam command found on system. Please install Steam to continue."
|
||||||
)
|
)
|
||||||
button_scan = "Scan for Steam"
|
button_scan = "Scan for Steam"
|
||||||
|
|
||||||
@@ -70,7 +72,8 @@ checkbox_steam_shortcut = (
|
|||||||
(
|
(
|
||||||
"This creates a standalone copy of DZGUI and adds it to Steam. "
|
"This creates a standalone copy of DZGUI and adds it to Steam. "
|
||||||
"You <b>must restart Steam</b> for the shortcut to appear. This will not replace or update existing shortcuts. "
|
"You <b>must restart Steam</b> for the shortcut to appear. This will not replace or update existing shortcuts. "
|
||||||
"If you update DZGUI when it is launched via Steam, updates will be applied to the Steam version."
|
"If you update DZGUI when it is launched via Steam, updates will be applied to the Steam version. "
|
||||||
|
"Not available if you selected the Flatpak client as your Steam default."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
checkbox_start_menu = (
|
checkbox_start_menu = (
|
||||||
@@ -79,6 +82,17 @@ checkbox_start_menu = (
|
|||||||
)
|
)
|
||||||
checkbox_desktop_shortcut = ("Add a desktop shortcut", "Depends on the option above.")
|
checkbox_desktop_shortcut = ("Add a desktop shortcut", "Depends on the option above.")
|
||||||
|
|
||||||
|
### Flatpak page
|
||||||
|
heading_flatpak = "Flatpak sandbox overrides"
|
||||||
|
blurb_flatpak = (
|
||||||
|
"You chose to add a shortcut directly to Flatpak Steam. "
|
||||||
|
"To launch DZGUI from Flatpak's sandbox, you must grant "
|
||||||
|
"permission to read from and write to the following locations."
|
||||||
|
)
|
||||||
|
tree_column = "Paths"
|
||||||
|
overrides_applied = "Overrides applied."
|
||||||
|
button_flatpak = "Grant permissions"
|
||||||
|
|
||||||
### Completion page
|
### Completion page
|
||||||
heading_completion = "Setup complete"
|
heading_completion = "Setup complete"
|
||||||
blurb_completion = "Configuration completed successfully. Click 'Close' to close this dialog and launch DZGUI."
|
blurb_completion = "Configuration completed successfully. Click 'Close' to close this dialog and launch DZGUI."
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
|
from dzgui.const.constants import STEAM_CMD
|
||||||
|
from dzgui.init.proc import is_sandboxed
|
||||||
|
|
||||||
|
|
||||||
def concat_bash_args(command: str) -> list[str]:
|
def concat_bash_args(command: str) -> list[str]:
|
||||||
|
# NOTE: NSGs spawned from sandbox cannot access host application commands,
|
||||||
|
# so fallback to Steam internal URIs
|
||||||
|
if is_sandboxed():
|
||||||
|
return [STEAM_CMD]
|
||||||
return shlex.split(command)
|
return shlex.split(command)
|
||||||
+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:
|
||||||
|
|||||||
+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)
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, GTK_APP_ID, TMP_PATH
|
||||||
|
|
||||||
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def apply_overrides(xdg: dict[str, "Path"]) -> None:
|
||||||
|
try:
|
||||||
|
# NOTE: D-Bus registration required for sandbox
|
||||||
|
args = [
|
||||||
|
"flatpak",
|
||||||
|
"--user",
|
||||||
|
"override",
|
||||||
|
f"--own-name={GTK_APP_ID}",
|
||||||
|
FLATPAK_APPID,
|
||||||
|
]
|
||||||
|
subprocess.run([*args], capture_output=True)
|
||||||
|
|
||||||
|
paths: list[str] = []
|
||||||
|
paths.extend([str(path) for path in xdg.values()])
|
||||||
|
paths.append(TMP_PATH)
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
args = [
|
||||||
|
"flatpak",
|
||||||
|
"--user",
|
||||||
|
"override",
|
||||||
|
f"--filesystem={path}",
|
||||||
|
FLATPAK_APPID,
|
||||||
|
]
|
||||||
|
subprocess.run([*args], capture_output=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
@@ -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)
|
||||||
@@ -297,7 +299,7 @@ class Options:
|
|||||||
init = Init(
|
init = Init(
|
||||||
is_steam_running="Is Steam running? DZGUI must be run on top of Steam.",
|
is_steam_running="Is Steam running? DZGUI must be run on top of Steam.",
|
||||||
is_dayz_running="Is DayZ already running? DZGUI cannot launch DayZ if another process is using it.",
|
is_dayz_running="Is DayZ already running? DZGUI cannot launch DayZ if another process is using it.",
|
||||||
requires_steam="DZGUI requires that Steam or Flatpak Steam be installed on the system.",
|
requires_steam="Your chosen client (Steam/Flatpak Steam) must be installed on the system.",
|
||||||
)
|
)
|
||||||
|
|
||||||
mod_panel = ModPanelStrings(
|
mod_panel = ModPanelStrings(
|
||||||
@@ -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():
|
||||||
|
|||||||
@@ -12,26 +12,30 @@ from dzgui.const.constants import (
|
|||||||
APP_NAME,
|
APP_NAME,
|
||||||
APP_NAME_LOWER,
|
APP_NAME_LOWER,
|
||||||
HERO_PATH,
|
HERO_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
|
||||||
from dzgui.config import freedesktop
|
from dzgui.config import freedesktop
|
||||||
from dzgui.init.proc import path_to_steam_cmd
|
from dzgui.init.proc import is_sandboxed, path_to_steam_cmd
|
||||||
from dzgui.init.migrate import migrate_legacy_conf
|
from dzgui.init.migrate import migrate_legacy_conf
|
||||||
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
||||||
from dzgui.strings import wizard
|
from dzgui.strings import wizard
|
||||||
from dzgui.util._json import write_json
|
from dzgui.util._json import write_json
|
||||||
|
from dzgui.util.flatpak import apply_overrides
|
||||||
from dzgui.util.open_links import open_link_by_url
|
from dzgui.util.open_links import open_link_by_url
|
||||||
from dzgui.util.css import add_class, load_css
|
from dzgui.util.css import add_class, load_css
|
||||||
from dzgui.views.components.buttons import WebButton
|
from dzgui.views.components.buttons import WebButton
|
||||||
from dzgui.views.components.entry import APIEntry
|
from dzgui.views.components.entry import APIEntry
|
||||||
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||||
|
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
|
||||||
@@ -44,7 +48,8 @@ class PageNum(Enum):
|
|||||||
STEAM_API = 4
|
STEAM_API = 4
|
||||||
USER_PREFS = 5
|
USER_PREFS = 5
|
||||||
SHORTCUTS = 6
|
SHORTCUTS = 6
|
||||||
FINAL = 7
|
FLATPAK = 7
|
||||||
|
FINAL = 8
|
||||||
|
|
||||||
|
|
||||||
class OptionalPageMixin:
|
class OptionalPageMixin:
|
||||||
@@ -143,7 +148,7 @@ class NotificationFrame(Gtk.Frame):
|
|||||||
margin_start=50,
|
margin_start=50,
|
||||||
margin_end=50,
|
margin_end=50,
|
||||||
)
|
)
|
||||||
wrapped = textwrap.fill(label, width=80)
|
wrapped = textwrap.fill(label, width=60)
|
||||||
self.label = Gtk.Label(
|
self.label = Gtk.Label(
|
||||||
label=wrapped,
|
label=wrapped,
|
||||||
justify=Gtk.Justification.CENTER,
|
justify=Gtk.Justification.CENTER,
|
||||||
@@ -157,13 +162,18 @@ class NotificationFrame(Gtk.Frame):
|
|||||||
add_class(self, "error-frame")
|
add_class(self, "error-frame")
|
||||||
|
|
||||||
def set_text(self, text: str) -> None:
|
def set_text(self, text: str) -> None:
|
||||||
wrapped = textwrap.fill(text, width=80)
|
wrapped = textwrap.fill(text, width=60)
|
||||||
self.label.set_markup(wrapped)
|
self.label.set_markup(wrapped)
|
||||||
|
|
||||||
|
|
||||||
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,
|
||||||
@@ -242,12 +252,23 @@ class IntroductionPage(EnumeratedWizardPage):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
enum=PageNum.INTRO,
|
enum=PageNum.INTRO,
|
||||||
heading=f"Welcome to {APP_NAME}!",
|
heading=wizard.welcome.format(APP_NAME),
|
||||||
description=wizard.blurb_intro,
|
description=wizard.blurb_intro,
|
||||||
)
|
)
|
||||||
self.page_type = Gtk.AssistantPageType.INTRO
|
self.page_type = Gtk.AssistantPageType.INTRO
|
||||||
self.set_title(wizard.title_intro)
|
self.set_title(wizard.title_intro)
|
||||||
|
|
||||||
|
self.error_box = NotificationFrame(wizard.error_sandbox, error=True)
|
||||||
|
self.add_start(self.error_box)
|
||||||
|
|
||||||
|
self.connect("map", self._on_map)
|
||||||
|
|
||||||
|
def _on_map(self, page: "EnumeratedWizardPage") -> None:
|
||||||
|
self.error_box.hide()
|
||||||
|
if is_sandboxed():
|
||||||
|
self.error_box.show()
|
||||||
|
EMITTER.emit("step_pending")
|
||||||
|
|
||||||
|
|
||||||
class Heading(Gtk.Label):
|
class Heading(Gtk.Label):
|
||||||
def __init__(self, label: str):
|
def __init__(self, label: str):
|
||||||
@@ -404,6 +425,44 @@ class PreferencesPage(EnumeratedWizardPage):
|
|||||||
EMITTER.emit("step_complete")
|
EMITTER.emit("step_complete")
|
||||||
|
|
||||||
|
|
||||||
|
class FlatpakOverridePage(EnumeratedWizardPage):
|
||||||
|
def __init__(self, xdg_paths: dict[str, Path]) -> None:
|
||||||
|
super().__init__(
|
||||||
|
enum=PageNum.FLATPAK,
|
||||||
|
heading=wizard.heading_flatpak,
|
||||||
|
description=wizard.blurb_flatpak,
|
||||||
|
)
|
||||||
|
self.xdg_paths = xdg_paths
|
||||||
|
self.page_type = Gtk.AssistantPageType.INTRO
|
||||||
|
|
||||||
|
store = Gtk.ListStore(str)
|
||||||
|
for path in xdg_paths.values():
|
||||||
|
store.append((str(path),))
|
||||||
|
store.append((TMP_PATH,))
|
||||||
|
|
||||||
|
tree = SimpleTree(store, wizard.tree_column)
|
||||||
|
tree.set_halign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
|
self.authorize_button = Gtk.Button(
|
||||||
|
label=wizard.button_flatpak, halign=Gtk.Align.CENTER
|
||||||
|
)
|
||||||
|
self.authorize_button.connect("clicked", self._on_authorize)
|
||||||
|
|
||||||
|
self.notice = NotificationFrame(wizard.overrides_applied)
|
||||||
|
self.connect("map", self._on_map)
|
||||||
|
|
||||||
|
for el in tree, self.authorize_button, self.notice:
|
||||||
|
self.add_start(el)
|
||||||
|
|
||||||
|
def _on_map(self, page: "EnumeratedWizardPage") -> None:
|
||||||
|
self.notice.hide()
|
||||||
|
|
||||||
|
def _on_authorize(self, button: Gtk.Button) -> None:
|
||||||
|
apply_overrides(self.xdg_paths)
|
||||||
|
self.notice.show()
|
||||||
|
EMITTER.emit("step_complete")
|
||||||
|
|
||||||
|
|
||||||
class CompletionPage(EnumeratedWizardPage):
|
class CompletionPage(EnumeratedWizardPage):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -417,7 +476,7 @@ class CompletionPage(EnumeratedWizardPage):
|
|||||||
|
|
||||||
|
|
||||||
class Assistant(Gtk.Assistant):
|
class Assistant(Gtk.Assistant):
|
||||||
def __init__(self, is_deck: bool, XDG: "Xdg"):
|
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
|
||||||
@@ -436,6 +495,7 @@ class Assistant(Gtk.Assistant):
|
|||||||
self.page_api = SteamValidationPage()
|
self.page_api = SteamValidationPage()
|
||||||
self.page_prefs = PreferencesPage()
|
self.page_prefs = PreferencesPage()
|
||||||
self.page_shortcuts = ShortcutCreationPage(XDG.shortcut)
|
self.page_shortcuts = ShortcutCreationPage(XDG.shortcut)
|
||||||
|
self.page_flatpak = FlatpakOverridePage(xdg_paths)
|
||||||
self.page_completion = CompletionPage()
|
self.page_completion = CompletionPage()
|
||||||
|
|
||||||
self.set_forward_page_func(self._advance_page)
|
self.set_forward_page_func(self._advance_page)
|
||||||
@@ -455,6 +515,7 @@ class Assistant(Gtk.Assistant):
|
|||||||
self.page_api,
|
self.page_api,
|
||||||
self.page_prefs,
|
self.page_prefs,
|
||||||
self.page_shortcuts,
|
self.page_shortcuts,
|
||||||
|
self.page_flatpak,
|
||||||
self.page_completion,
|
self.page_completion,
|
||||||
):
|
):
|
||||||
# NOTE: skip config migration page if no legacy config file
|
# NOTE: skip config migration page if no legacy config file
|
||||||
@@ -475,7 +536,10 @@ class Assistant(Gtk.Assistant):
|
|||||||
def write_config(self, config: dict[str, Any]) -> None:
|
def write_config(self, config: dict[str, Any]) -> None:
|
||||||
write_json(config, self.config_path)
|
write_json(config, self.config_path)
|
||||||
|
|
||||||
def get_final_page(self) -> int:
|
def get_completion_page(self) -> int:
|
||||||
|
return self.pages[self.page_completion]
|
||||||
|
|
||||||
|
def get_shortcut_page(self) -> int:
|
||||||
return self.pages[self.page_shortcuts]
|
return self.pages[self.page_shortcuts]
|
||||||
|
|
||||||
def _advance_page(self, index: int) -> int:
|
def _advance_page(self, index: int) -> int:
|
||||||
@@ -488,7 +552,7 @@ class Assistant(Gtk.Assistant):
|
|||||||
sp = page.migrated_conf["default_steam_path"]
|
sp = page.migrated_conf["default_steam_path"]
|
||||||
self.page_shortcuts.set_steam_path(sp)
|
self.page_shortcuts.set_steam_path(sp)
|
||||||
self.config_values = self.page_migration.migrated_conf
|
self.config_values = self.page_migration.migrated_conf
|
||||||
return self.get_final_page()
|
return self.get_shortcut_page()
|
||||||
case SteamPathPage():
|
case SteamPathPage():
|
||||||
path = page.get_path_from_radio()
|
path = page.get_path_from_radio()
|
||||||
self.config_values["default_steam_path"] = path
|
self.config_values["default_steam_path"] = path
|
||||||
@@ -505,10 +569,22 @@ class Assistant(Gtk.Assistant):
|
|||||||
)
|
)
|
||||||
case ShortcutCreationPage():
|
case ShortcutCreationPage():
|
||||||
page.create_shortcuts()
|
page.create_shortcuts()
|
||||||
|
if not self.needs_flatpak_override():
|
||||||
|
return self.get_completion_page()
|
||||||
|
case FlatpakOverridePage():
|
||||||
|
pass
|
||||||
case _:
|
case _:
|
||||||
raise AttributeError("Trying to advance a non-canonical page")
|
raise AttributeError("Trying to advance a non-canonical page")
|
||||||
return index + 1
|
return index + 1
|
||||||
|
|
||||||
|
def needs_flatpak_override(self) -> bool:
|
||||||
|
if (
|
||||||
|
self.page_shortcuts.is_steam_checkbox_active()
|
||||||
|
and FLATPAK_ROOT_PATH in self.config_values["default_steam_path"]
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def is_setup_complete(self) -> bool:
|
def is_setup_complete(self) -> bool:
|
||||||
return self.setup_complete
|
return self.setup_complete
|
||||||
|
|
||||||
@@ -634,6 +710,9 @@ class ShortcutCreationPage(OptionalPageMixin, EnumeratedWizardPage): # type: ig
|
|||||||
def set_steam_path(self, path: Path) -> None:
|
def set_steam_path(self, path: Path) -> None:
|
||||||
self.steam_path = path
|
self.steam_path = path
|
||||||
|
|
||||||
|
def is_steam_checkbox_active(self) -> bool:
|
||||||
|
return self.steam_checkbox.get_active()
|
||||||
|
|
||||||
def create_shortcuts(self) -> None:
|
def create_shortcuts(self) -> None:
|
||||||
# NOTE: best-effort, permissive even on failure (page is already marked as complete)
|
# NOTE: best-effort, permissive even on failure (page is already marked as complete)
|
||||||
if self.steam_checkbox.get_active():
|
if self.steam_checkbox.get_active():
|
||||||
@@ -702,10 +781,10 @@ 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") -> 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)
|
self.win = Window(is_deck, XDG, xdg_paths)
|
||||||
Gtk.main()
|
Gtk.main()
|
||||||
|
|
||||||
def is_setup_complete(self) -> int:
|
def is_setup_complete(self) -> int:
|
||||||
@@ -713,9 +792,9 @@ class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
|||||||
|
|
||||||
|
|
||||||
class Window(Gtk.Window):
|
class Window(Gtk.Window):
|
||||||
def __init__(self, is_deck: bool, XDG: "Xdg") -> 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)
|
self.assistant = Assistant(is_deck, XDG, xdg_paths)
|
||||||
|
|
||||||
|
|
||||||
class Emitter(GObject.GObject):
|
class Emitter(GObject.GObject):
|
||||||
|
|||||||
@@ -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 (
|
||||||
@@ -9,12 +10,11 @@ from dzgui.const.constants import (
|
|||||||
APPID_DAYZ_EXP,
|
APPID_DAYZ_EXP,
|
||||||
APPNAME_DAYZ,
|
APPNAME_DAYZ,
|
||||||
APPNAME_DAYZ_EXP_HUMAN,
|
APPNAME_DAYZ_EXP_HUMAN,
|
||||||
NO_EXPAND,
|
FLATPAK_RUN_CMD,
|
||||||
NO_FILL,
|
FLATPAK_SANDBOX,
|
||||||
NO_PADDING,
|
FLATPAK_STEAM_PATH,
|
||||||
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
|
||||||
@@ -51,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
|
||||||
|
|
||||||
@@ -63,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)
|
||||||
@@ -127,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)
|
||||||
|
|
||||||
@@ -139,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:
|
||||||
@@ -206,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:
|
||||||
@@ -230,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
|
||||||
@@ -246,6 +246,26 @@ class ToggleField(Gtk.Box):
|
|||||||
el.set_sensitive(state)
|
el.set_sensitive(state)
|
||||||
|
|
||||||
|
|
||||||
|
class FlatpakToggleField(ToggleField):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
controller: "Controller",
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
controller,
|
||||||
|
first_option=options.flatpak_standard,
|
||||||
|
second_option=options.flatpak_container,
|
||||||
|
context=Preferences.CLIENT,
|
||||||
|
)
|
||||||
|
self.add(InfoEventBox(options.flatpak_info, self.controller))
|
||||||
|
self.controller = controller
|
||||||
|
self.radio1.connect("toggled", self._on_radio_toggled, Preferences.CLIENT)
|
||||||
|
|
||||||
|
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
|
||||||
|
cmd = FLATPAK_RUN_CMD if self.radio1.get_active() else FLATPAK_SANDBOX
|
||||||
|
self.controller.update_config(context, cmd)
|
||||||
|
|
||||||
|
|
||||||
class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||||
def __init__(self, controller: "Controller"):
|
def __init__(self, controller: "Controller"):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -274,6 +294,7 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
self.player_box.set_halign(Gtk.Align.START)
|
self.player_box.set_halign(Gtk.Align.START)
|
||||||
self.player_box.get_children()[0].set_width_chars(30) # type: ignore
|
self.player_box.get_children()[0].set_width_chars(30) # type: ignore
|
||||||
|
|
||||||
|
self.flatpak_toggle = FlatpakToggleField(self.controller)
|
||||||
self.fullscreen_toggle = ToggleField(
|
self.fullscreen_toggle = ToggleField(
|
||||||
self.controller,
|
self.controller,
|
||||||
strings.options.last_used,
|
strings.options.last_used,
|
||||||
@@ -309,9 +330,17 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
[LeftLabel(options.start_tab), start_tab_hbox],
|
[LeftLabel(options.start_tab), start_tab_hbox],
|
||||||
[LeftLabel(strings.options.name), self.player_box],
|
[LeftLabel(strings.options.name), self.player_box],
|
||||||
]
|
]
|
||||||
|
default = self.controller.query_config(Preferences.DEFAULT)
|
||||||
|
if FLATPAK_STEAM_PATH in default:
|
||||||
|
pref_rows.insert(
|
||||||
|
0,
|
||||||
|
[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],
|
||||||
@@ -373,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,
|
||||||
@@ -406,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)
|
||||||
@@ -437,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,7 +407,7 @@ 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]:
|
||||||
|
|||||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user