Added translation base
This commit is contained in:
58
locales/dynalab.pot
Normal file
58
locales/dynalab.pot
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2026-07-09 11:20+0100\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=CHARSET\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:53
|
||||||
|
msgid "Fetched DynaLab config"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:56
|
||||||
|
msgid "Starting JSON data server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:66
|
||||||
|
msgid "JSON data server started"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:68
|
||||||
|
msgid "DynaLab Ready"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:101
|
||||||
|
msgid "File"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:102
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:103 src/dynalab/ui/windows.py:110
|
||||||
|
msgid "Record"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:105
|
||||||
|
msgid "Configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:107
|
||||||
|
msgid "Window"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:116
|
||||||
|
msgid "Data Input"
|
||||||
|
msgstr ""
|
||||||
BIN
locales/en/LC_MESSAGES/dynalab.mo
Normal file
BIN
locales/en/LC_MESSAGES/dynalab.mo
Normal file
Binary file not shown.
56
locales/en/LC_MESSAGES/dynalab.po
Normal file
56
locales/en/LC_MESSAGES/dynalab.po
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# DynaLab English Translations.
|
||||||
|
# Copyright (C) 2026 Exergie
|
||||||
|
# This file is distributed under the same license as the DynaLab package.
|
||||||
|
# Hector van der Aa <hector@h3cx.dev>, 2026.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: DynaLab\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"PO-Revision-Date: 2026-07-08 23:50+0100\n"
|
||||||
|
"Last-Translator: Hector van der Aa <hector@h3cx.dev>\n"
|
||||||
|
"Language-Team: English\n"
|
||||||
|
"Language: en\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:53
|
||||||
|
msgid "Fetched DynaLab config"
|
||||||
|
msgstr "Fetched DynaLab config"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:56
|
||||||
|
msgid "Starting JSON data server"
|
||||||
|
msgstr "Starting JSON data server"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:66
|
||||||
|
msgid "JSON data server started"
|
||||||
|
msgstr "JSON data server started"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:68
|
||||||
|
msgid "DynaLab Ready"
|
||||||
|
msgstr "DynaLab Ready"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:101
|
||||||
|
msgid "File"
|
||||||
|
msgstr "File"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:102
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Quit"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:103 src/dynalab/ui/windows.py:110
|
||||||
|
msgid "Record"
|
||||||
|
msgstr "Record"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:105
|
||||||
|
msgid "Configuration"
|
||||||
|
msgstr "Configuration"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:107
|
||||||
|
msgid "Window"
|
||||||
|
msgstr "Window"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:116
|
||||||
|
msgid "Data Input"
|
||||||
|
msgstr "Data Input"
|
||||||
BIN
locales/fr/LC_MESSAGES/dynalab.mo
Normal file
BIN
locales/fr/LC_MESSAGES/dynalab.mo
Normal file
Binary file not shown.
56
locales/fr/LC_MESSAGES/dynalab.po
Normal file
56
locales/fr/LC_MESSAGES/dynalab.po
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# DynaLab French Translations.
|
||||||
|
# Copyright (C) 2026 Exergie
|
||||||
|
# This file is distributed under the same license as the DynaLab package.
|
||||||
|
# Hector van der Aa <hector@h3cx.dev>, 2026.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: DynaLab\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"PO-Revision-Date: 2026-07-08 23:50+0100\n"
|
||||||
|
"Last-Translator: Hector van der Aa <hector@h3cx.dev>\n"
|
||||||
|
"Language-Team: French\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:53
|
||||||
|
msgid "Fetched DynaLab config"
|
||||||
|
msgstr "Configuration DynaLab récupérée"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:56
|
||||||
|
msgid "Starting JSON data server"
|
||||||
|
msgstr "Démarrage du serveur de données JSON"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:66
|
||||||
|
msgid "JSON data server started"
|
||||||
|
msgstr "Serveur de données JSON démarré"
|
||||||
|
|
||||||
|
#: src/dynalab/app.py:68
|
||||||
|
msgid "DynaLab Ready"
|
||||||
|
msgstr "DynaLab prêt"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:101
|
||||||
|
msgid "File"
|
||||||
|
msgstr "Fichier"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:102
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Quitter"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:103 src/dynalab/ui/windows.py:110
|
||||||
|
msgid "Record"
|
||||||
|
msgstr "Enregistrer"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:105
|
||||||
|
msgid "Configuration"
|
||||||
|
msgstr "Configuration"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:107
|
||||||
|
msgid "Window"
|
||||||
|
msgstr "Fenêtre"
|
||||||
|
|
||||||
|
#: src/dynalab/ui/windows.py:116
|
||||||
|
msgid "Data Input"
|
||||||
|
msgstr "Saisie des données"
|
||||||
29
scripts/i18n_compile.sh
Executable file
29
scripts/i18n_compile.sh
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DOMAIN="dynalab"
|
||||||
|
LOCALES_DIR="locales"
|
||||||
|
|
||||||
|
found_po=false
|
||||||
|
|
||||||
|
for po_file in "$LOCALES_DIR"/*/LC_MESSAGES/"$DOMAIN.po"; do
|
||||||
|
if [[ ! -f "$po_file" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
found_po=true
|
||||||
|
mo_file="${po_file%.po}.mo"
|
||||||
|
|
||||||
|
echo "Checking $po_file"
|
||||||
|
msgfmt --check "$po_file"
|
||||||
|
|
||||||
|
echo "Compiling $po_file -> $mo_file"
|
||||||
|
msgfmt "$po_file" -o "$mo_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$found_po" == false ]]; then
|
||||||
|
echo "No .po files found in $LOCALES_DIR/*/LC_MESSAGES/$DOMAIN.po"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Compiled all locales"
|
||||||
19
scripts/i18n_extract.sh
Executable file
19
scripts/i18n_extract.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DOMAIN="dynalab"
|
||||||
|
SOURCE_DIR="src/dynalab"
|
||||||
|
LOCALES_DIR="locales"
|
||||||
|
POT_FILE="$LOCALES_DIR/$DOMAIN.pot"
|
||||||
|
|
||||||
|
mkdir -p "$LOCALES_DIR"
|
||||||
|
|
||||||
|
xgettext \
|
||||||
|
--language=Python \
|
||||||
|
--keyword=_ \
|
||||||
|
--keyword=gettext_ \
|
||||||
|
--from-code=UTF-8 \
|
||||||
|
--output="$POT_FILE" \
|
||||||
|
$(find "$SOURCE_DIR" -name "*.py" | sort)
|
||||||
|
|
||||||
|
echo "Generated $POT_FILE"
|
||||||
@@ -13,6 +13,7 @@ import sys
|
|||||||
|
|
||||||
from dynalab.config import ConfigLoadError, load_config
|
from dynalab.config import ConfigLoadError, load_config
|
||||||
from dynalab.data.json import json_thread_main
|
from dynalab.data.json import json_thread_main
|
||||||
|
from dynalab.i18n import init_i18n, _
|
||||||
from dynalab.state import AppState
|
from dynalab.state import AppState
|
||||||
from dynalab.tags import WINDOW_DATA_INPUT_GAUGE_GRID
|
from dynalab.tags import WINDOW_DATA_INPUT_GAUGE_GRID
|
||||||
from dynalab.ui.error import build_error_modal, show_error_modal
|
from dynalab.ui.error import build_error_modal, show_error_modal
|
||||||
@@ -28,6 +29,8 @@ from dynalab.ui.windows import (
|
|||||||
import dpg_gauges as dpgg
|
import dpg_gauges as dpgg
|
||||||
from dpg_gauges.exceptions import GaugeNotFoundError
|
from dpg_gauges.exceptions import GaugeNotFoundError
|
||||||
|
|
||||||
|
from dynalab.utils import resource_path
|
||||||
|
|
||||||
|
|
||||||
def _setup_logging(level: int = logging.INFO) -> None:
|
def _setup_logging(level: int = logging.INFO) -> None:
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -38,29 +41,6 @@ def _setup_logging(level: int = logging.INFO) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _asset_path(relative_path: str) -> str:
|
|
||||||
path = Path(relative_path)
|
|
||||||
candidates: list[Path] = []
|
|
||||||
|
|
||||||
bundle_dir = getattr(sys, "_MEIPASS", None)
|
|
||||||
if bundle_dir is not None:
|
|
||||||
candidates.append(Path(bundle_dir) / path)
|
|
||||||
|
|
||||||
candidates.extend(
|
|
||||||
(
|
|
||||||
Path.cwd() / path,
|
|
||||||
Path(__file__).resolve().parents[2] / path,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for candidate in candidates:
|
|
||||||
if candidate.exists():
|
|
||||||
return str(candidate)
|
|
||||||
|
|
||||||
searched = ", ".join(str(candidate) for candidate in candidates)
|
|
||||||
raise FileNotFoundError(f"Missing asset {relative_path!r}. Searched: {searched}")
|
|
||||||
|
|
||||||
|
|
||||||
def _init_worker(
|
def _init_worker(
|
||||||
state: AppState, stop_event: threading.Event, message_queue: Queue[str]
|
state: AppState, stop_event: threading.Event, message_queue: Queue[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -69,10 +49,11 @@ def _init_worker(
|
|||||||
message_queue.put("Fetching DynaLab config")
|
message_queue.put("Fetching DynaLab config")
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
state.app_config = load_config()
|
state.app_config = load_config()
|
||||||
message_queue.put("Fetched DynaLab config")
|
state.language = init_i18n(state.app_config.display.language)
|
||||||
|
message_queue.put(_("Fetched DynaLab config"))
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
message_queue.put("Starting JSON data server")
|
message_queue.put(_("Starting JSON data server"))
|
||||||
# Init and start json connector thread
|
# Init and start json connector thread
|
||||||
state.data_json_thread = Thread(
|
state.data_json_thread = Thread(
|
||||||
target=json_thread_main,
|
target=json_thread_main,
|
||||||
@@ -82,9 +63,9 @@ def _init_worker(
|
|||||||
)
|
)
|
||||||
state.data_json_thread.start()
|
state.data_json_thread.start()
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
message_queue.put("JSON data server started")
|
message_queue.put(_("JSON data server started"))
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
message_queue.put("DynaLab Ready")
|
message_queue.put(_("DynaLab Ready"))
|
||||||
time.sleep(1.0)
|
time.sleep(1.0)
|
||||||
|
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
@@ -130,16 +111,16 @@ def run() -> None:
|
|||||||
# Load fonts at 2x size for HiDPI scaling
|
# Load fonts at 2x size for HiDPI scaling
|
||||||
with dpg.font_registry():
|
with dpg.font_registry():
|
||||||
app_font = dpg.add_font(
|
app_font = dpg.add_font(
|
||||||
_asset_path("assets/fonts/Inter-Regular.ttf"), 18 * 2
|
resource_path("assets/fonts/Inter-Regular.ttf"), 18 * 2
|
||||||
)
|
)
|
||||||
mono_font = dpg.add_font(
|
mono_font = dpg.add_font(
|
||||||
_asset_path("assets/fonts/JetBrainsMono-Regular.ttf"),
|
resource_path("assets/fonts/JetBrainsMono-Regular.ttf"),
|
||||||
size=36,
|
size=36,
|
||||||
label="mono_font",
|
label="mono_font",
|
||||||
)
|
)
|
||||||
dpg.bind_font(app_font)
|
dpg.bind_font(app_font)
|
||||||
|
|
||||||
_load_image(_asset_path("assets/images/logo@300.png"), "logo")
|
_load_image(resource_path("assets/images/logo@300.png"), "logo")
|
||||||
|
|
||||||
build_loading()
|
build_loading()
|
||||||
build_error_modal()
|
build_error_modal()
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ class JsonConnectorConfig(BaseModel):
|
|||||||
port: int = Field(default=8765, ge=1, le=65535)
|
port: int = Field(default=8765, ge=1, le=65535)
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayConfig(BaseModel):
|
||||||
|
language: str = ""
|
||||||
|
|
||||||
|
|
||||||
class AppConfig(BaseModel):
|
class AppConfig(BaseModel):
|
||||||
|
display: DisplayConfig = DisplayConfig()
|
||||||
json_connector: JsonConnectorConfig = JsonConnectorConfig()
|
json_connector: JsonConnectorConfig = JsonConnectorConfig()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ async def _handle_connector(
|
|||||||
|
|
||||||
for signal in connector_hello.signals:
|
for signal in connector_hello.signals:
|
||||||
state.new_signal_queue.put(signal)
|
state.new_signal_queue.put(signal)
|
||||||
|
state.available_signals.append(signal)
|
||||||
|
|
||||||
heartbeat_task = asyncio.create_task(_heartbeat_loop(writer))
|
heartbeat_task = asyncio.create_task(_heartbeat_loop(writer))
|
||||||
timeout_event = asyncio.Event()
|
timeout_event = asyncio.Event()
|
||||||
|
|||||||
57
src/dynalab/i18n.py
Normal file
57
src/dynalab/i18n.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import gettext
|
||||||
|
import locale
|
||||||
|
|
||||||
|
from dynalab.utils import resource_path
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN = "dynalab"
|
||||||
|
SUPPORTED_LANGUAGES = {"en", "fr"}
|
||||||
|
|
||||||
|
_translation: gettext.NullTranslations = gettext.NullTranslations()
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_language(language: str | None) -> str:
|
||||||
|
if not language:
|
||||||
|
return "en"
|
||||||
|
|
||||||
|
language = language.split(".")[0]
|
||||||
|
language = language.replace("-", "_")
|
||||||
|
language = language.split("_")[0]
|
||||||
|
|
||||||
|
if language in SUPPORTED_LANGUAGES:
|
||||||
|
return language
|
||||||
|
|
||||||
|
return "en"
|
||||||
|
|
||||||
|
|
||||||
|
def detect_system_language() -> str:
|
||||||
|
language, _encoding = locale.getlocale()
|
||||||
|
|
||||||
|
if not language:
|
||||||
|
language, _encoding = locale.getdefaultlocale()
|
||||||
|
|
||||||
|
return _normalize_language(language)
|
||||||
|
|
||||||
|
|
||||||
|
def init_i18n(config_language: str | None) -> str:
|
||||||
|
global _translation
|
||||||
|
|
||||||
|
if config_language:
|
||||||
|
language = _normalize_language(config_language)
|
||||||
|
else:
|
||||||
|
language = detect_system_language()
|
||||||
|
|
||||||
|
locale_dir = resource_path("locales")
|
||||||
|
|
||||||
|
_translation = gettext.translation(
|
||||||
|
DOMAIN, localedir=locale_dir, languages=[language], fallback=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return language
|
||||||
|
|
||||||
|
|
||||||
|
def gettext_(message: str) -> str:
|
||||||
|
return _translation.gettext(message)
|
||||||
|
|
||||||
|
|
||||||
|
_ = gettext_
|
||||||
@@ -7,6 +7,7 @@ import threading
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from dynalab_protocol.models import SignalDescriptor
|
from dynalab_protocol.models import SignalDescriptor
|
||||||
|
from dynalab_protocol.dlconf import DLConf
|
||||||
|
|
||||||
from dynalab.config import AppConfig
|
from dynalab.config import AppConfig
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ class AppState:
|
|||||||
new_signal_queue: Queue[SignalDescriptor] = Queue()
|
new_signal_queue: Queue[SignalDescriptor] = Queue()
|
||||||
removed_signal_queue: Queue[SignalDescriptor] = Queue()
|
removed_signal_queue: Queue[SignalDescriptor] = Queue()
|
||||||
|
|
||||||
|
available_signals: list[SignalDescriptor] = field(default_factory=list)
|
||||||
live_data: dict[UUID, float] = field(default_factory=dict)
|
live_data: dict[UUID, float] = field(default_factory=dict)
|
||||||
new_data_id: Queue[UUID] = Queue()
|
new_data_id: Queue[UUID] = Queue()
|
||||||
|
|
||||||
@@ -28,3 +30,8 @@ class AppState:
|
|||||||
init_error: str | None = None
|
init_error: str | None = None
|
||||||
|
|
||||||
window_map: dict[str, str] = field(default_factory=dict)
|
window_map: dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
|
language: str = ""
|
||||||
|
|
||||||
|
loaded_dlconf: DLConf | None = None
|
||||||
|
building_dlconf: DLConf | None = None
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
WINDOW_RECORD_CONFIGURATION: str = "window_record_configuration"
|
||||||
|
WINDOW_RECORD_CONFIGURATION_ADD_SIGNAL: str = "window_record_configuration_add_signal"
|
||||||
|
|
||||||
# Window groups
|
# Window groups
|
||||||
WINDOW_GROUP_DATA_INPUT: str = "window_group_data_input"
|
WINDOW_GROUP_DATA_INPUT: str = "window_group_data_input"
|
||||||
WINDOW_GROUP_RECORD: str = "window_group_record"
|
WINDOW_GROUP_RECORD: str = "window_group_record"
|
||||||
@@ -13,3 +16,8 @@ MENU_FILE_QUIT: str = "menu_file_quit"
|
|||||||
MENU_RECORD_CONFIGURATION: str = "menu_record_configuration"
|
MENU_RECORD_CONFIGURATION: str = "menu_record_configuration"
|
||||||
MENU_WINDOW_RECORD: str = "menu_window_record"
|
MENU_WINDOW_RECORD: str = "menu_window_record"
|
||||||
MENU_WINDOW_DATA_INPUT: str = "menu_window_data_input"
|
MENU_WINDOW_DATA_INPUT: str = "menu_window_data_input"
|
||||||
|
|
||||||
|
LISTBOX_RECORD_CONFIGURATION: str = "listbox_record_configuration"
|
||||||
|
LISTBOX_RECORD_CONFIGURATION_ADD_SINGAL: str = "listbox_record_configuration_add_singal"
|
||||||
|
|
||||||
|
TEXT_INPUT_RECORD_CONFIGURATION: str = "text_input_record_configuration"
|
||||||
|
|||||||
30
src/dynalab/ui/render.py
Normal file
30
src/dynalab/ui/render.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import dearpygui.dearpygui as dpg
|
||||||
|
|
||||||
|
from dynalab.state import AppState
|
||||||
|
from dynalab.tags import (
|
||||||
|
LISTBOX_RECORD_CONFIGURATION,
|
||||||
|
LISTBOX_RECORD_CONFIGURATION_ADD_SINGAL,
|
||||||
|
TEXT_INPUT_RECORD_CONFIGURATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def render_window_record_configuration(state: AppState) -> None:
|
||||||
|
dpg.set_value(
|
||||||
|
item=TEXT_INPUT_RECORD_CONFIGURATION, value=state.building_dlconf.name
|
||||||
|
)
|
||||||
|
|
||||||
|
items: list[str] = []
|
||||||
|
for signal in state.building_dlconf.signals:
|
||||||
|
name: str = f"{signal.name} | {signal.id}"
|
||||||
|
items.append(name)
|
||||||
|
|
||||||
|
dpg.configure_item(LISTBOX_RECORD_CONFIGURATION, items=items)
|
||||||
|
|
||||||
|
|
||||||
|
def render_listbox_record_configuration_add_item(state: AppState) -> None:
|
||||||
|
items: list[str] = []
|
||||||
|
for item in state.available_signals:
|
||||||
|
name: str = f"{item.name} | {item.id}"
|
||||||
|
items.append(name)
|
||||||
|
|
||||||
|
dpg.configure_item(LISTBOX_RECORD_CONFIGURATION_ADD_SINGAL, items=items)
|
||||||
@@ -1,7 +1,22 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from dynalab_protocol.dlconf import DLConf
|
||||||
from dynalab.state import AppState
|
from dynalab.state import AppState
|
||||||
|
|
||||||
import dearpygui.dearpygui as dpg
|
import dearpygui.dearpygui as dpg
|
||||||
|
|
||||||
|
from dynalab.tags import (
|
||||||
|
LISTBOX_RECORD_CONFIGURATION,
|
||||||
|
LISTBOX_RECORD_CONFIGURATION_ADD_SINGAL,
|
||||||
|
TEXT_INPUT_RECORD_CONFIGURATION,
|
||||||
|
WINDOW_RECORD_CONFIGURATION,
|
||||||
|
WINDOW_RECORD_CONFIGURATION_ADD_SIGNAL,
|
||||||
|
)
|
||||||
|
from dynalab.ui.render import (
|
||||||
|
render_window_record_configuration,
|
||||||
|
render_listbox_record_configuration_add_item,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def switch_window(sender, app_data, user_data: AppState) -> None:
|
def switch_window(sender, app_data, user_data: AppState) -> None:
|
||||||
for button_tag, window_group_tag in user_data.window_map.items():
|
for button_tag, window_group_tag in user_data.window_map.items():
|
||||||
@@ -9,3 +24,54 @@ def switch_window(sender, app_data, user_data: AppState) -> None:
|
|||||||
dpg.show_item(window_group_tag)
|
dpg.show_item(window_group_tag)
|
||||||
else:
|
else:
|
||||||
dpg.hide_item(window_group_tag)
|
dpg.hide_item(window_group_tag)
|
||||||
|
|
||||||
|
|
||||||
|
def listbox_record_configuration_remove_selected_item(
|
||||||
|
sender, app_data, user_data: AppState
|
||||||
|
) -> None:
|
||||||
|
selected = dpg.get_value(LISTBOX_RECORD_CONFIGURATION)
|
||||||
|
if selected is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
split = selected.split(" | ")[1]
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
signal_id = uuid.UUID(split)
|
||||||
|
render_window_record_configuration(user_data)
|
||||||
|
|
||||||
|
|
||||||
|
def listbox_record_configuration_add_item(
|
||||||
|
sender, app_data, user_data: AppState
|
||||||
|
) -> None:
|
||||||
|
render_listbox_record_configuration_add_item(user_data)
|
||||||
|
dpg.show_item(WINDOW_RECORD_CONFIGURATION_ADD_SIGNAL)
|
||||||
|
|
||||||
|
|
||||||
|
def listbox_record_configuration_add_item_select(
|
||||||
|
sender, app_data, user_data: AppState
|
||||||
|
) -> None:
|
||||||
|
value: str = dpg.get_value(LISTBOX_RECORD_CONFIGURATION_ADD_SINGAL)
|
||||||
|
try:
|
||||||
|
split = value.split(" | ")[1]
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
signal_id = uuid.UUID(split)
|
||||||
|
if user_data.building_dlconf is None:
|
||||||
|
user_data.building_dlconf = DLConf()
|
||||||
|
|
||||||
|
signal = next(
|
||||||
|
(signal for signal in user_data.available_signals if signal.id == signal_id),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if signal is not None:
|
||||||
|
user_data.building_dlconf.signals.append(signal)
|
||||||
|
render_window_record_configuration(user_data)
|
||||||
|
dpg.hide_item(WINDOW_RECORD_CONFIGURATION_ADD_SIGNAL)
|
||||||
|
|
||||||
|
|
||||||
|
def show_window_record_configuration(sender, app_data, user_data: AppState) -> None:
|
||||||
|
if user_data.building_dlconf is None:
|
||||||
|
user_data.building_dlconf = DLConf()
|
||||||
|
render_window_record_configuration(user_data)
|
||||||
|
dpg.show_item(WINDOW_RECORD_CONFIGURATION)
|
||||||
|
|||||||
@@ -7,15 +7,27 @@ import dpg_gauges as dpgg
|
|||||||
|
|
||||||
from dynalab.state import AppState
|
from dynalab.state import AppState
|
||||||
from dynalab.tags import (
|
from dynalab.tags import (
|
||||||
|
LISTBOX_RECORD_CONFIGURATION,
|
||||||
|
LISTBOX_RECORD_CONFIGURATION_ADD_SINGAL,
|
||||||
MENU_FILE_QUIT,
|
MENU_FILE_QUIT,
|
||||||
MENU_RECORD_CONFIGURATION,
|
MENU_RECORD_CONFIGURATION,
|
||||||
MENU_WINDOW_DATA_INPUT,
|
MENU_WINDOW_DATA_INPUT,
|
||||||
MENU_WINDOW_RECORD,
|
MENU_WINDOW_RECORD,
|
||||||
|
TEXT_INPUT_RECORD_CONFIGURATION,
|
||||||
WINDOW_DATA_INPUT_GAUGE_GRID,
|
WINDOW_DATA_INPUT_GAUGE_GRID,
|
||||||
WINDOW_GROUP_DATA_INPUT,
|
WINDOW_GROUP_DATA_INPUT,
|
||||||
WINDOW_GROUP_RECORD,
|
WINDOW_GROUP_RECORD,
|
||||||
|
WINDOW_RECORD_CONFIGURATION,
|
||||||
|
WINDOW_RECORD_CONFIGURATION_ADD_SIGNAL,
|
||||||
)
|
)
|
||||||
from dynalab.ui.routines import switch_window
|
from dynalab.ui.routines import (
|
||||||
|
listbox_record_configuration_add_item,
|
||||||
|
listbox_record_configuration_add_item_select,
|
||||||
|
listbox_record_configuration_remove_selected_item,
|
||||||
|
show_window_record_configuration,
|
||||||
|
switch_window,
|
||||||
|
)
|
||||||
|
from dynalab.i18n import _
|
||||||
|
|
||||||
|
|
||||||
LOADING_WINDOW_WIDTH = 600
|
LOADING_WINDOW_WIDTH = 600
|
||||||
@@ -94,23 +106,85 @@ def center_loading_content() -> None:
|
|||||||
dpg.set_item_width("loading_text_left_spacer", text_spacer_width)
|
dpg.set_item_width("loading_text_left_spacer", text_spacer_width)
|
||||||
|
|
||||||
|
|
||||||
|
def build_record_configuration_add_signal_window(state: AppState) -> None:
|
||||||
|
with dpg.window(
|
||||||
|
label="Add Signal",
|
||||||
|
tag=WINDOW_RECORD_CONFIGURATION_ADD_SIGNAL,
|
||||||
|
no_collapse=True,
|
||||||
|
show=False,
|
||||||
|
min_size=(500, 300),
|
||||||
|
modal=True,
|
||||||
|
):
|
||||||
|
dpg.add_listbox(
|
||||||
|
tag=LISTBOX_RECORD_CONFIGURATION_ADD_SINGAL,
|
||||||
|
num_items=10,
|
||||||
|
width=480,
|
||||||
|
)
|
||||||
|
with dpg.group(horizontal=True):
|
||||||
|
dpg.add_button(
|
||||||
|
label="Select",
|
||||||
|
callback=listbox_record_configuration_add_item_select,
|
||||||
|
user_data=state,
|
||||||
|
)
|
||||||
|
dpg.add_button(
|
||||||
|
label="Refresh",
|
||||||
|
callback=listbox_record_configuration_add_item,
|
||||||
|
user_data=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_record_configuration_window(state: AppState) -> None:
|
||||||
|
with dpg.window(
|
||||||
|
label="Recording Configuration",
|
||||||
|
tag=WINDOW_RECORD_CONFIGURATION,
|
||||||
|
no_collapse=True,
|
||||||
|
show=False,
|
||||||
|
min_size=(500, 300),
|
||||||
|
):
|
||||||
|
with dpg.group():
|
||||||
|
dpg.add_text("Configuration Name")
|
||||||
|
dpg.add_input_text(tag=TEXT_INPUT_RECORD_CONFIGURATION)
|
||||||
|
dpg.add_text("Selected Signals")
|
||||||
|
dpg.add_listbox(
|
||||||
|
tag=LISTBOX_RECORD_CONFIGURATION,
|
||||||
|
num_items=10,
|
||||||
|
width=480,
|
||||||
|
)
|
||||||
|
with dpg.group(horizontal=True):
|
||||||
|
dpg.add_button(
|
||||||
|
label="Add",
|
||||||
|
callback=listbox_record_configuration_add_item,
|
||||||
|
user_data=state,
|
||||||
|
)
|
||||||
|
dpg.add_button(
|
||||||
|
label="Remove",
|
||||||
|
callback=listbox_record_configuration_remove_selected_item,
|
||||||
|
user_data=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_windows(state: AppState) -> None:
|
def build_windows(state: AppState) -> None:
|
||||||
with dpg.window(label="DynaLab", tag="main_window", no_collapse=True):
|
with dpg.window(label="DynaLab", tag="main_window", no_collapse=True):
|
||||||
with dpg.menu_bar():
|
with dpg.menu_bar():
|
||||||
with dpg.menu(label="File"):
|
with dpg.menu(label=_("File")):
|
||||||
dpg.add_menu_item(tag=MENU_FILE_QUIT, label="Quit", enabled=True)
|
dpg.add_menu_item(tag=MENU_FILE_QUIT, label=_("Quit"), enabled=True)
|
||||||
with dpg.menu(label="Record"):
|
with dpg.menu(label=_("Record")):
|
||||||
dpg.add_menu_item(tag=MENU_RECORD_CONFIGURATION, label="Configuration")
|
dpg.add_menu_item(
|
||||||
with dpg.menu(label="Window"):
|
tag=MENU_RECORD_CONFIGURATION,
|
||||||
|
label=_("Configuration"),
|
||||||
|
callback=show_window_record_configuration,
|
||||||
|
user_data=state,
|
||||||
|
)
|
||||||
|
with dpg.menu(label=_("Window")):
|
||||||
dpg.add_menu_item(
|
dpg.add_menu_item(
|
||||||
tag=MENU_WINDOW_RECORD,
|
tag=MENU_WINDOW_RECORD,
|
||||||
label="Record",
|
label=_("Record"),
|
||||||
callback=switch_window,
|
callback=switch_window,
|
||||||
user_data=state,
|
user_data=state,
|
||||||
)
|
)
|
||||||
dpg.add_menu_item(
|
dpg.add_menu_item(
|
||||||
tag=MENU_WINDOW_DATA_INPUT,
|
tag=MENU_WINDOW_DATA_INPUT,
|
||||||
label="Data Input",
|
label=_("Data Input"),
|
||||||
callback=switch_window,
|
callback=switch_window,
|
||||||
user_data=state,
|
user_data=state,
|
||||||
)
|
)
|
||||||
@@ -121,9 +195,7 @@ def build_windows(state: AppState) -> None:
|
|||||||
# with dpg.group(tag=MENU_WINDOW_DATA_INPUT):
|
# with dpg.group(tag=MENU_WINDOW_DATA_INPUT):
|
||||||
with dpg.group(tag=WINDOW_GROUP_DATA_INPUT):
|
with dpg.group(tag=WINDOW_GROUP_DATA_INPUT):
|
||||||
state.window_map[MENU_WINDOW_DATA_INPUT] = WINDOW_GROUP_DATA_INPUT
|
state.window_map[MENU_WINDOW_DATA_INPUT] = WINDOW_GROUP_DATA_INPUT
|
||||||
with dpgg.gauge_panel(
|
with dpgg.gauge_panel(autosize_x=True, autosize_y=True):
|
||||||
label="Autobreak narrow panel", autosize_x=True, autosize_y=True
|
|
||||||
):
|
|
||||||
with dpgg.gauge_grid(
|
with dpgg.gauge_grid(
|
||||||
columns="auto",
|
columns="auto",
|
||||||
min_column_width=180,
|
min_column_width=180,
|
||||||
@@ -134,3 +206,6 @@ def build_windows(state: AppState) -> None:
|
|||||||
with dpg.group(tag=WINDOW_GROUP_RECORD, show=False):
|
with dpg.group(tag=WINDOW_GROUP_RECORD, show=False):
|
||||||
state.window_map[MENU_WINDOW_RECORD] = WINDOW_GROUP_RECORD
|
state.window_map[MENU_WINDOW_RECORD] = WINDOW_GROUP_RECORD
|
||||||
dpg.add_text("Some text")
|
dpg.add_text("Some text")
|
||||||
|
|
||||||
|
build_record_configuration_window(state)
|
||||||
|
build_record_configuration_add_signal_window(state)
|
||||||
|
|||||||
28
src/dynalab/utils.py
Normal file
28
src/dynalab/utils.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def resource_path(relative_path: str) -> str:
|
||||||
|
path = Path(relative_path)
|
||||||
|
candidates: list[Path] = []
|
||||||
|
|
||||||
|
bundle_dir = getattr(sys, "_MEIPASS", None)
|
||||||
|
if bundle_dir is not None:
|
||||||
|
candidates.append(Path(bundle_dir) / path)
|
||||||
|
|
||||||
|
candidates.extend(
|
||||||
|
(
|
||||||
|
Path.cwd() / path,
|
||||||
|
Path(__file__).resolve().parents[2] / path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for candidate in candidates:
|
||||||
|
if candidate.exists():
|
||||||
|
return str(candidate)
|
||||||
|
|
||||||
|
searched = ", ".join(str(candidate) for candidate in candidates)
|
||||||
|
raise FileNotFoundError(f"Missing asset {relative_path!r}. Searched: {searched}")
|
||||||
Reference in New Issue
Block a user