From b0c7cb653f61bb5ec8476a4be914601122836d41 Mon Sep 17 00:00:00 2001 From: Mamat79 Date: Wed, 12 Aug 2026 17:00:40 +0200 Subject: [PATCH] Release Controller Studio V.2026.5 redesign --- CHANGELOG.md | 7 + README.md | 36 +- README_EN.md | 18 +- docs/CONTROLLER_PROFILE_SOURCES.md | 2 +- .../windows/SiLeMIO-Controller-Studio.iss | 4 +- packaging/windows/build_setup.ps1 | 2 +- packaging/windows/install.ps1 | 2 +- pyproject.toml | 2 +- scripts/generate_manuals.py | 22 +- src/silemio_control_hub/__init__.py | 2 +- src/silemio_control_hub/desktop.py | 566 ++++++++++++------ src/silemio_control_hub/library_remote.py | 2 +- ...ler-Studio-for-LiveProfessor-Manual-EN.pdf | Bin 1067660 -> 1067964 bytes ...ler-Studio-for-LiveProfessor-Notice-FR.pdf | Bin 1068554 -> 1068841 bytes tests/test_product_identity.py | 2 +- 15 files changed, 450 insertions(+), 217 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ca716c..2bfa5d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Historique des versions +## V.2026 (2026.5) — 2026-08-12 + +- Réorganise l'interface autour d'une navigation latérale stable : Contrôle en direct, Contrôleurs, Plug-ins, Bibliothèque et accès AutoMap dédié. +- Simplifie la page Contrôle en direct autour de l'état du moteur, de l'action principale, des banques et des accès rapides, tout en gardant visibles le démarrage Windows et la connexion automatique. +- Regroupe les outils EC4, l'apprentissage MIDI et les réglages techniques dans une section avancée repliable sans supprimer aucune fonction. +- Protège les libellés français et anglais contre la troncature avec une fenêtre minimale plus large, des zones redimensionnables et des actions réorganisées. + ## V.2026 (2026.4) — 2026-08-10 - Simplifie l'écran d'attente EC4 en retirant la signature « By Mamat » et efface cet écran dès l'arrêt du moteur ou la fermeture de Controller Studio. diff --git a/README.md b/README.md index 7ce269f..2566f6d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

- Version V.2026.4 + Version V.2026.5 Windows 10 et 11 x64 Français et anglais

@@ -23,16 +23,16 @@ ## Dernière version (accès rapide) -**Version stable : [Controller Studio V.2026.4](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/tag/v2026.4)**
+**Version stable : [Controller Studio V.2026.5](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/tag/v2026.5)**
Téléchargement direct : -- [Installer Controller Studio pour Windows x64 (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Setup-v2026.4.exe) -- [Version portable Windows x64 (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor.exe) -- [Notice complète en français (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Notice-FR.pdf) -- [Full English manual (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Manual-EN.pdf) -- [Sommes de contrôle SHA-256](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/SHA256SUMS.txt) +- [Installer Controller Studio pour Windows x64 (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Setup-v2026.5.exe) +- [Version portable Windows x64 (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor.exe) +- [Notice complète en français (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Notice-FR.pdf) +- [Full English manual (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Manual-EN.pdf) +- [Sommes de contrôle SHA-256](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/SHA256SUMS.txt) -> **Version publique V.2026.4 pour Windows.** Controller Studio travaille sur une copie AutoMap et conserve le projet `.rack2` source intact. +> **Version publique V.2026.5 pour Windows.** Controller Studio adopte une navigation latérale plus claire, travaille sur une copie AutoMap et conserve le projet `.rack2` source intact. ## À quoi sert Controller Studio ? @@ -95,7 +95,7 @@ VST est une marque déposée de Steinberg Media Technologies GmbH. Les mentions ## Banque de contrôleurs intégrée -V.2026.4 fournit 33 profils déclaratifs prêts à exporter et permet d’en fabriquer d’autres directement dans l’application : +V.2026.5 fournit 33 profils déclaratifs prêts à exporter et permet d’en fabriquer d’autres directement dans l’application : - Akai Professional LPD8 MK2, MIDImix, APC Mini MK2, MPK Mini MK3, MPK Mini IV et MPK Mini Plus ; - Arturia MiniLab 3, KeyLab Essential MK3 et BeatStep ; @@ -210,25 +210,25 @@ LiveProfessor, Faderfox, Behringer, Novation, DJ TechTools et les autres noms ci

- Version V.2026.4 + Version V.2026.5 Windows 10 and 11 x64 French and English

## Latest version (quick access) -**Stable release: [Controller Studio V.2026.4](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/tag/v2026.4)**
+**Stable release: [Controller Studio V.2026.5](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/tag/v2026.5)**
Direct downloads: -- [Controller Studio installer for Windows x64 (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Setup-v2026.4.exe) -- [Portable Windows x64 version (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor.exe) -- [Full English manual (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Manual-EN.pdf) -- [Notice complète en français (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Notice-FR.pdf) -- [SHA-256 checksums](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/SHA256SUMS.txt) +- [Controller Studio installer for Windows x64 (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Setup-v2026.5.exe) +- [Portable Windows x64 version (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor.exe) +- [Full English manual (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Manual-EN.pdf) +- [Notice complète en français (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Notice-FR.pdf) +- [SHA-256 checksums](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/SHA256SUMS.txt) [Back to the French version ↑](#version-francaise) -> **Public V.2026.4 Windows release.** Controller Studio always works on a new AutoMap copy and keeps the source `.rack2` project untouched. +> **Public V.2026.5 Windows release.** Controller Studio introduces clearer sidebar navigation, always works on a new AutoMap copy, and keeps the source `.rack2` project untouched. ## What does Controller Studio do? @@ -291,7 +291,7 @@ VST is a registered trademark of Steinberg Media Technologies GmbH. Third-party ## Built-in controller bank -V.2026.4 includes 33 declarative profiles ready to export and can build more directly in the application: +V.2026.5 includes 33 declarative profiles ready to export and can build more directly in the application: - Akai Professional LPD8 MK2, MIDImix, APC Mini MK2, MPK Mini MK3, MPK Mini IV, and MPK Mini Plus; - Arturia MiniLab 3, KeyLab Essential MK3, and BeatStep; diff --git a/README_EN.md b/README_EN.md index 41fd032..5664e96 100644 --- a/README_EN.md +++ b/README_EN.md @@ -10,25 +10,25 @@

- Version V.2026.4 + Version V.2026.5 Windows 10 and 11 x64 French and English

## Latest version (quick access) -**Stable release: [Controller Studio V.2026.4](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/tag/v2026.4)**
+**Stable release: [Controller Studio V.2026.5](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/tag/v2026.5)**
Direct downloads: -- [Controller Studio installer for Windows x64 (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Setup-v2026.4.exe) -- [Portable Windows x64 version (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor.exe) -- [Full English manual (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Manual-EN.pdf) -- [Notice complète en français (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/Controller-Studio-for-LiveProfessor-Notice-FR.pdf) -- [SHA-256 checksums](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.4/SHA256SUMS.txt) +- [Controller Studio installer for Windows x64 (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Setup-v2026.5.exe) +- [Portable Windows x64 version (.exe)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor.exe) +- [Full English manual (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Manual-EN.pdf) +- [Notice complète en français (.pdf)](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/Controller-Studio-for-LiveProfessor-Notice-FR.pdf) +- [SHA-256 checksums](https://github.com/Mamat79/Controller-Studio-for-LiveProfessor/releases/download/v2026.5/SHA256SUMS.txt) [Lire cette présentation en français](README.md) -> **Public V.2026.4 Windows release.** Controller Studio always works on a new AutoMap copy and keeps the source `.rack2` project untouched. +> **Public V.2026.5 Windows release.** Controller Studio introduces clearer sidebar navigation, always works on a new AutoMap copy, and keeps the source `.rack2` project untouched. ## What does Controller Studio do? @@ -91,7 +91,7 @@ VST is a registered trademark of Steinberg Media Technologies GmbH. Third-party ## Built-in controller bank -V.2026.4 includes 33 declarative profiles ready to export and can build more directly in the application: +V.2026.5 includes 33 declarative profiles ready to export and can build more directly in the application: - Akai Professional LPD8 MK2, MIDImix, APC Mini MK2, MPK Mini MK3, MPK Mini IV, and MPK Mini Plus; - Arturia MiniLab 3, KeyLab Essential MK3, and BeatStep; diff --git a/docs/CONTROLLER_PROFILE_SOURCES.md b/docs/CONTROLLER_PROFILE_SOURCES.md index 766ab83..8feab30 100644 --- a/docs/CONTROLLER_PROFILE_SOURCES.md +++ b/docs/CONTROLLER_PROFILE_SOURCES.md @@ -1,6 +1,6 @@ # Sources et modes des profils de contrôleurs -Controller Studio V.2026.4 fournit 33 profils déclaratifs. Un profil `community` +Controller Studio V.2026.5 fournit 33 profils déclaratifs. Un profil `community` est utilisable pour l'export `.ctrl2`, AutoMap et l'apprentissage MIDI, mais son plan complet n'a pas encore été validé sur un exemplaire physique par le projet. Les profils programmables utilisent un plan SiLeMIO simple et reproductible à diff --git a/packaging/windows/SiLeMIO-Controller-Studio.iss b/packaging/windows/SiLeMIO-Controller-Studio.iss index 0275eaa..71155b9 100644 --- a/packaging/windows/SiLeMIO-Controller-Studio.iss +++ b/packaging/windows/SiLeMIO-Controller-Studio.iss @@ -1,5 +1,5 @@ #define MyAppName "Controller Studio for LiveProfessor" -#define MyAppVersion "2026.4" +#define MyAppVersion "2026.5" #define MyAppPublisher "Mamat" #define MyAppExeName "Controller-Studio-for-LiveProfessor.exe" #define MyShortcutName "Controller Studio for LiveProfessor" @@ -23,7 +23,7 @@ ArchitecturesAllowed=x64compatible PrivilegesRequired=lowest UsedUserAreasWarning=no UninstallDisplayIcon={app}\{#MyAppExeName} -VersionInfoVersion=2026.4.0.0 +VersionInfoVersion=2026.5.0.0 VersionInfoCompany={#MyAppPublisher} VersionInfoDescription={#MyAppName} installer VersionInfoProductName={#MyAppName} diff --git a/packaging/windows/build_setup.ps1 b/packaging/windows/build_setup.ps1 index 3b6bc38..e1dcbcd 100644 --- a/packaging/windows/build_setup.ps1 +++ b/packaging/windows/build_setup.ps1 @@ -7,7 +7,7 @@ $ErrorActionPreference = 'Stop' $projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path $application = Join-Path $projectRoot 'dist\Controller-Studio-for-LiveProfessor.exe' $setupScript = Join-Path $PSScriptRoot 'SiLeMIO-Controller-Studio.iss' -$setup = Join-Path $projectRoot 'dist\Controller-Studio-for-LiveProfessor-Setup-v2026.4.exe' +$setup = Join-Path $projectRoot 'dist\Controller-Studio-for-LiveProfessor-Setup-v2026.5.exe' $checksum = "$setup.sha256" if ($RebuildApplication -or -not (Test-Path -LiteralPath $application -PathType Leaf)) { diff --git a/packaging/windows/install.ps1 b/packaging/windows/install.ps1 index 6e195a6..64a9d72 100644 --- a/packaging/windows/install.ps1 +++ b/packaging/windows/install.ps1 @@ -44,7 +44,7 @@ $uninstallKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\SiLeM $hostExe = (Get-Process -Id $PID).Path $null = New-Item -Path $uninstallKey -Force Set-ItemProperty -Path $uninstallKey -Name DisplayName -Value 'Controller Studio for LiveProfessor' -Set-ItemProperty -Path $uninstallKey -Name DisplayVersion -Value '2026.4' +Set-ItemProperty -Path $uninstallKey -Name DisplayVersion -Value '2026.5' Set-ItemProperty -Path $uninstallKey -Name Publisher -Value 'Mamat' Set-ItemProperty -Path $uninstallKey -Name InstallLocation -Value $InstallRoot Set-ItemProperty -Path $uninstallKey -Name DisplayIcon -Value $targetExe diff --git a/pyproject.toml b/pyproject.toml index c9115e7..3d4351b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "silemio-control-hub" -version = "2026.4" +version = "2026.5" description = "Controller Studio for LiveProfessor by SiLeMI/O" requires-python = ">=3.11" dependencies = [ diff --git a/scripts/generate_manuals.py b/scripts/generate_manuals.py index c6635ef..a463422 100644 --- a/scripts/generate_manuals.py +++ b/scripts/generate_manuals.py @@ -156,7 +156,7 @@ ( "steps", [ - ("1", "Ouvrez l'onglet Plug-ins puis choisissez un projet .rack2."), + ("1", "Ouvrez Plug-ins dans la navigation latérale puis choisissez un projet .rack2."), ("2", "Cliquez sur Analyser les plug-ins. Les instances identiques sont regroupées par type."), ("3", "Sélectionnez un type puis ouvrez Créer / modifier le profil."), ("4", "Renseignez le nom, le libellé court, le type, le rôle, l'unité et la priorité des paramètres utiles."), @@ -617,7 +617,7 @@ def english_content() -> dict: ( "steps", [ - ("1", "Ouvrez l'onglet Plug-ins et choisissez un projet .rack2."), + ("1", "Ouvrez Plug-ins dans la navigation latérale puis choisissez un projet .rack2."), ("2", "Analysez le projet : les instances identiques sont regroupées automatiquement."), ("3", "Cliquez sur Récupérer tous les vrais noms pour traiter tous les types installés dans des processus isolés."), ("4", "Seuls les inventaires dont le nombre correspond exactement au projet sont enregistrés."), @@ -663,7 +663,7 @@ def english_content() -> dict: ("h2", "Une première page simple"), ( "p", - "La page Live garde uniquement le contrôleur actif, les commandes essentielles, l'état et le dernier événement. Les connexions MIDI/OSC sont dans Réglages et le journal complet s'ouvre dans une fenêtre séparée.", + "La navigation latérale donne un accès direct à Live, Banque de contrôleurs, Plug-ins, Bibliothèque et AutoMap. La page Live garde uniquement le contrôleur actif, les deux options de démarrage automatique, les commandes essentielles, l'état et le dernier événement. Les connexions MIDI/OSC sont regroupées dans Réglages avancés et le journal complet s'ouvre dans une fenêtre séparée.", ), ("h2", "Réactivité, feedback et Overlay"), ( @@ -774,7 +774,7 @@ def english_content() -> dict: "4. Organize parameters with Plugin Studio", [ ("h2", "Recognize a plug-in"), - ("steps", [("1", "Open the Plug-ins tab and choose a .rack2 project."), ("2", "Analyze the project; matching instances are grouped automatically."), ("3", "Click Retrieve all real names to process every installed type in isolated workers."), ("4", "Only inventories whose count exactly matches the project are saved."), ("5", "Open a profile to use Select all, Select none, or individual checkboxes."), ("6", "Adjust names and priorities if needed, save locally, then use the profile in AutoMap.")]), + ("steps", [("1", "Open Plug-ins from the sidebar and choose a .rack2 project."), ("2", "Analyze the project; matching instances are grouped automatically."), ("3", "Click Retrieve all real names to process every installed type in isolated workers."), ("4", "Only inventories whose count exactly matches the project are saved."), ("5", "Open a profile to use Select all, Select none, or individual checkboxes."), ("6", "Adjust names and priorities if needed, save locally, then use the profile in AutoMap.")]), ("callout", "For an older format or an unusual plug-in, the individual button automatically offers Companion/OSC interception as a fallback."), ("h2", "Safe local profiles"), ("p", "Profiles are declarative JSON files. Replaced versions can be backed up, downloaded code is never executed, and the analyzed project remains unchanged."), @@ -794,7 +794,7 @@ def english_content() -> dict: "6. Interface, log, and updates", [ ("h2", "A simple first page"), - ("p", "The Live page keeps only the active controller, essential actions, status, and last event. MIDI/OSC connections are under Settings, and the complete log opens in a separate window."), + ("p", "The sidebar provides direct access to Live, Controller bank, Plug-ins, Library, and AutoMap. The Live page keeps the active controller, both automatic-start options, essential actions, status, and last event. MIDI/OSC connections are grouped under Advanced settings, and the complete log opens in a separate window."), ("h2", "Responsiveness, feedback, and Overlay"), ("p", "Settings restores EC4 Bridge's compact advanced window: Overlay update rate and duration, Companion and label refresh delays, LiveProfessor feedback timeout, and persistent display. These timings are shared by compatible controllers; setup/group and SysEx commands remain EC4-only."), ("h2", "Automatic server startup"), @@ -828,12 +828,12 @@ def styles(): "subtitle": ParagraphStyle("Subtitle", parent=base["Normal"], fontName="Helvetica", fontSize=12, leading=17, textColor=MID, alignment=TA_CENTER), "h1": ParagraphStyle("H1", parent=base["Heading1"], fontName="Helvetica-Bold", fontSize=18, leading=22, textColor=NAVY, spaceAfter=5 * mm), "h2": ParagraphStyle("H2", parent=base["Heading2"], fontName="Helvetica-Bold", fontSize=12.2, leading=16, textColor=BLUE, spaceBefore=3 * mm, spaceAfter=2 * mm), - "body": ParagraphStyle("Body", parent=base["BodyText"], fontName="Helvetica", fontSize=10.2, leading=14.5, textColor=NAVY, alignment=TA_LEFT, spaceAfter=2.7 * mm), - "small": ParagraphStyle("Small", parent=base["BodyText"], fontName="Helvetica", fontSize=8.8, leading=12, textColor=MID, alignment=TA_CENTER), - "toc": ParagraphStyle("Toc", parent=base["BodyText"], fontName="Helvetica", fontSize=10.7, leading=16, leftIndent=5 * mm, textColor=NAVY), - "bullet": ParagraphStyle("Bullet", parent=base["BodyText"], fontName="Helvetica", fontSize=10.1, leading=14.3, leftIndent=6 * mm, firstLineIndent=-3 * mm, textColor=NAVY, spaceAfter=1.5 * mm), - "callout": ParagraphStyle("Callout", parent=base["BodyText"], fontName="Helvetica-Bold", fontSize=10.1, leading=14.3, textColor=NAVY, leftIndent=3 * mm, rightIndent=3 * mm), - "table_header": ParagraphStyle("TableHeader", parent=base["BodyText"], fontName="Helvetica-Bold", fontSize=10.1, leading=14.3, textColor=colors.white, alignment=TA_LEFT), + "body": ParagraphStyle("Body", parent=base["BodyText"], fontName="Helvetica", fontSize=10.8, leading=15.3, textColor=NAVY, alignment=TA_LEFT, spaceAfter=2.7 * mm), + "small": ParagraphStyle("Small", parent=base["BodyText"], fontName="Helvetica", fontSize=9.2, leading=12.6, textColor=MID, alignment=TA_CENTER), + "toc": ParagraphStyle("Toc", parent=base["BodyText"], fontName="Helvetica", fontSize=11, leading=16.5, leftIndent=5 * mm, textColor=NAVY), + "bullet": ParagraphStyle("Bullet", parent=base["BodyText"], fontName="Helvetica", fontSize=10.7, leading=15.1, leftIndent=6 * mm, firstLineIndent=-3 * mm, textColor=NAVY, spaceAfter=1.5 * mm), + "callout": ParagraphStyle("Callout", parent=base["BodyText"], fontName="Helvetica-Bold", fontSize=10.7, leading=15.1, textColor=NAVY, leftIndent=3 * mm, rightIndent=3 * mm), + "table_header": ParagraphStyle("TableHeader", parent=base["BodyText"], fontName="Helvetica-Bold", fontSize=10.7, leading=15.1, textColor=colors.white, alignment=TA_LEFT), } diff --git a/src/silemio_control_hub/__init__.py b/src/silemio_control_hub/__init__.py index 178fe34..5984aad 100644 --- a/src/silemio_control_hub/__init__.py +++ b/src/silemio_control_hub/__init__.py @@ -48,4 +48,4 @@ "prepare_liveprofessor_project", ] -__version__ = "2026.4" +__version__ = "2026.5" diff --git a/src/silemio_control_hub/desktop.py b/src/silemio_control_hub/desktop.py index 6ac444d..9ed33ae 100644 --- a/src/silemio_control_hub/desktop.py +++ b/src/silemio_control_hub/desktop.py @@ -104,6 +104,7 @@ PRODUCT_ICON_PATH = Path(__file__).resolve().parent / "assets" / "controller-studio.ico" +PRODUCT_LOGO_PATH = Path(__file__).resolve().parent / "assets" / "controller-studio.png" DISPLAY_VERSION = f"V.{__version__}" AUTO_START_RETRY_DELAYS_MS = (1200, 3000, 6000, 12000, 20000) @@ -121,6 +122,8 @@ "tab_controllers": "Banque de contrôleurs", "tab_plugins": "Plug-ins", "tab_library": "Bibliothèque", + "nav_automap": "⚡ AutoMap", + "sidebar_footer": "Journal · Aide · Réglages", "menu_file": "Fichier", "menu_options": "Options", "menu_tools": "Outils", @@ -132,6 +135,16 @@ "utiles à son pilote temps réel." ), "active_controller": "Contrôleur actif", + "automation_options": "Automatisation", + "quick_access": "Accès rapides", + "advanced_controller": "Réglages avancés du contrôleur", + "advanced_controller_open": "▼ Réglages avancés du contrôleur", + "advanced_controller_closed": "▶ Réglages avancés du contrôleur", + "controllers_title": "Banque de contrôleurs", + "controllers_description": ( + "Choisissez un profil existant, fabriquez votre contrôleur ou exportez " + "son fichier LiveProfessor." + ), "driver_ready": "PILOTE TEMPS RÉEL PRÊT", "driver_profile_only": "PROFIL AUTOMAP / EXPORT", "driver_profile_only_body": ( @@ -662,6 +675,8 @@ "tab_controllers": "Controller bank", "tab_plugins": "Plug-ins", "tab_library": "Library", + "nav_automap": "⚡ AutoMap", + "sidebar_footer": "Log · Help · Settings", "menu_file": "File", "menu_options": "Options", "menu_tools": "Tools", @@ -673,6 +688,16 @@ "to its real-time driver." ), "active_controller": "Active controller", + "automation_options": "Automation", + "quick_access": "Quick access", + "advanced_controller": "Advanced controller settings", + "advanced_controller_open": "▼ Advanced controller settings", + "advanced_controller_closed": "▶ Advanced controller settings", + "controllers_title": "Controller bank", + "controllers_description": ( + "Choose an existing profile, build your controller, or export its " + "LiveProfessor file." + ), "driver_ready": "REAL-TIME DRIVER READY", "driver_profile_only": "AUTOMAP / EXPORT PROFILE", "driver_profile_only_body": ( @@ -1332,14 +1357,16 @@ def __init__(self, root: tk.Tk, *, start_minimized: bool = False) -> None: self._library_buttons: list[ttk.Button] = [] self._closing = False self._suppress_unmap = False + self.current_page = "live" + self._live_advanced_open = False try: self.log_path = configure_runtime_logging(self.runtime_config.log_level) except OSError as exc: self.log_path = default_log_path() self._configuration_load_error = self._configuration_load_error or str(exc) - root.geometry("1040x680") - root.minsize(840, 540) + root.geometry("1180x760") + root.minsize(1080, 680) if PRODUCT_ICON_PATH.is_file(): try: root.iconbitmap(default=str(PRODUCT_ICON_PATH)) @@ -1384,73 +1411,154 @@ def _configure_style(self) -> None: style.configure("Treeview", rowheight=26, font=("Segoe UI", 9)) style.configure("Treeview.Heading", font=("Segoe UI Semibold", 9)) style.configure("Accent.TButton", font=("Segoe UI Semibold", 9)) + style.configure("Section.TLabelframe", padding=(12, 10)) + style.configure("Section.TLabelframe.Label", font=("Segoe UI Semibold", 10)) def _build_ui(self) -> None: self.root.title(self._t("window_title", version=DISPLAY_VERSION)) self._build_menu() - brand = tk.Frame( - self.root, - bg="#111820", - height=72, - highlightthickness=1, - highlightbackground="#2a4050", - ) - brand.pack(fill="x", padx=18, pady=(14, 8)) - brand.pack_propagate(False) + shell = ttk.Frame(self.root) + shell.pack(fill="both", expand=True) + shell.columnconfigure(1, weight=1) + shell.rowconfigure(0, weight=1) + + sidebar = tk.Frame(shell, bg="#101c26", width=210) + sidebar.grid(row=0, column=0, sticky="nsew") + sidebar.grid_propagate(False) + self.sidebar_logo_image: tk.PhotoImage | None = None + if PRODUCT_LOGO_PATH.is_file(): + try: + self.sidebar_logo_image = tk.PhotoImage(file=str(PRODUCT_LOGO_PATH)).subsample( + 12, 12 + ) + except tk.TclError: + self.sidebar_logo_image = None + if self.sidebar_logo_image is not None: + tk.Label( + sidebar, + image=self.sidebar_logo_image, + bg="#101c26", + borderwidth=0, + ).pack(pady=(16, 4)) tk.Label( - brand, + sidebar, text=PRODUCT_NAME, - bg="#111820", - fg="#e8f4f8", - font=("Segoe UI Semibold", 20), - ).pack(side="left", padx=(18, 12)) + bg="#101c26", + fg="#f2f8fb", + font=("Segoe UI Semibold", 17), + anchor="center", + justify="center", + ).pack(fill="x", padx=14, pady=(0 if self.sidebar_logo_image else 24, 2)) tk.Label( - brand, + sidebar, text=self._t("brand_context", version=DISPLAY_VERSION), - bg="#111820", - fg="#91a9b5", - font=("Segoe UI", 11), - ).pack(side="left") - signature = tk.Frame(brand, bg="#111820") - signature.pack(side="right", padx=16) + bg="#101c26", + fg="#8ca4b3", + font=("Segoe UI", 9), + anchor="center", + justify="center", + ).pack(fill="x", padx=14, pady=(0, 18)) + + self.sidebar_buttons: dict[str, tk.Button] = {} + nav = tk.Frame(sidebar, bg="#101c26") + nav.pack(fill="x", padx=12) + for page, label in ( + ("live", self._t("tab_live")), + ("controllers", self._t("tab_controllers")), + ("plugins", self._t("tab_plugins")), + ("library", self._t("tab_library")), + ): + button = tk.Button( + nav, + text=label, + command=lambda selected=page: self._show_page(selected), + anchor="w", + padx=14, + pady=10, + relief="flat", + borderwidth=0, + font=("Segoe UI Semibold", 10), + cursor="hand2", + ) + button.pack(fill="x", pady=2) + self.sidebar_buttons[page] = button + tk.Button( + nav, + text=self._t("nav_automap"), + command=self.prepare_automap, + bg="#08a7c8", + fg="#ffffff", + activebackground="#0ab4d7", + activeforeground="#ffffff", + anchor="w", + padx=14, + pady=10, + relief="flat", + borderwidth=0, + font=("Segoe UI Semibold", 10), + cursor="hand2", + ).pack(fill="x", pady=(12, 2)) + + sidebar_footer = tk.Frame(sidebar, bg="#101c26") + sidebar_footer.pack(side="bottom", fill="x", padx=20, pady=18) + signature = tk.Frame(sidebar_footer, bg="#101c26") + signature.pack(fill="x") tk.Label( signature, text=BRAND_NAME, - bg="#111820", - fg="#12c9e8", - font=("Segoe UI Semibold", 14), - ).pack(side="left", padx=(0, 12)) + bg="#101c26", + fg="#19d6f2", + font=("Segoe UI Semibold", 12), + anchor="w", + ).pack(side="left") tk.Label( signature, - text="By Mamat\n-------[]--", - bg="#111820", - fg="#91a9b5", - font=("Segoe UI", 9), - justify="right", - ).pack(side="left") - - ttk.Label( - self.root, - text=self._t("intro"), - style="Subtitle.TLabel", - ).pack(anchor="w", padx=20, pady=(0, 7)) - - notebook = ttk.Notebook(self.root) - self.notebook = notebook - notebook.pack(fill="both", expand=True, padx=18, pady=(4, 10)) - live_tab = ttk.Frame(notebook, padding=12) + text="By Mamat", + bg="#101c26", + fg="#8ca4b3", + font=("Segoe UI", 8), + anchor="e", + ).pack(side="right") + tk.Label( + sidebar_footer, + text="-------[]--", + bg="#101c26", + fg="#8ca4b3", + font=("Consolas", 8), + anchor="w", + ).pack(fill="x") + tk.Label( + sidebar_footer, + text=self._t("sidebar_footer"), + bg="#101c26", + fg="#6f8999", + font=("Segoe UI", 8), + anchor="w", + ).pack(fill="x", pady=(5, 0)) + + content = ttk.Frame(shell, padding=(24, 20, 24, 14)) + content.grid(row=0, column=1, sticky="nsew") + content.columnconfigure(0, weight=1) + content.rowconfigure(0, weight=1) + self.page_frames: dict[str, ttk.Frame] = {} + live_tab = ttk.Frame(content) self.live_tab = live_tab - controllers_tab = ttk.Frame(notebook, padding=12) - plugins_tab = ttk.Frame(notebook, padding=12) - library_tab = ttk.Frame(notebook, padding=12) - notebook.add(live_tab, text=self._t("tab_live")) - notebook.add(controllers_tab, text=self._t("tab_controllers")) - notebook.add(plugins_tab, text=self._t("tab_plugins")) - notebook.add(library_tab, text=self._t("tab_library")) + controllers_tab = ttk.Frame(content) + plugins_tab = ttk.Frame(content) + library_tab = ttk.Frame(content) + self.page_frames.update( + live=live_tab, + controllers=controllers_tab, + plugins=plugins_tab, + library=library_tab, + ) + for frame in self.page_frames.values(): + frame.grid(row=0, column=0, sticky="nsew") self._build_live_tab(live_tab) self._build_controllers_tab(controllers_tab) self._build_plugins_tab(plugins_tab) self._build_library_tab(library_tab) + self._show_page(self.current_page) ttk.Separator(self.root).pack(fill="x") ttk.Label( @@ -1460,6 +1568,27 @@ def _build_ui(self) -> None: anchor="w", ).pack(fill="x") + def _show_page(self, page: str) -> None: + if page not in self.page_frames: + page = "live" + self.current_page = page + self.page_frames[page].tkraise() + for name, button in self.sidebar_buttons.items(): + if name == page: + button.configure( + bg="#173647", + fg="#ffffff", + activebackground="#1c455a", + activeforeground="#ffffff", + ) + else: + button.configure( + bg="#101c26", + fg="#b8c8d2", + activebackground="#17303f", + activeforeground="#ffffff", + ) + def _build_menu(self) -> None: menu = tk.Menu(self.root) file_menu = tk.Menu(menu, tearoff=0) @@ -1557,20 +1686,37 @@ def _build_menu(self) -> None: def _build_live_tab(self, parent: ttk.Frame) -> None: parent.columnconfigure(0, weight=1) - parent.rowconfigure(6, weight=1) - ttk.Label(parent, text=self._t("live_title"), style="Title.TLabel").grid( + parent.rowconfigure(5, weight=1) + header = ttk.Frame(parent) + header.grid(row=0, column=0, sticky="ew", pady=(0, 12)) + header.columnconfigure(0, weight=1) + ttk.Label(header, text=self._t("live_title"), style="Title.TLabel").grid( row=0, column=0, sticky="w" ) ttk.Label( - parent, + header, text=self._t("live_description"), style="Subtitle.TLabel", - wraplength=900, + wraplength=720, justify="left", - ).grid(row=1, column=0, sticky="ew", pady=(2, 8)) + ).grid(row=1, column=0, sticky="ew", pady=(3, 0)) + self.live_driver_badge = tk.Label( + header, + bg="#e9f7f1", + fg="#16825d", + font=("Segoe UI Semibold", 8), + padx=10, + pady=5, + ) + self.live_driver_badge.grid(row=0, column=1, rowspan=2, sticky="ne") - controller = ttk.LabelFrame(parent, text=self._t("active_controller"), padding=10) - controller.grid(row=2, column=0, sticky="ew") + controller = ttk.LabelFrame( + parent, + text=self._t("active_controller"), + padding=12, + style="Section.TLabelframe", + ) + controller.grid(row=1, column=0, sticky="ew") controller.columnconfigure(0, weight=1) self.live_controller_combo = ttk.Combobox( controller, @@ -1586,125 +1732,201 @@ def _build_live_tab(self, parent: ttk.Frame) -> None: text=self._t("configure_active_controller"), command=self.edit_controller, ).grid(row=0, column=1, sticky="e", padx=(0, 8)) - self.live_driver_badge = tk.Label( - controller, - bg="#dff5fb", - fg="#087d9d", - font=("Segoe UI Semibold", 8), - padx=9, - pady=3, - ) - self.live_driver_badge.grid(row=0, column=2, sticky="e") self.live_driver_note = ttk.Label( controller, style="Subtitle.TLabel", - wraplength=850, + wraplength=780, justify="left", ) self.live_driver_note.grid( - row=1, column=0, columnspan=3, sticky="ew", pady=(7, 0) + row=1, column=0, columnspan=2, sticky="ew", pady=(7, 0) ) - startup_options = ttk.Frame(controller) - startup_options.grid( - row=2, column=0, columnspan=3, sticky="ew", pady=(8, 0) + + startup_options = ttk.LabelFrame( + parent, + text=self._t("automation_options"), + padding=10, + style="Section.TLabelframe", ) - ttk.Checkbutton( + startup_options.grid(row=2, column=0, sticky="ew", pady=(10, 0)) + startup_options.columnconfigure(0, weight=1, uniform="automation") + startup_options.columnconfigure(1, weight=1, uniform="automation") + windows_start = tk.Checkbutton( startup_options, text=self._t("start_with_windows"), variable=self.start_with_windows_var, command=self._save_start_with_windows, - ).pack(side="left") - self.auto_start_runtime_check = ttk.Checkbutton( + anchor="w", + justify="left", + wraplength=300, + bg="#ffffff", + activebackground="#ffffff", + highlightthickness=0, + font=("Segoe UI", 9), + ) + windows_start.grid(row=0, column=0, sticky="ew", padx=(0, 12)) + self.auto_start_runtime_check = tk.Checkbutton( startup_options, text=self._t("auto_start_runtime"), variable=self.auto_start_runtime_var, command=self._save_auto_start_runtime, + anchor="w", + justify="left", + wraplength=340, + bg="#ffffff", + activebackground="#ffffff", + highlightthickness=0, + font=("Segoe UI", 9), ) - self.auto_start_runtime_check.pack(side="left", padx=(18, 0)) + self.auto_start_runtime_check.grid(row=0, column=1, sticky="ew") - actions = ttk.Frame(parent) - actions.grid(row=3, column=0, sticky="ew", pady=(9, 5)) - tk.Button( - actions, - text=self._t("prepare_automap"), - command=self.prepare_automap, - bg="#087d9d", + dashboard = ttk.Frame(parent) + dashboard.grid(row=3, column=0, sticky="nsew", pady=(10, 0)) + dashboard.columnconfigure(0, weight=2, uniform="dashboard") + dashboard.columnconfigure(1, weight=1, uniform="dashboard") + dashboard.rowconfigure(0, weight=1) + + state_frame = ttk.LabelFrame( + dashboard, + text=self._t("state_frame"), + padding=14, + style="Section.TLabelframe", + ) + state_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 6)) + state_frame.columnconfigure(0, weight=1) + ttk.Label( + state_frame, + textvariable=self.runtime_status, + wraplength=470, + justify="left", + font=("Segoe UI Semibold", 12), + ).grid(row=0, column=0, columnspan=3, sticky="ew") + + runtime_controls = ttk.Frame(state_frame) + runtime_controls.grid(row=1, column=0, columnspan=3, sticky="ew", pady=(13, 12)) + runtime_controls.columnconfigure(0, weight=1) + self.runtime_start_button = tk.Button( + runtime_controls, + text=f"▶ {self._t('start')}", + command=self.start_runtime, + bg="#08a7c8", fg="#ffffff", - activebackground="#0aa1c9", + activebackground="#0ab4d7", activeforeground="#ffffff", + disabledforeground="#d7eef3", relief="flat", borderwidth=0, - padx=14, - pady=5, + padx=16, + pady=8, font=("Segoe UI Semibold", 10), cursor="hand2", - ).pack(side="right") - ttk.Button( - actions, - text=self._t("live_settings"), - command=self.show_live_settings, - ).pack(side="right", padx=(0, 7)) - ttk.Button( - actions, - text=self._t("log_window"), - command=self.show_runtime_log, - ).pack(side="right", padx=(0, 7)) - self.runtime_start_button = ttk.Button( - actions, text=self._t("start"), command=self.start_runtime ) self.runtime_stop_button = ttk.Button( - actions, text=self._t("stop"), command=self.stop_runtime + runtime_controls, text=self._t("stop"), command=self.stop_runtime ) self.runtime_restart_button = ttk.Button( - actions, text=self._t("restart"), command=self.restart_runtime + runtime_controls, text=self._t("restart"), command=self.restart_runtime ) - self.runtime_start_button.pack(side="left") - self.runtime_stop_button.pack(side="left", padx=(6, 0)) - self.runtime_restart_button.pack(side="left", padx=(6, 0)) - ttk.Button( - actions, - text=self._t("minimize"), - command=self.minimize_to_tray, - ).pack(side="left", padx=(6, 0)) + self.runtime_start_button.grid(row=0, column=0, sticky="ew") + self.runtime_stop_button.grid(row=0, column=1, padx=(8, 0)) + self.runtime_restart_button.grid(row=0, column=2, padx=(8, 0)) self.runtime_action_buttons: list[ttk.Button] = [] self.runtime_settings_action_buttons: list[ttk.Button] = [] - state_frame = ttk.LabelFrame(parent, text=self._t("state_frame"), padding=10) - state_frame.grid(row=4, column=0, sticky="ew", pady=(4, 6)) - state_frame.columnconfigure(0, weight=1) - ttk.Label( - state_frame, - textvariable=self.runtime_status, - wraplength=500, - justify="left", - ).grid( - row=0, column=0, sticky="ew" - ) self.previous_bank_button = ttk.Button( state_frame, text=self._t("previous_bank"), command=lambda: self.change_bank(-1), ) - self.previous_bank_button.grid(row=0, column=1, padx=(8, 4)) + self.previous_bank_button.grid(row=2, column=0, sticky="w") ttk.Label( state_frame, textvariable=self.runtime_bank, - width=12, + width=9, anchor="center", font=("Segoe UI Semibold", 10), - ).grid(row=0, column=2) + ).grid(row=2, column=1, padx=8) self.next_bank_button = ttk.Button( state_frame, text=self._t("next_bank"), command=lambda: self.change_bank(1), ) - self.next_bank_button.grid(row=0, column=3, padx=(4, 0)) + self.next_bank_button.grid(row=2, column=2, sticky="e") + ttk.Separator(state_frame).grid( + row=3, column=0, columnspan=3, sticky="ew", pady=(14, 10) + ) + ttk.Label( + state_frame, + text=self._t("last_event"), + style="Subtitle.TLabel", + ).grid(row=4, column=0, columnspan=3, sticky="w") + ttk.Label( + state_frame, + textvariable=self.runtime_last_event, + anchor="w", + justify="left", + wraplength=470, + ).grid(row=5, column=0, columnspan=3, sticky="ew", pady=(3, 0)) + quick = ttk.LabelFrame( + dashboard, + text=self._t("quick_access"), + padding=14, + style="Section.TLabelframe", + ) + quick.grid(row=0, column=1, sticky="nsew", padx=(6, 0)) + quick.columnconfigure(0, weight=1) + ttk.Button( + quick, + text=self._t("log_window"), + command=self.show_runtime_log, + ).grid(row=0, column=0, sticky="ew") + ttk.Button( + quick, + text=self._t("shortcuts"), + command=self.show_shortcuts, + ).grid(row=1, column=0, sticky="ew", pady=(8, 0)) + ttk.Button( + quick, + text=self._t("diagnostic"), + command=self.show_diagnostics, + ).grid(row=2, column=0, sticky="ew", pady=(8, 0)) + ttk.Button( + quick, + text=self._t("minimize"), + command=self.minimize_to_tray, + ).grid(row=3, column=0, sticky="ew", pady=(8, 0)) + + self.live_advanced_button = ttk.Button( + parent, + text=self._t("advanced_controller_closed"), + command=self._toggle_live_advanced, + ) + self.live_advanced_button.grid(row=4, column=0, sticky="ew", pady=(10, 0)) + self.live_advanced_frame = ttk.Frame(parent) + self.live_advanced_frame.grid(row=5, column=0, sticky="nsew", pady=(8, 0)) + self.live_advanced_frame.columnconfigure(0, weight=1) + self.live_advanced_frame.rowconfigure(1, weight=1) + advanced_actions = ttk.Frame(self.live_advanced_frame) + advanced_actions.grid(row=0, column=0, sticky="ew", pady=(0, 8)) + ttk.Button( + advanced_actions, + text=self._t("live_settings"), + command=self.show_live_settings, + ).pack(side="left") + ttk.Button( + advanced_actions, + text=self._t("configure_active_controller"), + command=self.edit_controller, + ).pack(side="left", padx=(8, 0)) self.ec4_live_frame = ttk.LabelFrame( - parent, text=self._t("ec4_zone"), padding=10 + self.live_advanced_frame, + text=self._t("ec4_zone"), + padding=10, + style="Section.TLabelframe", ) - self.ec4_live_frame.grid(row=5, column=0, sticky="ew", pady=(0, 6)) + self.ec4_live_frame.grid(row=1, column=0, sticky="nsew") target_row = ttk.Frame(self.ec4_live_frame) target_row.pack(fill="x") ttk.Label(target_row, text=self._t("target_setup")).pack(side="left") @@ -1746,22 +1968,43 @@ def _build_live_tab(self, parent: ttk.Frame) -> None: ttk.Button( learn_row, text=self._t("diagnostic"), command=self.show_diagnostics ).pack(side="right") - - event_frame = ttk.LabelFrame(parent, text=self._t("last_event"), padding=10) - event_frame.grid(row=6, column=0, sticky="nsew", pady=(2, 0)) - ttk.Label( - event_frame, - textvariable=self.runtime_last_event, - anchor="w", - justify="left", - wraplength=900, - ).pack(fill="both", expand=True) + if self._live_advanced_open: + self.live_advanced_button.configure( + text=self._t("advanced_controller_open") + ) + else: + self.live_advanced_frame.grid_remove() self._update_mapping_status() self._set_runtime_widget_states() + def _toggle_live_advanced(self) -> None: + self._live_advanced_open = not self._live_advanced_open + if self._live_advanced_open: + self.live_advanced_frame.grid() + self.live_advanced_button.configure( + text=self._t("advanced_controller_open") + ) + else: + self.live_advanced_frame.grid_remove() + self.live_advanced_button.configure( + text=self._t("advanced_controller_closed") + ) + def _build_controllers_tab(self, parent: ttk.Frame) -> None: parent.columnconfigure(0, weight=1) - parent.rowconfigure(0, weight=1) + parent.rowconfigure(2, weight=1) + ttk.Label( + parent, + text=self._t("controllers_title"), + style="Title.TLabel", + ).grid(row=0, column=0, columnspan=2, sticky="w") + ttk.Label( + parent, + text=self._t("controllers_description"), + style="Subtitle.TLabel", + wraplength=850, + justify="left", + ).grid(row=1, column=0, columnspan=2, sticky="ew", pady=(3, 12)) columns = ("maker", "model", "status", "version", "controls", "layout") self.controller_tree = ttk.Treeview( parent, @@ -1792,13 +2035,13 @@ def _build_controllers_tab(self, parent: ttk.Frame) -> None: parent, orient="vertical", command=self.controller_tree.yview ) self.controller_tree.configure(yscrollcommand=scrollbar.set) - self.controller_tree.grid(row=0, column=0, sticky="nsew") - scrollbar.grid(row=0, column=1, sticky="ns") + self.controller_tree.grid(row=2, column=0, sticky="nsew") + scrollbar.grid(row=2, column=1, sticky="ns") self.controller_tree.bind("<>", self._select_controller) self.controller_tree.bind("", lambda _event: self.edit_controller()) actions = ttk.Frame(parent, padding=(0, 12, 0, 0)) - actions.grid(row=1, column=0, columnspan=2, sticky="ew") + actions.grid(row=3, column=0, columnspan=2, sticky="ew") primary = ttk.Frame(actions) primary.pack(fill="x") tk.Button( @@ -1826,22 +2069,6 @@ def _build_controllers_tab(self, parent: ttk.Frame) -> None: text=self._t("controller_import"), command=self.import_controller, ).pack(side="left", padx=(8, 0)) - tk.Button( - primary, - text=self._t("prepare_automap"), - command=self.prepare_automap, - bg="#087d9d", - fg="#ffffff", - activebackground="#0aa1c9", - activeforeground="#ffffff", - relief="flat", - borderwidth=0, - padx=14, - pady=5, - font=("Segoe UI Semibold", 10), - cursor="hand2", - ).pack(side="right") - secondary = ttk.Frame(actions) secondary.pack(fill="x", pady=(7, 0)) ttk.Button(secondary, text=self._t("refresh"), command=self.reload_catalog).pack( @@ -1857,11 +2084,6 @@ def _build_controllers_tab(self, parent: ttk.Frame) -> None: text=self._t("contribute_controller"), command=self.contribute_controller, ).pack(side="left", padx=(8, 0)) - ttk.Button( - secondary, - text=self._t("minimize"), - command=self.minimize_to_tray, - ).pack(side="left", padx=(8, 0)) def _build_plugins_tab(self, parent: ttk.Frame) -> None: parent.columnconfigure(0, weight=1) @@ -1922,34 +2144,38 @@ def _build_plugins_tab(self, parent: ttk.Frame) -> None: toolbar, textvariable=self.plugin_analysis_status_var, style="Subtitle.TLabel", - ).grid(row=0, column=0, sticky="w") + wraplength=820, + justify="left", + ).grid(row=0, column=0, sticky="ew", pady=(0, 7)) + plugin_actions = ttk.Frame(toolbar) + plugin_actions.grid(row=1, column=0, sticky="ew") self.plugin_scan_all_button = ttk.Button( - toolbar, + plugin_actions, text=self._t("plugin_scan_all"), command=self._scan_all_plugin_names, state="disabled", style="Accent.TButton", ) - self.plugin_scan_all_button.grid(row=0, column=1, padx=(8, 0)) + self.plugin_scan_all_button.pack(side="left") self.plugin_edit_button = ttk.Button( - toolbar, + plugin_actions, text=self._t("plugin_edit"), command=self._open_plugin_profile_editor, state="disabled", ) - self.plugin_edit_button.grid(row=0, column=2, padx=(8, 0)) + self.plugin_edit_button.pack(side="left", padx=(8, 0)) self.plugin_automap_button = ttk.Button( - toolbar, + plugin_actions, text=self._t("plugin_use_automap"), command=self._use_plugin_project_in_automap, state="disabled", ) - self.plugin_automap_button.grid(row=0, column=3, padx=(8, 0)) + self.plugin_automap_button.pack(side="left", padx=(8, 0)) ttk.Button( - toolbar, + plugin_actions, text=self._t("plugin_open_folder"), command=self._open_plugin_profile_folder, - ).grid(row=0, column=4, padx=(8, 0)) + ).pack(side="right") tree_frame = ttk.Frame(parent) tree_frame.grid(row=4, column=0, sticky="nsew") diff --git a/src/silemio_control_hub/library_remote.py b/src/silemio_control_hub/library_remote.py index 2437176..4e3408c 100644 --- a/src/silemio_control_hub/library_remote.py +++ b/src/silemio_control_hub/library_remote.py @@ -226,7 +226,7 @@ def _installed_hub_version() -> str: try: return package_version("silemio-control-hub") except PackageNotFoundError: - return "2026.4.dev0" + return "2026.5.dev0" def _check_hub_compatibility(manifest: LibraryManifest, hub_version: str) -> None: diff --git a/src/silemio_control_hub/manuals/Controller-Studio-for-LiveProfessor-Manual-EN.pdf b/src/silemio_control_hub/manuals/Controller-Studio-for-LiveProfessor-Manual-EN.pdf index 2d6cd4c3e7ef3b1b669446de8c5da874837dc86b..1b75401b9493c49d1b96c67b181427437a874eb4 100644 GIT binary patch delta 12167 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 U82`Bcb%UVkUy8+nKK$i>1Ho72?*IS* delta 11878 zcmai)%M!EPp4Mw?+pS+~_iL%UtkNns1OkB^fe=VSASVc?9LRwXNC<>OLIOEYje~dS zRKHPs6_!2mBD?@EK*!#I+1*-Qdpj@_Dytx*V6Er>Jiqn-ufKNx{eS5GyPv=L`S*VQ z{hxpG^B?^D)1QC#^B?~FM?Zi0`R70X@y~zq^Dln><$wP1AAkF2Re~TGvij>^{r1P; z|9<)F|MA-&>2E#+`%9VE1e` zxMOvISk?ZSc^45Ajz-93h4adx(oM3WOA=RPPBtG)|1dnPN80Te9CND-bIrDCSJF3m zwQ_|QhL+ZCS3x%NC-Fyj?QtNNqX~8K`|aD?U}-?}>!UzD-!h15 zBX6qjr#`*yjYx!86775yEKGn=VZ5pz76Jrg7j72J&qKmEG3#}wXY!a26SB3`M86Pr z-sc-#AIPfb$YU>Rf38i>1GFJYHxBcWi0{vDG?RTfUwrb1y8~VMrL$+rx1m-5gAt() z+s1FtO@6{ns!Nd0P4sQ?sa^9H@Ju;qdqNLX|H2vg8(K#uaN-=P_p*$7+21bi@AL9W z(#390zbITbZxc{a{K%WdXj7>dQ+^w^Eo|~s@4ci`U)%D|qPyv%Ahud%sK!^8Xr9$L zhsJV^>-Gf?t6g-nkNNpvg{NsZZ1UIY&=zwjP%DN(P3bKrM~vSlNHfer&hywt}s8WhSs`GAQ3+b3qX7HJ{uG z8KafzIC|`ob8lBFZKUeRbjt(0uxEO9{y`(>@q61v=M{^^*Vo#L+!pV>=uVR5djalK z^d+}>&o_4_S=D6pb)0;(F%@)Y>}o!qQd`<0AX=t`N_A_o6*qBM_*tEYlU;RGz_d1L zrMNXNUEn8-E-kyz-8V)^FzoMVfu8J}=-jNd*R9aaQ2?5d#thh(PzxDh_4)c-F^G+^ zu3Jz*1LBA^j0)E!1@4oy*r$Q7yP;pchpS4@d{%SS+VV_Pj-!OyM@T?T$1*O|#PiK7 z+2B!0tKqn+R|7!64LQ3dO(uTsVGgVEUK~}H88YU&fu)Vysn8+#-fsGyCSjdb)BM88 zEq8u(DGZ2k!1&LGqI|Bq^Hs_~IX`)>-7s}a{po~6+IZYl37@US&vmhk76RJYMMU!4 zPFGjA1{Y;7^a~kQHn&AyB};4!R8MESj91fneIiADPM6|F&D!@*n_OYC&ZGUs%g&!b z`bZj~|8VyZf0=nPxc!_8VyzK-qKQ%O1|cn!dO>q$Me`Vs@I`-3eV1^R3lO78pk%w5 z>mAc=zHP|-QoKiY!_h8Ot3sU5o&0*FZ#=K$4_Cc@YY!?mJld`X?qfZDg{V%HPuU@@ z)N^(Ptf26fp_%*U-*&=ZA>zkB{q=wT>mTh8`A15WLB|TSmGiCdR7Q4%0A03g+n_#a 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 AQUCw| diff --git a/src/silemio_control_hub/manuals/Controller-Studio-for-LiveProfessor-Notice-FR.pdf b/src/silemio_control_hub/manuals/Controller-Studio-for-LiveProfessor-Notice-FR.pdf index 69c69e935f3347be2872dacec013e009de747f43..bd6029c39fc8a121cb1d7fba98c8443dab71f961 100644 GIT binary patch delta 13022 zcmajFNfUzXyXIHjout$MPCD-*-IYyO9Z&%o6a@hXltDp}NfZZg031LRoVPKHca=q! z`EQc!vrkojge;SNzCiLV+LP0%oO7~>8}%v5p`M59zV6?3|F{2x_&@(o;(zn!Kl$^Y z{`t@T{Ez5~JlG{IXk&?>;Qa~#j$YgCOqp~S&V97oCU-lspCtPM>CbX-f73`L ztzE1SUV=}&G9ffO^SLKZ)16yA8A61hdt$CE)9p4Qzg=ObY@=Vc=+`smSxRu(F+oQ6lkVDXO`0W|sumQX~W~V$Qm(gNMJDIul$^*;( zOc2XavZ=8`N-B8n!93e(3tERP+M{OQ+LTe&rcXjUagmmJL%RD3x4U$0dD0r1V0|7j zhzvF!l{a^tbujcPg$rkf9EpxfUapTewuyPf{*FR8O2u)4wd=Y^)5Uo2G#6)>?YH)+ zSudWUBbt}EK14s3f{ZlNU|Q~PkfRACAK)%MV0+Rz^zpT;kj}O`H4Wsr3}$U|8lH^t zeb6^@wJB*hecOMmeW1g)UAx7e4BdMAt-kt}zmO{uD|jg66ORgo-VA60{O|Xt7fE#n ziCieamnVI6+2deyoJvol-$Y+#)P?8#kcs0#JM?vu?fm{pqoL0BY>x0}7U^GfrI8N=;^i^qB=Ke(dx+CUT{*>#FdQci}##-m1uFhU7NMa`3 z#VDG_n{G=uzs5zxDH{9;E6YM}u~?`!WQffYmLF}8!&t9%u3Ff&S96y4V+Rn2MrCNd zcNw{EK#ls-6JQ>DH%_CP)t!dT%MKiz?l=(Yf6SlT0vSba_xa?976tzGe{25numAeb z{_P)~Z?o~2Zuc**_wyKLe^JACUWmjA6hri+ml@7y(i=l2weni75iArd^K2Vq@3(Q5~gN?@ zQxYOfdTr%U0>g20lrW5r21%(+y7k}Q|n)l*Q_Z4qA>U7Bs(6k`-j8FVj$9^k!1uC zoe27L?2X^+nuL>;hD*(Bc5e15@Z%BFUI$x}{o?tiv^@pR#cO$U$WeXG&WBT9Un{J7 z?CfvaPA61gFL$C%Yd2Ci69o61BqY;(+i%hFgC2nJ&AvUQ_<$|W<2L1;O(kA3JD4fx z0$dKI;XT(xnlXXRU;p>!AOGcF|M}niPISG-{KF%iwVKx(G=fAyu4hnGvEu?7pS;3P zdlg%sRAVkpJ%TGhi%6;>z zVUKhhTz@jr=?~9Cm+f4lp#9CsrY5hp0&}ZY!tgSTvF4E57UQ#W7yRE#aZi)W(oY|FqPa z{l;{r@VMOp2OIVXp;aq~b=+T}A|WFSy156Z1@&&t(SY>U z0s91wVGg3=ExRke{9{# zm6G0SU261#>uHhwc!cMaF8BQ_tck4GG!l17cPMW7{sZ zfUI9&pV?`av>%t|Va|GEGx{l-^%8H;wMfo0sfxYs@MzMJM!fDe!^Y_G;DFQnr8!xr zv_L)nnC*~PSNz>~_3Ovum|X8)n8|#z5VFnr`8lCV%_*05!WAc4r~RjQxW+n?Je_16 z6MBkB=VZQVFACe+@KreV51Vtf_l>&kI{5G>EpoK-QS)+?TM^1?R>qCSeexZb$Iw+-3Op2;{_P6co5%TDPeR(~w**mdos#Od z78p{!f0%a`$DnKD>-R*kB|F0@RKa64DWet9%z-&sy_+vs+vaN9;>_VrYc#rr6Viu$B+ z8>eI1ukX$ekRDp&lC_nfT1BZ-ea5dD7xfq#Ls>D=z$Z)o@06V;ms0I{nAt0fTQ&u@ z?zDS*b3crcF3m_)l*wagLM@-tZrtO|V(=M7%aYRIuLdvYd|!B$zk@Y7ylx_u52J?y zv0zM(6$a$<>=2P-NwvwN>`Hn$^AYQrYCAes6$3*j~~K{1*j+&hc{D zunqk&5~txh+YRKNkl0n=v_rR;hg7v_@6PP=g(q|L$f42fR`L<=tIrRCL;zqCP0h+%(F~*26O1BeP%Vusgd8FtyjIv=h&!-8E!?i!lB4-1T;EH(99z*BouJ zVFJ{SRpuo(7keaRpj{G7k0y3KSXXP(Iw0vV9`znwt{W|iT~(*{-+0*$pBG|{5Btp- zR6I@txFwKtwcQh|^fGdh(T&a@;`8*%A@Fg4HtjpkA;N3Xb`HJuq3~Fd=Vr~o#9B28 zTTQ!VNe(BBjYY3+<>OtO$M)lATv|CBa%iOkkF(;*bK0Q7>$#C->bFyyJ_;K+n>A5< zS=+KvCAsaiC4Fo@r>Vk<_m%_rGskZe=kqX~OK@t26Jgi2F6$Dg6m-OUZ=cr&%fN6= zJm5w-Y*gD=A2u9dPQFf$dWWKt#mWqx9LR36xN_Nftdv;eh%rIkLweYHGpxkyIy?S>vhjY{PJME zsjw|~!<9Fne6KBK7ltQMGhBf%i@0@kmON^8k19rZlT`xFzLL0 zKH0+R5CmunRH_XpAgfJr--5Mu;Adqbo*tRE|?4|EDo*$x|^8rn_2EIPt(029aK57gL74m3Z}T>%i(hLrQXCqW0ZuEvFc*Z z>lN!3WgVj-S1UhgquZ#`tuJTzWGFSNRO=ueTYVzL=H|-3M15T7&#sc0JA*krY36Zl z&|6ytm>FQ`;Bi2AQ_yIg^?Bu`0>_vHAu;KnJ<=L?oBb>!HJIhUNkX^kolP9T`Tm)b zr;)lU8~YP5nyXS^c6-~LFBNYrCSHD`+FcQQ?bc@a+IjY5*P77dRD;mvop?`H4_qCL(nI*h)A3SiVp1a)&=iO=2E4 z-e%znT|}^6{rRrcoqOs>fQ6BRXse!IeU`wFc8Ez^#K)DE-7tlZLFt$f11AuN1C6UmNu~Y%c=5w&*FkmYSWg|A%mwQca0}J5!m?{L8~Nkm#L>u|24~=V$C2IJB(glfU3&=m z8*}^Hwv3e4RS?}yjR;~9n4VZo-pxuUb~KM8ao^KwryQVe1$dJ?Bc+hd8rHKw8l9PPD z8s#DKd5Al@38cJ`@UW%K4oz`0IuAk5pTE|xq7D3=f%(6PKlr}}${+vw8=+9kYE2_A zkrxro*7Q|&lT(DWwaPu>6|eae`Qf4VGzMu_!IgFDF_TkpRcEJpt!wJuKC{Y2WGmtb zEe*5|rll_^y)vsLeJ-0Gv0)kno0*+;{oJejzZ=HCY@%6_D|^i|dl@#rh4S@ruh_t^ z13NF5k41y3Uft70EDq?b7Vft#xnhjbv^{=_)$4r5NM!AyHR|Tw3diR|Z+uz~If7p8 z_pd<>H#v7RSv_fW0zMZjr&Bz2_@kbdV=8qHRRyDmi*?BxH!mEv?^!3sxqm?^3*_8- zowAZ|mj-dLfQ;JF?%E&ko}1ZR1wH`mU$CLNxH~~qE>@@K;vE(1`KOEHKN?=$hvex?Y(PyXjpkh(e{H?6UweQvUj~H3% z1xE}mjW(TSzY3{sc5au-95TO9Pa&G6Fd?oEl}D>PxORU%G+$^G3hK~cahY!)MYrjq?d>F4RhKt715Z@><&N?1Xp*4g zBk2E3?-mGc;Btw4@iG(c)!lVjY!;92IWwJu*x<}>=t|KTtj`PR!iZ)kQ3KSRZoFcy z5cNlbY{tlJFz!yx+Rdv@NM-TERVR24hw2$Zca!d|5paA^fmOsE#hvEmHhX*9FRu=z zGt&>`XxDr1o;Gs1B=^u&(-*(v@9=gcKN>-C`Yr`|FJtZO+SnXh{s&44>HP8ejV;IK z^Bv4CW-aj1-e5PfFU$Ik6MDp&Pu;Zs9F8Xeoe*v&547=|@A4}j>tA<`eGg!;@2ya& zzp3F;Ud87`>ypTQ8BTF~)MB!_EVa2_PaUDVV!3r~HN{WXT6{(l`s~BV(0%xg!(~o@ z_kG{p)J9C_Np-Kw5&%|>0&+D*CDX1MdgTyr$VV=oao(bQ7p5sOh9=C$q^5nX=|R5Pp>YRm!>Na~yfi9n!vQ>Qj)1Wz2=Z%J5gc zgU02td?@87C^oVudbrMHp_&P+7@L2jApvYVA7`zE+bwc4ri&Xqy<{z}V4^hnV(h#b z&pBfPT@KhbR+(n6Tj@pDLAGC3?O++(Y-+zor8`zC_l8*vbQkUt`45;YrG9N7Q>*{| zHZXi=?Z4Ls<|ua8hONidb*E8D;Ry&Z&--J);8gjaIl3owy*YqV;BY&Ye>m1(qB-*8 zDeYm=%*KmHZFDace(p-a*K2i2WUO@2)MjmlNAh~X7w*FHE2Erscw>0Hyr0q{^|~46 z<<-ED>)85rDs8-Q-VQ0s1nQ0L^`&%&w=N(&ooj_>chD#xAFYoSWPZ`H?pNThho-n% z9yjQ||1S=Ky zBN1labvltdz1&B>=8sjSCjV&uQstB%mh^AqygeN3!>m91N>!P%i%FA1Us3T#S~lmg zQfnf^B-SklPlr1wY*{q;WM*yYwO&ozXKG%WUxxi!B6EXsWj=3>EEm0loEX04L+M_b zu+!=OT;MXzai=QfOxmM8yfXqZ4+kF$%-xLdtvl|bdrnu|Z!a3?uQbJU_DARk&f~{4 z0Apgt&wG(%TRm+YB*p8l6BK%NbZT<*6CL2w!dWS>2cvyzYwuEzK3$n!i7X36VHht%P#F$Yc9)3+8spwFnTy>2E8ab?rHzyrRm96a~EN zCRU-?8b664MO-#2fh@9Kp})8vPhc|PN2O8xMy^5|>5ZOK#1uM*R4N~x&h%6}kBxO% zn)M&ZoU4y;qA3m5g=2o_v9j?@33M+YOVXJv3gTqa-{Vr)qh{Ikh&)2E5$-r-$X+`u>F8}JLHk;EF!Jt_Ctd#eODqqS6Y#PrGdE#3mD0% zGD6L6#a#THdQkNvm59?NkV|1*FRrTmw4n}cWu&z3O-Da1=nqFtq~6(VhppC)IL#j5 zb;EgG-x8H)n!cz*3-N@rzU@3q(L%UQEAjoKN2kfGQ5U*o_?<E~@yl?C)`;H5Ziu&6s>^XDS(q7rP zR%Z{Qo5sFRs=uNrht_6Q#aBTq>@L74gAZ;mg=~&9Ob=N<2R@#!FW!({Ru0Q<2{;{3T0@U zlexa~!~VE)FI8l(f|vi+v`@bDKP)t#5gmz|75r^PcsAzkdY3iD9W19M2%#8Be4rR9 z9r>^$^(!T$RXr-r3j63Hr!xDH9z2ZN!JsRiENK4Pv12h+GxxZBU&;ge{+RbSh4s&~ zG_RilTKmvsFDQMHawq?#9b-N|e#eY#Gj5?|lU{U+aOAE_4N0(A?4l8-c< zJ&3|=m9g}kZRdS~hOp6z!3VTj9H-4a*Qyo#L8{7@JnYC)=zZ!8w=k{CbhI09WD{Td zP%BcKafVjz>i4sVp<}sR#bsto)Q^=B7H%ddV+CCMl{VABDNTAmb`PA!X1~dZA%P7w zr!G;2g}5wM{K@ssEY=O7p)_YHM{gH%>0Bca>+YI|v3;2@D{hXMM{H_Qmifcs6AS57 z_X79s{PcXVvR6biEY^4$jByWxdzUE`*T{=0 z9aGdB%rB?i@crWC6NSrQvR1bcsTAJ4PJzvCG)l=5FWINRyMM;jzQgHQLvJeU zLg(TXtCXYV{RT)r8XTr2{^g9Muv`wn5w2xIc#v;ZrYp&$Fs4=*p2qG)LS1fQ*zmPw(&D< zFHIPAwXS!RuShLJ=$h2rhLT5Y#UIpS@-0Q=>(4`&dF$1}iq4IziGDXJJ!(Aevb|;b z0ZvZvt0|u;XhvLrElV35)UK&Iv~AfnX1EV)Q0JG?pmqVTuQ2kUPmxnq!c(s}nVo+#X)feRgFY^okc0E2X)7s(O zL63L^V*<|Vz624!P&B+(6K#o{4xa=-+O?`Z&k?SKGzqPO4I$2e%WKQrjMaT$R`%Yd zZ}x!WCPV5aW^nu4I@iDlpfOxyUhCKc;C`%Sr@~^VwcMF|=0M-;DqpB>PLdMxx$Ov` zA~k+l#8gM=r=lO0;AUuV{F=uu=)upLe_o#EuA0YB{{|QNWFT%*v|uT=3+x_;u0z8T zEcP2Y5+IXD{rnSr@-q+;IowE1japqd_t+B)w(SU2>Wmn(#ht%)vM=!gcg*8+Y#jI< zM^c;7*s8Q7(L(F7_Nf+rBQ`c3`xCi7l}T!;6=*dJmL;{T*E@?}(lAX+Y-*gea(2(| zQ{oy#C;PMS0<>Nd-ph5{5~dMedtFbWIsl2~Qdp>HqoING_x&UQ=g&7@B!y3F!jv0l z@o+Y;o#s5r&K}a3MY9*Wv}VO-_JCtqsA+~*UiS1r-ms?tyIl{g2qpKT=R5AH3ocwBwuMpgH-hVuZqZbEj3XJfap@^0HQiAEEKhu;;%9*)`Z zc5D^AEl4uZ;;>zn)kZO;{aZ~s50!?gHLL;Ok$(tx3iQWN)YG`p0Y8_f?QWshNP}O~ zd=z)(eLxkv&1?yPDqUiC_;ETt@x&apf5&q_Nnm?)@x5wm+wEv;{{5W4d}?sSgB_Oj|c>?~^d-P^=rR&`eOgJQGtJuVLe z_-W1Ja(!Fsd{9d|aCficw3FH5QW6QjfWd}8|M{rHfPU}MRd)A9s8j6^Nchw5rOo;2o3I0> zM9ucwuBIPq!H)e+g`hQeP`g|7%c1m#ecpQlyYXIK*j^#rqKh4$W%t+}RRqQ6E0Qq=)zj9#erQpMZF!V23*;6$jpEeMlGv^Y-p=?&{p*{DAu@>=i9{`%4> zoJPNh9`0(NwcSopGW&HB4tTvz&SLfh@x-#?&x(U!-5LRmpRvY709v3&)fSWP((d1i z0g5qgm>=G(V9&fN>gYFAu)1B_TA@&Th3Su2)&`8osCZ#qm>X39D3eLWc*a{Ch`(D@ z`UxHebcK(28cZ4`L)tgR-Ls{4phr$rbG3{uCdNB7f3&Bn&~K`{&bi<>yb851@~2Sf z()f#W=gq*h!u#P?Wqi=XIFIa{`%U%1<4|1m=leQa@TI8OE-nFJa=EnU_rc>gRkZoe zUZ0=a#-_r(Y1qZd_{D^>QFP|FnnLQ=KJ%Wjj>6AR!g@zXpu@q}vg7S;OGsD~Z`%o!^qTahs;I^nUfiFYgsC<36JPm7Qj1YpZhF51od5l59C2GhENs)+??H5!>trbDjKq z;Oedu)2A0bpsz2&DD~4$MK&LaiLM!v6XciIzrJU}@c7JPF;KcDeR&b}RA0tycGP+5 zN3W2Z4-Yg)1NVa&NaPTcT01pK);akdO40d+D{dOTlRL%sJ_!d^Grt=H5gmxNBo zX;npYPh5LGcq{9z?D}*#v|YoBoKAU{bNx4t74_ds{_8Hx=}Ms{<3UhHAZXm5HzQQA zBouK@?;1-;Dk$kVwOKRmQtkXJQ^0=hsDXnm&0TYgA3`e@Z+-h&8TbQfc3`Xd27ecC z+sk81Uw%UPR0~OYIqap#OI`q}XLQG{_4xC&v-cG3AHM4!cCmcE7_;>2n*P9DCJXq= z9d?HMokU;yHIs!lC&Tg$XvHG_%_h_jyTI6OnSQcDmij#>Pfn3@XMBlm3RKWC4Z@=E0w{GZv^#gYZ3HXB__)4Z;l)L1 z2k?07^vf+OO}svqi!7mKPtv&ih3;d$|FyKXJCTV{{{k2G7r$5G>NA_&FTDjHHcbwP z+pmhNr3rR*7S?08AoL?XIu}EXd?{yxMMA9Uob!BM4Yng*3DzIG0^B2g!$iB{ZC1=( zj^e;nN7#f}4~{ zQ0A5xzYTQ8rL7y^QX*byMS%Y`am8{F(j7^hEAVafx{P^w->XgPcfUhNOJrZ);(BCv z4%3@PJE)I9%R?X}T;WlmZKwyRz6lHEONe_zJDCgLfhWd|0}x0rVI9R{OBD1>+Zk%KeRvo*MIV@HU5VD+sEJJ zfAg2$Q;lpe99jFvYg~pw>14}Vn*of$QgJPQXu*sZG*RCVJIb delta 12734 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