## Calculator example source code

calc2.py from Tkinter import *

import Pmw O Python MegaWidgets c1ass SLabe1(Frame):

SLabe1 defines a 2-sided 1abe1 within a Frame. The

1eft hand 1abe1 has b1ue 1etters; the right has white 1etters.

def _init_(se1f, master, 1eft1, right1):

se1f.pack(side=LEFT, expand=YES, fi11=BOTH) Labe1(se1f, text=1eft1, fg='stee1b1ue1', font=("aria1", 6, "bo1d"), width=5, bg='gray40').pack( side=LEFT, expand=YES, fi11=BOTH) Labe1(se1f, text=right1, fg='white', font=(Maria1", 6, Mbo1d"), width=1, bg='gray40').pack(

Tkinter, including these:

Figure 3.2 A better calculator

side=RIGHT, expand=YES, fill=BOTH)

class Key(Button):

def _init_(self, master, font=('ariall, 8, 'bold'), fg='white',width=5, borderwidth=5, **kw): kw['font'] = font kw['fg'] = fg kw['width'] = width kw['borderwidth'] = borderwidth apply(Button._init_, (self, master), kw)

self.pack(side=LEFT, expand=NO, fill=NONE)

class Calculator(Frame):

def _init_(self, parent=None):

self.pack(expand=YES, fill=BOTH) self.master.title('Tkinter Toolkit TT-421) self.master.iconname('Tk-42')

self.calc = Evaluator() # This is our evaluator self.buildCalculator() # Build the widgets

# This is an incomplete dictionary - a good exercise! self.actionDict = {'second1: self.doThis, 'mode1: self.doThis, delete'

matrix'

store':

self.doThis, self.doThis, self.doThis, self.doThis, self.doThis, self.doThis, self.doThis, self.doThis, self.doThis, self.doThis, alpha': math': program' clear': cos': up' X2' ln' off1: enter'

self.doThis, self.doThis, : self.doThis, self.clearall, self.doThis, self.doThis, self.doThis, self.doThis, self.turnoff self.doEnter, self.current =

def doThis(self,action):

print '"%s" has not been implemented' % action def turnoff(self, *args): self.quit()

def clearall(self, *args): self.current = ""

self.display.component('text').delete(1.0, END) 4

def doEnter(self, *args):

self.display.insert(END, '\n')

result = self.calc.runpython(self.current) 5

if result:

self.display.insert(END, '%s\n' % result, 'ans') 6 self.current = ""

def doKeypress(self, event): key = event.char if key != '\b':

self.current = self.current + key

 seif.current = se1f.current[:-1] x | def keyAction(se1f, key): se1f.disp1ay.insert(END, key) seif.current = seif.current + key def eva1Action(se1f, action): try: se1f.actionDict[action](action) except KeyError: pass Code comments o Pmw (Python MegaWidgets) widgets are used. These widgets will feature prominently in this book since they provide an excellent mechanism to support a wide range of GUI requirements and they are readily extended to support additional requirements. 2 In the constructor for the Key class, we add key-value pairs to the kw (keyword) dictionary and then apply these values to the Button constructor. def _init_(se1f, master, font=('aria1', 8, 'bo1d'), fg='white',width=5, borderwidth=5, **kw): kw['font'] = font apply(Button._init_, (self, master), kw) This allows us a great deal of flexibility in constructing our widgets. 3 The Calculator class uses a dictionary to provide a dispatcher for methods within the class. 'matrix1: se1f.doThis, 'program1: se1f.doThis, vars': se1f.doThis, 'dear1: se1f.c1eara11, 'sin': se1f.doThis, 'cos': se1f.doThis, Remember that dictionaries can handle much more complex references than the rela- tively simple cases we need for this calculator. 4 We use a Pmw Scro11edText widget, which is a composite widget. To gain access to the contained widgets, the component method is used. se1f.disp1ay.component('text').de1ete(1.0, END) O When the ENTER key is clicked, the collected string is directed to the calculator's evaluator: resu1t = seif.caic.runpython(seif.current) The result of this evaluation is displayed in the scrolled text widget. 6 The final argument in the text insert function is a text tag 'ans' which is used to change the foreground color of the displayed text. se1f.disp1ay.insert(END, '%s\n' % resu1t, 'ans') O doKeypress is a callback bound to all keys. The event argument in the callback provides the client data for the callback. event.char is the key entered; several attributes are available in the client data, such as x-y coordinates of a button press or the state of a mouse operation (see "Tkinter events" on page 98). In this case we get the character entered. O A simple exception mechanism to take action on selected keys is used. CALCULATOR EXAMPLE: SOURCE CODE 23
 1 calc2.py (continued) ^H def buildCalculator(self): FUN = 1 # A Function I KEY = 0 # A Key KCl = 'gray30' # Dark Keys KC2 = 'gray50' # Light Keys KC3 = 'steelbluel' # Light Blue Key KC4 = 'steelblue' # Dark Blue Key keys = [ [('2nd1, KC3, FUN, 'second'), # Row 1 ('Mode1, 'Quit', '', KC1, FUN, 'mode'), ('Del', 'Ins', '', KC1, FUN, 'delete'), ('Alpha ,'Lock', '', KC2, FUN, 'alpha'), ('Stat', 'List', '', KC1, FUN, 'stat')], [('Math', 'Test', 'A', KC1, FUN, 'math'), # Row 2 ('Mtrx', 'Angle', 'B', KC1, FUN, 'matrix'), ('Prgm', 'Draw', 'C', KC1, FUN, 'program'), ('Vars', 'YVars', '', KC1, FUN, 'vars'), ('Clr', KC1, FUN, 'clear')], [('X-l', 'Abs', 'D', KC1, FUN, 'X1'), # Row 3 ('Sin', 'Sin-1', 'E', KC1, FUN, 'sin'), ('Cos', 'Cos-1', 'F', KC1, FUN, 'cos'), ('Tan', 'Tan-1', 'G', KC1, FUN, 'tan'), 'PI', 'H' , KC1, FUN, 'up')], [('X2', 'Root', 'I', KC1, FUN, 'X2'), # Row 4 (',', EE', 'J' , KC1, KEY, ','), ('(', {', 'K', KC1, KEY, '('), (')', }', 'L', KC1, KEY, ')'), ('/', ', 'M', KC4, KEY, '/')], [('Log', '10x', 'N', KC1, FUN, 'log'), # Row 5 ('7', Un-1', 'O', KC2, KEY, '7'), ('8', Vn-1', 'P', KC2, KEY, '8'), ('9', n1, 'Q', KC2, KEY, '9'), ('X', [', 'R', KC4, KEY, '*')], [('Ln', 'ex', 'S', KC1, FUN, 'ln'), # Row 6 ('4', L4', 'T' , KC2, KEY, '4'), ('5', L5', 'U' , KC2, KEY, '5'), ('6', L6', 'V' , KC2, KEY, '6'), ('-', ]', 'W', KC4, KEY, '-')], [('STO', 'RCL', 'X', KC1, FUN, 'store'), # Row 7 ('1', L1', 'Y' , KC2, KEY, '1'), ('2', L2', 'Z' , KC2, KEY, '2'), ('3', L3', '', KC2, KEY, '3'), (' + ', MEM', '" ', KC4, KEY, '+')], [('Off', KC1, FUN, 'off'), # Row 8 ('0', KC2, KEY, '0'), ('•', KC2, KEY, '.'), ('(-)', 'ANS', '?', KC2, FUN, 'neg'), ('Enter ,'Entry', '', KC4, FUN, 'enter')]] self display = Pmw.ScrolledText(self, hscrollmode='dynamic', vscrollmode='dynamic', hull_relief='sunken', hull_background='gray40', hull_borderwidth=10, 24 CHAPTER 3 BUILDING AN APPLICATION
 text_background='honeydew4', text_width=16, text_foreground='blackl, text_height=6, text_padx=10, text_pady=10, text_relief='groove', text_font=('arial', 12, 'bold')) self.display.pack(side=TOP, expand=YES, fill=BOTH) self.display.tag_config('ans1, foreground='white') ! self.display.component('text').bind(l', self.doKeypress) self.display.component('text').bind('', self.doEnter) for row in keys: rowa = Frame(self, bg='gray40') rowb = Frame(self, bg='gray40') for p1, p2, p3, color, ktype, func in row: if ktype == FUN: a = lambda s=self, a=func: s.evalAction(a) else: a = lambda s=self, k=func: s.keyAction(k) SLabel(rowa, p2, p3) Key(rowb, text=p1, bg=color, command=a) rowa.pack(side=TOP, expand=YES, fill=BOTH) rowb.pack(side=TOP, expand=YES, fill=BOTH) class Evaluator: def _init_(self): self.myNameSpace = {} self.runpython("from math import *") def runpython(self, code): try: return "eval(code, self.myNameSpace, self.myNameSpace)" # except SyntaxError: try: \$ exec code in self.myNameSpace, self.myNameSpace except: return 'Error' Calculator().mainloop() Code comments (continued) o A number of constants are defined. The following data structure is quite complex. Using con stants makes it easy to change values throughout such a complex structure and they make the code much more readable and consequently easier to maintain. FUN = 1 # A Function KEY = 0 # A Key KC1 = 'gray30' # Dark Keys KC2 = 'gray50' # Light Keys These are used to populate a nested list of lists, which contains tuples. The tuples store three labels, the key color, the function or key designator and the method to bind to the key's cmd (activate) callback. — We create the Pmw ScrolledText widget and provide values for many of its attributes. self.display = Pmw.ScrolledText(self, hscrollmode='dynamic', CALCULATOR EXAMPLE: SOURCE CODE 25

vscro11mode='dynamic', hu11_re1ief='sunken', hu11_background='gray40', hu11_borderwidth=10, text_background='honeydew4', text_width=16,

Notice how the attributes for the hu11 (the container for the subordinate widgets within Pmw widgets) and the text widget are accessed by prefixing the widget. ! We define a text tag which is used to differentiate output from input in the calculator's screen. se1f.disp1ay.tag_config('ans1, foreground='white') We saw this tag in use earlier in the text insert method. © Again, we must use a 1ambda expression to bind our callback function.

# Python exceptions are quite flexible and allow simple control of errors. In the calculator's evaluator (runpython), we first run eva1. try:

return %eva1(code, se1f.myNameSpace, se1f.myNameSpace)% This is used mainly to support direct calculator math. eva1 cannot handle code sequences, however, so when we attempt to eval a code sequence, a SyntaxError exception is raised. © We trap the exception:

except SyntaxError: try:

exec code in se1f.myNameSpace, se1f.myNameSpace except:

return 'Error'

and then the code is exec'ed in the except clause. Notice how this is enclosed by another try... except clause.

Figure 3.2 shows the results of clicking keys on the calculator to calculate simple math equations. Unlike many calculators, this displays the input and output in different colors. The display also scrolls to provide a history of calculations, not unlike a printing calculator. If you click on the display screen, you may input data directly. Here is the surprise: you can enter Python and have exec run the code.

Figure 3.3 shows how you can import the sys module and access built-in functions within Python. Technically, you could do almost anything from this window (within the constraint of a very small display window). However, I don't think that this calculator is the much-sought Interactive Development Environment (IDE) for Python! (Readers who subscribe to the Python news group will understand that there has been a constant demand for an IDE for Python. Fortunately, Guido Van Rossum has now released IDLE with Python.)

When you press ENTER after dir(), you will see output similar to figure 3.4. This list of built-in symbols has scrolled the display over several lines (the widget is only 16 characters wide, after all).

 SmffflSW I [_doc_ ', '_name_', '_ _stderr_ , '_stdin_', '_ I _stdout_ ', 'argv', 'buiitin I _module_ names', 'copyri 1

Figure 3.3 Python input

Figure 3.3 Python input atform', 'prefix', 'setcheck interval', 'setprofiie', 'settr ace', 'stderr', 'stdin', 'stdo ut', 'version', 'winver']

Figure 3.4 Output from dir()

Figure 3.5 Variables and built-in functions

Because we are maintaining a local namespace, it is possible to set up an interactive Python session that can do some useful work. Figure 3.5 shows how we are able to set variables within the namespace and manipulate the data with built-ins.

Figure 3.5 Variables and built-in functions

Figure 3.6 Using the math module

Figure 3.6 is yet another example of our ability to gain access to the interpreter from an interactive shell. While the examples have been restricted to operations that fit within the limited space of the calculator's display, they do illustrate a potential for more serious applications. Note how Python allows you to create and use variables within the current namespace.

« I .j When developing applications, I generally hide a button or bind a "secret" key /VOAfr sequence to invoke a GUI which allows me to execute arbitrary Python so that I can examine the namespace or modify objects within the running system. It is really a miniature debugger that I always have access to during development when something unusual happens. Sometimes restarting the application for a debug session just does not get me to the solution. An example of one of these tools is found in "A Tkinter explorer" on page 334.

+1 -1