Parent

Monkeybars::View

The view is the gatekeeper to the actual Java (or sometimes non-Java) view class. The view defines how data moves into and out of the view via the model.

Any property of the underlying “real” view can be accessed as if it were a property of the view class.

Thus if you have a JFrame that has a member variable okButton, you could do the following:

okButton.text = "Confirm"

You must use the exact name that is used in the underlying view, no normal JRuby javaName to java_name conversion is performed. ok_button.text = “Confirm” would fail.

Example, (assume a JFrame with a label, text area and button):

require 'monkeybars'

class MyView < Monkeybars::View
  set_java_class "com.project.MyCoolJFrame"
  map :view => "titleLabel.text", :model => :title_text
  map :view => "okButton.text", :model => :button_text
  map :view => "mainTextArea.text", :model => :text, :using => [:convert_to_string, :convert_to_array]
  map :view => "titleLabel.selected_text_color", :transfer => :text_color
  COLOR_TRANSLATION = {:red => Color.new(1.0, 0.0, 0.0), :green => Color.new(0.0, 1.0, 0.0), :blue => Color.new(0.0, 0.0, 1.0)} 
  map :view => "titleLabel.selected_text_color", :model => :text, :translate_using => COLOR_TRANSLATION

  def convert_to_string(model) 
    model.text.join("\n")
  end

  def convert_to_array(model)
    mainTextArea.text.split("\n")
  end
end

It is important that you do not implement your own initialize method, doing so will interfere with the operation of the View class (or if you must, remember to call super on the first line of your initialize).

It is possible to have a view that is not related to a Java class, in which case no set_java_class delcaration is used. If using a pure Ruby view (or manually wrapping a Java class) you must set the @main_view_component member variable yourself. The object you assign to @main_view_component should respond to any methods that normally interact with the Java object such as visisble?, hide, and dispose

Public Class Methods

define_signal(options, method_name = nil) click to toggle source

Declares a mapping between a signal and a method to process the signal. When the signal is received, the method is called with the model and the transfer as parameters. If a signal is sent that is not defined, an UnknownSignalError exception is raised.

define_signal :name => :error_state, :handler => :disable_go_button

def disable_go_button(model, transfer)
  go_button.enabled = false
end
# File lib/monkeybars/view.rb, line 238
def self.define_signal(options, method_name = nil)
  if options.kind_of? Hash
    begin
      options.validate_only :name, :handler
      options.validate_all  :name, :handler
    rescue InvalidHashKeyError
      raise InvalidSignalError, ":signal and :handler must be provided for define_signal. Options provided: #{options.inspect}"
    end
    signal_mappings[options[:name]] = options[:handler]
  else
    #support two styles for now, deprecating the old (signal, method_name) style      
    warn "The usage of define_signal(signal, method_name) has been deprecated, please use define_signal :name => :signal, :handler => :method_name"
    signal_mappings[options] = method_name
  end

end
map(properties) click to toggle source

Declares a mapping between the properties of the view and either the model’s or the transfer’s properties. This mapping is used when creating the model. If you wish to trigger subsequent updates of the view, you may call update_view manually from the controller.

There are several ways to declare a mapping based on what level of control you need over the process. The simplest form is:

map :view => :foo, :model => :bar

or

map :view => :foo, :transfer => :bar

Which means, when update is called, self.foo = model.bar and self.foo = transfer respectively.

Strings may be used interchangably with symbols for model mappings. If you have nested view or properties you may specify them as a string:

map :view => "foo.sub_property", :model => "bar.other_sub_property"

which means, self.foo.sub_property = model.bar.other_sub_property

It should be noted that these mappings are bi-directional. They are referenced for both update and write_state. When used for write_state the assignment direction is reversed, so a view with

map :view => :foo, :model => :bar

would mean model.bar = self.foo

When a direct assignment is not sufficient you may provide a method to filter or adapt the contents of the model’s value before assignment to the view. This is accomplished by adding a :using key to the hash. The key’s value is an array of the method names to be used when converting from the model to the view, and when converting from the view back to the model. If you only want to use a custom method for either the conversion from a model to a view or vice versa, you can specify :default, for the other parameter and the normal mapping will take place. If you want to disable the copying of data in one direction you can pass nil as the method parameter.

map :view => :foo, :model => :bar, :using => [:from_model, :to_model]

would mean self.foo = from_model() when called by update and model.bar = to_model() when called by write_state.

map :view => :foo, :model => :bar, :using => [:from_model, :default]

would mean self.foo = from_model() when called by update and model.bar = self.foo when called by write_state.

map :view => :foo, :model => :bar, :using => [:from_model, nil]

would mean self.foo = from_model() when called by update and would do nothing when called by write_state.

For constant value translation, :translate_using provides a one-line approach. :translate_using takes a hash with the model values as the key, and the view values as the value. This only works when the view state and the model state has a one-to-one translation.

COLOR_TRANSLATION = {:red => Color.new(1.0, 0.0, 0.0), :green => Color.new(0.0, 1.0, 0.0), :blue => Color.new(0.0, 0.0, 1.0)} 
map :view => "titleLabel.selected_text_color", :model => :text, :translate_using => COLOR_TRANSLATION

If you want to invoke disable_handlers during the call to update you can add the :ignoring key. The key’s value is either a single type or an array of types to be ignored during the update process.

map :view => :foo, :model => :bar, :ignoring => :item

This will wrap up the update in a call to disable_handlers on the view component, which is assumed to be the first part of the mapping UP TO THE FIRST PERIOD. This means that

map :view => "foo.bar", :model => :model_property, :ignoring => :item

would translate to

foo.disable_handlers(:item) do
  foo.bar = model.model_property
end

during a call to update. During write_to_model, the ignoring definition has no meaning as there are no event handlers on models.

The final option for mapping properties is a simply your own method. As with a method provided via the using method you may provide a method for conversion into the view, out of the view or both.

raw_mapping :from_model, :to_model

would simply invoke the associated method when update or write_state was called. Thus any assignment to view properties must be done within the method (hence the ‘raw’).

To disable handlers in a raw mapping, call Component#disable_handlers inside your mapping method

# File lib/monkeybars/view.rb, line 218
def self.map(properties)
  mapping = Mapping.new(properties)
  view_mappings << mapping
end
nest(properties) click to toggle source

Declares how nested views from their respective nested controllers are to be added and removed from the view. Multiple nestings for the different nested controllers are possible through the :sub_view value, which is basically a grouping name. Two kinds of mapping are possible: A property nesting, and a method nesting. Property nesting:

nest :sub_view => :user_list, :view => :user_panel

This essentially calls user_panel.add nested_view_component on Monkeybars::Controller#add_nested_controller and user_panel.remove nested_view_componenent on Monkeybars::Controller#remove_nested_controller. A layout on the container being used is preferred, as no orientational information will be conveyed to either component.

Method nesting:

nest :sub_view => :user_list, :using => [:add_user, :remove_user]

def add_user(nested_view, nested_component, model, transfer)
  user_panel.add nested_component
  nested_component.set_location(nested_component.x, nested_component.height * transfer[:user_list_size])
end

def remove_user(nested_view, nested_component, model, transfer)
  # nested_view is the Ruby view object
  # nested_component is the Java form, aka @main_view_component

  user_panel.remove nested_component
  # lots of code to re-order previous components
end

Method nesting calls the methods in :using (in the same way that :using works for mapping) during add and remove (Monkeybars::Controller#add_nested_controller and Monkeybars::Controller#remove_nested_controller). Both methods are passed in the view, the view’s main view component, the mode, and the transfer, respectively.

New users using Netbeans will need to know that the GroupLayout (aka FreeDesign) is a picky layout that demands constraints while adding components. By default, all containers use GroupLayout in Netbeans’s GUI builder. If you’re not sure what all this means, just start off with a BoxLayout (which is not picky). If your layout needs constraints, you will need to pass them in with your Method Nesting. Some layouts even need @main_view_component#revalidate to be called. In short, be aware of Swing’s quirks.

# File lib/monkeybars/view.rb, line 294
def self.nest(properties)
  view_nestings[properties[:sub_view]] ||= []
  view_nestings[properties[:sub_view]] << Nesting.new(properties)
end
new() click to toggle source
# File lib/monkeybars/view.rb, line 299
def initialize
  @__field_references = {}
  # We have at three possibilities:
  #  - The UI form is a Java class all the way; that it, it came from Java code compiled into a .class file.
  #  - The UI form was written in Ruby, but inherits from a Java class (e.g. JFrame). It is quite servicable
  #    for the UI, but will behave differently in regards to Java reflection -- We'll refer to this as a JRuby class
  #  - The UI form is not Java at all. 
  @main_view_component = create_main_view_component
  raise MissingMainViewComponentError, "Missing @main_view_component. Use View.set_java_class or override create_main_view_component." if @main_view_component.nil?
  @is_java_class = !@main_view_component.class.respond_to?(:java_proxy_class)
  setup_implicit_and_explicit_event_handlers
  load
end
raw_mapping(to_view_method, from_view_method, handlers_to_ignore = []) click to toggle source

See View.map

# File lib/monkeybars/view.rb, line 224
def self.raw_mapping(to_view_method, from_view_method, handlers_to_ignore = [])
  view_mappings << Mapping.new(:using => [to_view_method, from_view_method], :ignoring => handlers_to_ignore)
end
set_java_class(java_class) click to toggle source

Declares what class to instantiate when creating the view. Any listeners set on the component are added to this class as well as the setting of the close action that is defined in the controller. Accepts a string that is the Java package, or a class for the Java or JRuby UI object

# File lib/monkeybars/view.rb, line 104
def self.set_java_class(java_class)
  # We're allowing two options: The existing "Give me a string", and
  # passing a constant (which is new behavior).
  # In a view class, the develoepr can simply give the name of a defined class
  # to use, in which case this code does not need to try to load anything.
  if java_class.is_a?(String)
    include_class java_class
    class_name = /.*?\.?(\w+)$/.match(java_class)[1]
    self.instance_java_class = const_get(class_name)
  elsif  java_class.is_a?(Class)
    self.instance_java_class = java_class
  else
    raise "Setting the view class requires either a string naming the class to load, or an actual class constant. set_java_class was given #{java_class.inspect}."
  end
end

Public Instance Methods

add_handler(handler, component) click to toggle source

This is set via the controller, do not call directly unless you know what you are doing.

Looks up the appropriate component and calls addXXXListener on the component. Components can be nested, so textField.document would be a valid component and the listner would be added to the document object of the text field.

add_handler returns a hash of objects as keys and their normalized (underscored and . replaced with _ ) names as values

# File lib/monkeybars/view.rb, line 376
def add_handler(handler, component)
  component = component.to_s
  if "global" == component
    raise "Global handler declarations are not yet supported"
  elsif "java_window" == component
    begin
      @main_view_component.send("add#{handler.type.camelize}Listener", handler)
    rescue NameError
      raise InvalidHandlerError, "There is no listener of type #{handler.type} on #{@main_view_component}"
    end
  else
    begin
      object = instance_eval(component, __FILE__, __LINE__)
    rescue NameError
      raise UndefinedComponentError, "Cannot add #{handler.type} handler to #{component} on #{self}, the component could not be found"
    end

    begin        
      object.send("add#{handler.type.camelize}Listener", handler)
    rescue NameError
      raise InvalidHandlerError, "There is no listener of type #{handler.type} on #{component}"
    end
  end
end
close_action(action, handler = nil) click to toggle source

This is set via the constructor, do not call directly unless you know what you are doing.

Defines the close action for a visible window. This method is only applicable for JFrame or decendants, it is ignored for all other classes.

close_action expects an action from Monkeybars::View::CloseActions

The CloseActions::METHOD action expects a second parameter which should be a MonkeybarsWindowAdapter.

# File lib/monkeybars/view.rb, line 323
def close_action(action, handler = nil)
  if @main_view_component.kind_of?(javax.swing.JFrame) || @main_view_component.kind_of?(javax.swing.JInternalFrame) || @main_view_component.kind_of?(javax.swing.JDialog)
    if CloseActions::METHOD == action
      @main_view_component.default_close_operation = CloseActions::DO_NOTHING
      unless @main_view_component.kind_of?(javax.swing.JInternalFrame)
        @main_view_component.add_window_listener(handler)
      else
        @main_view_component.add_internal_frame_listener(handler)
      end
    else
      @main_view_component.set_default_close_operation(action)
    end
  end
end
dispose() click to toggle source
# File lib/monkeybars/view.rb, line 534
def dispose
  @main_view_component.dispose if @main_view_component.respond_to? :dispose
end
focused?() click to toggle source

True if the controller’s view is focused. Focus mostly means this is the window where mouse clicks and keyboard presses are directed. There are also UI effects for focused components. For event driven notifications of focus, see the following: java.sun.com/j2se/1.4.2/docs/api/java/awt/event/FocusListener.html java.sun.com/docs/books/tutorial/uiswing/events/focuslistener.html java.sun.com/docs/books/tutorial/uiswing/misc/focus.html

# File lib/monkeybars/view.rb, line 446
def focused?
  @main_view_component.focus_owner?
end
get_field(field_name) click to toggle source

Uses reflection to pull a private field out of the Java objects. In cases where no Java object is being used, the view object itself is referenced. A field is not the same as the object it refers to, you only need this method if you want to change what a view field references using the set_value method.

field = get_field(“my_button”) field.set_value(Java.ruby_to_java(@main_view_component), Java.ruby_to_java(my_new_button))

# File lib/monkeybars/view.rb, line 506
def get_field(field_name)
  field_name = field_name.to_sym
  field = @__field_references[field_name]

  if field.nil?
    if @is_java_class
      [field_name.to_s, field_name.camelize, field_name.camelize(false), field_name.underscore].uniq.each do |name|
        begin
          field = self.class.instance_java_class.java_class.declared_field(name)
        rescue NameError, NoMethodError
        end
        break unless field.nil?
      end
      raise UndefinedComponentError, "There is no component named #{field_name} on view #{@main_view_component.class}" if field.nil?

      field.accessible = true
    else
      begin
        field = @main_view_component.method(field_name)
      rescue NameError, NoMethodError
        raise UndefinedComponentError, "There is no component named #{field_name} on view #{@main_view_component.class}"
      end
    end
    @__field_references[field_name] = field
  end
  field
end
get_field_names() click to toggle source
# File lib/monkeybars/view.rb, line 538
def get_field_names
  fields = []
  if @is_java_class
    klass = self.class.instance_java_class.java_class
    while(klass.name !~ /^java[x]?\./)
      fields << klass.declared_fields
      klass = klass.superclass
    end
    fields.flatten.map! {|field| field.name }
  else
    @main_view_component.send(:instance_variables).map! {|name| name.sub('@', '')}
  end
end
get_field_value(field_name) click to toggle source

Uses get_field to retrieve the value of a particular field, this is typically a component on a Java form. Used internally by method missing to enable:

some_component.method
# File lib/monkeybars/view.rb, line 485
def get_field_value(field_name)
  if "java_window" == field_name.to_s
    @main_view_component
  else
    field_name = field_name.to_sym
    if @is_java_class
      field_object = get_field(field_name)
      Java.java_to_ruby(field_object.value(Java.ruby_to_java(@main_view_component)))
    else
      get_field(field_name).call
    end
  end
end
hide() click to toggle source
# File lib/monkeybars/view.rb, line 362
def hide
  @main_view_component.visible = false
end
load() click to toggle source

Stub to be overriden in sub-class. This is where you put the code you would normally put in initialize. Load will be called whenever a new class is instantiated which happens when the Controller’s instance method is called on a non-instantiated controller. Thus this method will always be called before the Controller’s load method (which is called during Controlller#open).

# File lib/monkeybars/view.rb, line 476
def load; end
method_missing(method, *args, &block) click to toggle source

Attempts to find a member variable in the underlying @main_view_component object if one is set, otherwise falls back to default method_missing implementation.

Also, detect if the user is trying to set a new value for a given instance variable in the form. If so, the field will be updated to refer to the provided value. The passed in argument MUST BE A JAVA OBJECT or this call will fail.

# File lib/monkeybars/view.rb, line 407
def method_missing(method, *args, &block)
  if match = /(.*)=$/.match(method.to_s)
    if @is_java_class
      field = get_field(match[1])
      field.set_value(Java.ruby_to_java(@main_view_component), Java.ruby_to_java(args[0]))
    else
      set_jruby_field(match[1], args[0])
    end
  else
    begin
      return get_field_value(method)
    rescue NameError
      super
    end
  end
end
on_first_update(model, transfer) click to toggle source

This method is called when Controller#load has completed (usually during Controller#open) but before the view is shown. This method is meant to be overriden in views that need control over how their mappings are initially run. By overriding this method you could use disable_handlers to disable certain handlers during the initial mapping process or perform some actions after the mappings complete.

When overriding on_first_update, you must at some point make a call to super or the View#update method in order for your view’s mappings to be invoked.

# File lib/monkeybars/view.rb, line 346
def on_first_update(model, transfer)
  update(model, transfer)
end
process_signal(signal, model, transfer, &block) click to toggle source
# File lib/monkeybars/view.rb, line 461
def process_signal(signal, model, transfer, &block)
  handler = self.class.signal_mappings[signal]
  if handler.nil?
    raise UndefinedSignalError, "There is no signal '#{signal}' defined"
  else
    raise InvalidSignalHandlerError, "There is no handler method '#{handler}' on view #{self.class}" unless respond_to?(handler)
    self.send(handler, model, transfer, &block) unless handler.nil?
  end
end
set_jruby_field(getter, value) click to toggle source
# File lib/monkeybars/view.rb, line 424
def set_jruby_field(getter, value)
  @main_view_component.send("#{getter}=", value)
end
show() click to toggle source
# File lib/monkeybars/view.rb, line 358
def show
  @main_view_component.visible = true
end
unload() click to toggle source

Stub to be overriden in sub-class. This is called whenever the view is closed.

# File lib/monkeybars/view.rb, line 479
def unload; end
update(model, transfer) click to toggle source
# File lib/monkeybars/view.rb, line 450
def update(model, transfer)
  self.class.view_mappings.select{|mapping| mapping.maps_to_view?}.each {|mapping| mapping.to_view(self, model, transfer)}
  transfer.clear
end
visible=(visibility) click to toggle source
# File lib/monkeybars/view.rb, line 354
def visible=(visibility)
  @main_view_component.visible = visibility
end
visible?() click to toggle source
# File lib/monkeybars/view.rb, line 350
def visible?
  return @main_view_component.visible
end
write_state(model, transfer) click to toggle source

The inverse of update. Called when view_state is called in the controller.

# File lib/monkeybars/view.rb, line 456
def write_state(model, transfer)
  transfer.clear
  self.class.view_mappings.select{|mapping| mapping.maps_from_view?}.each {|mapping| mapping.from_view(self, model, transfer)}
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.