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 f36654a84d..18455f7dfa 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 @@ -16,10 +16,14 @@ public class Messages public static String acknowledgeFailed; public static String addComponentFailed; public static String disableAlarmFailed; + public static String disabled; + public static String disabledUntil; public static String enableAlarmFailed; public static String moveItemFailed; + public static String partlyDisabled; public static String removeComponentFailed; public static String renameItemFailed; + public static String timer; public static String unacknowledgeFailed; static diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeViewCell.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeViewCell.java index d9fe93624f..a2646d2e10 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeViewCell.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeViewCell.java @@ -7,22 +7,28 @@ *******************************************************************************/ package org.phoebus.applications.alarm.ui.tree; -import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.TreeCell; import javafx.scene.image.ImageView; import javafx.scene.layout.Background; -import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.CornerRadii; import javafx.scene.layout.HBox; import javafx.scene.paint.Color; +import javafx.util.Pair; import org.phoebus.applications.alarm.client.AlarmClientLeaf; import org.phoebus.applications.alarm.client.AlarmClientNode; import org.phoebus.applications.alarm.client.ClientState; import org.phoebus.applications.alarm.model.AlarmTreeItem; import org.phoebus.applications.alarm.model.SeverityLevel; import org.phoebus.applications.alarm.ui.AlarmUI; +import org.phoebus.applications.alarm.ui.Messages; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; /** TreeCell for AlarmTreeItem * @author Kay Kasemir @@ -40,7 +46,8 @@ class AlarmTreeViewCell extends TreeCell> // So we add our own "graphics" to hold an icon and text private final Label label = new Label(); private final ImageView image = new ImageView(); - private final HBox content = new HBox(image, label); + private final Label disabledTimerIndicator = new Label(""); + private final HBox content = new HBox(image, label, disabledTimerIndicator); // TreeCell optimizes redraws by suppressing updates // when old and new values match. @@ -61,6 +68,11 @@ protected boolean isItemChanged(final AlarmTreeItem before, final AlarmTreeIt return true; } + public AlarmTreeViewCell() { + content.setAlignment(Pos.CENTER_LEFT); + disabledTimerIndicator.setTextFill(Color.GRAY); + } + @Override protected void updateItem(final AlarmTreeItem item, final boolean empty) { @@ -79,7 +91,7 @@ protected void updateItem(final AlarmTreeItem item, final boolean empty) final StringBuilder text = new StringBuilder(); text.append("PV: ").append(leaf.getName()); - if (leaf.isEnabled() && !state.isDynamicallyDisabled()) + if (!isLeafDisabled(leaf)) { // Add alarm info if (state.severity != SeverityLevel.OK) { @@ -94,23 +106,63 @@ protected void updateItem(final AlarmTreeItem item, final boolean empty) label.setTextFill(AlarmUI.getColor(state.severity)); label.setBackground(AlarmUI.getBackground(state.severity)); image.setImage(AlarmUI.getIcon(state.severity)); - } - else - { - text.append(" (disabled)"); + + disabledTimerIndicator.setText(""); + } else { + if (leaf.getEnabled().enabled_date != null) { + LocalDateTime enabledDate = leaf.getEnabled().enabled_date; + String enabledDateString = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(enabledDate); + disabledTimerIndicator.setText("(" + Messages.disabledUntil + " " + enabledDateString + ")"); + } else { + disabledTimerIndicator.setText("(" + Messages.disabled + ")"); + } + label.setTextFill(Color.GRAY); label.setBackground(Background.EMPTY); image.setImage(AlarmUI.disabled_icon); } + label.setText(text.toString()); } else { final AlarmClientNode node = (AlarmClientNode) item; - label.setText(item.getName()); + + Optional> maybeLeavesDisabledStatusBooleanPair = leavesDisabledStatus(node); + if (maybeLeavesDisabledStatusBooleanPair.isPresent() && !maybeLeavesDisabledStatusBooleanPair.get().getKey().equals(LeavesDisabledStatus.AllEnabled)) { + Pair leavesDisabledStatusBooleanPair = maybeLeavesDisabledStatusBooleanPair.get(); + + if (leavesDisabledStatusBooleanPair.getKey().equals(LeavesDisabledStatus.AllDisabled)) { + if (leavesDisabledStatusBooleanPair.getValue()) { + disabledTimerIndicator.setText("(" + Messages.disabled + "; " + Messages.timer + ")"); + } + else { + disabledTimerIndicator.setText("(" + Messages.disabled + ")"); + } + } + else if (leavesDisabledStatusBooleanPair.getKey().equals(LeavesDisabledStatus.SomeEnabledSomeDisabled)) { + if (leavesDisabledStatusBooleanPair.getValue()) { + disabledTimerIndicator.setText("(" + Messages.partlyDisabled + "; " + Messages.timer + ")"); + } + else { + disabledTimerIndicator.setText("(" + Messages.partlyDisabled + ")"); + } + } + } + else { + disabledTimerIndicator.setText(""); + } + + String labelText = item.getName(); + label.setText(labelText); severity = node.getState().severity; - label.setTextFill(AlarmUI.getColor(severity)); + if (maybeLeavesDisabledStatusBooleanPair.isPresent() && maybeLeavesDisabledStatusBooleanPair.get().getKey().equals(LeavesDisabledStatus.AllDisabled)) { + label.setTextFill(Color.GRAY); + } + else { + label.setTextFill(AlarmUI.getColor(severity)); + } label.setBackground(AlarmUI.getBackground(severity)); image.setImage(AlarmUI.getIcon(severity)); } @@ -119,4 +171,65 @@ protected void updateItem(final AlarmTreeItem item, final boolean empty) setGraphic(content); } } + + private boolean isLeafDisabled(AlarmClientLeaf alarmClientLeaf) { + return !alarmClientLeaf.isEnabled() || alarmClientLeaf.getState().isDynamicallyDisabled(); + } + + private enum LeavesDisabledStatus { + AllEnabled, + SomeEnabledSomeDisabled, + AllDisabled, + } + + // leavesDisabledStatus() optionally returns a pair. + // + // If a pair is _not_ returned, it means that there exist no leaves + // in 'alarmClientNode', and the disabled status is undefined. + // + // When a pair _is_ returned, the first component describes + // whether all leaves are disabled, all leaves are enabled, or whether + // some leaves are enabled and some are disabled, and the second component + // indicates whether one or more disabled leaves have a timer associated + // with them ('true'), at the end of which they will automatically become + // enabled again. When the second component is 'false' there is no + // associated timer. + private Optional> leavesDisabledStatus(AlarmClientNode alarmClientNode) { + List> leavesDisabledStatusList = new LinkedList<>(); + for (var child : alarmClientNode.getChildren()) { + if (child instanceof AlarmClientLeaf alarmClientLeaf) { + + if (isLeafDisabled(alarmClientLeaf)) { + boolean timer = alarmClientLeaf.getEnabled().enabled_date != null; + leavesDisabledStatusList.add(new Pair<>(LeavesDisabledStatus.AllDisabled, timer)); + } + else { + leavesDisabledStatusList.add(new Pair<>(LeavesDisabledStatus.AllEnabled, false)); + } + } + else if (child instanceof AlarmClientNode alarmClientNode1 && !alarmClientNode1.getChildren().isEmpty()) { + if (leavesDisabledStatus(alarmClientNode1).isPresent()) { + leavesDisabledStatusList.add(leavesDisabledStatus(alarmClientNode1).get()); + } + // If leavesDisabledStatus(alarmClientNode1).isPresent() evaluates to false, there are no leaves and therefore no result. + } + else if (child instanceof AlarmClientNode alarmClientNode1 && alarmClientNode1.getChildren().isEmpty()) { + // Don't add any LeavesDisabledStatus, since there are no leaves + } + else { + throw new RuntimeException("Missing case: " + child.getClass().getName()); + } + } + + Optional> leavesDisabledStatus = leavesDisabledStatusList.stream().reduce((status1, status2) -> { + if (status1.getKey().equals(status2.getKey())) { + return new Pair<>(status1.getKey(), status1.getValue() || status2.getValue()); + } + else { + return new Pair<>(LeavesDisabledStatus.SomeEnabledSomeDisabled, status1.getValue() || status2.getValue()); + } + }); + + return leavesDisabledStatus; + } } 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 2ed7ab7472..b2ab1ecd20 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 @@ -21,8 +21,12 @@ error=Error acknowledgeFailed=Failed to acknowledge alarm(s) addComponentFailed=Failed to add component disableAlarmFailed=Failed to disable alarm +disabled=Disabled +disabledUntil=Disabled until enableAlarmFailed=Failed to enable alarm moveItemFailed=Failed to move item +partlyDisabled=Partly disabled removeComponentFailed=Failed to remove component renameItemFailed=Failed to rename item -unacknowledgeFailed=Failed to unacknowledge alarm(s) \ No newline at end of file +timer=Timer +unacknowledgeFailed=Failed to unacknowledge alarm(s)