File: pygadgets-products/unzipped/_PyClock/Clock/clock.py
"""
###############################################################################
PyClock 3.0: a clock GUI in Python/tkinter.
[SA] Sep-2017: Standalone release of PyCalc, PyClock, PyPhoto, PyToe.
Copyright 2017 M.Lutz, from book "Programming Python, 4th Edition".
License: provided freely, but with no warranties of any kind.
With both analog and digital display modes, a pop-up date label, clock face
images, general resizing, countdown loops, etc. May be run both standalone,
or embedded (attached) in other GUIs that need a clock display.
New in 3.0.1 [SA2]:
-Fix to update min and hour hands immediately after a deiconify, switch
to analog display, resume from suspend, and some dialog/menu views -- not
only at seconds=12; else, update delayed too long and time is inaccurate
-Avoid updating analog ampm label every second just like min/hour hands;
this seems to have further reduced the memory leak while open on Macs:
open-window growth is now just 1M/20mins, 3M/hour, 72M/day, and 500M/week;
this is roughly half what it was, and some popular Mac apps do worse
-Verify that no attrs here clash with those in tkinter's classes (which
use "_x" for all but methods, 'tk', 'master', 'children', 'widgetName',
and 2 'name*' oddballs.); this was further/futile memory-leak research
New in 3.0 [SA]:
-Mac OS port
-Add '?' help
-Use new configs model
-Build standalone app+exes
-Use PIL/Pillow if present
-Cache the background/center image for speed
-Work around a memory leak on Mac OS (see onUpdate() and ./MacMemoryLeak/)
-Save cpu/memory in general by skipping redraw while minimized everywhere
TBD: auto restart after N days on Macs to address gradual memory leak?
TBD: DemoMode (like PyToe) that starts clockStyles.py script/exe?
TBD: digital mode now leaks more memory than analog when open; why?
New in 2.1: updated to run under Python 3.X (2.X no longer supported).
New in 2.0: s/m keys set seconds/minutes timer for pop-up msg; window icon.
TBD: requiring PIL or Tk 8.6+ would allow other types of images besides GIF;
in 3.0, both are now used is present (e.g., apps), but are not required.
###############################################################################
"""
from tkinter import *
from tkinter.simpledialog import askinteger
from tkinter.messagebox import showinfo
import math, time, sys, os
# show versions
print('Using Python %s, Tk %s' % (sys.version.split()[0], TkVersion))
# monitor updates
trace = lambda *args: None # or print
RunningOnMac = sys.platform.startswith('darwin') # [SA] Mac port
RunningOnWindows = sys.platform.startswith('win') # [SA] Win backport
RunningOnLinux = sys.platform.startswith('linux') # and so on
# [SA] use PIL iff present (but disable PNG on Macs due to memory leak)
pillowwarning = """
Pillow 3rd-party package is not installed.
...This package is optional, but required for the clock images feature when
...using some image file types and Pythons (though not for GIFs with any Python,
...or PNGs with Pythons that use Tk 8.6 or later, including standard Windows
...installs of Python 3.4+). Pillow home: https://pypi.python.org/pypi/Pillow.
"""
try:
from PIL.ImageTk import PhotoImage # replace tkinter's version
except ImportError:
from tkinter import PhotoImage # else use the basic lib version
print(pillowwarning)
# but continue: falls back on Tk/tkinter's native PhotoImage, for PNGs, GIFs, etc.
# Pillow install is required for source-code only: apps/exes have it "baked in"
# [SA]: set window icons on Windows and Linux
from windowicons import trySetWindowIcon
# [SA] try to limit memory leaks? - no: see onUpdate()
import gc
#gc.set_debug(gc.DEBUG_LEAK)
def defaultImage():
"[SA] get image path in either standalone or PyGadgets context"
if os.path.isdir('images'):
# when CWD is my dir
return 'images/PyGadgets1024_128x128.gif' # '/' okay on Windows
elif os.path.isdir('_PyClock'):
# when CWD is PyGadgets dir
return '_PyClock/Clock/images/PyGadgets1024_128x128.gif'
else:
return None # bail nicely (no image displayed)
###############################################################################
# Option configuration classes and arg parser
###############################################################################
# [SA] DEFUNCT: now loaded from file or args
###############################################################################
# Digital display object
###############################################################################
class DigitalDisplay(Frame):
"""
Digital display mode (too simple to doc much)
"""
def __init__(self, parent, cfg): # [SA] tbd: help here too?
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.BgColor,
fg=cfg.DigitalFgColor or cfg.FgColor)
label.config(font=cfg.DigitalFont) # [SA] new digital configs
label.pack(side=LEFT) # TBD: could expand, and scale font on resize
# [SA] yes, make frame expandable; else tiny if window grows for digital
for widget in (self, self.hour, self.mins, self.secs, self.ampm):
widget.pack(expand=YES, fill=BOTH)
def onUpdate(self, hour, mins, secs, ampm, timenow, cfg):
"""
On timer update in parent Clock object, change time labels.
[SA] No longer called if window minimized, to save cpu/memory.
[SA2] TBD: should these avoid redraws unless secs=12 too?
"""
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):
"""
Draw analog clock at startup, to be shown/hidden by user on request.
A Canvas, within a Clock Frame (parent), within a Tk or Toplevel window.
This in turn nests a cached PhotoImage object, if one is being used.
"""
self.size = int(cfg.InitialSize.split('x')[0]) # [SA] one: square
Canvas.__init__(self, parent,
width=self.size, height=self.size, bg=cfg.BgColor)
self.parent = parent # Clock Frame
self.image = None
self.lastredrawtime = 0.0
self.drawClockface(cfg)
self.cog = None
# [SA] and wait for first unUpdate() to draw clock hands
def drawClockface(self, cfg):
"""
On start and resize (not timer): draw ovals+picture on empty canvas.
This is followed by an onUpdated() run from the timer loop logic.
"""
self.loadImage(cfg)
if self.image != None:
imgx = (self.size - self.image.width()) // 2 # center it
imgy = (self.size - self.image.height()) // 2 # 3.x // div
self.create_image(imgx+1, imgy+1, anchor=NW, image=self.image)
originX = originY = radius = self.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.FgColor) # 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.FgColor) # hours
self.ampm = self.create_text(3, 3, anchor=NW, fill=cfg.FgColor)
# [SA] add help popup via '?' click; see Clock.makeWidgets
help = self.create_text(self.size, 3, text='?', anchor=NE, fill=cfg.FgColor)
self.tag_bind(help, '<Button-1>', lambda event: self.parent.onHelp(click=True))
"""
[SA] this didn't work on Windows, and looked odd anyhow
help = Button(self, text='?', command=self.parent.onHelp, fg=cfg.FgColor)
self.create_window(self.size+1, 0, window=help, anchor=NE)
"""
def point(self, tick, units, radius, originX, originY):
"""
The geometry bit (see your favorite math textbook).
"""
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 loadImage(self, cfg):
"""
Load analog background image (if any) just once.
[SA] Factored off to cache, and make this more robust and explicit.
[SA] Substitue a default for PNGs on Mac to avoid a memory leak in
the both Tk 8.5 and 8.6 libs; see onUpdate() and ./MacMemoryLeak/.
"""
if (cfg.PictureFile and
cfg.PictureFile.lower().endswith('.png') and
RunningOnMac):
self.after_idle(lambda:
showinfo('PyClock: PNG Replaced',
'Your analog clockface image is being replaced with a '
'default, because PyClock disallows PNGs on Mac OS to '
'prevent a memory leak in the Tk GUI library.\n'
'\n'
'Please update your configurations file or arguments to '
'use a non-PNG image in the future. You can use the PyPhoto '
'gadget or Mac\'s Preview app to convert and resize images '
'for use in PyClock.'
))
cfg.PictureFile = defaultImage()
if cfg.PictureFile and self.image == None: # configured + not loaded?
try:
self.image = PhotoImage(file=cfg.PictureFile) # try first
except:
try:
self.image = BitmapImage(file=cfg.PictureFile) # save ref
except:
pass
return self.image
def onUpdate(self, hour, mins, secs, ampm, timenow, cfg):
"""
On timer update callback: redraw three clock hands and cog.
Run each second by the after() timer-loop logic in the parent
object's Clock.onTimer(), *unless* the window is minimized,
or the digital-mode object is currently displayed.
[SA] Recoded to just _move_ hands on updates 2..N by changing their
coords(), instead of deleting and recreating their objects each time.
Subtlety: this must also recreate hands after resize's delete('all').
To save cpu/memory, also no longer called if the window is minimized,
and avoids moving min+hour hands except on minute rollovers, and all
redisplays of a formerly idle or hidden analog clock (see SA2 below).
Moves are likely more efficient, but this was initially an attempt to
fix a memory leak on Macs that proved futile. The leak was determined
later to be a Tk bug when using PNG images only, and PNGs are now fully
disallowed on Macs. Other leak-fix attempts (forced gc.collect(), and
earlier recodings to erase and redraw both 'all' and the entire Canvas)
also failed; "batteries included" and "software stacks" are a mixed bag.
"""
#gc.collect() # [SA] had no effect, and used .x% more cpu
"""
# [SA] change coords instead of deleting+recreating
if self.cog:
self.delete(self.cog)
self.delete(self.hourHand) # erase prior hands
self.delete(self.minsHand)
self.delete(self.secsHand)
"""
originX = originY = radius = self.size // 2 # 3.x div
hour = hour + (mins / 60.0) # between points
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)
if not self.cog:
#
# [SA] create lines on first update and resizes (original code)
#
self.hourHand = self.create_line(originX, originY, hx, hy,
width=(self.size * .04),
arrow='last', arrowshape=(25,25,15), fill=cfg.HhColor)
self.minsHand = self.create_line(originX, originY, mx, my,
width=(self.size * .03),
arrow='last', arrowshape=(20,20,10), fill=cfg.MhColor)
self.secsHand = self.create_line(originX, originY, sx, sy,
width=1,
arrow='last', arrowshape=(5,10,5), fill=cfg.ShColor)
cogsz = self.size * .01
self.cog = self.create_oval(originX-cogsz, originY+cogsz,
originX+cogsz, originY-cogsz, fill=cfg.CogColor)
self.dchars(self.ampm, 0, END)
self.insert(self.ampm, END, ampm) # update am/pm text
trace('created hands:', self.secsHand, (originX, originY), (sx, sy))
else:
#
# [SA] move lines by changing their coords on later updates (new scheme)
#
if (secs == 0) or (timenow > self.lastredrawtime + 1.5):
#
# [SA] optimization: update hour+min+ampm only at min rollover;
# this reduces both cpu use everywhere and memory use on Macs;
#
# [SA2] _but_ also do so on the first timer update after window
# deiconify, switch from digital to analog display modes, resume
# after system suspend, some menu and modal-dialog views on some
# platforms, and any other state that precludes clock updates;
# else won't redraw till secs hand reaches twelve (a former bug);
#
# subltle bits: setting self.cog=None won't suffice, because
# prior hands are erased on resize only; display-mode switches
# run resize (forcing creates) but only until first manual resize;
# unlike flags, comparing new-to-last update time handles all cases,
# even those without portable Tk events like system suspend/resume;
#
self.coords(self.hourHand, (originX, originY, hx, hy))
self.coords(self.minsHand, (originX, originY, mx, my))
self.dchars(self.ampm, 0, END)
self.insert(self.ampm, END, ampm) # update am/pm text
trace('updated hour+min+ampm')
self.coords(self.secsHand, (originX, originY, sx, sy)) # last=top
trace('changed hands:', self.secsHand, (originX, originY), (sx, sy))
# [SA2] per docs above
self.lastredrawtime = timenow
def onResize(self, newWidth, newHeight, cfg):
"""
On user resize of window, redraw clock face at new size.
onUpdate will be run on next second to redraw clock hands.
"""
newSize = min(newWidth, newHeight)
if newSize != self.size+4:
self.size = newSize-4 # 4 for canvas border
self.delete('all') # erase all canvas objects
self.drawClockface(cfg)
self.cog = None
# to be followed by next onUpdate() for hands
###############################################################################
# Clock composite object
###############################################################################
ChecksPerSec = 10 # second change timer
class Clock(Frame):
"""
A custom Frame, with embedded AnalogDisplay and DigitalDisplay objects.
Nested in a Tk or Toplevel (parent), and AnalogDisplay embeds an image.
"""
def __init__(self, configs=object(), parent=None):
Frame.__init__(self, parent)
self.parent = parent # [SA] Tk or Toplevel
self.cfg = configs
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) # flip to draw analog now
self.onToggleLabel(None) # [SA] label starts on
self.onTimer()
def makeWidgets(self, parent):
"""
Make widgets, bind global actions (analog canvas also binds help click).
[SA] Label now uses new config or default colors (not red/blue from PP4E).
"""
self.digitalDisplay = DigitalDisplay(self, self.cfg)
self.analogDisplay = AnalogDisplay(self, self.cfg)
self.dateLabel = Label(self, bd=3)
self.justClickedHelp = False
# [SA] keys: same on all platforms
parent.bind('<KeyPress-x>', self.onSwitchMode)
parent.bind('<KeyPress-d>', self.onToggleLabel)
parent.bind('<KeyPress-s>', self.onCountdownSec)
parent.bind('<KeyPress-m>', self.onCountdownMin)
# [SA] question=? but portable, help key in all gadgets
parent.bind('<KeyPress-question>', lambda event: self.onHelp())
# [SA] clicks: platform-specific
if not RunningOnMac:
# original code: Windows+Linux; left-click B1 also fires when
# the new help text is clicked, and returning 'break' doesn't
# work for tag_bind, so we have to set and check a flag
#
parent.bind('<ButtonPress-1>', self.onSwitchMode) # leftclick
parent.bind('<ButtonPress-3>', self.onToggleLabel) # rightclick
else:
# on Mac OS, can't bind B1 to root window, else also fires
# on border clicks (e.g., window moves); also, use B2 instead
# of B3 for right-click, and allow Control-B1 as a synonym;
#
parent.bind('<ButtonPress-2>', self.onToggleLabel) # rightlick
parent.bind('<Control-ButtonPress-1>', self.onToggleLabel) # rightclick
# redraw analog on user resize
parent.bind('<Configure>', self.onResize)
def onSwitchMode(self, event):
"""
[SA] This is also fired on non-Macs when analog's "?" help text
is clicked, and returning "break" doesn't work to cancel other
bindings in tag_bind callbacks: set and check a state flag.
"""
trace('onSwitchMode', self.display.__class__.__name__)
if self.justClickedHelp and not RunningOnMac:
self.justClickedHelp = False
return
# the normal bits
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):
"""
Show or hide the date label at window bottom.
This is external to the analog/digitl displays.
"""
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):
"""
Check widget match, delegate resize to display object.
[SA] A return 'break' here doesn't stop B1 events on Mac.
"""
if event.widget == self.display:
trace('onResize', event.widget.__class__.__name__)
self.display.onResize(event.width, event.height, self.cfg)
def onTimer(self):
"""
On timer event: check second rollover, delegate redraw to display object.
Checks for rollover N times per second to avoid display stuttering.
time.time() is seconds since the epoch, as a floating point number.
Caveat: both Tk's widget.after() and Python's time.time() are vulnerable
to user changes to the system's clock. This can hang PyClock: restart.
"""
secsSinceEpoch = time.time()
timeTuple = time.localtime(secsSinceEpoch)
hour, min, sec = timeTuple[3:6]
if sec != self.lastSec:
self.lastSec = sec
if self.parent.force_state == 'Hidden':
# [SA] skip update if minimized to save cpu+memory
trace('skipped redraw')
else:
# Visible state: redraw
trace('redrawing')
ampm = ((hour >= 12) and 'PM') or 'AM' # 0...23
hour = (hour % 12) or 12 # 12..11
# update current display
self.display.onUpdate(hour, min, sec, ampm, secsSinceEpoch, self.cfg)
# [SA] better time-label format
#self.dateLabel.config(text=time.ctime(secsSinceEpoch))
timeFormat = '%a %b %d, %Y %X'
self.dateLabel.config(text=time.strftime(timeFormat, timeTuple))
# check countdown timer
self.countdownSeconds -= 1
if self.countdownSeconds == 0:
self.onCountdownExpire() # post alarm notice now
self.after(1000 // ChecksPerSec, self.onTimer) # run N times per second
# 3.x // trunc int div
def onCountdownSec(self, event):
secs = askinteger('PyClock Alarm', 'Seconds?') # self-validating
if secs:
self.countdownSeconds = secs # new sole timer
def onCountdownMin(self, event):
secs = askinteger('PyClock Alarm', 'Minutes') # self-validating
if secs:
self.countdownSeconds = secs * 60 # new sole timer
def onCountdownExpire(self):
"""
Display an expired-timer message fullscreen for attention.
Caveat: only one active user timer, no progress indicator.
"""
win = Toplevel()
win.title('PyClock Alarm')
trySetWindowIcon(win, 'icons', 'pygadgets') # [SA] for win+lin
if RunningOnMac or RunningOnLinux:
# [SA] emulate colored buttons on Mac
# [SA] avoid reversed colors on Linux
msg = Label(win, text='Timer Expired!')
msg.bind('<Button-1>', lambda e: win.destroy())
else:
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 RunningOnWindows or RunningOnMac: # go full screen mode
win.state('zoomed')
elif RunningOnLinux: # [SA] works on Mac too
win.wm_attributes('-fullscreen', 1)
def onHelp(self, click=False):
"""
[SA] Analog clock display's new '?' click help callback;
also run for '?' keypress and Mac menus in both modes.
For Canvas tag_bind clicks only, set a flag to skip global
click event next in onSwitchMode(); unlike other contexts,
a return 'break' here doesn't stop B1 events for tag_bind.
On Macs only, the display skips time updates during Help;
must redraw all clock hands when any modal dialog is closed.
"""
# [SA] ignore next click event
self.justClickedHelp = click
from helpmessage import showhelp
showhelp(self.parent, 'PyClock', self.HelpText, forcetext=False,
setwinicon=lambda win:
trySetWindowIcon(win, 'icons', 'pygadgets'))
#if self.parent: self.parent.focus_force() # now done in helpmessage
HelpText = ('PyClock 3.0\n'
'\n'
'A Python/tkinter clock GUI.\n'
'For Mac OS, Windows, and Linux.\n'
'From the book Programming Python.\n'
'Author and © M. Lutz 2001-2017.\n'
'\n'
'Usage:\n'
'▶ Key "X" switches between analog and digital '
'(or leftclick on non-Macs)\n'
'▶ Key "D" shows/hides date (or rightclick, '
'Mac 2-finger press or control+click)\n'
'▶ Keys "S" and "M" set the seconds or minutes alarm '
'timer, respectively\n'
'▶ Key "?" shows this help (the same as clicking the '
'analog display\'s "?")\n'
'▶ Resizing the window resizes the current clock display.\n'
'\n'
'Clock photos: analog clocks in app and executable '
'PyClocks support most image types. For source-code, '
'GIF always works, PNG works for Tk 8.6+, and all types '
'work after Pillow install (see README.txt).\n'
'\n'
'Mac OS users: use a GIF or JPEG for clocks; PNGs cause '
'rapid memory leaks in the Mac\'s Tk library. You can also '
'limit memory use in general by minimizing PyClock to the '
'Dock when not in use (see README.txt).\n'
'\n'
'On all platforms: minimizing PyClock may reduce the amount '
'of CPU resources it consumes, as display updates are '
'skipped.\n'
'\n'
'Version history:\n'
'● 3.0: Sep 2017, standalone release\n'
'● 2.1: May 2010, Programming Python 4E\n'
'● 2.0: 2006 PP3E, 1.0: 2001 PP2E\n'
'\n'
'For downloads and more apps, visit:\n'
'http://learning-python.com/programs.html'
)
###############################################################################
# Standalone clocks
###############################################################################
appname = 'PyClock 3.0'
# use custom Tk, Toplevel for icons, etc.
from PP4E.Gui.Tools.windows import PopupWindow, MainWindow
class ForceState:
"""
Mac Tk doesn't set win.state() to 'iconic' when in Dock: do manually,
by catching window iconify/deiconify events to track visibility state.
Needed to disable update/redraw when clock minimized to save cpu+memory.
Caveat: in Tk versions tested, <Map> and <Unmap> do not fire on Linux
(except once, to set state to Visible), and Linux Tk also fails to set
win.state() like Mac Tk. Only Windows Tk both fires <Map>/<Unmap> and
sets state() correctly (to normal or iconic). Luckily, this is also a
moot point: on Linux, CPU is 0% and memory doesn't grow in any state.
"""
def __init__(self):
self.force_state = None
self.bind('<Map>', lambda event: self.setState(event, 'Visible'))
self.bind('<Unmap>', lambda event: self.setState(event, 'Hidden'))
def setState(self, event, what):
if event.widget == self:
trace('setting state to', what)
self.force_state = what # enable/ disable redraws
class ClockMain(MainWindow, ForceState):
"""
A custom Tk, with an attached Clock Frame, which embeds a Canvas.
"""
def __init__(self, configs=object(), name=''):
MainWindow.__init__(self, appname, name)
ForceState.__init__(self)
clock = Clock(configs, self)
clock.pack(expand=YES, fill=BOTH)
self.clock = clock
class ClockPopup(PopupWindow, ForceState):
"""
A custom Toplevel, with an attached Clock Frame, , which embeds a Canvas.
"""
def __init__(self, configs=object(), name=''):
PopupWindow.__init__(self, appname, name)
ForceState.__init__(self)
clock = Clock(configs, self)
clock.pack(expand=YES, fill=BOTH)
self.clock = clock
class ClockWindow(Clock, ForceState):
"""
B/W compat: manual window borders, passed-in parent.
"""
def __init__(self, config=object(), parent=None, name=''):
Clock.__init__(self, configs, parent)
ForceState.__init__(self)
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__':
from getConfigs import getConfigs # [SA] common gadgets utility
defaults = dict(InitialSize='240x240',
DigitalFont=None, # default family and size
DigitalFgColor=None, # None=FgColor
BgColor='beige', # canvas
FgColor='brown', # ticks
HhColor='black', # hour hand
MhColor='navy', # minute hand
ShColor='blue', # second hand
CogColor='white' , # center point
PictureFile=defaultImage()) # middle photo path (or None)
configs = getConfigs('PyClock', defaults) # load from file or args
# alternatives
#myclock = ClockWindow(Tk(), configs)
#myclock = ClockPopup('popup', configs)
# parent is Tk root if standalone
myclock = ClockMain(configs)
trySetWindowIcon(myclock, 'icons', 'pygadgets') # [SA] for win+lin
if RunningOnMac:
# Mac requires menus, deiconifies, focus
# [SA] on Mac, customize app-wide automatic top-of-display menu
from guimaker_pp4e import fixAppleMenuBar
fixAppleMenuBar(window=myclock,
appname='PyClock',
helpaction=myclock.clock.onHelp,
aboutaction=None,
quitaction=myclock.quit) # app-wide quit: ask
# [SA] reopen auto on dock/app click and fix tk focus loss on deiconify
def onReopen():
myclock.lift()
myclock.update()
temp = Toplevel()
temp.lower()
temp.destroy()
myclock.createcommand('::tk::mac::ReopenApplication', onReopen)
myclock.mainloop()