[sitemap] Buttongrid as container for new Button elements (#4223)

Related to #4173

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo 2024-05-18 14:51:39 +02:00 committed by GitHub
parent 73f4722b91
commit 3b9a97101b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 121 additions and 16 deletions

View File

@ -79,6 +79,7 @@ import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.model.sitemap.SitemapProvider;
import org.openhab.core.model.sitemap.sitemap.Button;
import org.openhab.core.model.sitemap.sitemap.ButtonDefinition;
import org.openhab.core.model.sitemap.sitemap.Buttongrid;
import org.openhab.core.model.sitemap.sitemap.Chart;
import org.openhab.core.model.sitemap.sitemap.ColorArray;
@ -101,6 +102,7 @@ import org.openhab.core.model.sitemap.sitemap.Webview;
import org.openhab.core.model.sitemap.sitemap.Widget;
import org.openhab.core.types.State;
import org.openhab.core.ui.items.ItemUIRegistry;
import org.openhab.core.ui.items.ItemUIRegistry.WidgetLabelSource;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
@ -142,6 +144,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
* @author Laurent Garnier - Support added for multiple AND conditions in labelcolor/valuecolor/visibility
* @author Laurent Garnier - New widget icon parameter based on conditional rules
* @author Laurent Garnier - Added releaseCmd field for mappings used for switch element
* @author Laurent Garnier - Added support for Buttongrid as container for Button elements
*/
@Component(service = { RESTResource.class, EventSubscriber.class })
@JaxrsResource
@ -616,7 +619,7 @@ public class SitemapResource
bean.visibility = itemUIRegistry.getVisiblity(widget);
if (widget instanceof LinkableWidget linkableWidget) {
EList<Widget> children = itemUIRegistry.getChildren(linkableWidget);
if (widget instanceof Frame) {
if (widget instanceof Frame || widget instanceof Buttongrid) {
for (Widget child : children) {
String wID = itemUIRegistry.getWidgetId(child);
WidgetDTO subWidget = createWidgetBean(sitemapName, child, drillDown, uri, wID, locale,
@ -700,7 +703,7 @@ public class SitemapResource
bean.step = setpointWidget.getStep();
}
if (widget instanceof Buttongrid buttonGridWidget) {
for (Button button : buttonGridWidget.getButtons()) {
for (ButtonDefinition button : buttonGridWidget.getButtons()) {
MappingDTO mappingBean = new MappingDTO();
mappingBean.row = button.getRow();
mappingBean.column = button.getColumn();
@ -710,6 +713,23 @@ public class SitemapResource
bean.mappings.add(mappingBean);
}
}
if (widget instanceof Button buttonWidget) {
// Get the icon from the widget only
if (widget.getIcon() == null && widget.getStaticIcon() == null && widget.getIconRules().isEmpty()) {
bean.icon = null;
bean.staticIcon = null;
}
// Get the label from the widget only and fail back to the command if not set
bean.label = widget.getLabel() != null ? widget.getLabel() : buttonWidget.getCmd();
bean.labelSource = WidgetLabelSource.SITEMAP_WIDGET.toString();
bean.pattern = null;
bean.unit = null;
bean.row = buttonWidget.getRow();
bean.column = buttonWidget.getColumn();
bean.command = buttonWidget.getCmd();
bean.releaseCommand = buttonWidget.getReleaseCmd();
bean.stateless = buttonWidget.isStateless();
}
return bean;
}
@ -740,6 +760,10 @@ public class SitemapResource
if (isLeaf(frame.getChildren())) {
return false;
}
} else if (w instanceof Buttongrid grid) {
if (isLeaf(grid.getChildren())) {
return false;
}
} else if (w instanceof LinkableWidget linkableWidget) {
if (!itemUIRegistry.getChildren(linkableWidget).isEmpty()) {
return false;
@ -828,6 +852,8 @@ public class SitemapResource
// Consider all items inside the frame
if (widget instanceof Frame frame) {
items.addAll(getAllItems(frame.getChildren()));
} else if (widget instanceof Buttongrid grid) {
items.addAll(getAllItems(grid.getChildren()));
}
// Consider items involved in any icon condition
items.addAll(getItemsInIconCond(widget.getIconRules()));

View File

@ -28,6 +28,7 @@ import org.openhab.core.io.rest.core.item.EnrichedItemDTO;
* @author Laurent Garnier - New field columns
* @author Danny Baumann - New field labelSource
* @author Laurent Garnier - Remove field columns
* @author Laurent Garnier - New fields row, column, command, releaseCommand and stateless for Button element
*/
public class WidgetDTO {
@ -70,12 +71,17 @@ public class WidgetDTO {
public String yAxisDecimalPattern;
public Boolean legend;
public Boolean forceAsItem;
public Integer row;
public Integer column;
public String command;
public String releaseCommand;
public Boolean stateless;
public String state;
public EnrichedItemDTO item;
public PageDTO linkedPage;
// only for frames, other linkable widgets link to a page
// only for frames and button grids, other linkable widgets link to a page
public final List<WidgetDTO> widgets = new ArrayList<>();
public WidgetDTO() {

View File

@ -35,6 +35,8 @@ import org.openhab.core.items.events.GroupStateUpdatedEvent;
import org.openhab.core.items.events.ItemEvent;
import org.openhab.core.items.events.ItemStateChangedEvent;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.model.sitemap.sitemap.Button;
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;
@ -44,6 +46,7 @@ import org.openhab.core.model.sitemap.sitemap.VisibilityRule;
import org.openhab.core.model.sitemap.sitemap.Widget;
import org.openhab.core.types.State;
import org.openhab.core.ui.items.ItemUIRegistry;
import org.openhab.core.ui.items.ItemUIRegistry.WidgetLabelSource;
/**
* This is a class that listens on item state change events and creates sitemap events for the registered widgets.
@ -52,6 +55,7 @@ import org.openhab.core.ui.items.ItemUIRegistry;
* @author Laurent Garnier - Added support for icon color
* @author Laurent Garnier - Support added for multiple AND conditions in labelcolor/valuecolor/visibility
* @author Laurent Garnier - New widget icon parameter based on conditional rules
* @author Laurent Garnier - Buttongrid as container for Button elements
*/
public class WidgetsChangeListener implements EventSubscriber {
@ -124,6 +128,8 @@ public class WidgetsChangeListener implements EventSubscriber {
addItemWithName(items, widget.getItem());
if (widget instanceof Frame frame) {
items.addAll(getAllItems(frame.getChildren()));
} else if (widget instanceof Buttongrid grid) {
items.addAll(getAllItems(grid.getChildren()));
}
// now scan icon rules
for (IconRule rule : widget.getIconRules()) {
@ -193,6 +199,8 @@ public class WidgetsChangeListener implements EventSubscriber {
for (Widget w : widgets) {
if (w instanceof Frame frame) {
events.addAll(constructSitemapEvents(item, state, itemUIRegistry.getChildren(frame)));
} else if (w instanceof Buttongrid grid) {
events.addAll(constructSitemapEvents(item, state, itemUIRegistry.getChildren(grid)));
}
boolean itemBelongsToWidget = w.getItem() != null && w.getItem().equals(item.getName());
@ -218,6 +226,16 @@ public class WidgetsChangeListener implements EventSubscriber {
event.widgetId = itemUIRegistry.getWidgetId(widget);
event.icon = itemUIRegistry.getCategory(widget);
event.reloadIcon = widget.getStaticIcon() == null;
if (widget instanceof Button buttonWidget) {
// Get the icon from the widget only
if (widget.getIcon() == null && widget.getStaticIcon() == null && widget.getIconRules().isEmpty()) {
event.icon = null;
event.reloadIcon = false;
}
// Get the label from the widget only and fail back to the command if not set
event.label = widget.getLabel() != null ? widget.getLabel() : buttonWidget.getCmd();
event.labelSource = WidgetLabelSource.SITEMAP_WIDGET.toString();
}
event.visibility = itemUIRegistry.getVisiblity(widget);
event.descriptionChanged = false;
// event.item contains the (potentially changed) data of the item belonging to
@ -311,6 +329,8 @@ public class WidgetsChangeListener implements EventSubscriber {
for (Widget w : widgets) {
if (w instanceof Frame frame) {
events.addAll(constructSitemapEventsForUpdatedDescr(item, itemUIRegistry.getChildren(frame)));
} else if (w instanceof Buttongrid grid) {
events.addAll(constructSitemapEventsForUpdatedDescr(item, itemUIRegistry.getChildren(grid)));
}
boolean itemBelongsToWidget = w.getItem() != null && w.getItem().equals(item.getName());

View File

@ -39,8 +39,9 @@ public class RESTConstants {
* Version 5: transparent charts (#2502)
* Version 6: extended chart period parameter format (#3863)
* Version 7: extended chart period parameter format to cover past and future
* Version 8: Buttongrid as container for new Button elements
*/
public static final String API_VERSION = "7";
public static final String API_VERSION = "8";
public static final CacheControl CACHE_CONTROL = new CacheControl();
static {

View File

@ -15,10 +15,10 @@ Widget:
(LinkableWidget | NonLinkableWidget);
NonLinkableWidget:
Switch | Selection | Slider | Setpoint | Video | Chart | Webview | Colorpicker | Mapview | Input | Buttongrid | Default;
Switch | Selection | Slider | Setpoint | Video | Chart | Webview | Colorpicker | Mapview | Input | Button | Default;
LinkableWidget:
(Text | Group | Image | Frame)
(Text | Group | Image | Frame | Buttongrid)
('{'
(children+=Widget)+
'}')?;
@ -179,11 +179,23 @@ Input:
('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
Buttongrid:
'Buttongrid' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
'Buttongrid' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? &
(('icon=' icon=Icon) |
('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
('staticIcon=' staticIcon=Icon))? &
('buttons=[' buttons+=Button (',' buttons+=Button)* ']') &
('buttons=[' buttons+=ButtonDefinition (',' buttons+=ButtonDefinition)* ']')? &
('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
Button:
'Button' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
(('icon=' icon=Icon) |
('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
('staticIcon=' staticIcon=Icon))? &
('row=' row=INT) & ('column=' column=INT) & (stateless?='stateless')? &
('click=' cmd=Command) & ('release=' releaseCmd=Command)? &
('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
@ -200,7 +212,7 @@ Default:
('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
Button:
ButtonDefinition:
row=INT ':' column=INT ':' cmd=Command '=' label=(ID | STRING) ('=' icon=Icon)?;
Mapping:

View File

@ -15,6 +15,9 @@
*/
package org.openhab.core.model.sitemap.validation
import org.openhab.core.model.sitemap.sitemap.Button
import org.openhab.core.model.sitemap.sitemap.ButtonDefinition
import org.openhab.core.model.sitemap.sitemap.Buttongrid
import org.openhab.core.model.sitemap.sitemap.Frame
import org.openhab.core.model.sitemap.sitemap.LinkableWidget
import org.openhab.core.model.sitemap.sitemap.Setpoint
@ -24,7 +27,6 @@ import org.openhab.core.model.sitemap.sitemap.Widget
import org.eclipse.xtext.validation.Check
import java.math.BigDecimal
import org.openhab.core.model.sitemap.sitemap.Input
import org.eclipse.xtext.nodemodel.INode
import org.eclipse.xtext.nodemodel.util.NodeModelUtils
//import org.eclipse.xtext.validation.Check
@ -45,6 +47,11 @@ class SitemapValidator extends AbstractSitemapValidator {
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.FRAME__CHILDREN));
return;
}
if (w instanceof Button) {
error("Frames should not contain Button, Button is allowed only in Buttongrid",
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.FRAME__CHILDREN));
return;
}
}
}
@ -54,6 +61,11 @@ class SitemapValidator extends AbstractSitemapValidator {
var containsOtherWidgets = false
for (Widget w : sitemap.children) {
if (w instanceof Button) {
error("Sitemap should not contain Button, Button is allowed only in Buttongrid",
SitemapPackage.Literals.SITEMAP.getEStructuralFeature(SitemapPackage.SITEMAP__NAME));
return;
}
if (w instanceof Frame) {
containsFrames = true
} else {
@ -70,13 +82,21 @@ class SitemapValidator extends AbstractSitemapValidator {
@Check
def void checkFramesInWidgetList(LinkableWidget widget) {
if (widget instanceof Frame) {
// we have a dedicated check for frames in place
return;
}
if (widget instanceof Buttongrid) {
// we have a dedicated check for Buttongrid in place
return;
}
var containsFrames = false
var containsOtherWidgets = false
for (Widget w : widget.children) {
if (w instanceof Button) {
error("Linkable widget should not contain Button, Button is allowed only in Buttongrid",
SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.LINKABLE_WIDGET__CHILDREN));
return;
}
if (w instanceof Frame) {
containsFrames = true
} else {
@ -90,6 +110,25 @@ class SitemapValidator extends AbstractSitemapValidator {
}
}
@Check
def void checkWidgetsInButtongrid(Buttongrid grid) {
var nb = 0
for (ButtonDefinition b : grid.getButtons) {
nb = nb + 1
}
if (nb > 0 && grid.item === null) {
error("To use the \"buttons\" parameter in a Buttongrid, the \"item\" parameter is required",
SitemapPackage.Literals.BUTTONGRID.getEStructuralFeature(SitemapPackage.BUTTONGRID__CHILDREN));
}
for (Widget w : grid.children) {
if (!(w instanceof Button)) {
error("Buttongrid must contain only Button",
SitemapPackage.Literals.BUTTONGRID.getEStructuralFeature(SitemapPackage.BUTTONGRID__CHILDREN));
return;
}
}
}
@Check
def void checkSetpoints(Setpoint sp) {
if (BigDecimal.ZERO == sp.step) {
@ -107,7 +146,7 @@ class SitemapValidator extends AbstractSitemapValidator {
SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__MIN_VALUE));
}
}
@Check
def void checkInputHintParameter(Input i) {
if (i.inputHint !== null && !ALLOWED_HINTS.contains(i.inputHint)) {

View File

@ -32,7 +32,7 @@ import org.openhab.core.config.core.ConfigUtil;
import org.openhab.core.model.core.EventType;
import org.openhab.core.model.core.ModelRepositoryChangeListener;
import org.openhab.core.model.sitemap.SitemapProvider;
import org.openhab.core.model.sitemap.sitemap.Button;
import org.openhab.core.model.sitemap.sitemap.ButtonDefinition;
import org.openhab.core.model.sitemap.sitemap.ColorArray;
import org.openhab.core.model.sitemap.sitemap.IconRule;
import org.openhab.core.model.sitemap.sitemap.LinkableWidget;
@ -42,7 +42,7 @@ import org.openhab.core.model.sitemap.sitemap.SitemapFactory;
import org.openhab.core.model.sitemap.sitemap.SitemapPackage;
import org.openhab.core.model.sitemap.sitemap.VisibilityRule;
import org.openhab.core.model.sitemap.sitemap.Widget;
import org.openhab.core.model.sitemap.sitemap.impl.ButtonImpl;
import org.openhab.core.model.sitemap.sitemap.impl.ButtonDefinitionImpl;
import org.openhab.core.model.sitemap.sitemap.impl.ButtongridImpl;
import org.openhab.core.model.sitemap.sitemap.impl.ChartImpl;
import org.openhab.core.model.sitemap.sitemap.impl.ColorArrayImpl;
@ -392,7 +392,7 @@ public class UIComponentSitemapProvider implements SitemapProvider, RegistryChan
}
}
private void addWidgetButtons(EList<Button> buttons, UIComponent component) {
private void addWidgetButtons(EList<ButtonDefinition> buttons, UIComponent component) {
if (component.getConfig() != null && component.getConfig().containsKey("buttons")) {
Object sourceButtons = component.getConfig().get("buttons");
if (sourceButtons instanceof Collection<?> sourceButtonsCollection) {
@ -405,7 +405,8 @@ public class UIComponentSitemapProvider implements SitemapProvider, RegistryChan
String cmd = stripQuotes(splitted2[0].trim());
String label = stripQuotes(splitted2[1].trim());
String icon = splitted2.length < 3 ? null : stripQuotes(splitted2[2].trim());
ButtonImpl button = (ButtonImpl) SitemapFactory.eINSTANCE.createButton();
ButtonDefinitionImpl button = (ButtonDefinitionImpl) SitemapFactory.eINSTANCE
.createButtonDefinition();
button.setRow(row);
button.setColumn(column);
button.setCmd(cmd);