Skip to content

Commit 8eda103

Browse files
authored
Sitemaps: widget order in sitemap groups (#5747)
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
1 parent 2f8343f commit 8eda103

4 files changed

Lines changed: 328 additions & 11 deletions

File tree

bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
import org.openhab.core.items.ItemNotFoundException;
4545
import org.openhab.core.items.ItemNotUniqueException;
4646
import org.openhab.core.items.ItemRegistry;
47+
import org.openhab.core.items.Metadata;
48+
import org.openhab.core.items.MetadataKey;
49+
import org.openhab.core.items.MetadataRegistry;
4750
import org.openhab.core.library.items.CallItem;
4851
import org.openhab.core.library.items.ColorItem;
4952
import org.openhab.core.library.items.ContactItem;
@@ -128,12 +131,17 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
128131
private static final int MAX_BUTTONS = 4;
129132

130133
private static final String DEFAULT_SORTING = "NONE";
134+
protected static final String WIDGET_ORDER_KEY = "widgetOrder";
135+
protected static final String SEMANTICS_KEY = "semantics";
136+
protected static final String SEMANTICS_LOCATION = "Location";
137+
protected static final String SEMANTICS_PARENT_LOCATION_CONFIG = "isPartOf";
131138

132139
private final Logger logger = LoggerFactory.getLogger(ItemUIRegistryImpl.class);
133140

134141
protected final Set<ItemUIProvider> itemUIProviders = new HashSet<>();
135142

136143
private final ItemRegistry itemRegistry;
144+
private final MetadataRegistry metadataRegistry;
137145
private final SitemapFactory sitemapFactory;
138146
private final TimeZoneProvider timeZoneProvider;
139147

@@ -153,8 +161,10 @@ public WidgetLabelWithSource(String l, WidgetLabelSource s) {
153161

154162
@Activate
155163
public ItemUIRegistryImpl(final @Reference ItemRegistry itemRegistry,
156-
final @Reference SitemapFactory sitemapFactory, final @Reference TimeZoneProvider timeZoneProvider) {
164+
final @Reference MetadataRegistry metadataRegistry, final @Reference SitemapFactory sitemapFactory,
165+
final @Reference TimeZoneProvider timeZoneProvider) {
157166
this.itemRegistry = itemRegistry;
167+
this.metadataRegistry = metadataRegistry;
158168
this.sitemapFactory = sitemapFactory;
159169
this.timeZoneProvider = timeZoneProvider;
160170
}
@@ -828,19 +838,14 @@ private List<Widget> getDynamicGroupChildren(Group group) {
828838
List<Item> members = new ArrayList<>(groupItem.getMembers());
829839
switch (groupMembersSorting) {
830840
case "LABEL":
831-
members.sort((u1, u2) -> {
832-
String u1Label = u1.getLabel();
833-
String u2Label = u2.getLabel();
834-
if (u1Label != null && u2Label != null) {
835-
return u1Label.compareTo(u2Label);
836-
} else {
837-
return u1.getName().compareTo(u2.getName());
838-
}
839-
});
841+
members.sort(Comparator.comparing(this::getItemLabel));
840842
break;
841843
case "NAME":
842844
members.sort(Comparator.comparing(Item::getName));
843845
break;
846+
case "METADATA":
847+
sortByMetadata(members);
848+
break;
844849
default:
845850
break;
846851
}
@@ -865,6 +870,101 @@ private List<Widget> getDynamicGroupChildren(Group group) {
865870
return children;
866871
}
867872

873+
private void sortByMetadata(List<Item> members) {
874+
// Location items are sorted among themselves (by hierarchy, then widgetOrder),
875+
// and placed ahead of all other members.
876+
Map<Boolean, List<Item>> byLocation = members.stream().collect(Collectors.partitioningBy(this::isLocationItem));
877+
List<Item> sorted = new ArrayList<>(byLocation.getOrDefault(true, List.of()).stream()
878+
.sorted(Comparator.comparing(Item::getName, this::compareLocationMembers)).toList());
879+
sorted.addAll(byLocation.getOrDefault(false, List.of()).stream()
880+
.sorted(Comparator.comparing(Item::getName, this::compareWidgetOrder)).toList());
881+
members.clear();
882+
members.addAll(sorted);
883+
}
884+
885+
private boolean isLocationItem(Item item) {
886+
MetadataKey semanticsKey = new MetadataKey(SEMANTICS_KEY, item.getName());
887+
Metadata semantics = metadataRegistry.get(semanticsKey);
888+
return semantics != null && semantics.getValue().startsWith(SEMANTICS_LOCATION);
889+
}
890+
891+
private String getItemLabel(Item item) {
892+
String label = item.getLabel();
893+
return label != null ? label : item.getName();
894+
}
895+
896+
private String getItemLabel(String itemName) {
897+
Item item = get(itemName);
898+
return item != null ? getItemLabel(item) : itemName;
899+
}
900+
901+
private @Nullable String getParentLocationName(String locationItemName) {
902+
MetadataKey semanticsKey = new MetadataKey(SEMANTICS_KEY, locationItemName);
903+
Metadata semantics = metadataRegistry.get(semanticsKey);
904+
Object isPartOf = semantics != null ? semantics.getConfiguration().get(SEMANTICS_PARENT_LOCATION_CONFIG) : null;
905+
return isPartOf instanceof String ? (String) isPartOf : null;
906+
}
907+
908+
private int compareLocationMembers(String u1Name, String u2Name) {
909+
List<String> u1Ancestors = new ArrayList<>();
910+
u1Ancestors.add(u1Name);
911+
String u1ParentName = getParentLocationName(u1Name);
912+
while (u1ParentName != null && !u1Ancestors.contains(u1ParentName)) {
913+
u1Ancestors.add(u1ParentName);
914+
u1ParentName = getParentLocationName(u1ParentName);
915+
}
916+
List<String> u2Ancestors = new ArrayList<>();
917+
u2Ancestors.add(u2Name);
918+
String u2ParentName = getParentLocationName(u2Name);
919+
while (u2ParentName != null && !u2Ancestors.contains(u2ParentName)) {
920+
u2Ancestors.add(u2ParentName);
921+
u2ParentName = getParentLocationName(u2ParentName);
922+
}
923+
924+
int u1Depth = u1Ancestors.size();
925+
int u2Depth = u2Ancestors.size();
926+
int minDepth = Math.min(u1Depth, u2Depth);
927+
for (int d = 0; d < minDepth; d++) {
928+
String ancestorName1 = u1Ancestors.get(u1Depth - 1 - d);
929+
String ancestorName2 = u2Ancestors.get(u2Depth - 1 - d);
930+
if (!ancestorName1.equals(ancestorName2)) {
931+
return compareWidgetOrder(ancestorName1, ancestorName2);
932+
}
933+
}
934+
// A parent comes before its children
935+
return Integer.compare(u1Depth, u2Depth);
936+
}
937+
938+
private int compareWidgetOrder(String u1Name, String u2Name) {
939+
MetadataKey u1Key = new MetadataKey(WIDGET_ORDER_KEY, u1Name);
940+
Metadata u1Order = metadataRegistry.get(u1Key);
941+
MetadataKey u2Key = new MetadataKey(WIDGET_ORDER_KEY, u2Name);
942+
Metadata u2Order = metadataRegistry.get(u2Key);
943+
Float u1OrderValue = null;
944+
try {
945+
u1OrderValue = u1Order != null ? Float.parseFloat(u1Order.getValue()) : null;
946+
} catch (NumberFormatException e) {
947+
// ignore
948+
}
949+
Float u2OrderValue = null;
950+
try {
951+
u2OrderValue = u2Order != null ? Float.parseFloat(u2Order.getValue()) : null;
952+
} catch (NumberFormatException e) {
953+
// ignore
954+
}
955+
956+
if ((u1OrderValue == null && u2OrderValue == null)
957+
|| (u1OrderValue != null && u1OrderValue.equals(u2OrderValue))) {
958+
return getItemLabel(u1Name).compareTo(getItemLabel(u2Name));
959+
} else if (u1OrderValue == null) {
960+
return 1;
961+
} else if (u2OrderValue == null) {
962+
return -1;
963+
} else {
964+
return u1OrderValue.compareTo(u2OrderValue);
965+
}
966+
}
967+
868968
private boolean isReadOnly(String itemName) {
869969
try {
870970
Item item = getItem(itemName);

bundles/org.openhab.core.ui/src/main/resources/OH-INF/config/sitemap.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<option value="NONE">No sorting</option>
1515
<option value="LABEL">Sorted by label</option>
1616
<option value="NAME">Sorted by name</option>
17+
<option value="METADATA">Sorted by item widget order metadata</option>
1718
</options>
1819
<default>NONE</default>
1920
</parameter>

bundles/org.openhab.core.ui/src/main/resources/OH-INF/i18n/sitemap.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ system.config.sitemap.groupMembersSorting.description = Defines how the members
33
system.config.sitemap.groupMembersSorting.option.NONE = No sorting
44
system.config.sitemap.groupMembersSorting.option.LABEL = Sorted by label
55
system.config.sitemap.groupMembersSorting.option.NAME = Sorted by name
6+
system.config.sitemap.groupMembersSorting.option.METADATA = Sorted by item widget order metadata
67

78
service.system.sitemap.label = Sitemap

0 commit comments

Comments
 (0)