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 @@
-
+
@@ -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
-
+
## 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 @@
-
+
## 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 2d6cd4c..1b75401 100644
Binary files a/src/silemio_control_hub/manuals/Controller-Studio-for-LiveProfessor-Manual-EN.pdf and b/src/silemio_control_hub/manuals/Controller-Studio-for-LiveProfessor-Manual-EN.pdf differ
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 69c69e9..bd6029c 100644
Binary files a/src/silemio_control_hub/manuals/Controller-Studio-for-LiveProfessor-Notice-FR.pdf and b/src/silemio_control_hub/manuals/Controller-Studio-for-LiveProfessor-Notice-FR.pdf differ
diff --git a/tests/test_product_identity.py b/tests/test_product_identity.py
index e48422b..b1eca7d 100644
--- a/tests/test_product_identity.py
+++ b/tests/test_product_identity.py
@@ -15,4 +15,4 @@ def test_current_product_identity_is_liveprofessor_specific():
assert FULL_PRODUCT_NAME == "Controller Studio for LiveProfessor"
assert WINDOWS_PRODUCT_FOLDER == "Controller Studio for LiveProfessor"
assert WINDOWS_PRODUCT_SLUG == "Controller-Studio-for-LiveProfessor"
- assert __version__ == "2026.4"
+ assert __version__ == "2026.5"