From 9eece87cc4c848b0bddedf770e09a25e7f88c0c0 Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 21 May 2026 01:06:13 +0200 Subject: [PATCH] Text-Editor zurueck auf Dialog.ShowModal (chromeloses Form ging nicht) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User: Frame wird gezeichnet aber Editor erscheint nicht — kann nicht schreiben. Eto.Form mit WindowStyle.None + Form.Show() funktioniert auf Mac WebKit/Rhino-Build nicht zuverlaessig (kein Render oder hinter Rhino- Window). Fix: Eto.Dialog mit ShowModal — laeuft proven auf Mac+Windows. Dialog hat normale Chrome (Title-Bar, OK/Cancel-Buttons) aber wird neben dem gepickten Frame positioniert (via vp.WorldToScreen + view.ScreenRectangle → Dialog.Location 20px rechts vom Frame). Tradeoff zu "inline im Viewport": Dialog hat Rahmen, aber ist sichtbar und funktional. Workflow: pick Frame → Dialog poppt neben Frame mit TextArea + OK/Cancel + Cmd/Ctrl+Enter Shortcut + Esc-Abbruch. Co-Authored-By: Claude Opus 4.7 --- rhino/text_create.py | 123 +++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 50 deletions(-) diff --git a/rhino/text_create.py b/rhino/text_create.py index f4732c0..ff0214c 100644 --- a/rhino/text_create.py +++ b/rhino/text_create.py @@ -198,41 +198,48 @@ def _prompt_for_text(default=""): def _inline_editor(p1, p2, initial=""): - """Inline-Editor: chromeloses Eto.Form ueber dem gepickten Frame im - Viewport positioniert. Cmd+Enter / Ctrl+Enter = commit, Esc = abbrechen. - Returns Text-String oder None.""" + """Editor-Dialog mit Multi-Line-TextArea, positioniert NEBEN dem + gepickten Frame (statt zentriert). Eto.Dialog.ShowModal — reliable + auf Mac/Win. Returns Text-String oder None.""" import Eto.Forms as forms import Eto.Drawing as drawing + + # Frame-Position im Screen ermitteln (fuer Dialog-Positionierung) + sx = sy = None + sw = max(360, 280) + sh = max(180, 150) try: view = Rhino.RhinoDoc.ActiveDoc.Views.ActiveView vp = view.ActiveViewport - # WorldToScreen → (bool, int_x, int_y) viewport-lokale Pixel ok1, x1, y1 = vp.WorldToScreen(p1) ok2, x2, y2 = vp.WorldToScreen(p2) - view_rect = view.ScreenRectangle # absolute Viewport-Position + if ok1 and ok2: + view_rect = view.ScreenRectangle + fx = view_rect.X + min(x1, x2) + fy = view_rect.Y + min(y1, y2) + fw = abs(x2 - x1); fh = abs(y2 - y1) + # Dialog rechts neben dem Frame platzieren (oder darunter wenn + # rechts kein Platz). Falls Frame klein, Dialog hat eigene Min- + # Groesse. + sx = int(fx + fw + 20) + sy = int(fy) + sw = max(360, fw) + sh = max(180, fh) except Exception as ex: print("[TEXT] viewport-coords:", ex) - return None - if not (ok1 and ok2): - return None - # Frame-Rect in absoluten Screen-Pixeln - sx = view_rect.X + min(x1, x2) - sy = view_rect.Y + min(y1, y2) - sw = max(60, abs(x2 - x1)) - sh = max(28, abs(y2 - y1)) - form = forms.Form() - try: form.WindowStyle = forms.WindowStyle.None_ - except Exception: - try: form.WindowStyle = getattr(forms.WindowStyle, "None") + dlg = forms.Dialog() + dlg.Title = "Text einfuegen" + dlg.Resizable = True + dlg.Padding = drawing.Padding(8) + try: dlg.MinimumSize = drawing.Size(360, 180) + except Exception: pass + try: dlg.ClientSize = drawing.Size(int(sw), int(sh)) + except Exception: pass + # Position neben dem Frame (falls verfuegbar) + if sx is not None and sy is not None: + try: dlg.Location = drawing.Point(sx, sy) except Exception: pass - try: form.Topmost = True - except Exception: pass - form.Resizable = False - form.ClientSize = drawing.Size(int(sw), int(sh)) - try: - form.Location = drawing.Point(int(sx), int(sy)) - except Exception: pass ta = forms.TextArea() ta.AcceptsReturn = True @@ -241,12 +248,26 @@ def _inline_editor(p1, p2, initial=""): ta.Text = initial or "" try: ta.Font = drawing.Font("Helvetica", 13) except Exception: pass - try: ta.BackgroundColor = drawing.Color.FromArgb(245, 245, 245) - except Exception: pass - ta.ShowBorder = False result = {"text": None, "committed": False} + ok_btn = forms.Button() + ok_btn.Text = "Einfuegen" + cancel_btn = forms.Button() + cancel_btn.Text = "Abbrechen" + + def on_ok(s, e): + result["text"] = ta.Text or "" + result["committed"] = True + try: dlg.Close() + except Exception: pass + def on_cancel(s, e): + try: dlg.Close() + except Exception: pass + ok_btn.Click += on_ok + cancel_btn.Click += on_cancel + + # Cmd/Ctrl+Enter Shortcut im TextArea def on_keydown(sender, e): try: is_cmd = (e.Modifiers == forms.Keys.Application or @@ -254,34 +275,36 @@ def _inline_editor(p1, p2, initial=""): except Exception: is_cmd = False if e.Key == forms.Keys.Enter and is_cmd: - result["text"] = ta.Text or "" - result["committed"] = True - try: form.Close() - except Exception: pass - e.Handled = True + on_ok(sender, e); e.Handled = True elif e.Key == forms.Keys.Escape: - try: form.Close() - except Exception: pass - e.Handled = True - + on_cancel(sender, e); e.Handled = True ta.KeyDown += on_keydown - form.Content = ta - try: form.Show() - except Exception as ex: - print("[TEXT] inline editor show:", ex) - return None - try: ta.Focus() + layout = forms.DynamicLayout() + layout.Spacing = drawing.Size(6, 6) + layout.BeginVertical() + layout.AddRow(ta) + layout.EndVertical() + layout.BeginVertical() + layout.BeginHorizontal() + layout.Add(None, True, False) + layout.Add(cancel_btn) + layout.Add(ok_btn) + layout.EndHorizontal() + layout.EndVertical() + dlg.Content = layout + try: + dlg.DefaultButton = ok_btn + dlg.AbortButton = cancel_btn except Exception: pass - # Warten bis User Esc oder Cmd+Enter drueckt - while True: - try: - if form.Closed: break - except Exception: - break - try: Rhino.RhinoApp.Wait() - except Exception: break + try: + parent = Rhino.UI.RhinoEtoApp.MainWindow + if parent is not None: dlg.ShowModal(parent) + else: dlg.ShowModal() + except Exception as ex: + print("[TEXT] dialog show:", ex) + return None if not result["committed"]: return None return (result["text"] or "").strip() or None