diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/RulesDialog.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/RulesDialog.java
index 6804fb6ee6..816559f054 100644
--- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/RulesDialog.java
+++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/RulesDialog.java
@@ -33,7 +33,7 @@
import org.csstudio.display.builder.representation.javafx.ScriptsDialog;
import org.phoebus.framework.preferences.PhoebusPreferenceService;
import org.phoebus.ui.dialog.DialogHelper;
-import org.phoebus.ui.dialog.MultiLineInputDialog;
+import org.phoebus.ui.dialog.CodeDialog;
import org.phoebus.ui.javafx.EditCell;
import org.phoebus.ui.javafx.LineNumberTableCellFactory;
import org.phoebus.ui.javafx.TableHelper;
@@ -769,7 +769,7 @@ private Node createRulesTable ()
if (sel >= 0)
{
final String content = rule_items.get(sel).getRuleInfo().getTextPy(attached_widget);
- final MultiLineInputDialog dialog = new MultiLineInputDialog(btn_show_script, content);
+ final CodeDialog dialog = new CodeDialog(btn_show_script, content);
DialogHelper.positionDialog(dialog, btn_show_script, -200, -300);
dialog.setTextHeight(600);
dialog.show();
diff --git a/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/opieditor.css b/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/opieditor.css
index de31739148..16444339ca 100644
--- a/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/opieditor.css
+++ b/app/display/editor/src/main/resources/org/csstudio/display/builder/editor/opieditor.css
@@ -187,3 +187,7 @@
.widget_pane_focused{
-fx-border-color: #00A0D8;
}
+
+.code-dialog {
+ -fx-font-family: monospace !important;
+}
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/JFXRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/JFXRepresentation.java
index 1294f95c1d..9b191db3a5 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/JFXRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/JFXRepresentation.java
@@ -26,10 +26,7 @@
import java.util.function.Consumer;
import java.util.logging.Level;
-import org.csstudio.display.builder.model.DisplayModel;
-import org.csstudio.display.builder.model.UntypedWidgetPropertyListener;
-import org.csstudio.display.builder.model.Widget;
-import org.csstudio.display.builder.model.WidgetPropertyListener;
+import org.csstudio.display.builder.model.*;
import org.csstudio.display.builder.model.properties.PredefinedColorMaps;
import org.csstudio.display.builder.model.properties.WidgetColor;
import org.csstudio.display.builder.representation.ToolkitRepresentation;
@@ -92,14 +89,23 @@
* |
* scroll_body (Group)
* |
- * widget_parent (Pane)
+ * widget_pane (Pane)
+ * |
+ * widget_parent (Group)
+ *
*
*
- *
widget_parent:
- * This is where the widgets of the model get represented.
+ *
widget_pane:
+ * This contains the group with the child widgets.
* Its scaling factors are used to zoom.
* Also used to set the overall background color.
*
+ *
widget_parent:
+ * This is where the widgets of the model get represented.
+ * We need this to be a Group, as a Pane handle the prefSize properly when it contains
+ * transformed widgets (like a rotated Label). Groups set their prefSize according
+ * to the _transformed_ dimensions of its children.
+ *
*
scroll_body:
* Needed for scroll pane to use visual bounds, i.e. be aware of zoom.
* Otherwise scroll bars would enable/disable based on layout bounds,
@@ -152,8 +158,9 @@ public class JFXRepresentation extends ToolkitRepresentation
/** Update background color, grid */
private final UntypedWidgetPropertyListener background_listener = ( p, o, n ) -> execute(this::updateBackground);
+ private Group widget_parent;
private Line horiz_bound, vert_bound;
- private Pane widget_parent;
+ private Pane widget_pane;
private Group scroll_body;
private ScrollPane model_root;
@@ -191,8 +198,9 @@ final public ScrollPane createModelRoot()
if (model_root != null)
throw new IllegalStateException("Already created model root");
- widget_parent = new Pane();
- scroll_body = new Group(widget_parent);
+ widget_parent = new Group();
+ widget_pane = new Pane(widget_parent);
+ scroll_body = new Group(widget_pane);
if (isEditMode())
{
@@ -281,7 +289,7 @@ final public ScrollPane createModelRoot()
}
});
else
- widget_parent.addEventHandler(ScrollEvent.ANY, evt ->
+ widget_pane.addEventHandler(ScrollEvent.ANY, evt ->
{
if (evt.isShortcutDown())
{
@@ -319,7 +327,8 @@ private void doWheelZoom(final double delta, final double x, final double y)
// setText() only, otherwise it gets into an endless update due to getValue/setValue implementation in Editor. In Runtime was OK.
// Drawback: return to a previous "combo driven" zoom level from any wheel level not possible directly (no value change in combo)
setZoom(new_zoom);
- zoom_listener.accept(Integer.toString((int)(new_zoom * 100)) + " %");
+ if (zoom_listener != null)
+ zoom_listener.accept(Integer.toString((int)(new_zoom * 100)) + " %");
repositionScroller(scroll_body, model_root, realFactor, scrollOffset, new Point2D(x, y));
}
@@ -448,7 +457,7 @@ private double setZoom(double zoom)
if (zoom <= 0.0)
{ // Determine zoom to fit outline of display into available space
final Bounds available = model_root.getLayoutBounds();
- final Bounds outline = widget_parent.getLayoutBounds();
+ final Bounds outline = widget_pane.getLayoutBounds();
// 'outline' will wrap the actual widgets when the display
// is larger than the available viewport.
@@ -483,6 +492,7 @@ else if (zoom == ZOOM_HEIGHT)
}
widget_parent.getTransforms().setAll(new Scale(zoom, zoom));
+ widget_pane.getTransforms().setAll(new Scale(zoom, zoom));
// Appears similar to using this API:
// widget_parent.setScaleX(zoom);
// widget_parent.setScaleY(zoom);
@@ -502,7 +512,7 @@ else if (zoom == ZOOM_HEIGHT)
/** @return Zoom factor, 1.0 for 1:1 */
public double getZoom()
{
- final List transforms = widget_parent.getTransforms();
+ final List transforms = widget_pane.getTransforms();
if (transforms.isEmpty() ||
transforms.size() > 1 ||
! (transforms.get(0) instanceof Scale))
@@ -568,8 +578,8 @@ private void handleViewportChanges()
: model_height;
// Does not consider zooming.
- // If the widget_parent is zoomed 'out', e.g. 50%,
- // the widget_parent will only be half as large
+ // If the widget_pane is zoomed 'out', e.g. 50%,
+ // the widget_pane will only be half as large
// as we specify here in pixels
// -> Ignore. If user zooms out a lot, there'll be an
// area a gray area at the right and bottom of the display.
@@ -577,7 +587,7 @@ private void handleViewportChanges()
// so there is very little gray area.
// widget_parent.setMinWidth(show_x / zoom);
// widget_parent.setMinHeight(show_y / zoom);
- widget_parent.setMinSize(show_x, show_y);
+ widget_pane.setMinSize(show_x, show_y);
}
/** Update lines that indicate model's size in edit mode */
@@ -586,7 +596,7 @@ private void updateModelSizeIndicators()
int width = model.propWidth().getValue();
int height = model.propHeight().getValue();
- final ObservableList transforms = widget_parent.getTransforms();
+ final ObservableList transforms = widget_pane.getTransforms();
if (transforms.size() > 0 && transforms.get(0) instanceof Scale)
{
final Scale scale = (Scale) transforms.get(0);
@@ -883,7 +893,7 @@ private void updateBackground()
final WritableImage wimage = new WritableImage(gridStepX, gridStepY);
SwingFXUtils.toFXImage(image, wimage);
final ImagePattern pattern = new ImagePattern(wimage, 0, 0, gridStepX, gridStepY, false);
- widget_parent.setBackground(new Background(new BackgroundFill(pattern, CornerRadii.EMPTY, Insets.EMPTY)));
+ widget_pane.setBackground(new Background(new BackgroundFill(pattern, CornerRadii.EMPTY, Insets.EMPTY)));
}
// Future for controlling the audio player
@@ -1065,6 +1075,7 @@ public void shutdown()
logger.log(Level.WARNING, "Display representation still contains items on shutdown: " + widget_parent.getChildren());
widget_parent = null;
+ widget_pane = null;
model_root = null;
scroll_body = null;
zoom_listener = null;
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/ScriptsDialog.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/ScriptsDialog.java
index 8b2df8a360..da667e5663 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/ScriptsDialog.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/ScriptsDialog.java
@@ -26,8 +26,8 @@
import org.phoebus.framework.preferences.PhoebusPreferenceService;
import org.phoebus.framework.util.ResourceParser;
import org.phoebus.ui.application.ApplicationLauncherService;
+import org.phoebus.ui.dialog.CodeDialog;
import org.phoebus.ui.dialog.DialogHelper;
-import org.phoebus.ui.dialog.MultiLineInputDialog;
import org.phoebus.ui.javafx.EditCell;
import org.phoebus.ui.javafx.LineNumberTableCellFactory;
import org.phoebus.ui.javafx.TableHelper;
@@ -617,7 +617,7 @@ private void add(final String file, final String text)
{
Platform.runLater(() ->
{
- final MultiLineInputDialog dlg = new MultiLineInputDialog(scripts_table, selected_script_item.text);
+ final CodeDialog dlg = new CodeDialog(scripts_table, selected_script_item.text);
dlg.showAndWait().ifPresent(result -> selected_script_item.text = result);
});
}
@@ -751,7 +751,7 @@ private void editOrSelect()
}
else
{
- final MultiLineInputDialog dlg = new MultiLineInputDialog(scripts_table, selected_script_item.text);
+ final CodeDialog dlg = new CodeDialog(scripts_table, selected_script_item.text);
dlg.showAndWait().ifPresent(result -> selected_script_item.text = result);
}
}
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
index dde8845324..ead6362a90 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java
@@ -29,6 +29,7 @@
import javafx.geometry.Insets;
import javafx.scene.Parent;
+import javafx.scene.Group;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.layout.Background;
@@ -89,12 +90,16 @@ public class EmbeddedDisplayRepresentation extends RegionBaseRepresentationSet to null when representation is disposed,
* which is used as indicator to pending display updates.
*/
private volatile Pane inner;
+ private volatile Group inner_parent;
private volatile Background inner_background = Background.EMPTY;
/** Zoom for 'inner' pane */
@@ -103,7 +108,7 @@ public class EmbeddedDisplayRepresentation extends RegionBaseRepresentation scroll -> inner.
+ * jfx_node -> scroll -> inner -> inner_parent.
*
* If no scrolling is desired, the scrollbars can be hidden via
* scroll.setHbarPolicy(ScrollBarPolicy.NEVER),
@@ -113,7 +118,7 @@ public class EmbeddedDisplayRepresentation extends RegionBaseRepresentation inner.
+ * jfx_node-> inner -> inner_parent.
*/
private ScrollPane scroll;
@@ -137,12 +142,20 @@ protected boolean isFilteringEditModeClicks()
@Override
public Pane createJFXNode() throws Exception
{
+ // rotated child widgets break the prefWidth / prefHeight of a Pane as its own prefWidth / prefHeight uses
+ // the non-transformed dimensions of its children. So the child widget parent must be a Group instead
+ // See https://forums.oracle.com/ords/apexds/post/java-fx-strange-behaviour-when-trying-to-write-a-label-vert-3164
+ // This is easily reproducible, by creating an embedded display with sufficient width and height,
+ // and placing a very tall rotated label inside of it. The resulting embedded display will get a horizontal
+ // scrollbar, as the pane containing it would be wider than it is shown to be.
+ inner_parent = new Group();
+
// inner.setScaleX() and setScaleY() zoom from the center
// and not the top-left edge, requiring adjustments to
// inner.setTranslateX() and ..Y() to compensate.
// Using a separate Scale transformation does not have that problem.
// See http://stackoverflow.com/questions/10707880/javafx-scale-and-translate-operation-results-in-anomaly
- inner = new Pane();
+ inner = new Pane(inner_parent);
inner.getTransforms().add(zoom = new Scale());
scroll = new NonCachingScrollPane(inner);
@@ -165,7 +178,7 @@ public Pane createJFXNode() throws Exception
@Override
protected Parent getChildParent(final Parent parent)
{
- return inner;
+ return inner_parent;
}
@Override
@@ -343,7 +356,7 @@ private void representContent(final DisplayModel content_model)
zoom.setX(zoom_factor_x);
zoom.setY(zoom_factor_y);
- toolkit.representModel(inner, content_model);
+ toolkit.representModel(inner_parent, content_model);
backgroundChanged(null, null, null);
}
catch (final Exception ex)
@@ -426,7 +439,7 @@ public void updateChanges()
else
{ // Don't use a scroll pane
scroll.setContent(null);
- jfx_node.getChildren().setAll(inner);
+ jfx_node.getChildren().setAll(inner_parent);
// During runtime or if the resize property is set to Crop we clip inner
// but allow 'overdrawing' in edit mode so the out-of-region widgets are visible to the user
@@ -450,7 +463,7 @@ else if (inner.getHeight() != 0.0 && inner.getWidth() != 0.0)
rect.setManaged(false);
rect.resizeRelocate(0, scaled_height, inner.getWidth(), inner.getHeight() - scaled_height);
rect.setBackground(EDIT_OVERDRAWN_BACKGROUND);
- inner.getChildren().addAll(rect);
+ inner_parent.getChildren().addAll(rect);
}
// Check if wider than allowed
@@ -460,7 +473,7 @@ else if (inner.getHeight() != 0.0 && inner.getWidth() != 0.0)
rect.setManaged(false);
rect.resizeRelocate(scaled_width, 0, inner.getWidth() - scaled_width, inner.getHeight());
rect.setBackground(EDIT_OVERDRAWN_BACKGROUND);
- inner.getChildren().addAll(rect);
+ inner_parent.getChildren().addAll(rect);
}
}
}
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java
index 40f03b8a0c..8892ea4b67 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java
@@ -14,6 +14,7 @@
import java.util.Optional;
import java.util.logging.Level;
+import javafx.scene.input.*;
import org.csstudio.display.builder.model.ChildrenProperty;
import org.csstudio.display.builder.model.DirtyFlag;
import org.csstudio.display.builder.model.DisplayModel;
@@ -31,10 +32,8 @@
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Parent;
-import javafx.scene.input.ClipboardContent;
-import javafx.scene.input.Dragboard;
-import javafx.scene.input.MouseEvent;
-import javafx.scene.input.TransferMode;
+import org.phoebus.core.types.ProcessVariable;
+import org.phoebus.ui.dnd.DataFormats;
/** Base class for all JavaFX widget representations
* @param JFX Widget
@@ -184,7 +183,8 @@ protected void configurePVNameDrag()
// this prevents selecting content within a text field
// via a mouse drag.
// Ctrl-drag is thus required to start dragging a PV name.
- if (! event.isControlDown())
+ final Optional> writable = model_widget.checkProperty(CommonWidgetProperties.runtimePropPVWritable);
+ if (writable.isPresent() && !event.isControlDown())
return;
final String pv = pv_name.get().getValue();
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 599a44c907..edff92f24c 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
@@ -156,7 +156,12 @@ public static DisplayRuntimeInstance ofDisplayModel(final DisplayModel model)
dock_pane.addTab(dock_item);
representation.getModelParent().getProperties().put(MODEL_PARENT_DISPLAY_RUNTIME, this);
- representation.getModelParent().setOnContextMenuRequested(event ->
+
+ // since the model parent is now a Group, which takes on the bounds of its children,
+ // we need to add the context menu interaction to the Root, and not the parent
+ // I guess to be completely compliant to the 'old version', we'd need to get the
+ // widget_pane, but this seems more appropriate and generic
+ representation.getModelRoot().setOnContextMenuRequested(event ->
{
final DisplayModel model = active_model;
if (model != null)
diff --git a/app/logbook/olog/ui/src/main/resources/icons/logentry-add-16@2x.png b/app/logbook/olog/ui/src/main/resources/icons/logentry-add-16@2x.png
index 0fdbea9637..667ac2026b 100644
Binary files a/app/logbook/olog/ui/src/main/resources/icons/logentry-add-16@2x.png and b/app/logbook/olog/ui/src/main/resources/icons/logentry-add-16@2x.png differ
diff --git a/app/logbook/ui/src/main/resources/icons/logentry-add-16@2x.png b/app/logbook/ui/src/main/resources/icons/logentry-add-16@2x.png
index 0fdbea9637..667ac2026b 100644
Binary files a/app/logbook/ui/src/main/resources/icons/logentry-add-16@2x.png and b/app/logbook/ui/src/main/resources/icons/logentry-add-16@2x.png differ
diff --git a/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/Messages.java b/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/Messages.java
index 493c06469e..138cca3f7f 100644
--- a/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/Messages.java
+++ b/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/Messages.java
@@ -47,6 +47,7 @@ public class Messages
RestoreSelection_TT,
Saved,
Saved_Value_TimeStamp,
+ SearchPV,
Selected,
Snapshot,
Snapshot_TT,
diff --git a/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/PVTable.java b/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/PVTable.java
index 9bbfef2466..bc3358e815 100644
--- a/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/PVTable.java
+++ b/app/pvtable/src/main/java/org/phoebus/applications/pvtable/ui/PVTable.java
@@ -12,10 +12,12 @@
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
+import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javafx.collections.transformation.FilteredList;
import org.epics.vtype.VEnum;
import org.epics.vtype.VType;
import org.phoebus.applications.pvtable.PVTableApplication;
@@ -114,10 +116,11 @@ else if (b == TableItemProxy.NEW_ITEM)
* properties of the item (== column values) change
*/
private final ObservableList rows = FXCollections.observableArrayList(TableItemProxy.CHANGING_PROPERTIES);
- /** Sorted view of the rows.
+ /** Sorted and filtered views of the rows.
* Order of 'rows' is preserved, but comparator of this list changes to sort.
*/
- private final SortedList sorted = rows.sorted();
+ private final FilteredList filtered = new FilteredList<>(rows.sorted());
+ private final SortedList sorted = new SortedList<>(filtered);
private final TableView table = new TableView<>(sorted);
private TableColumn saved_value_col;
@@ -550,9 +553,29 @@ private void disableSaveRestore()
model.fireModelChange();
}
+ private TextField createSearchbar() {
+ TextField searchBar = new TextField();
+ searchBar.setPromptText(Messages.SearchPV);
+ searchBar.textProperty().addListener((obs, old, value) -> {
+ final Predicate predicate = s -> s.toLowerCase().contains(value.toLowerCase());
+
+ if (value.isBlank()) {
+ filtered.setPredicate(null);
+ }
+ else {
+ filtered.setPredicate(
+ row -> predicate.test(row.name.get()) || predicate.test(row.desc_value.get())
+ );
+ }
+ });
+ searchBar.setPrefWidth(500);
+ return searchBar;
+ }
+
private ToolBar createToolbar()
{
return new ToolBar(
+ createSearchbar(),
ToolbarHelper.createSpring(),
createButton("checked.gif", Messages.CheckAll_TT, event ->
{
diff --git a/app/pvtable/src/main/resources/org/phoebus/applications/pvtable/ui/messages.properties b/app/pvtable/src/main/resources/org/phoebus/applications/pvtable/ui/messages.properties
index 43e0e1dde9..29d2d3a267 100644
--- a/app/pvtable/src/main/resources/org/phoebus/applications/pvtable/ui/messages.properties
+++ b/app/pvtable/src/main/resources/org/phoebus/applications/pvtable/ui/messages.properties
@@ -32,6 +32,7 @@ RestoreSelection=Restore this row
RestoreSelection_TT=Write snapshot values back to PVs for all currently selected rows in table
Saved=Saved Value
Saved_Value_TimeStamp= Saved Value Timestamp
+SearchPV=Type to filter PVs...
Selected=Selected
Snapshot=Snapshot all checked
Snapshot_TT=Take snapshot of current values for all checked rows in the table
diff --git a/core/ui/src/main/java/org/phoebus/ui/dialog/CodeDialog.java b/core/ui/src/main/java/org/phoebus/ui/dialog/CodeDialog.java
new file mode 100644
index 0000000000..c440591ec4
--- /dev/null
+++ b/core/ui/src/main/java/org/phoebus/ui/dialog/CodeDialog.java
@@ -0,0 +1,67 @@
+package org.phoebus.ui.dialog;
+
+
+import javafx.scene.Node;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+
+/** Dialog for entering multi-line code
+ *
+ * Can also be used to just display code,
+ * allowing to 'copy' the text, but no changes.
+ *
+ * @author Dennis Hilhorst
+ */
+public class CodeDialog extends MultiLineInputDialog {
+
+ public CodeDialog(String initial_text) {
+ super(initial_text);
+ setupEditor();
+ }
+
+ public CodeDialog(Node parent, String initial_text) {
+ super(parent, initial_text);
+ setupEditor();
+ }
+
+ public CodeDialog(Node parent, String initial_text, boolean editable) {
+ super(parent, initial_text, editable);
+ setupEditor();
+ }
+
+ private void setupEditor() {
+ setStyling();
+ setTabToSpaces();
+ }
+
+ private void setTabToSpaces() {
+ text.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
+ if (event.getCode() == KeyCode.TAB) {
+ event.consume();
+ int caretPosition = text.getCaretPosition();
+
+ if (event.isShiftDown()) {
+ if (caretPosition >= 4) {
+ String textBeforeCaret = text.getText(caretPosition - 4, caretPosition);
+ if (" ".equals(textBeforeCaret)) {
+ // remove last 'tab'
+ text.deleteText(caretPosition - 4, caretPosition);
+ return;
+ }
+ }
+
+ // Move caret back by the specified number of spaces, but not past the start of the line
+ int lineStart = text.getText(0, caretPosition).lastIndexOf('\n') + 1;
+ text.positionCaret(Math.max(lineStart, caretPosition - 4));
+ }
+ else {
+ text.insertText(caretPosition, " ");
+ }
+ }
+ });
+ }
+
+ private void setStyling() {
+ text.getStyleClass().add("code-dialog"); // in opieditor.css
+ }
+}
diff --git a/core/ui/src/main/java/org/phoebus/ui/dialog/MultiLineInputDialog.java b/core/ui/src/main/java/org/phoebus/ui/dialog/MultiLineInputDialog.java
index 7a6a8fed8b..330afb96a4 100644
--- a/core/ui/src/main/java/org/phoebus/ui/dialog/MultiLineInputDialog.java
+++ b/core/ui/src/main/java/org/phoebus/ui/dialog/MultiLineInputDialog.java
@@ -24,7 +24,7 @@
*/
public class MultiLineInputDialog extends Dialog
{
- private final TextArea text;
+ protected final TextArea text;
/** @param initial_text Initial text */
public MultiLineInputDialog(final String initial_text)