Clarify portability and main program.
[python/dscho.git] / Demo / stdwin / clock.py
blob90f1d22bce8197fc59be088484418cb8b11322bd
1 #! /usr/bin/env python
3 # 'clock' -- A simple alarm clock
5 # The alarm can be set at 5 minute intervals on a 12 hour basis.
6 # It is controlled with the mouse:
7 # - Click and drag around the circle to set the alarm.
8 # - Click far outside the circle to clear the alarm.
9 # - Click near the center to set the alarm at the last time set.
10 # The alarm time is indicated by a small triangle just outside the circle,
11 # and also by a digital time at the bottom.
12 # The indicators disappear when the alarm is not set.
13 # When the alarm goes off, it beeps every minute for five minutes,
14 # and the clock turns into inverse video.
15 # Click or activate the window to turn the ringing off.
17 import stdwin
18 from stdwinevents import WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP, \
19 WE_TIMER, WE_DRAW, WE_SIZE, WE_CLOSE, WE_ACTIVATE
20 import mainloop
21 import time
22 from math import sin, cos, atan2, pi, sqrt
24 DEFWIDTH, DEFHEIGHT = 200, 200
26 MOUSE_EVENTS = (WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP)
27 ORIGIN = 0, 0
28 FARAWAY = 2000, 2000
29 EVERYWHERE = ORIGIN, FARAWAY
32 def main():
33 win = makewindow()
34 del win
35 mainloop.mainloop()
37 def makewindow():
38 stdwin.setdefwinsize(DEFWIDTH, DEFHEIGHT + stdwin.lineheight())
39 win = stdwin.open('clock')
40 setdimensions(win)
41 win.set = 1 # True when alarm is set
42 win.time = 11*60 + 40 # Time when alarm must go off
43 win.ring = 0 # True when alarm is ringing
44 win.dispatch = cdispatch
45 mainloop.register(win)
46 settimer(win)
47 return win
49 def cdispatch(event):
50 type, win, detail = event
51 if type == WE_DRAW:
52 drawproc(win, detail)
53 elif type == WE_TIMER:
54 settimer(win)
55 drawproc(win, EVERYWHERE)
56 elif type in MOUSE_EVENTS:
57 mouseclick(win, type, detail)
58 elif type == WE_ACTIVATE:
59 if win.ring:
60 # Turn the ringing off
61 win.ring = 0
62 win.begindrawing().invert(win.mainarea)
63 elif type == WE_SIZE:
64 win.change(EVERYWHERE)
65 setdimensions(win)
66 elif type == WE_CLOSE:
67 mainloop.unregister(win)
68 win.close()
70 def setdimensions(win):
71 width, height = win.getwinsize()
72 height = height - stdwin.lineheight()
73 if width < height: size = width
74 else: size = height
75 halfwidth = width/2
76 halfheight = height/2
77 win.center = halfwidth, halfheight
78 win.radius = size*45/100
79 win.width = width
80 win.height = height
81 win.corner = width, height
82 win.mainarea = ORIGIN, win.corner
83 win.lineheight = stdwin.lineheight()
84 win.farcorner = width, height + win.lineheight
85 win.statusarea = (0, height), win.farcorner
86 win.fullarea = ORIGIN, win.farcorner
88 def settimer(win):
89 now = time.time()
90 hours, minutes, seconds = win.times = calctime(now)
91 delay = 61 - seconds
92 win.settimer(10 * delay)
93 minutes = minutes + hours*60
94 if win.ring:
95 # Is it time to stop the alarm ringing?
96 since = (minutes - win.time + 720) % 720
97 if since >= 5:
98 # Stop it now
99 win.ring = 0
100 else:
101 # Ring again, once every minute
102 stdwin.fleep()
103 elif win.set and minutes == win.time:
104 # Start the alarm ringing
105 win.ring = 1
106 stdwin.fleep()
108 def drawproc(win, area):
109 hours, minutes, seconds = win.times
110 d = win.begindrawing()
111 d.cliprect(area)
112 d.erase(EVERYWHERE)
113 d.circle(win.center, win.radius)
114 d.line(win.center, calcpoint(win, hours*30 + minutes/2, 0.6))
115 d.line(win.center, calcpoint(win, minutes*6, 1.0))
116 str = "%02d:%02d" % (hours, minutes)
117 p = (win.width - d.textwidth(str))/2, win.height * 3 / 4
118 d.text(p, str)
119 if win.set:
120 drawalarm(win, d)
121 drawalarmtime(win, d)
122 if win.ring:
123 d.invert(win.mainarea)
125 def mouseclick(win, type, detail):
126 d = win.begindrawing()
127 if win.ring:
128 # First turn the ringing off
129 win.ring = 0
130 d.invert(win.mainarea)
131 h, v = detail[0]
132 ch, cv = win.center
133 x, y = h-ch, cv-v
134 dist = sqrt(x*x + y*y) / float(win.radius)
135 if dist > 1.2:
136 if win.set:
137 drawalarm(win, d)
138 erasealarmtime(win, d)
139 win.set = 0
140 elif dist < 0.8:
141 if not win.set:
142 win.set = 1
143 drawalarm(win, d)
144 drawalarmtime(win, d)
145 else:
146 # Convert to half-degrees (range 0..720)
147 alpha = atan2(y, x)
148 hdeg = alpha*360.0/pi
149 hdeg = 180.0 - hdeg
150 hdeg = (hdeg + 720.0) % 720.0
151 atime = 5*int(hdeg/5.0 + 0.5)
152 if atime <> win.time or not win.set:
153 if win.set:
154 drawalarm(win, d)
155 erasealarmtime(win, d)
156 win.set = 1
157 win.time = atime
158 drawalarm(win, d)
159 drawalarmtime(win, d)
161 def drawalarm(win, d):
162 p1 = calcpoint(win, float(win.time)/2.0, 1.02)
163 p2 = calcpoint(win, float(win.time)/2.0 - 4.0, 1.1)
164 p3 = calcpoint(win, float(win.time)/2.0 + 4.0, 1.1)
165 d.xorline(p1, p2)
166 d.xorline(p2, p3)
167 d.xorline(p3, p1)
169 def erasealarmtime(win, d):
170 d.erase(win.statusarea)
172 def drawalarmtime(win, d):
173 # win.time is in the range 0..720 with origin at 12 o'clock
174 # Convert to hours (0..12) and minutes (12*(0..60))
175 hh = win.time/60
176 mm = win.time%60
177 str = 'Alarm@%02d:%02d' % (hh, mm)
178 p1 = (win.width - d.textwidth(str))/2, win.height
179 d.text(p1, str)
181 def calcpoint(win, degrees, size):
182 alpha = pi/2.0 - float(degrees) * pi/180.0
183 x, y = cos(alpha), sin(alpha)
184 h, v = win.center
185 r = float(win.radius)
186 return h + int(x*size*r), v - int(y*size*r)
188 def calctime(now):
189 hours, minutes, seconds = time.localtime(now)[3:6]
190 hours = hours % 12
191 return hours, minutes, seconds
193 main()