Skip to content

Commit

Permalink
[sitemap] Extend icon parameter with optional conditional rules
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
lolodomo committed Aug 14, 2023
1 parent c12067a commit 049fd57
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -119,6 +121,10 @@ private Set<Item> getAllItems(EList<Widget> 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());
Expand All @@ -131,7 +137,7 @@ private Set<Item> getAllItems(EList<Widget> 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());
}
Expand Down Expand Up @@ -183,7 +189,7 @@ private Set<SitemapEvent> constructSitemapEvents(Item item, State state, List<Wi
if (!skipWidget && w instanceof Chart chartWidget) {
skipWidget = chartWidget.getRefresh() > 0;
}
if (!skipWidget || definesVisibilityOrColor(w, item.getName())) {
if (!skipWidget || definesVisibilityOrColorOrIcon(w, item.getName())) {
SitemapWidgetEvent event = constructSitemapEventForWidget(item, state, w);
events.add(event);
}
Expand All @@ -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
Expand Down Expand Up @@ -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<Widget> widgets) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -741,6 +744,8 @@ private Set<GenericItem> getAllItems(EList<Widget> 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()));
Expand Down Expand Up @@ -786,6 +791,24 @@ private Set<GenericItem> getItemsInColorCond(EList<ColorArray> colorList) {
return items;
}

private Set<GenericItem> getItemsInIconCond(EList<IconArray> ruleList) {
Set<GenericItem> 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<String> getSubscribedEventTypes() {
return Set.of(ItemStateChangedEvent.TYPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
*
* @author Kai Kreuzer - Initial contribution
* @author Laurent Garnier - New field iconcolor
* @author Laurent Garnier - New field staticIcon
*/
public class SitemapWidgetEvent extends SitemapEvent {

public String widgetId;

public String label;
public String icon;
public boolean staticIcon;
public String labelcolor;
public String valuecolor;
public String iconcolor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ private EList<Widget> 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);
Expand Down Expand Up @@ -371,6 +373,8 @@ private EList<Widget> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,39 @@ 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)* ']'))? &
('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)* ']'))?);

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)* ']'))? &
('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)* ']'))?);

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)* ']'))? &
('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)* ']'))?);

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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand All @@ -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)* ']'))? &
Expand Down Expand Up @@ -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;

Expand Down
Loading

0 comments on commit 049fd57

Please sign in to comment.