Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BasicUI] Implement Buttongrid widget #2195

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/**
* 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.HashMap;
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);

@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, Map<Integer, Button>> rows = new HashMap<>();

int maxColumn = 0;
int mawRow = 0;
for (Button button : grid.getButtons()) {
int row = button.getRow();
int column = button.getColumn();
if (row < 1 || column < 1) {
logger.warn("Invalid row or column number; button at position {}:{} is ignored", row, column);
continue;
}
if (row > mawRow) {
mawRow = row;
}
if (column > maxColumn) {
maxColumn = column;
}

Map<Integer, Button> columns = rows.get(row);
if (columns == null) {
columns = new HashMap<>();
rows.put(row, columns);
}
columns.put(column, button);
}

if (mawRow > 50 || maxColumn > 12) {
logger.warn("The button grid is too big ({},{})", mawRow, maxColumn);
return ECollections.emptyEList();
}

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 row = 1; row <= mawRow; row++) {
buildRow(grid.getItem(), maxColumn, rows.get(row), buttons);
}
snippet = snippet.replace("%buttons%", buttons.toString());

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

private void buildRow(String item, int columns, @Nullable Map<Integer, Button> buttonsInRow, StringBuilder builder)
throws RenderException {
// Add extra cells to fill the row
// Try to center the grid at best with one extra cell at beginning of row and one at end of row
int extraCellSizeDesktop = 12 % columns;
int extraCellSizeTablet = columns > 8 ? 0 : 8 % columns;
int column = columns + 1;
// Extra cell at beginning
if (extraCellSizeDesktop > 0) {
buildEmptyCell((extraCellSizeDesktop / 2) == 0, Math.max(1, extraCellSizeDesktop / 2),
(column > 8) || ((extraCellSizeTablet / 2) == 0), Math.max(1, extraCellSizeTablet / 2), true, 1,
builder);
} else if (extraCellSizeTablet > 0 && columns < 8) {
buildEmptyCell(true, 1, (extraCellSizeTablet / 2) == 0, Math.max(1, extraCellSizeTablet / 2), true, 1,
builder);
}

// Match the grid to a mdl-grid
int sizeDessktop = Math.max(1, 12 / columns);
int sizeTablet = Math.max(1, 8 / columns);
int sizePhone = Math.max(1, 4 / columns);
for (int col = 1; col <= columns; col++) {
Button button = buttonsInRow == null ? null : buttonsInRow.get(col);
if (button != null) {
String buttonHtml = buildButton(item, button.getLabel(), button.getCmd(), button.getIcon());
buildCell(false, sizeDessktop, col > 8, sizeTablet, col > 4, sizePhone, buttonHtml, builder);
} else {
buildEmptyCell(false, sizeDessktop, col > 8, sizeTablet, col > 4, sizePhone, builder);
}
}

// Extra cell at end
if (extraCellSizeDesktop > 0) {
buildEmptyCell(false, extraCellSizeDesktop / 2 + extraCellSizeDesktop % 2, column > 8,
Math.max(1, extraCellSizeTablet / 2 + extraCellSizeTablet % 2), column > 4, 1, builder);
} else if (extraCellSizeTablet > 0 && columns < 8) {
buildEmptyCell(true, 1, false, extraCellSizeTablet / 2 + extraCellSizeTablet % 2, column > 4, 1, builder);
}
}

private void buildEmptyCell(boolean hideDesktop, int sizeDessktop, boolean hideTablet, int sizeTablet,
boolean hidePhone, int sizePhone, StringBuilder builder) throws RenderException {
buildCell(hideDesktop, sizeDessktop, hideTablet, sizeTablet, hidePhone, sizePhone, "", builder);
}

private void buildCell(boolean hideDesktop, int sizeDessktop, boolean hideTablet, int sizeTablet, boolean hidePhone,
int sizePhone, String buttonHtml, StringBuilder builder) throws RenderException {
String divClass = "";
if (hideDesktop) {
divClass += " mdl-cell--hide-desktop";
}
if (hideTablet) {
divClass += " mdl-cell--hide-tablet";
}
if (hidePhone) {
divClass += " mdl-cell--hide-phone";
}
String buttonDiv = getSnippet("buttoncell");
buttonDiv = buttonDiv.replace("%size_desktop%", String.valueOf(sizeDessktop));
buttonDiv = buttonDiv.replace("%size_tablet%", String.valueOf(sizeTablet));
buttonDiv = buttonDiv.replace("%size_phone%", String.valueOf(sizePhone));
buttonDiv = buttonDiv.replace("%class%", divClass);
buttonDiv = buttonDiv.replace("%button%", buttonHtml);
builder.append(buttonDiv);
}

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

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

button = button.replace("%item%", item);
button = button.replace("%cmd%", escapeHtml(command));
String buttonClass = "buttongrid-button";
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;
}
}
button = button.replace("%buttonstyle%", style);
button = button.replace("%class%", buttonClass);

return 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));
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
}

// Process the color tags
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="mdl-cell mdl-cell--%size_desktop%-col mdl-cell--%size_tablet%-col-tablet mdl-cell--%size_phone%-col-phone buttongrid-cell %class%">
%button%
</div>
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-buttongrid mdl-form__row--height-auto mdl-cell mdl-cell--12-col %visibility_class%">
<div
class="mdl-form__control mdl-form__buttongrid mdl-grid"
data-control-type="buttons"
data-item="%item%"
data-ignore-state="true"
data-widget-id="%widget_id%"
data-header-row="%header_row%"
>
%buttons%
</div>
</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
17 changes: 17 additions & 0 deletions bundles/org.openhab.ui.basic/web-src/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@
display: none;
}
}
&__row-buttongrid {
margin: 0;
border-bottom: 1px solid $item-separator-color;
&:last-child {
border: none;
}
}
&__image {
&.mdl-form__control {
padding-left: 0;
Expand Down Expand Up @@ -241,6 +248,13 @@
left: 0;
}
}
.buttongrid-cell {
height: 36px;
.buttongrid-button {
min-width: 100%;
text-transform: none;
}
}
.mdl-button,
.mdl-button:focus {
box-shadow: none;
Expand Down Expand Up @@ -417,6 +431,9 @@
&__buttons {
padding-top: 2px;
}
&__buttongrid {
padding: 0;
}
&--no-label {
.mdl-form__title {
display: none;
Expand Down
3 changes: 2 additions & 1 deletion bundles/org.openhab.ui.basic/web-src/_theming.scss
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ body {
color: var(--container-text-color, #616161) !important;
}

.mdl-form__row {
.mdl-form__row,
.mdl-form__row-buttongrid {
border-bottom: 1px solid #ccc;
border-bottom: 1px solid var(--border-color, #ccc);
color: #616161 !important;
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