| Path: | howto-extend-arcadia |
| Last Update: | Tue Jan 29 00:19:04 +0100 2008 |
by Antonio Galeone on Jan 25, 2008
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
There are two ways to raise an Event depending on nature of notification:
The first method is for synchronous collaboration on event and requires three phases:
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:
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 :
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 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 :
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.
name=<the name of ext>
require=<the file that contain the ext class implementation>
class=<ext class>
active=<yes|no>
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.
The ui of an Extension is made using two kind of container:
(fig. 4) Static frames layout
To use it you must:
frame=<comma separed list of points for static frame>
labels=<list of titles of tratic frame>
self.frames(_n=0) to obtain a FixedFrameWrapper instance where _n is the index of the configured list
ex.
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:
float_frames=<list of geometry for float frame>
float_labels=<list of titles of float frame>
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
For comunication with user you can use this api:
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')
_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
…
Extensions can read two kind of config properties :
conf(_property_name)
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
In order to use different themes the widgets must have a common 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
}
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.