Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified app/alarm/ui/doc/images/configuration_editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/alarm/ui/doc/images/configuration_editor_dialogs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ public class Messages
public static String disableAlarmFailed;
public static String disableAlarms;
public static String disabled;
public static String disabledIndefinitely;
public static String disableMenu;
public static String disabledUntil;
public static String disabledCommonEnableDate;
public static String disabledVaryingEnableDate;
public static String displays;
public static String enableAlarmFailed;
public static String enableAlarms;
Expand All @@ -37,12 +40,10 @@ public class Messages
public static String headerConfirmEnable;
public static String indefinitely;
public static String moveItemFailed;
public static String partlyDisabled;
public static String promptTitle;
public static String promptContent;
public static String removeComponentFailed;
public static String renameItemFailed;
public static String timer;
public static String totalPVs;
public static String unacknowledgeFailed;
public static String withEnableDate;

Expand All @@ -52,7 +53,7 @@ public class Messages
NLS.initializeMessages(Messages.class);
}

private Messages()
private Messages()
{
// prevent instantiation
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@
*******************************************************************************/
package org.phoebus.applications.alarm.ui.tree;

import java.util.List;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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.model.AlarmTreePath;
import org.phoebus.applications.alarm.ui.Messages;
import org.phoebus.ui.dialog.DialogHelper;

import javafx.scene.control.TextInputDialog;
import javafx.scene.control.TreeView;
import org.phoebus.util.time.TimestampFormats;

/** @author Evan Smith
*/
Expand Down Expand Up @@ -104,18 +110,111 @@ public static boolean validateNewPath(String path, AlarmTreeItem<?> root)
item = item.getChild(path_elems[i]);
if (null == item)
{
// System.out.println("Path element " + path_elems[i] + " does not exist in the tree at that location.");
return false;
}
// Make sure the path does not contain a PV.
// PV cannot have children.
if (item instanceof AlarmClientLeaf)
{
// System.out.println("Path element " + path_elems[i] + " is a leaf.");
return false;
}
}

return true;
}

/**
* Collects {@link AlarmClientLeaf}s items.
* @param items A {@link List} of {@link AlarmTreeItem}s, typically selected by user in the tree view. This could
* be a mix of leaf and non-leaf nodes. Moreover, leaf nodes could be child nodes of non-leaf nodes
* in the {@link List}.
* @return A {@link Set} of only {@link AlarmClientLeaf}s, i.e. no duplicates even id user selection would indicate it.
*/
protected static Set<AlarmClientLeaf> getLeafItems(List<AlarmTreeItem<?>> items){
return items.stream().flatMap(item -> streamLeafItems(item)).collect(Collectors.toSet());
}

/**
* Collects {@link AlarmClientLeaf}s items.
* @param root The start node from where to get {@link AlarmClientLeaf}s. If this is an {@link AlarmClientLeaf}, it
* will be returned as the sole item in the {@link Set}
* @return A {@link Set} of only {@link AlarmClientLeaf}s.
*/
protected static Set<AlarmClientLeaf> getLeafItems(final AlarmTreeItem<?> root) {
return streamLeafItems(root).collect(Collectors.toSet());
}

private static Stream<AlarmClientLeaf> streamLeafItems(final AlarmTreeItem<?> alarmTreeItem){
if (alarmTreeItem instanceof AlarmClientLeaf alarmClientLeaf){
return Stream.of(alarmClientLeaf);
}
else {
return alarmTreeItem.getChildren().stream().flatMap(child -> streamLeafItems(child));
}
}

/**
*
* @param items {@link List} of {@link AlarmTreeItem}s that may be a mix of leaves and non-leaves, e.g. a user
* selection in the alarm tree view.
* @return A {@link TreeNodeInfo} object.
*/
public static TreeNodeInfo getTreeNodeInfo(List<AlarmTreeItem<?>> items){
Set<AlarmClientLeaf> leaves = getLeafItems(items);
int disabledIndefinitely = 0;
int disabledWithEnableDate = 0;
Optional<LocalDateTime> localDateTime = Optional.empty();

for(AlarmClientLeaf leaf : leaves){
if(!leaf.isEnabled()){
LocalDateTime enableDate = leaf.getEnabledDate();
if(enableDate != null){
if(localDateTime.isPresent() && !localDateTime.get().equals(enableDate)){
localDateTime = Optional.empty();
}
else{
localDateTime = Optional.of(enableDate);
}
disabledWithEnableDate++;
}
else{
disabledIndefinitely++;
}
}
}
return new TreeNodeInfo(leaves, disabledIndefinitely, disabledWithEnableDate, localDateTime);
}

/**
*
* @param item A {@link AlarmTreeItem}, can be either a leaf or non-leaf in the alarm tree view.
* @return A {@link TreeNodeInfo} object.
*/
public static TreeNodeInfo getTreeNodeInfo(AlarmTreeItem<?> item){
return getTreeNodeInfo(List.of(item));
}

/**
* Formats a {@link TreeNodeInfo} object based on its content.
* @param treeNodeInfo A {@link TreeNodeInfo}
* @return A string describing total number of leaves, disabled leaves (if any) and an enable date where applicable.
*/
public static String treeNodeInfoToString(TreeNodeInfo treeNodeInfo){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(MessageFormat.format(Messages.totalPVs, treeNodeInfo.leaves().size()));
if(treeNodeInfo.disabled() > 0){
stringBuilder.append(", ").append(MessageFormat.format(Messages.disabledIndefinitely, treeNodeInfo.disabled()));
}
int disabledWithEnableDate = treeNodeInfo.disabledWithEnableDate();
if(disabledWithEnableDate > 0){
stringBuilder.append(", ");
if(treeNodeInfo.commonEnableDate().isPresent()){
stringBuilder.append(MessageFormat.format(Messages.disabledCommonEnableDate, TimestampFormats.SECONDS_FORMAT.format(treeNodeInfo.commonEnableDate().get()), disabledWithEnableDate));
}
else{
stringBuilder.append(MessageFormat.format(Messages.disabledVaryingEnableDate, disabledWithEnableDate));
}
}
return stringBuilder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*******************************************************************************/
package org.phoebus.applications.alarm.ui.tree;

import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
Expand All @@ -15,20 +16,16 @@
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 org.phoebus.framework.jobs.JobManager;
import org.phoebus.util.time.TimestampFormats;

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
Expand Down Expand Up @@ -82,10 +79,8 @@ protected void updateItem(final AlarmTreeItem<?> item, final boolean empty)
setGraphic(null);
else
{
final SeverityLevel severity;
if (item instanceof AlarmClientLeaf)
if (item instanceof AlarmClientLeaf leaf)
{
final AlarmClientLeaf leaf = (AlarmClientLeaf) item;
final ClientState state = leaf.getState();

final StringBuilder text = new StringBuilder();
Expand All @@ -111,7 +106,7 @@ protected void updateItem(final AlarmTreeItem<?> item, final boolean empty)
} else {
if (leaf.getEnabled().enabled_date != null) {
LocalDateTime enabledDate = leaf.getEnabled().enabled_date;
String enabledDateString = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(enabledDate);
String enabledDateString = TimestampFormats.SECONDS_FORMAT.format(enabledDate);
disabledTimerIndicator.setText("(" + Messages.disabledUntil + " " + enabledDateString + ")");
} else {
disabledTimerIndicator.setText("(" + Messages.disabled + ")");
Expand All @@ -126,45 +121,25 @@ protected void updateItem(final AlarmTreeItem<?> item, final boolean empty)
}
else
{
final AlarmClientNode node = (AlarmClientNode) item;

Optional<Pair<LeavesDisabledStatus, Boolean>> maybeLeavesDisabledStatusBooleanPair = leavesDisabledStatus(node);
if (maybeLeavesDisabledStatusBooleanPair.isPresent() && !maybeLeavesDisabledStatusBooleanPair.get().getKey().equals(LeavesDisabledStatus.AllEnabled)) {
Pair<LeavesDisabledStatus, Boolean> 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 + ")");
// To get the information to display on non-leaf nodes one will need to walk a potentially deep
// tree structure, so this is done off the UI thread.
JobManager.schedule("Get Tree Node Info", monitor -> {
TreeNodeInfo info = AlarmTreeHelper.getTreeNodeInfo(item);
Platform.runLater(() -> {
String labelText = item.getName();
label.setText(labelText);
SeverityLevel severityLevel = item.getState().severity;
disabledTimerIndicator.setText(AlarmTreeHelper.treeNodeInfoToString(info));
if(info.disabled() + info.disabledWithEnableDate() == info.leaves().size()){
label.setTextFill(Color.GRAY);
}
else {
disabledTimerIndicator.setText("(" + Messages.partlyDisabled + ")");
else{
label.setTextFill(AlarmUI.getColor(severityLevel));
}
}
}
else {
disabledTimerIndicator.setText("");
}

String labelText = item.getName();
label.setText(labelText);

severity = node.getState().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));
label.setBackground(AlarmUI.getBackground(severityLevel));
image.setImage(AlarmUI.getIcon(severityLevel));
});
});
}
// Profiler showed small advantage when skipping redundant 'setGraphic' call
if (getGraphic() != content)
Expand All @@ -175,61 +150,4 @@ else if (leavesDisabledStatusBooleanPair.getKey().equals(LeavesDisabledStatus.So
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<Pair<LeavesDisabledStatus, Boolean>> leavesDisabledStatus(AlarmClientNode alarmClientNode) {
List<Pair<LeavesDisabledStatus, Boolean>> 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<Pair<LeavesDisabledStatus, Boolean>> 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;
}
}
Loading
Loading