| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In some applications the standard object classes as provided by the Forms Library may not be enough for your task. There are three ways of solving this problem. First of all, the application program can also open its own window or use a canvas (the preferred way) in which it does interaction with the user). A second way is to add your own object classes (see Part IV). This is especially useful when your new type of objects is of general use.
The third way is to add free objects to your form. Free objects are objects for which the application program handles the drawing and interaction. This chapter will give all the details needed to design and use free objects.
| 5.1 Free Object | ||
| 5.2 An Example |
To add a free object to a form use the call
FL_OBJECT *fl_add_free(int type, FL_Coord x, FL_Coord y,
FL_Coord w, FL_Coord h,
const char *label, int (*handle)());
|
type indicates the type of free object, see below for a list
and their meaning. x, y, w and h are the
bounding box. The label is normally not drawn unless the
handle routine takes care of this. handle is the routine
that does the redrawing and handles the interaction with the free
object. The application program must supply this routine.
This routine handle is called by the library whenever
an action has to be performed. The routine should have the form:
int handle(FL_OBJECT *obj, int event, FL_Coord mx, FL_Coord my,
int key, void *xev);
|
where obj is the object to which the event applies.
event indicates what has to happen to the object. See below for
a list of possible events. mx and my indicate the
position of the mouse (only meaningful with mouse related events)
relative to the form origin and key is the KeySym of the key
typed in by the user (only for FL_KEYPRESS events). xev
is the (cast) XEvent that causes the invocation of this handler.
event and xev->type can both be used to obtain the event
types. The routine should return whether the status of the object has
changed, i.e., whether fl_do_forms() or
fl_check_forms() should return this object.
The following types of events exist for which the routine must take action:
FL_DRAWThe object has to be redrawn. To figure out the size of the object you
can use the fields obj->x, obj->y, obj->w and
obj->h. Some other aspects might also influence the way the
object has to be drawn. E.g., you might want to draw the object
differently when the mouse is on top of it or when the mouse is
pressed on it. This can be figured out as follows. The field
obj->belowmouse indicates whether the object is below the
mouse. The field obj->pushed indicates whether the object is
currently being pushed with the mouse. Finally, obj->focus
indicates whether input focus is directed towards this object. When
required, the label should also be drawn. This label can be found in
the field obj->label. The drawing should be done such that it
works correctly in the visual/depth the current form is in. Complete
information is available on the state of the current form as well as
several routines that will help you to tackle the trickiest (also the
most tedious) part of X programming. In particular, the return value
of fl_get_vclass() can be used as an index into a table
of structures, FL_STATE fl_state[], from which all information
about current active visual can be obtained. See section Drawing Objects, for details on drawing objects and the
routines.
FL_DRAWLABELThis event is not always generated. It typically follows
FL_DRAW and indicates the object label needs to be (re)drawn.
You can ignore this event if (a) the object handler always draws the
label upon receiving FL_DRAW or (b) the object label is not
drawn at all(9).
FL_ENTERThis event is sent when the mouse has entered the bounding box. This
might require some action. Note that also the
field belowmouse in the object is being set. If
entering only changes the appearance redrawing the object normally
suffices. Don't do this directly! Always redraw the object using the
routine fl_redraw_object(). It will
send an FL_DRAW event to the object but also does
some other things (like setting window id's, taking care of double
buffering and some other bookkeeping tasks).
FL_LEAVEThe mouse has left the bounding box. Again, normally a redraw is enough (or nothing at all).
FL_MOTIONA motion event is sent between FL_ENTER and FL_LEAVE
events when the mouse position changes on the object. The mouse
position is given with the routine.
FL_PUSHThe user has pushed a mouse button in the object. Normally this requires some action.
FL_RELEASEThe user has released the mouse button. This event is only sent if a
FL_PUSH event was sent earlier.
FL_DBLCLICKThe user has pushed a mouse button twice within a certain time limit
(FL_CLICK_TIMEOUT), which by default is about 400 msec.
FL_TRPLCLICKThe user has pushed a mouse button three times within a certain time
window between each push. This event is sent after a
FL_DBLCLICK, FL_PUSH, FL_RELEASE sequence.
FL_UPDATEThe mouse position has changed. This event is sent to an object
between an FL_PUSH and an FL_RELEASE event (actually
this event is sent periodically, even if mouse has not moved). The
mouse position is given as the parameter mx and my and
action can be taken based on the position.
FL_FOCUSInput got focussed to this object. This event and the next two are
only sent to a free object of type FL_INPUT_FREE (see below).
FL_UNFOCUSInput is no longer focussed on this object.
FL_KEYPRESSA key was pressed. The KeySym is given with the routine. This event
only happens between FL_FOCUS and FL_UNFOCUS events.
FL_STEPA step event is sent all the time (at most 50 times per second but
often less because of time consuming redraw operations) to a free
object of type FL_CONTINUOUS_FREE such that it can update its
state or appearance.
FL_SHORTCUTHotkeys for the object have been triggered. Typically this should result in the returning of the free object.
FL_FREEMEMUpon receiving this event, the handler should free all object class specific memory allocated.
FL_OTHERSome other events typically caused by window manager events or
inter-client events. All information regarding the details of the
events is in xev.
Many of these events might make it necessary to (partially) redraw the
object. Always do this using the routine
fl_redraw_object().
As indicated above not all events are sent to all free objects. It
depends on their types. The following types exist (all objects are sent
FL_OTHER when it occurs):
FL_NORMAL_FREEThe object will receive the events FL_DRAW, FL_ENTER,
FL_LEAVE, FL_MOTION, FL_PUSH, FL_RELEASE
and FL_MOUSE.
FL_INACTIVE_FREEThe object only receives FL_DRAW events. This should be used
for objects without interaction (e.g. a picture).
FL_INPUT_FREESame as FL_NORMAL_FREE but the object also receives
FL_FOCUS, FL_UNFOCUS and FL_KEYPRESS events. The
obj->wantkey is by default set to FL_KEY_NORMAL, i.e.,
the free object will receive all normal keys (0-255) except
<Tab> and <Return> key. If you're interested in
<Tab> or <Return> key, you need to change
obj->wantkey to FL_KEY_TAB or FL_KEY_ALL.
See section Events, for details.
FL_CONTINUOUS_FREESame as FL_NORMAL_FREE but the object also receives
FL_STEP events. This should be used for objects that change
themselves continuously.
FL_ALL_FREEThe object receives all types of events.
See `free1.c' for a (terrible) example of the use of free objects. See also `freedraw.c', which is a nicer example of the use of free objects.
Free objects provide all the generality you want from the Forms Library. Because free objects behave a lot like new object classes it is recommended that you also read part IV of this documentation before designing free objects.
We conclude our discussion of the free object by examining a simple drawing program capable of drawing simple geometric figures like squares, circles, and triangles of various colors and sizes, and of course it also utilizes a free object.
The basic UI consists of three logical parts. A drawing area onto which the squares etc. are to be drawn; a group of objects that control what figure to draw and with what size; and a group of objects that control the color with which the figure is to be drawn.
The entire UI is designed interactively using the GUI builder
fdesign with most objects having their own callbacks.
fdesign writes two files, one is a header file containing
forward declarations of callback functions and other function
prototypes:
#ifndef FD_drawfree_h_
#define FD_drawfree_h_
extern void change_color(FL_OBJECT *, long);
extern void switch_figure(FL_OBJECT *, long);
/* more callback declarations omitted */
typedef struct {
FL_FORM * drawfree;
FL_OBJECT * freeobj;
FL_OBJECT * figgrp;
FL_OBJECT * colgrp;
FL_OBJECT * colorobj;
FL_OBJECT * miscgrp;
FL_OBJECT * sizegrp;
FL_OBJECT * wsli;
FL_OBJECT * hsli;
FL_OBJECT * drobj[3];
void * vdata;
long ldata;
} FD_drawfree;
extern FD_drawfree *create_form_drawfree(void);
#endif /* FD_drawfree_h_ */
|
The other file contains the actual C-code that creates the form when
compiled and executed. Since free objects are not directly supported by
fdesign, a box was used as a stub for the location and size of the
drawing area. After the C-code was generated, the box was changed
manually to a free object by replacing
fl_add_box(FL_DOWN_BOX,...) with
fl_add_free(FL_NORMAL_FREE,...). We list below the output
generated by fdesign with some comments:
FD_drawfree *create_form_drawfree(void) {
FL_OBJECT *obj;
FD_drawfree *fdui = fl_calloc(1, sizeof *fdui);
fdui->drawfree = fl_bgn_form(FL_NO_BOX, 530, 490);
obj = fl_add_box(FL_UP_BOX, 0, 0, 530, 490, "");
|
This is almost always the same for any form definition: we allocate a
structure that will hold all objects on the form as well as the form
itself. In this case, the first object on the form is a box of type
FL_UP_BOX.
fdui->figgrp = fl_bgn_group();
obj = fl_add_button(FL_RADIO_BUTTON, 10, 60, 40, 40,
"@#circle");
fl_set_object_lcol(obj,FL_YELLOW);
fl_set_object_callback(obj, switch_figure, 0);
obj = fl_add_button(FL_RADIO_BUTTON, 50, 60, 40, 40,
"@#square");
fl_set_object_lcol(obj, FL_YELLOW);
fl_set_object_callback(obj, switch_figure, 1);
obj = fl_add_button(FL_RADIO_BUTTON, 90, 60, 40, 40,
"@#8*>");
fl_set_object_lcol(obj, FL_YELLOW);
fl_set_object_callback(obj, switch_figure, 2);
fl_end_group();
|
This creates three buttons that control what figures are to be drawn.
Since figure selection is mutually exclusive, we use
RADIO_BUTTON for this. Further, the three buttons are placed
inside a group so that they won't interfere with other radio buttons
on the same form. Notice that the callback function
switch_figure() is bound to all three buttons but with
different arguments. Thus the callback function can resolve the
associated object via the callback function argument. In this case, 0
is used for circle, 1 for square and 2 for triangle. This association
of a callback function with a piece of user data can often reduce the
amount of code substantially, especially if you have a large group of
objects that control similar things. The advantage will become clear
as we proceed.
Next we add three sliders to the form. By using appropriate colors for
these sliding bars (red, green, blue), there is no need to label them.
There's also no need to store their addresses as their callback routine
change_color() will receive them automatically.
fdui->colgrp = fl_bgn_group();
obj = fl_add_slider(FL_VERT_FILL_SLIDER, 25, 170, 30, 125, "");
fl_set_object_color(obj, FL_COL1, FL_RED);
fl_set_object_callback(obj, change_color, 0);
obj = fl_add_slider(FL_VERT_FILL_SLIDER, 55, 170, 30, 125, "");
fl_set_object_color(obj, FL_COL1, FL_GREEN);
fl_set_object_callback(obj, change_color, 1);
obj = fl_add_slider(FL_VERT_FILL_SLIDER, 85, 170, 30, 125, "");
fl_set_object_color(obj, FL_COL1, FL_BLUE);
fl_set_object_callback(obj, change_color, 2);
fdui->colorobj = obj = fl_add_box(FL_BORDER_BOX,
25, 140, 90, 25, "");
fl_set_object_color(obj, FL_FREE_COL1, FL_FREE_COL1);
fl_end_group();
|
Again, a single callback function, change_color(), is bound to
all three sliders. In addition to the sliders, a box object is added
to the form. This box is set to use the color indexed by
FL_FREE_COL1 and will be used to show visually what the current
color setting looks like. This implies that in the
change_color() callback function, the entry FL_FREE_COL1
in the Forms Library's internal colormap will be changed. We also
place all the color related objects inside a group even though they
are not of radio buttons. This is to facilitate gravity settings which
otherwise require setting the gravities of each individual object.
Next we create our drawing area which is simply a free object of type
NORMAL_FREE with a handler to be written
obj = fl_add_frame(FL_DOWN_FRAME, 145, 30, 370, 405, "");
fl_set_object_gravity(obj, FL_NorthWest, FL_SouthEast);
fdui->freeobj = obj = fl_add_free(FL_NORMAL_FREE,
145, 30, 370, 405, "",
freeobject_handler);
fl_set_object_boxtype(obj, FL_FLAT_BOX);
fl_set_object_gravity(obj, FL_NorthWest, FL_SouthEast);
|
The frame is added for decoration purposes only. Although a free object with a down box would appear the same, the down box can be written over by the free object drawing while the free object can't draw on top of the frame since the frame is outside of the free object. Notice the gravity settings. This kind of setting maximizes the real estate of the free object when the form is resized.
Next, we need to have control over the size of the object. For this, two sliders are added, using the same callback function but with different user data (0 and 1 in this case):
fdui->sizegrp = fl_bgn_group();
fdui->wsli = obj = fl_add_valslider(FL_HOR_SLIDER,
15, 370, 120, 25, "Width");
fl_set_object_lalign(obj, FL_ALIGN_TOP);
fl_set_object_callback(obj, change_size, 0);
fdui->hsli = obj = fl_add_valslider(FL_HOR_SLIDER,
15, 55, 410,25, "Height");
fl_set_object_lalign(obj, FL_ALIGN_TOP);
fl_set_object_callback(obj, change_size, 1);
fl_end_group();
|
The rest of the UI consists of some buttons the user can use to exit
the program, elect to draw outlined instead of filled figures etc. The
form definition ends with fl_end_form(). The structure
that holds the form as well as all the objects within it is returned
to the caller:
fdui->miscgrp = fl_bgn_group();
obj = fl_add_button(FL_NORMAL_BUTTON, 395, 445, 105, 30,
"Quit");
fl_set_button_shortcut(obj, "Qq#q", 1);
obj = fl_add_button(FL_NORMAL_BUTTON, 280, 445, 105, 30,
"Refresh");
fl_set_object_callback(obj, refresh_cb, 0);
obj = fl_add_button(FL_NORMAL_BUTTON, 165, 445, 105, 30,
"Clear");
fl_set_object_callback(obj,clear_cb,0); fl_end_group();
obj = fl_add_checkbutton(FL_PUSH_BUTTON, 15, 25, 100, 35,
"Outline");
fl_set_object_color(obj, FL_MCOL, FL_BLUE);
fl_set_object_callback(obj, fill_cb, 0);
fl_set_object_gravity(obj, FL_NorthWest, FL_NorthWest);
fl_end_form();
return fdui;
}
|
After creating the UI we need to write the callback functions and the free object handler. The callback functions are relatively easy since each object is designed to perform a very specific task.
Before we proceed to code the callback functions we first need to define the overall data structure that will be used to glue together the UI and the routines that do real work.
The basic structure is the DrawFigure structure that holds the current drawing function as well as object attributes such as size and color:
#define MAX_FIGURES 500
typedef void (*DrawFunc)(int /* fill */,
int, int, int, int, /* x,y,w,h */
FL_COLOR /* color */ );
typedef struct {
DrawFunc drawit; /* how to draw this figure */
int fill, /* is it to be filled? */
x, y, w, h; /* position and sizes */
int pc[3]; /* primary color R,G,B */
int newfig; /* indicate a new figure */
FL_COLOR col; /* color index */
} DrawFigure;
static DrawFigure saved_figure[MAX_FIGURES],
*cur_fig;
static FD_drawfree *drawui;
int max_w = 30, /* max size of figures */
max_h = 30;
|
All changes to the figure attributes will be buffered in
cur_fig and when the actual drawing command is issued (mouse
click inside the free object), cur_fig is copied into
saved_figure array buffer.
Forms Library contains some low-level drawing routines that can draw and optionally fill arbitrary polygonal regions, so in principle, there is no need to use Xlib calls directly. To show how Xlib drawing routines are combined with Forms Library, we use Xlib routines to draw a triangle:
void draw_triangle(int fill, int x, int y,
int w, int h, FL_COLOR col) {
XPoint xp[4];
GC gc = fl_state[fl_get_vclass()].gc[0];
Window win = fl_winget();
Display *disp = fl_get_display();
xp[0].x = x;
xp[0].y = y + h - 1;
xp[1].x = x + w / 2;
xp[1].y = y;
xp[2].x = x + w - 1;
xp[2].y = y + h - 1;
XSetForeground(disp, gc, fl_get_pixel(col));
if (fill)
XFillPolygon(disp, win, gc, xp, 3, Nonconvex, Unsorted);
else {
xp[3].x = xp[0].x;
xp[3].y = xp[0].y;
XDrawLines(disp, win, gc, xp, 4, CoordModeOrigin);
}
}
|
Although more or less standard stuff, some explanation is in order. As
you have probably guessed, fl_winget() returns the
current "active" window, defined to be the window the object receiving
dispatcher's messages (FL_DRAW e.g.) belongs to(10). Similarly the routine
fl_get_display() returns the current connection to the X
server. Part IV has more details on the utility functions in the Forms
Library.
The structure fl_state[] keeps much "inside" information on the
state of the Forms Library. For simplicity, we choose to use the Forms
Library's default GC. There is no fundamental reason that this has be
so. We certainly can copy the default GC and change the foreground
color in the copy. Of course unlike using the default GC directly, we
might have to set the clip mask in the copy whereas the default GC
always have the proper clip mask (in this case, to the bounding box of
the free object).
We use the Forms Library's built-in drawing routines to draw circles and rectangles. Then our drawing functions can be defined as follows:
static DrawFunc drawfunc[] = {
fl_oval, fl_rectangle, draw_triangle };
|
Switching what figure to draw is just changing the member
drawit in cur_fig. By using the proper object callback
argument, figure switching is achieved by the following callback
routine that is bound to all figure buttons
void switch_object(FL_OBJECT *obj, long which) {
cur_fig->drawit = drawfunc[which];
}
|
So this takes care of the drawing functions. Similarly, the color callback function can be written as follows
void change_color(FL_OBJECT *obj, long which) {
cur_fig->c[which] = 255 * fl_get_slider_value(obj);
fl_mapcolor(cur_fig->col,
cur_fig->c[0], cur_fig->c[1], cur_fig->c[2]);
fl_mapcolor(FL_FREE_COL1,
cur_fig->c[0], cur_fig->c[1], cur_fig->c[2]);
fl_redraw_object(drawui->colorobj);
}
|
The first call of fl_mapcolor() defines the RGB
components for index cur_fig->col and the second
fl_mapcolor() call defines the RGB component for index
FL_FREE_COL1, which is the color index used by colorobj
that serves as current color visual feedback.
Object size is taken care of in a similar fashion by using a callback function bound to both size sliders:
void change_size(FL_OBJECT * obj, long which) {
if (which == 0)
cur_fig->w = fl_get_slider_value(obj);
else
cur_fig->h = fl_get_slider_value(obj);
}
|
Lastly, we toggle the fill/outline option by querying the state of the push button
void outline_callback(FL_OBJECT *obj, long data) {
cur_fig->fill = !fl_get_button(obj);
}
|
To clear the drawing area and delete all saved figures, a Clear button is provided with the following callback:
void clear_cb(FL_OBJECT *obj, long notused) {
saved_figure[0] = *cur_fig; /* copy attributes */
cur_fig = saved_figure;
fl_redraw_object(drawui->freeobj);
}
|
To clear the drawing area and redraw all saved figures, a Refresh button is provided with the following callback:
void refresh_cb(FL_OBJECT *obj, long notused) {
fl_redraw_object(drawui->freeobj);
}
|
With all attributes and other services taken care of, it is time to write the free object handler. The user can issue a drawing command inside the free object by clicking either the left or right mouse button.
int freeobject_handler(FL_OBJECT *obj, int event,
FL_Coord mx, FL_Coord my,
int key, void *xev) {
DrawFigure *dr;
switch (event) {
case FL_DRAW:
if (cur_fig->newfig == 1)
cur_fig->drawit(cur_fig->fill,
cur_fig->x + obj->x,
cur_fig->y + obj->y,
cur_fig->w, cur_fig->h,
cur_fig->col);
else {
fl_drw_box(obj->boxtype, obj->x, obj->y, obj->w,
obj->h, obj->col1, obj->bw);
for (dr = saved_figure; dr < cur_fig; dr++) {
fl_mapcolor(FL_FREE_COL1,
dr->c[0], dr->c[1], dr->c[2]);
dr->drawit(dr->fill,dr->x + obj->x,
dr->y + obj->y,
dr->w, dr->h, dr->col);
}
}
cur_fig->newfig = 0;
break;
case FL_PUSH:
if (key == FL_MIDDLE_MOUSE)
break;
cur_fig->x = mx - cur_fig->w / 2;
cur_fig->y = my - cur_fig->h / 2;
/* convert figure center to relative to the object*/
cur_fig->x -= obj->x;
cur_fig->y -= obj->y;
cur_fig->newfig = 1;
fl_redraw_object(obj);
*(cur_fig + 1) = *cur_fig;
fl_mapcolor(cur_fig->col + 1, cur_fig->c[0],
cur_fig->c[1], cur_fig->c[2] );
cur_fig++;
cur_fig->col++;
break;
}
return FL_RETURN_NONE;
}
|
In this particular program, we are only interested in mouse clicks and
redraw. The event dispatching routine cooks the X event and drives the
handler via a set of events (messages). For a mouse click inside the
free object, its handler is notified with an FL_PUSH together with the
current mouse position mx, my. In addition, the driver also sets the
clipping mask to the bounding box of the free object prior to sending
FL_DRAW. Mouse position (always relative to the origin of the
form) is directly usable in the drawing function. However, it is a
good idea to convert the mouse position so it is relative to the
origin of the free object if the position is to be used later. The
reason for this is that the free object can be resized or moved in
ways unknown to the handler and only the position relative to the free
object is meaningful in these situations.
It is tempting to call the drawing function in response to
FL_PUSH since it is FL_PUSH that triggers the drawing.
However, it is a (common) mistake to do this. The reason is that much
bookkeeping is performed prior to sending FL_DRAW, such as
clipping, double buffer preparation and possibly active window setting
etc. All of these is not done if the message is anything else than
FL_DRAW. So always use fl_redraw_object() to draw
unless it is a response to FL_DRAW. Internally
fl_redraw_object() calls the handler with FL_DRAW
(after some bookkeeping), so we only need to mark FL_PUSH with
a flag newfig and let the drawing part of the handler draw the
newly added figure.
FL_DRAW has two parts. One is simply to add a figure indicated
by newfig being true and in this case, we only need to draw the
figure that is being added. The other branch might be triggered as a
response to damaged drawing area resulting from Expose event or
as a response to Refresh command. We simply loop over all saved
figures and (re)draw each of them.
The only thing left to do is to initialize the program, which includes initial color and size, and initial drawing function. Since we will allow interactive resizing and also some of the objects on the form are not resizeable, we need to take care of the gravities.
void draw_initialize(FD_drawfree *ui) {
fl_set_form_minsize(ui->drawfree, 530, 490);
fl_set_object_gravity(ui->colgrp, FL_West, FL_West);
fl_set_object_gravity(ui->sizegrp, FL_SouthWest, FL_SouthWest);
fl_set_object_gravity(ui->figgrp, FL_NorthWest, FL_NorthWest);
fl_set_object_gravity(ui->miscgrp, FL_South, FL_South);
fl_set_object_resize(ui->miscgrp, FL_RESIZE_NONE);
cur_fig = saved_figure;
cur_fig->pc[0] = cur_fig->pc[1] = cur_fig->pc[2] = 127;
cur_fig->w = cur->fig->h = 30;
cur_fig->drawit = fl_oval;
cur_fig->col = FL_FREE_COL1 + 1;
cur_fig->fill = 1;
fl_set_button(ui->drobj[0], 1); /* show current selection */
fl_mapcolor(cur_fig->col, cur_fig->pc[0],
cur->fig->pc[1], cur->fig->pc[2]);
fl_mapcolor(FL_FREE_COL1, cur_fig->pc[0],
cur->fig->pc[1], cur->fig->pc[2]);
fl_set_slider_bounds(ui->wsli, 1, max_w);
fl_set_slider_bounds(ui->hsli, 1, max_h);
fl_set_slider_precision(ui->wsli, 0);
fl_set_slider_precision(ui->hsli, 0);
fl_set_slider_value(ui->wsli, cur_fig->w);
fl_set_slider_value(ui->hsli, cur_fig->h);
}
|
With all the parts in place, the main program simply creates, initializes and shows the UI, then enters the main loop:
int main(int argc, char *argv[]) {
fl_initialize(&argc, argv, "FormDemo", 0, 0);
drawui = create_form_drawfree();
draw_initialize(drawui);
fl_show_form(drawui->drawfree, FL_PLACE_CENTER|FL_FREE_SIZE,
FL_FULLBORDER, "Draw");
fl_do_forms();
return 0;
}
|
Since the only object that does not have a callback is the Quit
button, fl_do_forms() will return only if that button is
pushed. Full source code to this simple drawing program can be found
in `demos/freedraw.c'.
Label for free objects can't be drawn outside of the bounding box because of the clippings by the dispatcher.
If
fl_winget() is called while not handling messages, the
return value must be checked.
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Jens Thoms Toerring on May 25, 2013 using texi2html 1.82.