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)