File: pyedit-products/unzipped/PP4E/Gui/Tools/windows.py

"""
###############################################################################
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



[Home page] Books Code Blog Python Author Train Find ©M.Lutz