\chapter{Graphical user interface programming}

\section{Event driven programming}

\myidx{Graphical User Interface (GUI)}\ programming is very different
from the kind of programming we've been discussing so far. We might
call the programming we've done so far \emph{Sequence Driven
Programming}. In this style of program, the computer does something,
then does something else, perhaps waiting for the user to type
something in, or getting input from somewhere else, but essentially it
proceeds in a sequential fashion. GUI programs are different because
they are usually \myidx{Event Driven}. In this sort of program, there
is usually a lot more user interaction, and the program almost always
has to do something (redrawing itself on the screen if nothing else).

Before looking at actual GUI programs, it's worthwhile seeing how we
might simulate event driven programming using sequence driven
programming. After all, underneath the hood, at the system level, it
really is sequence driven programming that is happening. Consider the
Python program displayed in Figure \ref{fig-event-simulate}.

\begin{figure}
\begin{python}
def draw_screen():
    global text
    for i in range(20):
        print
    print text

def get_event():
    return raw_input()

def process_event(event):
    global text
    if event == "quit":
        quit()
    elif event == "hello":
        text = "Hello to you too"
    elif event == "goodbye":
        text = "Goodbye"
        draw_screen()
        process_event("quit")
    else:
        print "I don't know that event"


# set up a global variable
text = ""

# start the event loop
while True:
    event = get_event()
    process_event(event)
    draw_screen()
\end{python}
\caption{A simulated event loop. \label{fig-event-simulate}}
\end{figure}

The program in Figure \ref{fig-event-simulate} contains all the elements of an event-driven
program. It has an event loop that runs forever, a way of getting
events from the user, a function that processes events, and a function
that redraws the screen. It also has two other features that you
should note. Firstly, it makes use of \myidx{global~variables}. The
keyword \pythoninline{global} indicates that the variable is defined
in the global scope of the program, and not the local scope of the
function. Generally, this is considered bad programming practice, but
is almost unavoidable with procedural event-driven
programming. Object-oriented event-driven programming and functional
programming can be used to overcome this shortcoming, but for now,
using global variables is simpler. Python does include
object-oriented concepts and functional programming concepts, but we
touch on them only lightly in COMP150 --- we'll look briefly at
object-oriented programming in Lecture \ref{lec-oo}.

\section{TkInter introduction}

Tkinter (Tk interface) is Python's default GUI toolkit, although there are many
others. It is reasonably simple and lightweight and should be
perfectly adequate for GUI programs of small to medium
complexity. Let's have a look at a simple Tkinter program:

\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *

# create the root window
root = Tk()

# add a label
label = Label(root, text="Hello World")
label.grid(column=0,row=0)

# enter the main loop
root.mainloop()
\end{python}
\end{minipage}
If you run\footnote{In general, Tkinter programs should not be run
  from within Idle. The easiest way is to use a terminal and run from
  the command line.} the above program, you should see a window which looks like
Figure \ref{fig-tk-hello1}. 
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/Tk_HelloWorld.png}
\caption{Tkinter Hello World \label{fig-tk-hello1}}.
\end{figure}

The structure of the program above is
typical of a Tkinter program and has the following general steps:
\begin{enumerate}
\item create the root window (\pythoninline{root = Tk()}),
\item add user interface elements (in this case a label),
\item specify where in the interface the elements should go (by
calling the \pythoninline{grid} method),
\item enter the main loop.
\end{enumerate}
An astute reader may be asking what sort of variables are
\pythoninline{root} and \pythoninline{label}? These are special types
of variables 
defined by the Tkinter toolkit. Such variables are called
\myidx{objects}, and are the subject of
\myidx{object-oriented~programming}. Python is a multi-paradigm
language, and we've focused 
mostly on the procedural paradigm in this book. Object-oriented
programming is a different sort of paradigm - something you will learn
much more of in COMP160. For now, let's treat these objects just like
other variables, except that we access functions associated with the
objects using the dot-notation as above
(e.g. \pythoninline{label.grid(...)}). 

You may also have noticed the special form of some of the function
calls like \pythoninline{Label(root, text='Hello World')}. The
parameter \pythoninline{text='Hello World'} is an example of a
\myidx{named~parameter}. Named parameters are extremely useful when
combined with \myidx{default~parameter~values}. Here's an example:

\begin{minipage}{\textwidth}
\begin{python}
def read_value_type(prompt='Enter a value> ', convert=float):
    val = input(prompt)
    return convert(val)

print read_value_type()
print read_value_type(prompt='Enter a float> ')
print read_value_type(convert=int)
print read_value_type(prompt='Enter a boolean> ', convert=bool)
\end{python}
\end{minipage}
The only limitation on default parameters is that they must come after
non-default parameters in the function definition.

One other piece of notation you should be familiar with is the term
\myidx{widget}. A widget is a GUI element. Every GUI element can be
called a widget. Tkinter has several different types of widgets.
The most useful ones are:
\begin{description}
\item[\pythoninline{Tk}:] the root widget,
\item[\pythoninline{Label}:] a widget containing fixed text,
\item[\pythoninline{Button}:] a button widget,
\item[\pythoninline{Entry}:] a widget for entry of single lines of
text,
\item[\pythoninline{Canvas}:] a widget for drawing shapes onto the
screen.
\end{description}

\section{Introducing callbacks}

To do anything really useful with GUI programming, we need some way of
associating an action with a widget. Usually, when the user presses a
button on a GUI she expects something to happen as a result. Let's
make our Hello World program a bit more complex, by adding two
buttons:

\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *

def say_hi():
    print "hi there"

root = Tk()

b1 = Button(root, text="QUIT", fg="red", command=root.quit)
b1.grid(column=1,row=0)

b2 = Button(root, text="Hello", command=say_hi)
b2.grid(column=0,row=0)

root.mainloop()
\end{python}
\end{minipage}
If we run the above program, we will get a window that looks like
Figure \ref{fig-hello-quit}. In both calls to the function
\pythoninline{Button}, there is a parameter called
\pythoninline{command}. The value of the \pythoninline{command} parameter is
the callback or function associated with the action of pressing the
button. So, if we press the \pythoninline{Hello} button, it should print `hi
there' to the system console because that's what the function
\pythoninline{say\_hi} does. If we press the \pythoninline{Quit} button, then the
special function \pythoninline{root.quit} will be called which,
unsurprisingly, causes the application to terminate.
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/Tkinter_Btn_Mac.png}
\caption{Tkinter Buttons \label{fig-hello-quit}}.
\end{figure}

Since we're writing a GUI program, it would be very unusual (and quite
disconcerting) to make use of the console at all. When we want to
give a message to the user, it is more usual to use a \myidx{dialog}
widget. The module \pythoninline{tkMessageBox} includes numerous
useful dialogs. The above program can be modified to use a
\pythoninline{showinfo} dialog box instead:

\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *
from tkMessageBox import *

def say_hi():
    showinfo(message="Hi there")

root = Tk()

b1 = Button(root, text="Hello", command=say_hi)
b1.grid(column=0,row=0)

b2 = Button(root, text="QUIT", fg="red", command=root.quit)
b2.grid(column=1,row=0)

root.mainloop()
\end{python}
\end{minipage}
Now when we press the \pythoninline{Hello} button, we should see something
similar to Figure \ref{fig-hello-dialog}.
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/tkMessageBox_Mac.png}
\caption{Tkinter Hello World Dialog\label{fig-hello-dialog}}.
\end{figure}

\section{User input}
\label{sec-user_input}
Tkinter has several widgets available for getting input from the user.
Dialog boxes allow one sort of input, but most interfaces have some
form of input on the main window. There are two main widgets for doing
this: \pythoninline{Entry} and \pythoninline{Text}. We will use the
\pythoninline{Entry} widget for our simple application.

To make use of user input widgets, we need a mechanism for getting
information back out of the widget. The easiest way to do that is to
associate a variable with the widget when the widget is created.
Tkinter has special types that you can use for this purpose. They
are: \pythoninline{StringVar}, \pythoninline{IntVar},
\pythoninline{DoubleVar}, and \pythoninline{BooleanVar}, which hold a
string, integer, double value or boolean value respectively.

Let's have a look at an example of how to use an \pythoninline{Entry}
widget:

\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *
from tkMessageBox import *

def say_hi():
    global inVal
    showinfo(message="Hi there "+inVal.get())

root = Tk()

b1 = Button(root, text="Hello", command=say_hi)
b1.grid(column=2,row=0)

b2 = Button(root, text="QUIT", fg="red", command=root.quit)
b2.grid(column=3,row=0)

inVal = StringVar()
input = Entry(root, textvar=inVal)
input.grid(column=1,row=0)

label = Label(root, text="Enter your name: ")
label.grid(column=0,row=0)

root.mainloop()
\end{python}
\end{minipage}

The most important thing to notice about this program, is that we have
created a variable called \pythoninline{inVal} which we have
associated with the \pythoninline{input} variable. When the callback
\pythoninline{say\_hi} is called, the string representation of
\pythoninline{inVal} is retrieved and included as part of the dialog
message. The main window for this program can be seen in Figure
\ref{fig-entry}.
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/Tk_EntryExample.png}
\caption{Tkinter Entry Example\label{fig-entry}}.
\end{figure}
 
\section{Mini-case study}

Let's write a simple GUI program that can convert from Fahrenheit to
Celsius. The conversion equation is quite simple:
\begin{equation}
c = \frac{5}{9}(f-32),
\end{equation}
where $f$ is the temperature in Fahrenheit and $c$ is the temperature
in Celsius.

For this program, we'll need an \pythoninline{Entry} widget, an output
\pythoninline{Label} widget, a \pythoninline{Button} widget to do the
conversion and a \pythoninline{Button} widget to quit the application.
For our \pythoninline{Entry} widget, we want to input numbers and not
text, and since we need real numbers, a \pythoninline{DoubleVar} is
the appropriate type to use. The program,
\pythoninline{tk\_converter1.py}, is shown below: 

\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *

def convert():
    global inVal
    global outputLabel
    dval = inVal.get()
    dval = (dval-32)*5.0/9.0
    outputLabel.configure(text = str(dval))
	
root = Tk()
inVal = DoubleVar()

outputLabel = Label(root, text="0.0", width=20)
outputLabel.grid(column=1,row=1)
outputName = Label(root, text="Fahrenheit", width=20)
outputName.grid(column=1, row=0)

inputEntry = Entry(root, textvariable=inVal)
inputEntry.grid(column=0,row=1)
inputName = Label(root, text="Celsius")
inputName.grid(column=0, row=0)

convertButton = Button(root, text="Convert", command=convert)
convertButton.grid(row=2, column=0)

quitButton = Button(root, text="Quit", command=root.quit)
quitButton.grid(row=2, column=1)

root.mainloop()
\end{python}
\end{minipage}

The function \pythoninline{convert} does the conversion. Notice how it
first gets the value of \pythoninline{inVal} which is what the user
entered into the entry field. Then it does the conversion, and
finally, it changes the text on the output field by calling the
\pythoninline{configure} method. If you type in and run the above
program, you should get an application that looks like Figure
\ref{fig-convert1}. 
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/Tk_FtoC.png}
\caption{Fahrenheit to Celsius Converter\label{fig-convert1}}.
\end{figure}

\newpage
\section{Glossary}
\begin{description}
\item[Graphical User Interface (GUI):] a program interface using
graphical output and input is often taken directly from the keyboard
and mouse.
\item[Event Driven:] A programming style where the flow of control of
the program is determined by external events such as mouse-clicks,
keyboard presses, etc.
\item[Global variables:] Variables that can be accessed from anywhere
within a program. The use of global variables should be limited as
much as possible.
\item[Objects:] A special type of compound variable that has specific
functions (called methods) associated with that variable type.
\item[Object-Oriented Programming:] Programming in a language that
supports objects.
\item[Named Parameter:] When making a function call it is possible to
  explicitly name the parameters as in
  \pythoninline{read\_value\_type(prompt='Enter a float> ')}.
\item[Default Parameter:] A parameter which is assigned a default
  value in a function definition.
\item[Widget:] A GUI element such as a window, button, label, etc.
\item[Dialog:] A transient widget often used for alerting the user or
getting information from the user.
\end{description}

\newpage
\section{Laboratory exercises}
\begin{enumerate}
\item Add a function called \pythoninline{convert\_f\_to\_c} to
\pythoninline{tk\_converter1.py}. \pythoninline{convert\_f\_to\_c} should
take the temperature in Fahrenheit as input and return the temperature
in Celsius. It should pass the following doctests:
\begin{python}
def convert_f_to_c(f):
    """
    >>> c = convert_f_to_c(-40)
    >>> abs(c+40.0)<1e-8
    True
    >>> c = convert_f_to_c(100)
    >>> abs(c-37.7777777777)<1e-8
    True
    """
\end{python}
Note that this is the correct way to test equality of real numbers,
you should never use a direct comparison. You can have the doctests
tested each time you run the program by including the following code
just before the call to \pythoninline{root.mainloop}:
\begin{python}
import doctest
doctest.testmod()
\end{python}

\item Modify the function \pythoninline{convert} so that it
makes use of the new function \pythoninline{convert\_f\_to\_c}.

\item Create a new function called \pythoninline{convert\_c\_to\_f} and
devise appropriate doctests to test it.

\item In the \pythoninline{convert} function, create a variable called
\pythoninline{forwardConvert} which can be either \pythoninline{True}
or \pythoninline{False}. Modify  \pythoninline{convert} to use an
\pythoninline{if-else} statement to 
call \pythoninline{convert\_f\_to\_c} if \pythoninline{forwardConvert} is
\pythoninline{True} and \pythoninline{convert\_c\_to\_f} if
\pythoninline{forwardConvert} is \pythoninline{False}.

\item Add a \pythoninline{Checkbutton} button to your interface with the
following code:
\begin{python}
do_f_to_c = BooleanVar()
do_f_to_c.set(True)
check = Checkbutton(root, text="do forward conversion", variable=do_f_to_c)
check.grid(column=0,row=3)
\end{python}

\item Finally, modify the \pythoninline{convert} function by adding
the following line:
\begin{python}
forwardConvert = do_f_to_c.get()
\end{python}
Verify that selecting or unselecting the check box allows you to
convert from Fahrenheit to Celsius or from Celsius to Fahrenheit.

\item \emph{Extension Exercise:} Using the code from
  section~\ref{sec-user_input} as a starting point write a program
  which reads mathematical expressions from the user and displays a
  message box containing the result.

\end{enumerate}
