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.data.json import json_thread_main
|
||||
from dynalab.i18n import init_i18n, _
|
||||
from dynalab.state import AppState
|
||||
from dynalab.tags import WINDOW_DATA_INPUT_GAUGE_GRID
|
||||
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
|
||||
from dpg_gauges.exceptions import GaugeNotFoundError
|
||||
|
||||
from dynalab.utils import resource_path
|
||||
|
||||
|
||||
def _setup_logging(level: int = logging.INFO) -> None:
|
||||
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(
|
||||
state: AppState, stop_event: threading.Event, message_queue: Queue[str]
|
||||
) -> None:
|
||||
@@ -69,10 +49,11 @@ def _init_worker(
|
||||
message_queue.put("Fetching DynaLab config")
|
||||
time.sleep(0.5)
|
||||
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)
|
||||
|
||||
message_queue.put("Starting JSON data server")
|
||||
message_queue.put(_("Starting JSON data server"))
|
||||
# Init and start json connector thread
|
||||
state.data_json_thread = Thread(
|
||||
target=json_thread_main,
|
||||
@@ -82,9 +63,9 @@ def _init_worker(
|
||||
)
|
||||
state.data_json_thread.start()
|
||||
time.sleep(0.5)
|
||||
message_queue.put("JSON data server started")
|
||||
message_queue.put(_("JSON data server started"))
|
||||
time.sleep(0.5)
|
||||
message_queue.put("DynaLab Ready")
|
||||
message_queue.put(_("DynaLab Ready"))
|
||||
time.sleep(1.0)
|
||||
|
||||
stop_event.set()
|
||||
@@ -130,16 +111,16 @@ def run() -> None:
|
||||
# Load fonts at 2x size for HiDPI scaling
|
||||
with dpg.font_registry():
|
||||
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(
|
||||
_asset_path("assets/fonts/JetBrainsMono-Regular.ttf"),
|
||||
resource_path("assets/fonts/JetBrainsMono-Regular.ttf"),
|
||||
size=36,
|
||||
label="mono_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_error_modal()
|
||||
|
||||
@@ -13,7 +13,12 @@ class JsonConnectorConfig(BaseModel):
|
||||
port: int = Field(default=8765, ge=1, le=65535)
|
||||
|
||||
|
||||
class DisplayConfig(BaseModel):
|
||||
language: str = ""
|
||||
|
||||
|
||||
class AppConfig(BaseModel):
|
||||
display: DisplayConfig = DisplayConfig()
|
||||
json_connector: JsonConnectorConfig = JsonConnectorConfig()
|
||||
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ async def _handle_connector(
|
||||
|
||||
for signal in connector_hello.signals:
|
||||
state.new_signal_queue.put(signal)
|
||||
state.available_signals.append(signal)
|
||||
|
||||
heartbeat_task = asyncio.create_task(_heartbeat_loop(writer))
|
||||
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 dynalab_protocol.models import SignalDescriptor
|
||||
from dynalab_protocol.dlconf import DLConf
|
||||
|
||||
from dynalab.config import AppConfig
|
||||
|
||||
@@ -19,6 +20,7 @@ class AppState:
|
||||
new_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)
|
||||
new_data_id: Queue[UUID] = Queue()
|
||||
|
||||
@@ -28,3 +30,8 @@ class AppState:
|
||||
init_error: str | None = None
|
||||
|
||||
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>
|
||||
# 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_GROUP_DATA_INPUT: str = "window_group_data_input"
|
||||
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_WINDOW_RECORD: str = "menu_window_record"
|
||||
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
|
||||
|
||||
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:
|
||||
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)
|
||||
else:
|
||||
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.tags import (
|
||||
LISTBOX_RECORD_CONFIGURATION,
|
||||
LISTBOX_RECORD_CONFIGURATION_ADD_SINGAL,
|
||||
MENU_FILE_QUIT,
|
||||
MENU_RECORD_CONFIGURATION,
|
||||
MENU_WINDOW_DATA_INPUT,
|
||||
MENU_WINDOW_RECORD,
|
||||
TEXT_INPUT_RECORD_CONFIGURATION,
|
||||
WINDOW_DATA_INPUT_GAUGE_GRID,
|
||||
WINDOW_GROUP_DATA_INPUT,
|
||||
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
|
||||
@@ -94,23 +106,85 @@ def center_loading_content() -> None:
|
||||
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:
|
||||
with dpg.window(label="DynaLab", tag="main_window", no_collapse=True):
|
||||
with dpg.menu_bar():
|
||||
with dpg.menu(label="File"):
|
||||
dpg.add_menu_item(tag=MENU_FILE_QUIT, label="Quit", enabled=True)
|
||||
with dpg.menu(label="Record"):
|
||||
dpg.add_menu_item(tag=MENU_RECORD_CONFIGURATION, label="Configuration")
|
||||
with dpg.menu(label="Window"):
|
||||
with dpg.menu(label=_("File")):
|
||||
dpg.add_menu_item(tag=MENU_FILE_QUIT, label=_("Quit"), enabled=True)
|
||||
with dpg.menu(label=_("Record")):
|
||||
dpg.add_menu_item(
|
||||
tag=MENU_RECORD_CONFIGURATION,
|
||||
label=_("Configuration"),
|
||||
callback=show_window_record_configuration,
|
||||
user_data=state,
|
||||
)
|
||||
with dpg.menu(label=_("Window")):
|
||||
dpg.add_menu_item(
|
||||
tag=MENU_WINDOW_RECORD,
|
||||
label="Record",
|
||||
label=_("Record"),
|
||||
callback=switch_window,
|
||||
user_data=state,
|
||||
)
|
||||
dpg.add_menu_item(
|
||||
tag=MENU_WINDOW_DATA_INPUT,
|
||||
label="Data Input",
|
||||
label=_("Data Input"),
|
||||
callback=switch_window,
|
||||
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=WINDOW_GROUP_DATA_INPUT):
|
||||
state.window_map[MENU_WINDOW_DATA_INPUT] = WINDOW_GROUP_DATA_INPUT
|
||||
with dpgg.gauge_panel(
|
||||
label="Autobreak narrow panel", autosize_x=True, autosize_y=True
|
||||
):
|
||||
with dpgg.gauge_panel(autosize_x=True, autosize_y=True):
|
||||
with dpgg.gauge_grid(
|
||||
columns="auto",
|
||||
min_column_width=180,
|
||||
@@ -134,3 +206,6 @@ def build_windows(state: AppState) -> None:
|
||||
with dpg.group(tag=WINDOW_GROUP_RECORD, show=False):
|
||||
state.window_map[MENU_WINDOW_RECORD] = WINDOW_GROUP_RECORD
|
||||
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