howto-extend-arcadia

Path: howto-extend-arcadia
Last Update: Tue Jan 29 00:19:04 +0100 2008

How to extend Arcadia

by Antonio Galeone on Jan 25, 2008

Introduction

Arcadia architecture in based on "observer" pattern and on extensions oriented modulare approach that is the way to enhance arcadia. In order to avoid tightly coupling between objects (like Extensions) and to permit collaboration on same resourses it is realized through an event driven framework.

The talker (extension) creates the event and raises it through an EvenBus on which other objects(extensions) can register a listener.

  (fig. 1) Arcadia architecture

Events

There are two ways to raise an Event depending on nature of notification:

  1. Process an event
  2. Broadcast an event

The first method is for synchronous collaboration on event and requires three phases:

  • pre-processing
  • processing
  • post-processing.

The event can change state through processing and is passed between listeners in synchronous way, the talker takes the control again after the processing phases.

To raise this kind of event you must use:

  _event=Arcadia.process_event(EventClass.new(self, args))

The EventClass constructor has two parameters:

  • the caller
  • a hash of event properties

ex: to open the file "~/myfile.rb" you must write:

  Arcadia.process_event(Open BufferEvent.new(self,'file'=>"~/myfile.rb"))

the listeners receive this instance of OpenBufferEvent with the property "file" assigned:

  ...
  def on_open_buffer(_event)
    do_something(_event.file)
  end

  (fig. 2) Process event sequence

The second way is a one shot asynchronous notification used to give info on a happened fact (the name of this event must be represented by a verb in the past). The talker immediately gets control and is not concerned to a state change.

To raise this kind of event you must use:

  Arcadia.broadcast_event(_caller, _event)

  (fig. 3) Broadcast event sequence

Common Events to use are defined in a-contract.rb file and are the point to sharing. In fact these Events are builded around to common entities like buffer,debug, message etc that the extensions can use.

A common used resource in a Ide is BufferEvent‘s hierarchy ex. EditorExtension, FilehistoryExtension, DebugEstension etc. are interested to the Buffer events family, if you open a new file into the editor the Filehistory must register an entry in his business logic, on the other side the debuger extension must comunicate the current debugging line so the editor can raise this line. A particolar event is ActionEvent used to sending a particular message to a particular receiver , it can be used in the case the receiver had also defined the ActionEvent. it is used for example for add a custom toolbar button or a menu’ item action.

At example the listener on FooEvent is a class that can respond to this methods corrisponding to event processing phases:

  Class FooEventListener
    def on_before_foo(_event)
      {...}
    end
    def on_foo(_event)
           {...}
    end
    def on_after_foo(_event)
      {...}
    end
  end

The conventions for listener methods are :

  • on_<phase_><event name without final Event word>(<event instance>) (ex. BufferEvent => def on_buffer(_event))
  • All letter are downcase
  • capitalize letter are prepending by "_" (ex. OpenBufferEvent => on_open_buffer)

All phases methods of listener are optional, you must write only if you used it.

The listener isn‘t a particular class, can be simply the same Extension.

To register itself on EventBus in order to receive notification on FooEvent the extension must be use this class method:

  Arcadia.attach_listener(FooEvent, xlistener)

to unregist it must use

  Arcadia.detach_listener(FooEvent, xlistener))

Through this registration listener receive notification on FooEvent and his subclass hierarchy.

ex. if is declared:

  class BarEvent < FooEvent
  end

and the listener xl register:

  Arcadia.attach_listener(FooEvent, xl)

and are raised:

   Arcadia.process_event(BarEvent.new(self))
   Arcadia.process_event(FooEvent.new(self))

the method listener:

  def on_foo(_event)
  end

receive _event both of class BarEvent and FooEvent

Extensions

Extensions are the way to extend Arcadia. Arcadia-core raise same system Events on which extensions are registered that mark the life cycle of extensions. Those events are :

  • BuildEvent
  • ExitQueryEvent
  • FinalizeEvent

The first is raised at initialization time, the other two at Arcadia quit moment. So an extension can be defined as a listener on these events. An important require is that an Extension should not be interesting to his initialize phase. So no initialize method must be implemented :his real initialization must be at BuildEvent moment (in particular at on_build method) because the initialize phase of class must stay at arcadia-core control.

Pragmatic Steps to write an extension

  1. create a directory under home dir/.arcadia (on release moment under ext dir) (ex. ae-new)
  2. create a config file with name equal to this directory (ae-new.conf) this file is destinate to contain configuration properties of extension but must contain at least these system property:
          name=<the name of ext>
          require=<the file that contain the ext class implementation>
          class=<ext class>
          active=<yes|no>
    
  3. create a ruby file than contain extension class:
         ext class is a sub class of ArcadiaExtension
    

Extension class structure can be:

  class NewExtension < ArcadiaExtension

    def on_before_build(_event)
    end

    def on_build(_event)
    end

    def on_exit_query(_event)
    end

    def on_finalize(_event)
    end

  end

Normally in on_before_build method you must register to receive other Event notification: Ex.:

  class NewExtension < ArcadiaExtension
    def on_before_build(_event)
        Arcadia.attach_listener(FooEvent, self)
    end

    def on_build(_event)
        # in this method the initialization of extension
    end
    ...
    def on_before_foo(_event)
        {...}
    end

    def on_foo(_event)
        {...}
    end
    ...
  end

..note that i have register extension same as listener

In on_build must be realized the costruction of extension

The other two event on witch an extension is register:

  ExitQueryEvent is used by extension to comunicate that arcadia
  do not be quit.(setting _event.can_exit=false)
  FinalizeEvent is used for finalize the extension before destroy it.

Services for Extensions

UI Containers

The ui of an Extension is made using two kind of container:

  1. static frames
  2. float frames
  • Static frame are stabilited at global level by arcadia layout:at this moment thare are this static frame identified by this point coordinates:0,0 0,1 1,0.

  (fig. 4) Static frames layout

To use it you must:

  1. declare in <ext>.conf file these properties:
         frame=<comma separed list of points for static frame>
         labels=<list of titles of tratic frame>
    
  2. In extension class use:
  self.frames(_n=0) to obtain a FixedFrameWrapper instance
  where _n is the index of the configured list
  • Float frames are movabled frame caracterized by this geometry parameter: WIDTH<%>xHEIGHT<%><+|->X<%><+|->Y<%>

ex.

  • 100x200+20+20 for absolute value or
  • 50%x50%+25%+25% for percentual value or
  • 150x25%+25%+55% for a mix

symbol % indicates that the number are a percentual of layout_width(for widht or x) or layout_heigh(for height or y)

To use it you must:

  1. define in <ext>.conf file this properties:
         float_frames=<list of geometry for float frame>
         float_labels=<list of titles of float frame>
    
  2. use declased frames calling:
  float_frames(_n=0)
  where _n is the index of the configured list
  methods that return a  FloatFrameWrapper

frames and float_frames responds to this methods:

  * hinner_frame
    return a TkFrame instance (the client frame)
  * title(_title)
    to modify the frame title
  * show
    to show a float_frame
  * hide
    to hide a float_frame
  * free
    to destroy the frame

… So you have to use tkframe as container to build ui for Extension.

ex:

  def on_build(_event)
    @text = TkText.new(frames.hinner_frame).place('x'=>0,'y'=>0,'relheight'=>1,'relwidth'=>1)
  end

Messages

For comunication with user you can use this api:

  1. Console messages
  Arcadia.console(_sender, _args)

  where _args is an hash with this keys:
  * msg (= the text of message)
  * level (= one of : 'info', 'debug', 'error') default 'info'

  ex. Arcadia.console(self, 'msg'=>'Hello World')
  1. Modal Ui messages
  _a = Arcadia.dialog(_sender, _args)

  where _args is an hash with this keys:
  * type (= one of : 'ok', 'ok_cancel', 'yes_no', 'yes_no_cancel', 'abort_retry_ignore')
  * msg (= the text of message)
  * title (= the title of message)
  * level (= one of : 'info', 'question', 'warning', 'error') default 'info'

  return value _a is the value of button clicked (ex. 'ok')

  ex. if  Arcadia.dialog(self, 'type'=>'yes_no', 'msg'=>'Hello World?') == 'yes'
        # ...
      end

Properties

Extensions can read two kind of config properties :

  1. Local properties of his conf file calling:
  conf(_property_name)
  1. Global properties calling
  Arcadia.conf(_property_name)

The convention for properties are:

  property=value

optionally you can difference the same property in accord to platform prepending platform specific string::

es:

  font=courier 11
  freebsd::font=courier 12
  win::font={Courier New} 9

the first assignment is for default value

Instead of value you can put a link to other property (in particular global) through this syntax:

  property=>>>other_property

Common properties for tk widget

In order to use different themes the widgets must have a common properties.

  • Arcadia.style(_style_class_name) return an hash of tk properties

Actually are defined this _style_class_name:

  * menu
  * panel
  * label
  * titlelabel
  * scrollbar
  * meterpanel
  * splitter
  * tabpanel
  * treepanel
  * treeitem
  * button
  * toolbar
  * toolbarbutton
  * checkbox
  * edit
  * combobox

For example if you create a TkText:

 my_text = TkText.new(my_frame, Arcadia.style('edit')){
   #... other propeties or the same to override es font bold
 }

User Interaction

Extention can define custom user interaction item.

Actually arcadia provides two type of interaction:

  1. custom toolbar item
  2. custom menu item

This items are defined into <ext>.conf file in this way:

  <user_interaction_kind>.contexts=<list of groups of item>
  <user_interaction_kind>.caption=<list of titles of contexts>
  <user_interaction_kind>.<context>.context_path=<path between context and item>
  <user_interaction_kind>.<context> = <list of context item>
  <user_interaction_kind>.<context>.<item>.name=<item name>
  <user_interaction_kind>.<context>.<item>.caption=<item title>
  <user_interaction_kind>.<context>.<item>.hint=<item tooltip>
  <user_interaction_kind>.<context>.<item>.image_data=<item image data>
  <user_interaction_kind>.<context>.<item>.event_class=<Event to raise on click item event>
  <user_interaction_kind>.<context>.<item>.event_args=<hash of event argoments>

where <user_interaction_kind> is user_toolbar or user_menu.

[Validate]