From 049fd57667111f55c8df11675e3d2ccb7b004548 Mon Sep 17 00:00:00 2001 From: Laurent Garnier Date: Mon, 14 Aug 2023 14:04:50 +0200 Subject: [PATCH] [sitemap] Extend icon parameter with optional conditional rules Related to openhab/openhab-webui#1938 Allow dynamic icon based on other item states. Allow dynamic icon even with non OH icon sources. Example: icon=[item1>0=temperature,==0=material::settings,f7::house] Signed-off-by: Laurent Garnier --- .../sitemap/internal/PageChangeListener.java | 19 +++- .../sitemap/internal/SitemapResource.java | 25 +++++- .../sitemap/internal/SitemapWidgetEvent.java | 2 + .../sitemap/internal/SitemapResourceTest.java | 4 + .../openhab/core/model/sitemap/Sitemap.xtext | 64 +++++++++---- .../UIComponentSitemapProvider.java | 27 ++++++ .../ui/internal/items/ItemUIRegistryImpl.java | 90 ++++++++++++++++++- .../openhab/core/ui/items/ItemUIRegistry.java | 11 +++ 8 files changed, 220 insertions(+), 22 deletions(-) diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java index 30aa6bc5e92..2cb84376d27 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java @@ -37,6 +37,7 @@ import org.openhab.core.model.sitemap.sitemap.Chart; import org.openhab.core.model.sitemap.sitemap.ColorArray; import org.openhab.core.model.sitemap.sitemap.Frame; +import org.openhab.core.model.sitemap.sitemap.IconArray; import org.openhab.core.model.sitemap.sitemap.VisibilityRule; import org.openhab.core.model.sitemap.sitemap.Widget; import org.openhab.core.types.State; @@ -47,6 +48,7 @@ * * @author Kai Kreuzer - Initial contribution * @author Laurent Garnier - Added support for icon color + * @author Laurent Garnier - New widget icon parameter based on conditional rules */ public class PageChangeListener implements EventSubscriber { @@ -119,6 +121,10 @@ private Set getAllItems(EList widgets) { if (widget instanceof Frame frame) { items.addAll(getAllItems(frame.getChildren())); } + // now scan dynamic icon rules + for (IconArray rule : widget.getDynamicIcon()) { + addItemWithName(items, rule.getItem()); + } // now scan visibility rules for (VisibilityRule rule : widget.getVisibility()) { addItemWithName(items, rule.getItem()); @@ -131,7 +137,7 @@ private Set getAllItems(EList widgets) { for (ColorArray rule : widget.getValueColor()) { addItemWithName(items, rule.getItem()); } - // now scan value icon rules + // now scan icon color rules for (ColorArray rule : widget.getIconColor()) { addItemWithName(items, rule.getItem()); } @@ -183,7 +189,7 @@ private Set constructSitemapEvents(Item item, State state, List 0; } - if (!skipWidget || definesVisibilityOrColor(w, item.getName())) { + if (!skipWidget || definesVisibilityOrColorOrIcon(w, item.getName())) { SitemapWidgetEvent event = constructSitemapEventForWidget(item, state, w); events.add(event); } @@ -197,6 +203,10 @@ private SitemapWidgetEvent constructSitemapEventForWidget(Item item, State state event.pageId = pageId; event.label = itemUIRegistry.getLabel(widget); event.widgetId = itemUIRegistry.getWidgetId(widget); + if (widget.getStaticIcon() == null) { + event.icon = itemUIRegistry.getCategory(widget); + event.staticIcon = widget.getDynamicIcon() != null && !widget.getDynamicIcon().isEmpty(); + } event.visibility = itemUIRegistry.getVisiblity(widget); event.descriptionChanged = false; // event.item contains the (potentially changed) data of the item belonging to @@ -237,11 +247,12 @@ private Item getItemForWidget(Widget w) { return null; } - private boolean definesVisibilityOrColor(Widget w, String name) { + private boolean definesVisibilityOrColorOrIcon(Widget w, String name) { return w.getVisibility().stream().anyMatch(r -> name.equals(r.getItem())) || w.getLabelColor().stream().anyMatch(r -> name.equals(r.getItem())) || w.getValueColor().stream().anyMatch(r -> name.equals(r.getItem())) - || w.getIconColor().stream().anyMatch(r -> name.equals(r.getItem())); + || w.getIconColor().stream().anyMatch(r -> name.equals(r.getItem())) + || w.getDynamicIcon().stream().anyMatch(r -> name.equals(r.getItem())); } public void sitemapContentChanged(EList widgets) { diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java index 2874f5a7654..78d4747dc4e 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java @@ -79,6 +79,7 @@ import org.openhab.core.model.sitemap.sitemap.Chart; import org.openhab.core.model.sitemap.sitemap.ColorArray; import org.openhab.core.model.sitemap.sitemap.Frame; +import org.openhab.core.model.sitemap.sitemap.IconArray; import org.openhab.core.model.sitemap.sitemap.Image; import org.openhab.core.model.sitemap.sitemap.Input; import org.openhab.core.model.sitemap.sitemap.LinkableWidget; @@ -131,6 +132,7 @@ * @author Wouter Born - Migrated to OpenAPI annotations * @author Laurent Garnier - Added support for icon color * @author Mark Herwege - Added pattern and unit fields + * @author Laurent Garnier - New widget icon parameter based on conditional rules */ @Component(service = { RESTResource.class, EventSubscriber.class }) @JaxrsResource @@ -523,7 +525,8 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null } bean.widgetId = widgetId; bean.icon = itemUIRegistry.getCategory(widget); - bean.staticIcon = widget.getStaticIcon() != null; + bean.staticIcon = widget.getStaticIcon() != null + || (widget.getDynamicIcon() != null && !widget.getDynamicIcon().isEmpty()); bean.labelcolor = convertItemValueColor(itemUIRegistry.getLabelColor(widget), itemState); bean.valuecolor = convertItemValueColor(itemUIRegistry.getValueColor(widget), itemState); bean.iconcolor = convertItemValueColor(itemUIRegistry.getIconColor(widget), itemState); @@ -741,6 +744,8 @@ private Set getAllItems(EList widgets) { if (widget instanceof Frame frame) { items.addAll(getAllItems(frame.getChildren())); } + // Consider items involved in any icon condition + items.addAll(getItemsInIconCond(widget.getDynamicIcon())); // Consider items involved in any visibility, labelcolor, valuecolor and iconcolor condition items.addAll(getItemsInVisibilityCond(widget.getVisibility())); items.addAll(getItemsInColorCond(widget.getLabelColor())); @@ -786,6 +791,24 @@ private Set getItemsInColorCond(EList colorList) { return items; } + private Set getItemsInIconCond(EList ruleList) { + Set items = new HashSet<>(); + for (IconArray rule : ruleList) { + String itemName = rule.getItem(); + if (itemName != null) { + try { + Item item = itemUIRegistry.getItem(itemName); + if (item instanceof GenericItem genericItem) { + items.add(genericItem); + } + } catch (ItemNotFoundException e) { + // ignore + } + } + } + return items; + } + @Override public Set getSubscribedEventTypes() { return Set.of(ItemStateChangedEvent.TYPE); diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapWidgetEvent.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapWidgetEvent.java index a2ba2536d88..e1d5db094cb 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapWidgetEvent.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapWidgetEvent.java @@ -19,6 +19,7 @@ * * @author Kai Kreuzer - Initial contribution * @author Laurent Garnier - New field iconcolor + * @author Laurent Garnier - New field staticIcon */ public class SitemapWidgetEvent extends SitemapEvent { @@ -26,6 +27,7 @@ public class SitemapWidgetEvent extends SitemapEvent { public String label; public String icon; + public boolean staticIcon; public String labelcolor; public String valuecolor; public String iconcolor; diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java b/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java index b8a0b0da6ab..b525ca6b23b 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java @@ -330,6 +330,8 @@ private EList initSitemapWidgets() { when(w1.eClass()).thenReturn(sliderEClass); when(w1.getLabel()).thenReturn(WIDGET1_LABEL); when(w1.getItem()).thenReturn(ITEM_NAME); + when(w1.getDynamicIcon()).thenReturn(new BasicEList<>()); + when(w1.getStaticIcon()).thenReturn(null); // add visibility rules to the mock widget: VisibilityRule visibilityRule = mock(VisibilityRule.class); @@ -371,6 +373,8 @@ private EList initSitemapWidgets() { when(w2.eClass()).thenReturn(switchEClass); when(w2.getLabel()).thenReturn(WIDGET2_LABEL); when(w2.getItem()).thenReturn(ITEM_NAME); + when(w2.getDynamicIcon()).thenReturn(new BasicEList<>()); + when(w2.getStaticIcon()).thenReturn(null); when(w2.getVisibility()).thenReturn(visibilityRules); when(w2.getLabelColor()).thenReturn(labelColors); when(w2.getValueColor()).thenReturn(valueColors); diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext index 2a3088db948..09a7fb50f86 100644 --- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext +++ b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext @@ -25,7 +25,9 @@ LinkableWidget: Frame: {Frame} 'Frame' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)* ']'))? & @@ -33,7 +35,9 @@ Frame: Text: {Text} 'Text' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)* ']'))? & @@ -41,7 +45,9 @@ Text: Group: 'Group' (('item=' item=GroupItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)* ']'))? & @@ -49,7 +55,9 @@ Group: Image: 'Image' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('url=' url=STRING)? & ('refresh=' refresh=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -58,7 +66,9 @@ Image: Video: 'Video' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('url=' url=STRING) & ('encoding=' encoding=STRING)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -67,7 +77,9 @@ Video: Chart: 'Chart' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('service=' service=STRING)? & ('refresh=' refresh=INT)? & ('period=' period=ID) & ('legend=' legend=BOOLEAN_OBJECT)? & ('forceasitem=' forceAsItem=BOOLEAN_OBJECT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -78,7 +90,9 @@ Chart: Webview: 'Webview' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('height=' height=INT)? & ('url=' url=STRING) & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -87,7 +101,9 @@ Webview: Switch: 'Switch' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('mappings=[' mappings+=Mapping (',' mappings+=Mapping)* ']')? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -96,7 +112,9 @@ Switch: Mapview: 'Mapview' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('height=' height=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -105,7 +123,9 @@ Mapview: Slider: 'Slider' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('sendFrequency=' frequency=INT)? & (switchEnabled?='switchSupport')? & ('minValue=' minValue=Number)? & ('maxValue=' maxValue=Number)? & ('step=' step=Number)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -115,7 +135,9 @@ Slider: Selection: 'Selection' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('mappings=[' mappings+=Mapping (',' mappings+=Mapping)* ']')? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -124,7 +146,9 @@ Selection: Setpoint: 'Setpoint' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('minValue=' minValue=Number)? & ('maxValue=' maxValue=Number)? & ('step=' step=Number)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -133,7 +157,9 @@ Setpoint: Colorpicker: 'Colorpicker' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('sendFrequency=' frequency=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -142,7 +168,9 @@ Colorpicker: Input: 'Input' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('inputHint=' inputHint=STRING)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -151,7 +179,9 @@ Input: Default: 'Default' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('staticIcon=' staticIcon=Icon))? & ('height=' height=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -181,6 +211,10 @@ ColorArray: ((item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState) '=')? (arg=STRING); +IconArray: + ((item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState) '=')? + (arg=Icon); + Command returns ecore::EString: Number | ID | STRING; diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java index 6222e8f7cd9..16175b8d514 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java @@ -31,6 +31,7 @@ import org.openhab.core.model.core.ModelRepositoryChangeListener; import org.openhab.core.model.sitemap.SitemapProvider; import org.openhab.core.model.sitemap.sitemap.ColorArray; +import org.openhab.core.model.sitemap.sitemap.IconArray; import org.openhab.core.model.sitemap.sitemap.LinkableWidget; import org.openhab.core.model.sitemap.sitemap.Mapping; import org.openhab.core.model.sitemap.sitemap.Sitemap; @@ -44,6 +45,7 @@ import org.openhab.core.model.sitemap.sitemap.impl.DefaultImpl; import org.openhab.core.model.sitemap.sitemap.impl.FrameImpl; import org.openhab.core.model.sitemap.sitemap.impl.GroupImpl; +import org.openhab.core.model.sitemap.sitemap.impl.IconArrayImpl; import org.openhab.core.model.sitemap.sitemap.impl.ImageImpl; import org.openhab.core.model.sitemap.sitemap.impl.InputImpl; import org.openhab.core.model.sitemap.sitemap.impl.MappingImpl; @@ -75,6 +77,7 @@ * * @author Yannick Schaus - Initial contribution * @author Laurent Garnier - icon color support for all widgets + * @author Laurent Garnier - new icon parameter based on conditional rules */ @NonNullByDefault @Component(service = SitemapProvider.class) @@ -90,6 +93,8 @@ public class UIComponentSitemapProvider implements SitemapProvider, RegistryChan .compile("(?[A-Za-z]\\w*)\\s*(?==|!=|<=|>=|<|>)\\s*(?\\+|-)?(?\\S+)"); private static final Pattern COLOR_PATTERN = Pattern.compile( "((?[A-Za-z]\\w*)?\\s*((?==|!=|<=|>=|<|>)\\s*(?\\+|-)?(?\\S+))?\\s*=)?\\s*(?\\S+)"); + private static final Pattern ICON_PATTERN = Pattern.compile( + "((?[A-Za-z]\\w*)?\\s*((?==|!=|<=|>=|<|>)\\s*(?\\+|-)?(?\\S+))?\\s*=)?\\s*(?\\S+)"); private Map sitemaps = new HashMap<>(); private @Nullable UIComponentRegistryFactory componentRegistryFactory; @@ -285,6 +290,7 @@ protected Sitemap buildSitemap(RootUIComponent rootComponent) { addLabelColor(widget.getLabelColor(), component); addValueColor(widget.getValueColor(), component); addIconColor(widget.getIconColor(), component); + addDynamicIcon(widget.getDynamicIcon(), component); } return widget; @@ -389,6 +395,27 @@ private void addColor(EList color, UIComponent component, String key } } + private void addDynamicIcon(EList iconDef, UIComponent component) { + if (component.getConfig() != null && component.getConfig().containsKey("icon")) { + for (Object sourceIcon : (Collection) component.getConfig().get("icon")) { + if (sourceIcon instanceof String) { + Matcher matcher = ICON_PATTERN.matcher(sourceIcon.toString()); + if (matcher.matches()) { + IconArrayImpl iconArray = (IconArrayImpl) SitemapFactory.eINSTANCE.createIconArray(); + iconArray.setItem(matcher.group("item")); + iconArray.setCondition(matcher.group("condition")); + iconArray.setSign(matcher.group("sign")); + iconArray.setState(matcher.group("state")); + iconArray.setArg(matcher.group("arg")); + iconDef.add(iconArray); + } else { + logger.warn("Syntax error in icon rule '{}' for widget {}", sourceIcon, component.getType()); + } + } + } + } + } + @Override public void addModelChangeListener(ModelRepositoryChangeListener listener) { modelChangeListeners.add(listener); diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java index c290fb52640..323a739bcae 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java @@ -68,6 +68,7 @@ import org.openhab.core.model.sitemap.sitemap.ColorArray; import org.openhab.core.model.sitemap.sitemap.Default; import org.openhab.core.model.sitemap.sitemap.Group; +import org.openhab.core.model.sitemap.sitemap.IconArray; import org.openhab.core.model.sitemap.sitemap.LinkableWidget; import org.openhab.core.model.sitemap.sitemap.Mapping; import org.openhab.core.model.sitemap.sitemap.Sitemap; @@ -109,6 +110,7 @@ * @author Erdoan Hadzhiyusein - Adapted the class to work with the new DateTimeType * @author Laurent Garnier - new method getIconColor * @author Mark Herwege - new method getFormatPattern(widget), clean pattern + * @author Laurent Garnier - new icon parameter based on conditional rules */ @NonNullByDefault @Component(immediate = true, configurationPid = "org.openhab.sitemap", // @@ -638,11 +640,14 @@ private String transform(String label, boolean matchTransform, @Nullable String // the default is the widget type name, e.g. "switch" String category = widgetTypeName.toLowerCase(); + String dynamicIcon = getDynamicIcon(w); // if an icon is defined for the widget, use it if (w.getIcon() != null) { category = w.getIcon(); } else if (w.getStaticIcon() != null) { category = w.getStaticIcon(); + } else if (w.getDynamicIcon() != null && !w.getDynamicIcon().isEmpty() && dynamicIcon != null) { + category = dynamicIcon; } else { // otherwise check if any item ui provider provides an icon for this item String itemName = w.getItem(); @@ -800,6 +805,7 @@ private void copyProperties(Widget source, Widget target) { target.getLabelColor().addAll(EcoreUtil.copyAll(source.getLabelColor())); target.getValueColor().addAll(EcoreUtil.copyAll(source.getValueColor())); target.getIconColor().addAll(EcoreUtil.copyAll(source.getIconColor())); + target.getDynamicIcon().addAll(EcoreUtil.copyAll(source.getDynamicIcon())); } /** @@ -1182,7 +1188,6 @@ private boolean matchStateToValue(State state, String value, @Nullable String ma String itemName = color.getItem(); if (itemName != null) { // Try and find the item to test. - // If it's not found, return visible Item item; try { item = itemRegistry.getItem(itemName); @@ -1210,11 +1215,11 @@ private boolean matchStateToValue(State state, String value, @Nullable String ma } } - // Remove quotes off the colour - if they exist if (colorString == null) { return null; } + // Remove quotes off the colour - if they exist if (colorString.startsWith("\"") && colorString.endsWith("\"")) { colorString = colorString.substring(1, colorString.length() - 1); } @@ -1237,6 +1242,87 @@ private boolean matchStateToValue(State state, String value, @Nullable String ma return processColorDefinition(getState(w), w.getIconColor()); } + @Override + public @Nullable String getDynamicIcon(Widget w) { + List ruleList = w.getDynamicIcon(); + // Sanity check + if (ruleList == null) { + return null; + } + if (ruleList.isEmpty()) { + return null; + } + + logger.debug("Checking icon for widget '{}'.", w.getLabel()); + + String icon = null; + + // Check for the "arg". If it doesn't exist, assume there's just an + // unconditioned icon + if (ruleList.size() == 1 && ruleList.get(0).getState() == null) { + icon = ruleList.get(0).getArg(); + } else { + State defaultState = getState(w); + + // Loop through all elements looking for the definition associated + // with the supplied value + for (IconArray rule : ruleList) { + // Use a local state variable in case it gets overridden below + State cmpState = defaultState; + + if (rule.getState() == null) { + // If no state associated to the condition, we consider the condition as fulfilled. + // It allows defining a default icon as last condition in particular. + icon = rule.getArg(); + break; + } + + // If there's an item defined here, get its state + String itemName = rule.getItem(); + if (itemName != null) { + // Try and find the item to test. + Item item; + try { + item = itemRegistry.getItem(itemName); + + // Get the item state + cmpState = item.getState(); + } catch (ItemNotFoundException e) { + logger.warn("Cannot retrieve icon item {} for widget {}", rule.getItem(), + w.eClass().getInstanceTypeName()); + } + } + + // Handle the sign + String value; + if (rule.getSign() != null) { + value = rule.getSign() + rule.getState(); + } else { + value = rule.getState(); + } + + if (cmpState != null && matchStateToValue(cmpState, value, rule.getCondition())) { + // We have the icon for this value - break! + icon = rule.getArg(); + break; + } + } + } + + if (icon == null) { + logger.debug("No icon found for widget '{}'.", w.getLabel()); + return null; + } + + // Remove quotes off the icon - if they exist + if (icon.startsWith("\"") && icon.endsWith("\"")) { + icon = icon.substring(1, icon.length() - 1); + logger.debug("icon for widget '{}' is '{}'.", w.getLabel(), icon); + } + + return icon; + } + @Override public boolean getVisiblity(Widget w) { // Default to visible if parameters not set diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIRegistry.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIRegistry.java index a979543d4cf..eac87bc7a62 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIRegistry.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIRegistry.java @@ -35,6 +35,7 @@ * @author Chris Jackson - Initial contribution * @author Laurent Garnier - new method getIconColor * @author Mark Herwege - new method getFormatPattern + * @author Laurent Garnier - new method getDynamicIcon */ @NonNullByDefault public interface ItemUIRegistry extends ItemRegistry, ItemUIProvider { @@ -170,6 +171,16 @@ public interface ItemUIRegistry extends ItemRegistry, ItemUIProvider { @Nullable String getIconColor(Widget w); + /** + * Gets the dynamic icon for the widget. Checks conditional statements to + * find the icon based on the item value + * + * @param w Widget + * @return String with the icon reference + */ + @Nullable + String getDynamicIcon(Widget w); + /** * Gets the widget visibility based on the item state *