Tải bản đầy đủ (.pdf) (26 trang)

Concepts, Techniques, and Models of Computer Programming - Chapter 10 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (165.71 KB, 26 trang )

Part III
Specialized Computation Models
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.

Chapter 10
Graphical User Interface
Programming
“Nowadays the growth of a graphic image can be divided into two
sharply defined phases. The process begins with the search for a
visual form that will interpret as clearly as possible one’s train of
thought. [ ] After this, to my great relief, there dawns the second
phase, that is the making of the graphic print; for now the spirit can
take its rest while the work is taken over by the hands.”
– The Graphic Work of M.C. Escher, M.C. Escher (1898–1972)
This chapter shows a particularly simple and powerful way to do graphical user
interface (GUI) programming. We combine the declarative model together with
the shared-state concurrent model in an approach that takes advantage of the
good properties of each model. To introduce the approach, let us first summarize
the existing approaches:
• Purely procedural. The user interface is constructed by a sequence of graph-
ics commands. These commands can be purely imperative, as in tcl/tk,
object-oriented, as in the Java AWT (Abstract Window Toolkit) package or
its extension, the Swing components, or even functional, as in Haskell fud-
gets. The object-oriented or functional style is preferable to an imperative
style because it is easier to structure the graphics commands.
• Purely declarative. The user interface is constructed by choosing from a set
of predefined possibilities. This is an example of descriptive declarativeness,
as explained in Section 3.1. A well-known example is HTML (HyperText
Markup Language), the formatting language used for Web pages.


• Using an interface builder. The user interface is constructed manually by
the developer, using a direct manipulation interface. A well-known example
is Microsoft Visual Studio.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
690 Graphical User Interface Programming
The procedural approach is expressive (anything at all can be done at run time)
but is complex to use. The declarative approach is easy to use (a few simple
declarations suffice to create an interface) but lacks expressiveness. The interface
builder approach is easy to use and gives immediate feedback on the interface,
but it lacks expressiveness and the interface is hard to change at run time. None
of these approaches is satisfactory. In our view, this is because each is limited to
a single computation model.
This chapter gives an approach to building graphical user interfaces (GUIs)
that combines a declarative base together with a selected set of procedural con-
cepts including objects and threads. We provide a user interface toolkit that is
both expressive and easy to use. In the context of the book, this has two goals:
• To present the ideas underlying a practical tool for GUI design that gives
the user a high level of abstraction. It turns out that the combination of
declarative and non-declarative (i.e., procedural) techniques is particularly
appropriate for graphical user interface design.
• To give a realistic example that shows the advantages of programming with
concepts instead of programming in models. We start from the declarative
programming techniques of Chapter 3 and add state and concurrency ex-
actly where it is needed. This is a practical example of combining several
computation models.
To a first approximation, our user interface specifications are just data structures,
which can be calculated at run time. The declarative model makes it easy to
calculate with symbolic data structures such as records and lists. This means

that we can easily define and manipulate quite sophisticated user interfaces. For
example:
• We build a context-sensitive clock widget, that changes its shape and pre-
sentation depending on an external parameter, which is the window size.
Other widgets and external parameters are just as easily programmed.
• We show how to generate user interfaces quickly starting from program
data. It requires just a few simple data structure manipulations.
The ideas in this chapter are embodied in the
QTk module, which is part of the
Mozart system [65].
QTk (“Quick Tk”) is a full-featured GUI design tool based
on the declarative approach [66, 67].
QTk is implemented as a front end to the
tcl/tk graphics package. It has been used to build GUIs for real applications.
All the examples we give can be run directly with
QTk. This chapter gives most
of the key ideas underlying
QTk but only shows a small fraction of the available
widgets.
Structure of the chapter
The chapter consists of four sections:
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.1 Basic concepts 691
• Section 10.1 introduces the basic concepts underlying declarative and pro-
cedural approaches and how we propose to combine them.
• Section 10.2 gives an introduction to the principles of
QTk and how to use
it to build user interfaces.

• Section 10.3 gives four case studies to progressively illustrate different as-
pects of the approach: a simple progress monitor, a calendar widget, the
automatic generation of a user interface from a data set, and a context-
sensitive clock.
• Section 10.4 says a few words about how
QTk is implemented.
10.1 Basic concepts
What are the relative merits of the declarative and procedural approaches to
specifying user interfaces? The trade-off is between manipulability and expres-
siveness:
• The declarative approach defines a set of possibilities for different attributes.
The developer chooses among this set and defines a data structure that de-
scribes the interface. A purely declarative approach makes it easy to formal-
ly manipulate the user interface definitions, e.g., to translate raw data into
a user interface or to change representations. However, the expressiveness
is limited because it is only possible to express what the designers initially
thought of.
• The procedural approach gives a set of primitive operations and the ability
to write programs with them. These programs construct the interface. A
purely procedural approach has no limits on expressiveness, since in its
general form it defines a full-fledged programming language. However, this
makes it harder to do formal manipulations on the user interface definitions,
i.e., to calculate the user interface.
This trade-off is not a temporary state of affairs, to be solved by some ingenious
new approach. It is a deep property of computation models. As a language
becomes more expressive, its programs become less amenable to formal manipu-
lation. This is illustrated by the Halting Problem.
1
However, this trade-off is not as bad as it seems on first glance. It is still
possible to define a model that is both manipulable and expressive. We can do it

by combining the declarative and procedural approaches. We use the declarative
1
Assume a language as expressive as a Turing machine, i.e., it is based on a general-purpose
computer with potentially unbounded memory. Then it is impossible to write a program that,
when given an input program, determines in finite time whether or not the input program will
halt.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
692 Graphical User Interface Programming
approach in those areas where manipulability is important but a limited expres-
siveness is sufficient. We use the procedural approach for those areas where
expressiveness is essential. To be precise, for each window we define four parts
declaratively:
• The static structure of the window as a set of nested widgets, where a widget
is a primitive component of a graphical user interface.
• The widget types.
• The initial states of the widgets.
• The resize behavior of the window, i.e., how the widgets change size and
relative position when the window size changes.
We define two parts procedurally:
• Procedures that are executed when external events happen. These proce-
dures are called actions. Events are external activities that are detected by
the window.
• Objects that can be called to change the interface in various ways. These
objects are called handlers.
The complete definition of the interface is a nested record value with embedded
procedures and objects. Since it is a record, all the declarative parts can be
formally manipulated. Since it has procedures and objects, it can do arbitrary
computations.

When designing a graphical user interface, we recommend to use the declar-
ative approach as the primary approach, and to supplement it with procedural
aspects to increase expressiveness exactly where it is needed. There is a recent
standard for Web design, Dynamic HTML, that also makes it possible to combine
the declarative and procedural approaches [61]. It uses character strings instead
of records for the declarative part. It is not as tightly integrated with a pro-
gramming language as the approach of this chapter. At the time this book was
written, the performance of the declarative part was not yet adequate to support
the design approach we recommend.
10.2 Using the declarative/procedural approach
As much of the interface as possible is defined declaratively as record values.
Records are a good choice for two reasons: they are very general data structures
and it is easy to calculate with them. The GUI consists of a set of widgets,where
each widget is specified by a record. Specifying a GUI is done not by defining a
new mini-language, but by using records in the existing language. Programming
a complex GUI then becomes a simple matter of doing calculations with records
and lists. Since both are strongly supported by the declarative model, these
calculations are easy to specify and efficient.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.2 Using the declarative/procedural approach 693
User interface description: record
containing action procedures and
unbound variables (for handler objects)
Window on screen
Build user interface
Actions and handler objects
(interpret description to create
handler objects and event thread)

Data to be displayed
Calculate user interface description
ApplicationHuman user
Figure 10.1: Building the graphical user interface
10.2.1 Basic user interface elements
The GUI model of this chapter has five basic elements:
• Windows and widgets.Awindow isarectangularareaofthescreenthat
contains a set of widgets arranged hierarchically according to a particular
layout. A widget is a GUI primitive that is represented visually on the screen
and that contains an interaction protocol, which defines its interactions with
a human user. A widget is specified by a record, which gives its type, initial
state, a reference to its handler, and some of the actions it can invoke (see
below). An interaction protocol defines what information is displayed by
the widget and what sequences of user commands and widget actions are
acceptable.
• Events and actions.Anevent is a well-defined discrete interaction by the
external world on the user interface. An event is defined by its type, the
time at which it occurs, and possibly some additional information (such as
the mouse coordinates). Events are not seen directly by the program, but
only indirectly by means of actions. An event can trigger the invocation of
an action. An action is a procedure that is invoked when a particular event
occurs.
• Handlers.Ahandler is an object with which the program can control a
widget. Each widget can have a corresponding handler.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
694 Graphical User Interface Programming
Figure 10.2: Simple text entry window
10.2.2 Building the graphical user interface

Figure 10.1 shows how a graphical user interface is built. It starts with the data
to be displayed. This data is manipulated to create a record data structure, the
user interface description. This description defines the logical structure of the
interface as a nested record. The record contains embedded action procedures
and unbound variables which will become references to handler objects. The
record is passed to a procedure
QTk.build, which interprets it and builds the
interface.
QTk.build does two things.
• It builds a window using its underlying graphics package.
• It sets up an internal mechanism so that the application can interact with
the window. For this, it creates one handler object per widget and one
thread per window. It registers the action procedures with the widgets
and the events they are triggered on. The action procedures are executed
sequentially in the thread as window events arrive.
An example
The easiest way to see how this works is by means of an example. Here is a simple
user interface description defined as a record:
D=button(text:"Click this button")
The record D defines a widget of button type and the content of the text field
gives the initial text in the button. Other widgets follow the same conventions.
The record label denotes the widget type, the field names denote widget param-
eters, and the field contents denote either the parameters initial values or the
procedural parts of the interface (actions or handlers).
Some of the widgets are able to contain other widgets. By using these, the
complete user interface is a nested record that defines all the widgets and their
logical organization on the screen. For example, here is a simple interface for
doing text entry (see Figure 10.2):
D=td(lr(label(text:"Type your name:")
entry(handle:H))

button(text:"Ok" action:proc {$} {W close} end))
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.2 Using the declarative/procedural approach 695
fun {GetText A}
HTDWin
D=td(lr(label(text:A) entry(handle:H))
button(text:"Ok"
action:proc {$} T={H get($)} {W close} end))
W={QTk.build D}
{W show} {W wait}
T
end
Figure 10.3: Function for doing text entry
Figure 10.4: Windows generated with the lr and td widgets
The
td widget organizes its member widgets in top-down fashion. The lr widget
is similar, but goes left to right. This example has one action,
proc {$} {W
close} end
, and a handle, H, which we will explain later. At this point, both
H and W are still unbound variables. Create the window by passing D to the
QTk.build procedure:
W={QTk.build D}
This creates a window, a window object W that represents it, and a handler object
H. Now we display the window:
{W show}
The user can type text in this window. At any time, the text in the window can
be read by calling the handler

H:
T={H get($)}
This is usually done when the window is closed. To make sure it is done when
the window is closed, we can put it inside the action procedure.
To complete this example, let us encapsulate the whole user interface in a
function called
GetText. Figure 10.3 shows the resulting code. Calling GetText
will wait until the user types a line of text and then return the text:
{Browse {GetText "Type your name:"}}
Note that GetText does a {W wait} call to wait until the window is closed
before returning. Leaving out this call will return immediately with an unbound
variable that is bound later, when the user clicks the button.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
696 Graphical User Interface Programming
Figure 10.5: Window generated with newline and continue codes
10.2.3 Declarative geometry
In addition to the widgets themselves, there are two other aspects of a window
that are defined declaratively: the geometric arrangement of its widgets and the
behavior of its widgets when the window is resized. We describe each in turn. The
geometric arrangement of widgets is defined by means of three special widgets
that can contain other widgets:
• The
lr and td widgets arrange their member widgets left-right or top-down.
Figure 10.4 shows the two windows that are displayed with the following
two commands:
D=lr(label(text:"left")
label(text:"center")
label(text:"right"))

W1={QTk.build D}
{W1 show}
E=td(label(text:"top")
label(text:"center")
label(text:"down"))
W2={QTk.build E}
{W2 show}
• The placeholder widget defines a rectangular area in the window that can
contain any other widget as long as the window exists. The placeholder’s
content can be changed at any time during execution. A placeholder may be
put inside a placeholder, to any level of recursion. In the following example,
the window alternatively contains a label and a pushbutton:
placeholder(handle:P)

{P set(label(text:"Hello"))}

{P set(button(text:"World"))}
Calling {P set(D)} is almost the same as calling {QTk.build D}, i.e., it
interprets the nested record
D and creates handler objects, but the visible
effect is limited to the rectangular area of the placeholder widget.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.2 Using the declarative/procedural approach 697
W
we
n
s
Figure 10.6: Declarative resize behavior

• The
lr and td widgets support the special codes newline, empty,and
continue, which allows to organize their member widgets in a grid struc-
ture with aligned rows and columns of the same size (see Figure 10.5). The
code
newline makes the subsequent contained widgets jump to a new row
(for
lr) or column (for td). All the widgets in the new row or column are
aligned with the widgets in the previous row or column. The
empty spe-
cial code leaves an empty box the size of a widget. The
continue special
code lets the previous widget span over an additional box. The following
description:
lr(button(text:"One" glue:we)
button(text:"Two" glue:we)
button(text:"Three" glue:we) newline
button(text:"Four" glue:we)
button(text:"Five" glue:we)
button(text:"Six" glue:we) newline
button(text:"Seven" glue:we)
button(text:"Eight" glue:we)
button(text:"Nine" glue:we) newline
empty button(text:"Zero" glue:we) continue)
gives the window of Figure 10.5.
10.2.4 Declarative resize behavior
When the size of a window is changed, the interface has to define how the internal
widgets rearrange themselves. This is called the resize behavior.Theresize
behavior is dynamic, i.e., it defines a behavior over time. But it is a sufficiently
restricted kind of dynamic behavior that we can define it using a descriptive

declarative model.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
698 Graphical User Interface Programming
Figure 10.7: Window generated with the glue parameter
We define the resize behavior of a widget by an optional glue parameter, whose
value is an atom made up of any combination of the letters
n, s, w,ande.The
glue parameter places constraints on how the widget is placed and how it resizes.
As Figure 10.6 illustrates, a widget W is always placed inside a rectangular area
and has a “natural” size defined by its contents. One can choose for the widget
to occupy its natural size in either direction (horizontally or vertically) or to be
expanded to take as much space as possible in either direction. For the left-
right direction, the
w value, when present, will attach the widget to the left side
(“west”). The same for the
e value (“east”) and the right side. If w and e are
present simultaneously, then the widget is expanded. Otherwise, it takes up just
its natural size. For the top-down direction, the
n and s values play the same
roles (“north” and “south”). For example, the description:
lr(label(text:"Name" glue:w) entry(glue:we) glue:nwe)
gives the window of Figure 10.7.
10.2.5 Dynamic behavior of widgets
The dynamic behavior of widgets is defined by means of action procedures and
handler objects. Look again at the example of Section 10.2.2:
declare EDWin
D=td(lr(label(text:"Type your name:")
entry(handle:E))

button(text:"Ok" action:toplevel#close))
W={QTk.build D}
{W show}
The action toplevel#close is part of the button; when the button is clicked
then this causes the window to be closed. Generally, actions are zero-argument
procedures, except that short-cuts are given for a few common actions such as
closing the window. The handle
E is an object that allows to control the text
entry widget. For example, here’s how to set the value of the widget’s text entry
field:
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.3 Case studies 699
{E set("Type here")}
Here is how to read and display the value of the field:
{Browse {E get($)}}
Actions can also be attached to events such as mouse clicks:
proc {P} {Browse ´clicked with third mouse button!´} end
{E bind(event:"<3>" action:P)}
The event "<3>" means a click of the third mouse button. Attaching it to E
means that the button has to be clicked when the mouse is over E’s widget. A
complete list of possible events is given in the
QTk documentation in the Mozart
system.
10.3 Case studies
We present four case studies that show different techniques of user interface de-
sign:
• The first is a simple progress monitor. This example has no special features
except to show how simple it is to build a custom display for a particular

purpose.
• The second builds a simple calendar widget. It is based on an
lr widget
with gridding ability. It shows the flexibility of the gridding. It also shows
how to use a placeholder and how state can be introduced to optimize
execution of what is originally a purely declarative calculation.
• The third derives two different GUIs by transforming one data model into
two GUI specifications. The user can switch between the two at any time.
This shows the advantage of tightly integrating the GUI tool with the lan-
guage, since different data models can be represented with the same data
structures (e.g., records and lists) and transformed with the same opera-
tions.
• The fourth defines a clock with an interface that adapts itself according to
an external condition. The best view of the clock data is chosen dynamically
depending on the window size. Because of the mixed declarative/procedural
approach, each view can be completely defined in just a few lines of code.
The second through fourth case studies were originally written by Donatien Gro-
laux.
10.3.1 A simple progress monitor
We start by defining the simple interface that we used in Section 5.5.1 to monitor
the progress of a message-passing program. The interface has a check button
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
700 Graphical User Interface Programming
fun {NewProgWindow CheckMsg}
InfoHdl See={NewCell true}
H D=td(label(text:nil handle:InfoHdl)
checkbutton(
text:CheckMsg handle:H init:true

action:proc {$} See:={H get($)} end))
in
{{QTk.build D} show}
proc {$ Msg}
if @See then {Delay 50} {InfoHdl set(text:Msg)} end
end
end
Figure 10.8: A simple progress monitor
that can be enabled and disabled. Enabling and disabling this button is done
concurrently with the monitored program. When enabled, the interface displays
a message that can be updated dynamically. The program is slowed down so
that each new message appears for at least 50 ms. When disabled, the inter-
face freezes and lets the program run at full speed. This allows to monitor the
progress of a running program without unduly perturbing the program. Fig-
ure 10.8 shows the definition. A screenshot is given in Section 5.5.1. Calling
InfoMsg={NewProgWindow Msg} creates a new window with checkbutton mes-
sage
Msg. During program execution, {InfoMsg Msg} can be called as often as
desired to display
Msg in the window. With the checkbutton, the user can choose
to track these calls or to freeze the display.
10.3.2 A simple calendar widget
The grid structure of Section 10.2.3 can be used to build widgets with data
arranged in rectangular form. We show how by building a simple calendar widget.
Figure 10.9 shows what it looks like. We define the procedure
Calendar that
returns the calendar widget and its display procedure:
proc {Calendar ?Cal ?Redraw}
P in
Cal=placeholder(handle:P)

proc {Redraw T}

{P set( )}
end
end
The calendar widget is a placeholder that is updated by calling {Redraw T} with
atimeargument
T. The redraw procedure should be called at least once a day to
update the calendar. For simplicity, we will redraw the complete calendar each
time.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.3 Case studies 701
Figure 10.9: A simple calendar widget
Let us now see what is inside the
Redraw procedure. It has a time argument
T. Assume that the time has the format of the {OS.localTime} call, which is a
record that looks like this:
time(hour:11 isDst:0 mDay:12 min:5 mon:11 sec:7
wDay:2 yDay:346 year:100)
For the calendar we need only the fields wDay (weekday, 0 to 6, where 0 is Sunday),
mDay (day of month, 1 to 31), mon (month, 0 to 11), and year (years since 1900).
The calendar is a rectangular grid of weekday names and day numbers.
Redraw
builds this grid by using an lr widget. We first make a list of all the calendar
elements and use
newline to go to the next line in the calendar. We start by
defining the calendar’s header:
Header=[label newline

label(text:"Mo") label(text:"Tu") label(text:"We")
label(text:"Th") label(text:"Fr") label(text:"Sa")
label(text:"Su") newline
lrline(glue:we) continue continue continue
continue continue continue newline]
This displays the weekday names and underlines them. We now make a list that
contains all calendar numbers. First, we calculate the number of days in the
month, taking leap years into account:
2
ML={List.nth [31
if (T.year div 4)==0 then 29 else 28 end
31 30 31 30 31 31 30 31 30 31] T.mon+1}
Second, we calculate the number of blank spots in the grid before the calendar
day with number “1”:
2
As an exercise, correct the leap year calculation according to the complete rules for the
Gregorian calendar.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
702 Graphical User Interface Programming
SD=(((7-(T.mDay mod 7))+T.wDay) mod 7)
With these two numbers, we can make a list of calendar days, correctly offset in
the month:
fun {Loop Skip Col Nu}
if Nu>ML then nil
elseif Col==8 then
newline|{Loop Skip 1 Nu}
elseif Skip>0 then
label|{Loop Skip-1 Col+1 Nu}

elseif Nu==T.mDay then
label(text:Nu bg:black fg:white)|{Loop 0 Col+1 Nu+1}
else
label(text:Nu)|{Loop 0 Col+1 Nu+1}
end
end
R={Append Header {Loop SD 1 1}}
Here, Col gives the column (from 1 to 7) and Nu is today’s day number. Finally,
we can update the placeholder:
{P set({List.toTuple lr R})}
This completes the inside of Redraw. Let us now create and display a calendar:
declare Cal Redraw W in
{Calendar Cal Redraw}
W={QTk.build td(Cal)}
{Redraw {OS.localTime}}
{W show}
The calendar can be redrawn at any time by calling Redraw.
Memoization: using state to avoid repeating work
This redraw procedure will redraw the whole calendar each time it is called. For
many clocks this will be once per second or once per minute. This is very wasteful
of computational resources. We can avoid the repetitive redraw by storing the
yDay, year,andmon fields together in a cell, and redrawing only if the content
changes:
proc {Calendar ?Cal ?Redraw}
P Date={NewCell r(yDay:0 year:0 mon:0)}
in
Cal=placeholder(handle:P)
proc {Redraw T}
TOld=@Date
TNew=r(yDay:T.yDay year:T.year mon:T.mon)

in
if TOld==TNew then skip
else
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.3 Case studies 703
Figure 10.10: Automatic generation of a user interface
Date:=TNew

% Recalculate and redisplay as before
end
end
end
If we leave out the final call to P, then the original Redraw procedure has a
declarative implementation and the new
Redraw procedure has a stateful im-
plementation. Yet, both have identical behavior when viewed from the outside.
The state is used just to memorize the previous calendar calculation so that the
procedure can avoid doing the same calculation twice. Paraphrasing philosopher
George Santayana, we can say this is remembering the past to avoid repeating it.
This technique is called memoization. It is a common use of state. It is particu-
larly nice because it is modular: the use of state is hidden inside the procedure
(see Section 6.7.2).
10.3.3 Automatic generation of a user interface
Using records to specify the user interface makes it possible to calculate the us-
er interface directly from the data. This is a powerful technique that supports
advanced approaches to GUI design. One such approach is model-based design.
In this approach, different aspects of a GUI design are specified in separate for-
malisms called models. A running GUI is then obtained from all the models taken

together [145]. Typical models include a domain model, a presentation model, a
dialog model, a task model, a user model, a platform model, and a help model.
These models can be represented within the language as data structures consist-
ing of nested records and procedure values. From the records, it is possible to
calculate
QTk records. It is particularly easy to translate the domain model into
the presentation and dialog models, because the latter are directly supported by
QTk.
The domain model is very similar to the application’s data model. It defines
the data entities that the user can manipulate. The presentation model is a
representation of the visual, auditive and other elements that the UI offers to
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
704 Graphical User Interface Programming
EditPresentation
Original data
UI
Read−only view Editable view
widget (QTk)
Placeholder
UI
ViewPresentation
Figure 10.11: From the original data to the user interface
the users. The dialog model defines how the presentation model interacts with
the user. It represents the actions that a user can initiate via the presentation
elements and the responses of the application.
We give an example to show how one domain model can be automatically
mapped to two presentation models: a read-only view and an editable view of the
data. The user can freely switch between the views by clicking a check button (see

Figure 10.10). The mapping from the domain model to the presentation models
is done in a natural way by means of functions, using declarative programming.
There are two functions,
ViewPresentation and EditPresentation,eachof
which calculates the
QTk presentation model from the same original data (see
Figure 10.11). The presentation model is encapsulated in a common interface
for the two views. A placeholder is used to dynamically display one of the two
views. Because of the common interface, keeping coherence between views is
straightforward.
Let us write a simple example program to illustrate these ideas. We use a
very simple domain model: a list of pairs of the form
identifier#value,which
represents the known information about some entity. The purpose of the GUI is
to display or edit this information. Let us take the following information to start
with:
Data=[name#"Roger"
surname#"Rabbit"
age#14]
Now we define the two functions that translate this domain representation into the
view information needed by
QTk. The first function, ViewPresentation, builds
the read-only view. It builds a representation where each pair
identifier#value
is mapped to a label widget whose text is the identifier followed by a colon “:”
and the corresponding value. Figure 10.12 gives the source code. The function
returns a record with four fields:
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.

10.3 Case studies 705
fun {ViewPresentation Data}
Hdl In={NewCell Data}
HR={MakeRecord hr {Map Data fun {$ D#_} D end}}
in
r(spec: {Adjoin
{List.toTuple td
{Map Data fun {$ D#V}
label(glue:we handle:HR.D text:D#":"#V) end}}
td(glue:nswe handle:Hdl)}
handle: Hdl
set: proc {$ Ds}
In:=Ds
for D#V in Ds do {HR.D set(text:D#":"#V)} end
end
get: fun {$} @In end)
end
Figure 10.12: Defining the read-only presentation
fun {EditPresentation Data}
Hdl Feats={Map Data fun {$ D#_} D end}
HR={MakeRecord hr Feats}
fun {Loop Ds}
case Ds of D#V|Dr then
label(glue:e text:D#":") |
entry(handle:HR.D init:V glue:we) |
newline | {Loop Dr}
else nil end
end
in
r(spec: {Adjoin

{List.toTuple lr {Loop Data}}
lr(glue:nswe handle:Hdl)}
handle: Hdl
set: proc {$ Ds}
for D#V in Ds do {HR.D set(V)} end end
get: fun {$}
{Map Feats fun {$ D} D#{HR.D get($)} end} end)
end
Figure 10.13: Defining the editable presentation
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
706 Graphical User Interface Programming
• Field spec: the interface specification, which describes the widget in QTk
format.
• Field
handle: the widget’s handle, when the interface is created.
• Field
set: a one-argument procedure that updates the information dis-
played by the widget. During execution, this lets us change the data that
is displayed.
• Field
get: a zero-argument function that returns the data that is displayed,
in the same format as it was input. Since
ViewPresentation does not
change the data, this simply returns the last information that was set. (In
EditPresentation it will return the new information that the user has
typed.)
The
get and set operations are used to keep coherence of the data when switch-

ing between the two views. The
HR record collects all the data items’ handles,
for the use of the
get and set operations.
The second function,
EditPresentation, builds the editable view. It builds
a representation where each pair
identifier#value is mapped to a label con-
taining the identifier followed by ”:” and an
entry widget. Figure 10.13 gives
the source code. This function also returns a record with four fields, like the
ViewPresentation function. This time, the result of the get function is ob-
tained from the widgets themselves.
The main application calls both of these functions on the same data. The
main window contains a
placeholder widget and a checkbox widget. Once
the window is built, the specifications of both views are put in the
placeholder
widget. Subsequently they can be put back at any time by using just their
handles. Checking or unchecking the checkbox switches between the two views.
Data integrity between the views is maintained by using their associated
set and
get operations. Here is the source code:
PC
V1={ViewPresentation Data}
V2={EditPresentation Data}
{{QTk.build
td(placeholder(glue:nswe handle:P)
checkbutton(text:"Edit" init:false handle:C
action:

proc {$}
Old#New=if {C get($)} then V1#V2 else V2#V1 end
in {New.set {Old.get}} {P set(New.handle)} end))}
show}
{P set(V2.spec)}
{P set(V1.spec)}
This example shows the advantages of tightly integrating an executable model-
based GUI with an expressive programming language. The different models can
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.3 Case studies 707
Figure 10.14: Three views of FlexClock, a context-sensitive clock
(uses minimum size)

(refreshes each widget
once a second)
procedure
Refresh Widget
(record)
Minimum
size
(each definition has three parts)
Definition of views
Placeholder
selector
View
widget
events
Window resize

Clock
Figure 10.15: Architecture of the context-sensitive clock
all be expressed as data structures of the language. The mappings between the
different models can then be done easily within the language.
10.3.4 A context-sensitive clock
This section defines a simple clock utility, FlexClock, that dynamically displays
a different view depending on the size of the window. When the window is
resized, the “best” view for the new size is chosen. The application defines six
different views and takes about 100 lines of code. It dynamically chooses the
best view among the six. The best view is the one that gives the most detailed
time information in the available window size. Figure 10.14 shows three of the
six views. A more elaborate version with 16 views, an analog clock widget, and
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
708 Graphical User Interface Programming
a calendar widget, is available as part of the Mozart demos.
Figure 10.15 shows the architecture of the clock. Each view consists of three
parts: a refresh procedure, a widget definition, and a minimum size. A clock calls
all the refresh procedures once a second with the current time. A view selector
picks one of the views and puts it in a placeholder widget to display it. Each
time the window is resized, i.e., whenever a resize event occurs, then the view
selector chooses the best view and displays it. The refresh procedures and the
view selector run concurrently. This is acceptable because there is no interference
between them.
Let us implement the clock according to this architecture. We first set up a
small server that periodically calls each procedure in a list of procedures:
NotifyClock
local
Clocks={NewCell nil}

proc {Loop}
T={OS.localTime}
in
for I in @Clocks do {I T} end
{Delay 1000}
{Loop}
end
in
proc {NotifyClock P}
Clocks:=P|@Clocks
end
thread {Loop} end
end
The period is almost exactly once per second.
3
Calling {NotifyClock P} adds
P to the list of procedures to be notified. The refresh procedure of each view
will be put in this list. The
OS.localTime call returns a record that contains
all the time information. To help in calculating the views, we define some utility
functions to format the time information in various ways:
fun {TwoPos I} if I<10 then "0"#I else I end end
fun {FmtTime T} {TwoPos T.hour}#":"#{TwoPos T.min} end
fun {FmtTimeS T} {FmtTime T}#":"#{TwoPos T.sec} end
fun {FmtDate T} {TwoPos T.mDay}#"/"#{TwoPos T.mon+1} end
fun {FmtDateY T} {FmtDate T}#"/"#(1900+T.year) end
fun {FmtDay T}
{List.nth ["Sunday" "Monday" "Tuesday" "Wednesday"
"Thursday" "Friday" "Saturday"] T.wDay+1}
end

fun {FmtMonth T}
{List.nth ["January" "February" "March" "April" "May"
3
Section 4.6.2 explains how to do it exactly once per second.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.3 Case studies 709
"June" "July" "August" "September" "October"
"November" "December"]
T.mon+1}
end
Now we define each view as a record with three elements:
• Field
refresh: a procedure that is called with a time argument to update
the view’s display.
• Field
spec: a declarative specification of the widgets comprising the view.
• Field
surface: the minimal size (horizontally and vertically) that is re-
quired to correctly display the view.
Figure 10.16 defines all six views in one list. Alert readers will notice that there
is a seventh, empty view that will be displayed in case the window is too small to
display a text. The window that displays a view contains just a single placeholder
widget. A placeholder, as we saw before, is a container that can contain any
widget and that can change the displayed widget at any time as long as the
window exists. Here is the window:
declare PWin
W={QTk.build
td(title:"FlexClock demo"

placeholder(handle:P width:1 height:1 glue:nswe))}
To initialize the application, all views are placed once in the placeholder. After
this is done, any view can be displayed again in the placeholder by a single
command using the view’s handle. We also register each refresh procedure with
NotifyClock. The result is a list, Views, that has one triple per view, containing
the minimal width, minimal height, and a handle for the view:
Views={Map ViewList
fun{$ R}
Width#Height=R.surface
in
{P set(R.spec)}
{NotifyClock R.refresh}
Width#Height#(R.spec).handle
end}
Now we have initialized the placeholder and registered all the refresh procedures.
The next step is to set up the mechanism to calculate the best view and display
it. We will assume the best view is the one that satisfies the following three
conditions:
• The window size is big enough to display the view, i.e., window width ≥
minimal view width and window height ≥ minimal view height.
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
710 Graphical User Interface Programming
declare
H0 H1 H2 H3 H4 H5 H6
ViewList=
[r(refresh:proc{$ T} skip end
spec:label(handle:H0 glue:nswe bg:white)
surface:0#0)

r(refresh:proc{$ T} {H1 set(text:{FmtTime T})} end
spec:label(handle:H1 glue:nswe bg:white)
surface:40#10)
r(refresh:proc{$ T} {H2 set(text:{FmtTimeS T})} end
spec:label(handle:H2 glue:nswe bg:white)
surface:80#10)
r(refresh:
proc{$ T}
{H3 set(text:{FmtTime T}#´\n´#{FmtDate T})} end
spec:label(handle:H3 glue:nswe bg:white)
surface:40#30)
r(refresh:
proc{$ T}
{H4 set(text:{FmtTimeS T}#´\n´#{FmtDateY T})}
end
spec:label(handle:H4 glue:nswe bg:white)
surface:80#30)
r(refresh:
proc{$ T}
{H5 set(text:{FmtTimeS T}#´\n´#{FmtDay T}#", "#
{FmtDateY T})} end
spec:label(handle:H5 glue:nswe bg:white)
surface:130#30)
r(refresh:
proc{$ T}
{H6 set(text:{FmtTimeS T}#´\n´#{FmtDay T}#", "#
T.mDay#" "#{FmtMonth T}#" "#(1900+T.year))}
end
spec:label(handle:H6 glue:nswe bg:white)
surface:180#30)]

Figure 10.16: View definitions for the context-sensitive clock
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.
10.3 Case studies 711
Minimal size for view 3
Area in which view 3 is chosen(3)
3
1501000
0
(1)(0) (2) (2) (2)
(0) (3) (4) (5) (6)
50
5
0
(0) (0) (0) (0) (0)
Width
Height
30
40 80 130 180
0
12
4563
10
Figure 10.17: The best view for any size clock window
• The distance between the bottom right corner of the minimal view and the
bottom right corner of the window is minimized. It suffices to minimize the
square of this distance.
• If no view satisfies the above two conditions, the smallest view is chosen by
default.

Figure 10.17 shows how this divides up the plane among the seven views. The
procedure
Place does the calculation and displays the best view in the place-
holder:
proc {Place}
WW={QTk.wInfo width(P)}
WH={QTk.wInfo height(P)}
_#Handle={List.foldRInd Views
fun {$ I W#H#Handle Min#CH}
This=(W-WW)*(W-WW)+(H-WH)*(H-WH)
in
if W<WW andthen H<WH andthen
(Min==inf orelse This<Min) then This#Handle
else Min#CH end
end
inf#local (_#_#H)|_=Views in H end}
in
{P set(Handle)}
end
This starts with a minimum of inf, representing infinity, and is reduced for each
view with a smaller distance. When the window is resized,
Place has to be called
to set the correct view according to the new size of the window. This is done by
binding the <Configure> event of
QTk:
Copyright
c
 2001-3 by P. Van Roy and S. Haridi. All rights reserved.

×