Skip to content

Commit 084f918

Browse files
authored
Merge pull request #3519 from ControlSystemStudio/navtab_invalid
NavTabs: Better handling of invalid active_tab
2 parents c829f97 + 3d269fc commit 084f918

File tree

2 files changed

+60
-43
lines changed

2 files changed

+60
-43
lines changed

app/display/model/src/main/java/org/csstudio/display/builder/model/widgets/NavigationTabsWidget.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017-2023 Oak Ridge National Laboratory.
2+
* Copyright (c) 2017-2025 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -45,7 +45,6 @@
4545
/** Widget with tabs to select amongst several embedded displays
4646
* @author Kay Kasemir
4747
*/
48-
@SuppressWarnings("nls")
4948
public class NavigationTabsWidget extends VisibleWidget
5049
{
5150
/** Widget descriptor */
@@ -210,7 +209,7 @@ public Macros getEffectiveMacros()
210209
{
211210
final Macros base = super.getEffectiveMacros();
212211
// Join macros of active tab
213-
final int index;
212+
int index;
214213

215214
// To fetch the effective macros, we want to add those of the selected tab.
216215
// But if we get the tab index via active.getValue(), AND 'active' itself contains macros,
@@ -228,7 +227,13 @@ public Macros getEffectiveMacros()
228227
return base;
229228
}
230229

231-
if (index >= 0 && index < tabs.size())
230+
if (index >= tabs.size())
231+
{
232+
logger.log(Level.WARNING, this + " active tab " + index + " needs to be within 0 ... " + (tabs.size() - 1));
233+
index = tabs.size()-1;
234+
}
235+
236+
if (index >= 0)
232237
try
233238
{
234239
final Macros selected = tabs.getElement(index).macros().getValue();

app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabsRepresentation.java

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017-2018 Oak Ridge National Laboratory.
2+
* Copyright (c) 2017-2025 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -18,8 +18,6 @@
1818
import java.util.concurrent.atomic.AtomicReference;
1919
import java.util.logging.Level;
2020

21-
import javafx.util.Pair;
22-
import org.apache.commons.lang3.tuple.MutablePair;
2321
import org.csstudio.display.builder.model.DirtyFlag;
2422
import org.csstudio.display.builder.model.DisplayModel;
2523
import org.csstudio.display.builder.model.UntypedWidgetPropertyListener;
@@ -51,17 +49,32 @@
5149
*
5250
* @author Kay Kasemir
5351
*/
54-
@SuppressWarnings("nls")
5552
public class NavigationTabsRepresentation extends RegionBaseRepresentation<NavigationTabs, NavigationTabsWidget>
5653
{
5754
private final DirtyFlag dirty_sizes = new DirtyFlag();
5855
private final DirtyFlag dirty_tabs = new DirtyFlag();
5956
private final DirtyFlag dirty_tab_look = new DirtyFlag();
6057
private final DirtyFlag dirty_active_tab = new DirtyFlag();
61-
private class SelectedNavigationTabs extends MutablePair<Integer, HashMap<Pair<Integer, String>, HashMap<String, SelectedNavigationTabs>>> {
62-
public SelectedNavigationTabs(int activeTab) {
63-
left = activeTab;
64-
right = new HashMap<>(); // 'right' is a mapping of the form: Tab Number & Tab Name (Integer, String) -> Name of Navigator Tab Widget (String) -> Opened Tab and Sub-Tabs (SelectedNavigationTabs)
58+
59+
// Track the "active tab" of navigation tabs _inside_ this one.
60+
// As user toggles between tabs, this will allow restoring the active tab
61+
// of nested instances, which would otherwise start over at their default tab
62+
63+
// Tab index and name as unique identifier for a tab
64+
private static record UniqueTabID(int index, String name) {}
65+
66+
private static class SelectedNavigationTabs
67+
{
68+
// Index of active tab in this nav tabs widget
69+
int active_tab;
70+
71+
// Map from UniqueTabID -> ( Map from Name of child Navigator Tab Widget -> Opened Tab and Sub-Tabs )
72+
final HashMap<UniqueTabID,
73+
HashMap<String, SelectedNavigationTabs>> child_navtab_selections = new HashMap<>();
74+
75+
SelectedNavigationTabs(int activeTab)
76+
{
77+
active_tab = activeTab;
6578
}
6679
};
6780
protected SelectedNavigationTabs selectedNavigationTabs = new SelectedNavigationTabs(0);
@@ -187,7 +200,7 @@ private void activeTabChanged(final WidgetProperty<Integer> property, final Inte
187200
dirty_active_tab.mark();
188201
toolkit.scheduleUpdate(this);
189202
tab_display_listener.propertyChanged(null, null, null);
190-
selectedNavigationTabs.left = tab_index;
203+
selectedNavigationTabs.active_tab = tab_index;
191204
}
192205

193206
/** Update to the next pending display
@@ -244,37 +257,36 @@ private synchronized void updatePendingDisplay(final JobMonitor monitor)
244257
});
245258
checkCompletion(model_widget, completion, "timeout representing new content");
246259

247-
int tabNumber = model_widget.propActiveTab().getValue();
248-
String tabName = model_widget.propTabs().getValue().get(model_widget.propActiveTab().getValue()).name().getValue();
249-
Pair<Integer, String> tabNumberAndTabName = new Pair<>(tabNumber, tabName);
260+
final List<TabProperty> tabs = model_widget.propTabs().getValue();
261+
final int tabNumber = Math.min(model_widget.propActiveTab().getValue(), tabs.size()-1);
262+
final String tabName = tabs.get(tabNumber).name().getValue();
263+
final UniqueTabID tab_id = new UniqueTabID(tabNumber, tabName);
250264

251-
if (!selectedNavigationTabs.right.containsKey(tabNumberAndTabName)) {
252-
selectedNavigationTabs.right.put(tabNumberAndTabName, new HashMap<>());
253-
}
254-
HashMap<String, SelectedNavigationTabs> selectedNavigationTabsHashMapForCurrentTab = selectedNavigationTabs.right.get(tabNumberAndTabName);
255-
256-
new_model.getChildren()
257-
.stream()
258-
.filter(widget -> widget instanceof NavigationTabsWidget)
259-
.forEach(widget -> {
260-
NavigationTabsWidget nestedNavigationTabsWidget = (NavigationTabsWidget) widget;
261-
NavigationTabsRepresentation nestedNavigationTabsRepresentation = (NavigationTabsRepresentation) nestedNavigationTabsWidget.getUserData(Widget.USER_DATA_REPRESENTATION);
262-
if (nestedNavigationTabsRepresentation != null) {
263-
SelectedNavigationTabs nestedNavigationTabsRepresentation_selectedNavigationTabs;
264-
265-
if (!selectedNavigationTabsHashMapForCurrentTab.containsKey(nestedNavigationTabsWidget.getName())) {
266-
nestedNavigationTabsRepresentation_selectedNavigationTabs = new SelectedNavigationTabs(nestedNavigationTabsWidget.propActiveTab().getValue());
267-
selectedNavigationTabsHashMapForCurrentTab.put(nestedNavigationTabsWidget.getName(), nestedNavigationTabsRepresentation_selectedNavigationTabs);
268-
}
269-
else {
270-
nestedNavigationTabsRepresentation_selectedNavigationTabs = selectedNavigationTabsHashMapForCurrentTab.get(nestedNavigationTabsWidget.getName());
271-
if (nestedNavigationTabsWidget.propTabs().size() > nestedNavigationTabsRepresentation_selectedNavigationTabs.left) {
272-
nestedNavigationTabsWidget.propActiveTab().setValue(nestedNavigationTabsRepresentation_selectedNavigationTabs.left);
273-
}
274-
}
275-
nestedNavigationTabsRepresentation.selectedNavigationTabs = nestedNavigationTabsRepresentation_selectedNavigationTabs;
276-
}
277-
});
265+
// For this tab_id, create or update map of NavTabs children to their selected tab index and sub-navtabs
266+
final HashMap<String, SelectedNavigationTabs> selectedNavTabsMapForCurrentTab = selectedNavigationTabs.child_navtab_selections.computeIfAbsent(tab_id, tid -> new HashMap<>());
267+
268+
for (Widget w : new_model.getChildren())
269+
if (w instanceof NavigationTabsWidget childNavTab)
270+
{
271+
final NavigationTabsRepresentation childRepr = childNavTab.getUserData(Widget.USER_DATA_REPRESENTATION);
272+
if (childRepr != null)
273+
{
274+
SelectedNavigationTabs childRepr_selectedNavigationTabs = selectedNavTabsMapForCurrentTab.get(childNavTab.getName());
275+
if (childRepr_selectedNavigationTabs == null)
276+
{ // See childNavTab for the first time? Create its SelectedNavigationTabs with its propActiveTab as start value
277+
childRepr_selectedNavigationTabs = new SelectedNavigationTabs(childNavTab.propActiveTab().getValue());
278+
selectedNavTabsMapForCurrentTab.put(childNavTab.getName(), childRepr_selectedNavigationTabs);
279+
}
280+
// Known childNavTab? Re-select its last active tab (if valid index)
281+
else if (childRepr_selectedNavigationTabs.active_tab < childNavTab.propTabs().size())
282+
childNavTab.propActiveTab().setValue(childRepr_selectedNavigationTabs.active_tab);
283+
284+
// childRepr.selectedNavigationTabs was set when the childNavTabRepr got constructed,
285+
// but it's empty and its reference will be lost when we switch tabs.
286+
// Replace it with the one that's tracked by the parent navtab (this one)
287+
childRepr.selectedNavigationTabs = childRepr_selectedNavigationTabs;
288+
}
289+
}
278290

279291
model_widget.runtimePropEmbeddedModel().setValue(new_model);
280292
}

0 commit comments

Comments
 (0)