Copyright © 2003-2005 Université Claude Bernard, LIRIS, Thierry Excoffier
The goal of the ZMW library is to prove that it is possible to create a widget library that does not use a single bit of memory per widget.
A side effect of this goal is that the library usage is simplified because there is no widget reference, so if you use the library from the C language there is no pointer.
A bad side effect is that the window drawing complexity is $O(n log(n))$ in CPU time where $n$ is the number of widgets.
This library implementation is an usable prototype because the code is well tested. But, future version of the library may not be compatible with the current one because some functions will be renamed. The current API for 0.2.0 is nearly stable.
All the examples in this document are real running examples, they really automatically generated the pictures associated. And these pictures are part of the regression tests. So you should obtain exactly the sames results, except if the fonts used here are unknown on your computer.
The keyboard focus navigation does not use Tab to change the focus but Ctrl + Cursor keys or only the Cursor keys if it is not used by the focused widget.
The idea of ZMW was born in 2001 because classic widget library took too much time to create/destroy widgets. The first working prototype was from the begin of 2002. An research report was made in march 2003: http://www710.univ-lyon1.fr/~exco/ZMW/rr_2003_03_11.pdf it was published with the version 0.0.0 of the library. The version 0.0.1 was released publicly the 27 october 2003.
Sorry for the poor English, it is not my native language.
There is a small example using the ZMW library. It displays a text and a
button. An usage tip appears if the cursor stay still over the button. The example is followed by detailed explanations. If you have compiled the ZMW library, this example executable is named examples/quit/quit.exe.Example 1-1. Hello world
#include "zmw/zmw.h" void hello_world(void) { static GdkWindow *id_window = NULL, *id_tip = NULL ; ZMW(zmw_window_with_id(&id_window, "My Window")) { ZMW(zmw_vbox()) { zmw_label("Hello World!") ; zmw_button("Quit") ; if ( zmw_activated() ) zmw_main_quit(0) ; ZMW( zmw_tip() ) { zmw_border_width(2) ; ZMW(zmw_window_popup_right_with_id(&id_tip)) { ZMW(zmw_decorator(Zmw_Decorator_Border_Solid)) { zmw_label("Click here to close the application") ; } } } } } } int main(int argc, char *argv[]) { zmw_init(&argc, &argv) ; zmw_main(hello_world) ; return 0 ; } |
Required to use the library.
The user interface is defined by this function. This function is called to display the windows and their content, to dispatch events and so on. This function must never modify the program state, it must always compute exactly the same things from one call to another. A program state change is allowed only on user interaction.
For example, there is a label widget defined in this function. To display this label, the function is called once to compute the label size and then a second time to display it. If the label text has changed between the two calls it will be incorrectly displayed.
This first example use really 0 byte of memory per widget, so window pointer can not be stored in the widget, so it is stored in the user function. It is stored as static because its the value must be remembered from one call to another, it could be stored in the application data structures. The type is GdkWindow* because the library uses GDK as ``graphic'' library. The ZMW library allows to store these values in the resources system, the programs are shorter but they use more memory and processor time to manage the resource.
ZMW
define a widget.
The parameter is the widget type, in this case
it is window, the first parameter is the window pointer
and the second the window title.
The window content is defined in the following block.
To simplify the program we could replace this line by: ZMW(zmw_window("My Window")) The window pointer is no more stored in a static user variable but in the resource system.
The window contains a vertical stack of widget. The widget are defined top to bottom in the next block.
The top most widget is a label.
The second widget is a button. To activate the button, the user must click and release the mouse button with the cursor in the button. While the button is pressed, it is displayed with a darker colour.
The program call zmw_main_quit(0) if the previous widget is activated. In the example, the program exit is the button quit is clicked.
zmw_tip_visible() returns true if the cursor stay still over the previous widget for some time. In our case, the function is used to trigger the display of a tip message on the quit button. The tip content is defined in the next block.
As there is no memory to store widget attributes, the attributes are defined using a current state as in OpenGL. The new border width are applied to all the next widgets children of the widget being defined.
We choose to display the tip in a popup window at the right of the previous widget (the quit button).
This widget draw a border around its child.
The tip window contains a simple text.
Initialise the library. The parameters recognised by the library are removed from the program argument list.
This function call launch the user interface defined
by the function hello_world
,
it never returns.
Simple widget do not contains other widgets, they are the base of the widget interface.
zmw_label
(const char *text
) ;
This widget displays text
as a non modifiable label.
The text can be computed, but its value can only
change when an event is processed.
zmw_entry_with_cursor_and_start
(char **text
,
int *cursor_pos
,
int *start_pos
) ;
This widget display an editable text.
The text pointer should be NULL
are allocated with malloc
.
Copy/paste are allowed in the area.
This text is focusable, so once focused the cursor does not
need to be in the widget.
This text is activated each time it is modified.
cursor_pos
is the current
cursor position, 0 is the position
before the first character.
start_pos
is the selection
position or -1 if there is no selection.
If zmw_activated
is called
after an editable text it will return true if
the text is modified.
In this case, the program state can be changed.
zmw_entry_with_cursor
(char **text
,
int *cursor_pos
,
) ;
As zmw_entry_with_cursor_and_start
but the start_pos
is stored as a resource.
zmw_entry
(char **text
) ;
As zmw_entry_with_cursor
but the cursor_pos
is stored as a resource.
Example 2-1. Text widgets
void text(void) { int i ; char tmp[99] ; static char *text1 = NULL, *text2 = NULL ; static int cursor_pos = 5 ; // Must be static static int text1_length ; if ( text1 == NULL ) { // The initial value must be malloced text1 = strdup("text 1") ; text1_length = strlen(text1) ; text2 = strdup("text 2") ; } ZMW(zmw_window("Text")) { ZMW(zmw_vbox()) { for(i=0; i<2; i++) { // Texts can be computed sprintf(tmp, "Label number %d", i) ; zmw_label(tmp) ; } // "cursor_pos" is only modified on user event. sprintf(tmp, "cursor_pos = %d", cursor_pos) ; zmw_label(tmp) ; zmw_horizontal_alignment(-1) ; // Left zmw_entry_with_cursor(&text1, &cursor_pos) ; if ( zmw_changed() ) text1_length = strlen(text1) ; sprintf(tmp, "text1_length = %d", text1_length) ; zmw_label(tmp) ; zmw_horizontal_expand(Zmw_False) ; zmw_border_width(6) ; zmw_entry(&text2) ; } } } | Activate the first text: Select a fragment and type a letter: |
In this example, the value of the label cursor_pos
change on user intervention, so it is allowed.
These text values are based upon normal texts.
zmw_int_editable
(int *integer
) ;
Display an editable integer, beware each time you
it a key the string is converted to integer and
back to string. So, if you type a a
in front of an integer, it will be set to 0
.
If there is an integer overflow strange things will happen.
zmw_int
(intinteger
) ;Display an integer.
zmw_button
(const char *text
) ;
Display a button with the specified text.
As for editable texts, zmw_activated
can be used to test if the button is clicked.
If the button is not tested, then it is displayed greyed.
zmw_button_with_accelerator
(const char *text
,
GdkModifierType state
,
int character
) ;
As zmw_button
but with an accelerator.
The accelerators are processed before any other
widget event, so if 'a' is an accelerator
it can not be inserted in a editable text widget
The state can be a or between
GDK_SHIFT_MASK
,
GDK_LOCK_MASK
,
GDK_CONTROL_MASK
,
GDK_MOD1_MASK
,
GDK_MOD2_MASK
.
zmw_button_with_hidden_accelerator
(const char *text
,
GdkModifierType state
,
int character
) ;
Same as zmw_button_with_hidden_accelerator
but the accelerator is not automatically displayed
in the button label.
Example 2-2. Some buttons
void button(void) { static int on_1 = 0, on_2 = 0, on_3 = 0 ; char tmp[99] ; ZMW(zmw_window("Button")) { zmw_horizontal_expand(Zmw_False) ; ZMW(zmw_vbox()) { zmw_button("Button 1") ; if ( zmw_activated() ) on_1 = 1 ; zmw_button_with_accelerator ("Button 2", GDK_CONTROL_MASK, 'a') ; if ( zmw_activated() ) on_2 = 1 ; zmw_button_with_hidden_accelerator ("Button 3", GDK_CONTROL_MASK, 'b') ; if ( zmw_activated() ) on_3 = 1 ; zmw_button("Tested if on_2==True") ; if ( on_2 && zmw_activated() ) printf("I have been clicked !\n") ; sprintf(tmp, "State = %d %d %d", on_1, on_2, on_3) ; zmw_label(tmp) ; } } } | After press/release of The user press Control and wait |
zmw_check_button
(int value
) ;
Display a boolean check button, the button state is changed
when clicked.
We can use zmw_activated
to know
is the button state is modified.
If the button has the focus, a key press change
the button value.
It is allowed to have several check buttons modifying
the same value.
The returned value should be copied into the parameter,
so it is possible to have check state stored in a
single bit.
zmw_check_button_with_label
(int value
,
const char *label
) ;As above but display a label next to the check button.
zmw_check_button_bits
(int value
,
int bits
) ;
As zmw_check_button
but the
new value is an exclusive or between the old one and
bits
.
zmw_check_button_bits_with_label
(int value
,
int bits
,
const char *label
) ;
As zmw_check_button_bits
but with a label.
zmw_check_button[_bits]_(int|char)[_with_label]
((int|char) *value
) ;These eight functions allow to use former functions without using affectation operator.
zmw_radio_button
(int *value
,
int number
) ;When activated, the number is stored in the value. The other check buttons of the radio group use the same value but the numbers must but not be equal. There is not limitation on number values, so it can be pointers.
zmw_radio_button_with_label
(int *value
,
int number
,
const char *label
) ;
As zmw_radio_button
but with a label.
Example 2-3. Some check buttons
static char *choices[] = {"None", "Bonjour", "Au revoir", "Oui"} ; void toggle(void) { static int t1 = 0, t1_change = 0, bits = 56789932 ; static int radio = 0 ; int i ; ZMW(zmw_window("Toggle")) { zmw_horizontal_alignment(-1) ; ZMW(zmw_vbox()) { /* Some boolean toggles */ t1 = zmw_check_button_with_label(t1, "Toggle 1") ; if ( zmw_activated() ) t1_change = 1 ; zmw_check_button_int_with_label(&t1, "Toggle 2 : same as 1") ; zmw_check_button_int_with_label(&t1_change, "Toggle 1 was activated") ; /* Editing the bits of an integer */ ZMW(zmw_hbox()) { zmw_width(5) ; zmw_padding_width(0) ; zmw_focus_width(1) ; zmw_border_width(1) ; for(i=0; i<32; i++) zmw_check_button_bits_int(&bits, 1<<(31-i)) ; } zmw_int_editable(&bits) ; /* Radio button */ zmw_radio_button_with_label(&radio, 1, "Hello") ; zmw_radio_button_with_label(&radio, 2, "Goodbye") ; zmw_radio_button_with_label(&radio, 3, "Yes") ; zmw_label("French Translation:") ; zmw_label(choices[radio]) ; } } } | Initial state After some interactions |
zmw_anchor_vertical
(int *x
) ;
This widget is low level, it displays a focusable vertical line
in the current box, x
is the
widget position in the current box in pixel.
The x
value is modified when
the anchor is dragged.
There is no visual feedback, so if the value is
not used to change the anchor position, it is useless.
On value modification, the widget is activated.
zmw_anchor_vertical_float
(float *x
) ;
As above but the value is between 0
and 1
.
zmw_scrollbar2_with_delta
(Zmw_Float_0_1 *x
,
Zmw_Float_0_1 x_size
,
Zmw_Float_0_1 x_delta
,
Zmw_Float_0_1 *y
,
Zmw_Float_0_1 y_size
,
Zmw_Float_0_1 y_delta
) ;
It is a 2d scrollbar, (x
y
)
is the scrollbar position and
(x_size
y_size
)
define its width and height.
The scrollbar is draggable.
If it is focused, cursor keys move it,
the translation is specified by x_delta
and y_delta
,
0.5
means that the scrollbar
is translated on half its size.
On position change, zmw_changed()
will return true.
When the user release the scrollbar or use
keys to change the scrollbar value
then the widget is activated
and zmw_activated()
will return true.
zmw_scrollbar2
(Zmw_Float_0_1 *x
,
Zmw_Float_0_1 x_size
,
Zmw_Float_0_1 *y
,
Zmw_Float_0_1 y_size
,
) ;
As zmw_scrollbar2_with_delta
with
x_size
=0.
1 and
y_size
=0.1
.
It is a 2d scrollbar, (x
y
)
is the scrollbar position and
(x_size
y_size
)
define its width and height.
The scrollbar is draggable.
Cursor keys move it if it is focused,
the translation is specified by x_delta
and y_delta
,
0.5
means that the scrollbar
is translated on half its size.
On position change, the widget is activated.
zmw_hscrollbar_with_delta
(Zmw_Float_0_1 *x
,
Zmw_Float_0_1 x_size
,
Zmw_Float_0_1 x_delta
) ;The classical horizontal scrollbar.
zmw_hscrollbar
(Zmw_Float_0_1 *x
,
Zmw_Float_0_1 x_size
) ;
As zmw_hscrollbar_with_delta
with x_delta
=0.1
.
zmw_vscrollbar_with_delta
(Zmw_Float_0_1 *y
,
Zmw_Float_0_1 y_size
,
Zmw_Float_0_1 y_delta
) ;The classical vertical scrollbar.
zmw_vscrollbar
(Zmw_Float_0_1 *y
,
Zmw_Float_0_1 y_size
) ;
As zmw_vscrollbar_with_delta
with y_delta
=0.1
.
Example 2-4. Anchors and scrollbars
void anchor(void) { static int anchor_x = 100 ; static Zmw_Float_0_1 sb2_x = 0.5, sb2_y = 0.1 ; char tmp[99] ; ZMW(zmw_window("Anchor")) { ZMW(zmw_vbox()) { // Add a frame around the box ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed)) { // Box without automatic widget placement ZMW(zmw_fixed()) { zmw_height(20) ; zmw_y(0) ; zmw_x(0) ; zmw_label("Anchor-->") ; zmw_x(anchor_x) ; zmw_anchor_vertical(&anchor_x) ; zmw_x(anchor_x+10) ; zmw_int_editable(&anchor_x) ; } } sprintf(tmp, "sb2_x=%g sb2_y=%g", sb2_x, sb2_y) ; zmw_label(tmp) ; // Set the size of the scrollbar area zmw_width(150) ; zmw_height(80) ; zmw_scrollbar2(&sb2_x, anchor_x/200., &sb2_y, 0.1) ; // Synchronized with the previous. Default height zmw_height(ZMW_VALUE_UNDEFINED) ; zmw_hscrollbar(&sb2_x, anchor_x/200.) ; if ( zmw_activated() ) fprintf(stderr, "Scrollbar activated\n") ; else if ( zmw_changed() ) fprintf(stderr, "Scrollbar changed\n") ; } } } | Initial state After some interactions |
In this example the vertical anchor can be moved with the mouse or by editing the integer value. The two scrollbar are synchronised. The scrollbars width is defined by the first anchor position.
zmw_image
(GdkPixbuf *pixbuf
) ;The image defined is displayed. The image size must only change on user action.
zmw_image_from_file_with_pixbuf
(const char *filename
GdkPixbuf **pixbuf
) ;
The image in the file is loaded in the pixbuf
is it is NULL
.
The pixbuf
must be a static variable,
in order to not reload the file at each display.
If the file name change, the image will not be reloaded.
zmw_image_from_file
(const char *filename
) ;
As zmw_image_from_file_with_pixbuf
but the pixmap is stored in the resource system.
zmw_image_from_file_activable_with_pixbuf
(const char *filename
,
GdkPixbuf **pixbuf
) ;
As zmw_image_from_file_with_pixbuf
but the image is activable and focusable as a button.
zmw_image_from_file_activable
(const char *filename
) ;
As zmw_image_from_file_activable_with_pixbuf
but the pixmap is stored in the resource system.
zmw_image_dynamic_from_file_with_pixbuf
(const char *filename
,
GdkPixbuf **pixbuf
,
char **old_name
) ;
As zmw_image_from_file_with_pixbuf
but, if the filename change
the image is reloaded.
The old file name is updated, its initial value
must be NULL
.
zmw_image_dynamic_from_file
(const char *filename
,
char **old_name
) ;
As zmw_image_dynamic_from_file_with_pixbuf
but the pixbuf is stored in resource system.
Example 2-5. Images
void image(void) { static GdkPixbuf *pb = NULL ; static Zmw_Float_0_1 radius = 0.5, old_radius = -1 ; const int width = 100, height = 100 ; if ( pb == NULL ) pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, 0, 8, width, height) ; if ( radius != old_radius ) { // Create the image in the pixbuf create_circle(pb, radius*100, width, height) ; old_radius = radius ; } ZMW(zmw_window("Image")) { ZMW(zmw_vbox()) { // If the big disc button is clicked, radius is set to 50 zmw_image_from_file_activable("button.png") ; if ( zmw_activated() ) radius = 0.5 ; // Non activable image zmw_image_from_file("img.png") ; // A scrollbar to modify the radius zmw_hscrollbar(&radius, 0.1) ; // The image with the disc picture computed. zmw_image(pb) ; } } } | Initial state Scrollbar move |
The example contains, an image button stored in a file,
a decorative image and a computed image.
The scroll bar allows to change the disk radius
and the 50
.
These widgets are only useful as a menu item.
zmw_tearoff_with_id
(GdkWindow **window
) ;
When the tearoff is clicked, the window is
destroyed, the window pointer become NULL
,
zmw_tearoff
(void) ;
As zmw_tearoff_with_id
but the id is stored as the window resource.
Container widget are widgets that can contains other widgets. It is the container that choose how to place its children.
The windows can contain only one child.
zmw_window_with_id
(GdkWindow **id
,
const char *title
) ;
A standard window with a window manager frame.
The id
must be a static
variable.
It must be initialised to NULL
.
zmw_window
(const char *title
) ;
As zmw_window_with_id
but the
id
is stored in the resource system.
zmw_window_popup_right_with_id
(GdkWindow **id
) ;It is a popup window, so it has no frame around. It appears on the right of the last widget defined because it is usually a button used to popup a menu.
zmw_window_popup_right
(void) ;
As zmw_window_popup_right_with_id
but the id is stored in the resource system.
zmw_window_popup_bottom_...
(...) ;
All the popup_right
functions exist
as popup_bottom
zmw_window_popup_..._title
(...) ;
All the popup
functions exist
with a title. This title is used if the
popup is detached.
zmw_window_drag_with_id
(GdkWindow **id
) ;It is a window without a window manager frame that follow the mouse. It is used by the drag and drop.
zmw_window_drag
(void) ;
As zmw_window_drag_with_id
but the id
is stored in the resource system.
Example 3-1. Windows
void submenu() { ZMW(zmw_window_popup_right_with_title("Submenu")) { ZMW(zmw_vbox()) { zmw_tearoff() ; zmw_button("Action 2") ; zmw_button("Action 3") ; } } } void menu() { static int boolean ; static char *text = NULL ; if ( text == NULL ) text = strdup("Editable text") ; ZMW(zmw_window_popup_bottom_with_title("Menu")) { ZMW(zmw_vbox()) { zmw_tearoff() ; zmw_button("Submenu") ; ZMW ( zmw_menu() ) submenu() ; zmw_button("An action") ; zmw_check_button_int_with_label(&boolean, "Toggle") ; zmw_entry(&text) ; } } } void window(void) { ZMW(zmw_window("Window")) { ZMW(zmw_vbox()) { zmw_button("Menu") ; ZMW ( zmw_menu() ) menu() ; } } } | button/menu/submenu Detached menu |
This example use menu function because most windows are used to display menu or popup.
There is no menu widget as you can see in the previous example.
The functions returning a boolean should be used
as a parameter of the zmw_if
widget.
zmw_window_is_popped_with_detached
(const int *detached
) ;
This function return true if the button is pressed
or if the user navigate in the menu or if the
menu is detached.
This function must be called after a button
and as a zmw_if
widget
parameter.
The window widget must contain a
zmw_tearoff_with_detached
.
The window and tearoff widgets must use the same
detached
value.
zmw_window_is_popped
(void) ;
As zmw_window_is_popped_with_detached
but the detached value is stored in the resource system.
Both the window and the tearoff widgets
must not have a detached
parameter.
zmw_menu_with_detached
(const int *detached
) ;
This widget displays its content only
if the previous button is pressed.
It must contains a
window widget with a
zmw_tearoff_with_detached
.
The window and tearoff widgets must use the same
detached
value.
zmw_menu
(void) ;
As zmw_popped_with_detached
but the library take care of the detached value.
Example 3-2. A menu with explicit detached variable
void menu() { ZMW(zmw_window_popup_bottom_with_title("Menu")) { ZMW(zmw_vbox()) { zmw_tearoff() ; zmw_button("An action") ; if ( zmw_activated() ) printf("Action!\n") ; } } } void window(void) { static int detached = 0 ; ZMW(zmw_window("Window")) { ZMW(zmw_vbox()) { zmw_button("Menu") ; ZMW(zmw_menu_with_detached(&detached)) menu() ; } } } | Detached |
With the detached value in user space, the menu can be automatically detached when the program start, the program know that the menu is detached, the program is faster and it uses less memory because there is one less resource,
Utility to create an alert window.
zmw_alert
(const char *message
) ;
If *message
is not NULL
a window is displayed with the message and a
button.
Example 3-3. An error message window
void alert(void) { static char *message = NULL ; ZMW(zmw_window("Boxes")) { ZMW(zmw_vbox()) { zmw_button("Do not click on this button") ; if ( zmw_activated() ) message = "I said to not click on this button" ; zmw_button("This button do nothing") ; if ( zmw_activated() ) message = "It really do nothing!" ; zmw_alert(&message) ; } } } | Detached |
zmw_hbox
() ;
The widget are packed left to right.
when computing the widget position, it uses
the widget attributes
zmw_horizontal_expand
and
zmw_vertical_alignment
.
zmw_vbox
() ;
The widget are packed top to bottom.
when computing the widget position, it uses
the widget attributes
zmw_vertical_expand
and
zmw_horizontal_alignment
.
zmw_(h|v)box_activable
() ;As the 2 previous functions but the box is activable and focusable. It is useful to create a button by composition.
zmw_fixed
() ;This box does not manage its content. The children stay where they choose to. By default they are all at the box bottom left. The box size is computed in order to not clip children.
Example 3-4. Horizontal and vertical boxes
void boxed_text(const char *text, int i) { ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed)) { if ( i == 2 ) { zmw_horizontal_expand(Zmw_False) ; zmw_vertical_expand(Zmw_False) ; } zmw_label(text) ; } } void box(void) { int i ; char tmp[99] ; ZMW(zmw_window("Boxes")) { ZMW(zmw_vbox()) { for(i=0; i<3; i++) // Only i=1 is False { zmw_horizontal_expand(Zmw_True) ; ZMW(zmw_decorator(Zmw_Decorator_Interior |Zmw_Decorator_Border_In)) { zmw_horizontal_expand(Zmw_False) ; zmw_font_size(24) ; sprintf(tmp, "i = %d", i ) ; zmw_label(tmp) ; } zmw_horizontal_expand(i!=0) ; zmw_vertical_expand(i!=0) ; ZMW(zmw_vbox()) { zmw_horizontal_alignment(-1) ; boxed_text("Left", i) ; zmw_horizontal_alignment(0) ; boxed_text("Horizontal Center", i) ; zmw_horizontal_alignment(1) ; boxed_text("Right", i) ; zmw_height(40) ; ZMW(zmw_hbox()) { zmw_vertical_alignment(-1) ; boxed_text("Top", i) ; zmw_vertical_alignment(0) ; boxed_text("Middle", i) ; zmw_vertical_alignment(1) ; boxed_text("Bottom", i) ; } } } } } } | Strange case for i=1 |
In the case i=1 the alignment does not work. It is because we choose that alignment are only done by containers. In the case i=1 the decorator is expanded, the text it contains is also expanded, the decorator can not make an alignment because the text take all the space. The text is expanded but does not align itself because it is not a container.
Example 3-5. Interactive positioning of widget in a box
void box2(void) { static Zmw_Float_0_1 x = 0.4, y = 0.33 ; ZMW(zmw_window("Box")) { zmw_width(150) ; zmw_height(150) ; ZMW(zmw_vbox()) { zmw_horizontal_expand(Zmw_True) ; zmw_vertical_expand(Zmw_False) ; zmw_hscrollbar(&x,0.1) ; zmw_vertical_expand(Zmw_True) ; ZMW(zmw_hbox()) { zmw_horizontal_expand(Zmw_False) ; zmw_vscrollbar(&y,0.1) ; zmw_horizontal_expand(Zmw_True) ; ZMW(zmw_fixed()) { zmw_x(x*100) ; zmw_y(y*100) ; zmw_label("Here") ; zmw_x(x*100 - 20) ; zmw_y(y*100 + 20) ; zmw_label("UnderLeft of 'Here'") ; } } } } } | Initial state Move text to the left |
The scrollbars are used to modify the widgets positions in the box. As you see, the box does not clip its content.
The tables are simple, each cell contains exactly one widget. The columns and line size are computed to minimise space. The tables uses horizontal/vertical alignment/expand attributes to set their children positions.
zmw_table
(int nb_cols
) ;The widgets in the table are given in the reading order.
zmw_table_with_widths
(int nb_cols
,
int *widths
) ;The columns width are given by the application, the user can interactively change them. The table is ``activated'' on width change.
zmw_table_with_widths_and_selection
(int nb_cols
,
int *widths
,
Zmw_Boolean *selection
,
Zmw_Boolean unique
) ;
As zmw_table_with_widths
.
If unique
is true, then
the user can select only one line in the table.
If not, the user can select of subset of table lines.
The selection
table must
be able to store a number of item equal to
the number of lines.
The table is ``activated'' on width selection.
Example 3-6. Tables
void table(void) { int i ; static int width[] = { 20, 20, 20 } ; static int width2[] = { 50, 100 } ; static Zmw_Boolean selection[6] ; ZMW(zmw_window("Table")) { zmw_horizontal_expand(Zmw_False) ; ZMW(zmw_vbox()) { zmw_label("zmw_table") ; ZMW(zmw_table(3)) { for(i=0; i<12; i++) zmw_int(i) ; } zmw_label("+ widths") ; ZMW(zmw_table_with_widths(3, width)) { for(i=0; i<12; i++) zmw_int( width[i%3] ) ; } zmw_label("+ selection") ; ZMW(zmw_table_with_widths_and_selection (2, width2, selection, Zmw_False)) { zmw_label("i") ; zmw_label("i * i") ; for(i=0; i<5; i++) { zmw_int(1000+i) ; zmw_int((1000+i)*(1000+i)) ; } } if ( zmw_activated() ) for(i=1; i<5; i++) fprintf(stderr, "selection[%d] = %d\n", i, selection[i]) ; /* do not allow header selection */ selection[0] = Zmw_False ; } } } | Initial state After some interactions |
It is possible to use the same width table for two tables. The tables are not yet displayed nicely.
The decorator allows to add a border and a background to a widget. And also to add interactivity to an insensitive widget.
zmw_decorator
(int options
) ;
options
is a binary or between
possible flags.
Here is the flag list:
Zmw_Decorator_Focusable
The widget can take the focus,
It is possible to call zmw_focused()
in order to known its state.
The decorator allocates the space to draw the focus
and draws it is necessary.
Zmw_Decorator_Activable
The widget can be activable, to be activated
the user must press and release the button in the widget.
It is possible to call zmw_activated()
in order to detect the activation.
Zmw_Decorator_Border_In
If set the widget is pushed, else it is normal.
Zmw_Decorator_Border_Relief
Draw a 3d frame. The state pushed or poped
is defined by Zmw_Decorator_Border_In
.
Zmw_Decorator_Border_Embossed
Draw a 3d ridge. The state pushed or poped
is defined by Zmw_Decorator_Border_In
.
Zmw_Decorator_Border_Focus
Draw the focus without testing if it is needed.
Zmw_Decorator_Border_Solid
Draw a border of uniform colour.
Zmw_Decorator_Interior
Draw the background.
The colour depends on Zmw_Decorator_Border_In
value.
Zmw_Decorator_Pushable
Add the option
Zmw_Decorator_Border_In
if the widget is selected.
This option does not add decoration, so it
is not possible to see if the button is pushed or not.
A border or interior option is needed.
Zmw_Decorator_Clip
The child is clipped.
It is only useful if the decorator size is
smaller than its child.
for example if its size is given by
zmw_width
or if a window is downsized.
Zmw_Decorator_Feedback
If the cursor is above the decorator
then the option
Zmw_Decorator_Border_In
is added.
Zmw_Decorator_Translate
The child is translated when its size is allocated. The translation is defined after the options with two more parameters. It is currently only used by the viewport widget.
Example 3-7. Decorators
#define C(X,Y) X ## Y #define T(O) decorated_text(#O, C(Zmw_Decorator_,O)) ; \ decorated_text(#O, C(Zmw_Decorator_,O) \ | Zmw_Decorator_Border_In) void decorated_text(const char *text, int option) { ZMW(zmw_decorator(option)) zmw_label(text) ; if ( zmw_activated() ) printf("%s activated\n", text) ; } void decorator(void) { ZMW(zmw_window("Decorator")) { ZMW(zmw_table(2)) { zmw_label("Option") ; zmw_label("Option | Border_In") ; T(Focusable) ; T(Activable) ; T(Border_Solid) ; T(Border_Embossed) ; T(Border_Relief) ; T(Interior) ; T(Pushable) ; } } } | Initial state After some clicks |
In fact, the button is implemented as a decorator
around a text. The option of this decorator is
Zmw_Decorator_Interior
| Zmw_Decorator_Border_Relief
| Zmw_Decorator_Focusable
| Zmw_Decorator_Activable
| Zmw_Decorator_Pushable
The children of the notebook are the label of the page
and the content of the page.
So the number of children is even.
The children can be of any widget kind,
but the page label should be activable,
for example:
zmw_button
,
zmw_vbox_activable
,
zmw_decorator(Zmw_Decorator_Activable|Zmw_Decorator_Activable_By_Key|Zmw_Decorator_Focusable)
,
...
zmw_notebook
(int *page
) ;
page
is the number of the visible page.
Example 3-8. Notebooks
void notebook(void) { static int current_page = 0 ; // Give the initial page number int i ; ZMW(zmw_window("Notebook")) { ZMW(zmw_notebook(¤t_page)) { zmw_vertical_expand(Zmw_False) ; zmw_horizontal_expand(Zmw_False) ; zmw_button("Large content") ; // The page 0 label ZMW(zmw_hbox()) // The page 0 content for(i=0; i<7; i++) zmw_label("Large") ; ZMW(zmw_vbox_activable()) // The page 1 label is a box { zmw_label("Very") ; zmw_label("tall") ; zmw_label("content") ; } ZMW(zmw_vbox()) // The page 1 content for(i=0; i<5; i++) zmw_label("Tall") ; // The page 2 label is an image zmw_image_from_file_activable("smallcontent.png") ; // The page 2 content is not centered because // it is not in a box. zmw_label("Small content") ; } } } |
Display its child without any change. This widget is used to change the current state only for one widget and not the others. It does not works with more than one child because in this case a box will its work by saving the current state.
zmw_void
(void) ;The example shows that this container can be used for normal widgets or windows widgets.
This widget allows to display or not its content. It is the base of the transient widget. This widget allows transients to appear and diseapear without changing sibling names.
zmw_if_with_accelerators
(Zmw_Boolean visible
) ;
If visible
is true,
the content is here.
The parameter can not change of value at any time.
Only if zmw_state_change_allowed()
is true.
If visible
is false
the children will be executed once in order
to find the accelerators.
zmw_if
(Zmw_Boolean visible
) ;
As zmw_if_with_accelerators
but the content is
not executed if visible
is false.
It should be used when the content does not
contains accelerators.
zmw_tip_visible
(Zmw_Void) ;True if the tip should be displayed.
Composed widgets are containers that insert its child inside a frame composed of widgets.
zmw_viewport_with_scrollbar
(int *x
,
int *y
) ;When the user move the scrollbars the child widget is scrolled and clipped.
Example 3-10. Viewports
void viewport(void) { static Zmw_Float_0_1 x=0, y=0 ; int i ; static int toggle[20] = { 0 } ; char tmp[99] ; ZMW(zmw_window("Viewport")) { ZMW(zmw_vbox()) { // If the size is not set, the viewport take // the size of its content. zmw_height(100) ; zmw_width(90) ; ZMW(zmw_viewport_with_scrollbar(&x, &y)) { ZMW(zmw_vbox()) { for(i=0; i<sizeof(toggle)/sizeof(toggle[0]); i++) { sprintf(tmp, "i is equal to %d", i) ; zmw_check_button_int_with_label(&toggle[i], tmp) ; } } } zmw_height(ZMW_VALUE_UNDEFINED) ; zmw_width(ZMW_VALUE_UNDEFINED) ; sprintf(tmp, "x = %.6f", x) ; zmw_label(tmp) ; sprintf(tmp, "y = %.6f", y) ; zmw_label(tmp) ; } } } | Initial state After some clicks |
Viewport are unusable when they contain thousands
of widgets, for example a database table with many rows.
The scrolled view allows to only works with a subset
of the widgets.
With this widget it is possible to have
millions of widgets without any memory or speed problems.
This widget contains only one child but this child has
a variable number of children.
The children are in a vertical box or table,
if it is a table with columns, the number
of columns should be indicated.
The children must be named with zmw_name
if they use some ressources,
for example if they are an editable text or a menu.
zmw_scrolled_view_with_columns
(int *start
,
int *visible
intmax
intnb_columns
) ;
start
is the index of
the first row.
visible
is the initial number of rows
to display, if the widget is resized this number
will be updated.
max
is the total number
of rows displayable.
nb_columns
indicate
the number of columns if the child is a table.
zmw_scrolled_view
(int *start
,
int *visible
intmax
) ;
As zmw_scrolled_view_with_columns
but with nb_columns
set to 1
.
Example 3-11. Scrolled view
void scrolledview(void) { int i ; static int start=0, visible=10, max=1000 ; ZMW(zmw_window("Scrolled View")) { zmw_width(60) ; zmw_vertical_expand(Zmw_True) ; zmw_horizontal_expand(Zmw_True) ; ZMW(zmw_vbox()) { zmw_vertical_expand(Zmw_False) ; zmw_label("visible = ") ; zmw_int(visible) ; zmw_vertical_expand(Zmw_True) ; zmw_horizontal_expand(Zmw_True) ; ZMW(zmw_scrolled_view(&start, &visible, max)) { ZMW(zmw_vbox()) { zmw_vertical_expand(Zmw_False) ; for(i=start; i<start+visible; i++) zmw_int(i) ; } } } } } | Initial state After some clicks |
Experimental and unusable because there is no cache. So the directory is read each time it is displayed.
zmw_file_selection
(Zmw_Boolean *visible
,
char **filename
,
const char *title
,
const char *message
) ;
Example 3-12. Filechooser
void filechooser(void) { static char *filename_current = NULL ; static Zmw_Boolean choosing_a_filename = Zmw_False ; if ( filename_current == NULL ) filename_current = strdup("tst") ; ZMW(zmw_window("FC")) { ZMW(zmw_vbox()) { zmw_button("Load File") ; if ( zmw_activated() ) choosing_a_filename = Zmw_True ; zmw_file_selection(&choosing_a_filename , &filename_current , "Choose a filename" , "Load file" ) ; if ( zmw_activated() ) printf("Load file : '%s'\n", filename_current) ; zmw_label("File name current:") ; zmw_label(filename_current) ; } } } | Initial state After opening and typing a / |
A message window is a generic alert window that encapsulate the widget you require.
zmw_message
(int visible
,
const char *window_title
,
const char *button_name
) ;
If visible
is true
a window is displayed with the widget inside this one
with a button labelled
button_name
.
The return value must be affected to visible
, so the boolean can be of any type.
zmw_message_char
(char *visible
,
const char *window_title
,
const char *button_name
) ;
As zmw_message
but the
affectation is not necessary because the boolean
is given as a pointer.
Example 3-13. A message window
void message(void) { static int message_visible = Zmw_False ; ZMW(zmw_window("Message")) { zmw_button("Popup") ; if ( zmw_activated() ) message_visible = Zmw_True ; ZMW( message_visible = zmw_message(message_visible, "Message window", "Close window") ) { zmw_vertical_alignment(0) ; zmw_vertical_expand(Zmw_False) ; ZMW( zmw_hbox() ) { zmw_image_from_file("warning.png") ; zmw_label("Why did you click?") ; } } } } | After the button activation |
The modifications of the current state are inherited by the children and the following widgets. The modification never go up into the parent widget.
zmw_width
(int width
) ;
Set the width in pixel of the following widgets.
This attribute does not apply to the children.
If you want to let the widget compute its own size,
you can use ZMW_VALUE_UNDEFINED
as a pixel size.
The attribute does not apply to the windows widget,
it must be applied to the window content.
zmw_height
(int height
) ;As the width, but for the height.
zmw_x
(int x
) ;Set the position on the x axis of the next widgets.
This attribute does not apply to the children.
If you want to let the widget compute its own size,
you can use ZMW_VALUE_UNDEFINED
as a pixel size.
The value must be between 0
and the width of the parent widget.
zmw_y
(int y
) ;As zmw_x
, but for the y axis.
zmw_focus_width
(int nb_pixels
) ;Set the width of the focus rectangle. This value is used only for focusable widget.
zmw_border_width
(int nb_pixels
) ;The border is drawn inside the focus if there is one. Set the width of the border rectangle. This value is used only for widget with a border. If it is an embossed border, take an even value.
zmw_padding_width
(int nb_pixels
) ;Set the amount of unused space between the widget border and the widget content. This attribute applies to the padded widget, so a container can contains widget with various paddings.
zmw_horizontal_expand
(Zmw_Boolean expandable
) ;If true the widget can be stretched horizontally in order to fill the parent width. If there is more than one expandable widget, they stretch proportionally to their minimum size.
zmw_vertical_expand
(Zmw_Boolean expandable
) ;
As zmw_horizontal_expand
but
vertically.
zmw_horizontal_alignment
(int position
) ;
If position
is
0
the following widgets
are centred, if it is -1
they are left flushed and with 1
they are right flushed.
Beware, if the widget is expanded the alignment
is without purpose.
zmw_vertical_alignment
(int position
) ;
As zmw_horizontal_alignment
but
vertically.
zmw_auto_resize
(Zmw_Boolean auto
) ;
If auto
is true, the windows
adjust automatically to their minimum size.
Example 4-1. Changing widget geometry
void text(const char *text) { zmw_vertical_expand(Zmw_True) ; ZMW(zmw_decorator(Zmw_Decorator_Border_Embossed)) { zmw_horizontal_expand(Zmw_False) ; zmw_vertical_expand(Zmw_False) ; zmw_label(text) ; } } void label(const char *text) { zmw_vertical_expand(Zmw_False) ; zmw_label(text) ; } void geometry(void) { static int window_height = ZMW_VALUE_UNDEFINED ; ZMW(zmw_window("Widget Geometry")) { // Height of the window zmw_height(500) ; ZMW(zmw_hbox()) { // The content does not expand to the full size zmw_vertical_expand(Zmw_False) ; zmw_height(window_height) ; ZMW(zmw_vbox()) { zmw_button("Enlarge box") ; if ( zmw_activated() ) window_height = 490 ; // This text seems flushed left // because it take all the window width. label("zmw_width(100)") ; zmw_width(100) ; text("This") ; zmw_border_width(6) ; label("zmw_border_width(6)") ; // This box is not vertically expandable // See the "label" function. // The box width is the same than "This" text. ZMW(zmw_hbox()) { // zmw_width is reset to ZMW_VALUE_UNDEFINED // So the two next widgets width are // computed by the horizontal box. zmw_y(60) ; text("i") ; zmw_y(30) ; text("s") ; } zmw_width(ZMW_VALUE_UNDEFINED) ; label("zmw_width(ZMW_VALUE_UNDEFINED)") ; text("a") ; zmw_horizontal_expand(Zmw_False) ; label("zmw_horizontal_expand(Zmw_False)") ; text("vertical") ; // There is more space under because the padding // is bigger for the next widget. label("zmw_padding_width(6)") ; // This padding is applied to the decorator // and the text it contains zmw_padding_width(6) ; text("sentence") ; label("zmw_horizontal_alignment(1)") ; zmw_horizontal_alignment(1) ; text(".") ; } } } } | Initial state After the button activation |
zmw_font_family
(const char *font_family
) ;
The font_family
is a typical
name as times
,
helvetica
,
courier
zmw_font_size
(int font_size
) ;Normal sized font are 10.
zmw_font_weight
(int font_weight
) ;An integer between 0 and 1000, the bigger the more bold the text is. Medium weight is 500.
zmw_font_style
(Zmw_Font_Style font_style
) ;
Font_Style_Normal
or
Font_Style_Italic
.
zmw_color
(Zmw_Color type
,
int colour
) ;
The type
indicate the widget part
to be modified. It can be:
Zmw_Color_Background_Normal
Zmw_Color_Background_Pushed
Zmw_Color_Background_Poped
Zmw_Color_Border_Light
Zmw_Color_Border_Dark
Zmw_Color_Foreground
.
The colour
is an integer
coded as 256*(256*R+V)+B where RVB are between 0 and 255.
zmw_rgb
(Zmw_Float_0_1 red
,
Zmw_Float_0_1 green
,
Zmw_Float_0_1 blue
) ;
Set Zmw_Color_Background_Normal
to the color given and all the others accordingly
to this one.
Example 4-2. Widget appearance
#define RGB(R,G,B) (((R)<<16) + ((G)<<8) + (B)) void hexa(int i, int nb_digits) { char tmp[99] ; sprintf(tmp, "0x%0*X", nb_digits, i) ; zmw_label(tmp) ; } void appearance(void) { int i, r, g, b ; static char *fonts[] = { "courrier", "fixed", "lucida", "helvetica", "times", "newspaper", "monospace", "sans" } ; zmw_vertical_expand(Zmw_False) ; ZMW(zmw_window("Boxes")) { ZMW(zmw_table(5)) { zmw_label("Red") ; zmw_label("Green") ; zmw_label("Blue") ; zmw_label("Value") ; zmw_label("Sample") ; i = 0 ; for(r = 0; r < 256; r += 255) for(g = 0; g < 256; g += 255) for(b = 0; b < 256; b += 255) { hexa(r, 2) ; hexa(g, 2) ; hexa(b, 2) ; hexa(RGB(r, g, b), 6) ; // The color/font change is trapped by the box zmw_padding_width(0) ; ZMW(zmw_hbox()) { zmw_color(Zmw_Color_Foreground, RGB(r, g, b)) ; zmw_font_family(fonts[i]) ; zmw_font_size(8) ; zmw_label(fonts[i]) ; zmw_font_size(10) ; zmw_label(fonts[i]) ; zmw_font_size(12) ; zmw_label(fonts[i]) ; } i++ ; } } } } |
zmw_sensible
(Zmw_Boolean sensible
) ;
If sensible
is true the activable
buttons are sensible (not greyed) if their activation
is tested.
If it is false, all the next widgets are insensitive.
zmw_focus
(Zmw_Name* focus
) ;
By default only one widget has the focus.
With this function it is possible to have
a focused widget by window or even by container.
The parameter allows to name the focus environment
and to known the focused widget name.
focus
could be a pointer
on a local static variable initialised to NULL
and named with the function call:
zmw_name_initialize(&focus, "a comment")
The focus change must be made before the first
child of the container.
It is not recommended to have multiple focus box
because keyboard focus navigation will not allow
to go to another focus box, so focus navigation with
the keyboard is trapped inside the current focus box.
THis functionnality has been implemented to do the
same things than the other widget libraries but it is
a bad functionnality and it complexify ZMW code by
an order of magnitude.
zmw_name
(char *name
) ;By default, all the widgets are name '?'. A call to this function change the current name. This change is not inherited by the children. This function is only useful for debugging, for affecting resource and in some case where the widget tree is highly dynamic.
Example 4-3. Widget behaviour
void behaviour(void) { static Zmw_Name *focus[] = {NULL, NULL} ; static int sensible = Zmw_True ; static char *text = NULL ; int i ; if ( text == NULL ) { text = strdup("Edite me!") ; zmw_name_initialize(&focus[0] , "Left" ) ; zmw_name_initialize(&focus[1] , "Right" ) ; } ZMW(zmw_window("Behaviours")) { ZMW(zmw_hbox()) { for(i=0; i<2; i++) { if ( i == 1 ) zmw_sensitive(sensible) ; ZMW(zmw_vbox()) { zmw_focus(focus[i]) ; zmw_button("Make right column unsensible") ; if ( zmw_activated() ) sensible = Zmw_False ; zmw_name("secondbutton") ; zmw_button("Make right column sensible") ; if ( zmw_activated() ) sensible = Zmw_True ; zmw_check_button_int_with_label(&sensible, "Sensible state") ; zmw_entry(&text) ; } } } } } | Initial state After some clicks |
In this example, there is two columns. The right one may be sensitive or not. Each of the two columns may have one focused widget. If the cursor is in the left column, the left focused widget receive key event, same thing for the right column.
Event testing can be done inside a widget definition or after a widget instance. Some events are not really events but state testing.
These functions are intended to be used after the widget receiving the event.
zmw_activated
(void) ;Return true if the widget is activated. The event can be a click or a key Return press. If true is returned, the program is allowed to change its state.
zmw_changed
(void) ;
Return true if the widget has changed.
This event indicates that the widget changed
but the user does not terminate the interaction
for example for zmw_entry
and zmw_scrollbar
.
If true is returned, the program is allowed to
change its state.
zmw_accelerator
(GdkModifierType state
,
int character
) ;
The function returns true if the accelerator
is typed anywhere in the application.
If true is returned, the program is allowed to
change its state.
The parameters have the same usage than in the
zmw_button_with_accelerator
function.
zmw_selected
(void) ;
Return true if the mouse button had been pressed
on the widget but is not yet released.
It can be the case for the
button, anchor, scrollbar widgets.
If true is returned, the program is not allowed to
change its state but it can display a different
graphisme for example.
If you want to change the program state
you should test zmw_state_change_allowed()
return value.
zmw_selected_by_its_parents
(void) ;
As zmw_selected
but return true if a parent of the previous widget
is selected.
zmw_selected_by_a_children
(void) ;
As zmw_selected
but return true if a children of the previous widget
is selected.
zmw_drag_from_started
(void) ;
If it returns true, the previous widget
starts to be dragged,
the program may change its state.
It can use the function
zmw_drag_data_set
(char*)
in order to allow the receptor widget to get the data.
The data is a string, it is duplicated by the function.
zmw_drag_from_stopped
(void) ;
If it returns true, the previous widget
stops to be dragged,
the program may change its state.
It can use zmw_drag_accept_get
(void)
in order to know if the drag was accepted.
zmw_drag_from_running
(void) ;
This function should be used as a zmw_if
parameter.
The program must not change its state.
It can use zmw_drag_accept_get
(void) in order to know if the current widget under accept the drag or no.
Beware, this function should only be used
after a zmw_drag_from_started
or
a zmw_drag_from_stopped
.
zmw_drag_to_enter
(void) ;
If it returns true, a dragged widget starts
to be on the previous widget.
The program may change its state.
The program can call zmw_drag_data_get
(void) to retrieve the dragged data.
The program must call zmw_drag_accept_set
(Zmw_Boolean)
to indicate if it accepts the drag or no.
zmw_drag_to_leave
(void) ;If it returns true, a dragged widget stops to be on the previous widget. The program may change its state.
zmw_drag_to_dropped
(void) ;
If it returns true,
the dragged widget had been dropped on the previous widget,
even if the drag was not accepted.
The program may change its state.
It can use zmw_drag_data_get
(void) to retrieve the dragged data.
zmw_drag_swap
(int * index
,
int **index_current
,
) ;
This function allows to swap widgets by dragging them,
The function is called after the widget that can be swapped,
it could be used to change columns order in a table.
index
is a pointer on
the data to be swapped.
*index_current
is NULL
if there is no dragging, else it is the
index of the widget being dragged.
Beware the indexes change when there is a swap and
there is swaps while the button is pressed.
Example 5-1. Widget Drag and Drop
#define NUMBER_OF_BOXES 3 #define BOX_MAX_CONTENT 99 void box_item_remove(int *box, int item) { for(box = &box[item]; *box != -1; box++) box[0] = box[1] ; } void box_item_add(int *box, int value) { while( *box != -1 ) box++ ; box[0] = value ; box[1] = -1 ; } void box_item_draggable(int *box, int item) { char message[99] ; if ( zmw_drag_from_started() ) { sprintf(message, "%d value is dragged", box[item]) ; zmw_drag_data_set(message) ; } if ( zmw_drag_from_stopped() ) { if ( zmw_drag_accept_get() ) { box_item_remove(box, item) ; } } ZMW( zmw_if( zmw_drag_from_running() ) ) { ZMW(zmw_window_drag()) { sprintf(message, "%d %s accepted", box[item], zmw_drag_accept_get() ? " is" : " not" ) ; zmw_label(message) ; } } } void box_take_drop(int *box, char **message, int multiple) { int value ; if ( zmw_drag_to_enter() ) { value = atoi(zmw_drag_data_get()) ; zmw_drag_accept_set( (value % multiple) == 0 ) ; *message = zmw_drag_accept_get() ? "I Accept" : "I Refuse" ; } if ( zmw_drag_to_leave() ) { *message = NULL ; } if ( zmw_drag_to_dropped() ) { if ( zmw_drag_accept_get() ) box_item_add(box, atoi(zmw_drag_data_get()) ) ; *message = NULL ; } } void draganddrop(void) { static int boxes[NUMBER_OF_BOXES][BOX_MAX_CONTENT] = { {1, 6, -1}, {2, 4, 6, -1}, {3, 6, 9, -1} } ; static char *messages[NUMBER_OF_BOXES] = { NULL } ; int i, j ; char title[99] ; for(i=0; i<NUMBER_OF_BOXES; i++) { sprintf(title, "%d Multiples", i+1) ; ZMW(zmw_window(title)) { zmw_height(150) ; zmw_vertical_expand(Zmw_False) ; ZMW(zmw_vbox()) { zmw_label(title) ; for(j=0; boxes[i][j] != -1; j++) { ZMW(zmw_decorator(Zmw_Decorator_Border_Solid)) zmw_int(boxes[i][j]) ; box_item_draggable(boxes[i], j) ; } zmw_label(messages[i] ? messages[i] : "Nothing") ; } box_take_drop(boxes[i], &messages[i], i+1) ; } } } | Initial state Value 1 is dragged over box 3 After some drag and drop |
This example allow to drag and drop integers into box. The 2 multiple box and 3 multiple box do not allow any kind of integer, they can refuse the drop. The example is a little long because it shows that the receptor give a user feedback (refuse or accept) and the dragged value can also give this feedback to indicate if it was accepted or refused.
Example 5-2. Changing widget order
void swappable_text(char **i, char ***current) { zmw_label(*i) ; zmw_drag_swap((int*)i, (int**)current) ; ZMW( zmw_if( zmw_drag_from_running() ) ) { ZMW(zmw_window_drag() ) if ( *current ) zmw_label((char*)**current) ; } } void dragswap(void) { static int table1[] = { 1, 2, 3, 4 } ; static char *table2[] = { "A","B","C","D","E","F","G","H" } ; static int *current1 = NULL; static char **current2 = NULL ; int i ; ZMW(zmw_window("DragSwap")) { ZMW(zmw_vbox()) { // Swap the children of a container ZMW(zmw_hbox()) for(i=0; i<ZMW_TABLE_SIZE(table1); i++) { zmw_int(table1[i]) ; zmw_drag_swap(&table1[i], ¤t1) ; ZMW( zmw_if( zmw_drag_from_running() ) ) ZMW(zmw_window_drag() ) { if ( current1 ) zmw_int(*current1) ; } } // Swap the children in the TWO next containers ZMW(zmw_hbox()) for(i=0; i<ZMW_TABLE_SIZE(table2)/2; i++) swappable_text(&table2[i], ¤t2) ; ZMW(zmw_hbox()) for(i=ZMW_TABLE_SIZE(table2)/2; i<ZMW_TABLE_SIZE(table2); i++) swappable_text(&table2[i], ¤t2) ; } } } | Initial state Value 1 swapped with 3 Swap between A, B and F |
These functions are intended to be used inside
a widget definition input event dispatch,
but they may be used after.
In all the cases, zmw_event_remove
must be
called if the program state change and so the widget hierarchy change.
zmw_button_released_anywhere()
The mouse button was released somewhere.
zmw_button_pressed()
The mouse button was pressed.
zmw_button_released()
The mouse button was released.
zmw_cursor_enter()
The cursor enter in the widget.
As the cursor may enter a few widget at
the same time, the event is not removed.
So the program state change should not modify
the hierarchy.
It is not possible to call zmw_event_remove
.
zmw_cursor_leave()
The cursor leave the widget. Same remarks as above.
zmw_key_pressed()
True if a key is pressed.
In this case you can access the GDK key value
in zmw.event->key.key value
and the string generated in
zmw.event->key.string
.
These functions are intended to be used after widget instance.
zmw_debug_message()
Return true if you can display a debug message in the web browser. See the widget creation to know how to display a debug message.
zmw_drawing()
Return true if you can draw graphics with
GDK on the window
zmw_window_id()
.
zmw_state_change_allowed()
Return true if the application can change its state.
If the application change its window hierarchy,
zmw_event_remove()
should be called to stop the processing.
They are several ways to create new widgets.
The composed widget is the easiest to create. It is only a matter of putting together existing widgets around a user provided (external) widget.
The function must begin by ZMW_EXTERNAL_RESTART ;
and terminate by ZMW_EXTERNAL_STOP ;
.
To include the user child widget (only once), it uses
ZMW_EXTERNAL ;
.
Beware, ZMW_EXTERNAL ;
contains
a return
, so all the local
variables are lost. You should save them in a static stack
because your widget can include itself so static variables
are not directly usable.
If the widget definition returns a value as in
zmw_message
or zmw_check_button
ZMW_EXTERNAL_RETURNS(value)
must
be used in place of ZMW_EXTERNAL
Creating a widget from scratch is easy. A widget is defined by an unique function. The canvas is the following for a nearly minimal widget, it is a painted rectangle of pixels. It can be used as a ruler.
Example 6-2. A minimal widget and its use
void rectangle(/* any parameter you may want */) { switch( zmw_subaction_get() ) { case Zmw_Compute_Required_Size: /* Compute its minimum size from its children sizes */ zmw_min_width_set(5) ; zmw_min_height_set(5) ; break ; case Zmw_Compute_Children_Allocated_Size_And_Pre_Drawing: /* Compute its children sizes and placement and draw the background */ zmw_draw_rectangle(Zmw_Color_Foreground, Zmw_True ,zmw_allocated_x_get(), zmw_allocated_y_get() ,zmw_allocated_width_get(), zmw_allocated_height_get() ) ; break ; case Zmw_Compute_Children_Allocated_Size: /* Compute the children size and placement */ break ; case Zmw_Init: /* Do some initializations before the children */ break ; case Zmw_Post_Drawing: /* Do some drawing after the children drawing */ break ; case Zmw_Input_Event: /* Use the event if it is for the widget */ break ; case Zmw_Nothing: /* Nothing to be done */ break ; case Zmw_Clean: /* Free ressources used by the widget class */ break ; case Zmw_Debug_Message: /* Displays some debugging messages in the web browser */ break ; case Zmw_Subaction_Last: /* Only here to remove a compiler warning, this case is never called */ /* You can replace this case and the other unused by "default:" */ break ; } } void rect() { ZMW(rectangle()) { /* no children */ } } void minimal(void) { ZMW(zmw_window("Minimal")) { ZMW(zmw_hbox()) { ZMW(zmw_vbox()) { zmw_label("Above") ; rect() ; zmw_label("Under") ; } rect() ; zmw_label("Right") ; } } } |
Zmw_Compute_Required_Size
Code computing the size needed to display the widget.
This code use zmw_min_width_set()
and zmw_min_height_set()
.
It can use the size of its children defined in
zmw_nb_of_children_get()
,
zmw_child__required_width_get(i)
,
zmw_child__required_height_get(i)
,
zmw_child__required_x_get(i)
,
zmw_child__required_y_get(i)
.
For most of the children the x
and y
value are equals
to ZMW_VALUE_UNDEFINED
because children let their parent place them.
It is the container job to add the padding
around the widgets
zmw_child__padding_width_get(i)
Zmw_Compute_Children_Allocated_Size
Code computing the children sizes.
The widget use its allocated size defined
in zmw_allocated_x_get()
,
zmw_allocated_y_get()
,
zmw_allocated_width_get()
,
zmw_allocated_height_get()
,
to allocate the space to its children with
zmw_child__allocated_x_set(i,)
,
zmw_child__allocated_y_set(i,)
,
zmw_child__allocated_width_set(i,)
,
zmw_child__allocated_height_set(i,)
.
The x
and y
values are all absolute
and must be computed for the children.
The widget has access to
zmw_child__required_get(i)
in order to compute the allocated size from
the required size.
The required size is the minimal size modified
by the user preferences.
Beware, the computed value are not of the padded widget
but the real one.
Zmw_Compute_Children_Allocated_Size_And_Pre_Drawing
Same as Zmw_Compute_Children_Allocated_Size
but do some pre-drawing.
The drawing here is done before the drawing
done by the children.
Zmw_Post_Drawing
Code drawing to be done after the children drawing.
Zmw_Input_Event
Event handling code.
If this code change the program state,
it is required to call
zmw_event_remove()
to stop the event dispatch and
zmw_need_repaint()
to redraw the screen.
If focusable and activable functionalities
are needed, use a zmw_decorator
.
Testable event have been defined in
event handling section.
Zmw_Clean
The library is exiting, free the resources. It is useful to free local static variables allocated in the widget definition.
Zmw_Nothing
You are required to do nothing.
Zmw_Debug_Message
Insert a debug message in the web interface.
You should use the printf
compatible function http_printf
to output HTML code to
the browser.
Zmw_Init
Code to be executed before any code execution
in the children.
Currently the case is only used in the
zmw_window
widget
because the window need to be created in order
to compute children size.
In some cases value not useful to the user can be stored in the resource system. The resource system contains quadruplet: widget name, attribute name, attribute type, attribute value. Sometimes the user want to store the resource in the application and other time in the resource system. The API is defined in this point of view. The example use an high level subset of the API.
Example 6-3. Using the resource system
void text_with_width(char *text, int *width) { // Set width to a correct value if it is a NULL pointer // If the call to "zmw_resource_int_get" is made before ZMW. // The resource is taken from the previous widget if there is one // or from "parent_name/." if it is the first. // The code (more readable, but not intuitive) is : // // zmw_resource_int_get(&width, "TextWidth", 100 /* Default */) ; // ZMW(zmw_hbox()) // { // ZMW((zmw_resource_int_get(&width, "TextWidth", 100 /* Default */), zmw_hbox())) { zmw_horizontal_expand(Zmw_False) ; zmw_width(*width) ; zmw_label(text) ; zmw_width(ZMW_VALUE_UNDEFINED) ; zmw_button("Enlarge text") ; if ( zmw_activated() ) *width *= 1.5 ; } } void resource(void) { static int width = 100 ; zmw_auto_resize(Zmw_True) ; ZMW(zmw_window("Resource")) { ZMW(zmw_vbox()) { text_with_width("in resource", NULL) ; text_with_width("in application", &width) ; } } } | Initial state After a click on the first button After a click on the second button |
The registration system allows to quickly compare a widget name to a registered one.
name
, "A comment")
Any Zmw_Name variable must
is initialized to NULL
and must be allocated by this call.
The comment is used for debugging.
If the name is yet initialized, it will not be
initialized once more.
zmw_register
(Zmw_Name *name
) ;
Store the name of the current widget in the
variable name
.
If another widget was yet registered in the
name
it is unregistered.
zmw_unregister
(Zmw_Name *name
) ;
Unregister the registered name
.
zmw_name_registered
(const Zmw_Name *name
) ;Return NULL
if the parameter
is not registered.
If not NULL
it is the widget name.
zmw_name_is
(const Zmw_Name *name
) ;
True if the current widget name is the one stored
in name
.
zmw_name_is_inside
(const Zmw_Name *name
) ;
True if the current widget is inside or equal
to the widget stored in name
.
zmw_name_contains
(const Zmw_Name *name
) ;
True if the current widget contains or is equal
to the widget stored in name
.
Example 6-4. Using the registration system
void registration(void) { static Zmw_Name *clicked = NULL ; zmw_name_initialize(&clicked, "Clicked button") ; zmw_padding_width(1) ; zmw_border_width(1) ; ZMW(zmw_window("Registration")) { ZMW(zmw_vbox()) { ZMW(zmw_hbox()) { zmw_label("Clicked button =") ; if ( zmw_name_registered(clicked) ) zmw_label(zmw_name_registered(clicked)) ; else zmw_label("No button clicked ") ; } ZMW(zmw_decorator(Zmw_Decorator_Border_Solid)) { ZMW(zmw_hbox()) { zmw_label("Box1") ; zmw_button("Button1") ; if ( zmw_activated() ) zmw_name_register(clicked) ; } } ZMW(zmw_decorator(Zmw_Decorator_Border_Solid)) { ZMW(zmw_vbox()) { ZMW(zmw_hbox()) { zmw_label("Box2") ; zmw_button("Button2") ; if ( zmw_activated() ) zmw_name_register(clicked) ; } ZMW(zmw_hbox()) { zmw_button("Button3") ; if ( zmw_activated() ) zmw_name_register(clicked) ; if ( zmw_name_is(clicked) ) zmw_label("This button == last clicked") ; else zmw_label("This button != last clicked") ; } } } if ( zmw_name_contains(clicked) ) zmw_label("Box2 contains last clicked") ; else zmw_label("Last clicked is not in Box2") ; } } } | Initial state After a click on the first button After a click on the second button After a click on the third button |
Some explanation for the ZMW developers.
Some problems are raised by the fact the program works without widget instances in memory.
As the system does not use memory to store results.
It computes the values needed each time it is necessary.
So the line of codes are executed a few times even
if there is no apparent loop.
In fact ZMW
hides a loop.
The following example shows that incrementation should be
done in the same widget than initialisations.
The standard for
loop is safe.
Example 7-1. Multiple code executation
void multiple(void) { int i ; ZMW(zmw_window("Multiple")) { ZMW(zmw_vbox()) { zmw_label("Very Bad code:") ; i = 0 ; ZMW(zmw_decorator(Zmw_Decorator_Border_Solid)) { ZMW(zmw_vbox()) { zmw_int(i) ; i++ ; zmw_int(i) ; i++ ; } } zmw_label("Bad code:") ; ZMW(zmw_decorator(Zmw_Decorator_Border_Solid)) { i = 0 ; ZMW(zmw_vbox()) { zmw_int(i) ; i++ ; zmw_int(i) ; i++ ; } } zmw_label("Good code:") ; ZMW(zmw_decorator(Zmw_Decorator_Border_Solid)) { ZMW(zmw_vbox()) { i = 0 ; zmw_int(i) ; i++ ; zmw_int(i) ; i++ ; } } } } } |
When a widget compute its size, it must use only
use the children that are real and not the popup widget
as windows or tips.
To do so, it must discard children
where
zmw_child__used_by_its_parent_get()
is false.
In some cases the ZMW library needs to store
widget references.
For example to remember the focused widgets or
the button pushed.
The library use the widget name which is a string
to store the widget reference.
The default widget name for the third child of the
first widget of the hierarchy is:
/.0/.2
But with this naming scheme, if the widget hierarchy change, the widget names change. In this example, the focus go from the first check button to the second because a widget vanished.
Example 7-2. Naming problem
void toggle_and_its_name(int *value) { char buf[999] ; ZMW(zmw_hbox()) { zmw_check_button_int(value) ; strcpy(buf, zmw_name_full) ; zmw_label("toggle named") ; zmw_label(buf) ; } } /* * "hide_label" variable is used because * if "label_visible" is modified, the widget hierarchy change. * So the state change must be done outside widget hierarchy. * */ void naming(void) { static int t1=0, t2=0 ; static int label_visible=1 ; int hide_label ; hide_label = 0 ; ZMW(zmw_window("Default Name")) { ZMW(zmw_vbox()) { if ( label_visible ) zmw_label("Click on first toggle and go outside") ; toggle_and_its_name(&t1) ; if ( zmw_cursor_leave() ) { zmw_printf("HIDE\n") ; hide_label = 1 ; } toggle_and_its_name(&t2) ; } } zmw_printf("hide label = %d\n", hide_label) ; if ( hide_label ) label_visible = 0 ; } | Initial state Activate first check button Move the cursor to remove the label |
To solve this problem the widget should be named
by the application, with the function
zmw_name
(char *name
).
The name is not inherited by children but it is by
the sibling.
To differentiate siblings with the same name, they are numbered.
The corrected example is:
Example 7-3. Naming widget
void naming2(void)
{
static int t1=0, t2=0 ;
static int label_visible=1 ;
int hide_label ;
hide_label = 0 ;
ZMW(zmw_window("Named toggle"))
{
ZMW(zmw_vbox())
{
if ( label_visible )
zmw_label("Click on first toggle and go outside") ;
zmw_name("Toggle") ;
toggle_and_its_name(&t1) ;
if ( zmw_cursor_leave() )
hide_label = 1 ;
toggle_and_its_name(&t2) ;
}
}
if ( hide_label )
label_visible = 0 ;
}
| Initial state Activate first check button Move the cursor to remove the label |
When a parent widget is not visible, the windows it contains are not visible. This fact cause problems with detached menu, windows defined inside a hidden note book page and windows defined inside menus.
There how to solve this problem with popup windows defined inside menu. In this case a file browser popped from the menu
/Example 7-4. Hidden widgets : Load window popup
void hidden(void) { static char *filename_current = NULL ; static Zmw_Boolean choosing_a_filename = Zmw_False ; if ( filename_current == NULL ) filename_current = strdup("tst") ; ZMW(zmw_window("FC")) { zmw_button("File") ; ZMW( zmw_menu() ) ZMW(zmw_window_popup_bottom()) { zmw_button("Load") ; if ( zmw_activated() ) choosing_a_filename = Zmw_True ; } /* The file chooser must be here and not inside the former block. * If it is before it will disappear when the menu popdown */ zmw_height(100) ; zmw_file_selection(&choosing_a_filename , &filename_current , "Choose a filename" , "Load file" ) ; if ( zmw_activated() ) printf("Load file : '%s'\n", filename_current) ; } } | Initial state Menu popup Click on load |
Some programming shortcuts.
By adding the private include file defining macros in place of functions the program will be quicker. The drawback is that if the library data structures change the program must be recompiled.
#include "zmw.h" #include "zmw_private.h"
If you use many times horizontal box with a couple of children, it is a little verbose to type each time the box definition. A simple #define will ease the task.
Example 7-5. Using #define to have less verbose code
#define HB(X) do { ZMW(zmw_hbox()) { X; } } while(0) void define(void) { static char *surname = NULL, *firstname = NULL ; if ( surname == NULL ) { surname = strdup("Colombus") ; firstname = strdup("Christopher") ; } ZMW(zmw_window("Boxes")) { ZMW(zmw_vbox()) { HB(zmw_label("Surname") ; zmw_entry(&surname)) ; HB(zmw_label("Firstname"); zmw_entry(&firstname)) ; } } } |
If you want to do animation, you need to refresh screen
continuously.
To do so, call zmw_need_repaint()
in your widget.
The example displays several buttons, at any time it is possible to click on a button to make it move or if it is moving to reverse its way. The picture shows what happen when the four buttons are clicked.
The example is a bit long, but it should be easy to encapsulate all this complexity in a widget.
Example 7-6. Animation
struct button { char *label ; // Button label int x1, y1, x2, y2 ; // Position 1 and 2 float movement_duration ; // Duration of the translation int x,y ; // Current button position int x_new, y_new ; // New button position int normal ; // if 1: move from position 1 to 2 int moving ; // if 1: the button is moving float start_time ; // Time of the start of the movement } ; void animate(struct button *b) { float t ; // If it is the first call, initialise the button if ( b->start_time == 0 ) { b->x = b->x_new = b->x1 ; b->y = b->y_new = b->y1 ; b->start_time = -1 ; } zmw_x(b->x) ; zmw_y(b->y) ; zmw_button(b->label) ; if ( zmw_activated() ) { b->normal = !b->normal ; // Change the travel direction if ( b->moving ) { b->start_time = 2*current_time() - b->start_time - b->movement_duration ; } else { b->start_time = current_time() ; b->moving = 1 ; } } if ( zmw_drawing() && b->moving ) { t = ( current_time() - b->start_time) / b->movement_duration ; if ( t > 1 ) { t = 1 ; b->moving = 0 ; } if ( !b->normal ) t = 1 - t ; // do not modify x,y here because it change program state. b->x_new = (b->x2 - b->x1) * t + b->x1 ; b->y_new = (b->y2 - b->y1) * t + b->y1 ; } } void animation(void) { static struct button b[] = { { "leftright" , 0, 0, 100, 0, 1. }, { "slowleftright", 0, 30, 100,30, 5. }, { "downtop" , 0, 75, 0, 0, 3. }, { "diagonal" , 100, 75, 0, 0, 2. }, } ; int i ; ZMW(zmw_window("Animation")) { zmw_width(200) ; zmw_height(100) ; ZMW(zmw_fixed()) { for(i=0; i<ZMW_TABLE_SIZE(b); i++) animate(&b[i]) ; } } // Here a program state change is allowed even it is not // an event handling because it is outside any widget. for(i=0; i<ZMW_TABLE_SIZE(b); i++) { b[i].x = b[i].x_new ; b[i].y = b[i].y_new ; } for(i=0; i<ZMW_TABLE_SIZE(b); i++) if ( b[i].moving ) { zmw_need_repaint() ; break ; } } |
They are several ways to find bugs in the library code or in the application code.
When the application is running, type Control-F1 on any widget. A window with debugging information will appear. These information are mostly for the widget developer.
These function will help to find problems.
Use this function to make an abort in case of big problem in the library or in the widget use. For example if the number of children if not the requested one. This function display the current widget stack. For each widget there is the filename and line number indicating where it has been used.
The command line option are retrieved from
the argument list by zmw_init
.
--cache-size=X
Define the number of widget size to be cached in order to speed up display. If debugging was enabled when the library was compiled this option will slow down execution but more debugging tips will be given.
--pango_cache=X
Define the number of text size to be cached in order to speed up display. This option has been added because PANGO is really too slow to compute text size. A good value can be 4 times the number of different texts visible or in menu items.
--mstimeout=X
Set the number of milliseconds before screen draw. The default is 10000 for a 1/10 seconds screen update rate. If there is no need, the screen will not be redrawn.
--debug=X
Set the debugs options.
It is an addition of flags.
See the zmw.h for the list.
There is Zmw_Debug_Window
,
Zmw_Debug_Draw_Cross
, ...
Beware, if the trace is enabled, it displays many lines of textual information that freeze X11. So the output must be directed in a file.
The default value for the debug flag is not 0, some running time checking are done.
When your application is launched, it display a socket number. The library runs a HTTP on this socket. If your application is the first, its URL will be http://localhost:10000/
It is possible to interactively browse the widget tree, the resources, the drag state and to inspect widget attributes. It is useful to verify the geometry allocation algorithms.
This section certainly contains out of date informations. For an up to date list, see the TODO list in the ZMW sources.
If many text are displayed by the application in an xterm or xemacs X11 freeze. Hopefully Control-Alt-F1 works. The application should be killed in order to release X11. I see no answer to this problem, but it is not longer here with Debian Sarge (bug in GDK or X11?).
I did not find a documentation on how to use the interprocess drag and drop from GDK.
In the application/xml you will find the start of a library allowing to define user interfaces in XML. The way it is done is as the ZMW library without allocating memory to each widget. The application displays its owns description so the user can interactively edit the XML and see the modification immediately.
The parser is called recursively in order to define the widget tree. So the memory used is proportional to the XML widget tree depth. Currently there is 6 integers needed for each call, but it is not interesting to reduce this number.