From 42d9c1e27bc37a3d1024ceeb556cd53b7c3fffd9 Mon Sep 17 00:00:00 2001 From: karim Date: Tue, 19 May 2026 01:39:07 +0200 Subject: [PATCH] =?UTF-8?q?Overrides=20als=20Satelliten-Fenster=20vom=20Ob?= =?UTF-8?q?erleiste-Gear=20=C3=B6ffnen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OVERRIDES war als gedocktes Panel zu schmal. Jetzt: kein Panel mehr, sondern ein echtes Rhino-Fenster (Eto.Form + WebView, frei verschieb- und resizable), das vom Oberleiste-Gear-Button geoeffnet wird. panel_base.open_satellite_window: - Akzeptiert jetzt optional einen `bridge`-Parameter. Wenn gegeben, wird die Custom-Bridge (z.B. OverridesBridge) statt der einfachen inline SAVE/CANCEL-Bridge benutzt. So koennen vollwertige Panels (mit bidirektionalem Mess-Verkehr) als Satellite-Fenster laufen. overrides_panel.py: - register_and_open entfaellt — Overrides wird nicht mehr als Panel registriert. - Neue Funktion open_as_window(): erstellt OverridesBridge, registriert sie in sticky["overrides_bridge"] und oeffnet als Satellite-Window. Listener werden lazy beim ersten Aufruf installiert (_ensure_listeners_once). oberleiste.py: - OPEN_OVERRIDES_PANEL ruft jetzt overrides_panel.open_as_window() statt RhinoUI.Panels.OpenPanel(). OberleisteApp.jsx: - Settings-Gear (ToolButton mit icon="settings") nach dem Preset- Dropdown im Overrides-Bereich. Click ruft openOverridesPanel() → oeffnet das Satelliten-Fenster. Co-Authored-By: Claude Opus 4.7 --- rhino/oberleiste.py | 7 ++--- rhino/overrides_panel.py | 39 ++++++++++++++++++------ rhino/panel_base.py | 66 +++++++++++++++++++++------------------- src/OberleisteApp.jsx | 5 +++ 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/rhino/oberleiste.py b/rhino/oberleiste.py index 2932d43..c397735 100644 --- a/rhino/oberleiste.py +++ b/rhino/oberleiste.py @@ -915,11 +915,10 @@ class OberleisteBridge(panel_base.BaseBridge): print("[OBERLEISTE] notify overrides:", ex) elif t == "OPEN_OVERRIDES_PANEL": try: - import System - import Rhino.UI as RhinoUI - RhinoUI.Panels.OpenPanel(System.Guid(OVERRIDES_PANEL_GUID_STR)) + import overrides_panel + overrides_panel.open_as_window() except Exception as ex: - print("[OBERLEISTE] OpenPanel Overrides:", ex) + print("[OBERLEISTE] open_as_window Overrides:", ex) # --- Command-Line Integration ----------------------------------- elif t == "RUN_COMMAND": diff --git a/rhino/overrides_panel.py b/rhino/overrides_panel.py index f7c7f0b..5abae85 100644 --- a/rhino/overrides_panel.py +++ b/rhino/overrides_panel.py @@ -211,16 +211,37 @@ class OverridesBridge(panel_base.BaseBridge): self._send_state() -def _bridge_factory(): +def _ensure_listeners_once(): + """Overrides-Listener nur EINMAL global installieren (statt bei jedem + open_as_window).""" + if sc.sticky.get("overrides_listeners_installed"): + return + try: + overrides.install_listeners() + sc.sticky["overrides_listeners_installed"] = True + except Exception as ex: + print("[OVERRIDES] install_listeners:", ex) + + +def open_as_window(): + """Oeffnet OVERRIDES als echtes Rhino-Fenster (Eto.Form + WebView). + Wird vom Oberleiste-Bridge bei OPEN_OVERRIDES_PANEL gerufen. + + Pro Fenster eine eigene OverridesBridge-Instanz. Die letzte Instanz + landet in sticky["overrides_bridge"] — andere Panels (Oberleiste) die + Cross-Updates an Overrides senden, treffen das aktive Fenster.""" + _ensure_listeners_once() b = OverridesBridge() - try: overrides.install_listeners() - except Exception as ex: print("[OVERRIDES] install_listeners:", ex) - # Bridge im sticky ablegen, damit andere Panels (z.B. Oberleiste) sie - # bei Cross-Panel-Updates erreichen koennen. sc.sticky["overrides_bridge"] = b - return b + panel_base.open_satellite_window( + "overrides", + title="OVERRIDES", + size=(760, 580), + bridge=b) -panel_base.register_and_open("overrides", "OVERRIDES", PANEL_GUID_STR, _bridge_factory, - icon_spec=("tune", "#b5621e"), - min_size=(720, 560)) +# OVERRIDES laeuft jetzt als Satelliten-Fenster (geoeffnet vom Oberleiste- +# Gear-Button), nicht mehr als gedocktes Panel. Listener werden lazy beim +# ersten open_as_window installiert. Falls jemand das alte Panel via +# Workspace-Layout noch geoeffnet hat, wird es nicht mehr registriert → +# Rhino zeigt es leer / nicht mehr an. diff --git a/rhino/panel_base.py b/rhino/panel_base.py index 7e21191..93b431b 100644 --- a/rhino/panel_base.py +++ b/rhino/panel_base.py @@ -281,16 +281,20 @@ def attach_webview(panel, bridge, mode): # --- Satelliten-Fenster (echtes Rhino-Fenster mit eingebetteter WebView) ---- def open_satellite_window(mode, params=None, title=None, size=(420, 560), - on_save=None, on_cancel=None): + on_save=None, on_cancel=None, bridge=None): """Oeffnet ein echtes Rhino-Fenster (Eto.Form) mit eingebetteter WebView. Die WebView laedt die React-App mit dem gegebenen `mode` und `params`. - Die React-App sendet via Bridge `SAVE`/`CANCEL`-Messages. Wir rufen - dann die jeweilige Callback-Funktion auf (mit dem Save-Payload) und - schliessen das Fenster. + Zwei Bridge-Modi: + - **Default (kein `bridge`-Arg):** inline SAVE/CANCEL-Bridge. React + sendet SAVE/CANCEL → on_save/on_cancel-Callback → Fenster zu. Fuer + einfache One-Shot-Dialoge (Settings etc.). + - **`bridge` uebergeben:** eine vollwertige BaseBridge-Subklasse (z.B. + OverridesBridge). Das Fenster nutzt die wie ein normales Panel, + mit allen Mess-Typen die der Bridge handlet. Save/Cancel sind dort + nicht standard; Fenster bleibt offen bis User es manuell schliesst. - Returns die Form-Instance (User kann sie speichern um sie spaeter - programmatisch zu schliessen).""" + Returns die Form-Instance.""" form = forms.Form() if title is None: title = mode.replace('_', ' ').title() @@ -303,33 +307,31 @@ def open_satellite_window(mode, params=None, title=None, size=(420, 560), wv = forms.WebView() - # Inline-Bridge fuer Satelliten-Fenster: handle SAVE/CANCEL, schliesse - # bei beiden das Fenster. - class _SatelliteBridge(BaseBridge): - def __init__(self): - BaseBridge.__init__(self, mode) - def handle(self, data): - t = data.get("type", "") - p = data.get("payload") or {} - if t == "READY": - # React liest PANEL_PARAMS direkt vom window-Object — wir - # muessen also nichts mehr aktiv senden. - pass - elif t == "SAVE": - if on_save is not None: - try: on_save(p) - except Exception as ex: - print("[{}] on_save: {}".format(mode.upper(), ex)) - try: form.Close() - except Exception: pass - elif t == "CANCEL": - if on_cancel is not None: - try: on_cancel() + if bridge is None: + # Inline-Bridge fuer einfache Settings-Dialoge: SAVE/CANCEL, schliesse + # bei beiden das Fenster. + class _SatelliteBridge(BaseBridge): + def __init__(self): + BaseBridge.__init__(self, mode) + def handle(self, data): + t = data.get("type", "") + p = data.get("payload") or {} + if t == "READY": + pass + elif t == "SAVE": + if on_save is not None: + try: on_save(p) + except Exception as ex: + print("[{}] on_save: {}".format(mode.upper(), ex)) + try: form.Close() except Exception: pass - try: form.Close() - except Exception: pass - - bridge = _SatelliteBridge() + elif t == "CANCEL": + if on_cancel is not None: + try: on_cancel() + except Exception: pass + try: form.Close() + except Exception: pass + bridge = _SatelliteBridge() bridge.set_webview(wv) def on_title_(s, e): diff --git a/src/OberleisteApp.jsx b/src/OberleisteApp.jsx index 45b05e9..dd8eb22 100644 --- a/src/OberleisteApp.jsx +++ b/src/OberleisteApp.jsx @@ -374,6 +374,11 @@ export default function OberleisteApp() { + {/* Spacer am rechten Rand */}