From 0c1a049b2dcbf91fa20e547beb79d076a6dcfc5a Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Mon, 20 Oct 2025 15:34:57 +0100 Subject: [PATCH 01/12] Implement opening displays as standalone windows from an action Standalone windows are configured to have no tabs or toolbar showing. Panes are configured not to change with the global settings to show/hide toolbar/tabs. --- .../display/actions/OpenDisplayAction.java | 8 ++----- .../actions/OpenDisplayActionController.java | 13 +++++------- .../display/actions/OpenDisplayAction.fxml | 6 +++++- .../display/builder/model/DisplayModel.java | 11 ++++++++++ .../runtime/app/DisplayRuntimeInstance.java | 2 +- .../runtime/app/DockItemRepresentation.java | 10 ++++++++- .../OpenDisplayActionHandler.java | 1 + .../java/org/phoebus/ui/docking/DockPane.java | 15 ++++++++++++- .../org/phoebus/ui/docking/DockStage.java | 21 ++++++++++++++++++- 9 files changed, 68 insertions(+), 19 deletions(-) diff --git a/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDisplayAction.java b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDisplayAction.java index c6a2f6689a..6f2fa8181b 100644 --- a/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDisplayAction.java +++ b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDisplayAction.java @@ -63,10 +63,7 @@ public enum Target { /** * Open standalone window - * - * @deprecated Was only used in RCP version. */ - @Deprecated STANDALONE(Messages.Target_Standalone); private final String name; @@ -219,8 +216,6 @@ public List getContextMenuItems(ExecutorService executorService, Widge // Add variant for all the available Target types: Replace, new Tab, ... for (OpenDisplayAction.Target target : OpenDisplayAction.Target.values()) { - if (target == OpenDisplayAction.Target.STANDALONE || target == this.target) - continue; // Mention non-default targets in the description MenuItem additionalItem = createMenuItem(widget, description + " (" + target + ")"); OpenDisplayAction openDisplayAction = new OpenDisplayAction(description, file, macros, target); @@ -237,7 +232,8 @@ private OpenDisplayAction.Target modeToTargetConvert(int mode) { case 0 -> Target.REPLACE; // 7 - NEW_WINDOW // 8 - NEW_SHELL - case 7, 8 -> Target.WINDOW; + case 7 -> Target.WINDOW; + case 8 -> Target.STANDALONE; // 1 - NEW_TAB // 2 - NEW_TAB_LEFT // 3 - NEW_TAB_RIGHT diff --git a/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDisplayActionController.java b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDisplayActionController.java index 677636ee2f..f238a4c877 100644 --- a/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDisplayActionController.java +++ b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDisplayActionController.java @@ -37,6 +37,9 @@ public class OpenDisplayActionController extends ActionControllerBase { private RadioButton newWindowRadioButton; @SuppressWarnings("unused") @FXML + private RadioButton newStandaloneRadioButton; + @SuppressWarnings("unused") + @FXML private TextField displayPath; @SuppressWarnings("unused") @FXML @@ -79,16 +82,10 @@ public void initialize() { replaceRadioButton.setUserData(OpenDisplayAction.Target.REPLACE); newTabRadioButton.setUserData(OpenDisplayAction.Target.TAB); newWindowRadioButton.setUserData(OpenDisplayAction.Target.WINDOW); + newStandaloneRadioButton.setUserData(OpenDisplayAction.Target.STANDALONE); ToggleGroup toggleGroup = new ToggleGroup(); - toggleGroup.getToggles().addAll(replaceRadioButton, newTabRadioButton, newWindowRadioButton); - - /* - * Standalone is a deprecated name for Window - */ - if (target == OpenDisplayAction.Target.STANDALONE) { - target = OpenDisplayAction.Target.WINDOW; - } + toggleGroup.getToggles().addAll(replaceRadioButton, newTabRadioButton, newWindowRadioButton, newStandaloneRadioButton); toggleGroup.selectToggle(toggleGroup.getToggles().stream() .filter(t -> t.getUserData().equals(target)).findFirst().get()); diff --git a/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDisplayAction.fxml b/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDisplayAction.fxml index bd57c980e6..94535ae183 100644 --- a/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDisplayAction.fxml +++ b/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDisplayAction.fxml @@ -38,7 +38,11 @@ - + + + + + diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/DisplayModel.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/DisplayModel.java index 28804b996d..4a782a5c8c 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/DisplayModel.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/DisplayModel.java @@ -144,6 +144,7 @@ public boolean configureFromXML(final ModelReader model_reader, final Widget wid private volatile WidgetProperty gridStepX; private volatile WidgetProperty gridStepY; private volatile ChildrenProperty children; + private boolean standalone; /** Create display model */ public DisplayModel() @@ -156,6 +157,16 @@ public Version getVersion() { return VERSION; } + + public boolean isStandAlone() + { + return standalone; + } + + public void setStandAlone(boolean standalone) + { + this.standalone = standalone; + } /** Get display name * diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java index ef64dd730e..49e16e4f72 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java @@ -148,7 +148,7 @@ public static DisplayRuntimeInstance ofDisplayModel(final DisplayModel model) new ContextMenuSupport(this); - if (last_toolbar_visible) + if (last_toolbar_visible && !dock_pane.isStandAloneWindow()) layout.setTop(toolbar); layout.setCenter(representation.createModelRoot()); diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java index 5a13862a88..2ef916e582 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java @@ -20,6 +20,7 @@ import org.phoebus.ui.docking.DockItemWithInput; import org.phoebus.ui.docking.DockPane; import org.phoebus.ui.docking.DockStage; +import org.phoebus.ui.docking.Geometry; import javafx.scene.Node; import javafx.scene.Parent; @@ -59,7 +60,7 @@ public ToolkitRepresentation openNewWindow(final DisplayModel mode final Stage new_stage = new Stage(); // Configure for docking, i.e. with DockPane - DockStage.configureStage(new_stage); + DockStage.configureStage(new_stage, new Geometry(null), model.isStandAlone()); // Use location and size from model for the window double x = model.propX().getValue(); @@ -92,6 +93,13 @@ public ToolkitRepresentation openNewWindow(final DisplayModel mode // model will be opened in it. return representModelInNewDockItem(model); } + + @Override + public ToolkitRepresentation openStandaloneWindow(final DisplayModel model, + final Consumer close_handler) + { + return openNewWindow(model, close_handler); + } @Override public ToolkitRepresentation openPanel(final DisplayModel model, diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java index 7219bbc705..91a983c114 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java @@ -88,6 +88,7 @@ else if (openDisplayActionInfo.getTarget().equals(OpenDisplayAction.Target.WINDO } else if (openDisplayActionInfo.getTarget().equals(OpenDisplayAction.Target.STANDALONE)) { + new_model.setStandAlone(true); toolkit.submit(() -> { final ToolkitRepresentation new_toolkit = diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java index 0338c9dd55..b5640884a7 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java @@ -199,6 +199,9 @@ public static void alwaysShowTabs(final boolean do_show_single_tabs) /** Is this dock pane 'fixed' ? */ private boolean fixed = false; + + /** Is this a standalone window with no tabs/toolbar etc */ + private boolean standalone = false; /** Drop zone last seen under the mouse — used only to skip redundant border redraws in handleDragOver */ private DropZone active_drop_zone = DropZone.CENTER; @@ -269,6 +272,16 @@ public static void alwaysShowTabs(final boolean do_show_single_tabs) protected LinkedList tabsInOrderOfFocus = new LinkedList<>(); + public void setAsStandAloneWindow(boolean standalone) + { + this.standalone = standalone; + } + + public boolean isStandAloneWindow() + { + return standalone; + } + private void showContextMenu(final ContextMenuEvent event) { final ContextMenu menu = new ContextMenu(); @@ -530,7 +543,7 @@ private void autoHideTabs() private void doAutoHideTabs(final Scene scene) { - final boolean do_hide = getTabs().size() == 1 && !always_show_tabs; + final boolean do_hide = (getTabs().size() == 1 && !always_show_tabs) || isStandAloneWindow(); // Hack from https://www.snip2code.com/Snippet/300911/A-trick-to-hide-the-tab-area-in-a-JavaFX : // Locate the header's pane and set height to zero diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java index a88fe69c68..e8c2951f8a 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java @@ -116,6 +116,21 @@ public static DockPane configureStage(final Stage stage, final DockItem... tabs) { return configureStage(stage, new Geometry(null), tabs); } + + /** Helper to configure a Stage for docking + * + *

Adds a Scene with a BorderPane layout and a DockPane in the center + * + * @param stage Stage, should be empty + * @param geometry A geometry specification "{width}x{height}+{x}+{y}", see {@link Geometry} + * @param tabs Zero or more initial {@link DockItem}s + * + * @return {@link DockPane} that was added to the {@link Stage} + */ + public static DockPane configureStage(final Stage stage, final Geometry geometry, final DockItem... tabs) + { + return configureStage(stage, geometry, false, tabs); + } /** Helper to configure a Stage for docking * @@ -123,11 +138,12 @@ public static DockPane configureStage(final Stage stage, final DockItem... tabs) * * @param stage Stage, should be empty * @param geometry A geometry specification "{width}x{height}+{x}+{y}", see {@link Geometry} + * @param standalone Should this be configured as a standalone window * @param tabs Zero or more initial {@link DockItem}s * * @return {@link DockPane} that was added to the {@link Stage} */ - public static DockPane configureStage(final Stage stage, final Geometry geometry, final DockItem... tabs) + public static DockPane configureStage(final Stage stage, final Geometry geometry, boolean standalone, final DockItem... tabs) { stage.addEventFilter(MouseEvent.MOUSE_MOVED, mouseEvent -> { // Filtering MOUSE_MOVED events from unfocused windows prevents tooltips @@ -222,6 +238,9 @@ else if(layout.getChildren().get(0) instanceof SplitPane){ } }); + if (standalone) + pane.setAsStandAloneWindow(true); + return pane; } From 9f31f02eeb276e3ab58e5465c515a6de4bfcacdb Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Mon, 20 Oct 2025 17:35:38 +0100 Subject: [PATCH 02/12] Configure standalone window in memento --- .../builder/runtime/app/DisplayRuntimeInstance.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java index 49e16e4f72..40d2758e75 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java @@ -68,6 +68,7 @@ public class DisplayRuntimeInstance implements AppInstance /** Memento tags */ private static final String TAG_ZOOM = "ZOOM"; private static final String TAG_TOOLBAR = "toolbar"; + private static final String TAG_STANDALONE = "standalone"; /** Global tracker of last user's decision to show toolbar. * Used when opening new display @@ -262,6 +263,10 @@ public void restore(final Memento memento) }); }); memento.getBoolean(TAG_TOOLBAR).ifPresent(this::showToolbar); + memento.getBoolean(TAG_STANDALONE).ifPresent(standalone -> + { + dock_item.getDockPane().setAsStandAloneWindow(standalone); + }); } @Override @@ -271,6 +276,8 @@ public void save(final Memento memento) if (! JFXRepresentation.DEFAULT_ZOOM_LEVEL.equals(zoom)) memento.setString(TAG_ZOOM, zoom); memento.setBoolean(TAG_TOOLBAR, isToolbarVisible()); + if (dock_item.getDockPane().isStandAloneWindow()) + memento.setBoolean(TAG_STANDALONE, dock_item.getDockPane().isStandAloneWindow()); } /** Handle Alt-left & right as navigation keys */ From 8ca337706ea931c4408631e95419a06732d39872 Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Mon, 20 Oct 2025 20:42:04 +0100 Subject: [PATCH 03/12] Add ability to launch displays as standalone windows from command line and also when including the window size and layout. --- .../builder/runtime/app/DisplayRuntimeInstance.java | 11 ++++++++--- .../phoebus/ui/application/PhoebusApplication.java | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java index 40d2758e75..60a26ba3d8 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java @@ -125,14 +125,19 @@ public static DisplayRuntimeInstance ofDisplayModel(final DisplayModel model) DockPane dock_pane = null; if (prefTarget != null) { - if (prefTarget.startsWith("window")) + if (prefTarget.startsWith("window") || prefTarget.startsWith("standalone")) { + boolean standalone = false; + if (prefTarget.startsWith("standalone")) + standalone = true; // Open new Stage in which this app will be opened, its DockPane is a new active one final Stage new_stage = new Stage(); if (prefTarget.startsWith("window@")) - DockStage.configureStage(new_stage, new Geometry(prefTarget.substring(7))); + DockStage.configureStage(new_stage, new Geometry(prefTarget.substring(7)), standalone); + else if (prefTarget.startsWith("standalone@")) + DockStage.configureStage(new_stage, new Geometry(prefTarget.substring(11)), standalone); else - DockStage.configureStage(new_stage); + DockStage.configureStage(new_stage, new Geometry(null), standalone); new_stage.show(); } else diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index c9333d91d9..5c298d18a4 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -1173,7 +1173,7 @@ private void openResource(final URI resource, final boolean prompt) { if (end < 0) end = query.length(); final String target = query.substring(i + 7, end); - if (!target.startsWith("window")) { + if (!target.startsWith("window") && !target.startsWith("standalone")) { // Should the new panel open in a specific, named pane? final DockPane existing = DockStage.getDockPaneByName(target); if (existing != null) From 21291e4339e77f5f12bab423f5b94a8be43b17fa Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Thu, 19 Mar 2026 14:19:20 +0000 Subject: [PATCH 04/12] Remove show/hide toolbar context menu item in standalone windows --- .../display/builder/runtime/app/ContextMenuSupport.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java index b2e2653560..9d2f9cc47b 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java @@ -243,7 +243,9 @@ private void fillMenu(Runnable setFocus, final Widget widget) { items.add(new SeparatorMenuItem()); - items.add(new DisplayToolbarAction(instance)); + // Do not add the show/hide toolbar context menu item in a standalone window + if (!instance.getDockItem().getDockPane().isStandAloneWindow()) + items.add(new DisplayToolbarAction(instance)); // If the editor is available, add "Open in Editor" final AppResourceDescriptor editor = ApplicationService.findApplication("display_editor"); From 0f076f6e91aff5824d9bc4b8fc919cfd0672243c Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Wed, 29 Oct 2025 11:31:31 +0000 Subject: [PATCH 05/12] Implement opening applications in the main Phoebus window... when launching from a standalone window. As opposed to opening in the last active window. --- .../runtime/app/ContextMenuSupport.java | 8 +++++++- .../actionhandlers/OpenFileActionHandler.java | 8 ++++++++ .../java/org/phoebus/ui/docking/DockPane.java | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java index 9d2f9cc47b..4ae4023268 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java @@ -91,7 +91,13 @@ public void handleContextMenu(final Widget widget, final int screen_x, final int DisplayRuntimeInstance displayRuntimeInstance = DisplayRuntimeInstance.ofDisplayModel(displayModel); DockItem dockItem = displayRuntimeInstance.getDockItem(); DockPane dockPane = dockItem.getDockPane(); - setFocus = () -> DockPane.setActiveDockPane(dockPane); + if (dockPane.isStandAloneWindow()) { + // If in a standalone window, set the active dock pane to be the 'main' + // Phoebus pain instead of the current dock pane + setFocus = () -> DockPane.setActiveDockPane(DockPane.getMainDockPain()); + } else { + setFocus = () -> DockPane.setActiveDockPane(dockPane); + } } fillMenu(setFocus, widget); diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenFileActionHandler.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenFileActionHandler.java index 54431a7f20..6343f028db 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenFileActionHandler.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenFileActionHandler.java @@ -9,7 +9,9 @@ import org.csstudio.display.builder.model.spi.ActionInfo; import org.csstudio.display.builder.representation.ToolkitRepresentation; import org.csstudio.display.builder.runtime.ActionUtil; +import org.csstudio.display.builder.runtime.app.DisplayRuntimeInstance; import org.csstudio.display.builder.runtime.script.ScriptUtil; +import org.phoebus.ui.docking.DockPane; import org.csstudio.display.builder.model.spi.ActionHandler; import org.csstudio.display.actions.OpenFileAction; @@ -36,6 +38,12 @@ public void handleAction(Widget sourceWidget, ActionInfo pluggableActionInfo) { final ToolkitRepresentation toolkit = ToolkitRepresentation.getToolkit(top_model); toolkit.execute(() -> { + DockPane dockPane = DisplayRuntimeInstance.ofDisplayModel(top_model).getDockItem().getDockPane(); + if (dockPane.isStandAloneWindow()) { + // Open the file in the main Phoebus window if launched from + // a standalone screen. + DockPane.setActiveDockPane(DockPane.getMainDockPain()); + } try { toolkit.openFile(resolved_name); diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java index b5640884a7..2664b8cd3f 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java @@ -142,6 +142,24 @@ public static DockPane getActiveDockPane() return pane; } + /** + * @return The 'main' Phoebus dock pane + */ + public static DockPane getMainDockPain() { + for (Stage stage : DockStage.getDockStages()) + { + if (stage.getProperties().get(DockStage.KEY_ID).equals(DockStage.ID_MAIN)) { + for (DockPane check : DockStage.getDockPanes(stage)) + if (isDockPaneUsable(check)) + { + setActiveDockPane(check); + return check; + } + } + } + return getActiveDockPane(); + } + /** Set the 'active' dock pane * *

Called within the phoebus framework, From 442297e084e965a5ab01834777ad6ebb79124f89 Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Wed, 29 Oct 2025 14:49:19 +0000 Subject: [PATCH 06/12] Implement the ability to open the editor in the 'main' window Allows the active pane to be set to either the current window or main Phoebus window before opening the editor. --- .../display/builder/runtime/app/ContextMenuSupport.java | 2 +- .../display/builder/runtime/app/OpenInEditorAction.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java index 4ae4023268..4b835edf20 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java @@ -256,7 +256,7 @@ private void fillMenu(Runnable setFocus, final Widget widget) { // If the editor is available, add "Open in Editor" final AppResourceDescriptor editor = ApplicationService.findApplication("display_editor"); if (editor != null && AuthorizationService.hasAuthorization("edit_display")) - items.add(new OpenInEditorAction(editor, widget)); + items.add(new OpenInEditorAction(editor, widget, setFocus)); items.add(new SeparatorMenuItem()); diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/OpenInEditorAction.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/OpenInEditorAction.java index 2464895ded..d9595441c4 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/OpenInEditorAction.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/OpenInEditorAction.java @@ -25,9 +25,11 @@ public class OpenInEditorAction extends WeakRefWidgetAction { /** @param editor Editor to use * @param the_widget Widget + * @param setFocus set the focus to the dock pane */ public OpenInEditorAction(final AppResourceDescriptor editor, - final Widget the_widget) + final Widget the_widget, + Runnable setFocus) { super(Messages.OpenInEditor, ImageCache.getImageView(DisplayModel.class, "/icons/display.png"), @@ -35,6 +37,7 @@ public OpenInEditorAction(final AppResourceDescriptor editor, setOnAction(event -> { + setFocus.run(); try { final Widget widget = getWidget(); From 77071ce131d286da99ec41c227b8eeda9bc77fc4 Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Tue, 4 Nov 2025 12:16:36 +0000 Subject: [PATCH 07/12] Set focus on the main window if application is opened there from a context menu action Only context menu items that open new displays should set the focus. Other items such as copy PV will not. --- .../ui/actions/ContextMenuPVAlarmHistory.java | 5 +++++ .../ContextMenuDataBrowserLauncher.java | 6 +++++ .../app/NewDisplayContextMenuEntry.java | 6 +++++ .../runtime/app/ContextMenuSupport.java | 22 +++++++++++++++---- .../app/ProbeDisplayContextMenuEntry.java | 6 +++++ .../probe/ContextLaunchProbe.java | 5 +++++ .../pvtable/ContextMenuPVTableLauncher.java | 6 +++++ .../pvtree/ContextMenuPVTreeLauncher.java | 6 +++++ .../ui/application/ContextMenuHelper.java | 3 ++- .../java/org/phoebus/ui/docking/DockPane.java | 14 ++++++++++++ .../org/phoebus/ui/spi/ContextMenuEntry.java | 8 +++++++ 11 files changed, 82 insertions(+), 5 deletions(-) diff --git a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java index 0c43b460f2..e930765b61 100644 --- a/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java +++ b/app/alarm/logging-ui/src/main/java/org/phoebus/applications/alarm/logging/ui/actions/ContextMenuPVAlarmHistory.java @@ -54,4 +54,9 @@ public Class getSupportedType() { public Image getIcon() { return AlarmLogTableApp.icon; } + + @Override + public boolean isOpenAction() { + return true; + } } diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ContextMenuDataBrowserLauncher.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ContextMenuDataBrowserLauncher.java index 7c87534708..c673331747 100644 --- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ContextMenuDataBrowserLauncher.java +++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ContextMenuDataBrowserLauncher.java @@ -52,6 +52,12 @@ public Class getSupportedType() return supportedType; } + @Override + public boolean isOpenAction() + { + return true; + } + private volatile Duration time_span = Preferences.time_span; @Override diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/app/NewDisplayContextMenuEntry.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/app/NewDisplayContextMenuEntry.java index d39eb9772c..378311c7d1 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/app/NewDisplayContextMenuEntry.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/app/NewDisplayContextMenuEntry.java @@ -63,6 +63,12 @@ public Image getIcon() return ImageCache.getImage(DisplayModel.class, "/icons/display.png"); } + @Override + public boolean isOpenAction() + { + return true; + } + @Override public void call(Selection selection){ Optional file = diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java index 4b835edf20..2ef8b29ef4 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java @@ -18,6 +18,8 @@ import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.image.ImageView; +import javafx.stage.Stage; + import org.csstudio.display.builder.model.DisplayModel; import org.csstudio.display.builder.model.Widget; import org.csstudio.display.builder.model.WidgetProperty; @@ -92,11 +94,23 @@ public void handleContextMenu(final Widget widget, final int screen_x, final int DockItem dockItem = displayRuntimeInstance.getDockItem(); DockPane dockPane = dockItem.getDockPane(); if (dockPane.isStandAloneWindow()) { - // If in a standalone window, set the active dock pane to be the 'main' - // Phoebus pain instead of the current dock pane - setFocus = () -> DockPane.setActiveDockPane(DockPane.getMainDockPain()); + // If in a standalone window or defined in the preference, set the active dock pane + // to be the 'main' Phoebus pain instead of the current dock pane + setFocus = () -> { + DockPane.setActiveDockPane(DockPane.getMainDockPain()); + Stage stage = DockPane.getActiveStage(); + if (stage != null) { + stage.requestFocus(); + } + }; } else { - setFocus = () -> DockPane.setActiveDockPane(dockPane); + setFocus = () -> { + DockPane.setActiveDockPane(dockPane); + Stage stage = DockPane.getActiveStage(); + if (stage != null) { + stage.requestFocus(); + } + }; } } diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ProbeDisplayContextMenuEntry.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ProbeDisplayContextMenuEntry.java index 7ec7f33c7c..18f42bc848 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ProbeDisplayContextMenuEntry.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ProbeDisplayContextMenuEntry.java @@ -59,6 +59,12 @@ public Image getIcon() return ImageCache.getImage(DisplayModel.class, "/icons/runtime.png"); } + @Override + public boolean isOpenAction() + { + return true; + } + @Override public void call(final Selection selection) throws Exception { diff --git a/app/probe/src/main/java/org/phoebus/applications/probe/ContextLaunchProbe.java b/app/probe/src/main/java/org/phoebus/applications/probe/ContextLaunchProbe.java index 45f31e3a71..b02a264c96 100644 --- a/app/probe/src/main/java/org/phoebus/applications/probe/ContextLaunchProbe.java +++ b/app/probe/src/main/java/org/phoebus/applications/probe/ContextLaunchProbe.java @@ -32,6 +32,11 @@ public Image getIcon() return ImageCache.getImage(Probe.class, "/icons/probe.png"); } + @Override + public boolean isOpenAction() { + return true; + } + @Override public void call(Selection selection) { List pvs = selection.getSelections(); diff --git a/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ContextMenuPVTableLauncher.java b/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ContextMenuPVTableLauncher.java index a21603b956..13f35799d0 100644 --- a/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ContextMenuPVTableLauncher.java +++ b/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ContextMenuPVTableLauncher.java @@ -45,6 +45,12 @@ public Class getSupportedType() return supportedType; } + @Override + public boolean isOpenAction() + { + return true; + } + @Override public void call(final Selection selection) throws Exception { diff --git a/app/pvtree/src/main/java/org/phoebus/applications/pvtree/ContextMenuPVTreeLauncher.java b/app/pvtree/src/main/java/org/phoebus/applications/pvtree/ContextMenuPVTreeLauncher.java index 654d454690..f0474c4d08 100644 --- a/app/pvtree/src/main/java/org/phoebus/applications/pvtree/ContextMenuPVTreeLauncher.java +++ b/app/pvtree/src/main/java/org/phoebus/applications/pvtree/ContextMenuPVTreeLauncher.java @@ -46,6 +46,12 @@ public Class getSupportedType() return supportedType; } + @Override + public boolean isOpenAction() + { + return true; + } + @Override public void call(final Selection selection) throws Exception { diff --git a/core/ui/src/main/java/org/phoebus/ui/application/ContextMenuHelper.java b/core/ui/src/main/java/org/phoebus/ui/application/ContextMenuHelper.java index d9c649ddb8..80b9c471e3 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/ContextMenuHelper.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/ContextMenuHelper.java @@ -59,7 +59,8 @@ public static boolean addSupportedEntries(Runnable setFocus, final ContextMenu m item.setGraphic(new ImageView(icon)); item.setOnAction(e -> { - setFocus.run(); + if (entry.isOpenAction()) + setFocus.run(); try { List selection = new ArrayList<>(); diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java index 2664b8cd3f..27bc4831e8 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java @@ -142,6 +142,20 @@ public static DockPane getActiveDockPane() return pane; } + public static Stage getActiveStage() + { + final DockPane pane = active.get(); + if (pane != null) + { + for (Stage stage : DockStage.getDockStages()) + for (DockPane p : DockStage.getDockPanes(stage)) + if (p == pane) { + return stage; + } + } + return null; + } + /** * @return The 'main' Phoebus dock pane */ diff --git a/core/ui/src/main/java/org/phoebus/ui/spi/ContextMenuEntry.java b/core/ui/src/main/java/org/phoebus/ui/spi/ContextMenuEntry.java index 4fc0a0ef79..fc226a9842 100644 --- a/core/ui/src/main/java/org/phoebus/ui/spi/ContextMenuEntry.java +++ b/core/ui/src/main/java/org/phoebus/ui/spi/ContextMenuEntry.java @@ -66,6 +66,14 @@ public default void call() throws Exception throw new UnsupportedOperationException(getName() + ".call() Is not implemented."); } + /** + * Return whether action opens a new display or not + */ + public default boolean isOpenAction() + { + return false; + } + /** * Invoke the context menu * From 79878288d374e5b33413d02216ea190fc044542d Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Fri, 20 Mar 2026 10:48:06 +0000 Subject: [PATCH 08/12] Minor name changes/fixes to methods --- .../org/csstudio/display/actions/OpenDisplayAction.fxml | 2 +- .../org/csstudio/display/builder/model/DisplayModel.java | 4 ++-- .../display/builder/runtime/app/ContextMenuSupport.java | 8 ++++---- .../builder/runtime/app/DisplayRuntimeInstance.java | 8 ++++---- .../builder/runtime/app/DockItemRepresentation.java | 2 +- .../app/actionhandlers/OpenDisplayActionHandler.java | 2 +- .../runtime/app/actionhandlers/OpenFileActionHandler.java | 4 ++-- .../ui/src/main/java/org/phoebus/ui/docking/DockPane.java | 8 ++++---- .../src/main/java/org/phoebus/ui/docking/DockStage.java | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDisplayAction.fxml b/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDisplayAction.fxml index 94535ae183..ada99e2d8c 100644 --- a/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDisplayAction.fxml +++ b/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDisplayAction.fxml @@ -42,7 +42,7 @@ - + diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/DisplayModel.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/DisplayModel.java index 4a782a5c8c..8f73ec1299 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/DisplayModel.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/DisplayModel.java @@ -158,12 +158,12 @@ public Version getVersion() return VERSION; } - public boolean isStandAlone() + public boolean isStandalone() { return standalone; } - public void setStandAlone(boolean standalone) + public void setStandalone(boolean standalone) { this.standalone = standalone; } diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java index 2ef8b29ef4..c89f479f7f 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java @@ -93,11 +93,11 @@ public void handleContextMenu(final Widget widget, final int screen_x, final int DisplayRuntimeInstance displayRuntimeInstance = DisplayRuntimeInstance.ofDisplayModel(displayModel); DockItem dockItem = displayRuntimeInstance.getDockItem(); DockPane dockPane = dockItem.getDockPane(); - if (dockPane.isStandAloneWindow()) { - // If in a standalone window or defined in the preference, set the active dock pane + if (dockPane.isStandaloneWindow()) { + // If in a standalone window, set the active dock pane // to be the 'main' Phoebus pain instead of the current dock pane setFocus = () -> { - DockPane.setActiveDockPane(DockPane.getMainDockPain()); + DockPane.setActiveDockPane(DockPane.getMainDockPane()); Stage stage = DockPane.getActiveStage(); if (stage != null) { stage.requestFocus(); @@ -264,7 +264,7 @@ private void fillMenu(Runnable setFocus, final Widget widget) { items.add(new SeparatorMenuItem()); // Do not add the show/hide toolbar context menu item in a standalone window - if (!instance.getDockItem().getDockPane().isStandAloneWindow()) + if (!instance.getDockItem().getDockPane().isStandaloneWindow()) items.add(new DisplayToolbarAction(instance)); // If the editor is available, add "Open in Editor" diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java index 60a26ba3d8..3dce74ed8d 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java @@ -154,7 +154,7 @@ else if (prefTarget.startsWith("standalone@")) new ContextMenuSupport(this); - if (last_toolbar_visible && !dock_pane.isStandAloneWindow()) + if (last_toolbar_visible && !dock_pane.isStandaloneWindow()) layout.setTop(toolbar); layout.setCenter(representation.createModelRoot()); @@ -270,7 +270,7 @@ public void restore(final Memento memento) memento.getBoolean(TAG_TOOLBAR).ifPresent(this::showToolbar); memento.getBoolean(TAG_STANDALONE).ifPresent(standalone -> { - dock_item.getDockPane().setAsStandAloneWindow(standalone); + dock_item.getDockPane().setAsStandaloneWindow(standalone); }); } @@ -281,8 +281,8 @@ public void save(final Memento memento) if (! JFXRepresentation.DEFAULT_ZOOM_LEVEL.equals(zoom)) memento.setString(TAG_ZOOM, zoom); memento.setBoolean(TAG_TOOLBAR, isToolbarVisible()); - if (dock_item.getDockPane().isStandAloneWindow()) - memento.setBoolean(TAG_STANDALONE, dock_item.getDockPane().isStandAloneWindow()); + if (dock_item.getDockPane().isStandaloneWindow()) + memento.setBoolean(TAG_STANDALONE, dock_item.getDockPane().isStandaloneWindow()); } /** Handle Alt-left & right as navigation keys */ diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java index 2ef916e582..b44d615b44 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java @@ -60,7 +60,7 @@ public ToolkitRepresentation openNewWindow(final DisplayModel mode final Stage new_stage = new Stage(); // Configure for docking, i.e. with DockPane - DockStage.configureStage(new_stage, new Geometry(null), model.isStandAlone()); + DockStage.configureStage(new_stage, new Geometry(null), model.isStandalone()); // Use location and size from model for the window double x = model.propX().getValue(); diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java index 91a983c114..ddacbf6bc8 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java @@ -88,7 +88,7 @@ else if (openDisplayActionInfo.getTarget().equals(OpenDisplayAction.Target.WINDO } else if (openDisplayActionInfo.getTarget().equals(OpenDisplayAction.Target.STANDALONE)) { - new_model.setStandAlone(true); + new_model.setStandalone(true); toolkit.submit(() -> { final ToolkitRepresentation new_toolkit = diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenFileActionHandler.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenFileActionHandler.java index 6343f028db..903ff51827 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenFileActionHandler.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenFileActionHandler.java @@ -39,10 +39,10 @@ public void handleAction(Widget sourceWidget, ActionInfo pluggableActionInfo) { toolkit.execute(() -> { DockPane dockPane = DisplayRuntimeInstance.ofDisplayModel(top_model).getDockItem().getDockPane(); - if (dockPane.isStandAloneWindow()) { + if (dockPane.isStandaloneWindow()) { // Open the file in the main Phoebus window if launched from // a standalone screen. - DockPane.setActiveDockPane(DockPane.getMainDockPain()); + DockPane.setActiveDockPane(DockPane.getMainDockPane()); } try { diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java index 27bc4831e8..3453e21019 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java @@ -159,7 +159,7 @@ public static Stage getActiveStage() /** * @return The 'main' Phoebus dock pane */ - public static DockPane getMainDockPain() { + public static DockPane getMainDockPane() { for (Stage stage : DockStage.getDockStages()) { if (stage.getProperties().get(DockStage.KEY_ID).equals(DockStage.ID_MAIN)) { @@ -304,12 +304,12 @@ public static void alwaysShowTabs(final boolean do_show_single_tabs) protected LinkedList tabsInOrderOfFocus = new LinkedList<>(); - public void setAsStandAloneWindow(boolean standalone) + public void setAsStandaloneWindow(boolean standalone) { this.standalone = standalone; } - public boolean isStandAloneWindow() + public boolean isStandaloneWindow() { return standalone; } @@ -575,7 +575,7 @@ private void autoHideTabs() private void doAutoHideTabs(final Scene scene) { - final boolean do_hide = (getTabs().size() == 1 && !always_show_tabs) || isStandAloneWindow(); + final boolean do_hide = (getTabs().size() == 1 && !always_show_tabs) || isStandaloneWindow(); // Hack from https://www.snip2code.com/Snippet/300911/A-trick-to-hide-the-tab-area-in-a-JavaFX : // Locate the header's pane and set height to zero diff --git a/core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java b/core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java index e8c2951f8a..d2f695d2d8 100644 --- a/core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java +++ b/core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java @@ -239,7 +239,7 @@ else if(layout.getChildren().get(0) instanceof SplitPane){ }); if (standalone) - pane.setAsStandAloneWindow(true); + pane.setAsStandaloneWindow(true); return pane; } From 59c85e3c564fa4b6bebc1670097948f9ca470b52 Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Tue, 24 Mar 2026 17:53:47 +0000 Subject: [PATCH 09/12] Handle where applications/resources should open if the active pane is standalone In this case the active pane should be set as the main window so that applications open there instead of in place of the standaloene. --- .../org/phoebus/ui/application/PhoebusApplication.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index 5c298d18a4..bff1ccc3c5 100644 --- a/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java +++ b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java @@ -1189,6 +1189,13 @@ private void openResource(final URI resource, final boolean prompt) { } } + // If current active pane is a standalone then switch the + // active pane to be the main dock pane so that applications + // etc open there and not in place of the standalone + if (DockPane.getActiveDockPane().isStandaloneWindow()) { + DockPane.setActiveDockPane(DockPane.getMainDockPane()); + } + logger.log(Level.INFO, "Opening " + resource + " with " + application.getName()); application.create(resource); } From eddc12546f84335e092be65fed70aca19e866c0b Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Tue, 16 Jun 2026 15:16:24 +0100 Subject: [PATCH 10/12] Set smaller stage margins for standalone windows when opened from action --- .../builder/runtime/app/DockItemRepresentation.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java index b44d615b44..6d88de053f 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java @@ -84,8 +84,16 @@ public ToolkitRepresentation openNewWindow(final DisplayModel mode // Size needs to account for the border and toolbar. // Using fixed numbers, exact size of border and toolbar unknown // at this time in the code - new_stage.setWidth(model.propWidth().getValue() + 18); - new_stage.setHeight(model.propHeight().getValue() + 105); + int width_margin = 18; + int height_margin = 105; + // Don't need to include space for toolbar in standalone + if (model.isStandalone()) + { + width_margin = 5; + height_margin = 40; + } + new_stage.setWidth(model.propWidth().getValue() + width_margin); + new_stage.setHeight(model.propHeight().getValue() + height_margin); new_stage.show(); From aa3ab154e6efc41fbb9ddbe57e80aba2326a3ae0 Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Tue, 16 Jun 2026 15:18:19 +0100 Subject: [PATCH 11/12] Resize window to the size of the BOB screen when opening from command line --- .../runtime/app/DisplayRuntimeInstance.java | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java index 3dce74ed8d..e001af2759 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java @@ -21,6 +21,7 @@ import java.util.concurrent.FutureTask; import java.util.logging.Level; +import javafx.stage.Window; import org.csstudio.display.builder.model.DisplayModel; import org.csstudio.display.builder.model.Preferences; import org.csstudio.display.builder.model.Widget; @@ -99,6 +100,8 @@ public class DisplayRuntimeInstance implements AppInstance /** Toolbar button for navigation */ private ButtonBase navigate_backward, navigate_forward; + private Boolean auto_size_stage = false; + public String getDisplayName() { return active_model.getDisplayName(); } @@ -129,13 +132,20 @@ public static DisplayRuntimeInstance ofDisplayModel(final DisplayModel model) { boolean standalone = false; if (prefTarget.startsWith("standalone")) + { standalone = true; + auto_size_stage = true; + } // Open new Stage in which this app will be opened, its DockPane is a new active one final Stage new_stage = new Stage(); if (prefTarget.startsWith("window@")) DockStage.configureStage(new_stage, new Geometry(prefTarget.substring(7)), standalone); else if (prefTarget.startsWith("standalone@")) + { DockStage.configureStage(new_stage, new Geometry(prefTarget.substring(11)), standalone); + // Do not autosize the stage to the screen size if the user has specified the dimensions + auto_size_stage = false; + } else DockStage.configureStage(new_stage, new Geometry(null), standalone); new_stage.show(); @@ -220,10 +230,10 @@ private Node createToolbar() navigate_backward = NavigationAction.createBackAction(this, navigation); navigate_forward = NavigationAction.createForewardAction(this, navigation); return new ToolBar(ToolbarHelper.createSpring(), - zoom_action, - navigate_backward, - navigate_forward - ); + zoom_action, + navigate_backward, + navigate_forward + ); } /** @return true if toolbar is visible */ @@ -355,6 +365,17 @@ public void loadDisplayFile(final DisplayInfo info) final Future represented = representation.submit(() -> representModel(model)); represented.get(); + if (auto_size_stage) + { + Window window = dock_item.getDockPane().getScene().getWindow(); + double xMargin = (int) (window.getWidth() + - window.getScene().getWidth() + 2); + double yMargin = (int) (window.getHeight() + - window.getScene().getHeight() + 2); + window.setWidth(model.propWidth().getValue() + xMargin); + window.setHeight(model.propHeight().getValue() + yMargin); + } + // Start runtime for the model RuntimeUtil.startRuntime(model); @@ -425,8 +446,8 @@ private DisplayModel loadModel(final JobMonitor monitor, final DisplayInfo info) { monitor.beginTask(info.toString()); final DisplayModel model = info.shouldResolve() - ? ModelLoader.resolveAndLoadModel(null, info.getPath()) - : ModelLoader.loadModel(info.getPath()); + ? ModelLoader.resolveAndLoadModel(null, info.getPath()) + : ModelLoader.loadModel(info.getPath()); // This code is called // 1) When opening a new display @@ -488,8 +509,8 @@ void trackCurrentModel(final DisplayModel model) // or the new one has a different path, // or different macros _and_ there were original macros. if ( old_info == null || - !old_info.getPath().equals(info.getPath()) || - ( !old_info.getMacros().isEmpty() && !old_info.getMacros().equals(info.getMacros()))) + !old_info.getPath().equals(info.getPath()) || + ( !old_info.getMacros().isEmpty() && !old_info.getMacros().equals(info.getMacros()))) { display_info = Optional.of(info); dock_item.setInput(info.toURI()); @@ -582,4 +603,4 @@ public Optional getPositionAndSizeHint() { }); } -} +} \ No newline at end of file From f051af3e4c1fbddefa001eb9ee4e1b307f00b56e Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Tue, 7 Apr 2026 09:03:14 +0100 Subject: [PATCH 12/12] Resize standalone window when open display action replaces the current --- .../OpenDisplayActionHandler.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java index ddacbf6bc8..7e593dc4cd 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDisplayActionHandler.java @@ -4,6 +4,7 @@ package org.csstudio.display.builder.runtime.app.actionhandlers; +import javafx.stage.Window; import org.csstudio.display.builder.model.DisplayModel; import org.csstudio.display.builder.model.Widget; import org.csstudio.display.builder.model.persist.ModelLoader; @@ -12,11 +13,15 @@ import org.csstudio.display.builder.representation.ToolkitRepresentation; import org.csstudio.display.builder.runtime.ActionUtil; import org.csstudio.display.builder.runtime.RuntimeUtil; +import org.csstudio.display.builder.runtime.app.DisplayInfo; +import org.csstudio.display.builder.runtime.app.DisplayRuntimeApplication; import org.csstudio.display.builder.runtime.script.ScriptUtil; import org.csstudio.display.builder.model.spi.ActionHandler; import org.csstudio.display.actions.OpenDisplayAction; import org.phoebus.framework.macros.MacroHandler; import org.phoebus.framework.macros.MacroOrSystemProvider; +import org.phoebus.ui.docking.DockItemWithInput; +import org.phoebus.ui.docking.DockStage; import java.util.concurrent.Future; import java.util.logging.Level; @@ -108,10 +113,29 @@ else if (openDisplayActionInfo.getTarget().equals(OpenDisplayAction.Target.STAND { try { + final DockItemWithInput existing_dock_item = DockStage.getDockItemWithInput( + DisplayRuntimeApplication.NAME, + DisplayInfo.forModel(top_model).toURI()); + // Close old representation final Object parent = toolkit.disposeRepresentation(top_model); // Tell toolkit about new model to represent toolkit.representModel(parent, new_model); + + if (existing_dock_item != null) + { + // Perform window resizing if this is a standalone window + if (existing_dock_item.getDockPane().isStandaloneWindow()) + { + Window window = existing_dock_item.getDockPane().getScene().getWindow(); + double padding_height = window.getHeight() - + top_model.propHeight().getValue(); + double padding_width = window.getWidth() - + top_model.propWidth().getValue(); + window.setHeight(new_model.propHeight().getValue() + padding_height); + window.setWidth(new_model.propWidth().getValue() + padding_width); + } + } } catch (Throwable ex) {