Using SICStus Objects in the Design of Graphical User Interfaces

Thomas Sjöland

SICS, Box 1263, S-164 29 Kista, Sweden

 

Abstract

SICStus Objects, an embedded object oriented language written in SICStus Prolog, has been used in an experiment with interface construction. The approach is outlined and pros and cons are discussed.

 

Background

Some Prolog vendors supply their systems with possibilities for using object orientation together with Prolog. Commercial systems known to us are Prolog++ from Logic Programming Associates, Logical Object Systems (LOS) from Imperial College [McCabe89] and ELSA from ELSA Software.

SICStus Objects has a lot in common with LPA's Prolog++ [Vasey, Spencer et.al.90], and with the systems described by Fromherz [Fromherz91] and Moss [Moss90].

SICStus Objects

SICStus Objects provides the Prolog programmer with mechanisms for defining and using objects as a programming paradigm together with SICStus Prolog.

• Prolog goals can be called from components in objects and messages can be sent to objects from ordinary Prolog clauses.

• The language SICStus Objects is translated into Prolog. This gives the possibility of using the Prolog compiler.

NB: The current implementation is fairly straightforward. E.g. it does not optimize inheritance or updates of objects. This might limit the applicability of the system.

Terminology

Object oriented programs consist of definitions of data together with methods to manipulate the data. Often the notion "class" is used when talking about a definition and "object" is used when talking about an instance of the class which is created during run-time. SICStus Objects follows the convention of LPA's Prolog++ using "object" for the description and "instance" for objects created during the execution of the program. This is motivated by the fact that an object in SICStus Objects can be modified. Objects and instances behave similarly with the major difference that objects can be compiled whereas instances cannot. Methods and data (attributes), are here referred to as components. Components in compiled objects cannot be modified unless declared dynamic. Regardless of these differences it is appropriate to use the term "object" unless we are specifically talking about instances.

Compiling and running SICStus Objects

In SICStus Objects the user can freely mix SICStus Prolog code with the object oriented code. This allows a great deal of flexibility. When a SICStus Objects program is to be preprocessed or run you load the translator, the runtime system and the definitions of the object object.

:- use_module(library(objects)).

The object oriented programs should be translated into Prolog. This is accomplished by using a call of this form:

:- translate(myfile).

This call translates myfile into the Prolog source file myfile.pl, which can subsequently be compiled (or loaded) by SICStus Prolog in the usual way.

A program in SICStus Objects consist of a set of object definitions. Outside the definitions of objects you can write any ordinary clauses and directives in SICStus Prolog. The syntax of Prolog is extended with operators for invoking the object oriented system from Prolog.

The following operators are defined for SICStus Objects:

:- op(1200,fy,[open_object,close_object]).

:- op(800,xfx,['<-']).

:- op(600,xfx, [':=','+=','-=']).

Syntax

In SICStus Objects the Prolog syntax is used for definitions.

An object definition is bracketed thus:

open_object <ObjectName>.

…<definitions for hierarchy and components>

close_object <ObjectName>.

The syntax of components is that of Prolog clauses. The inheritance hierarchy is specified by defining the component super/1. A link is specified by defining the component link/2. The keyword dynamic is used to specify that a component may be modified. Undefined components are considered as dynamic when added to an object. Prolog goals are simply written in the body, while component access of SICStus Objects is invoked through using the goal <-/2.

 

 

An example of an object definition

open_object my_object.

super(another_object).

dynamic(attribute/1).

attribute(value).

attribute(anothervalue).

method(X) :- prologgoal(X),

johnnys_object <- method(X).

method(X) :- myself <- attribute(X),

self <- inherited_method(X,X).

link(method_of_other_object/3,other_object).

close_object my_object.

SICStus Objects is a dynamic object system

A static object system cannot create objects during the execution. Therefore in a static object system there is no need to distinguish between objects and instances. Each object which is being used has its own definition and a unique set of components, playing the role of state variables.

A dynamic object system allows the program to create objects in the system. SICStus Objects is a dynamic object system.

Modifying the state

The idea of a state variable is alien to logic programming but it is practical in many situations. State variables are implemented in SICStus Objects as components by using the database features of SICStus Prolog. Therefore components can be added and removed to/from objects as well as modified in addition to just being used.

Object definitions and object instances

An object definition which is written in the source code describes an object but also a class of object instances. When object instances are created, the object is used as a template. The object instance receives a name which can be used as a reference when sending messages. In SICStus Objects messages can be sent to objects and to instances. The distinction between objects and instances is that instances are not named in the source code, but the instances are given unique names upon being created.

 

Components

An object is a collection of components. Components are known to the object and its ancestors in the object hierarchy. The object hierarchy is specified with the component super/1, defining the object(s) from which the current object automatically inherits components which are not defined in the object itself. In SICStus Objects, mechanisms for creating objects and instances as well as for sending messages to the objects (instances) are provided.

A component of an object can be defined, accessed, modified and deleted. SICStus Objects allows a component to be seen as a relation, i.e. several alternatives are allowed. The access mechanism can backtrack upon failure just like in Prolog but backtracking is limited to be only within the first found object which contains the component.

Object hierarchies and inheritance mechanisms

When an object lacks a component the system can search for it in another object. By making precise exactly which objects are higher than a given object and introducing an object which is higher than any other object a semilattice of objects, an object hierarchy, is formed. The highest object in the hierarchy is the object object,which has no defined component super/1. Specialisation of objects is achieved through inheritance. When a component is not defined in an object it is looked for in the objects that are higher. The immediately higher objects are defined in the component super. Multiple inheritance is provided since an object may refer to several other objects with the component super/1 by simply adding more declarations of the component super/1.

Since the hierarchy links are components, explicit searches in the hierarchy can be programmed with a large degree of freedom.

Shadowing

Shadowing (overriding inheritance) means that components which are found in objects which are lower in the hierarchy hide components from higher objects.

In SICStus Objects a component is found in a unique object. Since SICStus Objects is based on Prolog, alternatives are allowed which can be used for backtracking. The alternatives are only searched in the chosen object, even if there are definitions in other places in the object hierarchy. In LOS [McCabe89] there are two different mechanisms for inheritance. Except for shadowing LOS uses the union of all components in the hierarchy for the Prolog search. In SICStus Objects shadowing can be avoided by using the component super/1 and including a clause to distribute messages upwards in the hierarchy:

method(InData) :-

myself <- super(S), S <- method(InData).

Since multiple inheritance is provided, shadowing can be implemented in various ways. In SICStus Objects the principle that is followed is this:

Inheritance as specialisation:

The object that is identified as the carrier of an inherited component is the leftmost one which does not have a subobject in which the component occurs.

In figure 1, the principle used to resolve multiple inheritance is shown with a perhaps somewhat contrived example. Consider four objects a,b,c and d where d has a, b and c as super and b and c have a as super. A component m is defined in a and c, a component n is defined in a and b, and a component o is defined in b and c. In some object oriented systems multiple inheritance is resolved by a left-to-right depth first search upwards in the lattice. Such a search traverses first one branch and then possibly the others, one at a time. An access to m from d would identify the m in a via b rather than the more specialised version in c. Considering the component n on the other hand it is clear that the problem cannot be resolved with a reordering of the branches. In one of the cases a component will be found which should have been shadowed by a specialized instance of it. Furthermore, since inheritance should be regarded as specialisation, a breadth first search is not sufficient to implement the principle (consider that d has a as a direct super which would give the m of a rather than that of c).

By using the rule above implemented as a topological sort, d gets the n and the o of b and the m of c.

Components can also be inherited from named objects without considering the hierarchy. This explicit inheritance is provided by defining links.

It is essential to ensure that the design of the inheritance relation does not create loops, i.e. the super relation should be a semi-lattice with object as the top element.

Polymorphism is achieved by using the same component name to define different behaviours for different objects.

Encapsulation and abstraction

Since all objects are defined in parentheses (open_object, close_object) and the components are unique to the object the components are encapsulated. In SICStus Objects components can also be defined as private. Such components are not inherited. Otherwise they are understood as public. Public is the default case.

The name of an object and the names of its public components are its abstraction.

Communication and control of the execution

The central operation for an object oriented system is transfer of messages. Message transfer is essentially the same as goal resolution in Prolog using the inheritance hierarchy to find the addressed component.

Messages are sent to objects and are of two types:

1) request for execution of a method or finding the value of a component, e.g.:

obj <- message(Hello)

2) request for modification of a component, e.g.:

obj <- update(attribute3(<new_value>))

obj <- retract(attribute3(_))

obj <- assert(attribute3(<new_value>))

self and myself

In order to use components from the current object even when inheritance is at play, the keyword self can be used. self is used to refer to the object that used the component. Another keyword, myself refers to the name of the object definition in which the method definition is localized. In fact this usage means that myself is redundant since you can always use the name of an object explicitely, but it is introduced for convenience and to produce more readable code.

 

sender provides selective component invocation

The keyword sender is used to refer to the object that requested the component. By testing sender the access to components can be arbitrarily limited.

Operations on objects and object instances

Objects can be created based on other object definitions and also be removed. An instance is normally considered to have as its super the object in respect to which it was created. The method new/1, defined in the object object is used to create instances of an object, e.g.:

my_object <- new(Reference)

Executing new/1 means that a new object is created which is considered as a subobject (instance) of the object my_object. It has a unique set of components which can be used for storing values.The components are inherited from my_object.

If components have been defined as dynamic they can be added and deleted from an object. Components which are not in the source code are considered to be dynamic.

The object object and its components

The top element of the object hierarchy is an object to which all other objects are subobjects (instances), the object object. The components of object are available to all objects in the system.

 

Graphical programming environment

Systems for object oriented programming are often implemented together with an interactive graphical programming environment. In this there are tools to inspect the control structures (e.g. "object browsers") and to construct program code. Such tools are yet to be implemented for SICStus Objects.

Standard

The combination of object oriented programming and logic programming in Prolog is still a matter of research and early development. There is currently no standard available.

An example: gm objects, interfacing to the graphics manager

As an example of the use of SICStus Objects we choose an object oriented interface to the graphics manager of SICStus Prolog, GM [AlAnFlFrNiSu91]. GM is written in C++ using Interviews, a set of classes used to specify graphical interactors, visible graphical "things" which have certain predefined behaviours. In order to make it possible to extend the interface in such a way as to allow the programmer to define new interactors with their own behaviour an object oriented approach is a significant help. We will show how the basic interface is constructed in SICStus Objects, and then we give some examples of how it can be extended to support a few new interactors of the programmer's choice. The name gm objects will be used in reference to the graphical subsystem.

In gm objects the object hierarchy of SICStus Objects is used to define methods for constructing and manipulating graphical objects. The available graphical objects are given by GM.

The components of gm objects

Objects in gm objects have a set of components to manage the state of the interface. The central idea of gm objects is to consider the "visible things" as objects in SICStus Objects. The contents of a visible object are specified in the component content/1. Some default operations for managing the visualisation of the object are provided.

In order to create an object which is subordinated to gm_object you use the method gmnew/1. When sent to an object which is subordinate to gm_object it will create an object of this type, performing the appropriate procedures for setting up the object and making it visible under Xwindows. The inheritance hierarchy is used to enable the system to automatically choose the desired initiation method gm_create/0.References to the graphical subsystem are handled without explicit intervention by the programmer. This makes it possible for the programmer to use gm objects without considering the underlying GM package with its handling of references to graphical objects. The references and properties needed to construct the object are to be found as components in the object. By doing so the costly communication in order to inspect the visual objects is avoided. In some cases this enhances the functionality of the graphic system since GM does not provide access to its internal state from the Prolog program. Composite objects may be constructed as objects in gm objects without modifying the low level GM system.

 

 

The object hierarchy of gm objects

The inheritance relation (super) of gm objects is shown in figure 2 (except for explicit links of particular components). The relation is interpreted thus: "when X is a super of Y a component of Y which is not defined in Y itself can be found in X".

 

Visible objects in gm objects

Some visible objects can be contained in other visible objects. A relation describing the visible objects that may be contained within other visible objects is a natural way to display this constraint.

gm_window : gm_hbox | gm_vbox | gm_view | gm_text

| gm_input | gm_output | gm_slider

| gm_button

gm_hbox : gm_vbox | gm_view | gm_text

| gm_input | gm_output | gm_slider

| gm_button

gm_vbox : gm_hbox | gm_view | gm_text

| gm_input | gm_output | gm_slider

| gm_button

gm_view : gm_menu | gm_line

| gm_ellipse | gm_fillellipse

| gm_circle | gm_fillcircle

| gm_rect | gm_fillrect

| gm_polygon | gm_fillpolygon

| gm_string | gm_bitmap | gm_picture

gm_text : gm_menu

 

How to use gm objects

gm objects hides the fact that GM requires that visible objects are constructed in a particular order. The user specifies the content/1 components of the visible objects. Thereafter (s)he should send the message gmnew/1 to the object specifying the window. The methods for creating the content of a window will then look up the content/1 components describing the layout of the window and creates an object for each of the visible parts in the appropriate order. It is essential that recursive objects are not specified since that will lead the construction method gmnew into an infinite regress. (A tool to check whether a specification is circular, recursive, is a good exercise in order to familiarize oneself with SICStus Objects and gm objects).

 

 

Event handlers

An important aspect of GUI construction is the handling of events. It is important to be able to construct a modeless interface, i.e. the interface should be so designed that it is possible to get a response for any keyklick, selection or similar interaction which is possible on the screen, rather than having the program wait for a particular input at any given time. Sometimes, but more rarely, it is essential that the program awaits particular inputs such as responses to queries etc. before accepting other input. This later kind of interface is called moded interface and should also be provided, although that issue is not tackled here. gm objects provides a method, run/0, which is located in the object gm. The call:

gm <- run.

will start the event handling loop. This loop awaits action from the user. Upon receiving a message it dispatches the messages to the object in which the event took place. By specifying an eventhandler as a method of that object the system allows the user to tailor the responses of the program based of the object hierarchy. The method should be specified as eventhandle/1. Any programming technique of SICStus Objects in combination with SICStus Prolog is available, but it is adviced that responses programmed in the eventhandler are of a reasonably short duration. The only method that should be allowed to go in an infinite loop is run/0 of the object gm.

If the generic event handling loop, run/0, is inappropriate the event handling can be programmed by using either

gm_object <- wait_event(E)

or

gm_object <- next_event(E)

which waits or polls the event queue for incoming events. Whenever the application wants to treat a received event,

gm_object <- event_handle(E)

can be used. This call can also be used to mimick the reception of an event. If a sequence of events is recorded, this could for instance be used in a later playback of a sequence of events.

 

Object definitions in gm objects

compile_objects(File) :- gm <- end, object <- clean, translate(File), compile(File).

object_listing :- object_listing(object,0).

object_listing(O,Tab) :-

tab(Tab), write(O), nl,

Tab1 is Tab+3,

findall(_,(O <- sub(Obj), object_listing(Obj,Tab1)),_).

full_object_listing :- full_object_listing(object,0).

full_object_listing(O,Tab) :-

tab(Tab), write(O), write('#'), nl,

Tab0 is Tab+1,

findall(_,(O <- attribute(A),tab(Tab0),write(A),nl),_),

Tab1 is Tab+3,

findall(_,(O <- sub(Obj), full_object_listing(Obj,Tab1)),_).

on(X, [X|_]).

on(X, [_|L]) :- on(X, L).

write_seq([]).

write_seq([X|L]) :- write(X), write(' '), write_seq(L).

 

 

%------------------------------------------------------------------------------------

% gm.

%------------------------------------------------------------------------------------

open_object gm.

dynamic(host/1).

host(hathor).

dynamic(username/1).

username(thomas).

start :- myself <- host(Host),

myself <- username(Username),

(myself <- started ->

myself <- end

;

true),

start.

start(Host) :- self <- update(host(Host)), start.

start(Host,Username) :-

myself <- update(username(Username)), myself <- start(Host).

started :- started.

end :-

bagof1(I,(gm_object <- instance(I), I <- delete),_), end, freeall.

% event loop

run :- write_seq([self,'waiting for event']), nl,

waitevent(E),

write_seq([self,'received event',E]), nl,

eventaddress(E,WindowObject),

write_seq([self,'dispatching to',WindowObject]), nl,

((WindowObject <- eventhandle(E)) ->

true ;

write_seq([self,'failed eventhandle'(WindowObject,E)]), nl),

((\+ E=menu(_,quit)) -> self <- run).

close_object gm.

objref_to_name(Ref, Name) :- clause(gmlib:named_object(Name,Ref,_), _).

eventaddress(down(_Window,View,_,_),Ref) :- objref_to_name(View,Ref).

eventaddress(button(Window,_),Ref) :- objref_to_name(Window,Ref).

eventaddress(menu(Window,_),Ref) :- objref_to_name(Window,Ref).

eventaddress(return(_Window,Browser,_),Ref) :- objref_to_name(Browser,Ref).

eventaddress(return(_Window,Input),Ref) :- objref_to_name(Input,Ref).

eventaddress(slider(_Window,Slider,_),Ref) :- objref_to_name(Slider,Ref).

eventaddress(noevent,gm_object).

%------------------------------------------------------------------------------------

% gm_object.

%------------------------------------------------------------------------------------

open_object gm_object.

% Attributes.

% constructor(Constructor)

% contains current Constructor as defined in subclasses

% gm_ref(GM-reference)

% contains current reference to GM-object

gmnew(Ref) :- self <- new(Ref),

Ref <- gmcreate.

gmcreate :- write_seq([self,'gmcreate/0']), nl,

self <- gmcreate(self).

gmcreate(T) :- T==[], !.

gmcreate(A) :- nonvar(A), A=[H|T], !,

self <- gmcreate(H),

self <- gmcreate(T).

gmcreate(Ref) :- self <- constructor(Constructor),

GmRef=Ref,

(gmcreate(GmRef,Constructor)

->

self <- update(gm_ref(GmRef)) ;

write_seq(['prolog:gmcreate/2 failed for',

GmRef,Constructor]),nl).

gmsend(Message) :-

self <- gm_ref(GmRef),

gmexist(GmRef),

gmsend(GmRef,Message).

gmexist:- self <- gm_ref(GmRef),

gmexist(GmRef).

% events (polling)

nextevent :- nextevent(Event),

self <- eventhandle(Event).

% events (waiting)

waitevent :- waitevent(Event),

self <- eventhandle(Event).

% local events to this object (polling)

nextevent(Event) :- self <- gmsend(nextevent(Event)).

% local events to this object (waiting)

waitevent(Event) :- self <- gmsend(waitevent(Event)).

eventhandle(button(Window,Eventname)) :-

write_seq(['An unknown button was pressed', button(Window,Eventname)]), nl.

eventhandle(menu(Window,Eventname)) :-

write_seq(['An unknown menu was selected',

menu(Window,Eventname)]), nl.

eventhandle(return(Window,Browser,LineNo)) :-

write_seq(['An unexpected return event occurred',

return(Window,Browser,LineNo)]), nl.

eventhandle(return(Window,Input)) :-

write_seq(['An unexpected return event occurred',

return(Window,Input)]), nl.

eventhandle(down(Window,View,X,Y)) :-

write_seq(['An unexpected down event occurred',

down(Window,View,X,Y)]), nl.

eventhandle(slider(Window,Slider,Value)) :-

write_seq(['An unexpected slider release event occurred',

slider(Window,Slider,Value)]), nl.

eventhandle(noevent) :-

write_seq(['An unexpected noevent event occurred']), nl.

close_object gm_object.

%------------------------------------------------------------------------------------

% gm_window.

%------------------------------------------------------------------------------------

% create subobjects to this class to describe different window types

% the field content(_) contains the object class name for the component(s)

% which is (are) automatically created and put into the window created by

% sending gmcreate to the window class.

%

open_object gm_window.

super(gm_object).

% attributes:

% content(Class)

% contentref(Ref)

gmcreate :- self <- gmcreate_window(self).

gmcreate_window(WindowTitle) :-

self <- update(name(WindowTitle)),

self <- content(Class),

Class <- gmnew(ObjRef),

ObjRef <- update(partof(self)),

self <- update(contentref(ObjRef)),

ObjRef <- gm_ref(GmRef),

self <- update(constructor(window(WindowTitle,GmRef))),

self <- gmcreate(self).

update :- self <- gmsend(close),

self <- gmcreate(self).

open :- self <- gmsend(open).

open(X,Y) :- self <- gmsend(open(X,Y)).

close :- self <- gmsend(close),

self<- contentref(R),

(R <- menuref(M) ->

(M <- delete)

;

true),

R <- delete,

self <- delete.

raise :- self <- gmsend(raise).

lower :- self <- gmsend(lower).

iconify :- self <- gmsend(iconify).

deiconify :- self <- gmsend(deiconify).

setcursor(Cursor) :- self <- gmsend(setcursor(Cursor)).

getcursor(Cursor) :- self <- gmsend(getcursor(Cursor)).

%------------------------------------------------------------------------------------

eventhandle(menu(Window,quit)) :-

write_seq(['Deleting window',Window]), nl,

self <- close.

%------------------------------------------------------------------------------------

close_object gm_window.

 

%------------------------------------------------------------------------------------

% gm_window_content.

%------------------------------------------------------------------------------------

open_object gm_window_content.

super(gm_object).

% Attributes:

% partof(_) reference to a window.

gmnewcontent(Ref) :-

self <- content(C),

(C=space ->

Ref=space

;

C=space(frame(O)) ->

(O <- gmnew(Ref0),Ref=space(frame(Ref0)))

;

C=frame(space(O)) ->

(O <- gmnew(Ref0),Ref=frame(space(Ref0)))

;

C=space(O) ->

(O <- gmnew(Ref0), Ref=space(Ref0))

;

C=border -> Ref=border

;

C=frame(O) ->

(O <- gmnew(Ref0), Ref=frame(Ref0))

;

C=scroller(O) ->

(O <- gmnew(Ref0), Ref=scroller(Ref0))

;

C <- gmnew(Ref)

).

close_object gm_window_content.

 

%------------------------------------------------------------------------------------

% gm_active.

%------------------------------------------------------------------------------------

open_object gm_active.

% for view, text, input and slider (and button)

super(gm_window_content).

enable :- self <- gmsend(enable).

disable :- self <- gmsend(disable).

close_object gm_active.

%------------------------------------------------------------------------------------

% gm_view.

%------------------------------------------------------------------------------------

open_object gm_view.

super(gm_active).

% graphic(_)

% a set of names of subclasses of gm_graphic which are contained here

%

% graphicref(_)

% a set of references to instances of gm_graphic which are in this view

%---not called by user directly ----------------------

eventhandle(_,quit) :- self <- partof(Window),

Window <- close.

%------------------------------------------------------------

gmcreate :- self <- gmcreate(200,100),

self <- enable.

gmcreate(Xsize,Ysize) :-

self <- update(constructor(view(Xsize,Ysize))),

self <- gmcreate(self),

bagof1((C,GraphicRef),

(self <- graphic(C),

C <- gmnewgraphic(self,GraphicRef),

self <- assert(graphicref(GraphicRef))),

_),

self <- setmenu.

line(X1,Y1,X2,Y2) :- self <- gmsend(line(X1,Y1,X2,Y2)).

ellipse(X,Y,R1,R2) :- self <- gmsend(ellipse(X,Y,R1,R2)).

fillellipse(X,Y,R1,R2) :- self <- gmsend(fillellipse(X,Y,R1,R2)).

circle(X,Y,R) :- self <- gmsend(circle(X,Y,R)).

fillcircle(X,Y,R) :- self <- gmsend(fillcircle(X,Y,R)).

rect(X1,Y1,X2,Y2) :- self <- gmsend(rect(X1,Y1,X2,Y2)).

fillrect(X1,Y1,X2,Y2) :- self <- gmsend(fillrect(X1,Y1,X2,Y2)).

polygon(Xedges,Yedges) :- self <- gmsend(polygon(Xedges,Yedges)).

fillpolygon(Xedges,Yedges) :- self <- gmsend(fillpolygon(Xedges,Yedges)).

string(X,Y,Text) :- self <- gmsend(string(X,Y,Text)).

bitmap(X,Y,Filename) :- self <- gmsend(bitmap(X,Y,Filename)).

setcolors(Fgcolor,Bgcolor) :- self <- gmsend(setcolors(Fgcolor,Bgcolor)).

setpattern(Pattern) :- self <- gmsend(setpattern(Pattern)).

setbrush(Pattern,Width) :- self <- gmsend(setbrush(Pattern,Width)).

setfont(Font) :- self <- gmsend(setfont(Font)).

stringlength(Text,Length) :- self <- gmsend(stringlength(Text,Length)).

batchmode :- self <- gmsend(batchmode).

batchmodeoff :- self <- gmsend(batchmodeoff).

update :- self <- update.

setmenu :- self <- setmenu(gm_menu).

setmenu(Menu) :-

Menu <- gmnew(MenuRef),

self <- update(menuref(MenuRef)),

MenuRef <- gm_ref(MenuGmRef),

self <- gmsend(setmenu(MenuGmRef)).

clear :- self <- gmsend(clear).

zoom(Amount) :- self <- gmsend(zoom(Amount)).

scrollto(X,Y) :- self <- gmsend(scrollto(X,Y)).

scrollby(X,Y) :- self <- gmsend(scrollby(X,Y)).

getcur(X,Y) :- self <- gmsend(getcur(X,Y)).

link(setcursor/1,gm_window).

link(getcursor/1,gm_window).

close_object gm_view.

%------------------------------------------------------------------------------------

% gm_text.

%------------------------------------------------------------------------------------

open_object gm_text.

super(gm_active).

gmcreate :- self <- gmcreate(12,40,5),

self <- enable.

gmcreate(Rows,Cols,Tab) :-

self <- update(constructor(text(Rows,Cols,Tab))),

self <- constructor(C),

self <- gmcreate(self),

self <- setmenu.

gmcreate(Rows,Cols,Tab,Font) :-

self <- update(constructor(text(Rows,Cols,Tab,Font))),

self <- gmcreate(self),

self <- setmenu.

readfile(File) :- self <- gmsend(readfile(File)).

writefile(File) :- self <- gmsend(writefile(File)).

link(setmenu/0,gm_view).

link(setmenu/1,gm_view).

insert(Text) :- self <- gmsend(insert(Text)).

delete_text :- self <- gmsend(delete).

delete_text(N) :- self <- gmsend(delete(N)).

clear :- self <- gmsend(clear).

readonly :- self <- gmsend(readonly).

readwrite :- self <- gmsend(readwrite).

moveto(X,Y) :- self <- gmsend(moveto(X,Y)).

moveby(X,Y) :- self <- gmsend(moveby(X,Y)).

in(all,Text) :- self <- gmsend(in(all,Text)).

in(selection,Text) :- self <- gmsend(in(selection,Text)).

in(line(LineFrom,LineTo),Text) :-

self <- gmsend(in(line(LineFrom,LineTo),Text)).

in(word,Text) :- self <- gmsend(in(word,Text)).

forwardsearch(Text) :- self <- gmsend(forwardsearch(Text)).

backwardsearch(Text) :- self <- gmsend(backwardsearch(Text)).

close_object gm_text.

%------------------------------------------------------------------------------------

% gm_input.

%------------------------------------------------------------------------------------

open_object gm_input.

super(gm_active).

gmcreate :- self <- gmcreate_input(self),

self <- enable.

gmcreate_input(Text) :- self <- update(constructor(input(Text))),

self <- gmcreate(self).

gmcreate(Text,Font) :- self <- update(constructor(input(Text,Font))),

self <- gmcreate(self).

in(Text) :- self <- gmsend(in(Text)).

out(Text) :- self <- gmsend(out(Text)).

close_object gm_input.

%------------------------------------------------------------------------------------

% gm_button.

%------------------------------------------------------------------------------------

open_object gm_button.

super(gm_active).

gmcreate :- self <- gmcreate(self,self),

self <- enable.

gmcreate(Name,Message) :- self <- update(constructor(button(Name,Message))),

self <- gmcreate(self).

gmcreate(Name,Message,Attribute) :-

self <-update(constructor(button(Name,Message,Attribute))),

self <- gmcreate(self).

close_object gm_button.

%------------------------------------------------------------------------------------

% gm_slider.

%------------------------------------------------------------------------------------

open_object gm_slider.

super(gm_active).

gmcreate :- self <- gmcreate_slider(self),

self <- enable,

self <- value(V),

self <- update(value(V)).

gmcreate_slider(Name) :-

self <- update(constructor(slider(Name))),

self <- gmcreate(self),

self <- enable.

value(Val) :- self <- gmsend(value(Val)),

self <- update(value(Val)).

close_object gm_slider.

%------------------------------------------------------------------------------------

% gm_inactive.

%------------------------------------------------------------------------------------

open_object gm_inactive.

super(gm_window_content).

close_object gm_inactive.

%------------------------------------------------------------------------------------

% gm_output.

%------------------------------------------------------------------------------------

open_object gm_output.

super(gm_inactive).

gmcreate :- self <- gmcreate_output(self).

gmcreate_output(Text) :- self <- update(constructor(output(Text))),

self <- gmcreate(self).

gmcreate(Text,Font) :- self <- update(constructor(output(Text,Font))),

self <- gmcreate(self).

out(Text) :- self <- gmsend(out(Text)).

close_object gm_output.

%------------------------------------------------------------------------------------

% gm_hbox.

%------------------------------------------------------------------------------------

% create subobjects to this class to describe your different hboxes

open_object gm_hbox.

super(gm_window_content).

gmcreate :- bagof1(ContentRef,

(self <- gmnewcontent(ContentRef)), ContentRefs),

self <- update(contentref(ContentRefs)),

self <- gmcreate_hbox(ContentRefs).

gmcreate_hbox(Items) :- self <- update(constructor(hbox(Items))),

self <- gmcreate(self).

close_object gm_hbox.

%------------------------------------------------------------------------------------

% gm_vbox.

%------------------------------------------------------------------------------------

% create subobjects to this class to describe your different hboxes

open_object gm_vbox.

super(gm_window_content).

gmcreate :- bagof1(ContentRef,(self <- gmnewcontent(ContentRef)),ContentRefs),

self <- update(contentref(ContentRefs)),

self <- gmcreate_vbox(ContentRefs).

gmcreate_vbox(Items) :-

self <- update(constructor(vbox(Items))),

self <- gmcreate(self).

close_object gm_vbox.

%------------------------------------------------------------------------------------

% gm_menu.

%------------------------------------------------------------------------------------

open_object gm_menu.

super(gm_object).

gmcreate :- self <- gmcreate([quit],[quit]).

gmcreate(Messages,Items) :-

self <- update(constructor(menu(Messages,Items))),

self <- gmcreate(self).

close_object gm_menu.

%------------------------------------------------------------------------------------

% gm_graphic.

%------------------------------------------------------------------------------------

open_object gm_graphic.

super(gm_object).

% dynamic(in_view/1).

% tells in what gm_view object this graphic resides

%

% graphicspec(Spec)

% specifies which graphic we are talking about

gmnewgraphic(View,GraphicRef) :-

self <- new(W),

W <- graphicspec(S0),

S0=..[Name|Args],

S1=..[Name,GraphicRef|Args],

View <- gmsend(S1),

W <- update(in_view(View)),

W <- update(gm_ref(GraphicRef)).

move(Dx,Dy) :- self <- in_view(W),

self <- gm_ref(R),

W <- gmsend(move(R,Dx,Dy)).

moveto(Dx,Dy) :- self <- in_view(W),

self <- gm_ref(R),

W <- gmsend(moveto(R,Dx,Dy)).

rotate(Ref,Angle) :- self <- in_view(W),

self <- gm_ref(R),

W <- gmsend(rotate(R,Ref,Angle)).

scale(Xs,Ys) :- self <- in_view(W),

self <- gm_ref(R),

W <- gmsend(scale(R,Xs,Ys)).

remove :- self <- in_view(W),

self <- gm_ref(R),

W <- gmsend(remove(R)),

self <- delete.

close_object gm_graphic.

 

 

%------------------------------------------------------------------------------------

% gm_line.

%------------------------------------------------------------------------------------

open_object gm_line.

super(gm_graphic).

% graphicspec(line(X1,Y1,X2,Y2)).

close_object gm_line.

%------------------------------------------------------------------------------------

% gm_ellipse.

%------------------------------------------------------------------------------------

open_object gm_ellipse.

super(gm_graphic).

% graphicspec(ellipse(X,Y,R1,R2)).

close_object gm_ellipse.

%------------------------------------------------------------------------------------

% gm_fillellipse.

%------------------------------------------------------------------------------------

open_object gm_fillellipse.

super(gm_graphic).

% graphicspec(fillellipse(X,Y,R1,R2))

close_object gm_fillellipse.

%------------------------------------------------------------------------------------

% gm_circle.

%------------------------------------------------------------------------------------

open_object gm_circle.

super(gm_graphic).

% graphicspec(circle(X,Y,R))

close_object gm_circle.

%------------------------------------------------------------------------------------

% gm_fillcircle.

%------------------------------------------------------------------------------------

open_object gm_fillcircle.

super(gm_graphic).

% graphicspec(fill_circle(X,Y,R)).

close_object gm_fillcircle.

%------------------------------------------------------------------------------------

% gm_rect.

%------------------------------------------------------------------------------------

open_object gm_rect.

super(gm_graphic).

% graphicspec(rect(X1,Y1,X2,Y2)).

close_object gm_rect.

%------------------------------------------------------------------------------------

% gm_fillrect.

%------------------------------------------------------------------------------------

open_object gm_fillrect.

super(gm_graphic).

% graphicspec(fillrect(X1,Y1,X2,Y2)).

close_object gm_fillrect.

 

 

%------------------------------------------------------------------------------------

% gm_polygon.

%------------------------------------------------------------------------------------

open_object gm_polygon.

super(gm_graphic).

% graphicspec(polygon(Xedges,Yedges)).

close_object gm_polygon.

%------------------------------------------------------------------------------------

% gm_fillpolygon.

%------------------------------------------------------------------------------------

open_object gm_fillpolygon.

super(gm_graphic).

% graphicspec(fillpolygon(Xedges,Yedges))

close_object gm_fillpolygon.

%------------------------------------------------------------------------------------

% gm_string.

%------------------------------------------------------------------------------------

open_object gm_string.

super(gm_graphic).

% graphicspec(string(X,Y,Text)).

newtext(Text) :- self <- gm_ref(R),

self <- in_view(W),

W <- gmsend(newtext(R,Text)).

close_object gm_string.

%------------------------------------------------------------------------------------

% gm_bitmap.

%------------------------------------------------------------------------------------

open_object gm_bitmap.

super(gm_graphic).

% graphicspec(bitmap(X,Y,Filename)).

close_object gm_bitmap.

%------------------------------------------------------------------------------------

 

User defined objects based on gm objects

% Examples of user defined objects for gm objects, i.e. use of gm objects

call_me :- gm <- start, my_window <- gmnew(W), W <- open, gm <- run.

%------------------------------------------------------------------------------------

% example of a window containing a possible object (only one NB)

%------------------------------------------------------------------------------------

open_object my_window.

super(gm_window).

content(my_vbox).

close_object my_window.

%------------------------------------------------------------------------------------

% example of a vertical box

%------------------------------------------------------------------------------------

open_object my_vbox.

super(gm_vbox).

content(my_hbox).

content(frame(space(gm_button))).

content(my_hbox).

content(border).

content(frame(my_view)).

content(my_hbox).

content(space(frame(gm_button))).

close_object my_vbox.

%------------------------------------------------------------------------------------

% example of a horizontal box

%------------------------------------------------------------------------------------

open_object my_hbox.

super(gm_hbox).

content(gm_slider).

content(space).

content(scroller(my_view)).

content(scroller(my_text)).

content(gm_output).

content(gm_input).

content(border).

content(gm_button).

close_object my_hbox.

%------------------------------------------------------------------------------------

% example of a text

%------------------------------------------------------------------------------------

open_object my_text.

super(gm_text).

% to implement: ... content('my_textfile').

close_object my_text.

%------------------------------------------------------------------------------------

% example of an input

%------------------------------------------------------------------------------------

open_object my_input.

super(gm_input).

close_object my_input.

%------------------------------------------------------------------------------------

% example of a view

%------------------------------------------------------------------------------------

open_object my_view.

super(gm_view).

graphic(my_line0).

graphic(my_line1).

graphic(my_line2).

graphic(my_line3).

close_object my_view.

%------------------------------------------------------------------------------------

% example of movable lines

%------------------------------------------------------------------------------------

open_object my_line0.

super(gm_line).

% in_view/1.

% graphicspec(line(X1,Y1,X2,Y2)).

graphicspec(line(0,0,100,100)).

close_object my_line0.

open_object my_line1.

super(gm_line).

% in_view/1.

% graphicspec(line(X1,Y1,X2,Y2)).

graphicspec(line(100,100,50,100)).

close_object my_line1.

open_object my_line2.

super(gm_line).

% in_view/1.

% graphicspec(line(X1,Y1,X2,Y2)).

graphicspec(line(50,100,0,0)).

close_object my_line2.

open_object my_line3.

super(gm_line).

% in_view/1.

% graphicspec(line(X1,Y1,X2,Y2)).

graphicspec(line(25,50,75,50)).

close_object my_line3.

 

Comments and conclusions

The object hierarchy defined above shows a natural way to utilize SICStus Objects in the construction of graphical user interface software. Our conclusion is that the readability, and thereby maintainability, of the code is increased by using SICStus Objects for applications of this kind. The semantics of the event handling and object construction is simplified compared to coding in Prolog directly. As usual issues of this kind are to a large extent a matter of taste, but the example shows that object orientation can help in clarifying the code of a user interface. The limitations of the approach have to do with efficiency of component updates rather than expressibility, something a better compiler might overcome. In the case of user interface design this limitation is not crucial since there are many other sources of overhead in the user interface software, such as X-windows.

Acknowledgements

The work presented here was done in the basic program of LPSlab, the Logic Programming and Parallel Systems Laboratory at SICS, the Swedish Institute of Computer Science. The basic program is funded by Ericsson Telecom, NobelTech Systems AB, Televerket, FMV (the Defense Materiel Administration), Asea Brown Boveri (ABB) and IBM Sweden together with the Swedish government's department of industry.

The work leading to the implementation of SICStus Objects was made by Seif Haridi while visiting the American University in Cairo, Egypt, under a contract from the United Nations [Egy88]. SICStus Objects was later implemented by Seif Haridi and Kent Boortz. A specification for a similar system was made by the author in [T92:01] for the project Applied Logic Programming, hosted by LPSlab for Ellemtel Utvecklings AB, a development company jointly owned by Ericsson and Televerket, (the Swedish Telecom).

GM was developed as a part of the IT4-project ISP, Industrialisation of SICStus Prolog, by Jan Sundberg and Claes Frisk under the supervision of the author. GM has been further refined by Stefan Andersson and Kent Boortz.

The author wishes to acknowledge Dan Sahlin, Kent Boortz, Ulf Bilting, Stephen Pope, Lennart E. Fahlén, Janusz Launberg and Per Kreuger for their help in making this text readable. Mats Carlsson is the main designer of SICStus Prolog. Seif Haridi initiated and supported the work reported here with many insightful comments in particular regarding the mechanisms used for inheritance and shadowing in SICStus Objects.

 

References

[ALPES90] J.A.S. Allegria, A. Natali, N. Preston, C. Ruggieri, ALPES Final Report, Advanced Logic Programming Environments, (ESPRIT P973), CRIL Frankrike, DEIS, Univ. of Bologna, Italy, ENIDATA, Italy, LRI, Univ. of Paris-Orly och LSI, Univ. of Toulouse, France, Techn. Univ. of Munich, Germany, Universidade Nova de Lisboa, Portugal, 1990

[AlAnFlFrNiSu91] J. Almgren, S. Andersson, L. Flood, C. Frisk, H. Nilsson, J. Sundberg, SICStus Prolog Library Manual, SICS technical report T91:12B, September 1991

[CaWiJAnSAnBoNiSj91] M. Carlsson, J. Widén, J. Andersson, S. Andersson, K. Boortz, H. Nilsson, T. Sjöland, SICStus Prolog User's Manual , SICS technical report T91:11B, September 1991

[Haridi91] S. Haridi, SICS, Expert Systems for Improved Crop Management, report for the UN, Egy/88/024, September 1991

[Fromherz91] M. P. J. Fromherz, Explore/L An Object-Oriented Logic Language, Institut für Informatik der Universität Zürich, Nr 91.06, June 1991

[KIWIS91] M. Ahlsén, A D'Atri, P Johannesson, E. Laenens, N. Leone, P. Rullo, P. Rossi, F. Staes, L. Tarantino, L. Van Beirensdonck, F. van Cadsand, W. Van Santvliet, J. Vanslembrouck, B. Verdonk, D. Vermeir (ed.), The KIWIS Knowledge Base Management System, Report 91-23, Universitaire Instelling Antwerpen, Belgium, March 1991

[McCabe89] F. G. McCabe, Logic and Objects, Ph.D. thesis, Dept. of Computing, Imperial College of Science and Technology, 1989

[Moss90] C. Moss, An Introduction to Prolog++, Chris Moss, Res. rep. DOC 90/10

Imperial College of Science, Technology and Medicine, London, 1990

[Sahlin90] D. Sahlin, Jämförelse mellan Ada och C++, SICS, 1990, (in Swedish)

[Sjöland92] T. Sjöland, Objektorientering i SICStus Prolog, SICS technical report T92:01, January 1992 (in Swedish)

[Shriver87] Eds. B.Shriver and P. Wegner, Research Directions in Object-Oriented Programming, MIT Press 1987

[Vasey, Spencer et.al.90] P. Vasey, C. Spencer, D. Westwood, A. Westwood, Prolog++, version 1.0 Programming Reference Manual, Logic Programming Associates Ltd., London, England, 1990

[Vermeir et.al.89] E Laenens, Philips Applications & Software Services, Eindhoven, The Netherlands, D. Vermeir, B. Verdonk, Dept. of Computer Science, Univ. of Antwerp, Belgien, LOCO, a Logic-based Language for Complex Objects. I Proc. of ESPRIT'89 Technical Conf., Project 2424, pp.604-616 , 1989

[Vermeir et.al.91a] D. Vermeir, Dept. of Mathematics and Computer Science, Univ. of Antwerp, A simple interface from LOCO to X-Windows, report from ESPRIT project 2424, 1991

[Vermeir et.al.91b] E. Laenens, D. Vermeir, Univ. of Antwerp, Belgien, N. Leone, P. Rullo, CRAI Italien, Efficient query evaluation in a language combining object-oriented and logic programming, report from ESPRIT project 2424, 1991