[ui] Switch widget as default widget for Number or String items with command options (#1422)

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
pull/1513/head
Christoph Weitkamp 2020-06-06 09:14:15 +02:00 committed by GitHub
parent b7ab807078
commit f52af0b748
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 195 additions and 88 deletions

View File

@ -119,11 +119,11 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
protected static final String IDENTIFY_FORMAT_PATTERN_PATTERN = "%((unit%)|((\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z])))";
private static final Pattern LABEL_PATTERN = Pattern.compile(".*?\\[.*? (.*?)\\]");
private static final int MAX_BUTTONS = 4;
protected final Set<ItemUIProvider> itemUIProviders = new HashSet<>();
private @Nullable ItemRegistry itemRegistry;
private @Nullable ItemBuilderFactory itemBuilderFactory;
private final Map<Widget, Widget> defaultWidgets = Collections.synchronizedMap(new WeakHashMap<>());
@ -140,15 +140,6 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
this.itemRegistry = null;
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
public void addItemUIProvider(ItemUIProvider itemUIProvider) {
itemUIProviders.add(itemUIProvider);
}
public void removeItemUIProvider(ItemUIProvider itemUIProvider) {
itemUIProviders.remove(itemUIProvider);
}
@Reference(policy = ReferencePolicy.DYNAMIC)
protected void setItemBuilderFactory(ItemBuilderFactory itemBuilderFactory) {
this.itemBuilderFactory = itemBuilderFactory;
@ -158,6 +149,15 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
this.itemBuilderFactory = null;
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
public void addItemUIProvider(ItemUIProvider itemUIProvider) {
itemUIProviders.add(itemUIProvider);
}
public void removeItemUIProvider(ItemUIProvider itemUIProvider) {
itemUIProviders.remove(itemUIProvider);
}
@Override
public @Nullable String getCategory(String itemName) {
for (ItemUIProvider provider : itemUIProviders) {
@ -239,60 +239,50 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
if (itemType == null) {
return null;
}
boolean readOnly = isReadOnly(itemName);
int nbOptions = getNbOptions(itemName);
if (SwitchItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createSwitch();
}
if (GroupItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createGroup();
}
if (NumberItem.class.isAssignableFrom(itemType)) {
return (!readOnly && nbOptions > 0) ? SitemapFactory.eINSTANCE.createSelection()
: SitemapFactory.eINSTANCE.createText();
}
if (ContactItem.class.equals(itemType)) {
} else if (CallItem.class.equals(itemType) //
|| ContactItem.class.equals(itemType) //
|| DateTimeItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createText();
}
if (DateTimeItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createText();
}
if (RollershutterItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createSwitch();
}
if (StringItem.class.equals(itemType)) {
return (!readOnly && nbOptions > 0) ? SitemapFactory.eINSTANCE.createSelection()
: SitemapFactory.eINSTANCE.createText();
}
if (LocationItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createMapview();
}
if (CallItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createText();
}
if (DimmerItem.class.equals(itemType)) {
} else if (ColorItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createColorpicker();
} else if (DimmerItem.class.equals(itemType)) {
Slider slider = SitemapFactory.eINSTANCE.createSlider();
slider.setSwitchEnabled(true);
return slider;
}
if (ColorItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createColorpicker();
}
if (PlayerItem.class.equals(itemType)) {
return createPlayerButtons();
}
if (ImageItem.class.equals(itemType)) {
} else if (ImageItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createImage();
} else if (LocationItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createMapview();
} else if (NumberItem.class.isAssignableFrom(itemType) //
|| StringItem.class.equals(itemType)) {
boolean isReadOnly = isReadOnly(itemName);
if (!isReadOnly && hasStateOptions(itemName)) {
return SitemapFactory.eINSTANCE.createSelection();
}
int commandOptionsSize = getCommandOptionsSize(itemName);
if (!isReadOnly && commandOptionsSize > 0) {
return commandOptionsSize <= MAX_BUTTONS ? SitemapFactory.eINSTANCE.createSwitch()
: SitemapFactory.eINSTANCE.createSelection();
} else {
return SitemapFactory.eINSTANCE.createText();
}
} else if (PlayerItem.class.equals(itemType)) {
return createPlayerButtons();
} else if (RollershutterItem.class.equals(itemType) //
|| SwitchItem.class.equals(itemType)) {
return SitemapFactory.eINSTANCE.createSwitch();
}
return null;
}
private Switch createPlayerButtons() {
Switch playerItemSwitch = SitemapFactory.eINSTANCE.createSwitch();
List<Mapping> mappings = playerItemSwitch.getMappings();
Mapping commandMapping = null;
final Switch playerItemSwitch = SitemapFactory.eINSTANCE.createSwitch();
final List<Mapping> mappings = playerItemSwitch.getMappings();
Mapping commandMapping;
mappings.add(commandMapping = SitemapFactory.eINSTANCE.createMapping());
commandMapping.setCmd(NextPreviousType.PREVIOUS.name());
commandMapping.setLabel("<<");
@ -720,7 +710,7 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
} else {
String itemName = widget.getItem();
if (itemName != null) {
Item item = itemRegistry.get(itemName);
Item item = get(itemName);
if (item != null) {
Widget defaultWidget = getDefaultWidget(item.getClass(), item.getName());
if (defaultWidget != null) {
@ -781,34 +771,37 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
private boolean isReadOnly(String itemName) {
try {
Item item = itemRegistry.getItem(itemName);
Item item = getItem(itemName);
StateDescription stateDescription = item.getStateDescription();
return stateDescription != null ? stateDescription.isReadOnly() : false;
} catch (ItemNotFoundException e) {
return false;
}
return false;
}
private int getNbOptions(String itemName) {
private boolean hasStateOptions(String itemName) {
try {
Item item = itemRegistry.getItem(itemName);
CommandDescription commandDescription = item.getCommandDescription();
int nbCommandOptions = (commandDescription != null && commandDescription.getCommandOptions() != null)
? commandDescription.getCommandOptions().size()
: 0;
Item item = getItem(itemName);
StateDescription stateDescription = item.getStateDescription();
int nbStateOptions = (stateDescription != null && stateDescription.getOptions() != null)
? stateDescription.getOptions().size()
: 0;
return nbCommandOptions > nbStateOptions ? nbCommandOptions : nbStateOptions;
return stateDescription != null && !stateDescription.getOptions().isEmpty();
} catch (ItemNotFoundException e) {
return false;
}
}
private int getCommandOptionsSize(String itemName) {
try {
Item item = getItem(itemName);
CommandDescription commandDescription = item.getCommandDescription();
return commandDescription != null ? commandDescription.getCommandOptions().size() : 0;
} catch (ItemNotFoundException e) {
return 0;
}
return 0;
}
private @Nullable Class<? extends Item> getItemType(String itemName) {
try {
Item item = itemRegistry.getItem(itemName);
Item item = getItem(itemName);
return item.getClass();
} catch (ItemNotFoundException e) {
return null;
@ -818,7 +811,7 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
@Override
public @Nullable State getItemState(String itemName) {
try {
Item item = itemRegistry.getItem(itemName);
Item item = getItem(itemName);
return item.getState();
} catch (ItemNotFoundException e) {
return null;
@ -827,7 +820,7 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
public @Nullable String getItemCategory(String itemName) {
try {
Item item = itemRegistry.getItem(itemName);
Item item = getItem(itemName);
return item.getCategory();
} catch (ItemNotFoundException e) {
return null;
@ -1368,7 +1361,6 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
if (labelUnit != null && !state.getUnit().toString().equals(labelUnit)) {
return state.toUnit(labelUnit);
}
return state;
}
@ -1376,12 +1368,10 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
if (label.isBlank()) {
return null;
}
Matcher m = LABEL_PATTERN.matcher(label);
if (m.matches()) {
return m.group(1);
}
return null;
}
}

View File

@ -12,6 +12,8 @@
*/
package org.openhab.core.ui.internal.items;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
@ -21,16 +23,30 @@ import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.emf.common.util.BasicEList;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.items.CallItem;
import org.openhab.core.library.items.ColorItem;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.DateTimeItem;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.ImageItem;
import org.openhab.core.library.items.LocationItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.PlayerItem;
import org.openhab.core.library.items.RollershutterItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
@ -39,12 +55,20 @@ import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
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.Group;
import org.openhab.core.model.sitemap.sitemap.Image;
import org.openhab.core.model.sitemap.sitemap.Mapping;
import org.openhab.core.model.sitemap.sitemap.Mapview;
import org.openhab.core.model.sitemap.sitemap.Selection;
import org.openhab.core.model.sitemap.sitemap.Sitemap;
import org.openhab.core.model.sitemap.sitemap.SitemapFactory;
import org.openhab.core.model.sitemap.sitemap.Slider;
import org.openhab.core.model.sitemap.sitemap.Switch;
import org.openhab.core.model.sitemap.sitemap.Text;
import org.openhab.core.model.sitemap.sitemap.Widget;
import org.openhab.core.types.CommandDescriptionBuilder;
import org.openhab.core.types.CommandOption;
import org.openhab.core.types.State;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
@ -60,20 +84,17 @@ public class ItemUIRegistryImplTest {
// we need to get the decimal separator of the default locale for our tests
private static final char SEP = (new DecimalFormatSymbols().getDecimalSeparator());
private static final String ITEM_NAME = "Item";
private ItemUIRegistryImpl uiRegistry;
@Mock
private ItemRegistry registry;
private @Mock ItemRegistry registry;
@Mock
private Widget widget;
private @Mock Widget widget;
@Mock
private Item item;
private @Mock Item item;
@Mock
private UnitProvider unitProvider;
private @Mock UnitProvider unitProvider;
@Before
public void setup() throws Exception {
@ -81,8 +102,8 @@ public class ItemUIRegistryImplTest {
uiRegistry = new ItemUIRegistryImpl();
uiRegistry.setItemRegistry(registry);
when(widget.getItem()).thenReturn("Item");
when(registry.getItem("Item")).thenReturn(item);
when(widget.getItem()).thenReturn(ITEM_NAME);
when(registry.getItem(ITEM_NAME)).thenReturn(item);
// Set default time zone to GMT-6
TimeZone.setDefault(TimeZone.getTimeZone("GMT-6"));
@ -357,8 +378,8 @@ public class ItemUIRegistryImplTest {
Widget w = mock(Widget.class);
Item item = mock(Item.class);
when(w.getLabel()).thenReturn(testLabel);
when(w.getItem()).thenReturn("Item");
when(registry.getItem("Item")).thenReturn(item);
when(w.getItem()).thenReturn(ITEM_NAME);
when(registry.getItem(ITEM_NAME)).thenReturn(item);
when(item.getState()).thenReturn(new DateTimeType("2011-06-01T00:00:00"));
String label = uiRegistry.getLabel(w);
assertEquals("Label [01.06.2011]", label);
@ -405,8 +426,8 @@ public class ItemUIRegistryImplTest {
Widget w = mock(Widget.class);
Item item = mock(Item.class);
when(w.getLabel()).thenReturn(testLabel);
when(w.getItem()).thenReturn("Item");
when(registry.getItem("Item")).thenReturn(item);
when(w.getItem()).thenReturn(ITEM_NAME);
when(registry.getItem(ITEM_NAME)).thenReturn(item);
when(item.getState()).thenReturn(new DateTimeType("2011-06-01T15:30:59"));
String label = uiRegistry.getLabel(w);
assertEquals("Label [15:30:59]", label);
@ -434,7 +455,7 @@ public class ItemUIRegistryImplTest {
@Test
public void getLabelWidgetWithoutLabel() {
String label = uiRegistry.getLabel(widget);
assertEquals("Item", label);
assertEquals(ITEM_NAME, label);
}
@Test
@ -503,7 +524,7 @@ public class ItemUIRegistryImplTest {
when(widget.getLabel()).thenReturn(testLabel);
when(widget.eClass()).thenReturn(SitemapFactory.eINSTANCE.createText().eClass());
when(registry.getItem("Item")).thenThrow(new ItemNotFoundException("Item"));
when(registry.getItem(ITEM_NAME)).thenThrow(new ItemNotFoundException(ITEM_NAME));
when(item.getState()).thenReturn(new StringType("State"));
String label = uiRegistry.getLabel(widget);
assertEquals("Label [-]", label);
@ -702,8 +723,8 @@ public class ItemUIRegistryImplTest {
Widget w = mock(Widget.class);
Item item = mock(Item.class);
when(w.getLabel()).thenReturn(testLabel);
when(w.getItem()).thenReturn("Item");
when(registry.getItem("Item")).thenReturn(item);
when(w.getItem()).thenReturn(ITEM_NAME);
when(registry.getItem(ITEM_NAME)).thenReturn(item);
when(item.getState()).thenReturn(new StringType("State"));
String label = uiRegistry.getLabel(w);
assertEquals("Memory [State]", label);
@ -748,4 +769,100 @@ public class ItemUIRegistryImplTest {
String color = uiRegistry.getLabelColor(widget);
assertEquals("yellow", color);
}
@Test
public void getDefaultWidgets() {
Widget defaultWidget = uiRegistry.getDefaultWidget(GroupItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Group.class)));
defaultWidget = uiRegistry.getDefaultWidget(CallItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Text.class)));
defaultWidget = uiRegistry.getDefaultWidget(ColorItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Colorpicker.class)));
defaultWidget = uiRegistry.getDefaultWidget(ContactItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Text.class)));
defaultWidget = uiRegistry.getDefaultWidget(DateTimeItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Text.class)));
defaultWidget = uiRegistry.getDefaultWidget(DimmerItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Slider.class)));
assertThat(((Slider) defaultWidget).isSwitchEnabled(), is(true));
defaultWidget = uiRegistry.getDefaultWidget(ImageItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Image.class)));
defaultWidget = uiRegistry.getDefaultWidget(LocationItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Mapview.class)));
defaultWidget = uiRegistry.getDefaultWidget(PlayerItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Switch.class)));
assertThat(((Switch) defaultWidget).getMappings(), hasSize(4));
defaultWidget = uiRegistry.getDefaultWidget(RollershutterItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Switch.class)));
defaultWidget = uiRegistry.getDefaultWidget(SwitchItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Switch.class)));
}
@Test
public void getDefaultWidgetsForNumberItem() {
// NumberItem without CommandOptions or StateOptions should return Text element
Widget defaultWidget = uiRegistry.getDefaultWidget(NumberItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Text.class)));
// NumberItem with one to four CommandOptions should return Switch element
final CommandDescriptionBuilder builder = CommandDescriptionBuilder.create()
.withCommandOptions(Stream
.of(new CommandOption("command1", "label1"), new CommandOption("command2", "label2"),
new CommandOption("command3", "label3"), new CommandOption("command4", "label4"))
.collect(Collectors.toList()));
when(item.getCommandDescription()).thenReturn(builder.build());
defaultWidget = uiRegistry.getDefaultWidget(NumberItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Switch.class)));
// NumberItem with more than four CommandOptions should return Selection element
builder.withCommandOption(new CommandOption("command5", "label5"));
when(item.getCommandDescription()).thenReturn(builder.build());
defaultWidget = uiRegistry.getDefaultWidget(NumberItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Selection.class)));
// NumberItem with one or more StateOptions should return Selection element
when(item.getStateDescription()).thenReturn(StateDescriptionFragmentBuilder.create()
.withOption(new StateOption("value", "label")).build().toStateDescription());
defaultWidget = uiRegistry.getDefaultWidget(NumberItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Selection.class)));
}
@Test
public void getDefaultWidgetsForStringItem() {
// StringItem without CommandOptions or StateOptions should return Text element
Widget defaultWidget = uiRegistry.getDefaultWidget(StringItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Text.class)));
// StringItem with one to four CommandOptions should return Switch element
final CommandDescriptionBuilder builder = CommandDescriptionBuilder.create()
.withCommandOptions(Stream
.of(new CommandOption("command1", "label1"), new CommandOption("command2", "label2"),
new CommandOption("command3", "label3"), new CommandOption("command4", "label4"))
.collect(Collectors.toList()));
when(item.getCommandDescription()).thenReturn(builder.build());
defaultWidget = uiRegistry.getDefaultWidget(StringItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Switch.class)));
// StringItem with more than four CommandOptions should return Selection element
builder.withCommandOption(new CommandOption("command5", "label5"));
when(item.getCommandDescription()).thenReturn(builder.build());
defaultWidget = uiRegistry.getDefaultWidget(StringItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Selection.class)));
// StringItem with one or more StateOptions should return Selection element
when(item.getStateDescription()).thenReturn(StateDescriptionFragmentBuilder.create()
.withOption(new StateOption("value", "label")).build().toStateDescription());
defaultWidget = uiRegistry.getDefaultWidget(StringItem.class, ITEM_NAME);
assertThat(defaultWidget, is(instanceOf(Selection.class)));
}
}