WXlib  tutorial

Fabrice Le Fessant
(Email: Fabrice.Le_Fessant@inria.fr)
INRIA Rocquencourt, B.P. 105, 78153 Le Chesnay Cedex, France






Chapter 1    Introduction

1.1  Overview

WXlib is a library for creating graphical user interfaces for Objective-Caml running under Unix X-windows system. This paper is a tutorial to help you to build your own interfaces.

The first version of WXlib is 016, released in June 16th, 1999. Lattest versions of WXlib are distributed as part of the Efuns1 package, and can be downloaded from:

http:\/\/pauillac.inria.fr\/efuns
Contributions to the WXlib , or any parts of the Efuns package, are welcome, and can be sent to fabrice.le_fessant@inria.fr. Bug reports, requests and questions should be sent to the same address.

1.2  Examples

Many examples are available in the efunstoolkitexamples directory. Other examples can be found in the efunsefuns directory, corresponding to programs used by Efuns, such as efuns_filebrowser or efuns_texbrowser.



Figure 1.1: The efuns_filebrowser window.


1
Efuns is an editor for X-window written in Objective-Caml .

Chapter 2    Getting started

2.1  How to compile programs

WXlib is available both as a bytecode library and a native one. A dedicated program, called wX_config is used to simplify compilation of WXlib programs by providing most useful compilation flags automatically.

For example, the next section presents the Hello World program, which can be found in the sources in efunstoolkitdemo_hello.ml. To compile this example, you can simply enter the following commands:

ocamlc `wX_config -byte` -o hello demo_hello.ml
or
ocamlopt `wX_config -opt` -o hello demo_hello.ml
To list the options of wX_config, use the -help option:

homard:~/devel/efuns/toolkit% wX_config -help
WXlib config finder
  -src : To compile without installing (from sources directory)
  -str : add flags for Str regexp library
  -c : print flags for compiling
  -opt : print flags for native linking
  -byte : print flags for bytecode linking

2.2  First example: Hello world

Here is the simplest source of the Hello World program. This program does nothing else than displaying a simple window with "Hello World" written inside.

open WX_types

let root = new WX_root.from_display "" 0
let top = new WX_wmtop.t root []
let label = new WX_label.t top#container "Hello World" []

let _ =
  top#container_add label#contained;
  top#show;
  loop ()
The first open statement opens the modules describing the types and simple values used by the WXlib library. In our example, only one value depends on this module, the final loop function.

We then create the root object, of real class WX_root.t, by providing the display name ( empty string) and the screen number (default one is zero). This root object will be used as the parent of all toplevel widgets (toplevel window, popup menus, etc).

We can now create the toplevel window top of class WX_wmtop. t. This object only displays a simple window, containing a second child window. The WX_wmtop.t class is different from the WX_top.t class as it provides additional methods to communicate with the Window-Manager, such as setWM_NAME, setWM_ICON_NAME, etc ...The [] parameter is a list of standard options to configure widgets, which will be detailed in section 2.5.

The child of the toplevel window is a simple WX_label.t object, a widget that displays a string in its window (here "Hello World"). Most children widgets require their parent to be passed with type WX_types.container when created. To avoid typing coercions for all of them, all objects contain a container method, returning the object coerced to the correct type.

Now, all objects have been created. Before mapping the windows on the screen, we must specify that the label window is contained in the top window. This is done by calling the container_add method ( a method defined by most container widgets), with the label as parameter. Most container widgets require their contained widgets to be passed with type WX_types.contained, which can be done automatically by calling the contained method of the child widget.

2.3  A more elaborate Hello World



Figure 2.1: The Hello World widget demo.

The previous program doesn't terminate nicely. Thus, we would like the user to simply click on a button, or type the "q" letter to terminate. This is done in the following example.

open Xtypes
open WX_types

let root = new WX_root.from_display "" 0
let top = new WX_wmtop.t root []
let label = new WX_button.with_label top#container "Hello World" []

let _ =
  top#container_add label#contained;
  label#set_action (fun _ -> exit 0);
  top#configure [Bindings [Key(XK.xk_q, anyModifier), (fun _ -> exit 0)]];
  top#show;
  loop () 
Compared to the previous version, we simply replaced the label by a button containing the label. For simplicity, we used the WX_button. with_label class, which is somewhat equivalent to:

let button = new WX_button.t top#container []
let label = new WX_label.t button#container "Hello World" []
let _ = button#container_add label#contained
We then bind some events to the termination action: For the button, we used the set_action which binds a click in the button to an action. For the toplevel window, we used the configure (whose arguments are detailed in section 2.5), to add the bindings between the "q" keystroke (XK.xk_q is the corresponding constant for the Xwindow system1 and the action.

2.4  Widgets contained in widgets

To create your interface, you will probably need lots of widgets. Thus, you need a way to organize all these widgets inside your toplevel windows. This section describe the main different ways of organizing them.

2.4.1  Using bars

The simplest way to organize your interface is the use of bars: bars are simple widgets in which you can insert other widgets. Inserted widgets will be placed from left to right for horizontal bars and from top to bottom for vertical bar. By inserting bars into other bars, you will able to create various interesting effects.



Figure 2.2: The demo_bar widget demo.

Here is a simple example of the use of bars.
open WX_types

let root = new WX_root.from_display "" 0
let top = new WX_top.t root None []
let vbar = new WX_bar.v top#container []
let text = new WX_text.of_string vbar#container 
"Your program has raised a stack overflow exception
Click OK if you want to continue.
Click CANCEL if you want to abort.
Click HELP if you need some help.
" []
let hbar = new WX_bar.h vbar#container [IpadX 5; IpadY 10]
let attrs = [IpadX 3; IpadY 3; ExpandX true]

let ok = new WX_button.with_label hbar#container "OK" attrs
let cancel = new WX_button.with_label hbar#container "CANCEL" attrs
let help = new WX_button.with_label hbar#container "HELP" attrs

let _ =
  ok#set_action (fun _ -> exit 0);
  cancel#set_action (fun _ -> exit 1);
  help#set_action (fun _ -> print_string "HELP"; print_newline ());
  top#container_add vbar#contained;
  vbar#container_add_s [text#contained; hbar#contained];
  hbar#container_add_s [ok#contained; cancel#contained; help#contained];
  top#show;
  loop ()

2.4.2  Using tables

See example in section 3.4.3.

2.5  Configuring widgets

Widgets can be configured in two ways: you can provide a list of options when the widget object is created, or you can at any time use the configure method with a list of options to apply on the object.

Here is the list of currently available options:

type base_attributes =
| MinWidth of int
| MinHeight of int
| MaxWidth of int
| MaxHeight of int
| ExpandX of bool
| ExpandY of bool
| IpadX of int
| IpadY of int
| Position of int * int
ExpandX and ExpandY flags are used to indicate that the widget can receive more space than it requires. Position is only used to specify the initial position of the WX_top widget. IpadX and IpadY are used to specify the distances between the rectangle actually used in the widget (for drawing or for contained widgets) and the window of the widget.


1
You can also use the following code, a bit slower: List.assoc "q" XK.name_to_keysym

Chapter 3    Widgets description

For each widget called widget, the corresponding class is WX_widget.t. Look at the contain of the corresponding files to learn which particular methods exist for each of these classes. Sometimes, the files also contain derived classes easier to used. For example, the WX_text module also contains the WX_text.of_file and WX_text.of_string classes.

3.1  Basic widgets

3.1.1  The display object

Object used to manipulate the display.

3.1.2  The root widget

Object used as the parent for all toplevel windows in a given screen.

3.1.3  The object widget

The common ancestor (inherited) for all classes which create windows. This class can be used to create new widgets.

3.2  Toplevel widgets

3.2.1  The top widget

Widget used to display a toplevel window, containing a child window.

3.2.2  The wmtop widget

Widget derived from the top widget with methods to interact with the Window-manager (following ICCCM conventions).

3.2.3  The xterm widget

Widget only used by Efuns (should be moved to Efuns directory ?).

3.2.4  The popup widget

Widget used to create simple popup menus.

3.3  Drawing widgets

3.3.1  The Graphics widget

This widget emulates the Graphics module of the standard Objective-Caml library. Several such widgets can be created in a single application, switching between widgets being provided using the active method.



Figure 3.1: The demo_graphics widget demo.

open WX_types
open XGraphics (* instead of Graphics *)

let root = new WX_root.from_display "" 0
let appli = new WX_appli.t root 
  [Bindings [Key (XK.xk_q,0), (fun () -> exit 0)]]
  (* This replace the open_graph call: *)
let graphics = new WX_Graphics.t appli#container [] 600 400

let help_menu = [|
    "About", (fun _ -> Printf.printf "ABOUT HELP"; print_newline ());
    "Index", (fun _ -> Printf.printf "INDEX HELP"; print_newline ());
  |]
  
let redraw x y =
  clear_graph();
  set_color blue;
  fill_poly [|100,100; 100,200; 200,200; 200,100; 100,100 |];
  set_color red;
  fill_poly [|x,y; x,y+100; x+100,y+100; x+100,y; x,y |];
  update()

let _ =
  appli#container_add (graphics#contained);  
  appli#add_menu "Help" help_menu;
  appli#show;
  set_update_style FlushAll;
  let _ = wait_next_event[Button_down] in
  redraw 0 0;
  while 
    let ev = wait_next_event[Button_up;Button_down;Mouse_motion;Key_pressed] in
    if ev.button then redraw ev.mouse_x ev.mouse_y;
    ev.key <> 'q'
  do () done;
  close_graph()

3.3.2  The label widget

This widget displays a simple string in its window. Justification can be specified through the set_justification method.

3.3.3  The ledit widget

This widget is an editable label.

3.3.4  The pixmap widget

This widget displays a pixmap in its window.

3.3.5  The scale widget

This widget is used to allow the user to modify a value within a specific range. An adjustement (WX_adjust.t must be used to specify the value).

3.3.6  The scrollbar widget

This widget display a scrollbar. See section 3.4.7 for an example of how to use scrollbars.

3.3.7  The selector widget

This widget displays a label, whose value can be selected by clicking on it and selecting a new value in the associated popup menu.

3.4  Container widgets

3.4.1  The bar widget

This widget allows to pack several widgets in a single bar, either horizontal or vertical.

3.4.2  The button widget

This widget displays a button. The set_action method specifies the action executed when the button is pressed.

3.4.3  The table widget

This widget is used to pack several widgets in a single window, with position related to rows and columns in a table. Here is a small example of what can be done with tables and not with bars.



Figure 3.2: The demo_table widget demo.

open Xtypes
open WX_types
  
let display = new WX_display.t ""
let root = new WX_root.t display 0
let top = new WX_wmtop.t root [MinWidth 300; MinHeight 300]
let table = new WX_table.t top#container [] 10 10 true
  
let list = [
    1,1,2,3,"yellow"; 
    2,5,2,2,"red";
    5,2,2,2,"blue";
    6,6,3,3,"pink";
  ]

let _ =
  List.iter (fun (x,y,dx,dy,color) ->
      let button = new WX_button.t table#container [] in
      let obj = new WX_object.t button#container [
          Background color; MinWidth (dx * 50); MinHeight (dy * 50)] in
      button#set_action (fun _ -> Printf.printf "Select: %s" color;
          print_newline ());
      button#container_add obj#contained;
      table#container_add button#contained x y dx dy;      
  ) list;
  top#container_add table#contained;
  top#show;
  loop ()

3.4.4  The tree widget

This widget is used to display several widgets as a tree, with branches which can opened by clicking. Here is a simple example of program using the tree widget.



Figure 3.3: The demo_tree widget demo.

open Xtypes
open WX_types
open WX_tree
  
let display = new WX_display.t ""
let root = new WX_root.t display 0
let top = new WX_wmtop.t root []
let tree = new WX_tree.t top#container []
let subtree = new WX_tree.t tree#container []
let list = [ "Bonjour"; "Hello"; "Gutten tag" ]
let sublist = [ "Salut"; "Coucou"; "Hi"; "Good morning" ]
  
let map = List.map (fun s ->
   Leaf (0,(new WX_label.t tree#container s [])#contained)
    ) list 
let map3 = List.map (fun s ->
        Leaf (0,(new WX_label.t tree#container s [])#contained)
    ) list
let submap = List.map (fun s ->
        Leaf (0,(new WX_label.t subtree#container s [])#contained)
    ) sublist
let bigmap = map @ (Branch (false,
        (new WX_label.t tree#container "Autres" [])#contained,
        subtree#contained) :: map3)

let _ =
  subtree#set_desc submap;
  tree#set_desc bigmap;
  top#container_add tree#contained;
  top#show;
  loop ()

3.4.5  The text widget

This widget displays a text (not editable) with incrusted widgets. The WX_text module also contains the WX_text.of_file and WX_text.of_string classes, which are easier to create for displaying a file or a simple string with newlines.

3.4.6  The radiobutton widget

This button is used to allow the user to select a value from a set. Each button is selected, if the current value is the value associated with the button, or not selected otherwise.

3.4.7  The viewport widget

This widget is used to partially display a widget. This is often used in association with scrollbars to select the part of the child widget displayed. Adjustements are used to communicate the position of the child widget.

open WX_types

let filename = Sys.argv.(1)
  
let root = new WX_root.from_display "" 0

let top = new WX_wmtop.t root []
let hbar = new WX_bar.h top#container []

let adx = new WX_adjust.t ()
let ady = new WX_adjust.t ()
let viewport = new WX_viewport.t hbar#container adx ady 
    [MinHeight 50; MinWidth 50;ExpandX true]
let vscroll = new WX_scrollbar.v hbar#container ady []

let filetext = new WX_text.of_file viewport#container filename
    [Relief ReliefRaised; Foreground "red"; Background "yellow"]
  
let _ =
  hbar#container_add_s [viewport#contained; vscroll#contained];
  viewport#container_add (filetext#contained);
  top#container_add (hbar#contained);  
  top#configure [Bindings [
      Key(XK.xk_Prior,0),(fun _ -> ady#page_up);
      Key(XK.xk_Next,0),(fun _ -> ady#page_down);
      Key(XK.xk_q,0),(fun _ -> exit 0);
    ]];
  top#show;
  loop ()

3.4.8  The panel widget

This widget is used to share one window between two widgets. The space available for each child widget is specified by an adjustement. The separation between the two children can be dragged with the mouse using the WX_panel.separator widget.



Figure 3.4: The demo_panel widget demo.

open WX_types

let _ = if Array.length Sys.argv <> 3 then 
    failwith "Usage: demo_panel file1 file2"
    
let filename1 = Sys.argv.(1)
let filename2 = Sys.argv.(2)

let root = new WX_root.from_display "" 0

let top = new WX_wmtop.t root  
    [Bindings [Key (XK.xk_q,0), (fun () -> exit 0)]]

let vbar = new WX_bar.v top#container []
let adj = new WX_adjust.t ()
let panel = new WX_panel.t Vertical vbar#container adj []
  
let viewtext parent filename =
  let hbar = new WX_bar.h parent#container [] in
  let adx = new WX_adjust.t () in
  let ady = new WX_adjust.t () in
  let viewport = new WX_viewport.t hbar#container adx ady 
      [MinHeight 50; MinWidth 50;ExpandX true] in
  let vscroll = new WX_scrollbar.v hbar#container ady [] in
  let text = new WX_text.of_file viewport#container filename 
       [Background "black"; ExpandX true] in
  hbar#container_add_s [viewport#contained; vscroll#contained];  
  viewport#container_add (text#contained);  
  hbar#contained

let text1 = viewtext panel filename1
let text2 = viewtext panel filename2

let sep = new WX_panel.separator panel adj []
  
let _ =
  panel#set_first text1;
  panel#set_second text2;
  adj#set_pos 1 2; (* initial position is half panel for each window *)
  panel#set_step 5;
  vbar#container_add panel#contained;
  top#container_add (vbar#contained);  
  top#show;
  loop ()

3.4.9  The swap widget

This widget is used to share one window between several widgets, only one child widget being viewable at a given time.

3.4.10  The notebook widget

This widget is used to display a widget if its label has been selected through a list of labels.



Figure 3.5: The demo_notebook widget demo.

open Xtypes
open WX_types
  
let display = new WX_display.t ""
let root = new WX_root.t display 0
let top = new WX_wmtop.t root [MinWidth 10; MinHeight 10; 
    MaxWidth 600; MaxHeight 700]
let book = new WX_notebook.v top#container []
  
let files = [ "demo_tree.ml"; "demo_table.ml"; "demo_graphics.ml";
    "demo_filesel.ml"; "demo_calc.ml";"demo_file.ml";"demo_notebook.ml"]
  
let _ =  
  book#container_add_s (List.map (fun name -> name,
        (new WX_text.of_file book#container name [])#contained
        ) files);
  top#container_add book#contained;
  top#show;
  loop ()

3.5  Widgets with no windows

3.5.1  The adjust widget

This adjustement object is used to monitor the evolution of a value. Objects can specify hooks to be executed when the value is changed, using the add_subject method.

3.5.2  The dummy widget

This widget can be used in any container to display ... well, a space between other widgets. The main interest is that the size of the space can be configured.

3.5.3  The deleg objects

These objects are used to delegate some methods to a given object.

3.6  Composite widgets

3.6.1  The appli widget

This widget is used to create a toplevel window, with a menubar at its top.

3.6.2  The filesel widget

This widget is used to select a filename in a dialog box, with a filter and two lists for directories and simple files.

3.6.3  The dialog widget

This widget is used to display a text and a list of button under the text.


This document was translated from LATEX by HEVEA.