77 *******************************************************************************/
88package org .phoebus .applications .alarm .ui .tree ;
99
10- import javafx .geometry .Insets ;
10+ import javafx .geometry .Pos ;
1111import javafx .scene .control .Label ;
1212import javafx .scene .control .TreeCell ;
1313import javafx .scene .image .ImageView ;
1414import javafx .scene .layout .Background ;
15- import javafx .scene .layout .BackgroundFill ;
16- import javafx .scene .layout .CornerRadii ;
1715import javafx .scene .layout .HBox ;
1816import javafx .scene .paint .Color ;
1917
18+ import javafx .util .Pair ;
2019import org .phoebus .applications .alarm .client .AlarmClientLeaf ;
2120import org .phoebus .applications .alarm .client .AlarmClientNode ;
2221import org .phoebus .applications .alarm .client .ClientState ;
2322import org .phoebus .applications .alarm .model .AlarmTreeItem ;
2423import org .phoebus .applications .alarm .model .SeverityLevel ;
2524import org .phoebus .applications .alarm .ui .AlarmUI ;
25+ import org .phoebus .applications .alarm .ui .Messages ;
26+
27+ import java .time .LocalDateTime ;
28+ import java .time .format .DateTimeFormatter ;
29+ import java .util .LinkedList ;
30+ import java .util .List ;
31+ import java .util .Optional ;
2632
2733/** TreeCell for AlarmTreeItem
2834 * @author Kay Kasemir
@@ -40,7 +46,8 @@ class AlarmTreeViewCell extends TreeCell<AlarmTreeItem<?>>
4046 // So we add our own "graphics" to hold an icon and text
4147 private final Label label = new Label ();
4248 private final ImageView image = new ImageView ();
43- private final HBox content = new HBox (image , label );
49+ private final Label disabledTimerIndicator = new Label ("" );
50+ private final HBox content = new HBox (image , label , disabledTimerIndicator );
4451
4552 // TreeCell optimizes redraws by suppressing updates
4653 // when old and new values match.
@@ -61,6 +68,11 @@ protected boolean isItemChanged(final AlarmTreeItem<?> before, final AlarmTreeIt
6168 return true ;
6269 }
6370
71+ public AlarmTreeViewCell () {
72+ content .setAlignment (Pos .CENTER_LEFT );
73+ disabledTimerIndicator .setTextFill (Color .GRAY );
74+ }
75+
6476 @ Override
6577 protected void updateItem (final AlarmTreeItem <?> item , final boolean empty )
6678 {
@@ -79,7 +91,7 @@ protected void updateItem(final AlarmTreeItem<?> item, final boolean empty)
7991 final StringBuilder text = new StringBuilder ();
8092 text .append ("PV: " ).append (leaf .getName ());
8193
82- if (leaf . isEnabled () && ! state . isDynamicallyDisabled ( ))
94+ if (! isLeafDisabled ( leaf ))
8395 { // Add alarm info
8496 if (state .severity != SeverityLevel .OK )
8597 {
@@ -94,23 +106,63 @@ protected void updateItem(final AlarmTreeItem<?> item, final boolean empty)
94106 label .setTextFill (AlarmUI .getColor (state .severity ));
95107 label .setBackground (AlarmUI .getBackground (state .severity ));
96108 image .setImage (AlarmUI .getIcon (state .severity ));
97- }
98- else
99- {
100- text .append (" (disabled)" );
109+
110+ disabledTimerIndicator .setText ("" );
111+ } else {
112+ if (leaf .getEnabled ().enabled_date != null ) {
113+ LocalDateTime enabledDate = leaf .getEnabled ().enabled_date ;
114+ String enabledDateString = DateTimeFormatter .ISO_LOCAL_DATE_TIME .format (enabledDate );
115+ disabledTimerIndicator .setText ("(" + Messages .disabledUntil + " " + enabledDateString + ")" );
116+ } else {
117+ disabledTimerIndicator .setText ("(" + Messages .disabled + ")" );
118+ }
119+
101120 label .setTextFill (Color .GRAY );
102121 label .setBackground (Background .EMPTY );
103122 image .setImage (AlarmUI .disabled_icon );
104123 }
124+
105125 label .setText (text .toString ());
106126 }
107127 else
108128 {
109129 final AlarmClientNode node = (AlarmClientNode ) item ;
110- label .setText (item .getName ());
130+
131+ Optional <Pair <LeavesDisabledStatus , Boolean >> maybeLeavesDisabledStatusBooleanPair = leavesDisabledStatus (node );
132+ if (maybeLeavesDisabledStatusBooleanPair .isPresent () && !maybeLeavesDisabledStatusBooleanPair .get ().getKey ().equals (LeavesDisabledStatus .AllEnabled )) {
133+ Pair <LeavesDisabledStatus , Boolean > leavesDisabledStatusBooleanPair = maybeLeavesDisabledStatusBooleanPair .get ();
134+
135+ if (leavesDisabledStatusBooleanPair .getKey ().equals (LeavesDisabledStatus .AllDisabled )) {
136+ if (leavesDisabledStatusBooleanPair .getValue ()) {
137+ disabledTimerIndicator .setText ("(" + Messages .disabled + "; " + Messages .timer + ")" );
138+ }
139+ else {
140+ disabledTimerIndicator .setText ("(" + Messages .disabled + ")" );
141+ }
142+ }
143+ else if (leavesDisabledStatusBooleanPair .getKey ().equals (LeavesDisabledStatus .SomeEnabledSomeDisabled )) {
144+ if (leavesDisabledStatusBooleanPair .getValue ()) {
145+ disabledTimerIndicator .setText ("(" + Messages .partlyDisabled + "; " + Messages .timer + ")" );
146+ }
147+ else {
148+ disabledTimerIndicator .setText ("(" + Messages .partlyDisabled + ")" );
149+ }
150+ }
151+ }
152+ else {
153+ disabledTimerIndicator .setText ("" );
154+ }
155+
156+ String labelText = item .getName ();
157+ label .setText (labelText );
111158
112159 severity = node .getState ().severity ;
113- label .setTextFill (AlarmUI .getColor (severity ));
160+ if (maybeLeavesDisabledStatusBooleanPair .isPresent () && maybeLeavesDisabledStatusBooleanPair .get ().getKey ().equals (LeavesDisabledStatus .AllDisabled )) {
161+ label .setTextFill (Color .GRAY );
162+ }
163+ else {
164+ label .setTextFill (AlarmUI .getColor (severity ));
165+ }
114166 label .setBackground (AlarmUI .getBackground (severity ));
115167 image .setImage (AlarmUI .getIcon (severity ));
116168 }
@@ -119,4 +171,65 @@ protected void updateItem(final AlarmTreeItem<?> item, final boolean empty)
119171 setGraphic (content );
120172 }
121173 }
174+
175+ private boolean isLeafDisabled (AlarmClientLeaf alarmClientLeaf ) {
176+ return !alarmClientLeaf .isEnabled () || alarmClientLeaf .getState ().isDynamicallyDisabled ();
177+ }
178+
179+ private enum LeavesDisabledStatus {
180+ AllEnabled ,
181+ SomeEnabledSomeDisabled ,
182+ AllDisabled ,
183+ }
184+
185+ // leavesDisabledStatus() optionally returns a pair.
186+ //
187+ // If a pair is _not_ returned, it means that there exist no leaves
188+ // in 'alarmClientNode', and the disabled status is undefined.
189+ //
190+ // When a pair _is_ returned, the first component describes
191+ // whether all leaves are disabled, all leaves are enabled, or whether
192+ // some leaves are enabled and some are disabled, and the second component
193+ // indicates whether one or more disabled leaves have a timer associated
194+ // with them ('true'), at the end of which they will automatically become
195+ // enabled again. When the second component is 'false' there is no
196+ // associated timer.
197+ private Optional <Pair <LeavesDisabledStatus , Boolean >> leavesDisabledStatus (AlarmClientNode alarmClientNode ) {
198+ List <Pair <LeavesDisabledStatus , Boolean >> leavesDisabledStatusList = new LinkedList <>();
199+ for (var child : alarmClientNode .getChildren ()) {
200+ if (child instanceof AlarmClientLeaf alarmClientLeaf ) {
201+
202+ if (isLeafDisabled (alarmClientLeaf )) {
203+ boolean timer = alarmClientLeaf .getEnabled ().enabled_date != null ;
204+ leavesDisabledStatusList .add (new Pair <>(LeavesDisabledStatus .AllDisabled , timer ));
205+ }
206+ else {
207+ leavesDisabledStatusList .add (new Pair <>(LeavesDisabledStatus .AllEnabled , false ));
208+ }
209+ }
210+ else if (child instanceof AlarmClientNode alarmClientNode1 && !alarmClientNode1 .getChildren ().isEmpty ()) {
211+ if (leavesDisabledStatus (alarmClientNode1 ).isPresent ()) {
212+ leavesDisabledStatusList .add (leavesDisabledStatus (alarmClientNode1 ).get ());
213+ }
214+ // If leavesDisabledStatus(alarmClientNode1).isPresent() evaluates to false, there are no leaves and therefore no result.
215+ }
216+ else if (child instanceof AlarmClientNode alarmClientNode1 && alarmClientNode1 .getChildren ().isEmpty ()) {
217+ // Don't add any LeavesDisabledStatus, since there are no leaves
218+ }
219+ else {
220+ throw new RuntimeException ("Missing case: " + child .getClass ().getName ());
221+ }
222+ }
223+
224+ Optional <Pair <LeavesDisabledStatus , Boolean >> leavesDisabledStatus = leavesDisabledStatusList .stream ().reduce ((status1 , status2 ) -> {
225+ if (status1 .getKey ().equals (status2 .getKey ())) {
226+ return new Pair <>(status1 .getKey (), status1 .getValue () || status2 .getValue ());
227+ }
228+ else {
229+ return new Pair <>(LeavesDisabledStatus .SomeEnabledSomeDisabled , status1 .getValue () || status2 .getValue ());
230+ }
231+ });
232+
233+ return leavesDisabledStatus ;
234+ }
122235}
0 commit comments