The graphical user interface

1.Introduction

1.1.Main architecture

The graphical toolkit used by Edit has two main components: an abstract window interface, which is very similar to X Window, and the actual toolkit. At this moment an abstract window interface for X Window has been implemented, but others might be added in the feature.

The abstract window interface consists of two main classes: display and window. The display class is responsible for

The window class is responsible for

In particular, the window-class inherits from the ps_device-class, which is responsible for providing the basic set of postscript-compatible graphical routines. Hence, user applications will draw their graphics on a ps_device, since this will both allow them to visualize them in a window or on a printer.

The graphical toolkit built on top of the abstract window interface is widget oriented. A large number of widget classes are implemented, which all inherit from the abstract widget class. Widgets may have a finite number of children and they are responsible for

Later a special widget-style manager will be implemented, which will be able to present widgets according to different “styles”.

1.1.1.A simple example

In order to create a window “Test” with the text “Hello world” in it, one first opens the display, then create the widget with the text and finally the window, with the widget attached to it

    display dis= open_display ();

widget wid= text_widget ("Hello world");

window win= plain_window (wid, dis, "Test");

Technically speaking, the window creation amounts to several actions:

The next step is to make the window visible by

    win->map ();

At this points repaint request events are generated, which are handled when starting the event loop by

    dis->event_loop ();

Eventually, the window win will be destroyed using

    delete win;

At this point, control is handled back by the event loop and we close and destroy the display by

    close_display (dis)

delete dis;

The user only has to bother about destroying window and displays; widgets are automatically destroyed if they are no longer being referenced to.

1.2.Widgets and event processing

From the implementation point of view, widgets are pointers to instances of the abstract widget representation class widget_rep. Moreover, the widget class supports reference counting. The widget_rep class contains information about the window it is attached to and its location within this window, its size, the position of the origin of its local coordinates (north_west, north, etc.) and its children. The widget_rep class also provides a virtual event handler widget_rep::handle. This handler returns true if the event could be handled.

The implemented widget representation classes are organized in a hierarchy, which contains both concrete and abstract classes. The abstract classes reimplement the virtual event handler widget_rep::handle in such a way that if the event is recognized, then the event is dispatched to a more particular virtual event handler, possibly after some processing.

For example, instances of the basic_widget_rep class can handle the most common events, such as keyboard, mouse, repaint events and so on. If a key is pressed, then the virtual function basic_widget_rep::handle_keypress is called with an argument of type keypress_event. The default implementation of this virtual function does nothing.

The user can also create his own events, which he can pass to any widget. For instance in order to invalidate a region for redrawing, one creates an invalidate event using emit_invalidate and sends it to the appropriate widget using the operator <<. Notice that the user is responsible for sending events to widgets which can handle them. Bad matches are only discovered at run time, in which case an error is generated by <<.

1.2.1.A simple example

Suppose that we want to make a widget, which tracks keyboard events and which displays them. Such a widget must have a construction routine and keyboard and repaint handlers:

    class track_widget_rep: basic_widget_rep {

string last_key;

track_widget_rep ();

void handle_keypress (keypress_event ev);

void handle_repaint (repaint_event ev);

};

The constructor is taken to be empty and places the origin at the center of the widget

    track_widget_rep::track_widget_rep (): basic_widget (center) {}

In particular last_key is initialized by the empty string. We also define the function

    void

track_widget () {

return new track_widget_rep ();

}

in order to create an instance of a track_widget.

The event handler for keyboard events should just reset the string last_key and invalidate the entire widget region.

    void

track_widget_rep::handle_keypress (keypress_event ev) {

last_key= ev->key;

this << emit_invalidate_all ();

}

The event handler for repainting first determines the string to be repainted as a function of last_key, computes its extents and repaints it at the center.

    void

track_widget_rep::handle_repaint (repaint_event ev) {

string s= (last_key == ""? "No key pressed": "Pressed " * last_key);



SI x1, y1, x2, y2;

win->get_extents (s, x1, y1, x2, y2); // CHECK THIS



win->set_color (black);

win->fill (ev->x1, ev->y1, ev->x2, ev->y2);

win->set_color (white);

win->draw_string (s, -(x1+x2)>>1, -(y1+y2)>>1);

}

2.The abstract window interface

2.1.Displays

3.Widget principles

3.1.The widget class

Widgets are pointers to instances of the abstract widget representation class widget_rep. Widgets support reference counting, so that a widget is automatically destroyed if it is not used any more (except in the case of circular referencing; see below). As a general rule, the user does not have to worry about the creation and destruction of widgets.

3.1.1.The widget representation class

The definition of the widget_rep class goes as follows:

    struct widget_rep: rep_struct {

window win; // underlying window

SI ox, oy; // origin of widget in window

SI w, h; // width and height of widget

gravity grav; // position of the origin in the widget

array<widget> a; // children of widget

array<string> name; // names for the children



widget_rep (array<widget> a, array<string> name, gravity grav);

virtual ~widget_rep ();



virtual operator tree () = 0;

virtual bool handle (event ev) = 0;



SI x1 (); SI y1 (); // lower left window coordinates of widget

SI x2 (); SI y2 (); // upper right window coordinates of widget

bool attached (); // tests whether (win != NULL)

volatile void fatal_error (string mess, string rout="", string fname="");



friend class widget;

};

The win field specifies the window to which the widget is attached (win=NULL, by default). The origin (ox,oy) of the widget is specified with respect to the windows origin. Next come the width w and the height h of the widget. The gravity grav determines where the origin of the widget is located (north_west, north, etc.). The array a specifies the children of the widget. The array name gives names to the children of the widget. This is useful for addressing children by comprehensible names; the names are also useful for designing menu widgets.

The virtual type casting operator for trees is used for debugging purposes; mainly in order to print widgets. The virtual member function handle processes an event which is send to the widget and returns TRUE if the event could be handled and FALSE if not.

3.1.2.The widget class

The definition of the widget class goes as follows:

    struct widget {

#import null_indirect_h (widget, widget_rep)

inline widget (widget_rep* rep2): rep (rep2) {

if (rep!=NULL) rep->ref_count++; }

inline widget operator [] (int i) { return rep->a[i]; }

widget operator [] (string s);

inline operator tree () { return (tree) (*rep); }

inline bool operator == (widget w) { return rep == w.rep; }

inline bool operator != (widget w) { return rep != w.rep; }

};

Widgets may be constructed in two ways. First, we may construct a symbolic “nil” widget by widget (). The function bool nil (widget) is provided in order to test whether a widget is “nil”. Secondly, we may construct a widget from a pointer of type widget_rep*.

The reference counting mechanism ensures widgets to be destroyed when they are no longer pointed to. An important exception is when two widgets point one to each other, which fools the reference counter (for instance a scrollbar and the widget which is scrolled need to point one to each other). In order to deal with such “circular dependencies”, one works directly with widget_rep* pointers if one does not want to the pointer to be taken into account in the reference counter.

Child widgets can again be accessed to in two ways. First, we have the direct way, using its index in the array a. Secondly, we can access to a child via its name. Actually, when using this method, a get_widget event is generated. In the basic widget class, the default action for this event is to search in the name array for the child. However, the user may override this default action and provide another child searching method.

3.2.The event class

Events are pointers to instances of the abstract event_rep class, which supports reference counting. Actually, concrete event representation classes just contain some information. Hence, events actually provide a safe and generic way to store and communicate information.

3.2.1.The event representation class

The definition of the event_rep structure is as follows:

    struct event_rep: public rep_struct {

int type; // the event type

inline event_rep (int type2): rep_struct (0), type (type2) {}

inline virtual ~event_rep () {}

virtual operator tree () = 0; // for displaying events (debugging)

};

The type field gives the type of the event. A complete list of the event types if given in the file

    Window/Event/event_codes.hpp

For each project which uses new event types, an analogue file should be made and the numbers of the event types should all be different. Unfortunately, there is no safe way in order to let this job be done by the compiler.

3.2.2.The event class

The event structure is defined by

    struct event {

#import indirect_h (event, event_rep)

inline event (event_rep* rep2): rep (rep2) {

if (rep!=NULL) rep->ref_count++; }

inline operator tree () { return (tree) (*rep); }

};

#import indirect_cc (event, event_rep)

3.2.3.Concrete event classes

Concrete event classes again come into two parts: the class itself and its representation class. For instance, the representation class for get_widget events is defined by

    struct get_widget_event_rep: public event_rep {

string which; widget& w;

get_widget_event_rep (string which, widget& w);

operator tree ();

};

The corresponding get_widget_event class is defined by

    #import event (get_widget_event, get_widget_event_rep)

The module event with two parameters is defined by

    #module event (T, R)

struct T {

R* rep;

T (T& ev);

~T ();

T (event& ev);

operator event ();

R* operator -> ();

T& operator = (T ev);

};

#endmodule // event (T, R)

The important thing to notice is that we have converters from and to the generic event class. Moreover, the generic event class and the specific get_widget_event class are compatible from the reference counting point of view.

The implementation of the get_widget_event_rep class is as follows:

    get_widget_event_rep::get_widget_event_rep (string ww, widget& w2):

event_rep (GET_WIDGET_EVENT), which (ww), w (w2) {}

get_widget_event_rep::operator tree () {

return tree ("get_widget_event", which); }

#import code_event (get_widget_event, get_widget_event_rep)

The actual events are created by

    event get_widget (string which, widget& w) {

return new get_widget_event_rep (which, w); }

3.2.4.Event handlers

Implementations of the generic event handler bool widget_rep::handle(event) usually do the following

For instance, the event handler for composite widgets is as follows:

    bool

composite_widget_rep::handle (event ev) {

switch (ev->type) {

case CLEAN_EVENT:

handle_clean (ev);

return TRUE;

case INSERT_EVENT:

handle_insert (ev);

return TRUE;

case REMOVE_EVENT:

handle_remove (ev);

return TRUE;

}

return basic_widget_rep::handle (ev);

}

The member function handle_insert is implemented as follows:

    void

composite_widget_rep::handle_insert (insert_event ev) {

a << ev->w;

name << ev->s;

}

In particular, we can retrieve the fields w and s from insert_event_rep from the insert_event in the member function.

3.2.5.Adding your own event classes

Summarizing, in order to add your own new event classes, you have to take care of the following steps:

3.3.The main event loop

The main event loop does the following

3.4.Coordinates

3.4.1.Coordinates, pixels and rounding

All coordinates and sizes are represented by instances of type SI, which is nothing but another name for int. The SI constant PIXEL, which is a power of two 1 << PIXEL_SHIFT denotes the size of a pixel on the screen. Since PIXEL>1, coordinates and sizes are not necessarily integer multiples of the pixel size. However, the coordinates of the origin and the size of a widget should always be such multiples.

In order to achieve this, some rounding functions are provided. The function round (SI&) rounds the argument to an integer multiple of PIXEL. Furthermore, the window member functions

    void inner_round (SI& x1, SI& y1, SI& x2, SI& y2);

void outer_round (SI& x1, SI& y1, SI& x2, SI& y2);

transform a rectangle into a new one with integer multiple of PIXEL coordinates, which is enclosed resp. encloses the original rectangle.

3.4.2.Local and global coordinates

Each widget has an origin (ox,oy) with respect to the window to which it has been attached. This is the origin of the “local coordinates”. The origin of the “global coordinates” is the origin of the window. The location of the local origin in the widget is determined by the widget's gravity, which is either one of north_west, north, north_east, west, center, east, south_west, south or south_east.

As a general rule, events are transmitted in global coordinates. Nevertheless, in widgets which are derived from the abstract basic widget class, by default, all computations are done with respect to local coordinates. This is due to two reasons

3.4.3.Screen coordinates

For some very particular purposes, such as popping up windows, one has to perform computations with respect to the screen coordinates. Given a point (x,y) in the coordinates of some window win, the screen coordinates of (x,y) are obtained by adding the windows origin, which is obtained by calling win->get_position (ox,oy).

3.5.Attaching and positioning widgets

3.5.1.Attaching widgets

When a widget is created, the win field of its representation is set to NULL, since it is not yet attached to a window. In order to attach a widget w to a window win, one emits an attach_window_event:

    w << emit_attach_window (win);

Notice that taking win==NULL results in detaching the widget. Notice also that a widget may be attached to at most one window: attempts to reattach a widget, which is already attached, to another window, result in a fatal error.

Some events can be handled by widgets which are not yet attached to a window, such as:

For some of these events, such as attribute changes, it may be necessary to emit invalidate events in case when the widget had been attached to some window. In order to test this one uses the member function

    bool widget_rep::attached ();

3.5.2.Positioning widgets

When an appropriate size (w,h) has been determined for a widget (using “get size” events) and when a widget has been attached to some window, the widget is positioned in the main window. By default, all children are recursively positioned at the top left of the window at sizes (w,h). But for complex widgets with children, a specific positioning routine usually has to be implemented.

Such a routine involves positioning of the children within the parent. This is done by emitting position events to the children. For instance,

    a[i] << emit_position (x[i], y[i], w[i], h[i], center);

positions the i-th child, such that the origin of a[i] is at position (x[i], y[i]) w.r.t. the local coordinates of this and such that the origin is situated in the center of a[i]. The width and height of a[i] are set to w[i] resp. h[i].

3.5.3.Repositioning widgets

During execution, it may happen that a particular widget has changed, so that it obtains a different size and/or position. In this case, one emits an update_event to the closest ancestor, whose position and size did not change.

For instance, consider the case of a footer footer, which consists of a left footer footer["left"], followed by some glue footer["middle"] and a right footer footer["right"]. When the left footer changes:

    footer << set_widget ("left", text_widget ("new text"));

the size of footer["left"] changes, and the size and position of the glue should also be changed. Nevertheless, the size and position of footer remain unaltered, whence we update footer:

    footer << emit_update ();

Updating an attached widget results in three actions to take place:

3.6.The keyboard

3.6.1.Keyboard focus

Each window win on the screen determines a main widget win->w which is attached to it and a descendant win->kbd_focus of this widget, which handles the keyboard input directed to the window. This latter widget win->kbd_focus, which is set to win->w by default, is said to have keyboard focus, if the window win has keyboard focus (i.e. if all keyboard events are sent to this window). Consequently, the widget which has keyboard focus receives all keyboard events.

When the keyboard focus of a window win changes, a keyboard_focus_event is sent to win->kbd_focus. The field ev->flag of this event ev is TRUE if the window got the focus, and FALSE if the window lost focus.

The keyboard focus widget win->kbd_focus associated to a window can be changed by calling the window_rep member function

    void window_rep::set_keyboard_focus (widget);

Setting the input focus to another widget than win->w is useful, for instance, if a particular text input field of some form needs keyboard focus after a mouse click on it.

3.6.2.Keyboard events

When a widget has the keyboard focus, and a key is pressed, it receives a keypress_event. The keypress_event_rep class contains a field key, which contains a comprehensible string corresponding to the key which was pressed.

More precisely, key is either a one character string, or a symbolic name like "<return>", "<right>", "<del>", etc. or a composed name like "<shift-F1>", "<ctrl-esc>" or "<meta-x>". The complete list of keys is as follows:

    "<F1>", "<F2>", "<F3>", "<F4>", "<F5>", "<F6>",

"<F7>", "<F8>", "<F9>", "<F10>", "<F11>", "<F12>",

"<esc>", "<tab>", "<less>", "<gtr>", "<del>", "<return>",

"<ins>", "<home>", "<end>", "<page-down>", "<page-up>",

"<left>", "<up>", "<down>", "<right>"

The keys "<less>" and "<gtr>" correspond resp. to "<" and ">". The allowed modifiers are "shift", "ctrl" and "meta" or combinations of these.

3.7.The mouse

3.7.1.Mouse events

A mouse event ev occurs on a button change or a mouse movement. The ev->type field contains the type of the event and ev->x and ev->y the corresponding coordinates of the mouse. Finally, the states of the mouse buttons can be questioned using the routine ev->pressed (string).

The possible values of ev->type on button change events are the following:

    "press-left", "press-middle", "press-right",

"release-left", "release-middle", "release-right"

The possible values for mouse movement events are

    "move", "enter", "leave"

The "enter" and "leave" events occur when the mouse enters resp. leaves the widget. Finally, the states of the left, middle and right mouse buttons can respectively be obtained using the calls

    ev->pressed ("left")

ev->pressed ("middle")

ev->pressed ("right")

3.7.2.Grabbing the mouse

For some applications such as popup menus or scrollbars, it is useful to direct all mouse events to a particular widget w. This is done by grabbing the mouse by emitting the event

    w << emit_grab_mouse (TRUE)

After such a grab, all mouse events are directed to w, even those events which occurred before the grab (contrary to X Window). The mouse grab is released by

    w << emit_grab_mouse (FALSE)

Actually, the display keeps track of a list of widgets for which a mouse grab occurred: if the mouse is grabbed by widgets w1 next w2, and again ungrabbed by w2, then all mouse events are again sent to w1. This feature is useful for successive grabs by recursive popup menus.

When a widget w1 grabs the mouse, and a previous mouse grab on a widget w2 is still active, then a "leave" event is sent to w2 and an "enter" event to w1. Similarly, if w1 releases the grab, then a "leave" event is sent to w1 and an "enter" event to w2.

3.8.The screen

Each window keeps track of a list of rectangles to be repainted (moreover, redundant rectangles are eliminated automatically and adjacent rectangles are transformed in larger rectangles). During the repaint stage in the event loop, the widget is requested to repaint these rectangles.

3.8.1.Repainting rectangles

The repaint handler takes on input a repaint_event ev, which determines the rectangle to be repainted. Moreover, repaint_event_rep contains a boolean field stop, which can be set in order to indicate that the repaint process was stopped somewhere in the middle.

Indeed, for widgets which take a long time to be repainted, it may be useful to abort repainting if a key is pressed. The arrival of an event which aborts repainting can be checked directly on the postscript device dev:

    if (dev->check_event (EVENT_STATUS)) { ... } // CHECK THIS

In the case of a window, such an event may signify that a key has been pressed; in the case of a printer, it might suggest the printer being turned off.

If the application decides to abort repainting, it sets ev->stop to TRUE. The rectangle which was being repainted is put back on the invalid rectangles list in the event loop; it will be processed again during the next pass through the repaint phase.

3.8.2.Invalidation of rectangles

When window is mapped on the screen or when a region is exposed, the window interface automatically invalidates the corresponding rectangle. The user may also invalidate a rectangle by using either one of the routines

    event emit_invalidate_all ();

event emit_invalidate (SI x1, SI y1, SI x2, SI y2);

The first routine creates an event to invalidate the entire widget area; the other routine invalidates a specified region.

3.9.The toolkit

3.9.1.Other standard widget classes

Many widgets from the toolkit are derived from some other standard abstract widget classes, which can handle some other special events.

3.9.2.Composite widgets

These widgets allow to add or remove children to or from a widget. This makes them particularly useful for menu widgets. They respond to clean, insert and remove events.

3.9.3.Attribute widgets

These widgets allow to set window attributes of some common types such as integers, strings, commands, points, etc. They can be used for instance to retrieve an input string or in order to set the scroll position in a canvas widget.

3.9.4.Glue widgets

Glue widgets are created by

    widget glue_widget (bool hext=TRUE, bool vext=TRUE, SI w=0, SI h=0);

The first two arguments determine whether the widget is extensible horizontally resp. vertically. The last two elements determine the default and minimal size of the widget.

3.9.5.Text widgets

Text widgets are created using

    widget text_widget (string s);

They just display the text s.

3.9.6.Buttons

Two types of buttons have been implemented. First, command buttons are created using

    widget command_button (string s, command cmd);

They display the text s and execute the command cmd when pressed. Secondly, we implemented popup buttons, which popup some window when pressed. Popup buttons are created by one of

    widget pulldown_button (string s, widget m);

widget pullright_button (string s, widget m);

depending on where the popup window should popup. The main widget attached to the popup window should be created using

    widget popup_widget (widget w, gravity quit);

The quit argument specifies that the popup window should disappear as soon as the pointer leaves the widget in the quit direction.

3.9.7.Menus

Horizontal and vertical menus are created using

    widget horizontal_menu ();

widget vertical_menu ();

By default, they are empty. Subsequently, they can be modified as composite widgets.

3.9.8.Canvas widgets

Canvas widgets are created using

    widget canvas_widget (widget w);

Canvas widget consist of a portion of the widget w and scrollbars, which enable to scroll w. The events

    event set_scrollable (widget w);

event set_extents (SI ew, SI ey);

event set_scroll_pos (SI x, SI y);

event get_extents (SI& ew, SI& eh);

event get_visible (SI& x1, SI& y1, SI& x2, SI& y2);

enable to change w, to set the extents of w, to set the scroll position, to get the extents of w and to get the rectangle of w, which is currently visible.

3.9.9.Input widgets

Input widgets enable to type a string and to retrieve it when finished. They are created using

    widget input_text_widget (command call_back);

Some initial text can be put in it using

    event set_input_string (string s);

The command call_back is executed when typing has been finished or aborted (by typing return, escape or ctrl-c). The typed string can then be retrieved using

    event get_input_string (string& s);

Usually, the returned s is a string enclosed between quotes. If typing was aborted, s contains the string "cancel".

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".