Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (99 page)

BOOK: Programming Python
8.68Mb size Format: txt, pdf, ePub
ads
PyClock Source Code

All of the PyClock
source code lives in one file, except for the precoded
configuration style objects. If you study the code at the bottom of the
file shown in
Example 11-12
,
you’ll notice that you can either make a clock object with a
configuration object passed in or specify configuration options by
command-line arguments such as the following (in which case, the script
simply builds a configuration object for you):

C:\...\PP4E\Gui\Clock>
clock.py -bg gold -sh brown -size 300

More generally, you can run this file directly to start a clock
with or without arguments, import and make its objects with
configuration objects to get a more custom display, or import and attach
its objects to other GUIs. For instance, PyGadgets in
Chapter 10
runs this file with command-line
options to tailor the display.

Example 11-12. PP4E\Gui\Clock\clock.py

"""
###############################################################################
PyClock 2.1: a clock GUI in Python/tkinter.
With both analog and digital display modes, a pop-up date label, clock face
images, general resizing, etc. May be run both standalone, or embedded
(attached) in other GUIs that need a clock.
New in 2.0: s/m keys set seconds/minutes timer for pop-up msg; window icon.
New in 2.1: updated to run under Python 3.X (2.X no longer supported)
###############################################################################
"""
from tkinter import *
from tkinter.simpledialog import askinteger
import math, time, sys
###############################################################################
# Option configuration classes
###############################################################################
class ClockConfig:
# defaults--override in instance or subclass
size = 200 # width=height
bg, fg = 'beige', 'brown' # face, tick colors
hh, mh, sh, cog = 'black', 'navy', 'blue', 'red' # clock hands, center
picture = None # face photo file
class PhotoClockConfig(ClockConfig):
# sample configuration
size = 320
picture = '../gifs/ora-pp.gif'
bg, hh, mh = 'white', 'blue', 'orange'
###############################################################################
# Digital display object
###############################################################################
class DigitalDisplay(Frame):
def __init__(self, parent, cfg):
Frame.__init__(self, parent)
self.hour = Label(self)
self.mins = Label(self)
self.secs = Label(self)
self.ampm = Label(self)
for label in self.hour, self.mins, self.secs, self.ampm:
label.config(bd=4, relief=SUNKEN, bg=cfg.bg, fg=cfg.fg)
label.pack(side=LEFT) # TBD: could expand, and scale font on resize
def onUpdate(self, hour, mins, secs, ampm, cfg):
mins = str(mins).zfill(2) # or '%02d' % x
self.hour.config(text=str(hour), width=4)
self.mins.config(text=str(mins), width=4)
self.secs.config(text=str(secs), width=4)
self.ampm.config(text=str(ampm), width=4)
def onResize(self, newWidth, newHeight, cfg):
pass # nothing to redraw here
###############################################################################
# Analog display object
###############################################################################
class AnalogDisplay(Canvas):
def __init__(self, parent, cfg):
Canvas.__init__(self, parent,
width=cfg.size, height=cfg.size, bg=cfg.bg)
self.drawClockface(cfg)
self.hourHand = self.minsHand = self.secsHand = self.cog = None
def drawClockface(self, cfg): # on start and resize
if cfg.picture: # draw ovals, picture
try:
self.image = PhotoImage(file=cfg.picture) # bkground
except:
self.image = BitmapImage(file=cfg.picture) # save ref
imgx = (cfg.size - self.image.width()) // 2 # center it
imgy = (cfg.size - self.image.height()) // 2 # 3.x // div
self.create_image(imgx+1, imgy+1, anchor=NW, image=self.image)
originX = originY = radius = cfg.size // 2 # 3.x // div
for i in range(60):
x, y = self.point(i, 60, radius-6, originX, originY)
self.create_rectangle(x-1, y-1, x+1, y+1, fill=cfg.fg) # mins
for i in range(12):
x, y = self.point(i, 12, radius-6, originX, originY)
self.create_rectangle(x-3, y-3, x+3, y+3, fill=cfg.fg) # hours
self.ampm = self.create_text(3, 3, anchor=NW, fill=cfg.fg)
def point(self, tick, units, radius, originX, originY):
angle = tick * (360.0 / units)
radiansPerDegree = math.pi / 180
pointX = int( round( radius * math.sin(angle * radiansPerDegree) ))
pointY = int( round( radius * math.cos(angle * radiansPerDegree) ))
return (pointX + originX+1), (originY+1 - pointY)
def onUpdate(self, hour, mins, secs, ampm, cfg): # on timer callback
if self.cog: # redraw hands, cog
self.delete(self.cog)
self.delete(self.hourHand)
self.delete(self.minsHand)
self.delete(self.secsHand)
originX = originY = radius = cfg.size // 2 # 3.x div
hour = hour + (mins / 60.0)
hx, hy = self.point(hour, 12, (radius * .80), originX, originY)
mx, my = self.point(mins, 60, (radius * .90), originX, originY)
sx, sy = self.point(secs, 60, (radius * .95), originX, originY)
self.hourHand = self.create_line(originX, originY, hx, hy,
width=(cfg.size * .04),
arrow='last', arrowshape=(25,25,15), fill=cfg.hh)
self.minsHand = self.create_line(originX, originY, mx, my,
width=(cfg.size * .03),
arrow='last', arrowshape=(20,20,10), fill=cfg.mh)
self.secsHand = self.create_line(originX, originY, sx, sy,
width=1,
arrow='last', arrowshape=(5,10,5), fill=cfg.sh)
cogsz = cfg.size * .01
self.cog = self.create_oval(originX-cogsz, originY+cogsz,
originX+cogsz, originY-cogsz, fill=cfg.cog)
self.dchars(self.ampm, 0, END)
self.insert(self.ampm, END, ampm)
def onResize(self, newWidth, newHeight, cfg):
newSize = min(newWidth, newHeight)
#print('analog onResize', cfg.size+4, newSize)
if newSize != cfg.size+4:
cfg.size = newSize-4
self.delete('all')
self.drawClockface(cfg) # onUpdate called next
###############################################################################
# Clock composite object
###############################################################################
ChecksPerSec = 10 # second change timer
class Clock(Frame):
def __init__(self, config=ClockConfig, parent=None):
Frame.__init__(self, parent)
self.cfg = config
self.makeWidgets(parent) # children are packed but
self.labelOn = 0 # clients pack or grid me
self.display = self.digitalDisplay
self.lastSec = self.lastMin = −1
self.countdownSeconds = 0
self.onSwitchMode(None)
self.onTimer()
def makeWidgets(self, parent):
self.digitalDisplay = DigitalDisplay(self, self.cfg)
self.analogDisplay = AnalogDisplay(self, self.cfg)
self.dateLabel = Label(self, bd=3, bg='red', fg='blue')
parent.bind('', self.onSwitchMode)
parent.bind('', self.onToggleLabel)
parent.bind('', self.onResize)
parent.bind('', self.onCountdownSec)
parent.bind('', self.onCountdownMin)
def onSwitchMode(self, event):
self.display.pack_forget()
if self.display == self.analogDisplay:
self.display = self.digitalDisplay
else:
self.display = self.analogDisplay
self.display.pack(side=TOP, expand=YES, fill=BOTH)
def onToggleLabel(self, event):
self.labelOn += 1
if self.labelOn % 2:
self.dateLabel.pack(side=BOTTOM, fill=X)
else:
self.dateLabel.pack_forget()
self.update()
def onResize(self, event):
if event.widget == self.display:
self.display.onResize(event.width, event.height, self.cfg)
def onTimer(self):
secsSinceEpoch = time.time()
timeTuple = time.localtime(secsSinceEpoch)
hour, min, sec = timeTuple[3:6]
if sec != self.lastSec:
self.lastSec = sec
ampm = ((hour >= 12) and 'PM') or 'AM' # 0...23
hour = (hour % 12) or 12 # 12..11
self.display.onUpdate(hour, min, sec, ampm, self.cfg)
self.dateLabel.config(text=time.ctime(secsSinceEpoch))
self.countdownSeconds -= 1
if self.countdownSeconds == 0:
self.onCountdownExpire() # countdown timer
self.after(1000 // ChecksPerSec, self.onTimer) # run N times per second
# 3.x // trunc int div
def onCountdownSec(self, event):
secs = askinteger('Countdown', 'Seconds?')
if secs: self.countdownSeconds = secs
def onCountdownMin(self, event):
secs = askinteger('Countdown', 'Minutes')
if secs: self.countdownSeconds = secs * 60
def onCountdownExpire(self):
# caveat: only one active, no progress indicator
win = Toplevel()
msg = Button(win, text='Timer Expired!', command=win.destroy)
msg.config(font=('courier', 80, 'normal'), fg='white', bg='navy')
msg.config(padx=10, pady=10)
msg.pack(expand=YES, fill=BOTH)
win.lift() # raise above siblings
if sys.platform[:3] == 'win': # full screen on Windows
win.state('zoomed')
###############################################################################
# Standalone clocks
###############################################################################
appname = 'PyClock 2.1'
# use new custom Tk, Toplevel for icons, etc.
from PP4E.Gui.Tools.windows import PopupWindow, MainWindow
class ClockPopup(PopupWindow):
def __init__(self, config=ClockConfig, name=''):
PopupWindow.__init__(self, appname, name)
clock = Clock(config, self)
clock.pack(expand=YES, fill=BOTH)
class ClockMain(MainWindow):
def __init__(self, config=ClockConfig, name=''):
MainWindow.__init__(self, appname, name)
clock = Clock(config, self)
clock.pack(expand=YES, fill=BOTH)
# b/w compat: manual window borders, passed-in parent
class ClockWindow(Clock):
def __init__(self, config=ClockConfig, parent=None, name=''):
Clock.__init__(self, config, parent)
self.pack(expand=YES, fill=BOTH)
title = appname
if name: title = appname + ' - ' + name
self.master.title(title) # master=parent or default
self.master.protocol('WM_DELETE_WINDOW', self.quit)
###############################################################################
# Program run
###############################################################################
if __name__ == '__main__':
def getOptions(config, argv):
for attr in dir(ClockConfig): # fill default config obj,
try: # from "-attr val" cmd args
ix = argv.index('-' + attr) # will skip __x__ internals
except:
continue
else:
if ix in range(1, len(argv)-1):
if type(getattr(ClockConfig, attr)) == int:
setattr(config, attr, int(argv[ix+1]))
else:
setattr(config, attr, argv[ix+1])
#config = PhotoClockConfig()
config = ClockConfig()
if len(sys.argv) >= 2:
getOptions(config, sys.argv) # clock.py -size n -bg 'blue'...
#myclock = ClockWindow(config, Tk()) # parent is Tk root if standalone
#myclock = ClockPopup(ClockConfig(), 'popup')
myclock = ClockMain(config)
myclock.mainloop()

And finally,
Example 11-13
shows the module
that is actually run from the PyDemos launcher script—it predefines a
handful of clock styles and runs seven of them at once, attached to new
top-level windows for a demo effect (though one clock per screen is
usually enough in practice, even for me!).

Example 11-13. PP4E\Gui\Clock\clockStyles.py

# precoded clock configuration styles
from clock import *
from tkinter import mainloop
gifdir = '../gifs/'
if __name__ == '__main__':
from sys import argv
if len(argv) > 1:
gifdir = argv[1] + '/'
class PPClockBig(PhotoClockConfig):
picture, bg, fg = gifdir + 'ora-pp.gif', 'navy', 'green'
class PPClockSmall(ClockConfig):
size = 175
picture = gifdir + 'ora-pp.gif'
bg, fg, hh, mh = 'white', 'red', 'blue', 'orange'
class GilliganClock(ClockConfig):
size = 550
picture = gifdir + 'gilligan.gif'
bg, fg, hh, mh = 'black', 'white', 'green', 'yellow'
class LP4EClock(GilliganClock):
size = 700
picture = gifdir + 'ora-lp4e.gif'
bg = 'navy'
class LP4EClockSmall(LP4EClock):
size, fg = 350, 'orange'
class Pyref4EClock(ClockConfig):
size, picture = 400, gifdir + 'ora-pyref4e.gif'
bg, fg, hh = 'black', 'gold', 'brown'
class GreyClock(ClockConfig):
bg, fg, hh, mh, sh = 'grey', 'black', 'black', 'black', 'white'
class PinkClock(ClockConfig):
bg, fg, hh, mh, sh = 'pink', 'yellow', 'purple', 'orange', 'yellow'
class PythonPoweredClock(ClockConfig):
bg, size, picture = 'white', 175, gifdir + 'pythonPowered.gif'
if __name__ == '__main__':
root = Tk()
for configClass in [
ClockConfig,
PPClockBig,
#PPClockSmall,
LP4EClockSmall,
#GilliganClock,
Pyref4EClock,
GreyClock,
PinkClock,
PythonPoweredClock
]:
ClockPopup(configClass, configClass.__name__)
Button(root, text='Quit Clocks', command=root.quit).pack()
root.mainloop()

Running this script creates the multiple clock display of
Figure 11-24
. Its
configurations support numerous options; judging from the seven clocks
on the display, though, it’s time to move on to our last
example.

[
40
]
Lest that make software engineers seem too doltish, I should
also note that I have been called on repeatedly to teach Python
programming to physicists, all of whom had mathematical training
well in advance of my own, and many of whom were still happily
abusing FORTRAN common blocks and go-tos. Specialization in modern
society can make novices of us all.

[
41
]
The PyDemos script of the preceding chapter, for instance,
launches seven clocks that run in the same process, and all update
smoothly on my (relatively slow) Windows 7 netbook laptop. They
together consume a low single-digit percentage of the CPU’s
bandwidth, and often less than the Task Manager.

BOOK: Programming Python
8.68Mb size Format: txt, pdf, ePub
ads

Other books

Haunted Ground by Irina Shapiro
The Elf King by Sean McKenzie
Doctor Who: Drift by Simon A. Forward
The Castle in the Attic by Elizabeth Winthrop
Truth by Julia Karr
Quick by Viola Grace
The Quickening by Michelle Hoover
Find Me by Cait Jarrod
The Punishing Game by Nathan Gottlieb
Summer Ball by Mike Lupica