assets/app/src/main/resources/ruboto/widget.rb
require 'ruboto/base'
require 'ruboto/activity'
#######################################################
#
# ruboto/widget.rb
#
# Import widgets and set up methods on activity to
# create and initialize. You do not need this if
# you want to call the Android methods directly.
#
#######################################################
#
# Prepare View
#
java_import 'android.view.View'
# Set an attribute value by name on the given target. This method is used by
# all the widget methods generated by "ruboto_import_widget" and
# "ruboto_import_widgets":
#
# button text: 'Click me!', layout: {weight: 1, gravity: :center}
#
# The attribute can be a public field, a JavaBean attribute, or a setter method.
#
# set_attribute button, :text, 'Click me!'
# set_attribute button.layout_attributes, :weight, 1
#
# The given attribute name can match the attribute name exactly or be accessed
# by the snake_case version of the attribute. It can also be accessed by the
# setter name, either snake_case or camelCase.
#
# set_attribute button, :textAlignment, android.view.View::TEXT_ALIGNMENT_CENTER
# set_attribute button, :text_alignment, android.view.View::TEXT_ALIGNMENT_CENTER
# set_attribute button, :set_text_alignment, android.view.View::TEXT_ALIGNMENT_CENTER
# set_attribute button, :setTextAlignment, android.view.View::TEXT_ALIGNMENT_CENTER
#
# You can use symbols as values for common constants. Currently constants for
# the following classes are enabled using symbols:
#
# * android.view.Gravity
# * android.view.ViewGroup::LayoutParams
# * android.widget.LinearLayout
# * android.widget.RelativeLayout
#
# set_attribute button, :text_alignment, :text_alignment_center
# set_attribute button.layout_attributes, :gravity, :center
# set_attribute button.layout_attributes, :width, :fill_parent
#
# Integer values in the range 0x80000000..0xFFFFFFFF are converted to a negative
# Integer value enabling setting of 4-byte color values as Ruby integer literals.
#
# set_attribute button, :background_color, 0x0000FF00
#
# You can use an array as a value for multi-value attributes like margins and
# padding.
#
# set_attribute button, :padding, [10, 20, 10, 30]
#
def set_attribute(target, k, v)
assign_method = "#{k}="
field_assign_method = "#{k.to_s.gsub(/(_)([a-z])/) { $2.upcase }}="
setter_method = "set#{k.to_s.gsub(/(^|_)([a-z])/) { $2.upcase }}"
method_name =
(target.respond_to?(assign_method) && assign_method) ||
(target.respond_to?(field_assign_method) && field_assign_method) ||
(target.respond_to?(setter_method) && setter_method) ||
k
invoke_with_converted_arguments(target, method_name, v)
end
def invoke_with_converted_arguments(target, method_name, values)
converted_values = [*values].map { |i| View.convert_constant(i) }
scaled_values = converted_values.map.with_index do |v, i|
v.is_a?(Integer) && v >= 0x80000000 && v <= 0xFFFFFFFF ?
v.to_i - 0x100000000 : v
end
target.send(method_name, *scaled_values)
end
View.class_eval do
@@convert_constants ||= {}
def self.add_constant_conversion(from, to)
symbol = from.to_s.downcase.to_sym
if @@convert_constants.include?(symbol)
puts "WARNING: Overwriting symbol to constant conversion for #{symbol.inspect}: #{@@convert_constants[symbol].inspect} => #{to.inspect}"
end
@@convert_constants[symbol] = to
end
def self.convert_constant(from)
return from unless from.is_a?(Symbol)
unless @@convert_constants.include?(from)
raise "Symbol #{from.inspect} doesn't have a corresponding View constant #{from.to_s.upcase}."
end
@@convert_constants[from]
end
def self.setup_constant_conversion
(self.constants - self.superclass.constants).each do |i|
View.add_constant_conversion i, self.const_get(i)
end
end
def configure(context, params = {})
if (width = params.delete(:width))
getLayoutParams.width = View.convert_constant(width)
puts "\nDEPRECATION: The ':width' option is deprecated. Use :layout => {:width => XX} instead."
end
if (height = params.delete(:height))
getLayoutParams.height = View.convert_constant(height)
puts "\nDEPRECATION: The ':height' option is deprecated. Use :layout => {:height => XX} instead."
end
if (margins = params.delete(:margins))
getLayoutParams.set_margins(*margins)
puts "\nDEPRECATION: The ':margins' option is deprecated. Use :layout => {:margins => [L, T, R, B]} instead."
end
if (layout = params.delete(:layout))
lp = getLayoutParams
layout.each { |k, v| set_attribute(lp, k, v) }
end
params.each { |k, v| set_attribute(self, k, v) }
end
end
#
# Load ViewGroup constants
#
java_import 'android.view.ViewGroup'
ViewGroup::LayoutParams.constants.each do |i|
View.add_constant_conversion i, ViewGroup::LayoutParams.const_get(i)
end
#
# Load Gravity constants
#
java_import 'android.view.Gravity'
Gravity.constants.each do |i|
View.add_constant_conversion i, Gravity.const_get(i)
end
#
# RubotoActivity View Generation
#
def ruboto_import_widgets(*widgets)
widgets.each { |i| ruboto_import_widget i }
end
def ruboto_import_widget(class_name, package_name='android.widget')
if class_name.is_a?(String) or class_name.is_a?(Symbol)
klass = java_import("#{package_name}.#{class_name}") || eval("Java::#{package_name}.#{class_name}")
else
klass = class_name
java_import klass
class_name = klass.java_class.name.split('.')[-1]
end
return unless klass
method_str = "
def #{(class_name.to_s.gsub(/([A-Z])/) { '_' + $1.downcase })[1..-1]}(params={})
if force_style = params.delete(:default_style)
rv = #{class_name}.new(self, nil, force_style)
elsif api_key = params.delete(:apiKey)
rv = #{class_name}.new(self, api_key)
else
rv = #{class_name}.new(self)
end
if parent = (params.delete(:parent) || @view_parent)
parent.addView(rv, (params.delete(:parent_index) || parent.child_count))
end
rv.configure self, params
return rv unless block_given?
old_view_parent, @view_parent = @view_parent, rv
yield
@view_parent = old_view_parent
rv
end
"
RubotoActivity.class_eval method_str
# FIXME(uwe): Remove condition when we stop support for api level < 11
if android.os.Build::VERSION::SDK_INT >= 11
android.app.Fragment.class_eval method_str.gsub('self', 'activity')
end
# EMXIF
setup_list_view if class_name == :ListView
setup_spinner if class_name == :Spinner
setup_button if class_name == :Button
setup_image_button if class_name == :ImageButton
setup_linear_layout if class_name == :LinearLayout
setup_relative_layout if class_name == :RelativeLayout
klass
end
#
# Special widget setup
#
def setup_linear_layout
Java::android.widget.LinearLayout.setup_constant_conversion
end
def setup_relative_layout
Java::android.widget.RelativeLayout.setup_constant_conversion
end
def setup_button
# legacy
end
def setup_image_button
# legacy
end
def setup_list_view
android.widget.ListView.__persistent__ = true
android.widget.ListView.class_eval do
def configure(context, params = {})
if (list = params.delete(:list))
item_layout = params.delete(:item_layout) || android.R::layout::simple_list_item_1
params[:adapter] = android.widget.ArrayAdapter.new(context, item_layout, list)
end
super(context, params)
end
def reload_list(list)
adapter.clear
adapter.addAll(list)
adapter.notifyDataSetChanged
invalidate
end
end
end
def setup_spinner
android.widget.Spinner.__persistent__ = true
android.widget.Spinner.class_eval do
def configure(context, params = {})
if (list = params.delete(:list))
item_layout = params.delete(:item_layout)
params[:adapter] = android.widget.ArrayAdapter.new(context, item_layout || android.R::layout::simple_spinner_item, list)
dropdown_layout = params.delete(:dropdown_layout)
params[:adapter].setDropDownViewResource(dropdown_layout) if dropdown_layout
end
super(context, params)
end
def reload_list(list)
adapter.clear
adapter.addAll(list)
adapter.notifyDataSetChanged
invalidate
end
end
end