Skip to content

Commit

Permalink
refactor to support both array/positional and kwargs
Browse files Browse the repository at this point in the history
This also makes buttons always as widgets in OH4.2+

Signed-off-by: Jimmy Tanagra <[email protected]>
  • Loading branch information
jimtng committed May 26, 2024
1 parent c4fc240 commit c6abfc9
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 127 deletions.
289 changes: 188 additions & 101 deletions lib/openhab/dsl/sitemaps/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,32 @@ module Sitemaps
# @!visibility private
org.openhab.core.model.sitemap.sitemap.impl.SitemapImpl.alias_method :uid, :name

# Base sitemap builder DSL
#
# A sitemap builder allows you to dynamically create openHAB sitemaps at runtime.
#
# @example
# sitemaps.build do
# sitemap "demo", label: "My home automation" do
# frame label: "Date" do
# text item: Date
# end
# frame label: "Demo" do
# switch item: Lights, icon: "light"
# text item: LR_Temperature, label: "Livingroom [%.1f °C]"
# group item: Heating
# text item: LR_Multimedia_Summary, label: "Multimedia [%s]", static_icon: "video" do
# selection item: LR_TV_Channel,
# mappings: { 0 => "off", 1 => "DasErste", 2 => "BBC One", 3 => "Cartoon Network" }
# slider item: LR_TV_Volume
# end
# end
# end
# end
#
# @see https://www.openhab.org/docs/ui/sitemaps.html
# @see OpenHAB::DSL.sitemaps
# @see OpenHAB::Core::Sitemaps::Provider#build sitemaps.build
#
class Builder
# @!visibility private
def initialize(provider, builder_proxy, update:)
Expand Down Expand Up @@ -680,7 +705,7 @@ def build

# Builds a `Button` element
#
# This element can only exist within a ButtonGrid element.
# This element can only exist within a `Buttongrid` element.
#
# @since openHAB 4.2
# @todo waiting for docs see www openhab.org/docs/ui/sitemaps.html#element-type-button
Expand Down Expand Up @@ -745,13 +770,25 @@ def stateless?

# @!visibility private
def build
widget = super
widget.row = row
widget.column = column
widget.cmd = click.to_s
widget.release_cmd = release.to_s unless release.nil?
widget.stateless = stateless? unless @stateless.nil?
widget
if Core.version >= Core::V4_2
super.tap do |widget|
widget.row = row
widget.column = column
widget.cmd = click.to_s
widget.release_cmd = release.to_s unless release.nil?
widget.stateless = stateless? unless @stateless.nil?
end
else
# @deprecated OH 4.1
# in OH 4.1, the button is a property of the Buttongrid, not a widget
SitemapBuilder.factory.create_button.tap do |button|
button.row = row
button.column = column
button.cmd = click.to_s
button.label = label
button.icon = icon if icon
end
end
end
end

Expand Down Expand Up @@ -1145,21 +1182,32 @@ class FrameBuilder < LinkableWidgetBuilder
# @see https://www.openhab.org/docs/ui/sitemaps.html#element-type-buttongrid
# @see org.openhab.core.model.sitemap.sitemap.Buttongrid
class ButtongridBuilder < LinkableWidgetBuilder
# @return [Array<Array<int, int, Command, String, String>>]
# An array of buttons to display
attr_reader :buttons
REQUIRED_BUTTON_ARGS = %i[row column click].freeze
private_constant :REQUIRED_BUTTON_ARGS

# @!deprecated OH 4.1 in OH 4.1, Buttongrid is not a LinkableWidget.
# Pretend that the buttons property is its children so we can add to it in LinkableWidgetBuilder#build
if (Core::V4_1...Core::V4_2).cover?(Core.version)
java_import org.openhab.core.model.sitemap.sitemap.Buttongrid
module Buttongrid
def children
buttons
end
end
end

# (see WidgetBuilder#initialize)
# @!method initialize(item: nil, label: nil, icon: nil, static_icon: nil, buttons: nil, label_color: nil, value_color: nil, icon_color: nil, visibility: nil)
# @param [Array<Array<int, int, Command, String, String>>] buttons An array of buttons to display.
# Each element is an array with the following elements:
# Each element can be a hash with keyword arguments (see {Sitemaps::ButtongridBuilder#button}),
# or an array with the following elements:
# - row: 1-12
# - column: 1-12
# - command: The command to send when the button is pressed
# - label: The label to display on the button
# - click: The command to send when the button is pressed
# - label: The label to display on the button (optional)
# - icon: The icon to display on the button (optional)
#
# @example
# @example Create a buttongrid with buttons as an argument
# # This creates a buttongrid to emulate a TV remote control
# sitemaps.build do
# sitemap "remote", label: "TV Remote Control" do
Expand All @@ -1171,125 +1219,164 @@ class ButtongridBuilder < LinkableWidgetBuilder
# [4, 2, "DOWN", "Down", "f7:arrowtriangle_down"],
# [3, 1, "LEFT", "Left", "f7:arrowtriangle_left"],
# [3, 3, "RIGHT", "Right", "f7:arrowtriangle_right"],
# [3, 2, "ENTER", "Enter", "material:adjust"]
#
# # Using keyword arguments:
# {row: 3, column: 2, click: "ENTER", label: "Enter", icon: "material:adjust" }
# ]
# end
# end
#
# @example Create a buttongrid with button widgets
# sitemaps.build do
# sitemap "remote", label: "TV Remote Control" do
# buttongrid item: LivingRoom_TV_RCButton do
# button 1, 1, click: "BACK", icon: "f7:return"
# button 1, 2, click: "HOME", icon: "material:apps"
# button 1, 3, click: "YELLOW", icon: "f7:search"
# button 2, 2, click: "UP", icon: "f7:arrowtriangle_up"
# button 4, 2, click: "DOWN", icon: "f7:arrowtriangle_down"
# button 3, 1, click: "LEFT", icon: "f7:arrowtriangle_left"
# button 3, 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
# button 3, 2, click: "ENTER", icon: "material:adjust"
# end
#
# # The following buttons use widget features introduced in openHAB 4.2+
# buttongrid item: LivingRoom_Curtain do
# button 1, 1, click: "up", release: "stop", icon: "f7:arrowtriangle_up"
# button 2, 1, click: "down", release: "stop", icon: "f7:arrowtriangle_up"
# end
# end
# end
#
# @see https://www.openhab.org/docs/ui/sitemaps.html#element-type-buttongrid
# @!visibility private
def initialize(type, builder_proxy, buttons: [], **kwargs, &block)
@buttons = buttons
buttons.each { |button| validate_button(button) }
super(type, builder_proxy, **kwargs, &block)

# Put the buttons given in the constructor before those added in the block
# We can't do this before calling the super constructor because `children` is initialized there
children.slice!(0..).then do |buttons_from_block|
buttons.each do |b|
if b.is_a?(Array)
button(*b)
else
button(**b)
end
end
children.concat(buttons_from_block)
end
end

#
# Adds a button to the buttongrid.
# @!method button(row = nil, column = nil, click = nil, label = nil, icon = nil, item: nil, label: nil, icon: nil, static_icon: nil, row:, column:, click:, release: nil, stateless: nil, label_color: nil, value_color: nil, icon_color: nil, visibility: nil)
# Adds a button inside the buttongrid
#
# - In openHAB 4.1, buttons are direct properties of the buttongrid.
# Only `row`, `column`, `click`, `label` (optional), and `icon` (optional) are used.
# All the other parameters are ignored.
# All the buttons will send commands to the same item assigned to the buttongrid.
#
# - In openHAB 4.2+, buttons are widgets within the containing buttongrid, and they
# support all the parameters listed in the method signature such as
# `release`, `label_color`, `visibility`, etc.
# Each Button element has an item associated with that button.
# When an item is not specified for the button, it will default to the containing buttongrid's item.
#
# openHAB 4.2 introduced the ability to have Button elements inside a Buttongrid.
# Each Button element is an independent widget linked to its own item.
# This means that a buttongrid can contain multiple buttons that each controls different items.
# This method supports positional arguments and/or keyword arguments.
# Their use can be mixed, however, the keyword arguments will override the positional arguments
# when both are specified.
#
# @example Adding buttons to a buttongrid
# @param (see ButtonBuilder#initialize)
# @return [ButtonBuilder]
#
# @example Adding buttons to a buttongrid with positional arguments
# sitemaps.build do
# sitemap "remote" do
# buttongrid item: RCButton do
# button [1, 1, "BACK", "Back", "f7:return"]
# button [1, 2, "HOME", "Menu", "material:apps"]
# button [1, 3, "YELLOW", "Search", "f7:search"]
# button [2, 2, "UP", "Up", "f7:arrowtriangle_up"]
# button [4, 2, "DOWN", "Down", "f7:arrowtriangle_down"]
# button [3, 1, "LEFT", "Left", "f7:arrowtriangle_left"]
# button [3, 3, "RIGHT", "Right", "f7:arrowtriangle_right"]
# button [3, 2, "ENTER", "Enter", "material:adjust"]
# button 1, 1, "BACK", "Back", "f7:return"
# button 1, 2, "HOME", "Menu", "material:apps"
# button 1, 3, "YELLOW", "Search", "f7:search"
# button 2, 2, "UP", "Up", "f7:arrowtriangle_up"
# button 4, 2, "DOWN", "Down", "f7:arrowtriangle_down"
# button 3, 1, "LEFT", "Left", "f7:arrowtriangle_left"
# button 3, 3, "RIGHT", "Right", "f7:arrowtriangle_right"
# button 3, 2, "ENTER", "Enter", "material:adjust"
# end
# end
# end
#
# @example Adding Button widgets to a buttongrid
# @example Adding buttons to a buttongrid with keyword arguments
# sitemaps.build do
# sitemap "remote" do
# buttongrid do
# button item: RCButton, row: 1, column: 1, click: "BACK", icon: "f7:return"
# button item: RCButton, row: 1, column: 2, click: "HOME", icon: "material:apps"
# button item: RCButton, row: 1, column: 3, click: "YELLOW", icon: "f7:search"
# button item: RCButton, row: 2, column: 2, click: "UP", icon: "f7:arrowtriangle_up"
# button item: RCButton, row: 4, column: 2, click: "DOWN", icon: "f7:arrowtriangle_down"
# button item: RCButton, row: 3, column: 1, click: "LEFT", icon: "f7:arrowtriangle_left"
# button item: RCButton, row: 3, column: 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
# button item: RCButton, row: 3, column: 2, click: "ENTER", icon: "material:adjust"
# button item: TVPower, row: 4, column: 3, click: ON, icon: "switch-on", visibility: "TVPower!=ON"
# button item: TVPower, row: 4, column: 3, click: OFF, icon: "switch-off", visibility: "TVPower==ON"
# buttongrid item: RCButton do
# # These buttons will use the default item assigned to the buttongrid (RCButton)
# button row: 1, column: 1, click: "BACK", icon: "f7:return"
# button row: 1, column: 2, click: "HOME", icon: "material:apps"
# button row: 1, column: 3, click: "YELLOW", icon: "f7:search"
# button row: 2, column: 2, click: "UP", icon: "f7:arrowtriangle_up"
# button row: 4, column: 2, click: "DOWN", icon: "f7:arrowtriangle_down"
# button row: 3, column: 1, click: "LEFT", icon: "f7:arrowtriangle_left"
# button row: 3, column: 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
# button row: 3, column: 2, click: "ENTER", icon: "material:adjust"
# end
# end
# end
#
# @example Mixing positional and keyword arguments
# sitemaps.build do
# sitemap "remote" do
# buttongrid item: RCButton do
# button 1, 1, click: "BACK", icon: "f7:return"
# button 1, 2, click: "HOME", icon: "material:apps"
# button 1, 3, click: "YELLOW", icon: "f7:search"
# button 2, 2, click: "UP", icon: "f7:arrowtriangle_up"
# button 4, 2, click: "DOWN", icon: "f7:arrowtriangle_down"
# button 3, 1, click: "LEFT", icon: "f7:arrowtriangle_left"
# button 3, 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
# button 3, 2, click: "ENTER", icon: "material:adjust"
# end
# end
# end
#
# @overload button(button)
# Adds a button to the buttongrid's main item
#
# @param [Array<int, int, Command, String, String>] button
# The button to add, which is an array with the following elements:
# - row: 1-12
# - column: 1-12
# - command: The command to send when the button is pressed
# - label: The label to display on the button
# - icon: The icon to display on the button (optional)
# @return [Array<Array<int, int, Command, String, String>>] the current buttons
#
# @overload button(item: nil, label: nil, icon: nil, static_icon: nil, row:, column:, click:, release: nil, stateless: nil, label_color: nil, value_color: nil, icon_color: nil, visibility: nil)
# Adds a Button widget inside the buttongrid
# @param (see OpenHAB::DSL::Sitemaps::ButtonBuilder#initialize)
# @return [ButtonBuilder]
# @example openHAB 4.2+ supports assigning different items to buttons, along with additional features
# sitemaps.build do
# sitemap "remote" do
# buttongrid item: RCButton do
# button 1, 1, click: "BACK", icon: "f7:return"
# button 1, 2, click: "HOME", icon: "material:apps"
# button 1, 3, click: "YELLOW", icon: "f7:search", icon_color: "yellow"
# button 2, 2, click: "UP", icon: "f7:arrowtriangle_up"
# button 4, 2, click: "DOWN", icon: "f7:arrowtriangle_down"
# button 3, 1, click: "LEFT", icon: "f7:arrowtriangle_left"
# button 3, 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
# button 3, 2, click: "ENTER", icon: "material:adjust", icon_color: "red"
#
# @since openHAB 4.2
# # These buttons will use the specified item, only supported in openHAB 4.2+
# button 4, 3, click: ON, static_icon: "switch-off", visibility: "TVPower!=ON", item: TVPower
# button 4, 3, click: OFF, static_icon: "switch-on", visibility: "TVPower==ON", item: TVPower
# end
# end
# end
#
def button(button = nil, **kwargs, &block)
if button.nil?
missing_args = (%i[row column click] - kwargs.keys).compact
unless missing_args.empty?
args = kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
missing_args = missing_args.map(&:to_s).join(", ")
raise ArgumentError, "button(#{args}) missing required parameters: #{missing_args}"
end
def button(row = nil, column = nil, click = nil, label = nil, icon = nil, **kwargs, &block)
args = [row, column, click, label, icon].compact

widget = ButtonBuilder.new(@builder_proxy, **kwargs, &block)
children << widget
return widget
end
args = args.first if args.first.is_a?(Array)
kwargs = %i[row column click label icon].zip(args).to_h.compact.merge(kwargs)

validate_button(button)
@buttons << button
end

# @!visibility private
def build
widget = super
buttons.each do |button|
button_object = if SitemapBuilder.factory.respond_to?(:create_button_definition)
SitemapBuilder.factory.create_button_definition
else
# @deprecated OH 4.1 in OH 4.2 this clause is not needed
SitemapBuilder.factory.create_button
end
button_object.row = button[0]
button_object.column = button[1]
button_object.cmd = button[2]
button_object.label = button[3]
button_object.icon = button[4] if button[4]
widget.buttons.add(button_object)
missing_args = (REQUIRED_BUTTON_ARGS - kwargs.keys).compact
unless missing_args.empty?
args = kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
missing_args = missing_args.map(&:to_s).join(", ")
raise ArgumentError, "button(#{args}) missing required parameters: #{missing_args}"
end

widget
end
kwargs[:item] ||= item if item # default to the buttongrid's item
kwargs[:label] ||= kwargs[:click].to_s

private

def validate_button(button)
return if button.is_a?(Array) && (4..5).cover?(button.size)

raise ArgumentError, "Invalid button: '#{button.inspect}'. It must be an array with (4..5) elements"
ButtonBuilder.new(@builder_proxy, **kwargs, &block).tap do |b|
children << b
end
end
end

Expand Down
Loading

0 comments on commit c6abfc9

Please sign in to comment.