7.5 Advanced topics

7.5.1 More on type declarations

The basic XPCE type-syntax is described in section 3.2.1 of this manual. Types are first-class reusable XPCE objects that are created from the type-declaration in arguments and variables. The conversion from the textual representation to the object is performed by XPCE itself (together with the resource syntax, one of the few places where XPCE defines syntax). All types can be specified as Prolog quoted atoms. For example:

mymethod(Me, A:'graphical|dict_item|0..') :->

For most cases however, this is not necessary. If the type is not an atom, the class-compiler will convert the Prolog term into an atom suitable for XPCE's type system. Hence, [point] will translate to the atom '[point]', which is interpreted by XPCE as ``an instance of class point or the constant @default''. The atoms * and ... are defined as postfix operators, while .. is an infix operator. This makes `any ...' a valid notation for ``any number of anything'' (see section 7.5.2 below) and `0..5' a valid expression for ``an integer in the range 0 to 5 (including the boundaries).

Also, [box|circle] is a valid description for ``an instance of box or circle or the constant @default. Note however that [box|circle|ellipse] is not valid Prolog syntax and should be written as '[box|circle|ellipse]'. Whenever you are in doubt, use quotes to prevent surprises.

7.5.2 Methods with variable number of arguments

Methods such as `chain->initialise' and `string->format' handle an arbitrary number of arguments. The argument declaration for such a method first defines a number (possibly zero) of `normal' arguments. The last argument is postfixed with `...'. The arguments assigned to the `vararg' type are passed in a Prolog list.

Below is a refinement of `label->report' that will colour the label depending on the nature of the message. The ->report method takes two obligatory arguments, the kind of the report and a format string, as well as an undefined number of arguments required by the format specification.

:- pce_begin_class(coloured_reporter, label,
                   "Coloured reporter label").

report(L, Kind:name, Format:char_array, Args:any ...) :->
        Msg =.. [report, Kind, Format | Args],
        send_super(L, Msg),
        get(L, colour_from_report_category, Kind, Colour),
        send(L, colour, Colour).

colour_from_report_category(L, Kind:name, Colour:colour) :<-
        <left to the user>.

:- pce_end_class. Using class templates

XPCE provides two alternatives to multiple inheritance. Delegation is discussed in section C.4. See also the directive delegate_to/1 for user-defined class definitions. The template mechanism is much closer to real multiple inheritance. A template is a named partial class-definition that may be included in other classes. It behaves as if the source-code of the template definition was literally included at the place of the use_class_template/1 directive.

In fact, the class-attributes (variables, method objects) are copied, while the implementation (the Prolog clauses) are shared between multiple references to the same template.

Templates itself form a hierarchy below class template, which is an immediate subclass of object. Including a template will make all variables and methods defined between the template class and class template available to the receiving class.

We illustrate the example below, making both editable boxes as editable ellipses. First we define the template class.

:- use_module(library(pce_template)).

:- pce_begin_class(editable_graphical, template).

:- pce_global(@editable_graphical_recogniser,

make_editable_graphical_recogniser(G) :-
        Gr = @arg1,
        new(Dev, Gr?device),
        new(P, popup),
        send_list(P, append,
                  [ menu_item(cut, message(Gr, free)),
                              message(Dev, display, Gr?clone,
                                      ?(Gr?position, plus,
        new(G, handler_group(new(resize_gesture),

event(G, Ev:event) :->
        (   send_super(G, event, Ev)
        ;   send(@editable_graphical_recogniser, event, Ev)
:- pce_end_class.

The main program can now be defined as:

:- require([use_class_template/1]).

:- pce_begin_class(editable_box, box).
:- use_class_template(editable_graphical).
:- pce_end_class.

:- pce_begin_class(editable_ellipse, ellipse).
:- use_class_template(editable_graphical).
:- pce_end_class.

test :-
        send(new(P, picture('Template Demo')), open),
        send(P, display,
             editable_box(100,50), point(20,20)),
        send(P, display,
             editable_ellipse(100, 50), point(20, 90)).

Note that use_class_template/1 imports the definitions from the template in the current class. Thus, the following will not extend further on the `editable_graphical->event' definition, but instead replace this definition. Of course it is allowed to subclass the definition of editable_box above and further refine the event method in the subclass.

:- require([use_class_template/1]).

:- pce_begin_class(editable_box, box).
:- use_class_template(editable_graphical).

event(Gr, Ev:event) :->
        (   send_super(Gr, event, Ev)
        ;   ...
:- pce_end_class.

7.5.3 Implementation notes

The XPCE/Prolog class-compilation is defined using the Prolog preprocessing capabilities of term_expansion/2. While the class is compiled, Prolog gathers the expressions belonging to the class. The expansion of :- pce_end_class(Class) emits the actual code for the class.

The method implementation is realised by the predicates pce_principal:send_implementation/3 and pce_principal:get_implementation/4. that take the form:

send_implementation(MethodId, Method(Arg...), Object)
Where MethodId is unique identifier for the class and method, Method is the method implemented, Arg... are the arguments accepted by the method and Object is the receiving object.
get_implementation(MethodId, Method(Arg...), Object, -Result)
This is the get-equivalent of send_implementation/3.
:- pce_begin_class(gnus, ...
gnu(X, A:int) :-> ...
gnats(X, A:name, B:int) :-> ...

is translated into

pce_principal:send_implementation('gnus$+$->gnu', gnu(A), O) :- ...
pce_principal:send_implementation('gnats$+$->gnu', gnats(A, B), O) :- ...

The remainder of the class specification is translated into a number of Prolog clauses describing the class. No XPCE class is created. If XPCE generates an undefined_class exception, it will scan for the class-description in the Prolog database and create the XPCE class instance. No methods are associated with the new class. Instead, all method binding is again based on exception handling.

Modifications to the class beyond what is provided by the preprocessing facilities (for example changing the `variable->clone_style') cannot be made by sending messages to the class inside the class definition body as this would address the not-yet-existing class. Instead, they should be embedded in the pce_class_directive/1 directive.9To facilitate the translation of old code, the construct :- send(@class, ... is treated automatically as if it was embedded using pce_class_directive/1. The Goal argument of pce_class_directive/1 should refer to the class using the XPCE var object @class. When the class is realised the exception system will bind @class to the current class while running Goal. Goal is called from the Prolog module in which the class is defined.

The main reason for the above approach is to exploit the runtime-generation facilities of the hosting Prolog system to create fast-starting portable and (depending on the hosting Prolog's capabilities) stand-alone executables.

One of the consequences of the chosen approach is that the class-building directives are not accessible as predicates. There is no preprocessing support for the dynamic creation of classes and the programmer should thus fall back to raw manipulation of the XPCE class objects.