An Introduction to TkGofer

An Introduction to TkGofer

Ton Vullinghs, Wolfram Schulte, Thilo Schwinn
 
Spring 1996
Universität Ulm
Fakultät für Informatik
Germany

Contents

This report gives an introduction to TkGofer. TkGofer is a library of functions for writing graphical user interfaces (GUIs) in the lazy and pure functional programming language Gofer. The library provides a convenient, abstract and high-level way to write window-oriented applications. The implementation rests on modern concepts like monads and constructor classes.

The objective of this report is to illustrate the way in which you write GUIs in TkGofer. All the provided widgets are introduced and explained by a set of illustrating examples. The last part of the manual lists the signatures of the user functions of the GUI library.

TkGofer is freely available. For more information please contact ton@informatik.uni-ulm.de

Acknowledgments

Several people have contributed their ideas and suggestions. Special thanks to Daniel Tuijnman, who designed and implemented substantial parts of earlier versions of the library. Furthermore we thank Erik Meijer and Klaus Achatz for their encouraging and helpful comments.

Introduction

 

Functional programming languages offer many advantages to programmers. Using functional languages often results in faster development times and shorter code compared with imperative languages. Furthermore, reasoning about and reusing programs is easier. Recent research in the field of functional programming resulted in new concepts such as monads to tame the imperative aspects of I/O and state [Wad90], and constructor classes to deal with higher order polymorphism [Jon95].

Today, the specification of graphical user interfaces (GUIs) is an essential part of any realization of interactive systems. Graphical user interface libraries make the implementation of user interfaces easier. High-level windowing environments and libraries, like Delphi, the Tk Toolkit, and the Java AWT package provide complete sets of user interface components, such as buttons, text fields, windows or even complex input dialogs. However, considered from a conceptual point of view, most of these libraries are still lacking in abstraction and clarity. Shortcomings of imperative GUI-library are often caused by inferior type systems, indistinct naming conventions and complex layout definitions.

Therefore, it is an obvious idea to incorporate GUI programming in functional languages, and to take advantage of the additional benefits these languages offer. Of course, integrating GUI programming and pure functional programming gives rise to a number of new problems. Typical problems concern state and IO. There exist many different proposals for input/output in lazy functional languages, e.g., streams, continuations, unique types and monads. Since the works of Moggi [Mog91], Wadler [Wad92] and many other people, monads have become most popular for this purpose.

In our solution, we use monads to handle IO. Additionally, monads play a vital role in the treatment and structuring of the GUI state and the application state.

We use type and constructor classes to develop an extendable hierarchy of widget classes. This hierarchy implies type secure and flexible GUI programming and permits homogeneous access to typed widget options. Furthermore, classes support code reuse and the definition of new widgets.

Several alternative approaches to integrate GUIs in pure functional languages exist. Eye-cathing projects are those carried out within the Haskell community like the Fudgets for Haskell [CH93] and Gadgets for Gofer [NR95]. Both approaches are based on a stream-like model of user interfaces. Other extensive functional GUI libraries are written for Clean [AvGP93], using unique types, and for Opal [FGPS96], using communicating agents.

The main objective of this report is to describe the TkGofer GUI Library. TkGofer is an extension of the pure, non-strict, functional language Gofer. For the implementation of the graphical IO routines, we decided to use the user interface toolkit Tcl/Tk. This document will mainly focus on the question how to to write programs in TkGofer. Furthermore, it gives a brief description of the implementation of the system. Since all the GUI functions are abstractions of Tcl/Tk procedure calls, it is useful but not necessary to know a little about Tk. The best way to get some insight in the ins and outs of Tcl/Tk is by reading John Ousterhout's book `Tcl and the Tk Toolkit', published by Addison Wesley in 1994. For a good introduction to functional programming we refer to [BW89]. Many of the more advanced concepts of functional programs such as monads and constructor classes are explained in [JM95]. The release notes and reference manual included in the standard Gofer distribution will tell you all the details about functional programming in Gofer [Jon93a, Jon93b].

Starting TkGofer

The TkGofer interpreter looks and behaves exactly the same as the standard gofer interpreter. TkGofer starts with loading the file tk.prelude. It is an extension of the cc.prelude and contains all the standard definitions you will need to write GUI programs in Gofer. You can start TkGofer by entering tkgofer, after which your display will show something like:

<TkGofer Start 1> =

Gofer Version 2.30a  Copyright (c) Mark P Jones 1991-1994
Reading script file "tk.prelude":
                   
Gofer session for:
tk.prelude
Type :? for help
<>
Macro never referenced.

The command :? will give you an overview of the available interpreter commands.

The First TkGofer Program

Figure 1.1 shows one of the simplest applications you may possibly write in TkGofer. It shows an entry and a button widget. The entry displays an integer. The value of this integer is incremented when the button is pressed.

   figure46
Figure 1.1: A simple adder

The program below shows all you have to write in TkGofer to implement the counter. Simply type `adder' to let the example run.

"examples/adder.gs" 2 =
  
adder :: IO ()
adder = start (
  window [title "Counter"]                      `bind` \w ->
  entry  [initValue 0] w                        `bind` \e ->
  button [text "Increment", command (incr e)] w `bind` \b ->
  pack (e ^-^ b)
  )
 
incr :: Entry Int -> GUI ()
incr e = getValue e `bind` (\x -> setValue e (x+1))
<>
 

The function adder implements the user interface of the application. It creates a window, and two window items. The items are combined horizontally, using the combinator ^ -^ . This means that the label and the entry are placed above each other and are aligned in length. The function bind combines two monadic actions.

The function incr defines the action that happens when we press the button. The actual value of the entry field is read, incremented and written back to the display.

Notation

All programs are written in Gofer. GUI datatypes and functions are defined in the tk.prelude, Most of the GUI-functions have a monadic type, i.e., they return a value of type GUI a. To bind together monadic functions, you may use the functions bind and result. We prefer however a more readable style, using the Gofer do-notation.

Using the do-notation we can rewrite the previous example in the following way:

"examples/adderdo.gs" 3 =

adder :: IO ()
adder = start $
  do w <- window [title "Counter"]
     e <- entry  [initValue 0] w
     b <- button [text "Increment", command (incr e)] w
     pack (e ^-^ b)

incr :: Entry Int -> GUI ()
incr e = do x <- getValue e ; setValue e (x+1)
<>

To avoid nested bracketing in large expressions we will often use the $ operator for infix function application. $ is right associative and has the lowest precedence.

<infix application 4> =

($) :: (a -> b) -> a -> b
f $ x = f x
<>
Macro never referenced.

Overview

This document is not a reference manual. Therefore, not every technical feature of TkGofer is covered here. Rather we try to give a smooth introduction to the main ideas of writing GUIs using TkGofer. The rest of this document is organized into the following way:

All the examples and signatures listed in this document correspond to TkGofer version 1.0. If you find any bugs in the library file or in the examples, please let us know. We will try to correct them in future releases of TkGofer.

Concepts

 

This chapter explains the main concepts of functional GUI programming using TkGofer. For a detailed discussion of the implementation of TkGofer we refer to [Sch96, VSS96b, VTS95]. Significant parts of TkGofer rest on advanced concepts like monads and higher order polymorphism. We therefore assume that you already have some knowledge about monadic programming and type and constructor classes. Detailed discussions about these topics can be found in [Jon95, LPJ95, PJW93, Wad90, Wad95].

The GUI Monad

 

The most important datatype of TkGofer is the GUI monad. The monad is implemented as a combination of the state reader monad and the IO monad [JD93]. Values of type GUI a represent actions that have some side effect on the user interface and return a value of type a. The type GUI () represents all void actions; i.e., actions which only have a side effect and do not return a proper value.

For example, the function incr (see the example in Sect. 1.2) has type Entry Int -> GUI (). It reads a value and updates the entry field, but does not return a value. An example of a non-void action is the function button :: [Conf Button] -> Window -> GUI Button. It constructs a button widget and returns an identifier for this button.

Two frequently used monadic functions are seqs and binds. seqs `executes' a list of void actions. binds `executes' a list of non-void actions and returns a list of results. The function void throws away the result of a monadic action, thus performing a cast from m a to m ().

<monadic primitives 5> =

seqs  :: Monad m => [m ()] -> m ()
binds :: Monad m => [m a] -> m [a]
void  :: Monad m => m a -> m ()
<>
Macro never referenced.

Monads can be used to implement lazy state threads, too [LPJ94, LPJ95]. We use them to store global data. A mutable variable is manipulated with the functions:

<mutable variables 6> =

newState   :: a -> GUI (Var a)               -- create a variable
readState  :: Var a -> GUI a                 -- read a variable
writeState :: Var a -> a -> GUI ()           -- write a variable
modState   :: Var a -> (a -> a) -> GUI ()    -- read/apply/write 
<>
Macro never referenced.

Applications of mutable variables are given in Sect. 3.4.3 and 3.8.

Starting the Eventloop

In Gofer an interactive (monadic) program must have type IO(). All GUI applications begin with the function start. This function initializes the communication with Tcl/Tk and sets up the eventloop. The eventloop can be interrupted using Ctrl-C or the function quit.

<start and quit 7> =

start :: GUI () -> IO ()
quit  :: GUI ()
<>
Macro never referenced.

The argument of start denotes the first action to perform. Typically, this first action will create the user interface.

Creating Widgets

The basic building blocks of a graphical user interface are widgets. A widget is a graphical entity with a particular appearance and behaviour. We distinguish between four kinds of widgets: toplevel-, window-, menu- and canvas-widgets. Widgets of kind toplevel serve as containers for other widgets. Examples are windows and menus. Window-widgets, like buttons and labels, may appear on a window. Menu-widgets may occur in a pull-down or pop-up menu. Examples are buttons and separators. Canvas-widgets like rectangles and circles, may be placed on a canvas.

The different widget kinds are identified by the data constructors TItem, WItem, MItem and CItem. Normally, these constructors are hidden using type synonyms. For example, the type Button is defined by:

<Button type 8> =

data Button0 = Button0
type Button  = WItem Button0
<>
Macro never referenced.

The constructor WItem defines the button as a window item. All other widgets are defined in the same way. For each widget, the library offers a construction function, e.g.:

<widget signatures 9> =

window    :: [Conf Window] -> GUI Window 
button    :: [Conf Button] -> Window -> GUI Button
entry     :: [Conf (Entry a)] -> Window -> GUI (Entry a)
cascade   :: [Conf Cascade] -> Menu -> GUI Cascade
cline     :: (Int,Int) -> (Int,Int) -> [Conf CLine] -> Canvas -> GUI CLine
<>
Macro never referenced.

Defining the external outline of individual widgets is done by giving appropriate values for the configuration options. Examples are the color of a widget, a displayed text or the dimensions of a widget. The possible configuration options are widget specific (see also Sect. 3.3.3). To constraint options to a specific class of widgets we introduce a hierarchy of type and constructor classes [Jon95, VSS96a]. We explain the widget hierarchy in Sect. 2.4.

The exact behaviour of toplevel widgets, window widgets, menu widgets and canvas widgets, is explained in the examples in Chap. 3.

The TkGofer Widget Hierarchy

 

Although a lot of differences between widgets exist, most properties that widgets may have are shared by some widgets or even by all widgets, e.g., the way in which they have to be accessed or the way in which we have to specify their outline. Type and constructor classes are used to express the common characteristics of a set of widgets.

Figure  2.1 shows the TkGofer class hierarchy. The functions defined in the class TkWidget deal with implementation aspects, and are not further discussed here.

   figure119
Figure 2.1: The TkGofer widget hierarchy

On top of the class TkWidget we find the class Widget. In this class we define functions that apply to every widget, e.g., the function cset to update the configuration of a widget.

<Class Widget 10> =

class TkWidget w => Widget w where
    cset       :: w -> Conf w -> GUI ()
    ...
<>
Macro never referenced.

All other classes in the hierarchy are specializations of Widget. HasCommand, for example, includes all widgets that additionally may be configured with a command. A typical instance is the datatype Button.

<Class HasCommand 11> =

class HasText w => HasCommand w where
  command :: GUI () -> Conf w
  invoke  :: w -> GUI ()
  ...

instance HasCommand Button
<>
Macro never referenced.

An example of a constructor class is the class HasInput. In this class we group widgets that can handle user input. Instances are for example entry fields and texts (single and multiple line input). Widgets in this class are parameterized over the type of their input. This type should be an instance of the class GUIValue in which parse and unparse functions are defined to display values on the screen (see also Sect. 3.4.2). The class HasInput is listed below:

<Class HasInput 12> =

class (Widget (c (w v)), GUIValue v) => HasInput c w v where
  getValue :: c (w v) -> GUI v
  setValue :: c (w v) -> v -> GUI ()
  updValue :: (v -> v) -> c (w v) -> GUI ()
  updValue f x = do i <- getValue x; setValue x (f i)
  ...
<>
Macro never referenced.

The class has three parameters: the constructor c ranges over the possible widget kinds (TItem, WItem, MItem, or CItem); w ranges over the constructors for widgets having an input value of type v.

In Chap. 4 we will explain how to extend the widget hierarchy and how we can define new widgets. A complete overview of all the classes and their member functions is given in Chap. 5.

Combining Widgets

 

Window items can be composed vertically and horizontally using layout combinators and functions. Our basic combinators are

<widget combinators 13> =

(<<), (^^) :: (Widget (WItem a),Widget (WItem b)) => WItem a -> WItem b -> Frame
<>
Macro never referenced.

where Frame abbreviates WItem Frame0. These combinators are associative and have the following meaning:

The resulting new widget is called the father of v and w.

With every widget we can associate an inherited and an occupied area. The inherited area is the area a widget gets from its father. The occupied area is actually used for displaying information, and is always a centered subarea of the inherited one.

Initially, the occupied and inherited area equal the minimal dimensions needed by the widget to display its information. After combination with some other widget, the occupied area of the father is minimal again. His concatenated sons are placed in the left uppermost corner of his occupied area. If widget v is bigger than widget w, the inherited area of v will equal its occupied area, and the inherited area of w will equal the rest of the occupied area of the father.

The fill functions make a widget occupy its inherited area either horizontally (fillX) or vertically (fillY). The expand function makes a widget claim from its father all occupied area that is not inherited by one of his (other) sons. flexible is just an abbreviation for fillXY . expand. Summarizing, we have the following idempotent widget transformers:

<layout functions 14> =
 
fillX, fillY, fillXY, expand, flexible :: Widget (WItem a) => WItem a -> WItem a
<>
Macro never referenced.

In Fig. 2.2 we see three possible layout situations after application of (variants of) the combinators and fill functions. In the first picture, A and B are composed horizontally. Together they are combined vertically with C. In the second one, we let A << B and C occupy their inherited area in a horizontal direction. As a result of this, the father of A and B grows over the full length of C. In the third one, we let A and B grow vertically.

   figure177
Figure 2.2: Layout combinators and fill functions

In Fig. 2.3 we show the result of expanding widgets. In the first picture, we let A claim and take the area of its father. Likewise, in the second one, this area is claimed and taken by B. Finally, in the last picture, the area is claimed, taken and divided by both A and B.

   figure189
Figure 2.3: Layout combinators and expand functions

In these examples, ex abbreviates expand. The additional associative combinators combine the principles of sizing and positioning:

<derived combinators 15> =

(<<), (<*<), (<-<), (<|<), (<+<), (<*-<), (<*|<), (<*+<),
(^^), (^*^), (^-^), (^|^), (^+^), (^*-^), (^*|^), (^*+^)
   (Widget (WItem a), Widget (WItem b)) => WItem a -> WItem b -> Frame
<>
Macro never referenced.

These combinators apply the same layout function on both arguments. For example ^ -^ places two widgets above each other and aligns them in length, <|< place them next to each other, aligned in height. + is just the combination of | and -. Finally, * applies an expand operation on the right and left operand.

What About Tk?

Since Gofer essentially serves as a generator for Tcl/Tk statements, it might be interesting to take a look at the generated code. For this purpose, we added the command line toggle x. Simply type the interpreter command :set +x and rerun the adder example from Chap. 1. Your output will look like the following:

<TkGofer Trace 16> =

[Initialize Tk (4.1 or higher)]
  window .@0
  wm title .@0 "Counter"
  entry .@0.@1
  .@0.@1 configure -textvariable ".@0.@1"
  set svar0 0
  global .@0.@1 ;set .@0.@1 $svar0
  button .@0.@2
  .@0.@2 configure -text "Increment"
  .@0.@2 configure -command {doEvent 0}
  frame  .@0.@1f
  pack .@0.@1f -in .@0
  raise .@0.@1f
  pack .@0.@1 -in .@0.@1f -si top -fi x
  raise .@0.@1
  pack .@0.@2 -in .@0.@1f -si top -fi x
  raise .@0.@2
[Tk is waiting for an event...]
<>
Macro never referenced.

This listing is the exact Tcl/Tk code Gofer sends to the Tcl interpreter. Especially if you are writing extensions to the library, or if an unexpected Tk error occurs, you can debug the generated code in this way. You can reset the toggle by :set -x.

Introducing Widgets

 

This chapter demonstrates the main techniques of writing GUIs in TkGofer. Similar to John Ousterhout's tour of the Tk Widgets ([Ous94], Chap. 16) we will briefly present the implemented widgets and describe how to create, configure and display them. All the widgets are presented on the basis of some clarifying examples.

All the examples included in this document are executable in TkGofer. They are automatically extracted if you run NuWeb. NuWeb [BR89] is a very simple literate programming environment that works with any programming language and tex2html_wrap_inline1435 . Using NuWeb it is possible to write documentation for multiple program source files in a single document. It runs very quickly and has some nice features for generating HTML-pages and index-tables.

To test the examples, you can read the generated files in the Gofer interpreter or simply load the project file demo.p. This project automatically includes the files:

"examples/demo.p" 17 =

label.gs
button.gs
message.gs 
checkbutton.gs
setget.gs
radio.gs
scale.gs
entry.gs
entry_short.gs
calc.gs
listbox.gs
scrollbar.gs
octdec.gs
edit.gs
canvas.gs
histo.gs
<>

All demos start with the name ex_filename (filename without the .gs extension). We proceed from the trivial `Hello world' to more sophisticated programs like a desk calculator and a text editor.

Windows and Labels

Let's start with the famous `hello world' example (see Fig. 3.1).

   figure218
Figure 3.1: Hello World!

"examples/label.gs" 18 =
 
<hello world part 1 19>
<hello world part 2 21>
<>

`Hello world' actually displays two widgets - a window and a label. In TkGofer, the implementation of this GUI looks like:

<hello world part 1 19> =

ex_label :: IO ()
ex_label = start $
  do w <- window [title "My Example"]
     l <- label [text "hello world", background "yellow", width 25] w
     pack l
<>
Macro referenced in scrap 18.

A user interface may contain one or more windows. The window widget serves as a container for other widgets. The library offers functions to open, close and configure windows. The function window creates and opens (displays) a new window. It takes a list of configuration options as argument. In the above example, we configured the title of the window with the string ``My Example''. The actual position of the window is determined by the window manager of Tcl/Tk, or by the configuration options of the window.

The second widget is the label widget. A label is a widget that displays a string or a bitmap. The configuration options define the exact value of this string or bitmap. Other valid configuration options are for example the widget's background color or its dimensions. The last argument of the function label refers to the window in which the label has to be displayed.

Finally, the function pack displays the label. In general, pack is used to combine and display widgets that have to appear in the same window.

Since a combination of window and pack will occur very frequently in your programs, the prelude offers the function openWindow as an abbreviation for this. It takes a list of configuration options as argument. and a function which creates window widgets of type WItem a. closeWindow removes a window from your display.

<openWindow and closeWindow signature 20> =

openWindow  :: [Conf Window] -> (Window -> GUI (WItem a)) -> GUI ()
closeWindow :: Window -> GUI ()
<>
Macro never referenced.

Using this function, the example can be rewritten in a shorter way:

<hello world part 2 21> =

hello = (start . openWindow [title "My Example"])
  (label [text "hello world", background "yellow", width 25])
<>
Macro referenced in scrap 18.

Messages

Message widgets are similar to labels except that they display multiline strings. A message automatically breaks a long string up into lines. The configuration function aspect controls the width/height ratio for the displayed text. Furthermore, with the function justify, we can center a text, or position it to the right or to the left.

"examples/message.gs" 22 =

ex_message :: IO ()
ex_message = start $ 
  do w   <- window [title "What's the message?"]
     ms  <- binds
              [ message [ text msg, aspect (75*i), justify pos] w
              | pos  <- ["left","center", "right"], i <- [1..3] 
              ]
     pack (matrix 3 ms)
  where msg = "the message widget displays and formats a text"
<>

This example also demonstrates the function matrix. This layout function takes a number of columns and a list of widgets as its arguments and composes them in a row major fashion. Since the second argument is a list of widgets, all widgets must be of the same kind.

   figure238
Figure 3.2: Several variations of aspect and justify

Build on matrix are the layout functions horizontal and vertical:

<horver 23> =

horizontal xs = matrix (length xs) xs
vertical   xs = matrix 1 xs
<>
Macro never referenced.

Buttons

In this section standard commandbuttons are introduced. Another variation are radiobuttons and checkbuttons. They have the same characteristics as commandbuttons, but additionally have some `dynamic' feature.

Commandbuttons

 

Figure 3.3 shows an extension to the `hello world' example. We added a button widget. A button is very similar to a label, except that it responds to the mouse. If the user moves the mouse pointer over the button, the button lights up to indicate that something will happen if the left mouse button is pressed -- if a command option is specified, the argument of the function command is executed.

   figure254
Figure 3.3: Adding a button

This argument has to be a void action, i.e., a function of type GUI (). In the extended `hello world' application, the program quits if the user presses the button.

"examples/button.gs" 24 =

ex_button :: IO ()
ex_button = start $
  do w <- window [title "My Example2"]
     l <- label  [text "hello world", background "yellow"] w
     b <- button [text "press me", command quit] w
     pack (l << b)
<>

Checkbuttons

Checkbuttons have a binary state (true or false) which is set or unset, each time the user presses the button. Using the function setValue the user may give this widget a specific value, or, by using getValue, read the actual value of the button state. The following example illustrates the use of checkbuttons:

"examples/checkbutton.gs" 25 =

ex_checkbutton :: IO ()
ex_checkbutton = start $
  do w   <- window [title "Check this out!"]
     l1  <- label [text "The moon is made of cheese"] w
     cb1 <- checkbutton [ text "Press me", indicatorOn False
                        , indicatorColor "green", background "red"
                        ] w
     cb2 <- checkbutton [text "Wrong", width 8] w
     cset cb2 (command (pressed cb2))
     pack (cb1 ^-^ (l1 << cb2))

pressed :: Checkbutton -> GUI ()
pressed c =
  do b <- getValue c
     cset c (text (if b then "Right" else "Wrong"))
<>

   figure265
Figure 3.4: Checkbuttons

The first checkbutton (cb1) defines a red checkbutton, whose color changes to green when we press it (indicatorColor "green"). The function indicatorOn specifies that either a small indicator or the relief of the button (sunken or raised) informs us about the state of the button (indicatorOn True is default).

The second checkbutton (cb2) responds on a mouse event by calling the function pressed. pressed reads the state of the checkbutton and changes the text of the button correspondingly.

Configuring Widgets

 

Changing and reading the configuration options of already generated widgets is done using the functions cset and cget, respectively. Both functions take a widget and a configuration function as argument. cset and cget have the following signature:

<cset and cget signature 26> =

cset :: Widget a => a -> Conf a -> GUI ()
cget :: (Widget a, GUIValue b) => a -> (b -> Conf a) -> GUI b
<>
Macro never referenced.

Configuration options are parameterized over the set of types they may apply on. For example the function justify, which is only allowed for message-widgets, has type String -> Conf Message, whereas the function background has the restricted polymorphic type HasBackground a => String -> Conf a. This means that background s is a valid option for all widgets that are an instance of the class HasBackground. An example of cset and cget is the following `reverse-button' function:

"examples/setget.gs" 27 =

ex_setget :: IO ()
ex_setget =  start $
  do w <- window []
     b <- button [text "hello"] w
     cset b (command (rev b))
     pack b
  where
    rev b = do x <- cget b text; cset b (text (reverse x))
<>

After pressing the button, the displayed text is reversed.

Sequentialization of Actions

A drawback of monadic programming is that it enforces a strong sequentialization of actions. In the previous program for example, we first generate a button, and then we apply the function cset. The obvious reason for this is that we cannot use the variable b before it is generated. There are however some tricks to solve the problem in some special situations.

Consider the function self, defined by:

<self 28> =

self :: (a -> a -> b) -> a -> b
self f a = (f a) a
<>
Macro never referenced.

Since configuration options essentially are functions from widgets to options, we can apply self to abbreviate the sequence

<self sequence 29> =

x <- widget [] w; cset x (c x)  <>
Macro never referenced.

by

<self definition 30> =
 
x <- widget [self (\x -> c x) ] w  
<>
Macro never referenced.

self takes the generated widget as an argument for the configuration option. Applied to the cset_cget example we get:

<self example 31> =

b <- button [ text "hello", self (command . rev)]
<>
Macro never referenced.

Radiobuttons

Radiobutton widgets are used to select one of several mutually exclusive options. The buttons are controlled by one (abstract) widget -- the radio. For radiobuttons and radios, the same operations are defined as for checkbuttons.

The next example (see Fig. 3.5) shows the use of radios and radiobuttons. The program simulates a very primitive trafficlight protocol.

"examples/radio.gs" 32 =

ex_radio :: IO ()
ex_radio = start $
  do w <- window []
     (ls1, r1) <- traffic w
     (ls2, r2) <- traffic w
     seqs (control ls1 r1 r2 ++ control ls2 r2 r1)
     pack (vertical ls1 << vertical ls2)
  where
    traffic w =
      do bs <- binds [radiobutton [indicatorColor c] w
                     | c <- ["red", "yellow", "green"]
                     ]
         r  <- radio [initValue 1] bs
         result (bs, r)

    control ls i j =
      [cset b (command $ do x <- getValue i; setValue j (2-x)) | b <- ls]
<>

   figure301
Figure 3.5: A small trafficlight controller

The function radio takes a list of radiobuttons as parameter and returns a controller for the group of buttons. The functions setValue and getValue are used to address the buttons of a radio. setValue takes an integer value, corresponding to the position of the radiobutton to set. Likewise, getValue returns the position of the actual selected button.

The function traffic creates one trafficlight. It returns a list of three buttons and the radio to control them. Initially, both trafficlights are yellow (initValue 1). The function control assigns a command to every button, which guarantees the exclusiveness of the two trafficlights.

Entries

 

An entry widget allows the user to type in and edit a one-line string. This string may represent any displayable type in a TkGofer program. Entries have some dynamic contents. Since entries are an instance of same class (viz. HasInput) as radio- and checkbuttons we may access them again using getValue and setValue.

The next example (see Fig. 3.6) implements a simple adder. When the user presses the enter button, the value of the entry field is increased.

"examples/entry.gs" 33 =

ex_entry :: IO ()
ex_entry = start $
  do w <- window []
     e <- entry [initValue 0] w
     cset e (on return $ do x <- getValue e; setValue e (x+1))
     pack e
<>

   figure322
Figure 3.6: A simple adder

This example demonstrates the use of user defined events (on .. do ..). They correspond to `bindings' in Tcl/Tk. The first argument is some key or mouse event, the second argument defines the function that is called if the event occurs.

The function initValue initializes the contents of the entry. By the way, if you do not like writing your applications using the do-notation, you might like to write the previous example in `dutch' style:

"examples/entry_short.gs" 34 =

ex_entry_short :: IO ()
ex_entry_short = 
  (start . openWindow []) 
    (entry [self $ on return . updValue (1+), initValue 0])
<>

updValue is an abbreviation for reading a value, applying a function to it, and writing the resulting value.

Typed Contents

An important feature of entry widgets (and also of other widgets with input) is the fact that they are typed over their contents. Entries, displaying integers, have type Entry Int. Entries displaying booleans have type Entry Bool, etc. If we want to assign a string to an integer input field, we get the following error message:

<type errors 35> =

setValue e "hello"

*** expression     : setValue e "hello"
*** term           : e
*** type           : WItem (Entry0 Int)
*** does not match : a (b [Char])
<>
Macro never referenced.

WItem (Entry0 Int) is the derived type for e. WItem (Entry0 a) may be abbreviated by the type synonym Entry a.

Remember that gofer needs enough information to derive the exact type of a widget. The following program cannot be typed correctly:

"examples/entry_error.gs" 36 =

type_error = start $
  do w <- window []
     e <- entry [] w
     pack e
<>

<overloading error 37> =

ERROR "entry_error.gs" (line 2): Unresolved top-level overloading
*** Binding             : type_error
*** Inferred type       : IO ()
*** Outstanding context : (Widget (Entry _24), GUIValue _24)
<>
Macro never referenced.

To solve this problem we can explicitly type the widget, for example by replacing the last line by:

<overload solution 38> =

pack (e :: Entry Int)
<>
Macro never referenced.

or we can give some hints by providing an initial value for the entry field. In most applications however, a widget occurs within a special context -- this context determines the type of the widget.

Reading and Writing Arbitrary Values

 

The previous section showed that some widgets are parameterized over their contents of some type a. Values of type a are printed on your display and can be read (user-input).

Since Tcl/Tk only deals with strings, we have to convert every displayed type in our application to string. Likewise, we have to parse strings if we want to use the input. The class GUIValue defines parse and unparse functions for any value that may be displayed at the GUI. If parsing fails we open a standard error dialog.

The following example shows a decimal-octal converter after entering an invalid input value (see Fig. 3.7).

   figure346
Figure 3.7: A decimal octal converter

We define Octal as an instance of the class GUIValue. We have to write an instance for the functions tk_defaultValue and tk_convert. tk_defaultValue denotes the value we have to return in case an input error occurs. tk_convert specifies the parse routine for the type Oct. Unparsing is defined by the function show as a default. Therefore, we have to write an instance of the class Text for the type Oct.

"examples/octdec.gs" 39 =

<define Oct as GUIValue 40>
<application of Oct 41>
<>

<define Oct as GUIValue 40> =

data Oct = Oct Int

instance GUIValue Oct where
  tk_defaultValue = Oct 0
  tk_convert s | all (flip elem "01234567") s = Tk_Ok (Oct (numval s))
  tk_convert s | otherwise = Tk_Err ("Invalid Oct String: " ++ s)
instance Text Oct where
  showsPrec d (Oct x) = shows x
<>
Macro referenced in scrap 39.

The application itself consists of two entry fields. The first entry has type Entry Int, the second one Entry Oct. Each time a value is entered in the decimal entry field the octal one displays the converted value and vice versa.

<application of Oct 41> =

ex_octdec = (start . openWindow [title "Convert"]) conv where
  conv w =
    do (f1,e1) <- input w "dec"
       (f2,e2) <- input w "oct"
       doconv (\n -> Oct (fromTo 10 8 n)) e1 e2
       doconv (\(Oct n) -> fromTo 8 10 n) e2 e1
       result (f1 << f2)

  input w s =
    do l <- label [text s] w
       e <- entry [width 9] w
       result ((l ^-^ e),e)

  doconv f a b =
    cset a (on return (do {x <- getValue a; setValue b (f x)}))

  fromTo n m = foldr (\a b -> b*n + a) 0 . digits m
    where digits j n = map (`mod` j) ((takeWhile (>0) . iterate (`div` j)) n)
<>
Macro referenced in scrap 39.

State and GUI: The Calculator

 

Another demonstration of entries and buttons is the desk-calculator example. This example shows how to deal with a global state. So far, we only met examples that did not use a global state. Actions only had some (local) side-effect on the GUI. In this example, we will need actions that use mutable variables (cf 2.1).

   figure368
Figure 3.8: The calculator

Take a look at Fig. 3.8 and imagine what should happen if the user presses the button `+'. The calculator has to read the actual value of the display and apply `+' to it. The calculator has to keep this accumulator function in its memory, till the user has entered a new number and pressed another operator key.

Since every command has the type GUI (), we cannot return the updated `memory' as the result of a button press. The simplest way to solve this problem is to use mutable variables. We implement the calculator's memory by a mutable variable which contains the displayed value and the value of the accumulator function. The definition of the calculator state and GUI is given below.

"examples/calc.gs" 42 =

type CalcState = (Int, Int -> Int)

ex_calc :: IO ()
ex_calc = start $
  do st <- newState (0, id)
     w <- windowDefault [title "Calculator"] [font "12x24"]
     c <- calc st w
     pack c

calc :: Var CalcState -> Window -> GUI Frame
calc st w =
  let disp   = entry [relief "sunken", width 12, initValue 0] w

      keys e = map (cmd e) [ '1', '2', '3', '+',
                             '4', '5', '6', '-',
                             '7', '8', '9', '*',
                             'C', '0', '=', '/'
                           ]

      cmd e c = button [text [c], command (next e (action c)), width 2] w

      next e f = do (disp, accu)  <- readState st
                    let (disp',accu') = f (disp, accu)
                    setValue e disp'
                    writeState st (disp',accu')

      action 'C' (d,a) = (0, id)
      action '=' (d,a) = (a d, const (a d))
      action  c  (d,a) | isDigit c = (10*d + ord c - ord '0', a)
                       | otherwise = (0, ((char2op c).a) d)

      char2op '+' = (+)
      char2op '-' = (-)
      char2op '*' = (*)
      char2op '/' = \x y -> if y == 0 then 99999999 else x `div` y

  in do e <- disp
        k <- binds (keys e)
        result (e ^-^ matrix 4 k)
<>

The function calc initializes the state and opens the window. The function windowDefault applies the second list of configuration options to every widget in the GUI.

The user interface is built using an entry widget and a matrix of buttons for the keypad. Whenever the user presses a digit, it is displayed and the value component of the state is updated. When an operator is pressed, the display is reset and the accumulator function is modified. After pressing the `=' button, the calculator evaluates the accumulator function.

Scales

A scale widget is a widget that displays an integer value and allows users to edit this value by dragging a slider. We have two functions to generate scales, hscale for horizontal scales and vscale for vertical scales. The range of values for the scale is specified by the function scaleRange. To display tickmarks next to a scale, we use tickInterval.

If the command option is specified, each time the value of the scale changes, the command is executed. The example below (see Fig. 3.9) shows the use of scales.

   figure385
Figure 3.9: Calculating the total time

The two scales represent an indicator for speed and distance. When the user moves the scales, the values are increased or decreased. The corresponding trip duration is recalculated and displayed.

"examples/scale.gs" 43 =

ex_scale :: IO ()
ex_scale = start $
  do w <- window [title "small scale application"]
     s1 <- makeScale "speed (m/s)" w
     s2 <- makeScale "distance (m)" w
     l1 <- label [text "time (s) "] w
     l2 <- label [width 10, relief "ridge"] w
     setCommands s1 s2 l2
     pack ((s1 ^-^ s2) <|< (l1 <*-< l2))
  where
    makeScale lab win =
      hscale [ scaleRange (0, 99)
             , tickInterval 10
             , text lab
             , height 400
             ] win
    setCommands s1 s2 l2 =
      let slide = do v <- getValue s1
                     d <- getValue s2
                     cset l2 ((text . take 4 . time d) v)
          time d 0 = "0.0"
          time d v = show ((fromInteger d / fromInteger v) :: Float)
      in do cset s1 (command slide)
            cset s2 (command slide)
<>

Listboxes

A listbox is a widget that displays a collection of elements and allows the user to select one or more of them. Also listboxes are parameterized over the type of their contents.

The example shows two listboxes (see Fig. 3.10). The left one displays strings, the right one displays integers. In the right listbox we have marked four elements.

"examples/listbox.gs" 44 =

ex_listbox :: IO ()
ex_listbox = start $
  do w   <- window []
     l1  <- label [text "Strings"] w
     l2  <- label [text "Integers"] w
     lb1 <- listbox [initValue (part 3 ['A'..'Z'])] w
     lb2 <- listbox [initValue [1..8], multipleSelect True] w
     pack ((l1 ^-^ lb1) <|< (l2 ^-^ lb2))
  where part n = map (take n) . takeWhile (not . null) . iterate (drop n)
<>

The type checker will derive that lb1 has type Listbox [String] and lb2 has type Listbox [Int].

   figure398
Figure 3.10: Two listboxes, the right one with four marked elements

To switch between the two select modi (single select, to select only one element from the list and multiple select to select a set of elements) we use the function multipleSelect. Selections are made using the mouse. To select two or more non-consecutive elements, we can use the control key to fix the first selections.

Scrolling Widgets

Scrollbars control the view in other widgets. Therefore, a scrollbar is always associated with another widget. Scrollbars can be generated by the functions vscroll and hscroll. Both functions have the same signature. The first argument is a list of configuration options and the second argument refers to the widget to scroll.

   figure409
Figure 3.11: Scrolling and selecting

Figure 3.11 shows a listbox and an entry. Both widgets are associated with a scrollbar. If the user selects a value in the listbox, the value is automatically displayed in the entry field.

"examples/scrollbar.gs" 45 =

ex_scrollbar :: IO ()
ex_scrollbar = start $
  do w <- window [title "select"]
     (e,f1) <- scrollEntry w
     (l,f2) <- scrollListbox w
     cset e (on return (readEntry l e))
     cset l (on (doubleClick 1) (writeEntry l e))
     focus e
     pack (f2 ^-^ f1)
  where
    scrollEntry w =
      do e <- entry [initValue ""] w
         s <- hscroll [] e
         result (e, e ^-^ s)
    scrollListbox w =
      do l <- listbox [] w
         s1 <- hscroll [] l
         s2 <- vscroll [] l
         result (l, (l ^-^ s1) <|< s2)
    readEntry l e = 
      do x <- getValue e
         putEnd l [x]
    writeEntry l e = 
      do [x] <- getSelection l
         [a] <- getFromTo l x x
         setValue e a
<>

The application focus e sets the input focus to the entry widget e. This ensures that all keystroke events will arrive at the entry field.

The function writeEntry shows some other features of listboxes (and editors, as we will see in the next section). To read the positions of the actual selected items, we use getSelection. To read the actual values of the elements on these positions we use the function getFromTo. getFromTo takes two positions as its arguments and returns all the elements within this range.

Editors and Menubars

 

The Edit widget displays one or more lines of texts and allows you to edit the text. Many default key- and mouse-bindings exist to browse a text (e.g. cursor keys). Since Editors and Listboxes both belong to the same class, we may use the same functions to access and modify the contents of the widget.

Two more advanced techniques that deal with texts are provided as well: marks and tags.

   figure425
Figure 3.12: A small editor

We discuss the edit widget on the basis of a small editor example (see 3.12). Simultaneously, we will introduce the menu widget.

"examples/edit.gs" 46 =

<edit-gui 47>
<edit-fileM 49>
<warning-dialogue 50>
<edit-editM 51>
<edit-styleM 53>
<>

The Editor

We want to develop a small editor, with variable fonts and a simple cut-copy-paste buffer. To realize this, we will need a global state again. The editor state is a mutable variable, containing the contents of the buffer and the actual fontsize.

<edit-gui 47> =

type State = Var (String,Int)  -- buffer, fontsize

ex_edit :: IO ()
ex_edit = start $ do 
  st <- newState ("",18)
  w  <- window [title "Write !!"]
  e  <- edit [width 40, height 15, wrap True,
              background "white", font "times-roman18"] w
  s  <- vscroll []  e
  f  <- frame [borderWidth 4] (flexible e <|< s) 
  bs <- menubar  
          [("File", fileM e), ("Edit", editM e st), ("Style", styleM e st)] w
  pack (flexible (horizontal bs ^-^ flexible f))
<>
Macro referenced in scrap 46.

The GUI consists of two main elements, a menubar and the edit-window. An edit-widget specific function is the function wrap; it determines whether a text should be broken into lines of words or lines of characters.

We use the frame widget to group and configure the edit widget and a scrollbar. Normally, widget-combinators generate frames and configure them with a default list of options. However, if we want to configure a frame explicitly, we may use the function frame. The function menubar is defined in the next section, It returns a list of menubuttons. We pack all the menubuttons horizontally using the function horizontal.

Menus and Menuitems

The menu widget can be used to implement pulldown menus, cascading menus and pop-up menus. A menu is a toplevel widget that contains a number of menu-items, arranged in a column. Possible items are buttons, radiobuttons, checkbuttons and cascade-menubuttons. They behave exactly the same as the corresponding window items. Furthermore, the separator widget just displays a horizontal line for decoration.

A pulldown menu is a menubutton with an associated menu. When the user presses the menu button, the menu is posted directly underneath the button.

The general pattern for creating pulldown menus is the following:

<menu 48> =

mb <- menubutton configs window    -- create menubutton
m  <- menu configs mb              -- create associated menu
b1 <- mbutton configs m            -- create menu items
b2 <- mbutton configs m            --
...
pack mb                            -- display menubutton
<>
Macro never referenced.

Notice that menu items are not packed. They are automatically displayed if the menu is posted. They are displayed in the order in which they were created.

A menubar is a vertical bar of menubuttons. The next code-fragment of the editor example shows a possible definition for the function menubar. It takes a list of tuples of strings and menu-items and associates every list of menu-items with a menubutton.

<edit-fileM 49> =

menubar :: [(String, Menu -> [GUI ()])] -> Window -> GUI [Menubutton]
menubar xs w =
 let (ss,fs) = unzip xs 
 in do bs <- binds [menubutton [text s] w | s <- ss]
       ms <- binds [menu [] b | b <- bs]
       (seqs . concat) [f m | (f,m) <- zip fs ms] 
       result bs

cmd :: (String, GUI ()) -> Menu -> GUI ()
cmd (s,c) m = void (mbutton [text s, command c] m)

fileM :: Edit -> Menu -> [GUI ()]
fileM e m =
  [cmd s m | s <- [("New", doNew), ("Quit", doQuit)]]
  where doNew  = warning "Really Clear?" (setValue e "") 
        doQuit = warning "Really Quit?" quit 
<>
Macro referenced in scrap 46.

The function cmd creates a commandbutton menuitem. This button behaves exactly the same as the standard button widget. Since we do not have to refer to this widget any longer, we apply the function void to nullify the resulting widget.

The first pulldown menu of the editor is implemented by fileM. It creates two menu items, i.e., a new and a quit button. Both commands buttons open a warning dialog if they are pressed.

<warning-dialogue 50> =

warning :: String -> GUI () -> GUI ()
warning msg yes = do
   w  <- windowDefault [title "Warning"][font "helvetica18"]
   m  <- message [text msg, relief "ridge"] w
   b1 <- button [text " Yes ", command (closeWindow w `seq` yes)] w
   b2 <- button [text " No " , command (closeWindow w)] w
   f  <- frame [borderWidth 2, relief "ridge"] (b1 << b2)
   focus b2
   pack (m ^*+^ f)
<>
Macro referenced in scrap 46.

The second argument of the warning dialog is the action to perform if the Yes-button is pressed. If the user presses the No-button, the dialog is closed.

Marks

 

Text operations often refer to some particular place in the text. For example an append action refers to the end-position of the actual input, whereas an insert action refers to the actual cursor position. Using marks we can read the actual value of the mouse cursor (mouseMark), the insertion cursor (insMark) and the end of the text (endMark). The function getMark reads the actual value of the mark, setMark updates this value.

In the definition of doPaste we find an application of marks; we want to paste the text at the actual cursor position.

<edit-editM 51> =

editM :: Edit -> State -> Menu -> [GUI ()]
editM e st m =
  cset e (onXY (click 3) (\xy -> popup xy m)) :
  [cmd s m | s <- [("cut", doCut), ("copy", doCopy), ("paste", doPaste)]]
  where doCut = selectionExists e ==> do
          ([p,q],t) <- getMarkedPart e
          delFromTo e p q
          modState st (\(_,i) -> (t,i))
        doCopy = do
          (_,t) <- getMarkedPart e
          modState st (\(_,i) -> (t,i))
        doPaste = do
          (t,_) <- readState st
          p <- getMark e insMark
          putPos e p t

selectionExists :: Edit -> GUI Bool
selectionExists e = do 
  ps <- getSelection e
  result (ps /= [])
<>
Macro referenced in scrap 46.

The edit menu contains three command buttons to cut, copy and paste pieces of text. The corresponding actions read and write the cut copy paste buffer. The first line of the body of the function doEdit defines an extra mouse binding for the edit-window. When the user presses the right mouse button, the cut-copy-paste menu is popped up directly underneath the mouse cursor. The second line actually defines the menu-items.

In the definition of doCut we see an application of the assert operator ==>. It takes a (monadic) conditional action as it first operand and only evaluates its second argument if the condition evaluates to true:

<==> definition 52> =

(==>) :: GUI Bool -> GUI () -> GUI ()
<>
Macro never referenced.

(In fact, this is almost the same as the monadic if operation does [Jon93b], but since we cannot define a zero operation for the GUI monad, we redefined this operation).

The function selectionExists uses the library function getSelection. It returns the range of the actual selection.

Tags

 

Tags are used to change the appearance of a particular piece of text, e.g., change its color or font. A tag represents a piece of text, identified by a list of positions that may be configured just like any other widget. The library offers operations for creating and deleting tags (tag, putEndTag, putPosTag and delTag). Furthermore, since a text fragment can be a part of more than one tag, an operation lowerTag exists, to modify the stacking order of tags.

In the editor-example, we use tags to change the font of a marked text part. The function setf first checks whether a selection exists or not, and if so, it will read the coordinates of the marked part and create a tag for this range with the desired font and fontsize.

<edit-styleM 53> =

styleM :: Edit -> State -> Menu -> [GUI ()]
styleM e st m = fonts ++ [void (separator m), subm]
  where fonts =
          [cmd (s ,setf ("times-"++s)) m | s <- ["roman", "bold", "italic"]]
        subm = do
          mb <- cascade [text "font size"] m
          m  <- menu [] mb
          bs <- binds [mradiobutton
                  [text (show n), command (modState st (\(t,_) -> (t,n)))] m
                      | n <- [8,10..24]]
          void (radio [] bs)
        setf s = selectionExists e ==> do
          (ps,_) <- getMarkedPart e
          (_,n)  <- readState st
          void (tag ps [font (s++show n)] e)
  
getMarkedPart :: Edit -> GUI ([(Int,Int)],String)
getMarkedPart e = do 
  ps <- getSelection e
  case ps of [a,b]     -> do tx <- getFromTo e a b
                             result (ps,tx)
             otherwise -> result ([],"")
<>
Macro referenced in scrap 46.

Canvas and Canvas Items

A canvas is a widget that displays a drawing surface on which you can place various items. TkGofer currently supports rectangles, ovals, lines, texts and bitmaps. To display canvas items, we first have to create a canvas. Every canvas item takes the canvas it should appear on as parameter.

If a canvas item is created, it is automatically displayed at the position specified in the first parameters. Items can be manipulated by changing the configuration options and coordinates.

   figure470
Figure 3.13: Drawing lines, rectangles and circles

The example (see Fig. 3.13) shows how to create and modify canvas items.

"examples/canvas.gs" 54 =

ex_canvas :: IO ()
ex_canvas = start $ do
  do w <- window [title "Move It!"]
     c <- canvas [background "white", width 200, height 200] w
     r <- crect (10,10) (50,50) opts c
     l <- cline (70,70) (120,120) opts c
     o <- coval (150,150) (200,200) opts c
     t <- ctext (10,130) [ text "hello world", moveIt, raiseIt] c
     pack c

opts :: HasFillColor a => [Conf a]
opts = [penWidth 3, penColor "red", fillColor "yellow", moveIt, raiseIt]

moveIt :: HasCoords a => Conf a
moveIt = self (onxy (motion 1) . moveIt') where
  moveIt' w (x,y) =
     do ((x',y'):ys) <- getCoords w
        moveObject w (x - x', y -y')

raiseIt :: HasCoords a => Conf a
raiseIt = self (on (click 1) . raiseObject)
<>

The function moveIt is called if we want to drag an item to another position. The function raiseIt puts an item on top of another item if two items overlap.

Drawing a Histogram

We conclude the description of the standard TkGofer widget set with a small example to illustrate the expressive power of functional GUI programming. It combines a small GUI and a function to calculate a histogram for some given list of integers.

"examples/histo.gs" 55 =

ex_histo :: IO ()
ex_histo = start $ do
  w <- window [title "Histogram"]
  c <- canvas [width (xmax +10), height (ymax + 10)] w
  e <- entry  [self (on return . draw c)] w
  pack (c ^-^ e)

draw :: Canvas -> Entry String -> GUI ()
draw c e = do
  clearCanvas c
  v <- getValue e
  seqs [ (void . crect (x1,y1) (x2,y2) [fillColor "cyan"]) c
       | (x1,y1,x2,y2) <- (bars . map numval . words) v
       ] 

bars :: [Int] -> [(Int,Int,Int,Int)]
bars bs =
  let yunit  = fromInteger ymax / fromInteger (maximum bs)
      xunit  = xmax / length bs
      hght i = ymax + 5 - truncate (fromInteger i * yunit )
  in [(x+5, hght y, x+xunit+3, ymax) | (x,y) <- zip [0,xunit..] bs]

xmax = 150
ymax = 100
<>

   figure481
Figure 3.14: The histogram application

If the user presses the enter key, the numbers displayed in the entry field are converted to integers and displayed as rectangles.

Defining New Widgets

 

Very often, a user interface is composed of building blocks, which are reused a number of times. In this chapter we describe the steps you should take, to write new widgets as a combination of other, more primitive widgets. The examples in this chapter are automatically loaded after loading the project newwidgets.p. The project includes:

"examples/newwidgets.p" 56 =

prompt.gs
indic.gs
<>

The Prompt Widget

The combination of an entry field and a label is typical for many input dialogues (see Fig. 4.1). Therefore, we would like to extend the library with the composed widget Prompt.

   figure494
Figure 4.1: Three input masks

"examples/prompt.gs" 57 =

<Prompt Datatype 58>
<Prompt Construction 59>
<Widget Prompt ?>
<Prompt Configs 61>
<HasText Prompt and HasInput Prompt 62>
<Prompt Application 63>
<>

Creating the Prompt Widget

First, we define a new datatype for inputfields. The components of the prompt widget are represented by a tuple of a label and a widget. The prompt widget is parameterized over the type of its input since its entry component is parameterized. The type Prompt is a synonym for the window item WItem Prompt0. Additionally we define two selection functions for the new widget.

<Prompt Datatype 58> =
 
data Prompt0 v = Prompt (Entry v, Label)
type Prompt  v = WItem (Prompt0 v)

promptE :: Prompt v -> Entry v
promptE p = let Prompt (e,l) = getWidget p in e

promptL :: Prompt v -> Label
promptL p = let Prompt (e,l) = getWidget p in l
<>
Macro referenced in scrap 57.

The exact representation of window items is irrelevant for us. We use the function getWidget to select the widget part of a window item.

Next, we define the construction function for Prompt. This function defines the exact layout of the widget.

<Prompt Construction 59> =

prompt :: (GUIValue a, Widget (Prompt a)) 
       => [Conf (Prompt a)] -> Window -> GUI (Prompt a) 
prompt cs w =
  do l <- label [] w
     e <- entry [] w
     composeWidget (Prompt (e,l)) (l << e) cs
<>
Macro referenced in scrap 57.

The function composeWidget actually creates and configures the widget. It has the following signature:

<compose signature 60> =

composeWidget :: (Widget (WItem w), Widget (WItem v), Widget (WItem w))
                 => w -> WItem v -> [Conf (WItem w)] -> GUI (WItem w)
<>
Macro never referenced.

Now, we have to define Prompt as an instance of the desired classes in the user hierarchy (cf. Sect. 2.4).

Configuring New Widgets

How does configuring work for prompt widgets? We have to make sure that the configuration options are correctly distributed over the components of the composed widget. This is done by giving a suitable redefinition of the function cset:

<Prompt Configs 61> =

instance Widget (Entry v) => Widget (Prompt v) where
  cset w c =
    case (c w) of
      (Tk_Text s)      -> cset (promptL w) (const (Tk_Text s))
      (Tk_InitValue x) -> cset (promptE w) (const (Tk_InitValue x))
      otherwise        -> do cset (promptE w) (const (c w))
                             cset (promptL w) (const (c w))
  onArgs e s a = onArgs e s a . promptE
<>
Macro referenced in scrap 57.

We apply the configuration function c to the widget w, so we get the actual constructor for the configuration option. Using pattern matching, we may now decide whether to apply an option on the label part, the entry part, or on both parts.

In our application, the configuration option Text now acts on the label part, InitValue on the entry part and all other options on both parts of the prompt widget. Of course, we have to redefine cget in a similar way, if we want to read configuration options as well.

Finally, we define Prompt as an instance of HasText, HasForeground, HasBackground and HasInput:

<HasText Prompt and HasInput Prompt 62> =

instance HasText (Prompt v)
instance HasForeground (Prompt v)
instance HasBackground (Prompt v)
instance HasInput WItem Entry0 v => HasInput WItem Prompt0 v where
  getValue = getValue . promptE
  setValue = setValue . promptE
<>
Macro referenced in scrap 57.

All the functionality offered for labels and entries, is automatically available for prompt widgets as well. An application of the prompt widget is the simple adder.

<Prompt Application 63> =
 
ex_prompt :: IO ()
ex_prompt = start $
  do w <- window [ title "Simple Adder 2" ]
     i <- prompt [ text "Press the return key"
                 , initValue 0, self $ on return . updValue (+1) ] w
     pack i
<>
Macro referenced in scrap 57.

   figure521
Figure 4.2: The prompt widget

In the tk.prelude an implementation of the prompt widget exists under the name input.

The Indicator Widget

This example shows a more complicated example of a composed widget. We want to develop a indicator widget, i.e., a widget, displaying a bar that informs us about some percentage (see Fig. 4.3).

   figure532
Figure 4.3: The indicator widget

"examples/indic.gs" 64 =

<indic widget 65>
<percent widget ?>
<>

The indicator widget has four components, two rectangle widgets that inform us about the status of the indicator, a canvas widget to contain the rectangles and a label to display the percentage. Furthermore, we use a mutable variable to represent the state of the widget. The state is determined by the position of the indicator. This position depends on the actual size of the widget. Therefore, also the actual size is a component of the widget state.

<indic widget 65> =

<indic type defs 66>
<indic construct 67>
<indic instance 68>
<indic configs 69>
<indic HasInput 70>
<indic application 71>
<>
Macro referenced in scrap 64.

The corresponding datatypes and selection functions are:

<indic type defs 66> =
        
type IndState = Var (Int,Int,Int)               -- value, width, height
type IndGUI   = (Canvas, Label, CRect, CRect)   -- canvas, border, indicator

data Indicator0 a = Indicator IndGUI IndState a
type Indicator    = WItem (Indicator0 Int)

canI :: Indicator -> Canvas
canI i = let (Indicator (c,_,_,_) _ _) = getWidget i in c

labI :: Indicator -> Label
labI i = let (Indicator (_,l,_,_) _ _) = getWidget i in l

borI :: Indicator -> CRect
borI i = let (Indicator (_,_,b,_) _ _) = getWidget i in b

recI :: Indicator -> CRect
recI i = let (Indicator (_,_,_,r) _ _) = getWidget i in r

stateI :: Indicator -> IndState
stateI i = let (Indicator _ st _) = getWidget i in st
<>
Macro referenced in scrap 65.

The type Indicator0 is parameterized over the type of it contents so we can define it as an instance of the class HasInput. Since indicator values are always integers, we instantiate this type variable with Int.

The construction function indicator first generates a canvas, a label and two rectangles. The actual size of the rectangles is specified by the width and height configuration options.

<indic construct 67> =

indicator :: [Conf Indicator] -> Window -> GUI (Indicator)
indicator cs w =
  let defaults  = [height 20, width 100, foreground "red"]
  in
  do c <- canvas [] w
     l <- label [width 4, text "0%"] w
     i <- crect (0,0) (0,0) [] c
     j <- crect (0,0) (0,0) [] c
     st <- newState (0,20,100)
     composeWidget (Indicator (c,l,i,j) st 0) (c<|<l) (defaults ++ cs)
<>
Macro referenced in scrap 65.

The next step is to define Indicator as an instance of the class Widget. The function cset distributes the configuration options over the several components of the widget. If we want to modify the width or height of the widget, we have to update the widget state.

<indic instance 68> =
 
instance Widget Indicator where
  cset w c =
    case (c w) of
        Tk_Height h     -> newheight w h
        Tk_Width h      -> newwidth w h
        Tk_Foreground r -> cset (recI w) (fillColor r)
        Tk_Background r -> do cset (labI w) (background r)
                              cset (canI w) (background r)
                              cset (canI w) (highlightBackground r)
        Tk_Font f       -> cset (labI w) (font f)
        otherwise       -> cset (canI w) (const (c w))
    where
      newheight ind v =
        do (i,x,y) <- readState (stateI ind)
           writeState (stateI ind) (i,x,v)
           let ratio = (fromInteger x / 100.0) * fromInteger i
           setCoords (borI ind) [(3,3),(x+7,v+3)]
           setCoords (recI ind) [(5,5),(5+truncate ratio,5+(v-4))]
           cset (canI ind) (height (v+4))
       
      newwidth ind v =
        do (i,x,y) <- readState  (stateI ind)
           writeState (stateI ind) (i,v,y)
           let ratio = (fromInteger v / 100.0) * fromInteger i
           setCoords (borI ind) [(3,3), (v+7,y+3)]
           setCoords (recI ind) [(5,5), (5+truncate ratio,5+(y-4))]
           cset (canI ind) (width (v+8))
<>
Macro referenced in scrap 65.

The function background changes the background of both widgets. By changing the highlightBackground as well, the widgets really look like `one' widget.

In Widget we defined how to handle configuration options. We still have to define Indicator as an instance of the classes that define the desired options:

<indic configs 69> =

instance HasBackground Indicator
instance HasForeground Indicator 
instance HasBorder     Indicator
instance HasWidth      Indicator
instance HasHeight     Indicator
<>
Macro referenced in scrap 65.

Finally, we define Indicator as an instance of the class HasInput. getValue reads the value directly from the widget state. setValue writes the new value to the widget state and changes the layout of the indicator-rectangle.

<indic HasInput 70> =

instance HasInput WItem Indicator0 Int where
  getValue w = do (i,_,_) <- readState (stateI w)
                  result i
 
  setValue w i =
    do (v,x,y) <- readState (stateI w)
       writeState (stateI w) (i,x,y)
       let newx = truncate ((fromInteger x / 100.0) * fromInteger i)
       setCoords (recI w) [(5,5), (5+newx,5+(y-4))]
       cset (labI w) (text (show i ++ "%"))
<>
Macro referenced in scrap 65.

The following example shows an application of the indicator widget. A scaler widget is used to control the indicator. If we move the scaler, the indicator changes correspondingly.

<indic application 71> =

ex_indic :: IO ()
ex_indic = start $
  do w <- window []
     i <- indicator [height 10, width 200, background "white"] w
     e <- hscale [scaleRange (0,100), height 200] w
     let cmd = do x <- getValue e; setValue i x
     cset e (command cmd)
     pack (i ^^ e)
<>
Macro referenced in scrap 65.

Signatures of the tk.prelude

 

This chapter lists the signatures of the user functions of the tk.prelude. For the exact implementation of these functions we refer to [VSS96b].

Start and Quit

<Start and Quit 72> =

start :: GUI () -> IO ()
quit  :: GUI ()
<>
Macro never referenced.

User Classes and Instances

<class Widget 73> =

class TkWidget w => Widget w where
  cset   :: w -> Conf w -> GUI ()
  cget   :: GUIValue v => w -> (v -> Conf w) -> GUI v
  csets  ::  w -> [Conf w] -> GUI ()
  on     :: TkEvent -> GUI () -> Conf w                 
  onXY   :: TkEvent -> ((Int,Int) -> GUI ()) -> Conf w  -- relative to screen
  onxy   :: TkEvent -> ((Int,Int) -> GUI ()) -> Conf w  -- relative to widget
  onArgs :: TkEvent -> String -> ([String] -> GUI ()) -> Conf w  
            -- see tk-substitution patterns for valid strings

instance Widget (TItem a)
instance Widget (WItem a)
instance Widget (MItem a)
instance Widget (CItem a)
instance Widget Radio 
instance Widget Tag
instance Widget (Entry a) => Widget (WItem (Input0 a))
<>
Macro never referenced.

---------------------------------

<class HasBackground 74> =

class Widget w => HasBackground w where
  background :: String -> Conf w    
                -- see local rgb.txt file for valid color names

instance HasBackground TkDefault
instance HasBackground Window
instance HasBackground Menu
instance HasBackground Frame
instance HasBackground Scrollbar
instance HasBackground Label
instance HasBackground Message
instance HasBackground Canvas
instance HasBackground Scale
instance HasBackground Edit
instance HasBackground (Entry a)
instance HasBackground (Input a)
instance HasBackground (Listbox a)
instance HasBackground Tag
instance HasBackground Button
instance HasBackground Radiobutton
instance HasBackground Menubutton
instance HasBackground Checkbutton
instance HasBackground MButton
instance HasBackground MRadiobutton
instance HasBackground MCheckbutton
instance HasBackground Cascade
instance HasBackground CText
instance HasBackground CBitmap
<>
Macro defined by scraps 74, 82.
Macro never referenced.

---------------------------------

<class HasForeground 75> =

class HasBackground w => HasForeground w where
  foreground :: String -> Conf w
  font       :: String -> Conf w
                -- execute `xlsfonts' for list of valid fonts

instance HasForeground TkDefault
instance HasForeground Label
instance HasForeground Message
instance HasForeground Scale
instance HasForeground Edit
instance HasForeground Tag
instance HasForeground (Entry a)
instance HasForeground (Listbox a)
instance HasForeground (Input a)
instance HasForeground Button
instance HasForeground Radiobutton
instance HasForeground Checkbutton
instance HasForeground Menubutton
instance HasForeground MButton
instance HasForeground MRadiobutton
instance HasForeground MCheckbutton
instance HasForeground Cascade
instance HasForeground CText
instance HasForeground CBitmap
<>
Macro never referenced.

---------------------------------

<class HasBorder 76> =

class HasBackground w => HasBorder w where
  borderWidth :: Int -> Conf w
  cursor      :: String -> Conf w -- see local cursorfont.h
  relief      :: String -> Conf w  
              -- valid options are sunken, ridge, flat, raised or groove

instance HasBorder Window
instance HasBorder Menu
instance HasBorder Frame
instance HasBorder Scrollbar
instance HasBorder Label
instance HasBorder Message
instance HasBorder Canvas
instance HasBorder Scale
instance HasBorder (Entry a)
instance HasBorder Edit
instance HasBorder (Listbox a)
instance HasBorder (Input a)
instance HasBorder Button
instance HasBorder Radiobutton
instance HasBorder Checkbutton
instance HasBorder Menubutton
<>
Macro never referenced.

---------------------------------

<class HasWidth 77> =

class HasBorder w => HasWidth w where
  width               :: Int -> Conf w
  highlightBackground :: String -> Conf w
  highlightColor      :: String -> Conf w
  highlightThickness  :: Int -> Conf w
  focus               :: w -> GUI ()
  takeFocus           :: Bool -> Conf w

instance HasWidth Window
instance HasWidth Frame
instance HasWidth Scrollbar  
instance HasWidth Label 
instance HasWidth Message
instance HasWidth Canvas 
instance HasWidth Scale 
instance HasWidth (Entry a)
instance HasWidth Edit 
instance HasWidth (Listbox a) 
instance HasWidth (Input a)
instance HasWidth Button
instance HasWidth Radiobutton 
instance HasWidth Checkbutton 
instance HasWidth Menubutton 

<>
Macro never referenced.

---------------------------------

<class HasHeight 78> =

class HasWidth w => HasHeight w where
  height :: Int -> Conf w

instance HasHeight Window
instance HasHeight Frame 
instance HasHeight Label  
instance HasHeight Canvas  
instance HasHeight Scale  
instance HasHeight Edit 
instance HasHeight (Listbox a)  
instance HasHeight Button 
instance HasHeight Radiobutton  
instance HasHeight Checkbutton  
instance HasHeight Menubutton 
<>
Macro never referenced.

---------------------------------

<class HasPad 79> =

class HasWidth w => HasPad w where
  padx :: Int -> Conf w
  pady :: Int -> Conf w

instance HasPad Label
instance HasPad Message
instance HasPad Edit
instance HasPad Button
instance HasPad Radiobutton
instance HasPad Menubutton
instance HasPad Checkbutton
<>
Macro never referenced.

---------------------------------

<class HasAnchor 80> =

class HasForeground w => HasAnchor w where
  anchor  :: String -> Conf w -- n, ne, se, s, sw, w, nw, center
  justify :: String -> Conf w -- left, right, center

instance HasAnchor Label
instance HasAnchor Message
instance HasAnchor Button
instance HasAnchor Radiobutton
instance HasAnchor Menubutton
instance HasAnchor Checkbutton
instance HasAnchor CText
instance HasAnchor CBitmap
<>
Macro never referenced.

---------------------------------

<class HasIndicator 81> =

class HasCommand w => HasIndicator w where
  indicatorColor :: String -> Conf w
  indicatorOn    :: Bool -> Conf w

instance HasIndicator Radiobutton
instance HasIndicator MRadiobutton
instance HasIndicator Checkbutton
instance HasIndicator MCheckbutton
<>
Macro never referenced.

---------------------------------

<class HasBackground 82> =

class HasCoords a => HasCoords a where
  moveObject   :: a -> (Int, Int) -> GUI ()
  removeObject :: a -> GUI ()
  lowerObject  :: a -> GUI ()
  raiseObject  :: a -> GUI ()
  getCoords    :: a -> GUI [(Int,Int)]
  setCoords    :: a -> [(Int,Int)] -> GUI ()

instance Widget (CItem a) => HasCoords (CItem a)
<>
Macro defined by scraps 74, 82.
Macro never referenced.

---------------------------------

<class HasFillColor 83> =

class HasCoords a => HasFillColor a where
  penWidth  :: Int -> Conf a
  penColor  :: String -> Conf a
  fillColor :: String -> Conf a

instance HasFillColor COval
instance HasFillColor CLine
instance HasFillColor CRect
<>
Macro never referenced.

---------------------------------

<class HasScroll 84> =

class HasBorder w => HasScroll w 

instance HasScroll Canvas
instance HasScroll (Entry a)
instance HasScroll Edit
instance HasScroll (Listbox a)
<>
Macro never referenced.

---------------------------------

<class HasInput 85> =

class (Widget (c (w v)), GUIValue v) => HasInput c w v where
  getValue  :: c (w v) -> GUI v
  setValue  :: c (w v) -> v -> GUI ()
  updValue  :: c (w v) -> (v -> v) -> GUI ()
  initValue :: v -> Conf (c (w v))
  readOnly  :: Bool -> Conf (c (w v))

instance HasInput TItem Radio0 Int
instance HasInput WItem Scale0 a
instance HasInput WItem Entry0 a
instance HasInput WItem (Edit0 (Int,Int)) [Char]
instance HasPosition Listbox0 Int [a] => HasInput WItem (Listbox0 Int) [a]
instance HasInput a Checkbutton0 b
instance HasInput WItem Entry0 a => HasInput WItem Input0 a
<>
Macro never referenced.

---------------------------------

<class HasPosition 86> =

class (HasInput WItem (w p) v, GUIValue p, Position p)
  => HasPosition w p v where
  putBegin          :: (WItem (w p v)) -> v -> GUI ()
  putEnd            :: (WItem (w p v)) -> v -> GUI ()
  putPos            :: (WItem (w p v)) -> p -> v -> GUI ()
  getFromTo         :: (WItem (w p v)) -> p -> p -> GUI v
  getSize           :: (WItem (w p v)) -> GUI p
  delFromTo         :: (WItem (w p v)) -> p -> p -> GUI ()
  setYView          :: (WItem (w p v)) -> Int -> GUI ()
  getSelection      :: (WItem (w p v)) -> GUI [p]
  setSelection      :: (WItem (w p v)) -> [p] -> GUI ()
  selectBackground  :: String -> Conf (WItem (w p v))
  selectForeground  :: String -> Conf (WItem (w p v))
  selectBorderWidth :: Int -> Conf (WItem (w p v))

instance HasPosition Edit0 (Int,Int) [Char]
instance HasPosition Listbox0 Int [a]
<>
Macro never referenced.

---------------------------------

<class HasText 87> =

class HasForeground w => HasText w where
  text      :: String -> Conf w
  bitmap    :: String -> Conf w  -- bitmap file name
  underline :: Int -> Conf w

instance HasText Label
instance HasText Message
instance HasText Scale
instance HasText Tag
instance HasText Button
instance HasText MButton
instance HasText Radiobutton
instance HasText MRadiobutton
instance HasText Menubutton
instance HasText Cascade
instance HasText Checkbutton
instance HasText MCheckbutton
instance HasText CText
instance HasText CBitmap
instance HasText (Input a)
<>
Macro never referenced.

---------------------------------

<class HasCommand 88> =

class HasText w => HasCommand w where
  command          :: GUI () -> Conf w
  active           :: Bool -> Conf w
  activeBackground :: String -> Conf w
  activeForeground :: String -> Conf w
  invoke           :: w -> GUI ()

instance HasText Scale
instance HasText Button
instance HasText MButton
instance HasText Radiobutton
instance HasText MRadiobutton
instance HasText Menubutton
instance HasText Cascade
instance HasText Checkbutton
instance HasText MCheckbutton
<>
Macro never referenced.

---------------------------------

<class GUIValue 89> =

data OkOrErr a = Tk_Ok a | Tk_Err String

class (Text g) => GUIValue g where
  tk_convert         :: String -> OkOrErr g
  tk_defaultValue    :: g
  tk_toGUI           :: g -> String
  tk_fromGUI         :: String -> GUI g
<>
Macro never referenced.

TopLevel items

<Window 90> =

type Window = TItem Window0
  window         :: [Conf Window] -> GUI Window
  windowDefault  :: [Conf Window] -> [Conf Default] -> GUI Window
  closeWindow    :: Window -> GUI ()
  openWindow     :: [Conf Window] -> (Window -> GUI (WItem w)) -> GUI ()
  openDefault    :: [Conf Window] -> [Conf Default] -> 
                    (Window -> GUI (WItem w)) -> GUI ()
  pack           :: WItem a -> GUI ()
  packDefault    :: WItem a -> [Conf Default] -> GUI ()
  title          :: String -> Conf Window
  winSize        :: (Int,Int) -> Conf Window
  winPosition    :: (Int,Int) -> Conf Window
<>
Macro never referenced.

<Menu 91> =

type Menu = TItem Menu0
  menu           :: Widget (c Menubutton0) 
                 => [Conf Menu] -> c Menubutton0 -> GUI Menu
  menuDefault    :: Widget (c Menubutton0)
                 => [Conf Menu] -> [Conf Default] -> c Menubutton0 -> GUI Menu
  popup          :: (Int, Int) -> Menu -> GUI ()
<>
Macro never referenced.

<Radio 92> =
 
type Radio = TItem (Radio0 Int)
  radio          :: (Widget (c Radiobutton0))
                 => [Conf Radio] -> [c Radiobutton0] -> GUI Radio
<>
Macro never referenced.

Window Items

<Frame 93> =

type Frame = WItem Frame0
  frame          :: Widget (WItem a) => WItem a -> [Conf Frame] -> GUI Frame
<>
Macro never referenced.

<Scrollbar 94> =
 
type Scrollbar = WItem Scrollbar0 
  scrollbar      :: [Conf Scrollbar] -> Window -> GUI Scrollbar
  hscroll        :: HasScroll (WItem w)
                 => [Conf Scrollbar] -> WItem w -> GUI Scrollbar
  vscroll        :: HasScroll (WItem w)
                 => [Conf Scrollbar] -> WItem w -> GUI Scrollbar
<>
Macro never referenced.

<Label 95> =
 
type Label = WItem Label0
  label          :: [Conf Label] -> Window -> GUI Label
<>
Macro never referenced.

<Message 96> =

type Message = WItem Message0
  message        :: [Conf Message] -> Window -> GUI Message
  aspect         :: Int -> Conf Message
<>
Macro never referenced.

<Canvas 97> =

type Canvas = WItem Canvas0
  canvas         :: [Conf Canvas] -> Window -> GUI Canvas
  scrollRegion   :: (Int,Int) -> Conf Canvas
  clearCanvas    :: Canvas -> GUI ()
<>
Macro never referenced.

<Scale 98> =

type Scale = WItem (Scale0 Int)
  vscale         :: [Conf Scale] -> Window -> GUI Scale
  hscale         :: [Conf Scale] -> Window -> GUI Scale
  scaleRange     :: (Int,Int) -> Conf Scale
  sliderLength   :: Int    -> Conf Scale
  tickInterval   :: Int    -> Conf Scale
  troughColor    :: String -> Conf Scale
<>
Macro never referenced.

<Entry 99> =

type Entry a = WItem (Entry0 a)
  entry          :: Widget (Entry a) 
                 => [Conf (Entry a)] -> Window -> GUI (Entry a)
<>
Macro never referenced.

<Edit 100> =

type Edit = WItem (Edit0 (Int,Int) [Char])
  edit           :: [Conf Edit] -> Window -> GUI Edit
  wrap           :: Bool -> Conf Edit

  data Mark = Mark String
    setMark        :: Edit -> (Int,Int) -> GUI Mark
    getMark        :: Edit -> Mark -> GUI (Int,Int)
  
  type Tag = Tag0 ()
    tag            :: [(Int,Int)] -> [Conf Tag] -> Edit -> GUI Tag
    putPosTag      :: Edit -> (Int,Int) -> String -> [Conf Tag] -> GUI Tag
    putEndTag      :: Edit -> String -> [Conf Tag] -> GUI Tag
    delTag         :: Tag -> GUI ()
    tagRange       :: Tag -> GUI [(Int,Int)]
    lowerTag       :: Tag -> GUI ()
<>
Macro never referenced.

<Listbox 101> =

type Listbox a = WItem (Listbox0 Int a)
  listbox        :: Widget (Listbox a)
                 => [Conf (Listbox a)] -> Window -> GUI (Listbox a)
  multipleSelect :: Bool -> Conf (Listbox a)
<>
Macro never referenced.

<Button 102> =

type Button  = WItem Button0
  button         :: [Conf Button] -> Window -> GUI Button
<>
Macro never referenced.

<Radiobutton 103> =

type Radiobutton  = WItem Radiobutton0
  radiobutton    :: [Conf Radiobutton] -> Window -> GUI Radiobutton
<>
Macro never referenced.

<Menubutton 104> =

type Menubutton = WItem Menubutton0
  menubutton     :: [Conf Menubutton] -> Window -> GUI Menubutton
<>
Macro never referenced.

<Checkbutton 105> =

type Checkbutton  = WItem Checkbutton0
  checkbutton    :: [Conf Checkbutton] -> Window -> GUI Checkbutton
<>
Macro never referenced.

Menu Items

<Menu items 106> =

type MButton = MItem Button0
  mbutton        :: [Conf MButton] -> Menu -> GUI MButton

type MRadiobutton = MItem Radiobutton0
  mradiobutton   :: [Conf MRadiobutton] -> Menu -> GUI MRadiobutton
   
type Cascade    = MItem Menubutton0
  cascade        :: [Conf Cascade] -> Menu -> GUI Cascade

type MCheckbutton = MItem Checkbutton0
  mcheckbutton   :: [Conf MCheckbutton] -> Menu -> GUI MCheckbutton

type Separator = MItem Separator0
  separator      :: Menu -> GUI Separator 
<>
Macro never referenced.

Canvas Items

<Canvas items 107> =

type COval = CItem Oval0
  coval  :: (Int,Int) -> (Int,Int) -> [Conf COval] -> Canvas -> GUI COval
  
type CLine = CItem Line0
  cline  :: (Int,Int) -> (Int,Int) -> [Conf CLine] -> Canvas -> GUI CLine

type CRect = CItem Rect0
  crect :: (Int,Int) -> (Int,Int) ->
           [Conf CRect] -> Canvas -> GUI CRect

type CText = CItem CText0
  ctext :: (Int,Int) -> [Conf CText] -> Canvas -> GUI CText

type CBitmap = CItem CBitmap0
  cbitmap :: (Int,Int) -> [Conf CBitmap] -> Canvas -> GUI CBitmap
<>
Macro never referenced.

User Defined Events

<User Defined Events 108> =

key                                           :: String -> TkEvent
click, doubleClick, motion                    :: Int -> TkEvent
return                                        :: TkEvent
cursorUp, cursorDown, cursorLeft, cursorRight :: TkEvent
<>
Macro never referenced.

Widget Combinators and Layout Functions

<Widget Combinators and Layout functions 109> =

infixl 7 <<, <*<, <-<, <|<, <+<, <*-<, <*|<, <*+<
infixl 6 ^^, ^*^, ^-^, ^|^, ^+^, ^*-^, ^*|^, ^*+^
 
(<<),(<*<), (<-<),(<|<), (<+<),(<*-<), (<*|<),(<*+<) 
  :: (Widget (WItem a),Widget (WItem b)) => WItem a -> WItem b -> Frame
(^*^),(^^),(^-^),(^|^),(^+^),(^*-^),(^*|^),(^*+^) 
  :: (Widget (WItem a),Widget (WItem b)) => WItem a -> WItem b -> Frame
 
matrix               :: Widget (WItem a) => Int -> [WItem a] -> Frame
horizontal, vertical :: Widget (WItem a) => [WItem a] -> Frame
fillX, fillY, fillXY :: Widget (WItem a) => WItem a -> WItem a
expand,flexible      :: Widget (WItem a) => WItem a -> WItem a
<>
Macro never referenced.

Monads and Variables

<Monads and Variables 110> =
 
infixr 1 ==>
(==>)      :: GUI Bool -> GUI () -> GUI ()
doneM      :: Monad m => m ()              
seq        :: Monad m => m a -> m b -> m b   
void       :: GUI a -> GUI ()               
seqs       :: Monad m => [m ()] -> m ()    
binds      :: Monad m => [m a] -> m [a] 

newState   :: a -> GUI (Var a)          
readState  :: Var a -> GUI a          
writeState :: Var a -> a -> GUI ()  
modState   :: Var a -> (a -> a) -> GUI ()
<>
Macro never referenced.

Miscellaneous

<Miscellaneous 111> =

self       :: (a -> a -> b) -> a -> b
rgb        :: Int -> Int -> Int -> String
numval     :: String -> Int
startClock :: Int -> GUI () -> GUI ClockId
stopClock  :: ClockId -> GUI ()
updateTask :: GUI ()
<>
Macro never referenced.

Composing Widgets

<Composing Widgets 112> =

composeWidget :: (Widget (WItem w), Widget (WItem v))
              => w -> WItem v -> [Conf (WItem w)] -> GUI (WItem w)
input         :: Widget (Input v)
              => [Conf (Input v)] -> Window -> GUI (Input v)
inputE        :: Input v -> Entry v
inputL        :: Input v -> Label
<>
Macro never referenced.

References

AvGP93
P.M. Achten, J.H.G. van Groningen, and M.J. Plasmeijer. High-level specification of I/O in functional languages. In Glasgow Workshop on Functional Programming 1992. Springer Verlag, 1993.

BR89
P. Briggs and J. Ramsdell. NuwWeb Version 0.87b: A Simple Literate Programming Tool, 1989. available by ftp from ftp.dante.de.

BW89
R. Bird and Ph. Wadler. Introduction to Functional Programming. Prentice Hall International, 1989.

CH93
M. Carlsson and Th. Hallgren. Fudgets -- graphical user interfaces and I/O in lazy functional languages. Licentiate Thesis, May 1993.

FGPS96
T. Frauenstein, W. Grieskamp, P. Pepper, and M. Südholt. Functional programming of communicating agents and its application to graphical user interfaces. In Perspectives of System Informatics, 1996.

HS95
M. Hermenegildo and S.D. Swierstra, editors. Programming Languages: Implementations, Logics and Programs. 7th International Symposium, PLILP '95, volume 982 of Lecture Notes in Computer Science. Springer-Verlag, September 1995.

JD93
M.P. Jones and L. Duponcheel. Composing monads. Research Report YALEU/DCS/RR-1004, Yale University, December 1993.

JM95
J. Jeuring and E. Meijer, editors. Advanced Functional Programming. First International Spring School on Advanced Functional Programming Techniques, Båstad, Sweden, May 1995, volume 925 of Lecture Notes in Computer Science. Springer-Verlag, 1995.

Jon93a
M.P. Jones. An introduction to Gofer (draft), 1993. Included as part of the standard Gofer distribution.

Jon93b
M.P. Jones. Release notes for Gofer 2.28, 1993. Included as part of the standard Gofer distribution.

Jon95
M.P. Jones. Functional programming with overloading and higher-order polymorphism. In Jeuring and Meijer [JM95], pages 97-136.

LPJ94
J. Launchbury and S.L. Peyton Jones. Lazy functional state threads. Technical report, University of Glasgow, November 1994.

LPJ95
J. Launchbury and S.L Peyton Jones. State in haskell. Lisp and Symbolic Computation, (8):293-341, 1995.

Mog91
E. Moggi. Notions of computation and monads. Information and Computation, 93:55-92, 1991.

NR95
R. Noble and C. Runciman. Gadgets: Lazy functional components for graphical user interfaces. In Hermenegildo and Swierstra [HS95], pages 321-340.

Ous94
J.K. Ousterhout. Tcl and the Tk toolkit. Addison Wesley, 1994.

PJW93
S.L. Peyton Jones and Ph. Wadler. Imperative functional programming. In Proc. 20th ACM Symposium on Principles of Programming Languages, Charlotte, North Carolina, January 1993.

Sch96
T. Schwinn. Funktionale implementierung grafischer benutzeroberflächen. Master's thesis, Universität Ulm, Fakultät für Informatik, 1996. in German.

VSS96a
T. Vullinghs, W. Schulte, and T. Schwinn. The design of a functional gui library using constructor classes. In Perspectives of System Informatics, Novosibirsk, 1996.

VSS96b
T. Vullinghs, T. Schwinn, and W. Schulte. Tkgofer: Implementation notes and reference manual. Technical report, Universität Ulm, Fakultät für Informatik, 1996. to appear.

VTS95
T. Vullinghs, D. Tuijnman, and W. Schulte. Lightweight GUIs for functional programming. In Hermenegildo and Swierstra [HS95], pages 341-356.

Wad90
Ph. Wadler. Comprehending monads. In Proc. 1990 ACM Conference on Lisp and Functional Programming, 1990.

Wad92
Ph. Wadler. The essence of functionnal programming. In ACM Principles of Programming Languages, 1992.

Wad95
Ph. Wadler. Monads for functional programming. In Jeuring and Meijer [JM95], pages 24-52.

Index

$:
3, 4, 16, 19, 22, 24, 25, 27, 32, 33, 34, 36, 42, 43, 44, 45, 47, 54, 55, 63, 71.
<*+<:
15, 109.
<*-<:
15, 43, 109.
<*<:
15, 109.
<*|<:
15, 109.
<+<:
15, 109.
<-<:
15, 109.
<<:
13, 15, 24, 25, 32, 41, 50, 59, 109.
<|<:
15, 43, 44, 45, 47, 67, 109.
==>:
51, 52, 53, 110.
active:
88.
activeBackground:
88.
activeForeground:
88.
anchor:
80.
aspect:
22, 96.
background:
19, 21, 24, 25, 47, 54, 68, 71, 74.
binds:
5, 22, 32, 42, 49, 53, 110.
bitmap:
87.
borderWidth:
47, 50, 76.
Button:
8, 9, 11, 74, 75, 76, 77, 78, 79, 80, 87, 88, 102.
button:
2, 3, 9, 16, 17, 24, 27, 31, 42, 50, 102.
Canvas:
9, 55, 66, 74, 76, 77, 78, 84, 97, 107.
canvas:
17, 54, 55, 66, 67, 97.
Cascade:
9, 74, 75, 87, 88, 106.
cascade:
9, 53, 106.
CBitmap:
74, 75, 80, 87, 107.
cbitmap:
107.
cget:
26, 27, 73.
Checkbutton:
25, 74, 75, 76, 77, 78, 79, 80, 81, 87, 88, 105.
checkbutton:
17, 25, 105.
CItem:
8, 73, 82, 107.
clearCanvas:
55, 97.
click:
51, 54, 108.
CLine:
9, 83, 107.
cline:
9, 54, 107.
closeWindow:
20, 50, 90.
command:
2, 3, 11, 16, 24, 25, 27, 31, 32, 42, 43, 49, 50, 53, 71, 88.
composeWidget:
59, 60, 67, 112.
Conf:
9, 10, 11, 20, 26, 54, 59, 60, 67, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 85, 86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 112.
COval:
83, 107.
coval:
54, 107.
CRect:
66, 83, 107.
crect:
54, 55, 67, 107.
cset:
10, 25, 26, 27, 29, 32, 33, 41, 43, 45, 51, 61, 68, 70, 71, 73.
csets:
73.
CText:
74, 75, 80, 87, 107.
ctext:
54, 107.
cursor:
76.
cursorDown:
108.
cursorLeft:
108.
cursorRight:
108.
cursorUp:
108.
delFromTo:
51, 86.
delTag:
100.
doneM:
110.
doubleClick:
45, 108.
Edit:
47, 49, 51, 53, 74, 75, 76, 77, 78, 79, 84, 100.
Entry:
2, 3, 9, 37, 38, 55, 58, 61, 73, 74, 75, 76, 77, 84, 99, 112.
entry:
2, 3, 9, 16, 17, 33, 34, 36, 41, 42, 45, 55, 59, 99.
expand:
14, 109.
fillColor:
54, 55, 68, 83.
fillX:
14, 109.
fillXY:
14, 109.
fillY:
14, 109.
flexible:
14, 47, 109.
focus:
45, 50, 77.
font:
42, 47, 50, 53, 68, 75.
foreground:
67, 75.
frame:
16, 47, 50, 93.
getCoords:
54, 82.
getFromTo:
45, 53, 86.
getMark:
51, 100.
getSelection:
45, 51, 53, 86.
getSize:
86.
getValue:
2, 3, 12, 25, 32, 33, 41, 43, 45, 55, 62, 70, 71, 85.
GUIValue:
12, 26, 37, 39, 40, 59, 73, 85, 86, 89.
HasAnchor:
80.
HasBackground:
62, 69, 74, 75, 76.
HasBorder:
69, 76, 77, 84.
HasCommand:
11, 81, 88.
HasCoords:
54, 82, 83.
HasFillColor:
54, 83.
HasForeground:
62, 69, 75, 80, 87.
HasHeight:
69, 78.
HasIndicator:
81.
HasInput:
12, 57, 62, 65, 70, 85, 86.
HasPad:
79.
HasPosition:
85, 86.
HasScroll:
84, 94.
HasText:
11, 57, 62, 87, 88.
HasWidth:
69, 77, 78, 79.
height:
43, 47, 54, 55, 66, 67, 68, 71, 78.
highlightBackground:
68, 77.
highlightColor:
77.
highlightThickness:
77.
horizontal:
23, 47, 109.
hscale:
43, 71, 98.
hscroll:
45, 94.
Indicator:
66, 67, 68, 69.
indicator:
66, 67, 71.
indicatorColor:
25, 32, 81.
indicatorOn:
25, 81.
initValue:
2, 3, 32, 33, 34, 42, 44, 45, 63, 85.
input:
41, 112.
inputE:
112.
inputL:
112.
invoke:
11, 88.
justify:
22, 80.
key:
63, 108.
Label:
58, 66, 74, 75, 76, 77, 78, 79, 80, 87, 95, 112.
label:
17, 19, 21, 24, 25, 41, 43, 44, 59, 67, 95.
Listbox:
74, 75, 76, 77, 78, 84, 101.
listbox:
17, 44, 45, 101.
lowerObject:
82.
lowerTag:
100.
Mark:
1, 100.
matrix:
22, 23, 42, 109.
MButton:
74, 75, 87, 88, 106.
mbutton:
48, 49, 106.
MCheckbutton:
74, 75, 81, 87, 88, 106.
mcheckbutton:
106.
Menu:
9, 49, 51, 53, 74, 76, 91, 106.
menu:
48, 49, 53, 91.
Menubutton:
49, 74, 75, 76, 77, 78, 79, 80, 87, 88, 104.
menubutton:
48, 49, 104.
menuDefault:
91.
Message:
74, 75, 76, 77, 79, 80, 87, 96.
message:
17, 22, 50, 96.
MItem:
8, 73, 106.
modState:
6, 51, 53, 110.
motion:
54, 108.
MRadiobutton:
74, 75, 81, 87, 88, 106.
mradiobutton:
53, 106.
multipleSelect:
44, 101.
newState:
6, 42, 47, 67, 110.
numval:
40, 55, 111.
on:
33, 34, 41, 45, 54, 55, 63, 73.
onArgs:
61, 73.
onXY:
51, 73.
onxy:
54, 73.
openDefault:
90.
openWindow:
20, 21, 34, 41, 90.
pack:
2, 3, 16, 19, 22, 24, 25, 27, 32, 33, 36, 38, 42, 43, 44, 45, 47, 48, 50, 54, 55, 63, 71, 90.
packDefault:
90.
padx:
79.
pady:
79.
penColor:
54, 83.
penWidth:
54, 83.
popup:
51, 91.
Prompt:
57, 58, 59, 61, 62.
prompt:
56, 59, 63.
putBegin:
86.
putEnd:
45, 86.
putEndTag:
100.
putPos:
51, 86.
putPosTag:
100.
quit:
7, 24, 49, 72.
Radio:
73, 92.
radio:
17, 32, 53, 92.
Radiobutton:
74, 75, 76, 77, 78, 79, 80, 81, 87, 88, 103.
radiobutton:
32, 103.
raiseObject:
54, 82.
readOnly:
85.
readState:
6, 42, 51, 53, 68, 70, 110.
relief:
42, 43, 50, 76.
removeObject:
82.
return:
33, 34, 41, 45, 55, 63, 108.
rgb:
74, 111.
Scale:
74, 75, 76, 77, 78, 87, 88, 98.
Scrollbar:
74, 76, 77, 94.
scrollbar:
17, 94.
scrollRegion:
97.
selectBackground:
86.
selectBorderWidth:
86.
selectForeground:
86.
self:
28, 30, 31, 34, 54, 55, 63, 111.
Separator:
106.
separator:
53, 106.
seq:
50, 110.
seqs:
5, 32, 49, 55, 110.
setCoords:
68, 70, 82.
setMark:
100.
setSelection:
86.
setValue:
2, 3, 12, 32, 33, 35, 41, 42, 45, 49, 62, 70, 71, 85.
setYView:
86.
sliderLength:
98.
start:
2, 3, 7, 19, 21, 22, 24, 25, 27, 32, 33, 34, 36, 41, 42, 43, 44, 45, 47, 54, 55, 63, 71, 72.
startClock:
111.
stopClock:
111.
Tag:
73, 74, 75, 87, 100.
tag:
53, 100.
tagRange:
100.
takeFocus:
77.
text:
2, 3, 16, 19, 21, 22, 24, 25, 27, 31, 41, 42, 43, 44, 49, 50, 53, 54, 63, 67, 70, 87.
tickInterval:
43, 98.
TItem:
8, 73, 85, 90, 91, 92.
title:
2, 3, 16, 19, 21, 22, 24, 25, 41, 42, 43, 45, 47, 50, 54, 55, 63, 90.
troughColor:
98.
underline:
87.
updateTask:
111.
updValue:
12, 34, 63, 85.
vertical:
23, 32, 109.
void:
5, 49, 53, 55, 110.
vscale:
98.
vscroll:
45, 47, 94.
Widget:
10, 12, 13, 14, 15, 26, 37, 57, 59, 60, 61, 68, 73, 74, 82, 85, 91, 92, 93, 99, 101, 109, 112.
width:
19, 21, 25, 41, 42, 43, 47, 54, 55, 66, 67, 68, 71, 77.
Window:
9, 20, 42, 49, 59, 67, 74, 76, 77, 78, 90, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 112.
window:
2, 3, 9, 16, 19, 22, 24, 25, 27, 32, 33, 36, 43, 44, 45, 47, 48, 54, 55, 63, 71, 90.
windowDefault:
42, 50, 90.
winPosition:
90.
winSize:
90.
WItem:
8, 13, 14, 15, 20, 35, 58, 60, 62, 66, 70, 73, 85, 86, 90, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 109, 112.
wrap:
47, 100.
writeState:
6, 42, 68, 70, 110.
^*+^:
15, 50, 109.
^*-^:
15, 109.
^*^:
15, 109.
^*|^:
15, 109.
^+^:
15, 109.
^-^:
2, 3, 15, 25, 41, 42, 43, 44, 45, 47, 55, 109.
^^:
13, 15, 71, 109.
^|^:
15, 109.



Ton Vullinghs
Wed Oct 9 17:12:40 MET DST 1996