Using the Zero Memory Widget library (ZMW 0.2.0)

Thierry Excoffier


Table of Contents
Foreword
1. Introduction
2. Simple widgets
Text widget
Special text values: integer
Text Buttons
Check buttons
Anchor and scrollbar
Images
Tearoff
3. Container widgets
Windows
Menu
Alert
Box
Table
Decorator
Notebook
Void widget
If widget
Composed widgets
Viewport
Scrolled view
Filechooser
Message window
4. Current state
Widget geometry
Widget appearance
Widget behaviour
5. Event handling
High level event handling
Low level event handling
State event handling
6. Creating widgets
Creation of a composed widget
Widget creation from scratch
Resource system
Registration system
7. Development
Programming traps
Multiple code execution
Problems computing widget size
Widget naming
Widget hidden by their parent
Tips and tricks
Using less CPU time to speed up things
Less verbose code
Animation
Debugging
Interactive debugging
Debugging functions
Command line options
Web debugging
Bugs, Todo
X11 freeze
Drag and drop works only inside the application
User interface defined in a file
List of Examples
1-1. Hello world
2-1. Text widgets
2-2. Some buttons
2-3. Some check buttons
2-4. Anchors and scrollbars
2-5. Images
3-1. Windows
3-2. A menu with explicit detached variable
3-3. An error message window
3-4. Horizontal and vertical boxes
3-5. Interactive positioning of widget in a box
3-6. Tables
3-7. Decorators
3-8. Notebooks
3-9. Localy changing the current state
3-10. Viewports
3-11. Scrolled view
3-12. Filechooser
3-13. A message window
4-1. Changing widget geometry
4-2. Widget appearance
4-3. Widget behaviour
5-1. Widget Drag and Drop
5-2. Changing widget order
6-1. Composed widget creation
6-2. A minimal widget and its use
6-3. Using the resource system
6-4. Using the registration system
7-1. Multiple code executation
7-2. Naming problem
7-3. Naming widget
7-4. Hidden widgets : Load window popup
7-5. Using #define to have less verbose code
7-6. Animation

Foreword

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.


Chapter 1. Introduction

There is a small example using the ZMW library. It displays a text and a Quit 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 ;
}

#include "zmw.h"

Required to use the library.

void hello_world(void)

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.

static GdkWindow *w = NULL, *tip = NULL ;

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(zmw_window_with_id(&id_window, "My Window"))

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.

ZMW(zmw_vbox())

The window contains a vertical stack of widget. The widget are defined top to bottom in the next block.

zmw_label("Hello World!")

The top most widget is a label.

zmw_button("Quit")

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.

if ( zmw_activated() ) zmw_main_quit(0) ;

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.

if ( zmw_tip_visible() )

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.

zmw_border_width(2)

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.

ZMW(zmw_window_popup_right_with_id(&id_tip))

We choose to display the tip in a popup window at the right of the previous widget (the quit button).

ZMW(zmw_decorator(Zmw_Decorator_Border_Solid))

This widget draw a border around its child.

zmw_label("Click here to close the application") ;

The tip window contains a simple text.

zmw_init(&argc, &argv)

Initialise the library. The parameters recognised by the library are removed from the program argument list.

zmw_main(hello_world)

This function call launch the user interface defined by the function hello_world, it never returns.


Chapter 2. Simple widgets

Simple widget do not contains other widgets, they are the base of the widget interface.


Text widget

void 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.

void 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.

void 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.

void 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.


Special text values: integer

These text values are based upon normal texts.

void 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.

void zmw_int (intinteger) ;

Display an integer.


Text Buttons

void 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.

void 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.

void 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 Button 1

The user press Control and wait


Check buttons

int 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.

int zmw_check_button_with_label (int value, const char *label ) ;

As above but display a label next to the check button.

int 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.

int zmw_check_button_bits_with_label (int value, int bits, const char *label ) ;

As zmw_check_button_bits but with a label.

void zmw_check_button[_bits]_(int|char)[_with_label] ((int|char) *value) ;

These eight functions allow to use former functions without using affectation operator.

void 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.

void 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


Anchor and scrollbar

void 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.

void zmw_anchor_vertical_float (float *x) ;

As above but the value is between 0 and 1.

void 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.

void 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.

void 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.

void zmw_hscrollbar (Zmw_Float_0_1 *x, Zmw_Float_0_1 x_size) ;

As zmw_hscrollbar_with_delta with x_delta=0.1.

void 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.

void 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.


Images

void zmw_image (GdkPixbuf *pixbuf) ;

The image defined is displayed. The image size must only change on user action.

void 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.

void zmw_image_from_file (const char *filename) ;

As zmw_image_from_file_with_pixbuf but the pixmap is stored in the resource system.

void 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.

void 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.

void 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.

void 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 big disk image button set the radius to 50.


Tearoff

These widgets are only useful as a menu item.

void zmw_tearoff_with_id (GdkWindow **window) ;

When the tearoff is clicked, the window is destroyed, the window pointer become NULL,

void zmw_tearoff (void) ;

As zmw_tearoff_with_id but the id is stored as the window resource.


Chapter 3. Container widgets

Container widget are widgets that can contains other widgets. It is the container that choose how to place its children.


Windows

The windows can contain only one child.

void 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.

void zmw_window (const char *title) ;

As zmw_window_with_id but the id is stored in the resource system.

void 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.

void zmw_window_popup_right (void) ;

As zmw_window_popup_right_with_id but the id is stored in the resource system.

void zmw_window_popup_bottom_... (...) ;

All the popup_right functions exist as popup_bottom

void zmw_window_popup_..._title (...) ;

All the popup functions exist with a title. This title is used if the popup is detached.

void 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.

void 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.


Menu

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_Boolean 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_Boolean 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.

void 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.

void 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,


Alert

Utility to create an alert window.

void zmw_alert (const char *message) ;

If *message is not NULL a window is displayed with the message and a close 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


Box

void 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.

void 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.

void 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.

void 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.


Table

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.

void zmw_table (int nb_cols) ;

The widgets in the table are given in the reading order.

void 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.

void 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.


Decorator

The decorator allows to add a border and a background to a widget. And also to add interactivity to an insensitive widget.

void 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


Notebook

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), ...

void 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(&current_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") ;
        }
    }
}


Void widget

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.

void zmw_void (void) ;

The example shows that this container can be used for normal widgets or windows widgets.

Example 3-9. Localy changing the current state

int main(int argc, char *argv[])
{
  zmw_init(&argc, &argv) ;
  zmw_main(zmwvoid) ;
  return 0 ;
}

zmw_move_cursor_to 30 30
zmw_dump_screen 0




If widget

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.

void 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.

void 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_Boolean zmw_tip_visible (Zmw_Void) ;

True if the tip should be displayed.


Composed widgets

Composed widgets are containers that insert its child inside a frame composed of widgets.


Viewport

void 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


Scrolled view

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.

void 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.

void 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


Filechooser

Experimental and unusable because there is no cache. So the directory is read each time it is displayed.

void 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 /


Message window

A message window is a generic alert window that encapsulate the widget you require.

int 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 close button labelled button_name. The return value must be affected to visible, so the boolean can be of any type.

void 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


Chapter 4. Current state

The modifications of the current state are inherited by the children and the following widgets. The modification never go up into the parent widget.


Widget geometry

void 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.

void zmw_height (int height) ;

As the width, but for the height.

void 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.

void zmw_y (int y) ;

As zmw_x, but for the y axis.

void zmw_focus_width (int nb_pixels) ;

Set the width of the focus rectangle. This value is used only for focusable widget.

void 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.

void 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.

void 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.

void zmw_vertical_expand (Zmw_Boolean expandable) ;

As zmw_horizontal_expand but vertically.

void 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.

void zmw_vertical_alignment (int position) ;

As zmw_horizontal_alignment but vertically.

void 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


Widget appearance

void zmw_font_family (const char *font_family) ;

The font_family is a typical name as times, helvetica, courier

void zmw_font_size (int font_size) ;

Normal sized font are 10.

void zmw_font_weight (int font_weight) ;

An integer between 0 and 1000, the bigger the more bold the text is. Medium weight is 500.

void zmw_font_style (Zmw_Font_Style font_style) ;

Font_Style_Normal or Font_Style_Italic.

void 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.

void 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++ ;
                }
        }
    }
}


Widget behaviour

void 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.

void 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.

void 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.


Chapter 5. Event handling

Event testing can be done inside a widget definition or after a widget instance. Some events are not really events but state testing.


High level event handling

These functions are intended to be used after the widget receiving the event.

Zmw_Boolean 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_Boolean 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_Boolean 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_Boolean 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_Boolean zmw_selected_by_its_parents (void) ;

As zmw_selected but return true if a parent of the previous widget is selected.

Zmw_Boolean zmw_selected_by_a_children (void) ;

As zmw_selected but return true if a children of the previous widget is selected.

Zmw_Boolean 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_Boolean 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_Boolean 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_Boolean 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_Boolean 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_Boolean 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.

void 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], &current1) ;
                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], &current2) ;
          ZMW(zmw_hbox())
            for(i=ZMW_TABLE_SIZE(table2)/2; i<ZMW_TABLE_SIZE(table2); i++)
              swappable_text(&table2[i], &current2) ;
        }
    }
}

Initial state

Value 1 swapped with 3

Swap between A, B and F


Low level event handling

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.


State event handling

These functions are intended to be used after widget instance.

Zmw_Boolean 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_Boolean zmw_drawing()

Return true if you can draw graphics with GDK on the window zmw_window_id().

Zmw_Boolean 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.


Chapter 6. Creating widgets

They are several ways to create new widgets.


Creation of a composed widget

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

Example 6-1. Composed widget creation

void between_braces(void)
{
  ZMW_EXTERNAL_RESTART ;
  ZMW(zmw_hbox())
    {
      zmw_label("{") ;
      ZMW_EXTERNAL ;
      zmw_label("}") ;
    }
  ZMW_EXTERNAL_STOP ;
}

void external(void)
{
  ZMW(zmw_window("Composition"))
    {
      ZMW(between_braces())
        {
          zmw_label("HELLO") ;
        }
    }
}


Widget creation from scratch

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.


Resource system

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


Registration system

The registration system allows to quickly compare a widget name to a registered one.

zmw_name_initialize(Zmw_Name **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.

void 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.

void zmw_unregister (Zmw_Name *name) ;

Unregister the registered name.

const char* zmw_name_registered (const Zmw_Name *name) ;

Return NULL if the parameter is not registered. If not NULL it is the widget name.

Zmw_Boolean zmw_name_is (const Zmw_Name *name) ;

True if the current widget name is the one stored in name.

Zmw_Boolean zmw_name_is_inside (const Zmw_Name *name) ;

True if the current widget is inside or equal to the widget stored in name.

Zmw_Boolean 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


Chapter 7. Development

Some explanation for the ZMW developers.


Programming traps

Some problems are raised by the fact the program works without widget instances in memory.


Multiple code execution

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++ ;
                }
            }             
        }
    }
}


Problems computing widget size

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.


Widget naming

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


Widget hidden by their parent

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 File/Load

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


Tips and tricks

Some programming shortcuts.


Using less CPU time to speed up things

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"


Less verbose code

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)) ;
        }
    }
}


Animation

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 ;
      }
}


Debugging

They are several ways to find bugs in the library code or in the application code.


Interactive debugging

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.


Debugging functions

These function will help to find problems.

ZMW_ABORT

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.


Command line options

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.


Web debugging

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.


Bugs, Todo

This section certainly contains out of date informations. For an up to date list, see the TODO list in the ZMW sources.


X11 freeze

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?).


Drag and drop works only inside the application

I did not find a documentation on how to use the interprocess drag and drop from GDK.


User interface defined in a file

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.