""" ############################################################################### Classes that encapsulate top-level interfaces. Allows same GUI to be main, pop-up, or attached; content classes may inherit from these directly, or be mixed together with them per usage mode; may also be called directly without a subclass; designed to be mixed in after (further to the right than) app-specific classes: else, subclass gets methods here (destroy, okayToQuit), instead of from app-specific classes--can't redefine. ############################################################################### """ import os, glob, sys from tkinter import Tk, Toplevel, Frame, PhotoImage, YES, BOTH, RIDGE from tkinter.messagebox import showinfo, askyesno class _window: """ ------------------------------------------------------------------- mixin shared by main and pop-up windows ------------------------------------------------------------------- """ # share by all instances, may be reset # Dec2015: support Linux app bar icons, not just Windows icons # May2017: Mac uses icons alot, but requires a full app bundle foundicon = None iconpatt = '*.ico' if sys.platform.startswith('win') else '*.gif' iconmine = 'py.ico' if sys.platform.startswith('win') else 'py.gif' def configBorders(self, app, kind, iconfile): if not iconfile: # no icon passed? iconfile = self.findIcon() # try curr,tool dirs title = app if kind: title += ' - ' + kind self.title(title) # on window border self.iconname(app) # when minimized if iconfile: try: if sys.platform.startswith('win'): # Windows all contexts self.iconbitmap(iconfile) # window border icon elif sys.platform.startswith('linux'): imgobj = PhotoImage(file=iconfile) # Dec2015: try Linux self.iconphoto(True, imgobj) # app bar icon self.saveiconimg = imgobj # still need a ref? elif sys.platform.startswith('darwin') or True: raise NotImplementedError # [4.0] Sep2016: Mac OS X # Mac TBD, neither yet except Exception as why: pass # bad file or platform self.protocol('WM_DELETE_WINDOW', self.quit) # don't close silently def findIcon(self): if _window.foundicon: # already found one? return _window.foundicon iconfile = None # try curr dir first iconshere = glob.glob(self.iconpatt) # assume just one if iconshere: # del icon for red Tk iconfile = iconshere[0] else: # try tools dir icon """ # [4.0] see windows-notes.txt: this is overkill mymod = __import__(__name__) # import self for dir path = __name__.split('.') # poss a package path for mod in path[1:]: # follow path to end mymod = getattr(mymod, mod) # only have leftmost mydir = os.path.dirname(mymod.__file__) """ mydir = os.path.dirname(__file__) # import not needed! myicon = os.path.join(mydir, self.iconmine) # use myicon, not tk if os.path.exists(myicon): iconfile = myicon _window.foundicon = iconfile # don't search again return iconfile class MainWindow(Tk, _window): """ ------------------------------------------------------------------- when run in main top-level window note that destroy() on the sole/last Tk quits the entire aplication, and is the same as a quit() on any widget; Jan-2017: add quit info, for extra details text; used by PyMailGUI to note number of windows with unsaved changes; Feb-2017: add onNo quit callback because caller can't know if return signals 'No' reply or not okayToQuit(); now used by PyMailGUI to lift windows with unsaved changes; Apr-2017: add withdraw to hide while building to avoid flashes; caller must deiconify() later to show window; ------------------------------------------------------------------- """ def __init__(self, app, kind='', iconfile=None, withdraw=False): self.findIcon() Tk.__init__(self) if withdraw: self.withdraw() # [Apr17] see above self.__app = app self.configBorders(app, kind, iconfile) def quit(self, info='', onNo=lambda: None): if self.okayToQuit(): # threads running? prompt = 'Verify Program Exit?' # [Jan17] add info if askyesno(self.__app, info + prompt): self.destroy() # quit whole app else: onNo() # [Feb17] add onNo else: showinfo(self.__app, 'Quit not allowed') # or in okayToQuit? def destroy(self): # exit app silently Tk.quit(self) # redef if exit ops def okayToQuit(self): # redef me if used return True # e.g., thread busy class PopupWindow(Toplevel, _window): """ ------------------------------------------------------------------- when run in secondary pop-up window Jan-2017: because the original version of this class uses no explicit parent (master) arg #1, the root Tk() (possibly a default) that endures for the full program's run is the new window's parent; this ensures that the new window isn't silently closed along with a transient parent; if you require popup auto-closure with a transient parent, pass the parent to the new closewith argument; Feb-2017: added onNo callback for Quit, because caller may need to process window without knowing if it has been closed or not; used by PyMailGUI to refocus on text area after dialog closures; Apr-2017: add withdraw to hide while building to avoid flashes; at startup caller must deiconify() later to unhide/show window; ------------------------------------------------------------------- """ def __init__(self, app, kind='', iconfile=None, closewith=None, withdraw=False): if not closewith: Toplevel.__init__(self) # original, default else: Toplevel.__init__(self, closewith) # [Jan17] see above if withdraw: self.withdraw() # [Apr17] see above self.__app = app self.configBorders(app, kind, iconfile) def quit(self, info='', onNo=lambda: None): # redef me to change prompt = 'Verify Window Close?' # [Jan17] add info if askyesno(self.__app, info + prompt): # or call destroy self.destroy() # quit this window else: onNo() # [Feb17] add onNo def destroy(self): # close win silently Toplevel.destroy(self) # redef for close ops # ex: pymailgui view class QuietPopupWindow(PopupWindow): def quit(self): self.destroy() # don't verify close class ComponentWindow(Frame): """ ------------------------------------------------------------------- when attached to another display ------------------------------------------------------------------- """ def __init__(self, parent): # if not a frame Frame.__init__(self, parent) # provide container self.pack(expand=YES, fill=BOTH) self.config(relief=RIDGE, border=2) # reconfig to change def quit(self): showinfo('Quit', 'Not supported in attachment mode') # destroy from Frame: erase frame silent # redef for close ops