This part of the documentation describes the Form Designer, a GUI builder meant to help you interactively design dialogue forms for use with the Forms Library. This part assumes the reader is familiar with the Forms Library and has read Part I of this document.
Even though designing forms is quite easy and requires only a relatively small number of lines of C-code, it can be time consuming to figure out all required positions and sizes of the objects. The Form Designer was written to facilitate the construction of forms. With Form Designer, there is no longer any need to calculate or guess where the objects should be. The highly interactive and WYSIWYG (what you see is what you get) nature of the Form Designer relieves the application programmer from the time consuming process of user interface construction so that he/she can concentrate more on what the application program intends to accomplish.
Form Designer provides the abilities to interactively place, move and scale
objects on a form, also the abilities to set all attributes of
an object. Once satisfactory forms are constructed, the Form Designer generates
a piece of C-code that can then be included in the application
program. This piece of code will contain one procedure
create_form_xxx()
for each form, where xxx indicates
the form name. The application
only needs to call it to generate the form designed.
The code produced is easily readable.
The Form Designer also lets the user identify each object with C variables for later reference in the application program and allows advanced object callback bindings all within the Form Designer. All actions are performed with the mouse or the function keys. It uses a large number of forms itself to let the user make choices, set attributes, etc. Most of these forms were designed using the Form Designer itself.
It is important to note that the Form Designer only helps you in designing the layout of your forms. It does not allow you to specify the actions that have to be taken when, e.g., a button is pushed. You can indicate the callback routine to call but the application program has to supply this callback routine. Also, the Form Designer does not allow you to initialize your objects. E.g., you cannot set the bounds of a slider inside the Form Designer. The bounds are not part of the layout of the form but are part of its use. Hence, it is the responsibility of the application program to initialize the objects.
To start up the Form Designer simply type fdesign
without any argument.
(If nothing happens, check whether the package has been installed
correctly.) A black window (the main window) will appear on the screen. This is
the window in which you can create your forms.
Next the control panel appears on the screen. No form is shown yet.
The control panel consists of five parts (see Fig. 8.1). The first part is the menu bar consisting of several groups of menus from which you can make selections or give commands to the program. At the left there is a list of forms you are working on. The list is empty, indicating that there are no forms yet. You can work on up to 50 forms at the same moment. You can use this list to switch from form to form. To the bottom of that there is a list of all groups in the form you are working on. It will be empty because there are no groups. Ignore this at the moment as we will come back to groups and their use later. Next to this you find a list of all different types of objects that can be placed on the forms. You can use the mouse to select the type of object you want to add to the form. At the right you find a number of buttons to give commands to the program. Each of these buttons is bound to a function key. You can either press the buttons with the mouse or press the function keys on the keyboard. This will have the same effect. The functions of these keys will be described below.
Figure 8.1: Form Designer control panel
To create a new form click with the mouse on the button labeled New Form on the top-left corner of the control panel just below the menu bar. A little notifier will appear prompting you for the name of the form. This is the name under which the application program will know the form. You will have to provide a name (which must be a legal C variable name). Type in the name and press <RETURN>. Now the background of the form appears in the main window. Note the form name is added in the list of forms in the control panel.
To add an object to the form, choose the type of object in the control panel by clicking in the list of object classes. Next move the mouse into the form you are creating and drag the mouse while pressing the left mouse button. Keep the mouse button pressed and create a box that has the required size. Release the button and the object will appear. Note that a red outline appears around the new object. This means that the object is selected. In this way you can put all kinds of objects on the form.
It is possible to move objects around or change their size. To this end, first select the object by pressing the right mouse button in it. A red outline will appear around the object. Now, using the middle mouse button you can move the object around. By grabbing the object at one of the four red corners you can scale it. In this way you can change the layout of the objects on the form. It is also possible to select multiple objects and move or scale them simultaneously. See below for details.
To change the attributes, e.g., the label, of an object, press the right mouse button inside the object to select it. Next, press the function key <F1> (either on the keyboard or in the control form). This can also be achieved by double-clicking the right mouse button. A form will appear in which you can indicate all the different attributes. Their meanings should be clear (if you have read the documentation on the Forms Library). Change the attributes by pressing a mouse button on them. A menu will appear in which you can make the required choice. Change the attributes you want to change and press the button labeled Accept. Or if you would like to preview how the new attributes look, you can press the Apply button. Press Restore to restore the original attributes. See below for more information about changing attributes.
In this way you can create the forms you want to have. Note that you can work
on different forms at the same moment. Just add another form in the way
described above and use the list of forms to switch between them.
After you have created all your forms choose Save from the
File menu to save them to disk.
It will ask you for a file name using the file selector. In this
file selector you can walk through the directory tree to locate
the place where you want to save the file. Next, you can type in
the name of the file (or point to it when you want to overwrite an
existing file). The name should end with .fd
. So for example,
choose ttt.fd
. The program now creates three files:
ttt.c
, ttt.h
and ttt.fd
. ttt.c
contains
a readable piece of C code that creates the forms you designed. The
file ttt.h
contains the corresponding header file for inclusion
in your application program.
The file ttt.fd
contains a description of the
forms in such a way that the Form Designer can read it back in later.
The application program now simply has to call the routine
create_form_xxx()
to create the different forms you designed.
These are the basic ideas behind the Form Designer. Below we describe the program in detail.
To start the Form Designer simply type
fdesign [-xformoptions] [-fdesignoptions] [files[.fd]]
An initial window will be created and mapped. Depending on the window manager, you may have the option to interactively select where to place the window if -geometry option is missing. Next the program places the control panel on the screen. You can move this panel, if required, to the place you want (you can also change the default placement of the control panel via resources).
fdesign
accepts all of the XForms command line options as well
as the following
fdesign
does its work interactively. This option causes
the fdesign
simply read a list of fdesign
output
file (the .fd files) and emit the corresponding C-routines and
header files.
fdesign -convert
.
forms.h
to
header
. Useful on systems where forms.h
is
renamed to something else or you need application
specific constants/defines for the UI to function. In
the later case, header may simply contain
#include "forms.h" #define mystuff 1
Note that -help, -version and -convert do not require a connection to an X server.
If an output unit other than the default (pixel) is selected, all object sizes in the output file will be in the unit requested. This kind of UI has a fixed and device resolution independent size (in theory at least) and can be useful for drawing applications.
fdesign
recognizes the following resources
Note that resource specification of convert requires an X connection.
In addition, all XForms's resources specification can be used to influence the appearance of various panels. The most useful ones are the font sizes
To create a new form press the button labeled New Form, indicate the enclosing box of the form and type in a (unique) name for the form. The form is shown in the main window and objects can be added to it.
There are two ways to change the size of a form at a later stage. The easiest way is to simply change the size of the main window and the form will resize itself to fit the new size. Or you can select the bottom box of the form, using the right mouse button. Next grab the box using the middle mouse at the lower-right corner and scale it. Note that objects lying outside the form will be invisible when the form is shown by the application program.
To change the name of the current visible form, press the button labeled Rename Form under the list of forms. You will be prompted for the new form name.
To delete a form, press the button labeled Delete Form. The current form will be removed.
To add an object, choose the class of the object from the object list in the middle of the control panel. Next drag the left mouse button on the form and an outline showing the current size of the object will appear. When the size is correct release the mouse button.
Note that the position and size of the object is rounded to multiples of 10 pixels. This can be changed. See below on alignments.
To perform operations on objects that are already visible in the form, we first have to select them. The right mouse button is used for selecting objects. Simply press it inside the object you want to select. A red outline will appear, indicating that the object is selected.
It is also possible to select multiple objects. To this end again use the right mouse button to draw a box around all the objects you want to select. All objects that lie fully inside the box will be selected. Each selected object will get a red outline and a red bounding box is drawn around all of them.
To add objects to an already existing selection, hold down the <Shift> key and press the right mouse button inside the objects. You can remove objects from the selection by doing the same on an already selected object.
It is possible to select all objects (except for the backface) at once using the function key <F4>.
One note on the backface of the form. Although this is a normal object, it can not be treated in the same way as the other objects. It can be selected, but never in combination with other objects. Only two operations are allowed on it: changing its attributes and scaling it (which scales the size of the form).
Moving and scaling of objects is done using the middle mouse button. To move an object or a collection of objects to a new place, first select it (them) using the right mouse button as described above. Next press the middle mouse button inside the bounding box (not near one of the corners) and move the box to its new position.
To scale the object or objects, pick up the bounding box near one of its corners (inside the red squares) and scale it, again using the middle mouse button.
When holding the <SHIFT> key while moving an object or group of objects, first a copy of the object(s) is made and the copy is moved. This allows for a very fast way of putting a number of equal objects on the form: First put one on the form, change the attributes as required and next copy it.
For precise object movement, the cursor keys can be used. Each pressing of
the four directional cursors moves the object 10 pixels. To change
the step size, precedes the cursor keys with 0-9
with
0
indicating 10 pixels. If <SHIFT> is down,
instead of moving the object, the object size is increased or
decreased by the step size.
Sometimes you have a number of objects and you want to align them in some way, e.g. centered or all starting at the same left position, etc. To this end press the button labeled Align. A special form will appear in the top right corner. You can leave this form visible as long as you want. You can hide it using the button Dismiss on the form.
Now select the objects you want to align. Next, press one of the alignment buttons in the form. The buttons mean: top row: align left, horizontal center, align right, horizontal equal distance (see below), bottom row: align bottoms, vertical center, align tops, vertical equal distance. Equal distance alignment means that between all the objects an equal sized gap is placed. The objects are kept in the same left to right or bottom to top order.
Figure 10.1: Object alignment control
In the alignment form you can also indicate the snapping size using the counter at the bottom. Choose 0 if you don't want to snap positions. Default the snapping is 10 pixels. Snapping helps in making objects the same size and in making them nicely aligned.
The objects in a form are drawn in the order in which they are added. Sometimes this is undesirable. For example, you might decide at a later stage to put a box around some buttons. Because you add this box later, it will be drawn over the buttons thus making the buttons invisible. This is definitely not what you want. The Form Designer makes it possible to raise objects (bring them to the top) or lower them (put them at the bottom). So you can lower the box to move it under the buttons. Raising or lowering objects is very simple. First select the objects using the right mouse button and next press the function key <F2> to lower the selection or <F3> to raise it.
To set attributes like type, color, label, etc., of an object first select it (using the right mouse button) and next press the function key <F1>. If only one object is selected you can change all its attributes, including its label, name, etc. It is also possible to change the attributes of multiple objects as long as they all are of the same class. In this case you cannot change the label, name, etc. because you probably want them to remain different for the objects.
A form will appear in which you can indicate the different settings. Here you can indicate type, boxtype, and colors of the object, and style, size, alignment and color of the label. The type, boxtype, style, size and alignment are set using a choice object. To change it either use the left or middle mouse button to cycle through the possibilities, or use the right mouse button to get a menu with all choices. To change one of the colors, push the mouse on it. A box will appear showing 64 colors in the color map. You can indicate the color you want with the mouse or use cancel to keep the color unchanged. (The color of the cancel button is the current color you are changing.) You can use the arrows to run through the color map to find other colors.
Once you are satisfied with the settings, press the button labeled Ready and the form will disappear. If you don't want to change the attributes after all press the button labeled Cancel.
Three more fields can be filled in in the attributes form: name, callback
and argument. Name indicates the name of the object. If you type in a name here
the object will be known to the application program under this name so that
the program can refer to it. Take care that all object names used are
different. They should be legal C variable names. It is possible to use
arrays of objects. E.g. if you define some objects as
tt[0]
, tt[1]
and tt[2]
the piece of C-code produced
by the Form Designer will contain a declaration of an array tt
of size 3.
(Only one-dimensional arrays are treated correctly.)
Callback indicates the callback routine.
If you type in something here, this routine will be bound to the object. In
this case you also have to provide an argument that must be an integer
(or cast to integer, as in (long)&variable
).
Of course, the application program will have to provide the callback
routine.
Note that when copying objects these fields are also copied. This might lead to multiple objects with the same name. This will lead to undesired effects. So watch out for these after copying an object.
You can remove objects from the form by first selecting them and next pressing function key <F12> or double-clicking the left mouse button. The objects will disappear but are in fact saved in a buffer. You can put them back in the form, or in another form, by pasting them using <F10>. Note that only the last collection of deleted objects is saved in the buffer.
It is also possible to put a copy of the selection in the buffer using <F9>. This selection can now be put into the same form or into a different form. This allows for a simple mechanism of making multiple copies of a set of objects and for moving information from one form to another.
As described in the tutorial about the Forms Library, sets of radio buttons must be placed inside groups. Groups are also useful for other purposes. E.g. you can hide a group inside an application program with one command. Hence, the Form Designer has some mechanism to deal with groups.
In the control panel there is a list of groups in the current form. As long as you don't have groups, this list will be empty. To create a group, select the objects that should come in the group and press the function key <F7>. You will be prompted for the name of the group. This should be a legal C variable name (under which the group will be known to the application program) or should be empty. This name will be added to the list. In this way you can create many groups. Note that each object can be in only one group. So if you select it again and put it in a new group, it will be removed from its old group. Groups that become empty this way automatically disappear from the list. (When putting objects in a group they will be raised. This is unavoidable due to the structure of groups.)
In the list of groups it is always indicated which groups are part of the current selection. (Only the groups that are fully contained in the selection are indicated, not those that are only partially contained in it.) It is also possible to add or delete groups in the current selection by pushing the mouse on their name in the list.
Note that there is no mechanism to add an object to a group directly. This can, however, be achieved using the following procedure. Select the group and the new object and press <F7> to group them. The old group will be discarded and a new group will be created. You only have to type in the group name again.
Sometimes you want to un-group the objects in an existing group, i.e., get them out of the group they are currently in. To this end simply select the group and press <F8>. (This only works if one group is selected.)
You can use the item Rename group under the Group menu to change the name of a selected group. If multiple groups are selected only the name of the first group is changed.
Sometimes it is useful to temporarily hide some objects in your form. In particular when you have sets of overlapping objects. To this end, select the objects you want to hide and press <F6>. The objects (though still selected) are now invisible. To show them again press <F5>. A problem might occur here. When you press <F5> only the selected objects will be shown again. But once an object is invisible it can no longer be selected. Fortunately, you can always use <F4> to select all objects, including the invisible ones, and press <F5> after that. It is better, though, to first group the objects before hiding them. Now you can select them by pressing the mouse on the group name in the group browser.
To test the current form, press the button labeled Test. The form will be displayed in the center of the screen. A panel will appear at the top right corner of the screen. This panel will show you the objects that will be returned and the callback routines called when working with the form. In this way you can verify whether the form behaves correctly and whether all objects have either callback routines or names (or both) associated with them. You can play with the form as long as you want. When ready, press the button Stop Testing.
Note that any changes you make to the form while testing do not show up when saving the form. E.g. filling in an input field or setting a slider does not mean that in the saved code the input field will be filled in or the slider set.
To save the set of forms created select the item Save
from the File menu. You will be prompted for a file name using
the file selector. Choose a name that ends with .fd
.
e.g. ttt.fd
.
The program will now generate three files ttt.c
,
ttt.h
and ttt.fd
.
ttt.c
contains a piece of C-code that builds up the
forms and ttt.h
contains all the object and form
names as indicated by the user. It also contains declaration
of the defined callback routines.
Depending on the options selected from the Options
menu, two more files may be emitted. Namely the
main program and callback function templates. They are
named ttt_cb.c
and ttt_main.c
respectively.
There are two different kind of formats for the C-code
generated. The default format allows more than one instances
of the form created and uses no global variables. The other
format, activated by altformat
on the command line,
or from the Options menu by selecting Alt Format,
uses global variables and does not allow more than one
instantiation of the designed forms. However, this format
has a global routine that creates all the forms defined,
which by default is named create_the_forms()
but it
can be changed (see below).
Depending on which format is output, the application program typically only needs to include the header file and call the form creation routine.
To illustrate the differences between the two output formats
and the typical way an application program is setup, we look at
the following hypothetical situation: We have two forms,
foo and bar, each of which contains several objects,
say fnobj1, fnobj2 etc. where n=1,2. The default output
format will generate the following header file (foobar.h
):
#ifndef FD_foobar_h_ #define FD_foobar_h_ /* call back routines if any */ extern void callback(FL_OBJECT *,long); typedef struct { FL_FORM *foo; FL_OBJECT *f1obj1; FL_OBJECT *f1obj2; void *vdata; long ldata; } FD_foo; typedef struct { FL_FORM *bar; FL_OBJECT *f2obj1; FL_OBJECT *f2obj2; void *vdata; long ldata; } FD_bar; extern FD_foo *create_form_foo(void); extern FD_bar *create_form_bar(void); #endif /* FD_foobar_h */
and the corresponding C file:
#include "forms.h" #include "foobar.h" FD_foo *create_form_foo(void) { FD_foo *fdui = (FD_foo *) fl_calloc(1, sizeof(FD_foo)); fdgui->foo = fl_bgn_form(....); fdgui->f1obj1 = fl_add_xxxx(....); ..... fl_end_form(); return fdui; } FD_bar *create_form_foo(void) { FD_bar *fdui = (FD_bar *) fl_calloc(1, sizeof(FD_bar)); fdgui->bar = fl_bgn_form(....); fdgui->f2obj1 = fl_add_xxxx(....); ..... fl_end_form(); return fdui; }
The application program would look something like the following:
#include "forms.h" #include "foobar.h" /* add call back routines here */ main(int argc, char *argv[]) { FD_foo *fd_foo; FD_bar *fd_bar; fl_initialize(...); fd_foo = create_form_foo(); init_fd_foo(fd_foo); /* application UI init routine */ fd_bar = create_form_bar(); init_fd_bar(fd_bar) /* application UI init routine */ fl_show_form(fd_foo->foo, ...); /* rest of the program */ }
As you see, fdesign generates a structure that groups together all objects on a particular form and the form itself. Application program can create more than one instances of the form if needed.
It is difficult to avoid globals in an event-driven
callback scheme with the most difficulties occuring
inside the callback function where another object on
the same form may need to be accessed. Current setup
makes it possible and relatively painless to
achieve this. The easiest way of doing this is to
use the vdata
in FD_
structure to
hold the ID of the object needs to be accessed. In case
of a need to access multiple objects,
there is a field u_vdata
in FL_OBJECT
structure you can
use. You simple use the field to hold the FD_
structure:
fdfoo = create_form_foo(); fdfoo->f1obj1->u_vdata = fdfoo; ...
and in the callback function, you can access other objects as follows:
void callback(FL_OBJECT *ob, long data) { FD_foo *fdf = ob->u_vdata; fl_set_object_xxx(fdf->f1obj2, ....); }
Not pretty, but adequate for practical purposes.
The other alternative is to use the FD_
structure
created as the user data in the callback
fl_set_object_callback(obj, callback, (long)fdui);
and use the callback as follows
void callback(FL_OBJECT *ob, long arg) { FD_foo *fdfoo = (FD_foo *) arg; fl_set_object_lcol(fdfoo->f1obj1, FL_RED); ... }
Avoiding globals is, in general, a good idea, but as everything else, an excess of a good thing can be bad. Sometimes, simply making the FD_ structure global makes a program clearer and more maintainable.
There still is another difficulty that might arise with
the current setup. For example, in f1obj1's callback
we change the state of some other objects , say,
f1obj2 via fl_set_button/input
. Now the state
of f1obj2 is changed and it needs to be handled.
You probably don't want to put too much f1obj2's handling
code in f1obj1's callback. In this situation,
the following function comes in handy
void fl_call_object_callback(FL_OBJECT *obj)
fl_call_object_callback(fdfoo->f1obj2)
will invoke
the f1obj2's callback in exactly the same way the
main loop would and as far as f1obj2 is concerned,
it just handles the state change as if the user changed it.
The alternative format outputs something like the following:
/* callback routines */ extern void callback(FL_OBJECT *, long); extern FL_FORM *foo, *bar; extern FL_OBJECT *f1obj1, f1obj2 ...; extern FL_OBJECT *f2obj1, f2obj2 ...; extern void create_form_foo(void), create_form_bar(void); extern void create_the_forms(void);
The C-routines:
FL_FORM *foo, *bar; FL_OBJECT *f1obj1, *f1obj2 ...; FL_OBJECT *f2obj1, *f2obj2 ...; void create_form_foo(void) { if(foo) return; foo = fl_bgn_form(....); ... } void create_form_bar(void) { if(bar) return; bar = fl_bgn_form(....); ... } void create_the_forms(void) { create_form_foo(); create_form_bar(); }
Normally the application program would look something like this:
#include "forms.h" #include "foobar.h" /* The call back routines */ main(int argc, char *argv[]) { fl_initialize(....); create_the_forms(); /* rest of the program */ }
Note that although the C-routine file in both cases is easily readable, editing it is strongly discouraged. If you were to do so, you will have to redo the changes whenever you call fdesign again to modify the layout.
The third file created, ttt.fd
, is in a format that can be
read in by the Form Designer. It is easy readable ASCII but you had better not change it
because not much error checking is done when reading it in.
To load such a file select the Open from the File
menu. You will be prompted for a file name using the file
selector. Press your mouse on the file you want to load and press the button
labeled Ready. The current set of forms will be discarded, and
replaced by the new set. You can also merge the forms in a file with the
current set. To this end select Merge from the
File menu.