Ton Vullinghs, Wolfram Schulte, Thilo Schwinn
Spring 1996
Universität Ulm
Fakultät für Informatik
Germany
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
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.
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].
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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:
v << w places widget w to the right of widget v;v ^^ w places widget w below widget v.
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.
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.
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.
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.
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
. 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.
Let's start with the famous `hello world' example (see Fig. 3.1).
"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.
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.
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.
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.
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.
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 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"))
<>
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.
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.
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.
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]
<>
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.
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
<>
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.
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.
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).
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.
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).
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.
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.
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)
<>
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].
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.
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.
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.
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.
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>
<>
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.
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.
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 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.
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.
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.
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
<>
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.
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 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.
"examples/prompt.gs" 57 =
<Prompt Datatype 58>
<Prompt Construction 59>
<Widget Prompt ?>
<Prompt Configs 61>
<HasText Prompt and HasInput Prompt 62>
<Prompt Application 63>
<>
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).
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.
In the tk.prelude an implementation of the prompt widget exists under the name input.
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).
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.
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 72> = start :: GUI () -> IO () quit :: GUI () <>Macro never referenced.
<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.
---------------------------------
<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.
---------------------------------
<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.
<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.
<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 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 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 108> = key :: String -> TkEvent click, doubleClick, motion :: Int -> TkEvent return :: TkEvent cursorUp, cursorDown, cursorLeft, cursorRight :: TkEvent <>Macro never referenced.
<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 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 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 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.
$:
<*+<:
<*-<:
<*<:
<*|<:
<+<:
<-<:
<<:
<|<:
==>:
active:
activeBackground:
activeForeground:
anchor:
aspect:
background:
binds:
bitmap:
borderWidth:
Button:
button:
Canvas:
canvas:
Cascade:
cascade:
CBitmap:
cbitmap:
cget:
Checkbutton:
checkbutton:
CItem:
clearCanvas:
click:
CLine:
cline:
closeWindow:
command:
composeWidget:
Conf:
COval:
coval:
CRect:
crect:
cset:
csets:
CText:
ctext:
cursor:
cursorDown:
cursorLeft:
cursorRight:
cursorUp:
delFromTo:
delTag:
doneM:
doubleClick:
Edit:
Entry:
entry:
expand:
fillColor:
fillX:
fillXY:
fillY:
flexible:
focus:
font:
foreground:
frame:
getCoords:
getFromTo:
getMark:
getSelection:
getSize:
getValue:
GUIValue:
HasAnchor:
HasBackground:
HasBorder:
HasCommand:
HasCoords:
HasFillColor:
HasForeground:
HasHeight:
HasIndicator:
HasInput:
HasPad:
HasPosition:
HasScroll:
HasText:
HasWidth:
height:
highlightBackground:
highlightColor:
highlightThickness:
horizontal:
hscale:
hscroll:
Indicator:
indicator:
indicatorColor:
indicatorOn:
initValue:
input:
inputE:
inputL:
invoke:
justify:
key:
Label:
label:
Listbox:
listbox:
lowerObject:
lowerTag:
Mark:
matrix:
MButton:
mbutton:
MCheckbutton:
mcheckbutton:
Menu:
menu:
Menubutton:
menubutton:
menuDefault:
Message:
message:
MItem:
modState:
motion:
MRadiobutton:
mradiobutton:
multipleSelect:
newState:
numval:
on:
onArgs:
onXY:
onxy:
openDefault:
openWindow:
pack:
packDefault:
padx:
pady:
penColor:
penWidth:
popup:
Prompt:
prompt:
putBegin:
putEnd:
putEndTag:
putPos:
putPosTag:
quit:
Radio:
radio:
Radiobutton:
radiobutton:
raiseObject:
readOnly:
readState:
relief:
removeObject:
return:
rgb:
Scale:
Scrollbar:
scrollbar:
scrollRegion:
selectBackground:
selectBorderWidth:
selectForeground:
self:
Separator:
separator:
seq:
seqs:
setCoords:
setMark:
setSelection:
setValue:
setYView:
sliderLength:
start:
startClock:
stopClock:
Tag:
tag:
tagRange:
takeFocus:
text:
tickInterval:
TItem:
title:
troughColor:
underline:
updateTask:
updValue:
vertical:
void:
vscale:
vscroll:
Widget:
width:
Window:
window:
windowDefault:
winPosition:
winSize:
WItem:
wrap:
writeState:
^*+^:
^*-^:
^*^:
^*|^:
^+^:
^-^:
^^:
^|^: