Re(de)fining methods is a common technique in object-oriented programming. This section describes how methods can be re(de)fined and what methods have special meaning in XPCE and are commonly redefined.
The method definition for a re(de)fined method is exactly the same as for a new method. The redefined method will inherit its group (see pce_group/1) from the method of the super-class.
When refining a method we often want to call the method of our super-class. For this reason there are two additional interface predicates to access the behaviour of a specific class. In 99% of the cases we wish to invoke a method of the immediate super-class. For this reason the class-compiler realises compile-time rewrite of send_super/[2-12] and get_super/[3-13] to send_class/2 and get_class/3.
Similar as the predicates send/2
and get/3
may be written as send/[3-12]
and get/[4-13]
this is possible for send_super/2
and get_super/3.
In addition the pre-5.0 `object->
send_super'
and `object<-
get_super'
are expanded to send_class/2
and get_class/3.
The following calls are all equivalent. The last one should not be used
by new code.
send_super(Object, my_method(Arg1)) send_super(Object, my_method, Arg1) send(Object, send_super, my_method, Arg1)
The most commonly redefined methods are ->
initialise
and ->
unlink to redefine object creation and
destruction. Note that none of these methods should ever be invoked
directly on an object, because the implementation often makes
assumptions that are only true in the context they are normally invoked
by the kernel.
->
initialise on th super-class. Omitting is a
common source of errors, often leading to crashes.
The initialise method should initialise all slots declared in this class that have no specified value in the variable declaration and cannot have the value @nil. See also checkpce/0.
If ->
initialise fails, the exception initialise_failed
will be raised, passing the instance and the argument vector.
Afterwards, the (possible named) reference is destroyed and the object's
slots are reset to @nil. Finally, the
instance is deallocated. ->
unlink (see below) is
not called. In general, it is not good programming style to let
->
initialise fail.
->
unlink
of the super-class somewhere in the process. It is an error if ->
unlink
fails.
This method is normally used to unlink the object from related
objects. For example, graphical
objects use it to remove themselves from their device if they are
displayed. There is no need to reset slot-values as dereferencing the
slot-values will be done by the object-management system after ->
unlink
has finished.
->
unlink is always called, whether the object
was destroyed using ->
free or by the
garbage-collector.
The receiver is not defined during the execution of this method. The method should either fail or succeed and return an instance of the requested class or one of its super-classes. The argument vector consists of a single argument. The type-conversion system guarantees the argument is of the satisfied type. It is allowed, but not obligatory to use the method of the super-class.
For example, suppose we are defining a class person, who has a unique name. There is a table @persons, that maps the name onto the person. We would like to be able to pass the name rather then a person instance to a method argument with the type person. If no such person exist, a new person instance is created. Below is the implementation for this:
convert(_, Name:name, P:person) :<- "Lookup from @persons or create a new one":: ( get(@persons, member, Name, P) -> true ; new(P, person(Name)) ).
See also <-
lookup described below.
->
initialise is considered. The
arguments are normally the same as for ->
initialise.
If this method returns an instance, this will be the value returned by
new(). If it fails, a new instance is allocated and ->
initialised.
The generic graphical class graphical is prepared to have several of its methods redefined in subclasses. This section describes the most important of these methods.
->
in_event_area' succeeds. These are normally all
graphicals that overlap with the current position of the pointer. It
will sort these objects to their stacking order, the topmost object
first. See `device<-
pointed'.
Next the device will use `event->
post'
to post the event to each of these graphicals until one accepts the
event, after which the method immediately returns success. If none of
the <-
pointed objects is prepared to accept the
event, `graphical->
event'
will be invoked, trying all he recogniser
objects associated with this graphical.
Notably most subclasses of class dialog_item,
the standard controllers, refine ->
event.
The method ->
event is commonly redefined in
user-defined graphicals to make them sensitive to the mouse. The
following fragment of a class definition makes it possible to resize and
move instances.
:- pce_global(@resize_and_move_recogniser, new(handler_group(new(resize_gesture), new(move_gesture)))). event(Gr, Ev:event) :-> "Make the object re-sizeable and movable":: ( send_super(Gr, event, Ev) ; send(@resize_and_move_recogniser, event, Ev) ).
Note that the implementation first tries the super-class. If the super-class has no specific event-handling, this allows recognisers to be attached that overrule the resize/move behaviour. Also, if it is a device, invoking the super-class behaviour will test components displayed on the device to be considered before the device as a whole.
It is not obligatory to use ->
event on the
super-class and if it is used, no specific ordering is required. If
there is no behaviour of the super-class that conflicts with your
extension we recommend to try the super-class first, to ensure
recognisers and local event-processing in graphicals displayed on a
device with redefined event-processing are considered before your
extensions.
Note the way recognisers are activated from event methods. The
graphical object itself is not passed. Instead, `recogniser->
event'
reads the receiver from `event<-
receiver'
set by `event->
post'.
As a consequence, do not call `graphical->
event'
directly. An event is directed to a graphical using `event->
post'.
For example, the event-method of a device displaying an editable text
object may decide to forward all button and keyboard events to the text.
The following accomplishes this:
event(D, Ev:event) :-> ( ( send(Ev, is_a, button) ; send(Ev, is_a, keyboard) ) -> % assumes text is named `text' get(D, member, text, Text), send(Ev, post, Text) ; send_super(D, event, Ev) ).
Redefining ->
geometry is the proper way to
interfere with positioning or resizing as this is the central method
called by all move and resize methods.
The example below takes the text-box to ensure proper geometry handling by this class. Note that (I) the size of a device is by definition the bounding box of all displayed graphicals and (II) the text must be centered again.
geometry(D, X:[int], Y:[int], W:[int], H:[int]) :-> get(D, member, box, B), get(D, member, text, T), send(B, set, @default, @default, W, H), send(T, center, B?center), send_super(D, geometry, X, Y).
Note that the relation between the text and the box could also be maintained using a constraint object. The above implementation however is only executed when the geometry of the device is changed, while constraints will be executed whenever a message arrives on the box or text.
->
geometry, except that the
interpretation of the units is left to the graphical. For example editor
will use the current font to translate W and H to
pixels and then invoke ->
geometry. Not used very
often.
->
request_compute and
may be used to delay expensive graphical operations. Suppose we have a
graphical representation and a database object linked using a hyper
like this:
new(_, hyper(Db, Gr, controller, model))
If the database object (model) is modified, it could use the following to inform all associated controllers about the change:
send(Db, send_hyper, controller, request_compute)
XPCE remembers that the state of this graphical
is not consistent. If
XPCE requires the graphical to be in a consistent
state, either because it needs to paint the graphical or because it
requires information about the geometry of the graphical, it will invoke
the method ->
compute on the graphical.
This mechanism is used by graphicals that have a complicated
structure and are difficult to update. An example in the built-in
classes is class
text_image,
displaying the text of an editor.
Any modification to the text in the displayed region of the text_image
requires expensive computation to recompute the layout of the text.
Suppose the ->
request_compute and ->
compute
mechanism is not available. It this case, multiple modifications by the
program to the text would require this expensive process to run several
times. Now, after modifying the text, ->
request_compute
is invoked on the text_image. Whenever XPCE has
processed all pending events, it will invoke ->
compute
to the text_image and then repaint it.
The method below is a far to simple example, where the ->
compute
method simply copies the name of the represented object into the text
object displayed on the device ->
compute is
defined on.
compute(C) :-> "Update according to model":: get(C, get_hyper, model, name, Name), get(C, member, text, T), send(T, string, Name), send_super(C, compute).
<-
area
of the device.
Exploitation of this method to realise new graphical primitives is explained in section 10.12.