3 Tutorial Example : a simple word processing application

The example given in this chapter describes how a gui is built using QTk module. The creation of the widgets, their geometry inside the window and their interaction with Mozart are described.


Figure 3.1: Notepad application


3.1 Geometry management

The example application will consist of a window composed by

The construction of a window is completely described by a record. This record defines the widgets that compose the window, how these widgets must be placed and what is their behaviour is they must be resized. The label of the record defines the widget that must be placed. The content of the record defines the parameters of the widget. Some widgets act as containers for other widgets. The two main containers are :

The toolbar wil be composed of button widgets. The text parameter defines the text of the button.


Figure 3.2.


The toolbar is described by :

Toolbar=lr(button(text:"Save")
           button(text:"Load")
           button(text:"Quit"))

which literally means : place these three buttons respectively from left to right, each button taking the size it needs to display itself. Combining with the text widget, we obtain :

Description=td(Toolbar
               text)

which literally means : place the toolbar and the text respectively from top to bottom, each widget taking the size it needs to display itself.

Now we can build a window from this description by the command :

Window={QTk.build Description}

This window is hidden by default, so you have to show it to make it visible :

{Window show}

If you resize the window, the widgets don't resize in a very clever way inside the window.


Figure 3.3.


One expects the toolbar to stick itself to the top left of the window, and the text widget to take all remaining available size below. All widgets have a glue parameter that allow to specify contraints to the geometry manager. Valid values for the glue parameters are atoms that are combinations of n, s, w and e. By default a widget takes as much place as it needs to draw itself, and if the area in which it is drawed is bigger than that size, the widget is centered inside. By specifying n (resp s, w, e) you enforce the north (resp south, west and east) border of the widget to glue to its top (resp down, left, right) neighboor. If you specify both ns (resp we) you enforce both opposite border to stick to their respective neighboor, resulting in the widget taking all the vertical (resp horizontal) space available.

Using the glue parameter we define :

Toolbar=lr(glue:we                     % the toolbar glues itself to the top left of the window
           button(text:"Save" glue:w)  % the button glues itself to the left of the lr widget
           button(text:"Load" glue:w)  % idem
           button(text:"Quit" glue:w)) % idem

and also

Description=td(Toolbar
               text(glue:nswe))

Note that the very first td or lr widget is always implicitely glue:nswe. Rebuilding and showing the window, we obtain an application that has a complete graphical user interface, except that it is an empty shell.

3.2 Interaction with the user

An action can be associated to buttons. It is executed when the user clicks the button :

button(text:"Quit" glue:w action:proc{$} {Application.exit 0})}

This definition makes the oz application terminate when the user clicks the Quit button. Another parameter for closes the window :

button(text:"Quit" glue:w action:toplevel#close)

Let's make procedures for the Save and Load buttons. The new definitions for these buttons are thus :

button(text:"Save" glue:w action:SaveText)
button(text:"Load" glue:w action:LoadText)

The LoadText and SaveText procedures will do these things :

We need a way to dynamically change or get its state, i.e. interact with the widget. This can be done with an handle :

TextHandle
Description=td(Toolbar
               text(glue:nswe handle:TextHandle))

After the window is built, the unbound variable TextHandle is bound to an object that controls the text widget. The current text can be obtained by :

{TextHandle get($)}

and set by :

{TextHandle set($)}

We can now give the SaveText and LoadText procedures :

proc{SaveText}
  Name={QTk.dialogbox save($)}
in 
  try 
    File={New Open.file init(name:Name flags:[write create])}
    Contents={TextHandle get($)}
  in 
    {File write(vs:Contents)}
    {File close}
  catch _ then skip end 
end 
 
proc{LoadText}
  Name={QTk.dialogbox load($)}
  Contents={TextHandle get($)}
in 
  try 
    File={New Open.file init(name:Name)}
    Contents={File read(list:$ size:all)}
  in 
    {TextHandle set(Contents)}
    {File close}
  catch _ then skip end 
end

And the application is now complete.

3.3 Enhancements

Instead of using standard buttons, we might want to use a toolbar look and feel. A QTk widget does that : tbbutton. As its interface is the same as standard buttons, we just have to change le label of the record :

Toolbar=lr(glue:we
           tbbutton(text:"Save" glue:w)
           tbbutton(text:"Load" glue:w)
           tbbutton(text:"Quit" glue:w))

If there is a lot of text, scrollbars are usefull to allow easy mouse navigation. This can be added with a parameter :

text(glue:nswe handle:TextHandle tdscrollbar:true)

Moreover one may prefer the white color as background color for the text :

text(glue:nswe handle:TextHandle tdscrollbar:true bg:white)

As you can see, widgets are highly configurable by parameters. Most of these parameters can be dynamically changed :

{TextHandle set(bg:white)}

Sets also the background to white but it can be done at any time as soon as the window is built and as long as it isn't closed. Interfaces to widgets were made as uniform as possible and similar widgets have similar parameters name and use.

3.4 The complete code

Source File

declare 
[QTk]={Module.link ["http://www.info.ucl.ac.be/people/ned/qtk/QTk.ozf"]}
 
proc{SaveText}
   Name={QTk.dialogbox save($)}
in  
   try  
      File={New Open.file init(name:Name flags:[write create])}
      Contents={TextHandle get($)}
   in  
      {File write(vs:Contents)}
      {File close}
   catch _ then skip end  
end  
 
proc{LoadText}
   Name={QTk.dialogbox load($)}
in  
   try  
      File={New Open.file init(name:Name)}
      Contents={File read(list:$ size:all)}
   in  
      {TextHandle set(Contents)}
      {File close}
   catch _ then skip end  
end 
 
Toolbar=lr(glue:we
           tbbutton(text:"Save" glue:w action:SaveText)
           tbbutton(text:"Load" glue:w action:LoadText)
           tbbutton(text:"Quit" glue:w action:toplevel#close))
 
TextHandle
 
Window={QTk.build td(Toolbar
                     text(glue:nswe handle:TextHandle bg:white tdscrollbar:true))}
 
{Window show}
 


Donatien Grolaux
Version 1.2.3 (20011129)