Skip to content

Commit

Permalink
[BasicUI] Implement Buttongrid widget
Browse files Browse the repository at this point in the history
Related to openhab/openhab-core#3441

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
  • Loading branch information
lolodomo committed Dec 2, 2023
1 parent 18e93f5 commit f42f4e8
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.ui.basic.internal.render;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.model.sitemap.sitemap.Button;
import org.openhab.core.model.sitemap.sitemap.Buttongrid;
import org.openhab.core.model.sitemap.sitemap.Widget;
import org.openhab.core.ui.items.ItemUIRegistry;
import org.openhab.ui.basic.internal.WebAppConfig;
import org.openhab.ui.basic.render.RenderException;
import org.openhab.ui.basic.render.WidgetRenderer;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This is an implementation of the {@link WidgetRenderer} interface, which
* can produce HTML code for Buttongrid widgets.
*
* @author Laurent Garnier - Initial contribution
*/
@Component(service = WidgetRenderer.class)
@NonNullByDefault
public class ButtongridRenderer extends AbstractWidgetRenderer {

private final Logger logger = LoggerFactory.getLogger(ButtongridRenderer.class);

// private static final int MAX_LABEL_SIZE = 9;
private static final String ELLIPSIS = "\u2026";

@Activate
public ButtongridRenderer(final BundleContext bundleContext, final @Reference TranslationProvider i18nProvider,
final @Reference ItemUIRegistry itemUIRegistry, final @Reference LocaleProvider localeProvider) {
super(bundleContext, i18nProvider, itemUIRegistry, localeProvider);
}

@Override
public boolean canRender(Widget w) {
return w instanceof Buttongrid;
}

@Override
public EList<Widget> renderWidget(Widget w, StringBuilder sb, String sitemap) throws RenderException {
Buttongrid grid = (Buttongrid) w;

Map<Integer, List<Button>> lines = new HashMap<>();

int maxColumn = 0;
int maxLine = 0;
for (Button button : grid.getButtons()) {
int column = (button.getPosition() - 1) % grid.getColumns() + 1;
if (column > maxColumn) {
maxColumn = column;
}
int line = (button.getPosition() - 1) / grid.getColumns() + 1;
if (line > maxLine) {
maxLine = line;
}

List<Button> elts = lines.get(line);
if (elts == null) {
elts = new ArrayList<>();
lines.put(line, elts);
}
elts.add(button);
}
logger.info("maxLine {} maxColumn {}", maxLine, maxColumn);
if (maxLine > 10 || maxColumn > 20) {
logger.warn("Your button grid is too big ({},{})", maxLine, maxColumn);
return ECollections.emptyEList();
}
for (List<Button> list : lines.values()) {
Collections.sort(list, new Comparator<Button>() {
@Override
public int compare(Button b1, Button b2) {
if (b1.getPosition() == b2.getPosition()) {
return 0;
} else if (b1.getPosition() < b2.getPosition()) {
return -1;
} else {
return 1;
}
}
});
}

String snippet = getSnippet("buttongrid");

boolean showHeaderRow = grid.getLabel() != null;
snippet = snippet.replace("%header_visibility_class%",
showHeaderRow ? "%visibility_class%" : "mdl-form__row--hidden");
snippet = snippet.replace("%header_row%", Boolean.valueOf(showHeaderRow).toString());

snippet = preprocessSnippet(snippet, w, true);

// Process the color tags
snippet = processColor(w, snippet);

StringBuilder buttons = new StringBuilder();
for (int line = 1; line <= maxLine; line++) {
buildGridLine(grid.getItem(), lines.get(line), buttons);
}
snippet = snippet.replace("%lines%", buttons.toString());

sb.append(snippet);
return ECollections.emptyEList();
}

private void buildGridLine(String item, @Nullable List<Button> buttonsInLine, StringBuilder builder)
throws RenderException {
String line = getSnippet("buttonline");

StringBuilder buttons = new StringBuilder();
if (buttonsInLine != null) {
for (Button button : buttonsInLine) {
buildButton(item, button.getLabel(), button.getCmd(), button.getIcon(), -1, buttons);
}
}
line = line.replace("%buttons%", buttons.toString());

builder.append(line);
}

private void buildButton(String item, @Nullable String lab, String cmd, @Nullable String icon, int maxLabelSize,
StringBuilder builder) throws RenderException {
String button = getSnippet("button");

String command = cmd;
String label = lab == null ? cmd : lab;

if (maxLabelSize >= 1 && label.length() > maxLabelSize) {
label = label.substring(0, maxLabelSize - 1) + ELLIPSIS;
}

button = button.replace("%item%", item);
button = button.replace("%cmd%", escapeHtml(command));
String buttonClass = "";
String style = "";
if (icon == null || !config.isIconsEnabled()) {
button = button.replace("%label%", escapeHtml(label));
button = button.replace("%icon_snippet%", "");
} else {
button = button.replace("%label%", "");
button = preprocessIcon(button, icon, true);
buttonClass = "mdl-button-icon";
switch (config.getTheme()) {
case WebAppConfig.THEME_NAME_BRIGHT:
style = "style=\"color-scheme: light\"";
break;
case WebAppConfig.THEME_NAME_DARK:
style = "style=\"color-scheme: dark\"";
break;
default:
break;
}
}
buttonClass += " mdl-button-fixed-width";
button = button.replace("%buttonstyle%", style);
button = button.replace("%class%", buttonClass);

builder.append(button);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ public EList<Widget> renderWidget(Widget w, StringBuilder sb, String sitemap) th
}
}
snippet = snippet.replace("%buttons%", buttons.toString());
snippet = snippet.replace("%count%", Integer.toString(nbButtons));
}

// Process the color tags
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div class="mdl-form__row mdl-form__row--header mdl-cell mdl-cell--12-col %header_visibility_class%">
<span %iconstyle% class="mdl-form__icon">
%icon_snippet%
</span>
<span %labelstyle% class="mdl-form__label">
%label%
</span>
</div>
<div class="mdl-form__row mdl-form__row--height-auto mdl-cell mdl-cell--12-col %visibility_class%">
<div
class="mdl-form__control mdl-form__buttons mdl-grid"
data-control-type="buttons"
data-item="%item%"
data-ignore-state="true"
data-widget-id="%widget_id%"
data-header-row="%header_row%"
>
%lines%
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="mdl-cell mdl-cell--12-col">
%buttons%
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
data-control-type="buttons"
data-item="%item%"
data-has-value="%has_value%"
data-count="%count%"
data-widget-id="%widget_id%"
data-icon-with-state="%icon_with_state%"
>
Expand Down
4 changes: 4 additions & 0 deletions bundles/org.openhab.ui.basic/web-src/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@
vertical-align: middle;
}
}
.mdl-button-fixed-width {
min-width: 96px;
max-width: 96px;
}
.mdl-textfield {
&__input,
&__label {
Expand Down
16 changes: 12 additions & 4 deletions bundles/org.openhab.ui.basic/web-src/smarthome.js
Original file line number Diff line number Diff line change
Expand Up @@ -1009,9 +1009,9 @@
var
_t = this;

_t.ignoreState = _t.parentNode.getAttribute("data-ignore-state") === "true";
_t.hasValue = _t.parentNode.getAttribute("data-has-value") === "true";
_t.value = _t.parentNode.parentNode.querySelector(o.formValue);
_t.count = _t.parentNode.getAttribute("data-count") * 1;
_t.suppressUpdateButtons = false;
_t.reset = function() {
_t.buttons.forEach(function(button) {
Expand All @@ -1024,8 +1024,10 @@
var
value = this.getAttribute("data-value") + "";

_t.reset();
this.classList.add(o.buttonActiveClass);
if (!_t.ignoreState) {
_t.reset();
this.classList.add(o.buttonActiveClass);
}

_t.parentNode.dispatchEvent(createEvent(
"control-change", {
Expand All @@ -1037,6 +1039,10 @@
_t.valueMap = {};
_t.buttons = [].slice.call(_t.parentNode.querySelectorAll(o.controlButton));
_t.setValuePrivate = function(value, itemState) {
if (_t.ignoreState) {
return;
}

if (_t.hasValue) {
_t.value.innerHTML = value;
}
Expand All @@ -1056,7 +1062,9 @@
};

_t.setValueColor = function(color) {
_t.value.style.color = color;
if (_t.hasValue) {
_t.value.style.color = color;
}
};

_t.buttons.forEach.call(_t.buttons, function(button) {
Expand Down

0 comments on commit f42f4e8

Please sign in to comment.