clean up imports and switch to absolute imports

This commit is contained in:
ange committed 2026-01-11 22:35:42 +01:00
1 parent fb8f4dc1d7
commit dbb2146b2f
27 files changed
+82 -731

No files matched your search

+7 -1
View File
@@ -69,6 +69,10 @@ instance/
# Scrapy stuff:
.scrapy
#vscode settings
.vscode/
# Sphinx documentation
docs/_build/
@@ -174,6 +178,8 @@ cython_debug/
# PyPI configuration file
.pypirc
prompt.md
Powered by Gitea
Version:
1.24.4
@@ -182,4 +188,4 @@ Page:
Template:
3ms
Licenses
API
API
+13 -1
View File
@@ -1,3 +1,15 @@
# synodetik
Modular audiovisual synoptic node editor.
Modular audiovisual synoptic node editor.
## installation
Just git clone the project. Open a terminal in the cloned folder.
``` shell
```
## module development
+2 -7
View File
@@ -4,7 +4,7 @@ Synodetik - Video/Audio synoptic editor.
A node-based visual editor for designing and documenting
signal routing in professional AV environments.
"""
from .core import (
from synodetik.core import (
# Models
FluxCategory, create_flux_category,
Flux, create_flux_type,
@@ -22,11 +22,9 @@ from .core import (
# Events
EventType, Event, EventDispatcher,
get_dispatcher,
)
from .modules import (
from synodetik.modules import (
Module, ModuleInfo, MenuItem, ModuleError,
ModuleManager
)
@@ -53,9 +51,6 @@ __all__ = [
'EventType', 'Event', 'EventDispatcher',
'get_dispatcher',
# Registry
'Registry', 'get_registry',
# Modules
'Module', 'ModuleInfo', 'MenuItem', 'ModuleError',
'ModuleManager',
+7 -12
View File
@@ -15,27 +15,25 @@ Library:
Services:
- ConfigManager: Display and editor settings
- EventDispatcher: Inter-module communication
- Registry: Service locator
"""
# Models
from .model import (
from synodetik.core.model import (
FluxCategory, create_flux_category,
Flux, create_flux_type,
Machine, create_machine
)
from .port import Port, PortDirection, create_input, create_output, create_full_duplex
from .connection import Connection
from .synoptik import Synoptik
from synodetik.core.port import Port, PortDirection, create_input, create_output, create_full_duplex
from synodetik.core.connection import Connection
from synodetik.core.synoptik import Synoptik
# Library
from .library import Library, get_library
from synodetik.core.library import Library, get_library
# Config
from .config import ConfigManager, AppConfig, get_config_manager
from synodetik.core.config import ConfigManager, AppConfig, get_config_manager
# Events
from .events import EventDispatcher, EventType, Event, get_dispatcher
from synodetik.core.events import EventDispatcher, EventType, Event, get_dispatcher
__all__ = [
@@ -54,7 +52,4 @@ __all__ = [
# Events
"EventDispatcher", "EventType", "Event", "get_dispatcher",
# Registry
"Registry", "get_registry",
]
+1 -1
View File
@@ -1,7 +1,7 @@
"""
Event system for decoupled component communication.
"""
from .dispatcher import (
from synodetik.core.events.dispatcher import (
EventType,
Event,
EventHandler,
+1 -1
View File
@@ -7,7 +7,7 @@ All items are stored as JSON files in ~/.synodetik/
from pathlib import Path
from typing import Optional
from .model import Machine, Flux, FluxCategory
from synodetik.core.model import Machine, Flux, FluxCategory
class Library:
+3 -3
View File
@@ -6,9 +6,9 @@ Provides the main domain objects:
- Flux: Signal/connection type definition
- Machine: Equipment with input/output ports
"""
from .flux_category import FluxCategory, create_flux_category
from .flux import Flux, create_flux_type
from .machine import Machine, create_machine
from synodetik.core.model.flux_category import FluxCategory, create_flux_category
from synodetik.core.model.flux import Flux, create_flux_type
from synodetik.core.model.machine import Machine, create_machine
__all__ = [
"FluxCategory", "create_flux_category",
+5 -5
View File
@@ -11,8 +11,8 @@ import uuid
import json
if TYPE_CHECKING:
from ..port import Port
from .flux import Flux
from synodetik.core.port import Port
from synodetik.core.model.flux import Flux
@dataclass
@@ -58,7 +58,7 @@ class Machine:
def add_input(self, name: str, flux_type: "Flux") -> "Port":
"""Add an input port and return it."""
from ..port import create_input
from synodetik.core.port import create_input
port = create_input(name, flux_type, index=len(self.inputs))
port.machine_id = self.id
self.inputs.append(port)
@@ -66,7 +66,7 @@ class Machine:
def add_output(self, name: str, flux_type: "Flux") -> "Port":
"""Add an output port and return it."""
from ..port import create_output
from synodetik.core.port import create_output
port = create_output(name, flux_type, index=len(self.outputs))
port.machine_id = self.id
self.outputs.append(port)
@@ -133,7 +133,7 @@ class Machine:
@classmethod
def from_dict(cls, data: dict) -> "Machine":
"""Deserialize from dictionary."""
from ..port import Port
from synodetik.core.port import Port
machine = cls(
id=data.get("id", str(uuid.uuid4())),
+2 -2
View File
@@ -10,7 +10,7 @@ from typing import Optional, TYPE_CHECKING
import uuid
if TYPE_CHECKING:
from .model.flux import Flux
from synodetik.core.model.flux import Flux
class PortDirection(Enum):
@@ -69,7 +69,7 @@ class Port:
@classmethod
def from_dict(cls, data: dict) -> "Port":
"""Deserialize from dictionary."""
from .model.flux import Flux
from synodetik.core.model.flux import Flux
return cls(
id=data.get("id", str(uuid.uuid4())),
+4 -4
View File
@@ -9,10 +9,10 @@ from typing import Optional
from datetime import datetime
import uuid
from .model.machine import Machine
from .connection import Connection
from .port import Port
from .model.flux import Flux
from synodetik.core.model.machine import Machine
from synodetik.core.connection import Connection
from synodetik.core.port import Port
from synodetik.core.model.flux import Flux
@dataclass
+1 -1
View File
@@ -5,7 +5,7 @@ This module re-exports the event system from core.events
to maintain backward compatibility with imports like:
from synodetik.events import EventType, get_dispatcher
"""
from .core.events import (
from synodetik.core.events import (
EventType,
Event,
EventHandler,
+4 -4
View File
@@ -1,13 +1,13 @@
"""
GUI module - DearPyGui interface components.
"""
from .app import SynoptikApp, create_app
from .themes import (
from synodetik.gui.app import SynoptikApp, create_app
from synodetik.gui.themes import (
DarkTheme, LightTheme, CATEGORY_COLORS,
hex_to_rgba, color_with_opacity
)
from .components import PalettePanel, PropertiesPanel, NodeEditorPanel
from .dialogs import (
from synodetik.gui.components import PalettePanel, PropertiesPanel, NodeEditorPanel
from synodetik.gui.dialogs import (
MachineEditor,
FluxEditor, FluxManager, CategoryEditor
)
+7 -7
View File
@@ -8,19 +8,19 @@ import dearpygui.dearpygui as dpg
from pathlib import Path
from typing import Optional, List
from ..core import (
from synodetik.core import (
Synoptik, Machine,
Library, get_library,
get_config_manager
)
from ..utils import serialization
from ..events import EventType, get_dispatcher
from ..modules import ModuleManager
from synodetik.utils import serialization
from synodetik.events import EventType, get_dispatcher
from synodetik.modules import ModuleManager
from synodetik.modules.base import MenuItem
from .themes import DarkTheme, hex_to_rgba
from .components import PalettePanel, PropertiesPanel, NodeEditorPanel
from .dialogs import MachineEditor, FluxManager, CategoryEditor
from synodetik.gui.themes import DarkTheme, hex_to_rgba
from synodetik.gui.components import PalettePanel, PropertiesPanel, NodeEditorPanel
from synodetik.gui.dialogs import MachineEditor, FluxManager, CategoryEditor
class SynoptikApp:
+3 -3
View File
@@ -1,8 +1,8 @@
"""
GUI components package.
"""
from .palette import PalettePanel
from .properties import PropertiesPanel
from .node_editor import NodeEditorPanel
from synodetik.gui.components.palette import PalettePanel
from synodetik.gui.components.properties import PropertiesPanel
from synodetik.gui.components.node_editor import NodeEditorPanel
__all__ = ['PalettePanel', 'PropertiesPanel', 'NodeEditorPanel']
+2 -2
View File
@@ -7,11 +7,11 @@ connections are drawn as links between ports.
import dearpygui.dearpygui as dpg
from typing import TYPE_CHECKING, Optional, Callable
from ..themes import DarkTheme, hex_to_rgba, color_with_opacity
from synodetik.gui.themes import DarkTheme, hex_to_rgba, color_with_opacity
from synodetik.events import get_dispatcher, EventType
if TYPE_CHECKING:
from ...core import Machine, Synoptik, Connection
from synodetik.core import Machine, Synoptik, Connection
class NodeEditorPanel:
+2 -2
View File
@@ -7,10 +7,10 @@ allowing users to add new machines to the synoptik.
import dearpygui.dearpygui as dpg
from typing import TYPE_CHECKING, Callable, Optional
from ..themes import DarkTheme, hex_to_rgba
from synodetik.gui.themes import DarkTheme, hex_to_rgba
if TYPE_CHECKING:
from ...core import Library
from synodetik.core import Library
class PalettePanel:
+2 -2
View File
@@ -6,10 +6,10 @@ Displays and allows editing of selected machine/connection properties.
import dearpygui.dearpygui as dpg
from typing import TYPE_CHECKING, Optional, Callable
from ..themes import DarkTheme
from synodetik.gui.themes import DarkTheme
if TYPE_CHECKING:
from ...core import Machine, Synoptik
from synodetik.core import Machine, Synoptik
class PropertiesPanel:
+2 -2
View File
@@ -7,8 +7,8 @@ Provides editor dialogs for:
- CategoryEditor: Manage flux categories
- FluxManager: Browse and manage all flux types
"""
from .template_editor import MachineEditor
from .flux_editor import FluxEditor, CategoryEditor, FluxManager
from synodetik.gui.dialogs.template_editor import MachineEditor
from synodetik.gui.dialogs.flux_editor import FluxEditor, CategoryEditor, FluxManager
__all__ = [
'MachineEditor',
+4 -4
View File
@@ -7,10 +7,10 @@ and their categories (video, audio, network, etc.).
import dearpygui.dearpygui as dpg
from typing import TYPE_CHECKING, Optional, Callable
from ..themes import DarkTheme, hex_to_rgba
from synodetik.gui.themes import DarkTheme, hex_to_rgba
if TYPE_CHECKING:
from ...core import Flux, FluxCategory, Library
from synodetik.core import Flux, FluxCategory, Library
class FluxEditor:
@@ -130,7 +130,7 @@ class FluxEditor:
def _on_save(self, sender=None, app_data=None):
"""Save the flux type."""
from ...core import Flux
from synodetik.core import Flux
name = dpg.get_value(f"{self.WINDOW_TAG}_name")
if not name:
@@ -269,7 +269,7 @@ class CategoryEditor:
def _on_add_category(self, sender=None, app_data=None):
"""Add a new category."""
from ...core import FluxCategory
from synodetik.core import FluxCategory
cat_id = dpg.get_value(f"{self.WINDOW_TAG}_new_id")
cat_name = dpg.get_value(f"{self.WINDOW_TAG}_new_name")
+4 -4
View File
@@ -7,10 +7,10 @@ including port configuration and visual properties.
import dearpygui.dearpygui as dpg
from typing import TYPE_CHECKING, Optional, Callable
from ..themes import DarkTheme, hex_to_rgba
from synodetik.gui.themes import DarkTheme, hex_to_rgba
if TYPE_CHECKING:
from ...core import Machine, Library, Port
from synodetik.core import Machine, Library, Port
# Default machine categories
@@ -209,7 +209,7 @@ class MachineEditor:
def _collect_ports(self, direction: str) -> list['Port']:
"""Collect port data from the UI."""
from ...core import create_input, create_output
from synodetik.core import create_input, create_output
ports = []
parent = f"{self.WINDOW_TAG}_{direction}s"
@@ -239,7 +239,7 @@ class MachineEditor:
def _on_save(self, sender=None, app_data=None):
"""Save the machine."""
from ...core import Machine
from synodetik.core import Machine
name = dpg.get_value(f"{self.WINDOW_TAG}_name")
if not name:
-407
View File
@@ -1,407 +0,0 @@
"""
Flux and category editor windows.
Provides UI for managing flux types and categories.
"""
import dearpygui.dearpygui as dpg
from typing import Optional, Callable
from ..core import FluxTemplate, FluxLibrary, get_config_manager, FluxCategoryConfig
from .themes import DarkTheme, hex_to_rgba
class FluxTemplateEditor:
"""Editor window for flux templates."""
def __init__(
self,
flux_library: FluxLibrary,
on_save: Optional[Callable[[FluxTemplate], None]] = None,
template: Optional[FluxTemplate] = None
):
self.flux_library = flux_library
self.config = get_config_manager()
self.on_save = on_save
self.template = template
self._window_tag = f"flux_editor_{id(self)}"
def show(self):
"""Display the editor window."""
if dpg.does_item_exist(self._window_tag):
dpg.focus_item(self._window_tag)
return
is_edit = self.template is not None
title = "Edit Flux" if is_edit else "New Flux Type"
categories = self.config.get_category_names()
category_ids = self.config.get_category_ids()
# Find current category index
current_cat_idx = 0
if is_edit and self.template.category_id in category_ids:
current_cat_idx = category_ids.index(self.template.category_id)
with dpg.window(
label=title,
tag=self._window_tag,
width=400,
height=400,
on_close=self._on_close,
pos=(150, 150)
):
dpg.add_input_text(
label="Name",
tag=f"{self._window_tag}_name",
default_value=self.template.name if is_edit else "",
width=250
)
dpg.add_combo(
label="Category",
tag=f"{self._window_tag}_category",
items=categories,
default_value=categories[current_cat_idx] if categories else "",
width=200
)
# Color picker
default_color = hex_to_rgba(self.template.color if is_edit else "#FFFFFF")
dpg.add_color_edit(
label="Color",
tag=f"{self._window_tag}_color",
default_value=(
default_color[0] / 255.0,
default_color[1] / 255.0,
default_color[2] / 255.0,
1.0
),
no_alpha=True,
width=200
)
dpg.add_input_text(
label="Connector Type",
tag=f"{self._window_tag}_connector",
default_value=self.template.connector_type if is_edit else "",
width=200
)
dpg.add_combo(
label="Signal Type",
tag=f"{self._window_tag}_signal",
items=["digital", "analog", "hybrid"],
default_value=self.template.signal_type if is_edit else "digital",
width=150
)
dpg.add_checkbox(
label="Bidirectional",
tag=f"{self._window_tag}_bidir",
default_value=self.template.bidirectional if is_edit else False
)
dpg.add_input_text(
label="Description",
tag=f"{self._window_tag}_desc",
default_value=self.template.description if is_edit else "",
multiline=True,
height=60,
width=350
)
dpg.add_separator()
with dpg.group(horizontal=True):
dpg.add_button(label="Save", callback=self._on_save, width=100)
dpg.add_button(label="Cancel", callback=self._on_close, width=80)
def _on_save(self, sender=None, app_data=None):
"""Save the flux template."""
name = dpg.get_value(f"{self._window_tag}_name")
if not name:
return
# Get category
cat_name = dpg.get_value(f"{self._window_tag}_category")
category_ids = self.config.get_category_ids()
category_names = self.config.get_category_names()
cat_idx = category_names.index(cat_name) if cat_name in category_names else 0
category_id = category_ids[cat_idx] if category_ids else "data"
# Get color
color_value = dpg.get_value(f"{self._window_tag}_color")
color_hex = (
f"#{int(color_value[0]*255):02x}"
f"{int(color_value[1]*255):02x}"
f"{int(color_value[2]*255):02x}"
)
if self.template:
template = self.template
template.name = name
else:
template = FluxTemplate(name=name, category_id=category_id)
template.category_id = category_id
template.color = color_hex
template.connector_type = dpg.get_value(f"{self._window_tag}_connector")
template.signal_type = dpg.get_value(f"{self._window_tag}_signal")
template.bidirectional = dpg.get_value(f"{self._window_tag}_bidir")
template.description = dpg.get_value(f"{self._window_tag}_desc")
self.flux_library.save_template(template)
if self.on_save:
self.on_save(template)
self._on_close()
def _on_close(self, sender=None, app_data=None):
"""Close the window."""
if dpg.does_item_exist(self._window_tag):
dpg.delete_item(self._window_tag)
class CategoryEditor:
"""Editor window for flux categories."""
def __init__(self, on_save: Optional[Callable[[], None]] = None):
self.config = get_config_manager()
self.on_save = on_save
self._window_tag = "category_editor"
def show(self):
"""Display the editor window."""
if dpg.does_item_exist(self._window_tag):
dpg.focus_item(self._window_tag)
return
with dpg.window(
label="Manage Categories",
tag=self._window_tag,
width=450,
height=400,
on_close=self._on_close,
pos=(200, 100)
):
dpg.add_text("Flux Categories:")
dpg.add_separator()
with dpg.group(tag=f"{self._window_tag}_list"):
self._refresh_list()
dpg.add_separator()
dpg.add_text("New Category:", color=DarkTheme.TEXT_SECONDARY)
with dpg.group(horizontal=True):
dpg.add_input_text(
hint="ID (unique)",
tag=f"{self._window_tag}_new_id",
width=100
)
dpg.add_input_text(
hint="Name",
tag=f"{self._window_tag}_new_name",
width=120
)
dpg.add_color_edit(
tag=f"{self._window_tag}_new_color",
default_value=(0.5, 0.5, 0.5, 1.0),
no_alpha=True,
no_inputs=True,
width=50
)
dpg.add_button(label="Add", callback=self._on_add_category)
def _refresh_list(self):
"""Refresh the category list."""
parent = f"{self._window_tag}_list"
if dpg.does_item_exist(parent):
dpg.delete_item(parent, children_only=True)
for cat in self.config.get_categories():
with dpg.group(horizontal=True, parent=parent):
dpg.add_color_button(
default_value=(
*[c/255 for c in hex_to_rgba(cat.color)[:3]],
1.0
),
width=20, height=20,
no_border=True
)
dpg.add_text(f"{cat.name} ({cat.id})")
dpg.add_button(
label="Delete",
callback=self._on_delete_category,
user_data=cat.id,
width=50
)
def _on_add_category(self, sender=None, app_data=None):
"""Add a new category."""
cat_id = dpg.get_value(f"{self._window_tag}_new_id")
cat_name = dpg.get_value(f"{self._window_tag}_new_name")
if not cat_id or not cat_name:
return
color_value = dpg.get_value(f"{self._window_tag}_new_color")
color_hex = (
f"#{int(color_value[0]*255):02x}"
f"{int(color_value[1]*255):02x}"
f"{int(color_value[2]*255):02x}"
)
category = FluxCategoryConfig(
id=cat_id.lower().replace(" ", "_"),
name=cat_name,
color=color_hex
)
self.config.add_category(category)
self._refresh_list()
# Reset inputs
dpg.set_value(f"{self._window_tag}_new_id", "")
dpg.set_value(f"{self._window_tag}_new_name", "")
if self.on_save:
self.on_save()
def _on_delete_category(self, sender, app_data, user_data):
"""Delete a category."""
self.config.remove_category(user_data)
self._refresh_list()
if self.on_save:
self.on_save()
def _on_close(self, sender=None, app_data=None):
"""Close the window."""
if dpg.does_item_exist(self._window_tag):
dpg.delete_item(self._window_tag)
class FluxManager:
"""Manager window for flux types."""
def __init__(
self,
flux_library: FluxLibrary,
on_change: Optional[Callable[[], None]] = None
):
self.flux_library = flux_library
self.config = get_config_manager()
self.on_change = on_change
self._window_tag = "flux_manager"
def show(self):
"""Display the manager window."""
if dpg.does_item_exist(self._window_tag):
dpg.focus_item(self._window_tag)
return
with dpg.window(
label="Manage Flux Types",
tag=self._window_tag,
width=500,
height=500,
on_close=self._on_close,
pos=(100, 80)
):
with dpg.group(horizontal=True):
dpg.add_button(label="+ New Flux", callback=self._on_new_flux)
dpg.add_button(label="Manage Categories", callback=self._on_manage_categories)
dpg.add_separator()
with dpg.group(tag=f"{self._window_tag}_list"):
self._refresh_list()
def _refresh_list(self):
"""Refresh the flux list."""
parent = f"{self._window_tag}_list"
if dpg.does_item_exist(parent):
dpg.delete_item(parent, children_only=True)
# Group by category
for cat in self.config.get_categories():
flux_list = self.flux_library.get_by_category(cat.id)
if flux_list:
with dpg.tree_node(
label=f"{cat.name} ({len(flux_list)})",
parent=parent,
default_open=True
):
for flux in flux_list:
with dpg.group(horizontal=True):
dpg.add_color_button(
default_value=(
*[c/255 for c in hex_to_rgba(flux.color)[:3]],
1.0
),
width=16, height=16,
no_border=True
)
dpg.add_text(flux.name)
dpg.add_text(
f"[{flux.connector_type}]",
color=DarkTheme.TEXT_SECONDARY
)
dpg.add_button(
label="Edit",
callback=self._on_edit_flux,
user_data=flux.id,
width=50
)
dpg.add_button(
label="X",
callback=self._on_delete_flux,
user_data=flux.id,
width=25
)
def _on_new_flux(self, sender=None, app_data=None):
"""Create a new flux."""
editor = FluxTemplateEditor(
flux_library=self.flux_library,
on_save=self._on_flux_saved
)
editor.show()
def _on_edit_flux(self, sender, app_data, user_data):
"""Edit a flux."""
template = self.flux_library.get_template(user_data)
if template:
editor = FluxTemplateEditor(
flux_library=self.flux_library,
on_save=self._on_flux_saved,
template=template
)
editor.show()
def _on_delete_flux(self, sender, app_data, user_data):
"""Delete a flux."""
self.flux_library.delete_template(user_data)
self._refresh_list()
if self.on_change:
self.on_change()
def _on_flux_saved(self, template: FluxTemplate):
"""Handle flux save."""
self._refresh_list()
if self.on_change:
self.on_change()
def _on_manage_categories(self, sender=None, app_data=None):
"""Open category manager."""
editor = CategoryEditor(on_save=self._refresh_list)
editor.show()
def _on_close(self, sender=None, app_data=None):
"""Close the window."""
if dpg.does_item_exist(self._window_tag):
dpg.delete_item(self._window_tag)
-250
View File
@@ -1,250 +0,0 @@
"""
Fenêtre d'édition de template de machine.
"""
import dearpygui.dearpygui as dpg
from typing import Optional, Callable
from ..core import MachineTemplate, PortTemplate, MachineLibrary, get_config_manager
from .themes import DarkTheme, hex_to_rgba
class MachineTemplateEditor:
"""Éditeur de template de machine dans une fenêtre séparée."""
# Catégories de machines prédéfinies
MACHINE_CATEGORIES = [
"Source", "Affichage", "Mélangeur", "Distribution",
"Audio", "Réseau", "Contrôle", "Enregistrement", "Conversion", "Autre"
]
def __init__(
self,
library: MachineLibrary,
on_save: Optional[Callable[[MachineTemplate], None]] = None,
template: Optional[MachineTemplate] = None
):
self.library = library
self.on_save = on_save
self.template = template
self._input_count = 0
self._output_count = 0
self._window_tag = f"template_editor_{id(self)}"
def show(self):
"""Affiche la fenêtre d'édition."""
if dpg.does_item_exist(self._window_tag):
dpg.focus_item(self._window_tag)
return
is_edit = self.template is not None
title = "Modifier Machine" if is_edit else "Nouvelle Machine"
with dpg.window(
label=title,
tag=self._window_tag,
width=550,
height=650,
on_close=self._on_close,
pos=(100, 50)
):
# Informations générales
with dpg.collapsing_header(label="Informations", default_open=True):
dpg.add_input_text(
label="Nom",
tag=f"{self._window_tag}_name",
default_value=self.template.name if is_edit else "",
width=300
)
dpg.add_combo(
label="Catégorie",
tag=f"{self._window_tag}_category",
items=self.MACHINE_CATEGORIES,
default_value=self.template.category if is_edit else self.MACHINE_CATEGORIES[0],
width=200
)
dpg.add_input_text(
label="Fabricant",
tag=f"{self._window_tag}_manufacturer",
default_value=self.template.manufacturer if is_edit else "",
width=200
)
dpg.add_input_text(
label="Modèle",
tag=f"{self._window_tag}_model",
default_value=self.template.model if is_edit else "",
width=200
)
# Couleur
default_color = hex_to_rgba(self.template.color if is_edit else "#2D2D2D")
default_color_normalized = (
default_color[0],
default_color[1],
default_color[2],
1.0
)
dpg.add_color_edit(
label="Couleur",
tag=f"{self._window_tag}_color",
default_value=default_color_normalized,
no_alpha=True,
width=200
)
dpg.add_input_text(
label="Description",
tag=f"{self._window_tag}_description",
default_value=self.template.description if is_edit else "",
multiline=True,
height=60,
width=450
)
# Ports d'entrée
with dpg.collapsing_header(label="Entrées", default_open=True):
with dpg.group(tag=f"{self._window_tag}_inputs"):
if is_edit:
for port in self.template.inputs:
self._add_port_row("input", port.name, port.flux_type_name)
dpg.add_button(
label="+ Ajouter Entrée",
callback=lambda: self._add_port_row("input")
)
# Ports de sortie
with dpg.collapsing_header(label="Sorties", default_open=True):
with dpg.group(tag=f"{self._window_tag}_outputs"):
if is_edit:
for port in self.template.outputs:
self._add_port_row("output", port.name, port.flux_type_name)
dpg.add_button(
label="+ Ajouter Sortie",
callback=lambda: self._add_port_row("output")
)
dpg.add_separator()
# Boutons d'action
with dpg.group(horizontal=True):
dpg.add_button(
label="Sauvegarder",
callback=self._on_save,
width=120
)
dpg.add_button(
label="Annuler",
callback=self._on_close,
width=100
)
def _add_port_row(
self,
direction: str,
name: str = "",
flux_name: str = ""
):
"""Ajoute une ligne de port."""
parent = f"{self._window_tag}_{direction}s"
if direction == "input":
idx = self._input_count
self._input_count += 1
else:
idx = self._output_count
self._output_count += 1
row_tag = f"{self._window_tag}_{direction}_{idx}"
# Récupère les noms de flux depuis la bibliothèque
flux_names = self.library.get_flux_names()
with dpg.group(horizontal=True, parent=parent, tag=row_tag):
dpg.add_input_text(
hint="Nom du port",
default_value=name,
width=180,
tag=f"{row_tag}_name"
)
dpg.add_combo(
items=flux_names,
default_value=flux_name if flux_name in flux_names else (flux_names[0] if flux_names else ""),
width=140,
tag=f"{row_tag}_flux"
)
dpg.add_button(
label="X",
callback=self._on_remove_port,
user_data=row_tag,
width=30
)
def _on_remove_port(self, sender, app_data, user_data):
"""Supprime une ligne de port."""
row_tag = user_data
if dpg.does_item_exist(row_tag):
dpg.delete_item(row_tag)
def _collect_ports(self, direction: str) -> list[PortTemplate]:
"""Collecte les ports depuis l'interface."""
ports = []
parent = f"{self._window_tag}_{direction}s"
if not dpg.does_item_exist(parent):
return ports
children = dpg.get_item_children(parent, 1)
if not children:
return ports
for child in children:
row_children = dpg.get_item_children(child, 1)
if row_children and len(row_children) >= 2:
name = dpg.get_value(row_children[0])
flux = dpg.get_value(row_children[1])
if name and flux:
ports.append(PortTemplate(name=name, flux_type_name=flux))
return ports
def _on_save(self, sender=None, app_data=None):
"""Sauvegarde le template."""
name = dpg.get_value(f"{self._window_tag}_name")
if not name:
print("Erreur: Le nom est requis")
return
# Récupère la couleur
color_value = dpg.get_value(f"{self._window_tag}_color")
color_hex = f"#{int(color_value[0]*255):02x}{int(color_value[1]*255):02x}{int(color_value[2]*255):02x}"
# Crée ou met à jour le template
if self.template is not None:
template = self.template
template.name = name
else:
template = MachineTemplate(name=name)
template.category = dpg.get_value(f"{self._window_tag}_category")
template.manufacturer = dpg.get_value(f"{self._window_tag}_manufacturer")
template.model = dpg.get_value(f"{self._window_tag}_model")
template.color = color_hex
template.description = dpg.get_value(f"{self._window_tag}_description")
# Ports
template.inputs = self._collect_ports("input")
template.outputs = self._collect_ports("output")
# Sauvegarde dans la bibliothèque
self.library.save_template(template)
# Callback
if self.on_save:
self.on_save(template)
self._on_close()
def _on_close(self, sender=None, app_data=None):
"""Ferme la fenêtre."""
if dpg.does_item_exist(self._window_tag):
dpg.delete_item(self._window_tag)
+2 -2
View File
@@ -20,8 +20,8 @@ Example:
def on_unload(self):
pass
"""
from .base import Module, ModuleInfo, MenuItem, ModuleError
from .manager import ModuleManager
from synodetik.modules.base import Module, ModuleInfo, MenuItem, ModuleError
from synodetik.modules.manager import ModuleManager
__all__ = [
'Module',
+1 -1
View File
@@ -37,7 +37,7 @@ import dearpygui.dearpygui as dpg
from synodetik.core.events import EventDispatcher, EventType, get_dispatcher
if TYPE_CHECKING:
from synodetik.core.gui.app import SynoptikApp
from synodetik.gui.app import SynoptikApp
@dataclass
+1 -1
View File
@@ -10,7 +10,7 @@ import sys
from pathlib import Path
from typing import TYPE_CHECKING, Optional, List
from .base import Module, ModuleInfo, ModuleError, MenuItem
from synodetik.modules.base import Module, ModuleInfo, ModuleError, MenuItem
from synodetik.core.events import get_dispatcher, EventType
if TYPE_CHECKING:
+1 -1
View File
@@ -5,7 +5,7 @@ This module re-exports utilities from core.utils
to maintain backward compatibility with imports like:
from synodetik.utils import serialization
"""
from ..core.utils import (
from synodetik.core.utils import (
save_json, load_json, to_json_string, from_json_string,
save, load
)
+1 -1
View File
@@ -5,7 +5,7 @@ This module re-exports serialization utilities from core.utils.serialization
to maintain backward compatibility with imports like:
from synodetik.utils import serialization
"""
from ..core.utils.serialization import (
from synodetik.core.utils.serialization import (
save_json, load_json, to_json_string, from_json_string,
save, load
)