]>
Commit | Line | Data |
---|---|---|
1 | # AnalogClock's main class | |
2 | # E. A. Tacao <e.a.tacao |at| estadao.com.br> | |
3 | # http://j.domaindlx.com/elements28/wxpython/ | |
4 | # 15 Fev 2006, 22:00 GMT-03:00 | |
5 | # Distributed under the wxWidgets license. | |
6 | # | |
7 | # For more info please see the __init__.py file. | |
8 | ||
9 | import wx | |
10 | ||
11 | from styles import * | |
12 | from helpers import Dyer, Face, Hand, HandSet, TickSet, Box | |
13 | from setup import Setup | |
14 | ||
15 | #---------------------------------------------------------------------- | |
16 | ||
17 | class AnalogClock(wx.PyWindow): | |
18 | """An analog clock.""" | |
19 | ||
20 | def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, | |
21 | size=wx.DefaultSize, style=wx.NO_BORDER, name="AnalogClock", | |
22 | clockStyle=DEFAULT_CLOCK_STYLE, | |
23 | minutesStyle=TICKS_CIRCLE, hoursStyle=TICKS_POLY): | |
24 | ||
25 | wx.PyWindow.__init__(self, parent, id, pos, size, style, name) | |
26 | ||
27 | # Base size for scale calc purposes. | |
28 | self.basesize = wx.Size(348, 348) | |
29 | ||
30 | # Store some references. | |
31 | self.clockStyle = clockStyle | |
32 | self.minutesStyle = minutesStyle | |
33 | self.hoursStyle = hoursStyle | |
34 | ||
35 | self.DrawHands = self._drawHands | |
36 | self.DrawBox = self._drawBox | |
37 | self.RecalcCoords = self._recalcCoords | |
38 | ||
39 | self.shadowOffset = 3 | |
40 | ||
41 | self.allHandStyles = [SHOW_HOURS_HAND, | |
42 | SHOW_MINUTES_HAND, | |
43 | SHOW_SECONDS_HAND] | |
44 | ||
45 | # Initialize clock face. | |
46 | # | |
47 | # By default we don't use colours or borders on the clock face. | |
48 | bg = self.GetBackgroundColour() | |
49 | face = Face(dyer=Dyer(bg, 0, bg)) | |
50 | ||
51 | # Initialize tick marks. | |
52 | # | |
53 | # TickSet is a set of tick marks; there's always two TickSets defined | |
54 | # regardless whether they're being shown or not. | |
55 | ticksM = TickSet(self, style=minutesStyle, size=5, kind="minutes") | |
56 | ticksH = TickSet(self, style=hoursStyle, size=25, kind="hours", | |
57 | rotate=clockStyle&ROTATE_TICKS) | |
58 | ||
59 | # Box holds the clock face and tick marks. | |
60 | self.Box = Box(self, face, ticksM, ticksH) | |
61 | ||
62 | # Initialize hands. | |
63 | # | |
64 | # HandSet is the set of hands; there's always one HandSet defined | |
65 | # regardless whether hands are being shown or not. | |
66 | # | |
67 | # A 'lenfac = 0.95', e.g., means that the lenght of that hand will | |
68 | # be 95% of the maximum allowed hand lenght ('nice' maximum lenght). | |
69 | handH = Hand(size=7, lenfac=0.7) | |
70 | handM = Hand(size=5, lenfac=0.95) | |
71 | handS = Hand(size=1, lenfac=0.95) | |
72 | self.Hands = HandSet(self, handH, handM, handS) | |
73 | ||
74 | # Create the customization dialog. | |
75 | self.Setup = None | |
76 | ||
77 | # Make a context menu. | |
78 | popup1 = wx.NewId() | |
79 | popup2 = wx.NewId() | |
80 | cm = self.cm = wx.Menu() | |
81 | cm.Append(popup1, "Customize...") | |
82 | cm.Append(popup2, "About...") | |
83 | ||
84 | # Set event handlers. | |
85 | self.Bind(wx.EVT_SIZE, self._OnSize) | |
86 | self.Bind(wx.EVT_PAINT, self._OnPaint) | |
87 | self.Bind(wx.EVT_ERASE_BACKGROUND, lambda evt: None) | |
88 | self.Bind(wx.EVT_TIMER, self._OnTimer) | |
89 | self.Bind(wx.EVT_WINDOW_DESTROY, self._OnDestroyWindow) | |
90 | self.Bind(wx.EVT_CONTEXT_MENU, self._OnContextMenu) | |
91 | self.Bind(wx.EVT_MENU, self._OnShowSetup, id=popup1) | |
92 | self.Bind(wx.EVT_MENU, self._OnShowAbout, id=popup2) | |
93 | ||
94 | # Set initial size based on given size, or best size | |
95 | self.SetBestFittingSize(size) | |
96 | ||
97 | # Do initial drawing (in case there is not an initial size event) | |
98 | self.RecalcCoords(self.GetSize()) | |
99 | self.DrawBox() | |
100 | ||
101 | # Initialize the timer that drives the update of the clock face. | |
102 | # Update every half second to ensure that there is at least one true | |
103 | # update during each realtime second. | |
104 | self.timer = wx.Timer(self) | |
105 | self.timer.Start(500) | |
106 | ||
107 | ||
108 | def DoGetBestSize(self): | |
109 | # Just pull a number out of the air. If there is a way to | |
110 | # calculate this then it should be done... | |
111 | size = wx.Size(50,50) | |
112 | self.CacheBestSize(size) | |
113 | return size | |
114 | ||
115 | ||
116 | def _OnSize(self, evt): | |
117 | size = self.GetClientSize() | |
118 | if size.x < 1 or size.y < 1: | |
119 | return | |
120 | ||
121 | self.RecalcCoords(size) | |
122 | self.DrawBox() | |
123 | ||
124 | ||
125 | def _OnPaint(self, evt): | |
126 | dc = wx.BufferedPaintDC(self) | |
127 | self.DrawHands(dc) | |
128 | ||
129 | ||
130 | def _OnTimer(self, evt): | |
131 | dc = wx.BufferedDC(wx.ClientDC(self), self.GetClientSize()) | |
132 | self.DrawHands(dc) | |
133 | ||
134 | ||
135 | def _OnDestroyWindow(self, evt): | |
136 | self.timer.Stop() | |
137 | del self.timer | |
138 | ||
139 | ||
140 | def _OnContextMenu(self, evt): | |
141 | self.PopupMenu(self.cm) | |
142 | ||
143 | ||
144 | def _OnShowSetup(self, evt): | |
145 | if self.Setup is None: | |
146 | self.Setup = Setup(self) | |
147 | self.Setup.Show() | |
148 | self.Setup.Raise() | |
149 | ||
150 | ||
151 | def _OnShowAbout(self, evt): | |
152 | msg = "AnalogClock\n\n" \ | |
153 | "by Several folks on wxPython-users\n" \ | |
154 | "with enhancements from E. A. Tacao." | |
155 | title = "About..." | |
156 | style = wx.OK|wx.ICON_INFORMATION | |
157 | ||
158 | dlg = wx.MessageDialog(self, msg, title, style) | |
159 | dlg.ShowModal() | |
160 | dlg.Destroy() | |
161 | ||
162 | ||
163 | def _recalcCoords(self, size): | |
164 | """ | |
165 | Recalculates all coordinates/geometry and inits the faceBitmap | |
166 | to make sure the buffer is always the same size as the window. | |
167 | """ | |
168 | ||
169 | self.faceBitmap = wx.EmptyBitmap(*size.Get()) | |
170 | ||
171 | # Recalc all coords. | |
172 | scale = min([float(size.width) / self.basesize.width, | |
173 | float(size.height) / self.basesize.height]) | |
174 | ||
175 | centre = wx.Point(size.width / 2., size.height / 2.) | |
176 | ||
177 | self.Box.RecalcCoords(size, centre, scale) | |
178 | self.Hands.RecalcCoords(size, centre, scale) | |
179 | ||
180 | # Try to find a 'nice' maximum length for the hands so that they won't | |
181 | # overlap the tick marks. OTOH, if you do want to allow overlapping the | |
182 | # lenfac value (defined on __init__ above) has to be set to | |
183 | # something > 1. | |
184 | niceradius = self.Box.GetNiceRadiusForHands(centre) | |
185 | self.Hands.SetMaxRadius(niceradius) | |
186 | ||
187 | ||
188 | def _drawBox(self): | |
189 | """Draws clock face and tick marks.""" | |
190 | ||
191 | dc = wx.BufferedDC(wx.ClientDC(self), self.GetClientSize()) | |
192 | dc.BeginDrawing() | |
193 | dc.SelectObject(self.faceBitmap) | |
194 | dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.SOLID)) | |
195 | dc.Clear() | |
196 | self.Box.Draw(dc) | |
197 | dc.EndDrawing() | |
198 | ||
199 | ||
200 | def _drawHands(self, dc): | |
201 | """ | |
202 | Draws the face bitmap, created on the last DrawBox call, and | |
203 | clock hands. | |
204 | """ | |
205 | ||
206 | dc.BeginDrawing() | |
207 | dc.DrawBitmap(self.faceBitmap, 0, 0) | |
208 | self.Hands.Draw(dc) | |
209 | dc.EndDrawing() | |
210 | ||
211 | ||
212 | # Public methods -------------------------------------------------- | |
213 | ||
214 | def GetHandSize(self, target=ALL): | |
215 | """Gets thickness of hands.""" | |
216 | ||
217 | return self.Hands.GetSize(target) | |
218 | ||
219 | ||
220 | def GetHandFillColour(self, target=ALL): | |
221 | """Gets fill colours of hands.""" | |
222 | ||
223 | return self.Hands.GetFillColour(target) | |
224 | ||
225 | ||
226 | def GetHandBorderColour(self, target=ALL): | |
227 | """Gets border colours of hands.""" | |
228 | ||
229 | return self.Hands.GetBorderColour(target) | |
230 | ||
231 | ||
232 | def GetHandBorderWidth(self, target=ALL): | |
233 | """Gets border widths of hands.""" | |
234 | ||
235 | return self.Hands.GetBorderWidth(target) | |
236 | ||
237 | ||
238 | def GetTickSize(self, target=ALL): | |
239 | """Gets sizes of ticks.""" | |
240 | ||
241 | return self.Box.GetTickSize(target) | |
242 | ||
243 | ||
244 | ||
245 | def GetTickFillColour(self, target=ALL): | |
246 | """Gets fill colours of ticks.""" | |
247 | ||
248 | return self.Box.GetTickFillColour(target) | |
249 | ||
250 | ||
251 | ||
252 | def GetTickBorderColour(self, target=ALL): | |
253 | """Gets border colours of ticks.""" | |
254 | ||
255 | return self.Box.GetTickBorderColour(target) | |
256 | ||
257 | ||
258 | ||
259 | def GetTickBorderWidth(self, target=ALL): | |
260 | """Gets border widths of ticks.""" | |
261 | ||
262 | return self.Box.GetTickBorderWidth(target) | |
263 | ||
264 | ||
265 | ||
266 | def GetTickPolygon(self, target=ALL): | |
267 | """ | |
268 | Gets lists of points to be used as polygon shapes | |
269 | when using the TICKS_POLY style. | |
270 | """ | |
271 | ||
272 | return self.Box.GetTickPolygon(target) | |
273 | ||
274 | ||
275 | ||
276 | def GetTickFont(self, target=ALL): | |
277 | """ | |
278 | Gets fonts for tick marks when using TICKS_DECIMAL or | |
279 | TICKS_ROMAN style. | |
280 | """ | |
281 | ||
282 | return self.Box.GetTickFont(target) | |
283 | ||
284 | ||
285 | ||
286 | def GetTickOffset(self, target=ALL): | |
287 | """Gets the distance of tick marks for hours from border.""" | |
288 | ||
289 | return self.Box.GetTickOffset(target) | |
290 | ||
291 | ||
292 | ||
293 | def GetFaceFillColour(self): | |
294 | """Gets fill colours of watch.""" | |
295 | ||
296 | return self.Box.Face.GetFillColour() | |
297 | ||
298 | ||
299 | ||
300 | def GetFaceBorderColour(self): | |
301 | """Gets border colours of watch.""" | |
302 | ||
303 | return self.Box.Face.GetBorderColour() | |
304 | ||
305 | ||
306 | ||
307 | def GetFaceBorderWidth(self): | |
308 | """Gets border width of watch.""" | |
309 | ||
310 | return self.Box.Face.GetBorderWidth() | |
311 | ||
312 | ||
313 | ||
314 | def GetShadowColour(self): | |
315 | """Gets the colour to be used to draw shadows.""" | |
316 | ||
317 | a_clock_part = self.Box | |
318 | return a_clock_part.GetShadowColour() | |
319 | ||
320 | ||
321 | ||
322 | def GetClockStyle(self): | |
323 | """Returns the current clock style.""" | |
324 | ||
325 | return self.clockStyle | |
326 | ||
327 | ||
328 | def GetTickStyle(self, target=ALL): | |
329 | """Gets the tick style(s).""" | |
330 | ||
331 | return self.Box.GetTickStyle(target) | |
332 | ||
333 | ||
334 | def Refresh(self): | |
335 | """ | |
336 | Overriden base wx.Window method. Forces an immediate | |
337 | recalculation and redraw of all clock elements. | |
338 | """ | |
339 | ||
340 | size = self.GetClientSize() | |
341 | if size.x < 1 or size.y < 1: | |
342 | return | |
343 | self.Freeze() | |
344 | self.RecalcCoords(size) | |
345 | self.DrawBox() | |
346 | dc = wx.BufferedDC(wx.ClientDC(self), self.GetClientSize()) | |
347 | self.DrawHands(dc) | |
348 | self.Thaw() | |
349 | ||
350 | ||
351 | def SetHandSize(self, size, target=ALL): | |
352 | """Sets thickness of hands.""" | |
353 | ||
354 | self.Hands.SetSize(size, target) | |
355 | ||
356 | ||
357 | def SetHandFillColour(self, colour, target=ALL): | |
358 | """Sets fill colours of hands.""" | |
359 | ||
360 | self.Hands.SetFillColour(colour, target) | |
361 | ||
362 | ||
363 | def SetHandBorderColour(self, colour, target=ALL): | |
364 | """Sets border colours of hands.""" | |
365 | ||
366 | self.Hands.SetBorderColour(colour, target) | |
367 | ||
368 | ||
369 | def SetHandBorderWidth(self, width, target=ALL): | |
370 | """Sets border widths of hands.""" | |
371 | ||
372 | self.Hands.SetBorderWidth(width, target) | |
373 | ||
374 | ||
375 | def SetTickSize(self, size, target=ALL): | |
376 | """Sets sizes of ticks.""" | |
377 | ||
378 | self.Box.SetTickSize(size, target) | |
379 | self.Refresh() | |
380 | ||
381 | ||
382 | def SetTickFillColour(self, colour, target=ALL): | |
383 | """Sets fill colours of ticks.""" | |
384 | ||
385 | self.Box.SetTickFillColour(colour, target) | |
386 | self.Refresh() | |
387 | ||
388 | ||
389 | def SetTickBorderColour(self, colour, target=ALL): | |
390 | """Sets border colours of ticks.""" | |
391 | ||
392 | self.Box.SetTickBorderColour(colour, target) | |
393 | self.Refresh() | |
394 | ||
395 | ||
396 | def SetTickBorderWidth(self, width, target=ALL): | |
397 | """Sets border widths of ticks.""" | |
398 | ||
399 | self.Box.SetTickBorderWidth(width, target) | |
400 | self.Refresh() | |
401 | ||
402 | ||
403 | def SetTickPolygon(self, polygon, target=ALL): | |
404 | """ | |
405 | Sets lists of points to be used as polygon shapes | |
406 | when using the TICKS_POLY style. | |
407 | """ | |
408 | ||
409 | self.Box.SetTickPolygon(polygon, target) | |
410 | self.Refresh() | |
411 | ||
412 | ||
413 | def SetTickFont(self, font, target=ALL): | |
414 | """ | |
415 | Sets fonts for tick marks when using text-based tick styles | |
416 | such as TICKS_DECIMAL or TICKS_ROMAN. | |
417 | """ | |
418 | ||
419 | self.Box.SetTickFont(font, target) | |
420 | self.Refresh() | |
421 | ||
422 | ||
423 | def SetTickOffset(self, offset, target=ALL): | |
424 | """Sets the distance of tick marks for hours from border.""" | |
425 | ||
426 | self.Box.SetTickOffset(offset, target) | |
427 | self.Refresh() | |
428 | ||
429 | ||
430 | def SetFaceFillColour(self, colour): | |
431 | """Sets fill colours of watch.""" | |
432 | ||
433 | self.Box.Face.SetFillColour(colour) | |
434 | self.Refresh() | |
435 | ||
436 | ||
437 | def SetFaceBorderColour(self, colour): | |
438 | """Sets border colours of watch.""" | |
439 | ||
440 | self.Box.Face.SetBorderColour(colour) | |
441 | self.Refresh() | |
442 | ||
443 | ||
444 | def SetFaceBorderWidth(self, width): | |
445 | """Sets border width of watch.""" | |
446 | ||
447 | self.Box.Face.SetBorderWidth(width) | |
448 | self.Refresh() | |
449 | ||
450 | ||
451 | def SetShadowColour(self, colour): | |
452 | """Sets the colour to be used to draw shadows.""" | |
453 | ||
454 | self.Hands.SetShadowColour(colour) | |
455 | self.Box.SetShadowColour(colour) | |
456 | self.Refresh() | |
457 | ||
458 | ||
459 | def SetClockStyle(self, style): | |
460 | """ | |
461 | Set the clock style, according to the options below. | |
462 | ||
463 | ==================== ================================ | |
464 | SHOW_QUARTERS_TICKS Show marks for hours 3, 6, 9, 12 | |
465 | SHOW_HOURS_TICKS Show marks for all hours | |
466 | SHOW_MINUTES_TICKS Show marks for minutes | |
467 | ||
468 | SHOW_HOURS_HAND Show hours hand | |
469 | SHOW_MINUTES_HAND Show minutes hand | |
470 | SHOW_SECONDS_HAND Show seconds hand | |
471 | ||
472 | SHOW_SHADOWS Show hands and marks shadows | |
473 | ||
474 | ROTATE_TICKS Align tick marks to watch | |
475 | OVERLAP_TICKS Draw tick marks for minutes even | |
476 | when they match the hours marks. | |
477 | ==================== ================================ | |
478 | """ | |
479 | ||
480 | self.clockStyle = style | |
481 | self.Box.SetIsRotated(style & ROTATE_TICKS) | |
482 | self.Refresh() | |
483 | ||
484 | ||
485 | def SetTickStyle(self, style, target=ALL): | |
486 | """ | |
487 | Set the tick style, according to the options below. | |
488 | ||
489 | ================= ====================================== | |
490 | TICKS_NONE Don't show tick marks. | |
491 | TICKS_SQUARE Use squares as tick marks. | |
492 | TICKS_CIRCLE Use circles as tick marks. | |
493 | TICKS_POLY Use a polygon as tick marks. A | |
494 | polygon can be passed using | |
495 | SetTickPolygon, otherwise the default | |
496 | polygon will be used. | |
497 | TICKS_DECIMAL Use decimal numbers as tick marks. | |
498 | TICKS_ROMAN Use Roman numbers as tick marks. | |
499 | TICKS_BINARY Use binary numbers as tick marks. | |
500 | TICKS_HEX Use hexadecimal numbers as tick marks. | |
501 | ================= ====================================== | |
502 | """ | |
503 | ||
504 | self.Box.SetTickStyle(style, target) | |
505 | self.Refresh() | |
506 | ||
507 | ||
508 | def SetBackgroundColour(self, colour): | |
509 | """Overriden base wx.Window method.""" | |
510 | ||
511 | wx.Window.SetBackgroundColour(self, colour) | |
512 | self.Refresh() | |
513 | ||
514 | ||
515 | def SetForegroundColour(self, colour): | |
516 | """ | |
517 | Overriden base wx.Window method. This method sets a colour for | |
518 | all hands and ticks at once. | |
519 | """ | |
520 | ||
521 | wx.Window.SetForegroundColour(self, colour) | |
522 | self.SetHandFillColour(colour) | |
523 | self.SetHandBorderColour(colour) | |
524 | self.SetTickFillColour(colour) | |
525 | self.SetTickBorderColour(colour) | |
526 | self.Refresh() | |
527 | ||
528 | ||
529 | def SetWindowStyle(self, *args, **kwargs): | |
530 | """Overriden base wx.Window method.""" | |
531 | ||
532 | size = self.GetSize() | |
533 | self.Freeze() | |
534 | wx.Window.SetWindowStyle(self, *args, **kwargs) | |
535 | self.SetSize((10, 10)) | |
536 | self.SetSize(size) | |
537 | self.Thaw() | |
538 | ||
539 | ||
540 | def SetWindowStyleFlag(self, *args, **kwargs): | |
541 | """Overriden base wx.Window method.""" | |
542 | ||
543 | self.SetWindowStyle(*args, **kwargs) | |
544 | ||
545 | ||
546 | # For backwards compatibility ----------------------------------------- | |
547 | ||
548 | class AnalogClockWindow(AnalogClock): | |
549 | """ | |
550 | A simple derived class that provides some backwards compatibility | |
551 | with the old analogclock module. | |
552 | """ | |
553 | def SetTickShapes(self, tsh, tsm=None): | |
554 | self.SetTickPolygon(tsh) | |
555 | ||
556 | def SetHandWeights(self, h=None, m=None, s=None): | |
557 | if h: | |
558 | self.SetHandSize(h, HOUR) | |
559 | if m: | |
560 | self.SetHandSize(m, MINUTE) | |
561 | if s: | |
562 | self.SetHandSize(h, SECOND) | |
563 | ||
564 | def SetHandColours(self, h=None, m=None, s=None): | |
565 | if h and not m and not s: | |
566 | m=h | |
567 | s=h | |
568 | if h: | |
569 | self.SetHandBorderColour(h, HOUR) | |
570 | self.SetHandFillColour(h, HOUR) | |
571 | if m: | |
572 | self.SetHandBorderColour(m, MINUTE) | |
573 | self.SetHandFillColour(m, MINUTE) | |
574 | if s: | |
575 | self.SetHandBorderColour(h, SECOND) | |
576 | self.SetHandFillColour(h, SECOND) | |
577 | ||
578 | def SetTickColours(self, h=None, m=None): | |
579 | if not m: | |
580 | m=h | |
581 | if h: | |
582 | self.SetTickBorderColour(h, HOUR) | |
583 | self.SetTickFillColour(h, HOUR) | |
584 | if m: | |
585 | self.SetTickBorderColour(m, MINUTE) | |
586 | self.SetTickFillColour(m, MINUTE) | |
587 | ||
588 | def SetTickSizes(self, h=None, m=None): | |
589 | if h: | |
590 | self.SetTickSize(h, HOUR) | |
591 | if m: | |
592 | self.SetTickSize(h, MINUTE) | |
593 | ||
594 | def SetTickFontss(self, h=None, m=None): | |
595 | if h: | |
596 | self.SetTickFont(h, HOUR) | |
597 | if m: | |
598 | self.SetTickFont(h, MINUTE) | |
599 | ||
600 | ||
601 | def SetMinutesOffset(self, o): | |
602 | pass | |
603 | ||
604 | def SetShadowColour(self, s): | |
605 | pass | |
606 | ||
607 | def SetWatchPenBrush(self, p=None, b=None): | |
608 | if p: | |
609 | self.SetFaceBorderColour(p.GetColour()) | |
610 | self.SetFaceBorderWidth(p.GetWidth()) | |
611 | if b: | |
612 | self.SetFaceFillColour(b.GetColour()) | |
613 | ||
614 | def SetClockStyle(self, style): | |
615 | style |= SHOW_HOURS_HAND|SHOW_MINUTES_HAND|SHOW_SECONDS_HAND | |
616 | AnalogClock.SetClockStyle(self, style) | |
617 | ||
618 | def SetTickStyles(self, h=None, m=None): | |
619 | if h: | |
620 | self.SetTickStyle(h, HOUR) | |
621 | if m: | |
622 | self.SetTickStyle(h, MINUTE) | |
623 | ||
624 | ||
625 | # Test stuff ---------------------------------------------------------- | |
626 | ||
627 | if __name__ == "__main__": | |
628 | print wx.VERSION_STRING | |
629 | ||
630 | class AcDemoApp(wx.App): | |
631 | def OnInit(self): | |
632 | frame = wx.Frame(None, -1, "AnalogClock", size=(375, 375)) | |
633 | clock = AnalogClock(frame) | |
634 | frame.CentreOnScreen() | |
635 | frame.Show() | |
636 | return True | |
637 | ||
638 | acApp = AcDemoApp(0) | |
639 | acApp.MainLoop() | |
640 | ||
641 | ||
642 | # | |
643 | ## | |
644 | ### eof |