diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/widgets/NavigationTabsWidget.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/widgets/NavigationTabsWidget.java index de21dfdcf0..65ae853686 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/widgets/NavigationTabsWidget.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/widgets/NavigationTabsWidget.java @@ -8,6 +8,7 @@ package org.csstudio.display.builder.model.widgets; import static org.csstudio.display.builder.model.ModelPlugin.logger; +import static org.csstudio.display.builder.model.properties.CommonWidgetProperties.newBooleanPropertyDescriptor; import static org.csstudio.display.builder.model.properties.CommonWidgetProperties.propDirection; import static org.csstudio.display.builder.model.properties.CommonWidgetProperties.propFile; import static org.csstudio.display.builder.model.properties.CommonWidgetProperties.propFont; @@ -26,6 +27,7 @@ import org.csstudio.display.builder.model.ArrayWidgetProperty; import org.csstudio.display.builder.model.DisplayModel; import org.csstudio.display.builder.model.MacroizedWidgetProperty; +import org.csstudio.display.builder.model.Messages; import org.csstudio.display.builder.model.StructuredWidgetProperty; import org.csstudio.display.builder.model.Widget; import org.csstudio.display.builder.model.WidgetCategory; @@ -47,7 +49,9 @@ */ public class NavigationTabsWidget extends VisibleWidget { - /** Widget descriptor */ + private static final WidgetColor DEFAULT_SELECT_COLOR = new WidgetColor(236, 236, 236); + private static final WidgetColor DEFAULT_DESELECT_COLOR = new WidgetColor(200, 200, 200); + /** Widget descriptor */ public static final WidgetDescriptor WIDGET_DESCRIPTOR = new WidgetDescriptor("navtabs", WidgetCategory.STRUCTURE, "Navigation Tabs", @@ -61,10 +65,17 @@ public Widget createWidget() } }; - // 'state' structure that describes one state + // 'tab' structure that describes one tab private static final StructuredWidgetProperty.Descriptor propTab = new StructuredWidgetProperty.Descriptor(WidgetPropertyCategory.BEHAVIOR, "tab", "Tab"); + // Elements of the 'tab' structure + private static final WidgetPropertyDescriptor propIndividualSelectedColor = + CommonWidgetProperties.newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "selected_color", "Selected Color"); + + private static final WidgetPropertyDescriptor propIndividualDeselectedColor = + CommonWidgetProperties.newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "deselected_color", "Deselected Color"); + /** Structure for one tab item and its embedded display */ public static class TabProperty extends StructuredWidgetProperty { @@ -77,7 +88,9 @@ public TabProperty(final Widget widget, final int index) Arrays.asList(propName.createProperty(widget, "Tab " + (index + 1)), propFile.createProperty(widget, ""), propMacros.createProperty(widget, new Macros()), - propGroupName.createProperty(widget, "") + propGroupName.createProperty(widget, ""), + propIndividualSelectedColor.createProperty(widget, DEFAULT_SELECT_COLOR), + propIndividualDeselectedColor.createProperty(widget, DEFAULT_DESELECT_COLOR) )); } /** @return Tab name */ @@ -88,6 +101,11 @@ public TabProperty(final Widget widget, final int index) public WidgetProperty macros() { return getElement(2); } /** @return Optional sub-group of file */ public WidgetProperty group() { return getElement(3); } + /** @return Tab color when selected */ + public WidgetProperty individual_selected_color() { return getElement(4); } + /** @return Tab color when not selected */ + public WidgetProperty individual_deselected_color() { return getElement(5); } + } // 'tabs' array @@ -101,15 +119,18 @@ public TabProperty(final Widget widget, final int index) private static final WidgetPropertyDescriptor propTabSpacing = CommonWidgetProperties.newIntegerPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "tab_spacing", "Tab Spacing"); - private static final WidgetPropertyDescriptor propDeselectedColor = - CommonWidgetProperties.newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "deselected_color", "Deselected Color"); - + private static final WidgetPropertyDescriptor propEnablePerTabColors = + CommonWidgetProperties.newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "enable_per_tab_colors", "Per Tab Colors"); + private static final WidgetPropertyDescriptor propDeselectedColor = + CommonWidgetProperties.newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "deselected_color", "Deselected Color"); + private volatile ArrayWidgetProperty tabs; private volatile WidgetProperty direction; private volatile WidgetProperty tab_width; private volatile WidgetProperty tab_height; private volatile WidgetProperty tab_spacing; + private volatile WidgetProperty enable_per_tab_colors; private volatile WidgetProperty selected_color; private volatile WidgetProperty deselected_color; private volatile WidgetProperty font; @@ -134,8 +155,9 @@ protected void defineProperties(final List> properties) properties.add(tab_width = propTabWidth.createProperty(this, ActionButtonWidget.DEFAULT_WIDTH)); properties.add(tab_height = propTabHeight.createProperty(this, ActionButtonWidget.DEFAULT_HEIGHT)); properties.add(tab_spacing = propTabSpacing.createProperty(this, 2)); - properties.add(selected_color = propSelectedColor.createProperty(this, new WidgetColor(236, 236, 236))); - properties.add(deselected_color = propDeselectedColor.createProperty(this, new WidgetColor(200, 200, 200))); + properties.add(enable_per_tab_colors = propEnablePerTabColors.createProperty(this, false)); + properties.add(selected_color = propSelectedColor.createProperty(this, DEFAULT_SELECT_COLOR)); + properties.add(deselected_color = propDeselectedColor.createProperty(this, DEFAULT_DESELECT_COLOR)); properties.add(font = propFont.createProperty(this, WidgetFontService.get(NamedWidgetFonts.DEFAULT))); properties.add(active = propActiveTab.createProperty(this, 0)); properties.add(embedded_model = runtimeModel.createProperty(this, null)); @@ -171,6 +193,12 @@ public WidgetProperty propTabSpacing() return tab_spacing; } + /** @return 'enable_per_tab_colors' property */ + public WidgetProperty propEnablePerTabColors() + { + return enable_per_tab_colors; + } + /** @return 'selected_color' property */ public WidgetProperty propSelectedColor() { diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabs.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabs.java index 7af2b89f6d..3ecbd7aab6 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabs.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabs.java @@ -12,7 +12,9 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.csstudio.display.builder.model.properties.Direction; +import org.csstudio.display.builder.model.properties.WidgetColor; import org.csstudio.display.builder.representation.javafx.JFXUtil; +import org.phoebus.ui.javafx.NonCachingScrollPane; import javafx.collections.ObservableList; import javafx.css.PseudoClass; @@ -26,7 +28,6 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Font; -import org.phoebus.ui.javafx.NonCachingScrollPane; /** Navigation Tabs * @@ -72,10 +73,20 @@ public static interface Listener private final Pane body = new Pane(); /** Labels for the tabs */ - private final List tabs = new CopyOnWriteArrayList<>(); + private final List tab_names = new CopyOnWriteArrayList<>(); + + /** Selected colors for the tabs */ + private final List tab_selected_colors = new CopyOnWriteArrayList<>(); + /** Deselected colors for the tabs */ + private final List tab_deselected_colors = new CopyOnWriteArrayList<>(); + + /** Size and spacing for the tabs */ private int tab_width = 100, tab_height = 50, tab_spacing = 2; + /** Enable per tab colors */ + private boolean enable_per_tab_colors = false; + /** Direction of tabs */ private Direction direction = Direction.VERTICAL; @@ -84,6 +95,7 @@ public static interface Listener deselected = Color.rgb(200, 200, 200); private Font font = null; + private int selected_tab = -1; /** Listener to selected tab * @@ -123,21 +135,33 @@ public void removeListener(final Listener listener) } /** @param tabs Tab labels */ - public void setTabs(final List tabs) + public void setTabNames(final List tab_names) + { + this.tab_names.clear(); + this.tab_names.addAll(tab_names); + updateTabs(); + } + + /** @param tabs Selected colors */ + public void setTabSelectedColors(final List tab_selected_colors) { - this.tabs.clear(); - this.tabs.addAll(tabs); + this.tab_selected_colors.clear(); + this.tab_selected_colors.addAll(tab_selected_colors); + updateTabs(); + } + + /** @param tabs Deselected colors */ + public void setTabDeselectedColors(final List tab_deselected_colors) + { + this.tab_deselected_colors.clear(); + this.tab_deselected_colors.addAll(tab_deselected_colors); updateTabs(); } /** @return Index of the selected tab. -1 if there are no buttons or nothing selected */ public int getSelectedTab() { - final ObservableList siblings = buttons.getChildren(); - for (int i=0; i siblings = buttons.getChildren(); - int i = 0, selected_tab = -1; + int i = 0; + selected_tab = -1; + Color tmpColor = deselected; + WidgetColor tmpWidgetColor = null; for (Node sibling : siblings) { final ToggleButton button = (ToggleButton) sibling; if (button == pressed) { + // Set color to global "selected" color value + tmpColor = selected; // If user clicked a button that was already selected, // it would now be de-selected, leaving nothing selected. if (! pressed.isSelected()) @@ -290,14 +353,30 @@ private void handleTabSelection(final ToggleButton pressed, final boolean notify pressed.setSelected(true); } // Highlight active tab by setting it to the 'selected' color - pressed.setStyle("-fx-color: " + JFXUtil.webRGB(selected)); + // If the per-tab colors are enabled, the color to apply is to be found in the tab_selected_colors list + if (enable_per_tab_colors == true) { + if(i < tab_selected_colors.size()) { + tmpWidgetColor = tab_selected_colors.get(i); + tmpColor = JFXUtil.convert(tmpWidgetColor); + } + } + pressed.setStyle("-fx-color: " + JFXUtil.webRGB(tmpColor)); selected_tab = i; } else if (button.isSelected()) { // Radio-button behavior: De-select other tabs button.setSelected(false); - button.setStyle("-fx-color: " + JFXUtil.webRGB(deselected)); + // Set color to global "deselected" color value + tmpColor = deselected; + // If the per-tab colors are enabled, the color to apply is to be found in the tab_deselected_colors list + if (enable_per_tab_colors == true) { + if(i < tab_deselected_colors.size()) { + tmpWidgetColor = tab_deselected_colors.get(i); + tmpColor = JFXUtil.convert(tmpWidgetColor); + } + } + button.setStyle("-fx-color: " + JFXUtil.webRGB(tmpColor)); } ++i; } diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabsRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabsRepresentation.java index 6f42f09f06..d550bb265b 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabsRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/NavigationTabsRepresentation.java @@ -24,6 +24,7 @@ import org.csstudio.display.builder.model.Widget; import org.csstudio.display.builder.model.WidgetProperty; import org.csstudio.display.builder.model.WidgetPropertyListener; +import org.csstudio.display.builder.model.properties.WidgetColor; import org.csstudio.display.builder.model.widgets.NavigationTabsWidget; import org.csstudio.display.builder.model.widgets.NavigationTabsWidget.TabProperty; import org.csstudio.display.builder.representation.EmbeddedDisplayRepresentationUtil.DisplayAndGroup; @@ -98,7 +99,7 @@ private static class SelectedNavigationTabs */ private final AtomicReference active_content_model = new AtomicReference<>(); - private final WidgetPropertyListener tab_name_listener = (property, old_value, new_value) -> + private final UntypedWidgetPropertyListener tabs_listener = (property, old_value, new_value) -> { dirty_tabs.mark(); toolkit.scheduleUpdate(this); @@ -164,6 +165,7 @@ protected void registerListeners() model_widget.propTabWidth().addUntypedPropertyListener(tabLookChangedListener); model_widget.propTabHeight().addUntypedPropertyListener(tabLookChangedListener); model_widget.propTabSpacing().addUntypedPropertyListener(tabLookChangedListener); + model_widget.propEnablePerTabColors().addUntypedPropertyListener(tabLookChangedListener); model_widget.propSelectedColor().addUntypedPropertyListener(tabLookChangedListener); model_widget.propDeselectedColor().addUntypedPropertyListener(tabLookChangedListener); model_widget.propFont().addUntypedPropertyListener(tabLookChangedListener); @@ -187,6 +189,7 @@ protected void unregisterListeners() model_widget.propTabWidth().removePropertyListener(tabLookChangedListener); model_widget.propTabHeight().removePropertyListener(tabLookChangedListener); model_widget.propTabSpacing().removePropertyListener(tabLookChangedListener); + model_widget.propEnablePerTabColors().removePropertyListener(tabLookChangedListener); model_widget.propSelectedColor().removePropertyListener(tabLookChangedListener); model_widget.propDeselectedColor().removePropertyListener(tabLookChangedListener); model_widget.propFont().removePropertyListener(tabLookChangedListener); @@ -348,10 +351,12 @@ private void removeTabs(final List removed) { for (TabProperty tab : removed) { - tab.name().removePropertyListener(tab_name_listener); + tab.name().removePropertyListener(tabs_listener); tab.file().removePropertyListener(tab_display_listener); tab.macros().removePropertyListener(tab_display_listener); tab.group().removePropertyListener(tab_display_listener); + tab.individual_selected_color().removePropertyListener(tabs_listener); + tab.individual_deselected_color().removePropertyListener(tabs_listener); } } @@ -359,10 +364,12 @@ private void addTabs(final List added) { for (TabProperty tab : added) { + tab.individual_selected_color().addUntypedPropertyListener(tabs_listener); + tab.individual_deselected_color().addUntypedPropertyListener(tabs_listener); tab.group().addUntypedPropertyListener(tab_display_listener); tab.macros().addUntypedPropertyListener(tab_display_listener); tab.file().addUntypedPropertyListener(tab_display_listener); - tab.name().addPropertyListener(tab_name_listener); + tab.name().addUntypedPropertyListener(tabs_listener); } } @@ -382,15 +389,27 @@ public void updateChanges() jfx_node.setTabSize(model_widget.propTabWidth().getValue(), model_widget.propTabHeight().getValue()); jfx_node.setTabSpacing(model_widget.propTabSpacing().getValue()); + jfx_node.setEnablePerTabColors(model_widget.propEnablePerTabColors().getValue()); jfx_node.setSelectedColor(JFXUtil.convert(model_widget.propSelectedColor().getValue())); jfx_node.setDeselectedColor(JFXUtil.convert(model_widget.propDeselectedColor().getValue())); jfx_node.setFont(JFXUtil.convert(model_widget.propFont().getValue())); } if (dirty_tabs.checkAndClear()) { - final List tabs = new ArrayList<>(); - model_widget.propTabs().getValue().forEach(tab -> tabs.add(tab.name().getValue())); - jfx_node.setTabs(tabs); + final List tab_names = new ArrayList<>(); + final List tab_selected_colors = new ArrayList<>(); + final List tab_deselected_colors = new ArrayList<>(); + + List tabList = model_widget.propTabs().getValue(); + tabList.forEach(tab -> { + tab_names.add(tab.name().getValue()); + tab_selected_colors.add(tab.individual_selected_color().getValue()); + tab_deselected_colors.add(tab.individual_deselected_color().getValue()); + }); + + jfx_node.setTabNames(tab_names); + jfx_node.setTabSelectedColors(tab_selected_colors); + jfx_node.setTabDeselectedColors(tab_deselected_colors); } if (dirty_active_tab.checkAndClear()) jfx_node.selectTab(model_widget.propActiveTab().getValue()); diff --git a/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/sandbox/NavigationTabsDemo.java b/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/sandbox/NavigationTabsDemo.java index b68cb16986..251b10cffc 100644 --- a/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/sandbox/NavigationTabsDemo.java +++ b/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/sandbox/NavigationTabsDemo.java @@ -35,7 +35,7 @@ public void start(final Stage stage) final NavigationTabs nav_tabs = new NavigationTabs(); final List tabs = IntStream.range(1, 10).mapToObj(i -> "Step" + i).collect(Collectors.toList()); - nav_tabs.setTabs(tabs); + nav_tabs.setTabNames(tabs); nav_tabs.setTabSize(80, 40); nav_tabs.setTabSpacing(5); nav_tabs.getBodyPane().getChildren().setAll(new Label(" Go on, select something!"));