From fb0ab17fdef7cd534a9831a83e4092bf7154d8e1 Mon Sep 17 00:00:00 2001 From: Laurent Garnier Date: Wed, 6 Sep 2023 08:47:02 +0200 Subject: [PATCH] Allow multiple conditions with AND operator in color or icon rules Closes #3058 Signed-off-by: Laurent Garnier --- .../sitemap/internal/PageChangeListener.java | 47 +++-- .../sitemap/internal/SitemapResource.java | 49 ++--- .../sitemap/internal/SitemapResourceTest.java | 19 +- .../openhab/core/model/sitemap/Sitemap.xtext | 41 ++--- .../UIComponentSitemapProvider.java | 36 ++-- .../ui/internal/items/ItemUIRegistryImpl.java | 168 +++++++++--------- .../items/ItemUIRegistryImplTest.java | 69 ++++--- 7 files changed, 253 insertions(+), 176 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 868a82d4127..8ae8be8d505 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 @@ -36,8 +36,9 @@ import org.openhab.core.library.CoreItemFactory; import org.openhab.core.model.sitemap.sitemap.Chart; import org.openhab.core.model.sitemap.sitemap.ColorArray; +import org.openhab.core.model.sitemap.sitemap.Condition; import org.openhab.core.model.sitemap.sitemap.Frame; -import org.openhab.core.model.sitemap.sitemap.IconArray; +import org.openhab.core.model.sitemap.sitemap.IconRule; import org.openhab.core.model.sitemap.sitemap.VisibilityRule; import org.openhab.core.model.sitemap.sitemap.Widget; import org.openhab.core.types.State; @@ -122,8 +123,12 @@ private Set getAllItems(EList widgets) { items.addAll(getAllItems(frame.getChildren())); } // now scan dynamic icon rules - for (IconArray rule : widget.getDynamicIcon()) { - addItemWithName(items, rule.getItem()); + for (IconRule rule : widget.getDynamicIcon()) { + if (rule.getConditions() != null) { + for (Condition condition : rule.getConditions()) { + addItemWithName(items, condition.getItem()); + } + } } // now scan visibility rules for (VisibilityRule rule : widget.getVisibility()) { @@ -131,15 +136,27 @@ private Set getAllItems(EList widgets) { } // now scan label color rules for (ColorArray rule : widget.getLabelColor()) { - addItemWithName(items, rule.getItem()); + if (rule.getConditions() != null) { + for (Condition condition : rule.getConditions()) { + addItemWithName(items, condition.getItem()); + } + } } // now scan value color rules for (ColorArray rule : widget.getValueColor()) { - addItemWithName(items, rule.getItem()); + if (rule.getConditions() != null) { + for (Condition condition : rule.getConditions()) { + addItemWithName(items, condition.getItem()); + } + } } // now scan icon color rules for (ColorArray rule : widget.getIconColor()) { - addItemWithName(items, rule.getItem()); + if (rule.getConditions() != null) { + for (Condition condition : rule.getConditions()) { + addItemWithName(items, condition.getItem()); + } + } } } } @@ -248,10 +265,20 @@ private Item getItemForWidget(Widget w) { 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.getDynamicIcon().stream().anyMatch(r -> name.equals(r.getItem())); + || w.getLabelColor().stream().anyMatch(r -> colorRuleDependsOnItem(r, name)) + || w.getValueColor().stream().anyMatch(r -> colorRuleDependsOnItem(r, name)) + || w.getIconColor().stream().anyMatch(r -> colorRuleDependsOnItem(r, name)) + || w.getDynamicIcon().stream().anyMatch(r -> iconRuleDependsOnItem(r, name)); + } + + private boolean colorRuleDependsOnItem(ColorArray rule, String name) { + return rule != null && rule.getConditions() != null + && rule.getConditions().stream().anyMatch(c -> name.equals(c.getItem())); + } + + private boolean iconRuleDependsOnItem(IconRule rule, String name) { + return rule != null && rule.getConditions() != null + && rule.getConditions().stream().anyMatch(c -> name.equals(c.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 1b78177be49..deda66dde6d 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 @@ -78,8 +78,9 @@ import org.openhab.core.model.sitemap.SitemapProvider; import org.openhab.core.model.sitemap.sitemap.Chart; import org.openhab.core.model.sitemap.sitemap.ColorArray; +import org.openhab.core.model.sitemap.sitemap.Condition; import org.openhab.core.model.sitemap.sitemap.Frame; -import org.openhab.core.model.sitemap.sitemap.IconArray; +import org.openhab.core.model.sitemap.sitemap.IconRule; import org.openhab.core.model.sitemap.sitemap.Image; import org.openhab.core.model.sitemap.sitemap.Input; import org.openhab.core.model.sitemap.sitemap.LinkableWidget; @@ -774,34 +775,42 @@ private Set getItemsInVisibilityCond(EList ruleList private Set getItemsInColorCond(EList colorList) { Set items = new HashSet<>(); - for (ColorArray color : colorList) { - String itemName = color.getItem(); - if (itemName != null) { - try { - Item item = itemUIRegistry.getItem(itemName); - if (item instanceof GenericItem genericItem) { - items.add(genericItem); + for (ColorArray rule : colorList) { + if (rule.getConditions() != null) { + for (Condition condition : rule.getConditions()) { + String itemName = condition.getItem(); + if (itemName != null) { + try { + Item item = itemUIRegistry.getItem(itemName); + if (item instanceof GenericItem genericItem) { + items.add(genericItem); + } + } catch (ItemNotFoundException e) { + // ignore + } } - } catch (ItemNotFoundException e) { - // ignore } } } return items; } - private Set getItemsInIconCond(EList ruleList) { + 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); + for (IconRule rule : ruleList) { + if (rule.getConditions() != null) { + for (Condition condition : rule.getConditions()) { + String itemName = condition.getItem(); + if (itemName != null) { + try { + Item item = itemUIRegistry.getItem(itemName); + if (item instanceof GenericItem genericItem) { + items.add(genericItem); + } + } catch (ItemNotFoundException e) { + // ignore + } } - } catch (ItemNotFoundException e) { - // ignore } } } 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 b525ca6b23b..2b759b55692 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 @@ -49,6 +49,7 @@ import org.openhab.core.library.types.PercentType; import org.openhab.core.model.sitemap.SitemapProvider; import org.openhab.core.model.sitemap.sitemap.ColorArray; +import org.openhab.core.model.sitemap.sitemap.Condition; import org.openhab.core.model.sitemap.sitemap.Sitemap; import org.openhab.core.model.sitemap.sitemap.VisibilityRule; import org.openhab.core.model.sitemap.sitemap.Widget; @@ -342,21 +343,33 @@ private EList initSitemapWidgets() { // add label color conditions to the item: ColorArray labelColor = mock(ColorArray.class); - when(labelColor.getItem()).thenReturn(LABEL_COLOR_ITEM_NAME); + Condition conditon = mock(Condition.class); + when(conditon.getItem()).thenReturn(LABEL_COLOR_ITEM_NAME); + BasicEList conditions = new BasicEList<>(); + conditions.add(conditon); + when(labelColor.getConditions()).thenReturn(conditions); EList labelColors = new BasicEList<>(); labelColors.add(labelColor); when(w1.getLabelColor()).thenReturn(labelColors); // add value color conditions to the item: ColorArray valueColor = mock(ColorArray.class); - when(valueColor.getItem()).thenReturn(VALUE_COLOR_ITEM_NAME); + Condition conditon2 = mock(Condition.class); + when(conditon2.getItem()).thenReturn(VALUE_COLOR_ITEM_NAME); + BasicEList conditions2 = new BasicEList<>(); + conditions2.add(conditon2); + when(valueColor.getConditions()).thenReturn(conditions2); EList valueColors = new BasicEList<>(); valueColors.add(valueColor); when(w1.getValueColor()).thenReturn(valueColors); // add icon color conditions to the item: ColorArray iconColor = mock(ColorArray.class); - when(iconColor.getItem()).thenReturn(ICON_COLOR_ITEM_NAME); + Condition conditon3 = mock(Condition.class); + when(conditon3.getItem()).thenReturn(ICON_COLOR_ITEM_NAME); + BasicEList conditions3 = new BasicEList<>(); + conditions3.add(conditon3); + when(iconColor.getConditions()).thenReturn(conditions3); EList iconColors = new BasicEList<>(); iconColors.add(iconColor); when(w1.getIconColor()).thenReturn(iconColors); 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 09a7fb50f86..817c4adf256 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 @@ -26,7 +26,7 @@ LinkableWidget: Frame: {Frame} 'Frame' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -36,7 +36,7 @@ Frame: Text: {Text} 'Text' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -46,7 +46,7 @@ Text: Group: 'Group' (('item=' item=GroupItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -56,7 +56,7 @@ Group: Image: 'Image' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('url=' url=STRING)? & ('refresh=' refresh=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -67,7 +67,7 @@ Image: Video: 'Video' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('url=' url=STRING) & ('encoding=' encoding=STRING)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -78,7 +78,7 @@ Video: Chart: 'Chart' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('service=' service=STRING)? & ('refresh=' refresh=INT)? & ('period=' period=ID) & ('legend=' legend=BOOLEAN_OBJECT)? & ('forceasitem=' forceAsItem=BOOLEAN_OBJECT)? & @@ -91,7 +91,7 @@ Chart: Webview: 'Webview' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('height=' height=INT)? & ('url=' url=STRING) & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -102,7 +102,7 @@ Webview: Switch: 'Switch' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('mappings=[' mappings+=Mapping (',' mappings+=Mapping)* ']')? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -113,7 +113,7 @@ Switch: Mapview: 'Mapview' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('height=' height=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -124,7 +124,7 @@ Mapview: Slider: 'Slider' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('sendFrequency=' frequency=INT)? & (switchEnabled?='switchSupport')? & ('minValue=' minValue=Number)? & ('maxValue=' maxValue=Number)? & ('step=' step=Number)? & @@ -136,7 +136,7 @@ Slider: Selection: 'Selection' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('mappings=[' mappings+=Mapping (',' mappings+=Mapping)* ']')? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -147,7 +147,7 @@ Selection: Setpoint: 'Setpoint' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('minValue=' minValue=Number)? & ('maxValue=' maxValue=Number)? & ('step=' step=Number)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -158,7 +158,7 @@ Setpoint: Colorpicker: 'Colorpicker' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('sendFrequency=' frequency=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -169,7 +169,7 @@ Colorpicker: Input: 'Input' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('inputHint=' inputHint=STRING)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -180,7 +180,7 @@ Input: Default: 'Default' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & (('icon=' icon=Icon) | - ('icon=[' (dynamicIcon+=IconArray (',' dynamicIcon+=IconArray)* ']')) | + ('icon=[' (dynamicIcon+=IconRule (',' dynamicIcon+=IconRule)*) ']') | ('staticIcon=' staticIcon=Icon))? & ('height=' height=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -208,12 +208,13 @@ IconName: (ID '-')* ID; ColorArray: - ((item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState) '=')? - (arg=STRING); + ((conditions+=Condition ('AND' conditions+=Condition)*) '=')? (arg=STRING); -IconArray: - ((item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState) '=')? - (arg=Icon); +IconRule: + ((conditions+=Condition ('AND' conditions+=Condition)*) '=')? (arg=Icon); + +Condition: + (item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState); 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 8b313051b6a..1564c462745 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,7 +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.IconRule; import org.openhab.core.model.sitemap.sitemap.LinkableWidget; import org.openhab.core.model.sitemap.sitemap.Mapping; import org.openhab.core.model.sitemap.sitemap.Sitemap; @@ -42,10 +42,11 @@ import org.openhab.core.model.sitemap.sitemap.impl.ChartImpl; import org.openhab.core.model.sitemap.sitemap.impl.ColorArrayImpl; import org.openhab.core.model.sitemap.sitemap.impl.ColorpickerImpl; +import org.openhab.core.model.sitemap.sitemap.impl.ConditionImpl; 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.IconRuleImpl; 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; @@ -93,8 +94,7 @@ public class UIComponentSitemapProvider implements SitemapProvider, RegistryChan .compile("(?[A-Za-z]\\w*)\\s*(?==|!=|<=|>=|<|>)\\s*(?\\+|-)?(?.+)"); private static final Pattern COLOR_PATTERN = Pattern.compile( "((?[A-Za-z]\\w*)?\\s*((?==|!=|<=|>=|<|>)\\s*(?\\+|-)?(?[^=]+))?\\s*=)?\\s*(?\\S+)"); - private static final Pattern ICON_PATTERN = Pattern.compile( - "((?[A-Za-z]\\w*)?\\s*((?==|!=|<=|>=|<|>)\\s*(?\\+|-)?(?[^=]+))?\\s*=)?\\s*(?\\S+)"); + private static final Pattern ICON_PATTERN = COLOR_PATTERN; private Map sitemaps = new HashMap<>(); private @Nullable UIComponentRegistryFactory componentRegistryFactory; @@ -380,10 +380,12 @@ private void addColor(EList color, UIComponent component, String key Matcher matcher = COLOR_PATTERN.matcher(sourceColor.toString()); if (matcher.matches()) { ColorArrayImpl colorArray = (ColorArrayImpl) SitemapFactory.eINSTANCE.createColorArray(); - colorArray.setItem(matcher.group("item")); - colorArray.setCondition(matcher.group("condition")); - colorArray.setSign(matcher.group("sign")); - colorArray.setState(matcher.group("state")); + ConditionImpl condition = (ConditionImpl) SitemapFactory.eINSTANCE.createCondition(); + condition.setItem(matcher.group("item")); + condition.setCondition(matcher.group("condition")); + condition.setSign(matcher.group("sign")); + condition.setState(matcher.group("state")); + colorArray.eSet(SitemapPackage.COLOR_ARRAY__CONDITIONS, condition); colorArray.setArg(matcher.group("arg")); color.add(colorArray); } else { @@ -395,19 +397,21 @@ private void addColor(EList color, UIComponent component, String key } } - private void addDynamicIcon(EList iconDef, UIComponent component) { + 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); + IconRuleImpl iconRule = (IconRuleImpl) SitemapFactory.eINSTANCE.createIconRule(); + ConditionImpl condition = (ConditionImpl) SitemapFactory.eINSTANCE.createCondition(); + condition.setItem(matcher.group("item")); + condition.setCondition(matcher.group("condition")); + condition.setSign(matcher.group("sign")); + condition.setState(matcher.group("state")); + iconRule.eSet(SitemapPackage.ICON_RULE__CONDITIONS, condition); + iconRule.setArg(matcher.group("arg")); + iconDef.add(iconRule); } else { logger.warn("Syntax error in icon rule '{}' for widget {}", sourceIcon, component.getType()); } 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 edb14d69e37..84ea9e95c5d 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,7 +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.IconRule; import org.openhab.core.model.sitemap.sitemap.LinkableWidget; import org.openhab.core.model.sitemap.sitemap.Mapping; import org.openhab.core.model.sitemap.sitemap.Sitemap; @@ -1166,53 +1166,50 @@ private boolean matchStateToValue(State state, String value, @Nullable String ma String colorString = null; - // Check for the "arg". If it doesn't exist, assume there's just an - // static colour - if (colorList.size() == 1 && colorList.get(0).getState() == null) { - colorString = colorList.get(0).getArg(); - } else { - // Loop through all elements looking for the definition associated - // with the supplied value - for (ColorArray color : colorList) { - // Use a local state variable in case it gets overridden below - State cmpState = state; - - if (color.getState() == null) { - // If no state associated to the condition, we consider the condition as fulfilled. - // It allows defining a default color as last condition in particular. - colorString = color.getArg(); - break; - } - - // If there's an item defined here, get its state - String itemName = color.getItem(); - if (itemName != null) { - // Try and find the item to test. - Item item; - try { - item = itemRegistry.getItem(itemName); + // Loop through all elements looking for the definition associated + // with the supplied value + for (ColorArray rule : colorList) { + boolean allConditionsOk = true; + if (rule.getConditions() != null) { + // Go through all AND conditions + for (org.openhab.core.model.sitemap.sitemap.Condition condition : rule.getConditions()) { + // Use a local state variable in case it gets overridden below + State cmpState = state; + + // If there's an item defined here, get its state + String itemName = condition.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 color item {} for widget", color.getItem()); + // Get the item state + cmpState = item.getState(); + } catch (ItemNotFoundException e) { + logger.warn("Cannot retrieve color item {} for widget", condition.getItem()); + } } - } - // Handle the sign - String value; - if (color.getSign() != null) { - value = color.getSign() + color.getState(); - } else { - value = color.getState(); - } + // Handle the sign + String value; + if (condition.getSign() != null) { + value = condition.getSign() + condition.getState(); + } else { + value = condition.getState(); + } - if (cmpState != null && matchStateToValue(cmpState, value, color.getCondition())) { - // We have the color for this value - break! - colorString = color.getArg(); - break; + if (cmpState == null || !matchStateToValue(cmpState, value, condition.getCondition())) { + allConditionsOk = false; + break; + } } } + if (allConditionsOk) { + // We have the color for this value - break! + colorString = rule.getArg(); + break; + } } if (colorString == null) { @@ -1244,7 +1241,7 @@ private boolean matchStateToValue(State state, String value, @Nullable String ma @Override public @Nullable String getDynamicIcon(Widget w) { - List ruleList = w.getDynamicIcon(); + List ruleList = w.getDynamicIcon(); // Sanity check if (ruleList == null) { return null; @@ -1257,56 +1254,53 @@ private boolean matchStateToValue(State state, String value, @Nullable String ma 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); + State defaultState = getState(w); + + // Loop through all elements looking for the definition associated + // with the supplied value + for (IconRule rule : ruleList) { + boolean allConditionsOk = true; + if (rule.getConditions() != null) { + // Go through all AND conditions + for (org.openhab.core.model.sitemap.sitemap.Condition condition : rule.getConditions()) { + // Use a local state variable in case it gets overridden below + State cmpState = defaultState; + + // If there's an item defined here, get its state + String itemName = condition.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()); + // Get the item state + cmpState = item.getState(); + } catch (ItemNotFoundException e) { + logger.warn("Cannot retrieve icon item {} for widget {}", condition.getItem(), + w.eClass().getInstanceTypeName()); + } } - } - // Handle the sign - String value; - if (rule.getSign() != null) { - value = rule.getSign() + rule.getState(); - } else { - value = rule.getState(); - } + // Handle the sign + String value; + if (condition.getSign() != null) { + value = condition.getSign() + condition.getState(); + } else { + value = condition.getState(); + } - if (cmpState != null && matchStateToValue(cmpState, value, rule.getCondition())) { - // We have the icon for this value - break! - icon = rule.getArg(); - break; + if (cmpState == null || !matchStateToValue(cmpState, value, condition.getCondition())) { + allConditionsOk = false; + break; + } } } + if (allConditionsOk) { + // We have the icon for this value - break! + icon = rule.getArg(); + break; + } } if (icon == null) { diff --git a/bundles/org.openhab.core.ui/src/test/java/org/openhab/core/ui/internal/items/ItemUIRegistryImplTest.java b/bundles/org.openhab.core.ui/src/test/java/org/openhab/core/ui/internal/items/ItemUIRegistryImplTest.java index cd00015cac1..bb8b1a5b629 100644 --- a/bundles/org.openhab.core.ui/src/test/java/org/openhab/core/ui/internal/items/ItemUIRegistryImplTest.java +++ b/bundles/org.openhab.core.ui/src/test/java/org/openhab/core/ui/internal/items/ItemUIRegistryImplTest.java @@ -60,6 +60,7 @@ import org.openhab.core.library.types.StringType; import org.openhab.core.model.sitemap.sitemap.ColorArray; import org.openhab.core.model.sitemap.sitemap.Colorpicker; +import org.openhab.core.model.sitemap.sitemap.Condition; import org.openhab.core.model.sitemap.sitemap.Group; import org.openhab.core.model.sitemap.sitemap.Image; import org.openhab.core.model.sitemap.sitemap.Mapping; @@ -736,9 +737,13 @@ public void getLabelColorLabelWithDecimalValue() { when(widgetMock.getLabel()).thenReturn(testLabel); + Condition conditon = mock(Condition.class); + when(conditon.getState()).thenReturn("21"); + when(conditon.getCondition()).thenReturn("<"); + BasicEList conditions = new BasicEList<>(); + conditions.add(conditon); ColorArray colorArray = mock(ColorArray.class); - when(colorArray.getState()).thenReturn("21"); - when(colorArray.getCondition()).thenReturn("<"); + when(colorArray.getConditions()).thenReturn(conditions); when(colorArray.getArg()).thenReturn("yellow"); BasicEList colorArrays = new BasicEList<>(); colorArrays.add(colorArray); @@ -756,9 +761,13 @@ public void getLabelColorLabelWithUnitValue() { when(widgetMock.getLabel()).thenReturn(testLabel); + Condition conditon = mock(Condition.class); + when(conditon.getState()).thenReturn("20"); + when(conditon.getCondition()).thenReturn("=="); + BasicEList conditions = new BasicEList<>(); + conditions.add(conditon); ColorArray colorArray = mock(ColorArray.class); - when(colorArray.getState()).thenReturn("20"); - when(colorArray.getCondition()).thenReturn("=="); + when(colorArray.getConditions()).thenReturn(conditions); when(colorArray.getArg()).thenReturn("yellow"); BasicEList colorArrays = new BasicEList<>(); colorArrays.add(colorArray); @@ -925,15 +934,19 @@ public void getLabelColorDefaultColor() { when(widgetMock.getLabel()).thenReturn(testLabel); + Condition conditon = mock(Condition.class); + when(conditon.getState()).thenReturn("21"); + when(conditon.getCondition()).thenReturn("<"); + BasicEList conditions = new BasicEList<>(); + conditions.add(conditon); ColorArray colorArray = mock(ColorArray.class); - when(colorArray.getState()).thenReturn("21"); - when(colorArray.getCondition()).thenReturn("<"); + when(colorArray.getConditions()).thenReturn(conditions); when(colorArray.getArg()).thenReturn("yellow"); BasicEList colorArrays = new BasicEList<>(); colorArrays.add(colorArray); + BasicEList conditions2 = new BasicEList<>(); ColorArray colorArray2 = mock(ColorArray.class); - when(colorArray2.getState()).thenReturn(null); - when(colorArray2.getCondition()).thenReturn(null); + when(colorArray2.getConditions()).thenReturn(conditions2); when(colorArray2.getArg()).thenReturn("blue"); colorArrays.add(colorArray2); when(widgetMock.getLabelColor()).thenReturn(colorArrays); @@ -946,20 +959,28 @@ public void getLabelColorDefaultColor() { @Test public void getValueColor() { + Condition conditon = mock(Condition.class); + when(conditon.getState()).thenReturn("21"); + when(conditon.getCondition()).thenReturn("<"); + BasicEList conditions = new BasicEList<>(); + conditions.add(conditon); ColorArray colorArray = mock(ColorArray.class); - when(colorArray.getState()).thenReturn("21"); - when(colorArray.getCondition()).thenReturn("<"); + when(colorArray.getConditions()).thenReturn(conditions); when(colorArray.getArg()).thenReturn("yellow"); BasicEList colorArrays = new BasicEList<>(); colorArrays.add(colorArray); + Condition conditon2 = mock(Condition.class); + when(conditon2.getState()).thenReturn("24"); + when(conditon2.getCondition()).thenReturn("<"); + BasicEList conditions2 = new BasicEList<>(); + conditions2.add(conditon2); ColorArray colorArray2 = mock(ColorArray.class); - when(colorArray2.getState()).thenReturn("24"); - when(colorArray2.getCondition()).thenReturn("<"); + when(colorArray2.getConditions()).thenReturn(conditions2); when(colorArray2.getArg()).thenReturn("red"); colorArrays.add(colorArray2); + BasicEList conditions3 = new BasicEList<>(); ColorArray colorArray3 = mock(ColorArray.class); - when(colorArray3.getState()).thenReturn(null); - when(colorArray3.getCondition()).thenReturn(null); + when(colorArray3.getConditions()).thenReturn(conditions3); when(colorArray3.getArg()).thenReturn("blue"); colorArrays.add(colorArray3); when(widgetMock.getValueColor()).thenReturn(colorArrays); @@ -982,20 +1003,28 @@ public void getValueColor() { @Test public void getIconColor() { + Condition conditon = mock(Condition.class); + when(conditon.getState()).thenReturn("21"); + when(conditon.getCondition()).thenReturn("<"); + BasicEList conditions = new BasicEList<>(); + conditions.add(conditon); ColorArray colorArray = mock(ColorArray.class); - when(colorArray.getState()).thenReturn("21"); - when(colorArray.getCondition()).thenReturn("<"); + when(colorArray.getConditions()).thenReturn(conditions); when(colorArray.getArg()).thenReturn("yellow"); BasicEList colorArrays = new BasicEList<>(); colorArrays.add(colorArray); + Condition conditon2 = mock(Condition.class); + when(conditon2.getState()).thenReturn("24"); + when(conditon2.getCondition()).thenReturn("<"); + BasicEList conditions2 = new BasicEList<>(); + conditions2.add(conditon2); ColorArray colorArray2 = mock(ColorArray.class); - when(colorArray2.getState()).thenReturn("24"); - when(colorArray2.getCondition()).thenReturn("<"); + when(colorArray2.getConditions()).thenReturn(conditions2); when(colorArray2.getArg()).thenReturn("red"); colorArrays.add(colorArray2); + BasicEList conditions3 = new BasicEList<>(); ColorArray colorArray3 = mock(ColorArray.class); - when(colorArray3.getState()).thenReturn(null); - when(colorArray3.getCondition()).thenReturn(null); + when(colorArray3.getConditions()).thenReturn(conditions3); when(colorArray3.getArg()).thenReturn("blue"); colorArrays.add(colorArray3); when(widgetMock.getIconColor()).thenReturn(colorArrays);