From 249119e27baa8edf1987e1513aa33575eea5e64d Mon Sep 17 00:00:00 2001 From: Milena Schedle Date: Wed, 1 Jul 2026 10:33:09 +0200 Subject: [PATCH 1/3] Updated Disable Context Menu --- .../applications/alarm/ui/Messages.java | 1 + .../ComponentConfigDialogController.java | 2 + .../alarm/ui/config/DateTimePicker.java | 1 + .../config/DisableUntilDialogController.java | 142 +++++++++++++ .../alarm/ui/tree/AlarmTreeView.java | 3 +- .../alarm/ui/tree/DisableAction.java | 189 ++++++++++++++++++ .../alarm/ui/tree/EnableComponentAction.java | 3 +- .../alarm/ui/tree/RemoveComponentAction.java | 5 +- .../alarm/ui/config/DisableUntilDialog.fxml | 35 ++++ .../applications/alarm/ui/messages.properties | 6 +- .../alarm/ui/tree/CheckEnableDatesTest.java | 74 +++++++ 11 files changed, 454 insertions(+), 7 deletions(-) create mode 100644 app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/DisableUntilDialogController.java create mode 100644 app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/DisableAction.java create mode 100644 app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/DisableUntilDialog.fxml create mode 100644 app/alarm/ui/src/test/java/org/phoebus/applications/alarm/ui/tree/CheckEnableDatesTest.java diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java index 4c622d57ae..de326f939b 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java @@ -24,6 +24,7 @@ public class Messages public static String disableAlarmFailed; public static String disableAlarms; public static String disabled; + public static String disableMenu; public static String disabledUntil; public static String displays; public static String enableAlarmFailed; diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ComponentConfigDialogController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ComponentConfigDialogController.java index 55fd9a8f36..a9ef27d0ad 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ComponentConfigDialogController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ComponentConfigDialogController.java @@ -153,6 +153,7 @@ public void validateAndStore() { } } +/// ////////// REMOVE//////////////// /** * Updates a component to disable a hierarchy of PVs with an enable date. * @@ -191,6 +192,7 @@ private void updateEnablement(LocalDateTime enableDate) { }); } + /////////////////// CAUTION!! MOVED THIS TO DisablesAction.java!! (the Set-Version, not the List-Version) /////////////////////////// /** * Recursively counts alarm tree items in a subtree to find total number, number of disabled, and * number of disabled with enable date. diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/DateTimePicker.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/DateTimePicker.java index 0bedb633b9..c8665f2a08 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/DateTimePicker.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/DateTimePicker.java @@ -108,6 +108,7 @@ private ObjectProperty formatProperty() { return format; } + // removed "private" here private void setFormat(String format) { this.format.set(format); alignColumnCountWithFormat(); diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/DisableUntilDialogController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/DisableUntilDialogController.java new file mode 100644 index 0000000000..5cb37b53d3 --- /dev/null +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/DisableUntilDialogController.java @@ -0,0 +1,142 @@ +package org.phoebus.applications.alarm.ui.config; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.paint.Color; +import javafx.util.StringConverter; +import org.phoebus.applications.alarm.AlarmSystem; +import org.phoebus.util.time.TimeParser; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAmount; + +public class DisableUntilDialogController { + + @FXML + private DateTimePicker enabledDatePicker; + + @FXML + private ComboBox relativeDate; + + @FXML + private Label invalidDate; + + + protected final SimpleStringProperty relativeDateProperty = new SimpleStringProperty(null); + protected final SimpleObjectProperty enableDateProperty = new SimpleObjectProperty<>(); + protected final BooleanProperty invalidDateProperty = new SimpleBooleanProperty(); + + public void initialize(){ + invalidDate.setTextFill(Color.RED); + relativeDate.valueProperty().bindBidirectional(relativeDateProperty); + enabledDatePicker.dateTimeValueProperty().bindBidirectional(enableDateProperty); + invalidDate.visibleProperty().bind(invalidDateProperty); + enableDateProperty.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + relativeDateProperty.setValue(null); + } + }); + + + enabledDatePicker.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue && invalidDateProperty.get()){ + invalidDateProperty.set(false); + } + }); + + relativeDate.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue && invalidDateProperty.get()){ + invalidDateProperty.set(false); + } + }); + + relativeDateProperty.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + enableDateProperty.setValue(null); + } + }); + + enabledDatePicker.setDayCellFactory(picker -> new DateCell() { + @Override + public void updateItem(LocalDate date, boolean empty) { + super.updateItem(date, empty); + setDisable(empty || date.isBefore(LocalDate.now())); + } + }); + + enabledDatePicker.addEventHandler(KeyEvent.KEY_PRESSED, keyEvent -> { + if (keyEvent.getCode() == KeyCode.ENTER) { + try { + TextFormatter tf = enabledDatePicker.getEditor().getTextFormatter(); + @SuppressWarnings("unchecked") + StringConverter conv = + (StringConverter) tf.getValueConverter(); + conv.fromString(enabledDatePicker.getEditor().getText()); + enableDateProperty.set(conv.fromString(enabledDatePicker.getEditor().getText())); + enabledDatePicker.getEditor().commitValue(); + } catch (DateTimeParseException ex) { + keyEvent.consume(); + } + } + }); + + + String[] shelvingOptions = new String[AlarmSystem.shelving_options.length + 1]; + System.arraycopy(AlarmSystem.shelving_options, 0, shelvingOptions, 1, AlarmSystem.shelving_options.length); + relativeDate.getItems().addAll(shelvingOptions); + + + } + + public void setDefaultDate(LocalDateTime date){ + enableDateProperty.set(date); + } + + + /** + * @param enableDate A non-null {@link LocalDateTime} + * @return true if the specified date/time is considered valid, e.g. in the future. + */ + private boolean isEnableDateValid(LocalDateTime enableDate) { + return !enableDate.isBefore(LocalDateTime.now()) && !enableDate.isEqual(LocalDateTime.now()); + } + + + /** + * Attempts to determine a {@link LocalDateTime} based on the user input. + * + * @return A non-null {@link LocalDateTime} if user has specified a valid date/time, or null if + * there is no user input from which to determine a date/time. + * @throws IllegalArgumentException if user has entered an invalid date/time. + */ + public LocalDateTime determineEnableDate() { + + if (enableDateProperty.isNotNull().get()) { + if (isEnableDateValid(enableDateProperty.get())) { + return enableDateProperty.get(); + } else { + invalidDateProperty.set(true); + throw new IllegalArgumentException("Enable date invalid"); + } + } else if (relativeDateProperty.isNotNull().get()) { + final TemporalAmount amount = + TimeParser.parseTemporalAmount(relativeDateProperty.get()); + final LocalDateTime updateDate = LocalDateTime.now().plus(amount); + if (isEnableDateValid(updateDate)) { + return updateDate; + } else { + invalidDateProperty.set(true); + throw new IllegalArgumentException("Enable date invalid"); + } + } + return null; + } +} diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java index dac36bff57..f3d65c9801 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java @@ -617,8 +617,9 @@ else if (selection.size() == 1) if (selection.size() >= 1) { menu_items.add(new EnableComponentAction(tree_view, model, selection)); - menu_items.add(new DisableComponentAction(tree_view, model, selection)); + menu_items.add(new DisableAction(tree_view, model, selection)); menu_items.add(new RemoveComponentAction(tree_view, model, selection)); + } } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/DisableAction.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/DisableAction.java new file mode 100644 index 0000000000..fa762b0dd0 --- /dev/null +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/DisableAction.java @@ -0,0 +1,189 @@ +package org.phoebus.applications.alarm.ui.tree; + +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import org.phoebus.applications.alarm.client.AlarmClientLeaf; +import org.phoebus.applications.alarm.ui.AlarmUI; +import org.phoebus.applications.alarm.ui.config.DisableUntilDialogController; +import org.phoebus.framework.jobs.JobManager; +import org.phoebus.framework.nls.NLS; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; +import org.phoebus.ui.javafx.ImageCache; + +import javafx.scene.Node; + +import org.phoebus.applications.alarm.client.AlarmClient; +import org.phoebus.applications.alarm.model.AlarmTreeItem; + +import org.phoebus.applications.alarm.ui.Messages; + +import java.io.IOException; +import java.text.MessageFormat; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +public class DisableAction extends Menu { + + private AlarmClient alarmClient; + + public DisableAction(final Node node, final AlarmClient model, final List> items) { + this.alarmClient = model; + setText(Messages.disableMenu); + setGraphic(ImageCache.getImageView(AlarmUI.class, "/icons/disabled.png")); + MenuItem disable = new DisableComponentAction(node, model, items); + MenuItem disableUntil = new MenuItem(Messages.disabledUntil); + disableUntil.setDisable(true); + Set totalLeafItems = new HashSet<>(); + Set leafItemsWithEnableDate = new HashSet<>(); + setOnShowing(e -> { + + new Thread(() -> { + if (checkEnableDates(items, totalLeafItems, leafItemsWithEnableDate)) { + Platform.runLater(() -> disableUntil.setDisable(false)); + } + + }).start(); + + + }); + disableUntil.setOnAction(e -> { + final FXMLLoader fxmlLoader = new FXMLLoader(); + fxmlLoader.setResources(NLS.getMessages(Messages.class)); + fxmlLoader.setLocation(DisableUntilDialogController.class.getResource("DisableUntilDialog.fxml")); + + final GridPane root; + try { + root = fxmlLoader.load(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + DisableUntilDialogController dialogController = fxmlLoader.getController(); + + + if (!leafItemsWithEnableDate.isEmpty()){ + LocalDateTime defaultDate = leafItemsWithEnableDate.iterator().next().getEnabledDate(); + dialogController.setDefaultDate(defaultDate); + } + + + final Dialog dlg = new Dialog<>(); + dlg.setTitle("Disable until"); + dlg.getDialogPane().setContent(root); + dlg.getDialogPane().getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK); + dlg.setResultConverter(button -> { + if (button.equals(ButtonType.OK)) { + return dialogController.determineEnableDate(); + } + return null; + }); + Optional localDateTime = dlg.showAndWait(); + if (localDateTime.isPresent()) { + updateEnablement(localDateTime.get(), totalLeafItems); + System.out.println(localDateTime.get()); + } + + }); + + getItems().addAll(disable, disableUntil); + } + + /** + * Divides items the user clicked on in leaf items and non leaf items + * Returns true when all leaf items of the same structure either have no enable dates or all the same + * Returns false if the enable dates differ + * + * @param items Root item + * @param totalLeafItems {@link Set} that will hold all leaf nodes + * @param leafItemsWithEnableDate {@link Set} that will hold all leaf nodes with non-null enable date + * + */ + + public static boolean checkEnableDates(final List> items, Set totalLeafItems, Set leafItemsWithEnableDate) { + Set> nonLeafItems = + items.stream().filter(i -> !(i instanceof AlarmClientLeaf)).collect(Collectors.toSet()); + Set> leafItems = + items.stream().filter(i -> (i instanceof AlarmClientLeaf)).collect(Collectors.toSet()); + nonLeafItems.forEach(i -> findAffectedPVs(i, totalLeafItems, leafItemsWithEnableDate)); + leafItems.forEach(i -> findAffectedPVs(i, totalLeafItems, leafItemsWithEnableDate)); + if (leafItemsWithEnableDate.isEmpty()) { + return true; + } else if (totalLeafItems.size() != leafItemsWithEnableDate.size()) { + return false; + } else { + LocalDateTime firstDate = leafItemsWithEnableDate.iterator().next().getEnabledDate(); + for (AlarmClientLeaf alarmClientLeaf : totalLeafItems) { + LocalDateTime currDate = alarmClientLeaf.getEnabledDate(); + if (!firstDate.equals(currDate)) { + return false; + } + } + return true; + } + } + + + /** + * Recursively counts alarm tree items in a subtree to find total number and + * number of disabled with enable date. + * + * @param item Root item + * @param total {@link Set} that will hold all leaf nodes + * @param withEnableDate {@link Set} that will hold all leaf nodes with non-null enable date + * + */ + public static void findAffectedPVs(final AlarmTreeItem item, final Set total, final Set withEnableDate) { + if (item instanceof AlarmClientLeaf) { + final AlarmClientLeaf pv = (AlarmClientLeaf) item; + total.add(pv); + if (pv.getEnabledDate() != null) { + withEnableDate.add(pv); + } + } else { + for (AlarmTreeItem sub : item.getChildren()) { + findAffectedPVs(sub, total, withEnableDate); + } + } + } + + + /** + * Updates a component to disable a hierarchy of PVs with an enable date. + * + * @param enableDate The {@link LocalDateTime} to set on all leaf nodes specified in items . + */ + private void updateEnablement(LocalDateTime enableDate, Set totalLeafItems) { + if (totalLeafItems.isEmpty()) { + return; + } + if (totalLeafItems.size() > 1) { + final Alert dialog = new Alert(Alert.AlertType.CONFIRMATION); + dialog.setTitle(Messages.disableAlarms); + dialog.setHeaderText(MessageFormat.format(Messages.headerConfirmDisableWithEnableDate, enableDate, totalLeafItems.size())); + + if (dialog.showAndWait().get() != ButtonType.OK) { + return; + } + } + + JobManager.schedule(Messages.disableAlarms, monitor -> + { + for (AlarmClientLeaf pv : totalLeafItems) { + final AlarmClientLeaf copy = pv.createDetachedCopy(); + if (copy.setEnabledDate(enableDate)) { + try { + alarmClient.sendItemConfigurationUpdate(pv.getPathName(), copy); + } catch (Exception e) { + ExceptionDetailsErrorDialog.openError(Messages.error, + Messages.disableAlarmFailed, + e); + throw e; + } + } + } + }); + } +} diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/EnableComponentAction.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/EnableComponentAction.java index 625d3513e0..e5a8d0b96a 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/EnableComponentAction.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/EnableComponentAction.java @@ -8,6 +8,7 @@ package org.phoebus.applications.alarm.ui.tree; import javafx.scene.Node; +import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import org.phoebus.applications.alarm.client.AlarmClient; import org.phoebus.applications.alarm.model.AlarmTreeItem; @@ -30,12 +31,12 @@ class EnableComponentAction extends MenuItem { * @param items Items to enable */ public EnableComponentAction(final Node node, final AlarmClient model, final List> items) { + if (doEnable()) { setText(Messages.enableAlarms); setGraphic(ImageCache.getImageView(AlarmUI.class, "/icons/enabled.png")); } else { setText(Messages.disableAlarms); - setGraphic(ImageCache.getImageView(AlarmUI.class, "/icons/disabled.png")); } setOnAction(event -> ComponentActionHelper.updateEnablement(node, model, items, doEnable())); diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/RemoveComponentAction.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/RemoveComponentAction.java index 8844ab90c7..20b82468c1 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/RemoveComponentAction.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/RemoveComponentAction.java @@ -10,6 +10,7 @@ import java.util.List; import javafx.application.Platform; +import javafx.scene.control.*; import org.phoebus.applications.alarm.client.AlarmClient; import org.phoebus.applications.alarm.model.AlarmTreeItem; import org.phoebus.applications.alarm.ui.Messages; @@ -19,11 +20,7 @@ import org.phoebus.ui.javafx.ImageCache; import javafx.scene.Node; -import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.ButtonType; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TreeView; /** Action that deletes item from the alarm tree configuration * @author Kay Kasemir diff --git a/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/DisableUntilDialog.fxml b/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/DisableUntilDialog.fxml new file mode 100644 index 0000000000..006a7f083b --- /dev/null +++ b/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/DisableUntilDialog.fxml @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/messages.properties b/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/messages.properties index c2c66a719d..2d263dd797 100644 --- a/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/messages.properties +++ b/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/messages.properties @@ -17,6 +17,7 @@ # # acknowledgeFailed=Failed to acknowledge alarm(s) +absoluteDateLabel=Absolute Date: addComponentFailed=Failed to add component alarmCount=Alarm Count [within delay]: alarmCountTooltip=Alarms are indicated when they occur this often within the delay @@ -39,6 +40,7 @@ detail=Detail disabled=Disabled disableAlarmFailed=Failed to disable alarm disableAlarms=Disable Alarms +disableMenu=Disable... disabledUntil=Disabled until disableUntil=Disable until: displays=Displays: @@ -55,6 +57,7 @@ headerConfirmDisable=Disable all PVs in the selected section of the alarm hierar headerConfirmDisableWithEnableDate=Disable all PVs in the selected section of the alarm hierarchy until {0}?\nThis would affect {1} PVs. headerConfirmEnable=Enable all PVs in the selected section of the alarm hierarchy?\nThis would enable {0} PVs. info=Info +invalidDate=You have selected a date that is either invalid or in the past latch=Latch latchTooltip=Latch alarm until acknowledged? moveItemFailed=Failed to move item @@ -62,8 +65,9 @@ option=Option partlyDisabled=Partly disabled partlyDisabled2=(Partly disabled) path=Path: -promptTitle='Disable until' is set to a point in time in the past +promptTitle=Absolute Date is set to a point in time in the past promptContent=The option 'disable until' must be set to a point in time in the future. +relativeDateLabel=Relative Date: relativeDateTooltip=Select a predefined duration for disabling the alarm removeComponentFailed=Failed to remove component renameItemFailed=Failed to rename item diff --git a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/ui/tree/CheckEnableDatesTest.java b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/ui/tree/CheckEnableDatesTest.java new file mode 100644 index 0000000000..edba2cef87 --- /dev/null +++ b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/ui/tree/CheckEnableDatesTest.java @@ -0,0 +1,74 @@ +package org.phoebus.applications.alarm.ui.tree; + +import org.junit.jupiter.api.Test; +import org.phoebus.applications.alarm.client.AlarmClientLeaf; +import org.phoebus.applications.alarm.model.AlarmTreeItem; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CheckEnableDatesTest { + + AlarmClientLeaf leaf0 = new AlarmClientLeaf("test/path/0", "testName0"); + AlarmClientLeaf leaf1 = new AlarmClientLeaf("test/path/1", "testName1"); + AlarmClientLeaf leaf2 = new AlarmClientLeaf("test/path/2", "testName2"); + + List> items = new ArrayList<>(); + Set totalLeafItems = new HashSet<>(); + Set leavesWithEnableDate = new HashSet<>(); + + LocalDateTime testTime = LocalDateTime.now().plusDays(3); + + @Test + public void noEnableDates(){ + items.add(leaf0); + items.add(leaf1); + items.add(leaf2); + + assertTrue(DisableAction.checkEnableDates(items, totalLeafItems, leavesWithEnableDate)); + } + + @Test + public void identicalEnableDates(){ + leaf0.setEnabledDate(testTime); + leaf1.setEnabledDate(testTime); + leaf2.setEnabledDate(testTime); + + items.add(leaf0); + items.add(leaf1); + items.add(leaf2); + + assertTrue(DisableAction.checkEnableDates(items, totalLeafItems, leavesWithEnableDate)); + } + + @Test + public void differentEnableDates(){ + leaf0.setEnabledDate(testTime); + leaf1.setEnabledDate(testTime.plusDays(1)); + leaf2.setEnabledDate(testTime); + + items.add(leaf0); + items.add(leaf1); + items.add(leaf2); + + assertFalse(DisableAction.checkEnableDates(items, totalLeafItems, leavesWithEnableDate)); + } + + @Test + public void sameSize(){ + leaf0.setEnabledDate(testTime); + leaf1.setEnabledDate(testTime); + + items.add(leaf0); + items.add(leaf1); + items.add(leaf2); + + assertFalse(DisableAction.checkEnableDates(items, totalLeafItems, leavesWithEnableDate)); + } +} From 3638e30a09616befd80505cbce0833c9549e1bfc Mon Sep 17 00:00:00 2001 From: milenaschedle Date: Thu, 2 Jul 2026 09:10:30 +0200 Subject: [PATCH 2/3] Restructured The Component Dialog of the alarm tree plus cleanup --- .../ComponentConfigDialogController.java | 222 ------------------ .../ui/config/ConfigDialogController.java | 169 +------------ .../alarm/ui/config/ItemConfigDialog.java | 9 +- .../ui/config/LeafConfigDialogController.java | 61 ++--- ...entConfigDialog.fxml => ConfigDialog.fxml} | 30 +-- .../alarm/ui/config/LeafConfigDialog.fxml | 37 +-- .../DisableActionTest.java} | 14 +- 7 files changed, 71 insertions(+), 471 deletions(-) delete mode 100644 app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ComponentConfigDialogController.java rename app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/{ComponentConfigDialog.fxml => ConfigDialog.fxml} (53%) rename app/alarm/ui/src/test/java/org/phoebus/applications/alarm/ui/{config/ComponentConfigDialogControllerTest.java => tree/DisableActionTest.java} (75%) diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ComponentConfigDialogController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ComponentConfigDialogController.java deleted file mode 100644 index a9ef27d0ad..0000000000 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ComponentConfigDialogController.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2025 European Spallation Source ERIC. - */ -package org.phoebus.applications.alarm.ui.config; - -import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.TextField; -import javafx.scene.layout.HBox; -import org.phoebus.applications.alarm.client.AlarmClient; -import org.phoebus.applications.alarm.client.AlarmClientLeaf; -import org.phoebus.applications.alarm.model.AlarmTreeItem; -import org.phoebus.applications.alarm.ui.Messages; -import org.phoebus.applications.alarm.ui.tree.ComponentActionHelper; -import org.phoebus.framework.jobs.JobManager; -import org.phoebus.ui.dialog.DialogHelper; -import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; - -import java.text.MessageFormat; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * FXML controller for LeafConfigDialog.fxml. - */ -@SuppressWarnings("nls") -public class ComponentConfigDialogController extends ConfigDialogController { - - - // ── FXML-injected fields ────────────────────────────────────────────────── - - @SuppressWarnings("unused") - @FXML - private ScrollPane scroll; - @SuppressWarnings("unused") - @FXML - private javafx.scene.layout.GridPane layout; - - // Path row (always visible) - @SuppressWarnings("unused") - @FXML - private TextField path; - - // Leaf-only rows - @SuppressWarnings("unused") - @FXML - private Label descriptionLabel; - @SuppressWarnings("unused") - @FXML - private TextField description; - - @SuppressWarnings("unused") - @FXML - private Label behaviorLabel; - @SuppressWarnings("unused") - @FXML - private HBox behaviorBox; - @SuppressWarnings("unused") - @FXML - private CheckBox enabled; - - @SuppressWarnings("unused") - @FXML - private Label disableUntilLabel; - @SuppressWarnings("unused") - @FXML - private ComboBox relativeDate; - - @SuppressWarnings("unused") - @FXML - private DateTimePicker enabledDatePicker; - - @SuppressWarnings("unused") - @FXML - private Label partlyDisabledLabel; - - private List alarmClientLeaves; - - public ComponentConfigDialogController(AlarmClient alarmClient, AlarmTreeItem alarmTreeItem) { - super(alarmClient, alarmTreeItem); - } - - @SuppressWarnings("unused") - @FXML - public void initialize() { - - super.initialize(); - - alarmClientLeaves = new ArrayList<>(); - List disabled = new ArrayList<>(); - List withEnableDate = new ArrayList<>(); - // Check subtree for disabled PVs and PVs with non-null enable date - findAffectedPVs(alarmTreeItem, alarmClientLeaves, disabled, withEnableDate); - - if(disabled.isEmpty()) { - enabled.setSelected(true); - itemEnabledProperty.setValue(true); - } - else if (alarmClientLeaves.size() != disabled.size()) { - partlyDisabledLabel.setVisible(true); - enabled.setSelected(false); - itemEnabledProperty.setValue(false); - } - - if (!withEnableDate.isEmpty()) { - relativeDate.setDisable(true); - enabledDatePicker.setDisable(true); - } - } - - /** - * Validates input and sends the configuration off to the message broker. - * - */ - public void validateAndStore() { - - // First check if user has specified a valid enable date - LocalDateTime enableDate; - try { - enableDate = determineEnableDate(); - } catch (Exception e) { - Logger.getLogger(LeafConfigDialogController.class.getName()) - .log(Level.WARNING, "Invalid enable date specified", e); - return; - } - - alarmTreeItem.setGuidance(optionsTablesViewController.getGuidance()); - alarmTreeItem.setDisplays(optionsTablesViewController.getDisplays()); - alarmTreeItem.setCommands(optionsTablesViewController.getCommands()); - alarmTreeItem.setActions(optionsTablesViewController.getActions()); - - try { - alarmClient.sendItemConfigurationUpdate(alarmTreeItem.getPathName(), alarmTreeItem); - } catch (Exception ex) { - ExceptionDetailsErrorDialog.openError("Error", "Cannot update item", ex); - return; - } - - // Lastly update enable or - if non-null - set enable date. - if (enableDate != null) { - updateEnablement(enableDate); - } else { - ComponentActionHelper.updateEnablement(scroll, alarmClient, List.of(alarmTreeItem), itemEnabledProperty.get()); - } - } - -/// ////////// REMOVE//////////////// - /** - * Updates a component to disable a hierarchy of PVs with an enable date. - * - * @param enableDate The {@link LocalDateTime} to set on all leaf nodes specified in items . - */ - private void updateEnablement(LocalDateTime enableDate) { - if (alarmClientLeaves.isEmpty()) { - return; - } - if (alarmClientLeaves.size() > 1) { - final Alert dialog = new Alert(Alert.AlertType.CONFIRMATION); - dialog.setTitle(Messages.disableAlarms); - dialog.setHeaderText(MessageFormat.format(Messages.headerConfirmDisableWithEnableDate, enableDate, alarmClientLeaves.size())); - - DialogHelper.positionDialog(dialog, scroll, -50, -25); - if (dialog.showAndWait().get() != ButtonType.OK) { - return; - } - } - - JobManager.schedule(Messages.disableAlarms, monitor -> - { - for (AlarmClientLeaf pv : alarmClientLeaves) { - final AlarmClientLeaf copy = pv.createDetachedCopy(); - if (copy.setEnabledDate(enableDate)) { - try { - alarmClient.sendItemConfigurationUpdate(pv.getPathName(), copy); - } catch (Exception e) { - ExceptionDetailsErrorDialog.openError(Messages.error, - Messages.disableAlarmFailed, - e); - throw e; - } - } - } - }); - } - - /////////////////// CAUTION!! MOVED THIS TO DisablesAction.java!! (the Set-Version, not the List-Version) /////////////////////////// - /** - * Recursively counts alarm tree items in a subtree to find total number, number of disabled, and - * number of disabled with enable date. - * - * @param item Root item - * @param total {@link AtomicInteger} that will hold the total number of leaf nodes - * @param disabled {@link AtomicInteger} that will hold the number of disabled leaf nodes (with or without enable date) - * @param withEnableDate {@link AtomicInteger} that will hold the number of leaf nodes with non-null enable date - * - */ - public static void findAffectedPVs(final AlarmTreeItem item, final List total, final List disabled, final List withEnableDate) { - if (item instanceof AlarmClientLeaf) { - final AlarmClientLeaf pv = (AlarmClientLeaf) item; - total.add(pv); - if (!pv.isEnabled()) { - disabled.add(pv); - if (pv.getEnabledDate() != null) { - withEnableDate.add(pv); - } - } - } else { - for (AlarmTreeItem sub : item.getChildren()) { - findAffectedPVs(sub, total, disabled, withEnableDate); - } - } - } -} diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ConfigDialogController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ConfigDialogController.java index 58944e389a..b0ab443b1d 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ConfigDialogController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ConfigDialogController.java @@ -4,35 +4,15 @@ package org.phoebus.applications.alarm.ui.config; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ComboBox; -import javafx.scene.control.DateCell; import javafx.scene.control.ScrollPane; import javafx.scene.control.TextField; -import javafx.scene.control.TextFormatter; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; import javafx.scene.layout.GridPane; -import javafx.scene.layout.StackPane; -import javafx.util.StringConverter; -import org.phoebus.applications.alarm.AlarmSystem; import org.phoebus.applications.alarm.client.AlarmClient; import org.phoebus.applications.alarm.model.AlarmTreeItem; -import org.phoebus.applications.alarm.ui.Messages; -import org.phoebus.ui.dialog.DialogHelper; -import org.phoebus.util.time.TimeParser; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeParseException; -import java.time.temporal.TemporalAmount; - -public abstract class ConfigDialogController { +public class ConfigDialogController { @SuppressWarnings("unused") @FXML @@ -46,32 +26,6 @@ public abstract class ConfigDialogController { @FXML private TextField path; - @SuppressWarnings("unused") - @FXML - protected CheckBox enabled; - - @SuppressWarnings("unused") - @FXML - protected ComboBox relativeDate; - - - @SuppressWarnings("unused") - @FXML - protected DateTimePicker enabledDatePicker; - - // Shared table placeholders - @SuppressWarnings("unused") - @FXML - private StackPane guidancePlaceholder; - @SuppressWarnings("unused") - @FXML - private StackPane displaysPlaceholder; - @SuppressWarnings("unused") - @FXML - private StackPane commandsPlaceholder; - @SuppressWarnings("unused") - @FXML - private StackPane actionsPlaceholder; @FXML protected OptionsTablesController optionsTablesViewController; @@ -79,10 +33,6 @@ public abstract class ConfigDialogController { protected final AlarmClient alarmClient; protected final AlarmTreeItem alarmTreeItem; - protected final SimpleBooleanProperty itemEnabledProperty = new SimpleBooleanProperty(); - protected final SimpleStringProperty relativeDateProperty = new SimpleStringProperty(null); - protected final SimpleObjectProperty enableDateProperty = - new SimpleObjectProperty<>(null); public ConfigDialogController(AlarmClient alarmClient, AlarmTreeItem alarmTreeItem) { this.alarmClient = alarmClient; @@ -94,115 +44,22 @@ public void initialize() { path.setText(alarmTreeItem.getPathName()); - relativeDate.valueProperty().bindBidirectional(relativeDateProperty); - enabledDatePicker.dateTimeValueProperty().bindBidirectional(enableDateProperty); - - enabled.setOnAction(e -> { - itemEnabledProperty.setValue(enabled.isSelected()); - relativeDateProperty.set(null); - enableDateProperty.set(null); - }); - - enableDateProperty.addListener((observable, oldValue, newValue) -> { - enabled.setSelected(newValue == null && relativeDateProperty.isNull().get()); - if (newValue != null) { - relativeDateProperty.setValue(null); - } - }); - - relativeDateProperty.addListener((observable, oldValue, newValue) -> { - enabled.setSelected(newValue == null && enableDateProperty.isNull().get()); - if (newValue != null) { - enableDateProperty.setValue(null); - } - }); - - // Day-cell factory – disable past dates - enabledDatePicker.setDayCellFactory(picker -> new DateCell() { - @Override - public void updateItem(LocalDate date, boolean empty) { - super.updateItem(date, empty); - setDisable(empty || date.isBefore(LocalDate.now())); - } - }); - - // ENTER key handler on the date picker's editor - enabledDatePicker.addEventHandler(KeyEvent.KEY_PRESSED, keyEvent -> { - if (keyEvent.getCode() == KeyCode.ENTER) { - try { - TextFormatter tf = enabledDatePicker.getEditor().getTextFormatter(); - @SuppressWarnings("unchecked") - StringConverter conv = - (StringConverter) tf.getValueConverter(); - conv.fromString(enabledDatePicker.getEditor().getText()); - enableDateProperty.set(conv.fromString(enabledDatePicker.getEditor().getText())); - enabledDatePicker.getEditor().commitValue(); - } catch (DateTimeParseException ex) { - keyEvent.consume(); - } - } - }); - - // Make sure first element in shelving options is null - // so user can "deselect" a relative date. - String[] shelvingOptions = new String[AlarmSystem.shelving_options.length + 1]; - System.arraycopy(AlarmSystem.shelving_options, 0, shelvingOptions, 1, AlarmSystem.shelving_options.length); - relativeDate.getItems().addAll(shelvingOptions); - - // ── Scroll-pane width listener ──────────────────────────────────────── +// // ── Scroll-pane width listener ──────────────────────────────────────── scroll.widthProperty().addListener((p, old, width) -> layout.setPrefWidth(Math.max(width.doubleValue() - 40, 450))); } - /** - * Attempts to determine a {@link LocalDateTime} based on the user input. - * - * @return A non-null {@link LocalDateTime} if user has specified a valid date/time, or null if - * there is no user input from which to determine a date/time. - * @throws IllegalArgumentException if user has entered an invalid date/time. - */ - protected LocalDateTime determineEnableDate() { - - if (enableDateProperty.isNotNull().get()) { - if (isEnableDateValid(enableDateProperty.get())) { - return enableDateProperty.get(); - } else { - showInvalidEnableDateDialog(); - throw new IllegalArgumentException("Enable date invalid"); - } - } else if (relativeDateProperty.isNotNull().get()) { - final TemporalAmount amount = - TimeParser.parseTemporalAmount(relativeDateProperty.get()); - final LocalDateTime updateDate = LocalDateTime.now().plus(amount); - if (isEnableDateValid(updateDate)) { - return updateDate; - } else { - showInvalidEnableDateDialog(); - throw new IllegalArgumentException("Enable date invalid"); - } - } - return null; - } - /** - * @param enableDate A non-null {@link LocalDateTime} - * @return true if the specified date/time is considered valid, e.g. in the future. - */ - private boolean isEnableDateValid(LocalDateTime enableDate) { - return !enableDate.isBefore(LocalDateTime.now()) && !enableDate.isEqual(LocalDateTime.now()); - } + public void validateAndStore(){ + alarmTreeItem.setGuidance(optionsTablesViewController.getGuidance()); + alarmTreeItem.setDisplays(optionsTablesViewController.getDisplays()); + alarmTreeItem.setCommands(optionsTablesViewController.getCommands()); + alarmTreeItem.setActions(optionsTablesViewController.getActions()); - /** - * Shows a dialog indicate that the user-specified date is invalid, e.g. a date/time not in the future. - */ - private void showInvalidEnableDateDialog() { - Alert prompt = new Alert(Alert.AlertType.INFORMATION); - prompt.setTitle(Messages.promptTitle); - prompt.setHeaderText(Messages.promptTitle); - prompt.setContentText(Messages.promptContent); - DialogHelper.positionDialog(prompt, enabledDatePicker, 0, 0); - prompt.showAndWait(); + try { + alarmClient.sendItemConfigurationUpdate(alarmTreeItem.getPathName(), alarmTreeItem); + } catch (Exception ex) { + ExceptionDetailsErrorDialog.openError("Error", "Cannot update item", ex); + } } - - public abstract void validateAndStore(); } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ItemConfigDialog.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ItemConfigDialog.java index 719a171a3e..6ba3ea814c 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ItemConfigDialog.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/ItemConfigDialog.java @@ -44,16 +44,21 @@ public ItemConfigDialog(final AlarmClient model, final AlarmTreeItem item) { fxmlLoader.setLocation(this.getClass().getResource("LeafConfigDialog.fxml")); } else{ - fxmlLoader.setLocation(this.getClass().getResource("ComponentConfigDialog.fxml")); + // renames component config dialog fxml to config dialig + fxmlLoader.setLocation(this.getClass().getResource("ConfigDialog.fxml")); } fxmlLoader.setControllerFactory(clazz -> { try { if(clazz.isAssignableFrom(LeafConfigDialogController.class)) { return clazz.getConstructor(AlarmClient.class, AlarmTreeItem.class).newInstance(model, item); } - if(clazz.isAssignableFrom(ComponentConfigDialogController.class)) { + // GEORG is that ok? + if(clazz.isAssignableFrom(ConfigDialogController.class)) { return clazz.getConstructor(AlarmClient.class, AlarmTreeItem.class).newInstance(model, item); } +// if(clazz.isAssignableFrom(ComponentConfigDialogController.class)) { +// return clazz.getConstructor(AlarmClient.class, AlarmTreeItem.class).newInstance(model, item); +// } else if(clazz.isAssignableFrom(TitleDetailTableController.class)) { return new TitleDetailTableController(); } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/LeafConfigDialogController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/LeafConfigDialogController.java index 59226d8275..0f85f7c788 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/LeafConfigDialogController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/config/LeafConfigDialogController.java @@ -9,28 +9,22 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.fxml.FXML; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Spinner; -import javafx.scene.control.SpinnerValueFactory; -import javafx.scene.control.TextField; -import javafx.scene.control.TextFormatter; -import javafx.scene.control.Tooltip; +import javafx.scene.control.*; import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; import javafx.util.Duration; + import org.phoebus.applications.alarm.client.AlarmClient; import org.phoebus.applications.alarm.client.AlarmClientLeaf; import org.phoebus.applications.alarm.model.AlarmTreeItem; import org.phoebus.applications.alarm.ui.Messages; import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; import org.phoebus.util.time.SecondsParser; - import java.text.MessageFormat; import java.text.NumberFormat; import java.text.ParsePosition; -import java.time.LocalDateTime; import java.util.function.UnaryOperator; -import java.util.logging.Level; -import java.util.logging.Logger; + /** * FXML controller for LeafConfigDialog.fxml. Intended for configuration @@ -61,6 +55,23 @@ public class LeafConfigDialogController extends ConfigDialogController { @FXML private TextField filter; + @FXML + protected CheckBox enabled; + + @SuppressWarnings("unused") + @FXML + private StackPane guidancePlaceholder; + @SuppressWarnings("unused") + @FXML + private StackPane displaysPlaceholder; + @SuppressWarnings("unused") + @FXML + private StackPane commandsPlaceholder; + @SuppressWarnings("unused") + @FXML + private StackPane actionsPlaceholder; + + private final SimpleStringProperty descriptionProperty = new SimpleStringProperty(""); private final SimpleBooleanProperty latchingProperty = new SimpleBooleanProperty(); private final SimpleBooleanProperty annunciatingProperty = new SimpleBooleanProperty(); @@ -69,6 +80,9 @@ public class LeafConfigDialogController extends ConfigDialogController { private SpinnerValueFactory countValueFactory; private SpinnerValueFactory delayValueFactory; + protected final SimpleBooleanProperty itemEnabledProperty = new SimpleBooleanProperty(); + + public LeafConfigDialogController(AlarmClient alarmClient, AlarmTreeItem alarmTreeItem) { super(alarmClient, alarmTreeItem); } @@ -84,8 +98,12 @@ public void initialize() { annunciating.selectedProperty().bindBidirectional(annunciatingProperty); filter.textProperty().bindBidirectional(enablingFilterProperty); - relativeDate.disableProperty().bind(itemEnabledProperty.not()); - enabledDatePicker.disableProperty().bind(itemEnabledProperty.not()); + + + enabled.setOnAction(e -> { + itemEnabledProperty.setValue(enabled.isSelected()); + }); + AlarmClientLeaf leaf = (AlarmClientLeaf) alarmTreeItem; @@ -117,7 +135,6 @@ public void initialize() { descriptionProperty.set(leaf.getDescription()); enablingFilterProperty.set(leaf.getFilter()); - enableDateProperty.set(leaf.getEnabledDate()); // Behavior checkboxes itemEnabledProperty.setValue(leaf.isEnabled()); @@ -125,9 +142,9 @@ public void initialize() { latchingProperty.setValue(leaf.isLatching()); annunciatingProperty.setValue(leaf.isAnnunciating()); + BooleanBinding binding = Bindings.createBooleanBinding(() -> - itemEnabledProperty.not().get() || relativeDateProperty.isNotNull().get() || enableDateProperty.isNotNull().get(), - itemEnabledProperty, relativeDateProperty, enableDateProperty); + itemEnabledProperty.not().get(), itemEnabledProperty); latching.disableProperty().bind(binding); annunciating.disableProperty().bind(binding); @@ -162,21 +179,9 @@ public void validateAndStore() { final AlarmClientLeaf pv = new AlarmClientLeaf(null, alarmTreeItem.getName()); - LocalDateTime enableDate; - try { - enableDate = determineEnableDate(); - } catch (Exception e) { - Logger.getLogger(LeafConfigDialogController.class.getName()) - .log(Level.WARNING, "Invalid enable date specified", e); - return; - } - if (enableDate != null) { - pv.setEnabledDate(enableDate); - } else { - pv.setEnabled(itemEnabledProperty.get()); - } pv.setDescription(descriptionProperty.get()); + pv.setEnabled(itemEnabledProperty.get()); pv.setLatching(latchingProperty.get()); pv.setAnnunciating(annunciatingProperty.get()); pv.setDelay(delayValueFactory.getValue()); diff --git a/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/ComponentConfigDialog.fxml b/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/ConfigDialog.fxml similarity index 53% rename from app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/ComponentConfigDialog.fxml rename to app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/ConfigDialog.fxml index b983bec3ae..f16d936ca5 100644 --- a/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/ComponentConfigDialog.fxml +++ b/app/alarm/ui/src/main/resources/org/phoebus/applications/alarm/ui/config/ConfigDialog.fxml @@ -3,9 +3,8 @@ - - + @@ -25,33 +24,6 @@