Parent

Monkeybars::Controller

Controllers are the traffic cops of your application. They decide how to react to events, they coordinate interaction with other controllers and they define some of the overall characteristics of a view such as which events it will generate and how it should respond to things like the close button being pressed. For a general introduction to the idea of MVC and the role of a controller, please see: en.wikipedia.org/wiki/Model-view-controller. Monkeybars is not, strictly speaking, an MVC framework. However the general idea of the seperations of concerns that most people think of when you say ‘MVC’ is applicable.

The controller defines the model and view classes it is associated with. Most controllers will declare a view and a model that will be instantiated along with the controller and whose life cycle is managed by the controller. It is not required to declare a view or model but a controller is of questionable usefulness without it.

The controller is where you define any events you are interested in handling (see add_listener) as well as two special events, the pressing of the “close” button of the window (see close_action), and the updating of the MVC tuple (see update_method). Handlers are methods named according to a certain convention that are the recipient of events. Handlers are named <component_event>. No events are actually generated and sent to the controller unless a listener has been added for that component, however, component-specific handlers will automatically add a listener for that component when the class is instantiated. Therefore a method named ok_button_action_performed would be the equivalent of

add_listener :type => :action, :components => ["ok_button"]

These automatic listener registrations work for any component that can be resolved directly in your view. In the above example, the view could contain a component named ok_button, okButton or even OkButton and the listener would be added correctly. If you have a nested component such as text_field.document then you will need to use an explicit add_listener registration.

Handler methods can optionally take one parameter which is the event generated by Swing. This would look like

def some_component_event_name(swing_event)

Automatic listeners can also be decorated with a trailing ! to indicate that the update_view method should automatically be run on completion of the callback being run from a Swing event. In the previous example

def some_component_event_name!
end

is the equivalent of

def some_component_event_name
  update_view
end

While an event handler is running, the Swing Event Dispatch Thread (usually called the EDT) is blocked and as such, no repaint events will occur and no new events will be proccessed. If you have a process that is long running, but you don’t want to make asynchronous by spawning a new thread, you can use the repaint_while method which takes a block to execute while still allowing Swing to process graphical events (but not new interaction events like mouse clicking or typing).

def button_action_performed
  repaint_while do
    sleep(20) # the gui is still responsive while we're in here sleeping
  end
  model.text = "done sleeping!"
  update_view
end

Example of a controller, this assumes the existance of a Ruby class named MyModel that has an attribute named user_name that is mapped to a field on a subclass of Monkeybars::View named MyView that has a button named “ok_button” and a text field called user_name

require 'monkeybars'

class MyController < Monkeybars::Controller
  set_view :MyView
  set_model :MyModel

  close_action :exit

  def ok_button_mouse_released
    puts "The user's name is: #{view_state.model.user_name}"
  end
end

It is important that you do not implement your own initialize and update methods, this will interfere with the operation of the Controller class (or if you do be sure to call super as the first line).

Public Class Methods

active_controllers() click to toggle source

Returns a frozen hash of ControllerName => [instances] pairs. This is useful if you need to iterate over all active controllers to call update or to check for a status.

# NOTE: Controllers that have close called on them will not show up on this list, even if open is subsequently called. If you need a window to remain in the list but not be updated when not visible you can do:

Monkeybars::Controller.active_controllers.values.flatten.each{|c| c.update if c.visible? }
# File lib/monkeybars/controller.rb, line 219
def self.active_controllers
  @@instance_list.clone.freeze     
end
add_listener(details) click to toggle source
# File lib/monkeybars/controller.rb, line 227
def self.add_listener(details)
  original_add_listener(details)
  hide_protected_class_methods #workaround for JRuby bug #1283
end
Also aliased as: original_add_listener
create_instance() click to toggle source

Always returns a new instance of the controller.

controller1 = MyController.create_instance
controller2 = MyController.create_instance
...
controller32 = MyController.create_instance
# File lib/monkeybars/controller.rb, line 126
def self.create_instance
  new
end
instance() click to toggle source

Controllers cannot be instantiated via a call to new, instead use instance to retrieve the instance of the view. Currently only one instance of a controller is created but in the near future a configurable limit will be available so that you can create n instances of a controller.

# File lib/monkeybars/controller.rb, line 108
def self.instance
  @@instance_lock[self].synchronize do
    controller = @@instance_list[self]
    unless controller.empty?
      controller.last
    else
      __new__
    end
  end
end
new() click to toggle source
# File lib/monkeybars/controller.rb, line 245
def initialize
  @model_has_block = false
  @view_has_block = false
  @__model = create_new_model unless self.class.model_class.nil?
  self.class.model_class.last.call(@__model) if @model_has_block
  @__view = create_new_view unless self.class.view_class.nil?
  self.class.view_class.last.call(@__view) if @view_has_block
  @__transfer = {}
  @__view_state = nil
  setup_implicit_and_explicit_event_handlers
  
  action = close_action
  unless [:nothing, :close, :exit, :dispose, :hide].include?(action)
    raise "Unknown close action: #{action}.  Only :nothing, :close, :exit, :dispose, and :hide are supported"
  end
  
  window_type = if @__view.instance_variable_get(:@main_view_component).kind_of? javax.swing.JInternalFrame
    "internalFrame"
  else
    "window"
  end
  
  unless @__view.nil?
    @__view.close_action(Monkeybars::View::CloseActions::METHOD, MonkeybarsWindowAdapter.new(:"#{window_type}Closing" => self.method(:built_in_close_method)))
  end
  
  @closed = true
end
original_add_listener(details) click to toggle source
Alias for: add_listener
set_close_action(action) click to toggle source

Valid close actions are

  • :nothing

  • :close (default)

  • :exit

  • :dispose

  • :hide

  • action :nothing - close action is ignored, this means you cannot close the window unless you provide another way to do this

  • action :close - calls the controller’s close method

  • action :exit - closes the application when the window’s close button is pressed

  • action :dispose - default action, calls Swing’s dispose method which will release the resources for the window and its components, can be brought back with a call to show

  • action :hide - sets the visibility of the window to false

# File lib/monkeybars/controller.rb, line 206
def self.set_close_action(action)
  self.send(:class_variable_set, :@@close_action, action)
end
set_model(model=nil, &block) click to toggle source

The declared model class is auto-required prior to the class being instantiated. It is not a requirement that you have a model, Monkeybars will operate without one as long as you do not attempt to use methods that interact with the model.

The set_model method may be used in 3 different ways.

The first and most common use is to pass the name of the model class as a string. Internally the controller instantiates this class and makes it available to the controller via a private method model. Note that this form of set_model does not allow passing any parameters to the model class, thus the model must implement a zero-argument constructor.

class FooController < ApplicationController
  set_model 'FooModel'
  set_view 'FooView'
  set_close_action :exit
end

The second format passes a block to set_model. The block is executed via instance_eval with the result assigned as the model. The construction of the model is fully under your control.

class FooController < ApplicationController
  set_model { FooModel.new("arg1", "arg2", "arg3") }
  set_view 'FooView'
  set_close_action :exit
end

The third format takes both a string with the model class name and a block for the purpose of setting initial values. The block will be passed the model object. No parameters may be passed to the constructor in this format.

class FooModel
  attr_accessor :some_value
end

class FooController < Monkeybars::Controller
  set_model "FooModel" do |model|
    model.some_value = 5
  end
end
# File lib/monkeybars/controller.rb, line 172
def self.set_model(model=nil, &block)
  self.model_class = [model,block]
end
set_update_method(method) click to toggle source

Declares a method to be called whenever the controller’s update method is called.

# File lib/monkeybars/controller.rb, line 184
def self.set_update_method(method)
  raise "Argument must be a symbol" unless method.kind_of? Symbol
  raise "'Update' is a reserved method name" if :update == method
  self.send(:class_variable_set, :@@update_method_name, method)
end
set_view(view=nil, &block) click to toggle source

See Controller.set_model. Uses same 3 options for declaring the view to use and for optionally supplying a block or both a class and a block.

# File lib/monkeybars/controller.rb, line 179
def self.set_view(view=nil, &block)
  self.view_class = [view,block]
end

Public Instance Methods

add_nested_controller(name, sub_controller) click to toggle source

Nests a controller under this controller with the given key

def add_user_button_action_performed
  @controllers << UserController.create_instance
  add_nested_controller(:user_list, @controllers.last)
  @controllers.last.open
end

This forces the view to perform its nesting. See also Monkeybars::Controller#remove_nested_controller

# File lib/monkeybars/controller.rb, line 329
def add_nested_controller(name, sub_controller)
  nested_view = sub_controller.instance_variable_get(:@__view)
  @__view.add_nested_view(name, nested_view, nested_view.instance_variable_get(:@main_view_component), model, transfer)
end
close() click to toggle source

Hides the view and unloads its resources

# File lib/monkeybars/controller.rb, line 377
def close
  @closed = true          
  @__view.unload unless @__view.nil?
  unload
  @__view.dispose if @__view.respond_to? :dispose
  @@instance_lock[self.class].synchronize do
    @@instance_list[self.class].delete self
  end
end
closed?() click to toggle source

True if close has been called on the controller

# File lib/monkeybars/controller.rb, line 372
def closed?
  @closed
end
dispose() click to toggle source

Disposes the view

# File lib/monkeybars/controller.rb, line 362
def dispose
  @__view.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/controller.rb, line 290
def focused?
  @__view.focused?
end
hide() click to toggle source

Hides the view

# File lib/monkeybars/controller.rb, line 357
def hide
  @__view.hide
end
load(*args) click to toggle source

Stub to be overriden in sub-class. This is where you put the code you would normally put in initialize, it will be called the first time open is called on the controller.

# File lib/monkeybars/controller.rb, line 411
def load(*args); end
open(*args, &block) click to toggle source

Calls load if the controller has not been opened previously, then calls update_view and shows the view.

# File lib/monkeybars/controller.rb, line 389
def open(*args, &block)
  @@instance_lock[self.class].synchronize do
    unless @@instance_list[self.class].member? self
      @@instance_list[self.class] << self
    end
  end
  
  if closed?
    load(*args, &block) 
    @__view.on_first_update(model, transfer)
    clear_view_state
    @closed = false
  end
  
  show
  
  self #allow var assignment off of open, i.e. screen = SomeScreen.instance.open
end
remove_nested_controller(name, sub_controller) click to toggle source

Removes the nested controller with the given key This does not do any cleanup on the nested controller’s instance.

def remove_user_button_action_performed
  remove_nested_controller(:user_list, @controllers.last)
  UserController.destroy_instance @controllers.last
  @controllers.delete @controllers.last
end

This performs the view’s nesting. See also Monkeybars::Controller#add_nested_controller

# File lib/monkeybars/controller.rb, line 346
def remove_nested_controller(name, sub_controller)
  nested_view = sub_controller.instance_variable_get(:@__view)
  @__view.remove_nested_view(name, nested_view, nested_view.instance_variable_get(:@main_view_component), model, transfer)
end
show() click to toggle source

Shows the view

# File lib/monkeybars/controller.rb, line 367
def show
  @__view.show         
end
signal(signal_name, &callback) click to toggle source
Sends a signal to the view.  The view will process the signal (if it is
defined in the view via View.define_signal) and optionally invoke the

callback that is passed in as a block.

This is useful for communicating one off events such as a state transition

  def update
    signal(:red_alert) if model.threshold_exceeded?
  end
# File lib/monkeybars/controller.rb, line 316
def signal(signal_name, &callback)
  @__view.process_signal(signal_name, model, transfer, &callback)
end
unload() click to toggle source

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

# File lib/monkeybars/controller.rb, line 414
def unload; end
update() click to toggle source

Calls the method that was set using Controller.set_update_method. If no method has been set defined, this call is ignored.

# File lib/monkeybars/controller.rb, line 294
def update
  if self.class.class_variables.member?("@@update_method_name")
    method_name = self.class.send(:class_variable_get, :@@update_method_name) 
    send(method_name)
  end
end
update_view() click to toggle source

Triggers updating of the view based on the mapping and the current contents of the model and the transfer

# File lib/monkeybars/controller.rb, line 303
def update_view
  @__view.update(model, transfer)
end
visible?() click to toggle source

Returns true if the view is visible, false otherwise

# File lib/monkeybars/controller.rb, line 352
def visible?
  @__view.visible?
end

Private Instance Methods

clear_view_state() click to toggle source

Resets memoized view_state value. This is called automatically after each event so it would only need to be called if view_state is used outside of an event handler.

# File lib/monkeybars/controller.rb, line 497
def clear_view_state # :doc:
  @__view_state = nil
end
model() click to toggle source

Returns the model object. This is the object that is passed to the view when update_view is called. This model is not the same model that you get from view_state. Values that you want to propogate from the view_state model to this model can be done using update_model.

# File lib/monkeybars/controller.rb, line 429
def model #:doc:
  @__model
end
transfer() click to toggle source

Returns the transfer object which is a transient hash passed to the view whenever update_view is called. The transfer is cleared after each call to update_view. The transfer is used to pass data to and from the view that is not part of your model. For example, if you had a model that was an ActiveRecord object you would probably not want to put things like the currently selected item into your model. That data could instead be passed as a value in the transfer.

transfer[:selected_framework] = "monkeybars"

Then in your view, you could use that transfer value to select the correct value out of a list.

map :view => "framework_list.selected_item", :transfer => :selected_framework

See View#map for more details on the options for your mapping.

# File lib/monkeybars/controller.rb, line 449
def transfer #:doc:
  @__transfer      
end
update_model(source, *properties) click to toggle source

This method is almost always used from within an event handler to propogate the view_state to the model. Updates the model from the source provided (typically from view_state). The list of properties defines what is modified on the model.

def ok_button_action_perfomed
  update_model(view_state.model, :user_name, :password)
end

This would have the same effect as:

model.user_name = view_state.model.user_name
model.password = view_state.model.password
# File lib/monkeybars/controller.rb, line 514
def update_model(source, *properties) # :doc:
  update_provided_model(source, @__model, *properties)
end
update_provided_model(source, destination, *properties) click to toggle source

This method works just like Controller#update_model except that the target is not implicitly the model. The second parameter is a target object for the properties to be propogated to. This is useful if you have a composite model or need to updated other controllers.

def ok_button_action_perfomed
  update_provided_model(view_state.model, model.user, :user_name, :password)
end

This would have the same effect as:

model.user.user_name = view_state.model.user_name
model.user.password = view_state.model.password
# File lib/monkeybars/controller.rb, line 531
def update_provided_model(source, destination, *properties) # :doc:
  properties.each do |property|
    destination.send("#{property}=", source.send(property))
  end
end
view_model() click to toggle source

Equivalent to view_state.model

# File lib/monkeybars/controller.rb, line 485
def view_model # :doc:
  view_state.model
end
view_state() click to toggle source

Returns a ViewState object which contains a model and a transfer hash of the view’s current contents as defined by the view’s mappings. This is for use in event handlers. The contents of the model and transfer are not the same as the contents of the model and transfer in the controller, they are new objects created when view_state was called. If you wish to propogate the values from the view state’s model into the actual model, you must do this yourself. A helper method update_model is provided to make this easier. In an event handler this method is thread safe as Swing is single threaded and blocks any modification to the GUI while the handler is being proccessed.

The view state object has two properties, model and transfer.

def ok_button_action_performed
  if view_state.transfer[:foo] == :bar
    model.baz = view_model.baz
  end
end

Any subsequent call to view_state will return the same object, that is, this method is memoized internally. At the end of each event (after all handlers have been called) the memoized view state is cleared. If you call view_state outside of an event handler it is important that you clear the view state yourself by calling clear_view_state.

# File lib/monkeybars/controller.rb, line 476
def view_state # :doc:
  return @__view_state unless @__view_state.nil?
  model = self.class.model_class.nil? ? nil : create_new_model
  transfer = {}
  @__view.write_state(model, transfer)
  @__view_state = ViewState.new(model, transfer)
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.