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