7.3 Refining and redefining methods

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.

send_class(+Object, +Class, +Message)
Invoke Message on Object using the implementation defined with class Class. Class must be the actual class of Object or one of its super-classes or an error is raised.
get_class(+Object, +Class, +Message, -Result)
This is the get-equivalent of send_class/3.
send_super(+Object, +Message)
The class-compiler converts goals of this format to an appropriate send_class/3 call. Note that it is not possible to provide predicates as an alternative to the compile-time expansion and therefore meta-calls cannot use send_super/2.
get_super(+Object, +Message, -Result)
This is the get-equivalent of send_super/2.

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)

7.3.1 General redefinitions

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.

object ->initialise: <Class-Defined>
Initialise a new instance of the class. The initialisation is not allowed to access behaviour or slots of the super-class without invoking the ->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.

object ->unlink:
Called from the object-management system if the object is to be destroyed. This method must call ->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.

object <-convert: <Class-Defined>
Instance This get method converts another object into an object of this class. It is called by the type-checker. Suppose an object X is handed to the type checker for checking against this class. If X is not already an instance of this class or any of its subclasses, the type checker will:

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.

object <-lookup: <Class-Defined>
Instance Called from the new() virtual machine operation to deal with reusable objects before ->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.

7.3.2 Redefinition in graphical classes

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.

graphical ->event: event
Called when a user-event needs to be dispatched. This message is initially sent to the window object receiving the event. Graphical devices (and thus windows) collect all graphicals for which `graphical ->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)
        ).
graphical ->geometry: X:[int], Y:[int], W:[int], H:[int]
Requests the receiver to position itself at the X,Y and to be W × H pixels in size. Any of these values may be @default, indicating that the specific parameter is not to be changed.

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.

graphical ->request_geometry: X:[int], Y:[int], W:[int], H:[int]
Is much like ->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.
graphical ->compute:
This method cooperates with ->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).
graphical ->_redraw_area: area
Called by the graphical repaint thread. Its task is to repaint itself. Area indicates the area in the device coordinate system that needs to be repainted. This area overlaps with the <-area of the device.

Exploitation of this method to realise new graphical primitives is explained in section 10.12.