if ( $obj-> isa('Prima::Component')) {
# set and get a property
my $name = $obj-> name;
$obj->name( 'an object' );
# set a notification callback
$obj-> onPostMessage( sub {
shift;
print "hey! I've received this: @_\n";
});
# can set multiple properties. note, that 'name' and 'owner',
# replace the old values, while onPostMessage are aggregated.
$obj-> set(
name => 'AnObject',
owner => $new_owner,
onPostMessage => sub {
shift;
print "hey! me too!\n";
},
);
# de-reference by name
$new_owner-> AnObject-> post_message(1,2);
}
Prima::Component-> create( .. parameters ... );
call. This document describes the basic concepts of the OO programming with Prima toolkit. Although Component has wider functionality than Object, all examples will be explained on Component, since Object has no descendant classes and all the functionality of Object is present in Component. Some of the information here can be found in Prima::internals as well, the difference is that Prima::internals considers the coding tasks from a C programmer's view, whereas this document is wholly about perl programming.
$new_object = Class-> create(
parameter => value,
parameter => value,
...
);
Parameters and values form a hash, which is passed to the create() method. This hash is applied to a default parameter-value hash ( a profile ), specific to every Prima class. The object creation is performed in several stages.
name => ref $_[ 0],
owner => $::application,
delegations => undef,
While the exact meaning of these parameters is described later, in ``Properties'', the idea is that a newly created object will have 'owner' parameter set to '$::application' and 'delegations' to undef etc etc - unless these parameters are explicitly passed to create(). Example:
$a1 = Prima::Component-> create();
$a1's owner will be $::application
$a2 = Prima::Component-> create( owner => $a1);
$a2's owner will be $a1. The actual merging of the default and the parameter hashes is performed on the next stage, in profile_check_in() method which is called inside profile_add() method.
Another example can be taken from Prima::Widget::profile_check_in(). Prima::Widget horizontal position can be specified by using basic "left" and "width" parameters, and as well by auxiliary "right", "size" and "rect". The default of both "left" and "width" is 100. But if only "right" parameter, for example, was passed to create() it is profile_check_in() job to determine "left" value, given that "width" is still 100.
After profiles gets merged, the resulting hash is passed to the third stage, init().
Note: usually init() attaches the object to its owner in order to keep the newly-created object instance from being deleted by garbage-collection mechanisms. More on that later ( see ``Links between objects'').
After init() finishes, create() calls setup() method
As can be noticed from the code pieces above, a successful create() call returns a newly created object. If an error condition occurred, undef is returned. It must be noted, that only errors that were generated via die() during init() stage result in undef. Other errors raise an exception instead. It is not recommended to frame create() calls in an "eval{}" block, because the error conditions can only occur in two situations. The first is a system error, either inside perl or Prima guts, and not much can be done here, since that error can very probably lead to an unstable program and almost always signals an implementation bug. The second reason is a caller's error, when an unexistent parameter key or invalid value is passed; such conditions are not subject to a runtime error handling as are not the syntax errors.
After create(), the object is subject to the event flow. As "onCreate" event is the first event the object receives, only after that stage other events can be circulated.
"onDestroy" is the last event the object sees. After cleanup() no events are allowed to circulate.
The typical conditions that lead to object destructions are direct destroy() call, garbage collections mechanisms, user-initiated window close ( on "Prima::Window" only ), and exception during init() stage. Thus, one must be careful implementing done() which is called after init() throws an exception.
$object-> method();
and
method( $object);
The actual code is a sub, located under the object class package. The overloaded methods that call their ancestor code use
$object-> SUPER::method();
syntax. Most Prima methods have fixed number of parameters.
The property returns a value if no parameters ( except the object) are passed, and changes the internal data to the passed parameters otherwise. Here's a sketch code for "::name" property implementation:
sub name
{
return $_[0]-> {name} unless $#_;
$_[0]->{name} = $_[1];
}
There are many examples of properties throughout the toolkit. Not all properties deal with scalar values, some accept arrays or hashes as well. The properties can be set-called not only by name like
$object-> name( "new name");
but also with set() method. The set() method accepts a hash, that is much like to create(), and assigns the values to the corresponding properties. For example, the code
$object-> name( "new name"); $object-> owner( $owner);
can be rewritten as
$object-> set(
name => "new name",
owner => $owner
);
A minor positive effect of a possible speed-up is gained by eliminating C-to-perl and perl-to-C calls, especially if the code called is implemented in C. The negative effect of such technique is that the order in which the properties are set, is undefined. Therefore, the usage of set() is recommended either when the property order is irrelevant, or it is known beforehand that such a call speeds up the code, or is an only way to achieve the result. An example of the latter case from Prima::internals shows that Prima::Image calls
$image-> type( $a);
$image-> palette( $b);
and
$image-> palette( $b);
$image-> type( $a);
produce different results. It is indeed the only solution to call for such a change using
$image-> set(
type => $a,
palette => $b
);
when it is known beforehand that "Prima::Image::set" is aware of such combinations and calls neither "::type" nor "::palette" but performs another image conversion instead.
Some properties are read-only and some are write-only. Some methods that might be declared as properties are not; these are declared as plain methods with get_ or set_ name prefix. There is not much certainty about what methods are better off being declared as properties and vice versa.
However, if get_ or set_ methods cannot be used in correspondingly write or read fashion, the R/O and W/O properties can. They raise an exception on an attempt to do so.
Prima::Component::owner property maintains this relation, and is writable - the object can change its owner dynamically. There is no corresponding property that manages children objects, but is a method get_components(), that returns an array of the child references.
The owner-child relationship is used in several ways in the toolkit. For example, the widgets that are children of another widget appear ( usually, but not always ) in the geometrical interior of the owner widget. Some events ( keyboard events, for example ) are propagated automatically up and/or down the object tree. Another important feature is that when an object gets destroyed, its children are destroyed first. In a typical program the whole object tree roots in a Prima::Application object instance. When the application finishes, this feature helps cleaning up the widgets and quitting gracefully.
Implementation note: name 'owner' was taken instead of initial 'parent', because the 'parent' is a fixed term for widget hierarchy relationship description. Prima::Widget relationship between owner and child is not the same as GUI's parent-to-child. The parent is the widget for the children widgets located in and clipped by its inferior. The owner widget is more than that, its children can be located outside its owner boundaries.
The special convenience variety of create(), the insert() method is used to explicitly select owner of the newly created object. insert() can be considered a 'constructor' in OO-terms. It makes the construct
$obj = Class-> create( owner => $owner, name => 'name);
more readable by introducing
$obj = $owner-> insert( 'Class', name => 'name');
scheme. These two code blocks are identical to each other.
There is another type of relation, where objects can hold references to each other. Internally this link level is used to keep objects from deletion by garbage collection mechanisms. This relation is many-to-many scheme, where every object can have many links to other objects. This functionality is managed by attach() and detach() methods.
Prima defines also a non-object event dispatching and filtering mechanism, available through ``event_hook'' static method.
$obj-> notify("PostMessage", $data1, $data2);
call is issued for all these examples.
sub on_postmessage
{
my ( $self, $data1, $data2) = @_;
...
}
The callback name is a modified lower-case event name: the name for Create event is on_create, PostMessage - on_postmessage etc. These methods can be overloaded in the object's class descendants. The only note on declaring these methods in the first instance is that no "::SUPER" call is needed, because these methods are not defined by default.
Usually the direct methods are used for the internal object book-keeping, reacting on the events that are not designed to be passed higher. For example, a Prima::Button class catches mouse and keyboard events in such a fashion, because usually the only notification that is interesting for the code that employs push-buttons is "Click". This scheme is convenient when an event handling routine serves the internal, implementation-specific needs.
$obj-> delegations([ $owner, 'PostMessage']);
where the actual callback sub will be
sub Obj_PostMessage
{
my ( $self, $obj, $data1, $data2) = @_;
}
Note that the naming style is different - the callback name is constructed from object name ( let assume that $obj's name is 'Obj') and the event name. ( This is one of the reasons why Component::profile_check_in() performs automatic naming of newly created onbjects). Note also that context objects are $self ( that equals $owner ) and $obj.
The delegated methods can be used not only for the owner-child relations. Every Prima object is free to add a delegation method to every other object. However, if the objects are in other than owner-child relation, it is a good practice to add Destroy notification to the object which events are of interest, so if it gets destroyed, the partner object gets a message about that.
Contrary to the usual OO event implementations, when only one routine per class dispatches an event, and calls inherited handlers when it is appropriate, Prima event handling mechanism can accept many event handlers for one object ( it is greatly facilitated by the fact that perl has anonymous subs, however).
All the callback routines are called when an event is triggered, one by one in turn. If the direct and delegated methods can only be multiplexed by the usual OO inheritance, the anonymous subs are allowed to be multiple by the design. There are three syntaxes for setting such a event hook; the example below sets a hook on $obj using each syntax for a different situation:
- during create():
$obj = Class-> create(
...
onPostMessage => sub {
my ( $self, $data1, $data2) = @_;
},
...
);
- after create using set()
$obj-> set( onPostMessage => sub {
my ( $self, $data1, $data2) = @_;
});
- after create using event name:
$obj-> onPostMessage( sub {
my ( $self, $data1, $data2) = @_;
});
As was noted in Prima, the events can be addressed as properties, with the exception that they are not substitutive but additive. The additivity is that when the latter type of syntax is used, the subs already registered do not get overwritten or discarded but stack in queue. Thus,
$obj-> onPostMessage( sub { print "1" });
$obj-> onPostMessage( sub { print "2" });
$obj-> notify( "PostMessage", 0, 0);
code block would print
21
as the execution result.
This, it is a distinctive feature of a toolkit is that two objects of same class may have different set of event handlers.
$obj-> onPostMessage( sub { print "1" });
$obj-> onPostMessage( sub { print "2" });
$obj-> notify( "PostMessage", 0, 0);
results in 21, not 12 because PostMessage event type is prototyped "nt::FluxReverse".
nt::Single nt::Multiple nt::Event
These constants are mutually exclusive, and may not appear together in an event type declaration. A "nt::Single"-prototyped notification calls only the first ( or the last - depending on order and direction bits ) callback. The usage of this constant is somewhat limited.
In contrary of "nt::Single", the "nt::Multiple" constant sets the execution control to call all the available callbacks, with respect to direction and order bits.
The third constant, "nt::Event", is the impact as "nt::Multiple", except that the event flow can be stopped at any time by calling clear_event() method.
Although there are 12 possible event type combinations, a half of them are not viable. Another half were assigned to unique more-less intelligible names:
nt::Default ( PrivateFirst | Multiple | FluxReverse) nt::Property ( PrivateFirst | Single | FluxNormal ) nt::Request ( PrivateFirst | Event | FluxNormal ) nt::Notification ( CustomFirst | Multiple | FluxReverse ) nt::Action ( CustomFirst | Single | FluxReverse ) nt::Command ( CustomFirst | Event | FluxReverse )
Implementation note: a call of clear_event() inside a "nt::Event"-prototyped event call does not automatically stops the execution. The execution stops if the state value equals to 0 after the callback is finished. A ::eventFlag(1) call thus cancels the effect of clear_event().
A particular coding style is used when the event is "nt::Single"-prototyped and is called many times in a row, so overheads of calling notify() become a burden. Although notify() logic is somewhat complicated, it is rather simple with "nt::Single" case. The helper function get_notify_sub() returns the context of callback to-be-called, so it can be used to emulate notify() behavior. Example:
for ( ... ) {
$result = $obj-> notify( "Measure", @parms);
}
can be expressed in more cumbersome, but efficient code if "nt::Single"-prototyped event is used:
my ( $notifier, @notifyParms) = $obj-> get_notify_sub( "Measure" );
$obj-> push_event;
for ( ... ) {
$notifier-> ( @notifyParms, @parms);
# $result = $obj-> eventFlag; # this is optional
}
$result = $obj-> pop_event;
print $obj-> name if Prima::Object::alive( $obj);
$obj = Class-> create( PARAMETERS); $obj = Prima::Object::create( "class" , PARAMETERS);
Is never called in an object context.
Alias: new()
Note: the eventual child objects are destroyed inside done() call.
$obj = $owner-> insert( 'Class', name => 'name');
is adequate to
$obj = Class-> create( owner => $owner, name => 'name);
code. insert() has another syntax that allows simultaneous creation of several objects:
@objects = $owner-> insert(
[ 'Class', %parameters],
[ 'Class', %parameters],
...
);
With such syntax, all newly created objects would have $owner set to their 'owner' properties.
Can be called in a context of class.
sub set
{
my $obj = shift;
my %PARAMETERS = @_;
$obj-> $_( $PARAMETERS{$_}) for keys %PARAMETERS;
}
code. Assigns object properties correspondingly to PARAMETERS hash. Many Prima::Component descendants overload set() to make it more efficient for particular parameter key patterns.
As the code above, raises an exception if the key in PARAMETERS has no correspondent object property.
INDEX is a desired insert position in the notification list. By default it is -1, what means 'in the start'. If the notification type contains nt::FluxNormal bit set, the newly inserted SUB will be called first. If it has nt::FluxReverse, it is called last, correspondingly.
Returns positive integer value on success, 0 on failure. This value can be later used to refer to the SUB in remove_notification().
See also: "remove_notification", "get_notification".
See also: "detach".
$obj-> name( "Obj"); $obj-> owner( $owner); ... $owner-> Obj-> destroy;
See also: ``Events'', "push_event", "pop_event", "::eventFlag", "notify".
See also: "attach"
If no SUB is set, returns currently installed event hook pointer. If SUB is set, replaces the old hook sub with SUB. If SUB is 'undef', event filtering is not used.
Since the 'event_hook' mechanism allows only one hook routine to be installed at a time, direct usage of the method is discouraged. Instead, use Prima::EventHook for multiplexing of the hook access.
The method is static, and can be called either with or without class or object as a first parameter.
See: "create", ``Links between objects''.
Can be used to pass the handle value outside the program, for an eventual interprocess communication scheme.
See also: "remove_notification", "add_notification".
See ``Success state'' for example.
Can be called in a context of class.
See ``Events'' and ``Flow'' for details.
notify() accepts variable number of parameters, and while it is possible, it is not recommended to call notify() with the exceeding number of parameters; the call with the deficient number of parameters results in an exception.
Example:
$obj-> notify( "PostMessage", 0, 1);
See ``Events'' and ``Flow'' for details.
See "push_event", ``Events''
See also ``post'' in Prima::Utils
See "pop_event", ``Events''
See also: "add_notification", "get_notification".
$obj-> onPostMessage( sub { ... });
or
$obj-> set( onPostMessage => sub { ... });
that are shortcuts for
$obj-> add_notification( "PostMessage", sub { ... });
See also: ``Success state'', "clear_event", ``Events''.
$obj-> name("Obj");
$obj-> delegations([ $owner, 'PostMessage']);
registers Obj_PostMessage callback if it is present in $owner namespace.
In get- context returns an array reference that reflects the object's delegated events list content.
See also: ``Delegated methods''.
$obj-> name( "Obj"); $obj-> owner( $owner); ... $owner-> Obj-> destroy;
and to prevent system-dependent issues. If the system provides capabilities that allow to predefine some object parameters by its name ( or class), then it is impossible to know beforehand the system naming restrictions. For example, in X window system the following resource string would make all Prima toolkit buttons green:
Prima*Button*backColor: green
In this case, using special characters such as ":" or "*" in the name of an object would make the X resource unusable.
Changing owner dynamically is allowed, but it is a main source of implementation bugs, since the whole hierarchy tree is needed to be recreated. Although this effect is not visible in perl, the results are deeply system-dependent, and the code that changes owner property should be thoroughly tested.
Changes to "owner" result in up to three notifications: "ChangeOwner", which is called to the object itself, "ChildLeave", which notifies the previous owner that the object is about to leave, and "ChildEnter", telling the new owner about the new child.