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/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..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 @@ -38,7 +38,11 @@ - + + + + + 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/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..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 @@ -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/ContextMenuSupport.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/ContextMenuSupport.java index b2e2653560..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 @@ -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; @@ -91,7 +93,25 @@ 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.getMainDockPane()); + Stage stage = DockPane.getActiveStage(); + if (stage != null) { + stage.requestFocus(); + } + }; + } else { + setFocus = () -> { + DockPane.setActiveDockPane(dockPane); + Stage stage = DockPane.getActiveStage(); + if (stage != null) { + stage.requestFocus(); + } + }; + } } fillMenu(setFocus, widget); @@ -243,12 +263,14 @@ 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"); 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/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java index ef64dd730e..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; @@ -68,6 +69,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 @@ -98,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(); } @@ -124,14 +128,26 @@ 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; + 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))); + 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); + DockStage.configureStage(new_stage, new Geometry(null), standalone); new_stage.show(); } else @@ -148,7 +164,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()); @@ -214,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 */ @@ -262,6 +278,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 +291,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 */ @@ -343,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); @@ -413,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 @@ -476,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()); @@ -570,4 +603,4 @@ public Optional getPositionAndSizeHint() { }); } -} +} \ No newline at end of file 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..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 @@ -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(); @@ -83,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(); @@ -92,6 +101,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/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(); 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/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..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; @@ -88,6 +93,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 = @@ -107,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) { 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..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 @@ -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.getMainDockPane()); + } try { toolkit.openFile(resolved_name); 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/application/PhoebusApplication.java b/core/ui/src/main/java/org/phoebus/ui/application/PhoebusApplication.java index c9333d91d9..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 @@ -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) @@ -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); } 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..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 @@ -142,6 +142,38 @@ 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 + */ + public static DockPane getMainDockPane() { + 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, @@ -199,6 +231,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 +304,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 +575,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..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 @@ -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; } 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 *