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..e4aa5238394 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 @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import org.eclipse.emf.common.util.EList; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.events.Event; import org.openhab.core.events.EventSubscriber; @@ -36,7 +37,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.IconRule; import org.openhab.core.model.sitemap.sitemap.VisibilityRule; import org.openhab.core.model.sitemap.sitemap.Widget; import org.openhab.core.types.State; @@ -47,6 +50,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 +123,10 @@ private Set getAllItems(EList widgets) { if (widget instanceof Frame frame) { items.addAll(getAllItems(frame.getChildren())); } + // now scan icon rules + for (IconRule rule : widget.getIconRules()) { + addItemsFromConditions(items, rule.getConditions()); + } // now scan visibility rules for (VisibilityRule rule : widget.getVisibility()) { addItemWithName(items, rule.getItem()); @@ -140,6 +148,14 @@ private Set getAllItems(EList widgets) { return items; } + private void addItemsFromConditions(Set items, @Nullable EList conditions) { + if (conditions != null) { + for (Condition condition : conditions) { + addItemWithName(items, condition.getItem()); + } + } + } + private void addItemWithName(Set items, String itemName) { if (itemName != null) { try { @@ -183,7 +199,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 +213,8 @@ private SitemapWidgetEvent constructSitemapEventForWidget(Item item, State state event.pageId = pageId; event.label = itemUIRegistry.getLabel(widget); event.widgetId = itemUIRegistry.getWidgetId(widget); + event.icon = itemUIRegistry.getCategory(widget); + event.reloadIcon = widget.getStaticIcon() == null; event.visibility = itemUIRegistry.getVisiblity(widget); event.descriptionChanged = false; // event.item contains the (potentially changed) data of the item belonging to @@ -237,11 +255,16 @@ 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.getIconRules().stream().anyMatch(r -> conditionsDependsOnItem(r.getConditions(), name)); + } + + private boolean conditionsDependsOnItem(@Nullable EList conditions, String name) { + return conditions != null && conditions.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 1b73d4222da..f229ed906bc 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 @@ -80,7 +80,9 @@ import org.openhab.core.model.sitemap.sitemap.Buttongrid; 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.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; @@ -135,6 +137,7 @@ * @author Mark Herwege - Added pattern and unit fields * @author Laurent Garnier - Added support for new sitemap element Buttongrid * @author Laurent Garnier - Added icon field for mappings used for switch element + * @author Laurent Garnier - New widget icon parameter based on conditional rules */ @Component(service = { RESTResource.class, EventSubscriber.class }) @JaxrsResource @@ -527,7 +530,7 @@ 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.getIconRules().isEmpty(); bean.labelcolor = convertItemValueColor(itemUIRegistry.getLabelColor(widget), itemState); bean.valuecolor = convertItemValueColor(itemUIRegistry.getValueColor(widget), itemState); bean.iconcolor = convertItemValueColor(itemUIRegistry.getIconColor(widget), itemState); @@ -757,6 +760,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.getIconRules())); // Consider items involved in any visibility, labelcolor, valuecolor and iconcolor condition items.addAll(getItemsInVisibilityCond(widget.getVisibility())); items.addAll(getItemsInColorCond(widget.getLabelColor())); @@ -802,6 +807,32 @@ private Set getItemsInColorCond(EList colorList) { return items; } + private Set getItemsInIconCond(EList ruleList) { + Set items = new HashSet<>(); + for (IconRule rule : ruleList) { + getItemsInConditions(rule.getConditions(), items); + } + return items; + } + + private void getItemsInConditions(@Nullable EList conditions, Set items) { + if (conditions != null) { + for (Condition condition : conditions) { + 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 + } + } + } + } + } + @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..09127cf7675 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 reloadIcon */ public class SitemapWidgetEvent extends SitemapEvent { @@ -26,6 +27,7 @@ public class SitemapWidgetEvent extends SitemapEvent { public String label; public String icon; + public boolean reloadIcon; public String labelcolor; public String valuecolor; public String iconcolor; diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java index b5fe7d2f0c5..43d7b605f2d 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java @@ -36,6 +36,11 @@ public class WidgetDTO { public String label; public String icon; + /** + * staticIcon is a boolean indicating if the widget state must be ignored when requesting the icon. + * It is set to true when the widget has either the staticIcon property set or the icon property set + * with conditional rules. + */ public Boolean staticIcon; public String labelcolor; public String valuecolor; 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..f48683aa9f9 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,8 @@ 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.IconRule; import org.openhab.core.model.sitemap.sitemap.Sitemap; import org.openhab.core.model.sitemap.sitemap.VisibilityRule; import org.openhab.core.model.sitemap.sitemap.Widget; @@ -61,6 +63,7 @@ * Test aspects of the {@link SitemapResource}. * * @author Henning Treu - Initial contribution + * @author Laurent Garnier - Extended tests for static icon and icon based on conditional rules */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -79,10 +82,16 @@ public class SitemapResourceTest extends JavaTest { private static final String LABEL_COLOR_ITEM_NAME = "labelColorItemName"; private static final String VALUE_COLOR_ITEM_NAME = "valueColorItemName"; private static final String ICON_COLOR_ITEM_NAME = "iconColorItemName"; + private static final String ICON_ITEM_NAME = "iconItemName"; private static final String WIDGET1_LABEL = "widget 1"; private static final String WIDGET2_LABEL = "widget 2"; + private static final String WIDGET3_LABEL = "widget 3"; private static final String WIDGET1_ID = "00"; private static final String WIDGET2_ID = "01"; + private static final String WIDGET3_ID = "02"; + private static final String WIDGET1_ICON = "icon1"; + private static final String WIDGET2_ICON = "icon2"; + private static final String WIDGET3_ICON = "icon3"; private static final String CLIENT_IP = "127.0.0.1"; private @NonNullByDefault({}) SitemapResource sitemapResource; @@ -92,6 +101,7 @@ public class SitemapResourceTest extends JavaTest { private @NonNullByDefault({}) GenericItem labelColorItem; private @NonNullByDefault({}) GenericItem valueColorItem; private @NonNullByDefault({}) GenericItem iconColorItem; + private @NonNullByDefault({}) GenericItem iconItem; private @Mock @NonNullByDefault({}) HttpHeaders headersMock; private @Mock @NonNullByDefault({}) Sitemap defaultSitemapMock; @@ -120,6 +130,7 @@ public void setup() throws Exception { labelColorItem = new TestItem(LABEL_COLOR_ITEM_NAME); valueColorItem = new TestItem(VALUE_COLOR_ITEM_NAME); iconColorItem = new TestItem(ICON_COLOR_ITEM_NAME); + iconItem = new TestItem(ICON_ITEM_NAME); when(localeServiceMock.getLocale(null)).thenReturn(Locale.US); @@ -251,6 +262,30 @@ public void whenLongPollingShouldObserveItemsFromValueColorConditions() { // return } + @Test + public void whenLongPollingShouldObserveItemsFromIconConditions() { + ItemEvent itemEvent = mock(ItemEvent.class); + when(itemEvent.getItemName()).thenReturn(iconItem.getName()); + new Thread(() -> { + try { + Thread.sleep(STATE_UPDATE_WAIT_TIME); // wait for the #getPageData call and listeners to attach to the + // item + sitemapResource.receive(itemEvent); + } catch (InterruptedException e) { + } + }).start(); + + // non-null is sufficient here. + when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of()); + + Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null, + false); + + PageDTO pageDTO = (PageDTO) response.getEntity(); + assertThat(pageDTO.timeout, is(false)); // assert that the item state change did trigger the blocking method to + // return + } + @Test public void whenGetPageDataShouldReturnPageBean() throws ItemNotFoundException { item.setState(new PercentType(50)); @@ -269,13 +304,15 @@ public void whenGetPageDataShouldReturnPageBean() throws ItemNotFoundException { assertThat(pageDTO.timeout, is(false)); assertThat(pageDTO.widgets, notNullValue()); - assertThat((Collection) pageDTO.widgets, hasSize(2)); + assertThat((Collection) pageDTO.widgets, hasSize(3)); assertThat(pageDTO.widgets.get(0).widgetId, is(WIDGET1_ID)); assertThat(pageDTO.widgets.get(0).label, is(WIDGET1_LABEL)); assertThat(pageDTO.widgets.get(0).labelcolor, is("GREEN")); assertThat(pageDTO.widgets.get(0).valuecolor, is("BLUE")); assertThat(pageDTO.widgets.get(0).iconcolor, is("ORANGE")); + assertThat(pageDTO.widgets.get(0).icon, is(WIDGET1_ICON)); + assertThat(pageDTO.widgets.get(0).staticIcon, is(true)); assertThat(pageDTO.widgets.get(0).state, nullValue()); assertThat(pageDTO.widgets.get(0).item, notNullValue()); assertThat(pageDTO.widgets.get(0).item.name, is(ITEM_NAME)); @@ -286,10 +323,24 @@ public void whenGetPageDataShouldReturnPageBean() throws ItemNotFoundException { assertThat(pageDTO.widgets.get(1).labelcolor, nullValue()); assertThat(pageDTO.widgets.get(1).valuecolor, nullValue()); assertThat(pageDTO.widgets.get(1).iconcolor, nullValue()); + assertThat(pageDTO.widgets.get(1).icon, is(WIDGET2_ICON)); + assertThat(pageDTO.widgets.get(1).staticIcon, is(false)); assertThat(pageDTO.widgets.get(1).state, is("ON")); assertThat(pageDTO.widgets.get(1).item, notNullValue()); assertThat(pageDTO.widgets.get(1).item.name, is(ITEM_NAME)); assertThat(pageDTO.widgets.get(1).item.state, is("50")); + + assertThat(pageDTO.widgets.get(2).widgetId, is(WIDGET3_ID)); + assertThat(pageDTO.widgets.get(2).label, is(WIDGET3_LABEL)); + assertThat(pageDTO.widgets.get(2).labelcolor, nullValue()); + assertThat(pageDTO.widgets.get(2).valuecolor, nullValue()); + assertThat(pageDTO.widgets.get(2).iconcolor, nullValue()); + assertThat(pageDTO.widgets.get(2).icon, is(WIDGET3_ICON)); + assertThat(pageDTO.widgets.get(2).staticIcon, is(true)); + assertThat(pageDTO.widgets.get(2).state, is("ON")); + assertThat(pageDTO.widgets.get(2).item, notNullValue()); + assertThat(pageDTO.widgets.get(2).item.name, is(ITEM_NAME)); + assertThat(pageDTO.widgets.get(2).item.state, is("50")); } private void configureItemUIRegistry(State state1, State state2) throws ItemNotFoundException { @@ -299,9 +350,10 @@ private void configureItemUIRegistry(State state1, State state2) throws ItemNotF when(itemUIRegistryMock.getItem(LABEL_COLOR_ITEM_NAME)).thenReturn(labelColorItem); when(itemUIRegistryMock.getItem(VALUE_COLOR_ITEM_NAME)).thenReturn(valueColorItem); when(itemUIRegistryMock.getItem(ICON_COLOR_ITEM_NAME)).thenReturn(iconColorItem); + when(itemUIRegistryMock.getItem(ICON_ITEM_NAME)).thenReturn(iconItem); when(itemUIRegistryMock.getWidgetId(widgets.get(0))).thenReturn(WIDGET1_ID); - when(itemUIRegistryMock.getCategory(widgets.get(0))).thenReturn(""); + when(itemUIRegistryMock.getCategory(widgets.get(0))).thenReturn(WIDGET1_ICON); when(itemUIRegistryMock.getLabel(widgets.get(0))).thenReturn(WIDGET1_LABEL); when(itemUIRegistryMock.getVisiblity(widgets.get(0))).thenReturn(true); when(itemUIRegistryMock.getLabelColor(widgets.get(0))).thenReturn("GREEN"); @@ -310,13 +362,22 @@ private void configureItemUIRegistry(State state1, State state2) throws ItemNotF when(itemUIRegistryMock.getState(widgets.get(0))).thenReturn(state1); when(itemUIRegistryMock.getWidgetId(widgets.get(1))).thenReturn(WIDGET2_ID); - when(itemUIRegistryMock.getCategory(widgets.get(1))).thenReturn(""); + when(itemUIRegistryMock.getCategory(widgets.get(1))).thenReturn(WIDGET2_ICON); when(itemUIRegistryMock.getLabel(widgets.get(1))).thenReturn(WIDGET2_LABEL); when(itemUIRegistryMock.getVisiblity(widgets.get(1))).thenReturn(true); when(itemUIRegistryMock.getLabelColor(widgets.get(1))).thenReturn(null); when(itemUIRegistryMock.getValueColor(widgets.get(1))).thenReturn(null); when(itemUIRegistryMock.getIconColor(widgets.get(1))).thenReturn(null); when(itemUIRegistryMock.getState(widgets.get(1))).thenReturn(state2); + + when(itemUIRegistryMock.getWidgetId(widgets.get(2))).thenReturn(WIDGET3_ID); + when(itemUIRegistryMock.getCategory(widgets.get(2))).thenReturn(WIDGET3_ICON); + when(itemUIRegistryMock.getLabel(widgets.get(2))).thenReturn(WIDGET3_LABEL); + when(itemUIRegistryMock.getVisiblity(widgets.get(2))).thenReturn(true); + when(itemUIRegistryMock.getLabelColor(widgets.get(2))).thenReturn(null); + when(itemUIRegistryMock.getValueColor(widgets.get(2))).thenReturn(null); + when(itemUIRegistryMock.getIconColor(widgets.get(2))).thenReturn(null); + when(itemUIRegistryMock.getState(widgets.get(2))).thenReturn(state2); } private EList initSitemapWidgets() { @@ -330,6 +391,19 @@ private EList initSitemapWidgets() { when(w1.eClass()).thenReturn(sliderEClass); when(w1.getLabel()).thenReturn(WIDGET1_LABEL); when(w1.getItem()).thenReturn(ITEM_NAME); + when(w1.getIcon()).thenReturn(null); + when(w1.getStaticIcon()).thenReturn(null); + + // add icon rules to the mock widget: + IconRule iconRule = mock(IconRule.class); + Condition conditon0 = mock(Condition.class); + when(conditon0.getItem()).thenReturn(ICON_ITEM_NAME); + EList conditions0 = new BasicEList<>(); + conditions0.add(conditon0); + when(iconRule.getConditions()).thenReturn(conditions0); + EList iconRules = new BasicEList<>(); + iconRules.add(iconRule); + when(w1.getIconRules()).thenReturn(iconRules); // add visibility rules to the mock widget: VisibilityRule visibilityRule = mock(VisibilityRule.class); @@ -359,6 +433,7 @@ private EList initSitemapWidgets() { iconColors.add(iconColor); when(w1.getIconColor()).thenReturn(iconColors); + iconRules = new BasicEList<>(); visibilityRules = new BasicEList<>(); labelColors = new BasicEList<>(); valueColors = new BasicEList<>(); @@ -371,14 +446,30 @@ private EList initSitemapWidgets() { when(w2.eClass()).thenReturn(switchEClass); when(w2.getLabel()).thenReturn(WIDGET2_LABEL); when(w2.getItem()).thenReturn(ITEM_NAME); + when(w2.getIcon()).thenReturn(WIDGET2_ICON); + when(w2.getStaticIcon()).thenReturn(null); + when(w2.getIconRules()).thenReturn(iconRules); when(w2.getVisibility()).thenReturn(visibilityRules); when(w2.getLabelColor()).thenReturn(labelColors); when(w2.getValueColor()).thenReturn(valueColors); when(w2.getIconColor()).thenReturn(iconColors); - BasicEList widgets = new BasicEList<>(2); + Widget w3 = mock(Widget.class); + when(w3.eClass()).thenReturn(switchEClass); + when(w3.getLabel()).thenReturn(WIDGET3_LABEL); + when(w3.getItem()).thenReturn(ITEM_NAME); + when(w3.getIcon()).thenReturn(null); + when(w3.getStaticIcon()).thenReturn(WIDGET3_ICON); + when(w3.getIconRules()).thenReturn(iconRules); + when(w3.getVisibility()).thenReturn(visibilityRules); + when(w3.getLabelColor()).thenReturn(labelColors); + when(w3.getValueColor()).thenReturn(valueColors); + when(w3.getIconColor()).thenReturn(iconColors); + + EList widgets = new BasicEList<>(3); widgets.add(w1); widgets.add(w2); + widgets.add(w3); return widgets; } 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 f4716e46008..b50799baebb 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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('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=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('staticIcon=' staticIcon=Icon))? & ('inputHint=' inputHint=STRING)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -151,7 +179,9 @@ Input: Buttongrid: 'Buttongrid' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('staticIcon=' staticIcon=Icon))? & ('columns=' columns=INT) & ('buttons=[' buttons+=Button (',' buttons+=Button)* ']') & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & @@ -161,7 +191,9 @@ Buttongrid: Default: 'Default' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & - (('icon=' icon=Icon) | ('staticIcon=' staticIcon=Icon))? & + (('icon=' icon=Icon) | + ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') | + ('staticIcon=' staticIcon=Icon))? & ('height=' height=INT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -194,6 +226,12 @@ ColorArray: ((item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState) '=')? (arg=STRING); +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 2f59e2ed531..424c7e2ae37 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 @@ -32,6 +32,7 @@ import org.openhab.core.model.sitemap.SitemapProvider; import org.openhab.core.model.sitemap.sitemap.Button; import org.openhab.core.model.sitemap.sitemap.ColorArray; +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; @@ -44,9 +45,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.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; @@ -95,6 +98,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 = COLOR_PATTERN; private Map sitemaps = new HashMap<>(); private @Nullable UIComponentRegistryFactory componentRegistryFactory; @@ -296,6 +300,7 @@ protected Sitemap buildSitemap(RootUIComponent rootComponent) { addLabelColor(widget.getLabelColor(), component); addValueColor(widget.getValueColor(), component); addIconColor(widget.getIconColor(), component); + addIconRules(widget.getIconRules(), component); } return widget; @@ -426,6 +431,29 @@ private void addColor(EList color, UIComponent component, String key } } + private void addIconRules(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()) { + 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()); + } + } + } + } + } + @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..4a2e4f18119 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.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; @@ -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 conditionalIcon = getConditionalIcon(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 (conditionalIcon != null) { + category = conditionalIcon; } 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.getIconRules().addAll(EcoreUtil.copyAll(source.getIconRules())); } /** @@ -1295,6 +1301,86 @@ public boolean getVisiblity(Widget w) { return false; } + @Override + public @Nullable String getConditionalIcon(Widget w) { + List ruleList = w.getIconRules(); + // Sanity check + if (ruleList == null || ruleList.isEmpty()) { + return null; + } + + logger.debug("Checking icon for widget '{}'.", w.getLabel()); + + String icon = null; + + // Loop through all elements looking for the definition associated + // with the supplied value + for (IconRule rule : ruleList) { + if (allConditionsOk(rule.getConditions(), w)) { + // 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; + } + + private boolean allConditionsOk(@Nullable List conditions, + Widget w) { + boolean allConditionsOk = true; + if (conditions != null) { + State defaultState = getState(w); + + // Go through all AND conditions + for (org.openhab.core.model.sitemap.sitemap.Condition condition : conditions) { + // Use a local state variable in case it gets overridden below + State state = 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 + state = item.getState(); + } catch (ItemNotFoundException e) { + logger.warn("Cannot retrieve item {} for widget {}", itemName, + w.eClass().getInstanceTypeName()); + } + } + + // Handle the sign + String value; + if (condition.getSign() != null) { + value = condition.getSign() + condition.getState(); + } else { + value = condition.getState(); + } + + if (state == null || !matchStateToValue(state, value, condition.getCondition())) { + allConditionsOk = false; + break; + } + } + } + return allConditionsOk; + } + enum Condition { EQUAL("=="), GTE(">="), 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..81026e1ad64 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 getConditionalIcon */ @NonNullByDefault public interface ItemUIRegistry extends ItemRegistry, ItemUIProvider { @@ -170,6 +171,17 @@ public interface ItemUIRegistry extends ItemRegistry, ItemUIProvider { @Nullable String getIconColor(Widget w); + /** + * Gets the icon for the widget. Checks conditional statements to + * find the icon based on the item value + * + * @param w Widget + * @return the icon reference or null in case no conditional statement is defined or no conditional statement is + * fulfilled. + */ + @Nullable + String getConditionalIcon(Widget w); + /** * Gets the widget visibility based on the item state * 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..35ba92d0c42 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 @@ -27,6 +27,7 @@ import javax.measure.quantity.Temperature; import org.eclipse.emf.common.util.BasicEList; +import org.eclipse.emf.ecore.EClass; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -60,7 +61,9 @@ 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.IconRule; import org.openhab.core.model.sitemap.sitemap.Image; import org.openhab.core.model.sitemap.sitemap.Mapping; import org.openhab.core.model.sitemap.sitemap.Mapview; @@ -83,6 +86,7 @@ /** * @author Kai Kreuzer - Initial contribution + * @author Laurent Garnier - Tests added for getCategory */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -1015,4 +1019,114 @@ public void getIconColor() { color = uiRegistry.getIconColor(widgetMock); assertEquals("blue", color); } + + @Test + public void getCategoryWhenIconSetWithoutRules() { + EClass textEClass = mock(EClass.class); + when(textEClass.getName()).thenReturn("text"); + when(textEClass.getInstanceTypeName()).thenReturn("org.openhab.core.model.sitemap.Text"); + when(widgetMock.eClass()).thenReturn(textEClass); + when(widgetMock.getIcon()).thenReturn("temperature"); + when(widgetMock.getStaticIcon()).thenReturn(null); + when(widgetMock.getIconRules()).thenReturn(null); + + String icon = uiRegistry.getCategory(widgetMock); + assertEquals("temperature", icon); + } + + @Test + public void getCategoryWhenIconSetWithRules() { + EClass textEClass = mock(EClass.class); + when(textEClass.getName()).thenReturn("text"); + when(textEClass.getInstanceTypeName()).thenReturn("org.openhab.core.model.sitemap.Text"); + when(widgetMock.eClass()).thenReturn(textEClass); + when(widgetMock.getIcon()).thenReturn(null); + when(widgetMock.getStaticIcon()).thenReturn(null); + Condition conditon = mock(Condition.class); + when(conditon.getState()).thenReturn("21"); + when(conditon.getCondition()).thenReturn(">="); + Condition conditon2 = mock(Condition.class); + when(conditon2.getState()).thenReturn("24"); + when(conditon2.getCondition()).thenReturn("<"); + BasicEList conditions = new BasicEList<>(); + conditions.add(conditon); + conditions.add(conditon2); + IconRule rule = mock(IconRule.class); + when(rule.getConditions()).thenReturn(conditions); + when(rule.getArg()).thenReturn("temperature"); + BasicEList rules = new BasicEList<>(); + rules.add(rule); + BasicEList conditions2 = new BasicEList<>(); + IconRule rule2 = mock(IconRule.class); + when(rule2.getConditions()).thenReturn(conditions2); + when(rule2.getArg()).thenReturn("humidity"); + rules.add(rule2); + when(widgetMock.getIconRules()).thenReturn(rules); + + when(itemMock.getState()).thenReturn(new DecimalType(20.9)); + + String icon = uiRegistry.getCategory(widgetMock); + assertEquals("humidity", icon); + + when(itemMock.getState()).thenReturn(new DecimalType(21.0)); + + icon = uiRegistry.getCategory(widgetMock); + assertEquals("temperature", icon); + + when(itemMock.getState()).thenReturn(new DecimalType(23.5)); + + icon = uiRegistry.getCategory(widgetMock); + assertEquals("temperature", icon); + + when(itemMock.getState()).thenReturn(new DecimalType(24.0)); + + icon = uiRegistry.getCategory(widgetMock); + assertEquals("humidity", icon); + } + + @Test + public void getCategoryWhenStaticIconSet() { + EClass textEClass = mock(EClass.class); + when(textEClass.getName()).thenReturn("text"); + when(textEClass.getInstanceTypeName()).thenReturn("org.openhab.core.model.sitemap.Text"); + when(widgetMock.eClass()).thenReturn(textEClass); + when(widgetMock.getIcon()).thenReturn(null); + when(widgetMock.getStaticIcon()).thenReturn("temperature"); + when(widgetMock.getIconRules()).thenReturn(null); + + String icon = uiRegistry.getCategory(widgetMock); + assertEquals("temperature", icon); + } + + @Test + public void getCategoryWhenIconSetOnItem() { + EClass textEClass = mock(EClass.class); + when(textEClass.getName()).thenReturn("text"); + when(textEClass.getInstanceTypeName()).thenReturn("org.openhab.core.model.sitemap.Text"); + when(widgetMock.eClass()).thenReturn(textEClass); + when(widgetMock.getIcon()).thenReturn(null); + when(widgetMock.getStaticIcon()).thenReturn(null); + when(widgetMock.getIconRules()).thenReturn(null); + + when(itemMock.getCategory()).thenReturn("temperature"); + + String icon = uiRegistry.getCategory(widgetMock); + assertEquals("temperature", icon); + } + + @Test + public void getCategoryDefaultIcon() { + EClass textEClass = mock(EClass.class); + when(textEClass.getName()).thenReturn("text"); + when(textEClass.getInstanceTypeName()).thenReturn("org.openhab.core.model.sitemap.Text"); + when(widgetMock.eClass()).thenReturn(textEClass); + when(widgetMock.getIcon()).thenReturn(null); + when(widgetMock.getStaticIcon()).thenReturn(null); + when(widgetMock.getIconRules()).thenReturn(null); + + when(itemMock.getCategory()).thenReturn(null); + + String icon = uiRegistry.getCategory(widgetMock); + assertEquals("text", icon); + } }