Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0c1a049
Implement opening displays as standalone windows from an action
rjwills28 Oct 20, 2025
9f31f02
Configure standalone window in memento
rjwills28 Oct 20, 2025
8ca3377
Add ability to launch displays as standalone windows from command line
rjwills28 Oct 20, 2025
21291e4
Remove show/hide toolbar context menu item in standalone windows
rjwills28 Mar 19, 2026
0f076f6
Implement opening applications in the main Phoebus window...
rjwills28 Oct 29, 2025
442297e
Implement the ability to open the editor in the 'main' window
rjwills28 Oct 29, 2025
77071ce
Set focus on the main window if application is opened there from a co…
rjwills28 Nov 4, 2025
7987828
Minor name changes/fixes to methods
rjwills28 Mar 20, 2026
59c85e3
Handle where applications/resources should open if the active pane is…
rjwills28 Mar 24, 2026
eddc125
Set smaller stage margins for standalone windows when opened from action
rjwills28 Jun 16, 2026
aa3ab15
Resize window to the size of the BOB screen when opening from command…
rjwills28 Jun 16, 2026
f051af3
Resize standalone window when open display action replaces the current
rjwills28 Apr 7, 2026
9b19b61
Fix spelling mistake in comment
rjwills28 Jun 30, 2026
f6d47bd
Fixing SonarQube Cloud issues including...
rjwills28 Jun 30, 2026
8617c8e
Merge branch 'master' into add_standalone_windows
rjwills28 Jun 30, 2026
7f70ce0
Fix more SonarQube cloud issues including...
rjwills28 Jun 30, 2026
5f067c8
Change the text for the standalone window option shown in the action …
rjwills28 Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ public Class<?> getSupportedType() {
public Image getIcon() {
return AlarmLogTableApp.icon;
}

@Override
public boolean isOpenAction() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ public Class<?> getSupportedType()
return supportedType;
}

@Override
public boolean isOpenAction()
{
return true;
}

private volatile Duration time_span = Preferences.time_span;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -219,8 +216,6 @@ public List<MenuItem> 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);
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
<HBox.margin>
<Insets right="10.0" />
</HBox.margin></RadioButton>
<RadioButton fx:id="newWindowRadioButton" text="New Window" />
<RadioButton fx:id="newWindowRadioButton" text="New Window">
<HBox.margin>
<Insets right="10.0" />
</HBox.margin></RadioButton>
<RadioButton fx:id="newStandaloneRadioButton" text="New Window (undecorated)" />
<GridPane.margin>
<Insets bottom="5.0" top="5.0" />
</GridPane.margin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> file =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public boolean configureFromXML(final ModelReader model_reader, final Widget wid
private volatile WidgetProperty<Integer> gridStepX;
private volatile WidgetProperty<Integer> gridStepY;
private volatile ChildrenProperty children;
private boolean standalone;

/** Create display model */
public DisplayModel()
Expand All @@ -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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 pane 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);
Expand Down Expand Up @@ -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());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -68,6 +69,7 @@
/** 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
Expand Down Expand Up @@ -98,6 +100,8 @@
/** Toolbar button for navigation */
private ButtonBase navigate_backward, navigate_forward;

private Boolean auto_size_stage = false;

Check warning on line 103 in app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "auto_size_stage" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ8T8Av2qzuh6f_cvVqp&open=AZ8T8Av2qzuh6f_cvVqp&pullRequest=3859

public String getDisplayName() {
return active_model.getDisplayName();
}
Expand All @@ -124,14 +128,27 @@
DockPane dock_pane = null;
if (prefTarget != null)
{
if (prefTarget.startsWith("window"))
if (isCreateNewStage(prefTarget))
{
boolean standalone = false;
if (prefTarget.startsWith(TAG_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)));
else
DockStage.configureStage(new_stage);
int extract_sub_str = prefTarget.indexOf("@");

Check warning on line 142 in app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ8YrFt0nfCPNx-x9gpS&open=AZ8YrFt0nfCPNx-x9gpS&pullRequest=3859
String geometry_str = null;

Check warning on line 143 in app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ8YrFt0nfCPNx-x9gpT&open=AZ8YrFt0nfCPNx-x9gpT&pullRequest=3859
if (extract_sub_str != -1)
{
geometry_str = prefTarget.substring(extract_sub_str + 1);
// Do not autosize the stage to the screen size if the user has specified the dimensions.
// Will only be used if in standalone mode.
auto_size_stage = false;
}
DockStage.configureStage(new_stage, new Geometry(geometry_str), standalone);
new_stage.show();
}
else
Expand All @@ -148,7 +165,7 @@

new ContextMenuSupport(this);

if (last_toolbar_visible)
if (last_toolbar_visible && !dock_pane.isStandaloneWindow())
layout.setTop(toolbar);

layout.setCenter(representation.createModelRoot());
Expand Down Expand Up @@ -214,10 +231,10 @@
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 <code>true</code> if toolbar is visible */
Expand Down Expand Up @@ -262,6 +279,10 @@
});
});
memento.getBoolean(TAG_TOOLBAR).ifPresent(this::showToolbar);
memento.getBoolean(TAG_STANDALONE).ifPresent(standalone ->
{
dock_item.getDockPane().setAsStandaloneWindow(standalone);
});
}

@Override
Expand All @@ -271,6 +292,8 @@
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 */
Expand Down Expand Up @@ -343,6 +366,17 @@
final Future<Void> represented = representation.submit(() -> representModel(model));
represented.get();

if (Boolean.TRUE.equals(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);

Expand Down Expand Up @@ -413,8 +447,8 @@
{
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
Expand Down Expand Up @@ -476,8 +510,8 @@
// 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());
Expand Down Expand Up @@ -570,4 +604,14 @@
});
}

}
/**
* Detemines whether a new stage needs to be created to display the new model
*
* @param target A string containing the target option.
* @return Boolean returning true if the target option is 'window' or 'standalone'
*/
private boolean isCreateNewStage(String target)
{
return target.startsWith("window") || target.startsWith(TAG_STANDALONE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,7 +60,7 @@
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();
Expand All @@ -83,15 +84,30 @@
// 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;

Check warning on line 87 in app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ8T8Awxqzuh6f_cvVqs&open=AZ8T8Awxqzuh6f_cvVqs&pullRequest=3859
int height_margin = 105;

Check warning on line 88 in app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ8T8Awxqzuh6f_cvVqt&open=AZ8T8Awxqzuh6f_cvVqt&pullRequest=3859
// 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();

// New DockPane is now the 'active' one,
// model will be opened in it.
return representModelInNewDockItem(model);
}

@Override
public ToolkitRepresentation<Parent, Node> openStandaloneWindow(final DisplayModel model,
final Consumer<DisplayModel> close_handler)

Check warning on line 107 in app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AZ8T8Awxqzuh6f_cvVqu&open=AZ8T8Awxqzuh6f_cvVqu&pullRequest=3859
{
return openNewWindow(model, close_handler);
}

@Override
public ToolkitRepresentation<Parent, Node> openPanel(final DisplayModel model,
Expand Down
Loading
Loading