jarvisteach/appJar

View on GitHub
appJar/appjar.py

Summary

Maintainability
F
7 mos
Test Coverage
# -*- coding: utf-8 -*-
""" appJar.py: Provides a GUI class, for making simple tkinter GUIs. """
# Nearly everything I learnt came from: http://effbot.org/tkinterbook/
# with help from: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
# with snippets from stackexchange.com

# make print & unicode backwards compatible
from __future__ import print_function
from __future__ import unicode_literals

# Import tkinter classes - handles python2 & python3
try:
    # for Python2
    from Tkinter import *
    import tkMessageBox as MessageBox
    import tkSimpleDialog as SimpleDialog
    from tkColorChooser import askcolor
    import tkFileDialog as filedialog
    import ScrolledText as scrolledtext
    import tkFont as tkFont
    # used to check if functions have a parameter
    from inspect import getargspec as getArgs
    PYTHON2 = True
    PY_NAME = "Python"
    UNIVERSAL_STRING = basestring
except ImportError:
    # for Python3
    from tkinter import *
    from tkinter import messagebox as MessageBox
    from tkinter import simpledialog as SimpleDialog
    from tkinter.colorchooser import askcolor
    from tkinter import filedialog
    from tkinter import scrolledtext
    from tkinter import font as tkFont
    # used to check if functions have a parameter
    from inspect import getfullargspec as getArgs
    PYTHON2 = False
    PY_NAME = "python3"
    UNIVERSAL_STRING = str

# import other useful classes
import os
import sys
import locale
import re
import imghdr  # images
import time  # splashscreen
import calendar  # datepicker
import datetime  # datepicker & image
import logging  # python's logger
import inspect  # for logging
from contextlib import contextmanager  # generators
try: import argparse   # argument parser
except ImportError: argparse = None

import __main__ as theMain
from platform import system as platform

# we need to import these too
# but will only import them when needed
random = None
ttk = ThemedStyle = None
hashlib = None
ToolTip = None
nanojpeg = PngImageTk = array = None  # extra image support
EXTERNAL_DND = None
INTERNAL_DND = None
types = None  # used to register dnd functions
winsound = None
PlotCanvas = PlotNav = PlotFig = None  # matplotlib
parseString = TreeItem = TreeNode = None  # AjTree
# GoogleMap
base64 = urlencode = urlopen = urlretrieve = quote_plus = json = None
ConfigParser = codecs = ParsingError = None  # used to parse language files
Thread = Queue = None
sqlite3 = None
turtle = None
webbrowser = None  # links
OrderedDict = None  # tabbedFrames

# to allow tkinter or ttk
frameBase = Frame
labelBase = Label
scaleBase = Scale
entryBase = Entry

# details
__author__ = "Richard Jarvis"
__copyright__ = "Copyright 2015-2018, Richard Jarvis"
__credits__ = ["Graham Turner", "Sarah Murch"]
__license__ = "Apache 2.0"
__version__ = "0.94.0"
__maintainer__ = "Richard Jarvis"
__email__ = "info@appJar.info"
__status__ = "Development"
__url__ = "http://appJar.info"
try:
    __locale__ = locale.getdefaultlocale()[0]
except ValueError:
    __locale__ = None

class Enum(object):
    """ class to emulate enum type - works in all python versions
        also provides some extra functions """

    __initialized = False
    def __init__(self, widgets, containers, excluded, keepers):
        self.widgets = widgets + containers
        self.containers = containers
        self.excluded = excluded
        self.keepers = []
        for k in keepers:
            self.keepers.append(self.get(k))
        self.funcList = []
        for w in self.widgets:
            if w not in self.excluded:
                self.funcList.append(w)
        self.__initialized = True

    def __getattr__(self, name):
        return self.get(name)

    def get(self, name):
        try: return self.widgets.index(name)
        except: raise KeyError("Invalid key: " + str(name))

    def getIgnoreCase(self, name):
        name = name.upper()
        for w in self.widgets:
            if w.upper() == name:
                return self.widgets.index(w)
        else:
            raise KeyError("Invalid key: " + str(name))

    def __setattr__(self, name, value):
        if self.__initialized: raise Exception("Unable to change Widget store")
        else: super(Enum, self).__setattr__(name, value)

    def __delattr__(self, name):
        raise Exception("Unable to delete from Widget store")

    def name(self, i):
        """Get the real name of the widget"""
        return self.widgets[i]

    def funcs(self):
        """ Get a list of names to use as functions """
        return self.funcList


# static list of widget names in an enum
WIDGET_NAMES = Enum(
    widgets=["Label", "Message", "Entry", "TextArea", "Button", "FileEntry",
        "DirectoryEntry", "Scale", "Link", "Meter", "Image", "CheckBox",
        "RadioButton", "ListBox", "SpinBox", "OptionBox", "TickOptionBox",
        "Map", "PieChart", "Properties", "Table", "Plot", "MicroBit",
        "Tree", "DatePicker", "Separator", "Turtle", "Canvas",
        "Menu", "Toolbar", "FlashLabel", "Widget", "RootPage",
        "ContainerLog", "AnimationID", "ImageCache", "Bindings"],
    containers=[
        "LabelFrame", "Frame", "TabbedFrame", "PanedFrame", "ToggleFrame",
        "FrameStack", "SubFrame", "FrameBox", "FrameLabel", "SubWindow", "Window",
        "ScrollPane", "PagedWindow", "Notebook", "Note", "Tab", "Page", "Pane"],
    excluded=["DatePicker", "SubWindow", "Window", "Toolbar",
        "Note", "Tab", "Page", "Pane", "RootPage", "FlashLabel",
        "AnimationID", "ImageCache", "TickOptionBox", "Bindings",
        "FileEntry", "DirectoryEntry",
        "FrameBox", "FrameLabel", "ContainerLog", "Menu"],
    keepers=["Bindings", "ImageCache", "Menu", "Toolbar"]
)



####################################################
# The main GUI class - this provides all functions
####################################################
class gui(object):
    """ Class to represent the GUI
        - Create one of these
        - add some widgets
        - call the go() function """

    # ensure only one instance of gui is created
    # set to True in constructor
    # set back to false in stop()
    instantiated = False
    built = False

    # static variables
    exe_file = None
    exe_path = None
    lib_file = None
    lib_path = None

    # globals for supported platforms
    WINDOWS = 1
    MAC = 2
    LINUX = 3

    # positioning
    N = N
    NE = NE
    E = E
    SE = SE
    S = S
    SW = SW
    W = W
    NW = NW
    CENTER = CENTER
    LEFT = LEFT
    RIGHT = RIGHT

    # reliefs
    SUNKEN = SUNKEN
    RAISED = RAISED
    GROOVE = GROOVE
    RIDGE = RIDGE
    FLAT = FLAT

    ###################################
    # Constants for music stuff
    ###################################
    BASIC_NOTES = {
        "A": 440,
        "B": 493,
        "C": 261,
        "D": 293,
        "E": 329,
        "F": 349,
        "G": 392,
    }

    NOTES = {'f8': 5587, 'c#6': 1108, 'f4': 349, 'c7': 2093,
             'd#2': 77, 'g8': 6271, 'd4': 293, 'd7': 2349,
            'd#7': 2489, 'g#4': 415, 'e7': 2637, 'd9': 9397,
            'b8': 7902, 'a#4': 466, 'b5': 987, 'b2': 123,
            'g#9': 13289, 'g9': 12543, 'f#2': 92, 'c4': 261,
            'e1': 41, 'e6': 1318, 'a#8': 7458, 'c5': 523,
            'd6': 1174, 'd3': 146, 'g7': 3135, 'd2': 73,
            'd#3': 155, 'g#6': 1661, 'd#4': 311, 'a3': 219,
            'g2': 97, 'c#5': 554, 'd#9': 9956, 'a8': 7040,
            'a#5': 932, 'd#5': 622, 'a1': 54, 'g#8': 6644,
            'a2': 109, 'g#5': 830, 'f3': 174, 'a6': 1760,
            'e8': 5274, 'c#9': 8869, 'f5': 698, 'b1': 61,
            'c#4': 277, 'f#9': 11839, 'e5': 659, 'f9': 11175,
            'f#5': 739, 'a#1': 58, 'f#8': 5919, 'b7': 3951,
            'c#8': 4434, 'g1': 48, 'c#3': 138, 'f#7': 2959,
            'c6': 1046, 'c#2': 69, 'c#7': 2217, 'c3': 130,
            'e9': 10548, 'c9': 8372, 'a#6': 1864, 'a#7': 3729,
            'g#2': 103, 'f6': 1396, 'b3': 246, 'g#3': 207,
            'b4': 493, 'a7': 3520, 'd#6': 1244, 'd#8': 4978,
            'f2': 87, 'd5': 587, 'f7': 2793, 'f#6': 1479,
            'g6': 1567, 'e3': 164, 'f#3': 184, 'g#1': 51,
            'd8': 4698, 'f#4': 369, 'f1': 43, 'c8': 4186,
            'g4': 391, 'g3': 195, 'a4': 440, 'a#3': 233,
            'd#1': 38, 'e2': 82, 'e4': 329, 'a5': 880,
            'a#2': 116, 'g5': 783, 'g#7': 3322, 'b6': 1975,
            'c2': 65, 'f#1': 46
    }

    DURATIONS = {"BREVE": 2000, "SEMIBREVE": 1000, "MINIM": 500,
                "CROTCHET": 250, "QUAVER": 125, "SEMIQUAVER": 63,
                "DEMISEMIQUAVER": 32, "HEMIDEMISEMIQUAVER": 16
    }

###############################################
# USEFUL STATIC METHODS
###############################################

    @staticmethod
    def CENTER(win, up=0):
        gui.SET_LOCATION("CENTER", win=win, up=up)

    @staticmethod
    def SET_LOCATION(x, y=None, ignoreSettings=None, win=None, up=0):
        if ignoreSettings is not None:
            win.ignoreSettings = ignoreSettings

        if gui.GET_PLATFORM() != gui.LINUX:
            trans = win.attributes('-alpha')
            win.attributes('-alpha', 0.0)

        win.update_idletasks()

        if isinstance(x, UNIVERSAL_STRING) and x.lower() in ['c', 'center', 'centre'] and y is None:
            x = y = 'c'
        else:
            x, y = gui.PARSE_TWO_PARAMS(x, y)
        gui.trace("Set location called with %s, %s", x, y)

        # get the window's dimensions
        dims = gui.GET_DIMS(win)

        # set any center positions
        if isinstance(x, UNIVERSAL_STRING) and x.lower() in ['c', 'center', 'centre']: x = dims["x"]
        if isinstance(y, UNIVERSAL_STRING) and y.lower() in ['c', 'center', 'centre']: y = dims["y"]

        # move the window up a bit if requested
        y = y - up if up < y else 0

        # fix any out of bounds positions
        if x < 0 or x > dims['s_width']: x = dims['x']
        if y < 0 or y > dims['s_height']: y = dims['y']

        gui.trace("Screen: %sx%s. Requested: %sx%s. Location: %s, %s",
                    dims["s_width"], dims["s_height"], dims["b_width"],
                    dims["b_height"], x, y)
        win.geometry("+%d+%d" % (x, y))
        win.locationSet = True

        if gui.GET_PLATFORM() != gui.LINUX:
            win.attributes('-alpha', trans)


    @staticmethod
    def CLEAN_CONFIG_DICTIONARY(**kw):
        """ Used by all Classes to tidy up dictionaries passed into config functions
            Allows us to more quickly process the dictionaries when overriding config """

        try: kw['bg'] = kw.pop('background')
        except: pass
        try: kw['fg'] = kw.pop('foreground')
        except: pass
        kw = dict((k.lower().strip(), v) for k, v in kw.items())
        return kw

    @staticmethod
    def GET_PLATFORM():
        """ returns one of the gui class's three static platform variables """
        if platform() in ["win32", "Windows"]:
            return gui.WINDOWS
        elif platform() == "Darwin":
            return gui.MAC
        elif platform() in ["Linux", "FreeBSD"]:
            return gui.LINUX
        else:
            raise Exception("Unknown platform: " + platform())

    @staticmethod
    def SHOW_VERSION():
        """ returns a printable string containing version information """
        verString = \
            "appJar: " + str(__version__) \
            + "\nPython: " + str(sys.version_info[0]) \
            + "." + str(sys.version_info[1]) + "." + str(sys.version_info[2]) \
            + "\nTCL: " + str(TclVersion) \
            + ", TK: " + str(TkVersion) \
            + "\nPlatform: " + str(platform()) \
            + "\npid: " + str(os.getpid()) \
            + "\nlocale: " + str(__locale__)

        return verString

    @staticmethod
    def SHOW_PATHS():
        """ returns a printable string containing path to libraries, etc """
        pathString = \
            "File Name: " + (gui.exe_file if gui.exe_file is not None else "") \
            + "\nFile Location: " + (gui.exe_path if gui.exe_path is not None else "") \
            + "\nLib Location: " + (gui.lib_path if gui.lib_path is not None else "")

        return pathString

    @staticmethod
    def GET_DIMS(container):
        """ returns a dictionary of dimensions for the supplied container """
        container.update()
        dims = {}
        # get the apps requested width & height
        dims["r_width"] = container.winfo_reqwidth()
        dims["r_height"] = container.winfo_reqheight()

        # get the current width & height
        dims["w_width"] = container.winfo_width()
        dims["w_height"] = container.winfo_height()

        # get the window's width & height
        dims["s_width"] = container.winfo_screenwidth()
        dims["s_height"] = container.winfo_screenheight()

        # determine best geom for OS
        # on MAC & LINUX, w_width/w_height always 1 unless user-set
        # on WIN, w_height is bigger then r_height - leaving empty space
        if gui.GET_PLATFORM() in [gui.MAC, gui.LINUX]:
            if dims["w_width"] != 1:
                dims["b_width"] = dims["w_width"]
                dims["b_height"] = dims["w_height"]
            else:
                dims["b_width"] = dims["r_width"]
                dims["b_height"] = dims["r_height"]
        else:
            dims["b_height"] = max(dims["r_height"], dims["w_height"])
            dims["b_width"] = max(dims["r_width"], dims["w_width"])

        # GUI's corner - widget's corner
        # widget's corner can be 0 on windows when size not set by user
        dims["outerFrameWidth"] = 0 if container.winfo_x() == 0 else container.winfo_rootx() - container.winfo_x()
        dims["titleBarHeight"] = 0 if container.winfo_rooty() == 0 else container.winfo_rooty() - container.winfo_y()

        # add it all together
        dims["actualWidth"] = dims["b_width"] + (dims["outerFrameWidth"] * 2)
        dims["actualHeight"] = dims["b_height"] + dims["titleBarHeight"] + dims["outerFrameWidth"]

        dims["x"] = (dims["s_width"] // 2) - (dims["actualWidth"] // 2)
        dims["y"] = (dims["s_height"] // 2) - (dims["actualHeight"] // 2)

        return dims

    @staticmethod
    def PARSE_TWO_PARAMS(x, y):
        """ used to convert different possible x/y params to a tuple
        """
        if y is not None:
            return (x,y)
        else:
            if isinstance(x, (list, tuple)):
                return (x[0], x[1])
            else:
                if isinstance(x, UNIVERSAL_STRING):
                    x=x.strip()
                    if "," in x:
                        return [int(w.strip()) for w in x.split(",")]
                return (x, x)

    @staticmethod
    def SPLIT_GEOM(geom):
        """ returns 2 lists made from the geom string
        :param geom: the geom string to parse
        :returns: a tuple containing a width/heiht tuple & a x/y position tuple
        """
        geom = geom.lower().split("x")
        width = int(float(geom[0]))
        height = int(float(geom[1].split("+")[0]))
        try:
            x = int(float(geom[1].split("+")[1]))
            y = int(float(geom[1].split("+")[2]))
        except IndexError:
            x = y = -1

        return (width, height), (x, y)

    @staticmethod
    def MOUSE_POS_IN_WIDGET(widget, event, findRoot=True):
        """ returns the mouse's relative position in a widget
        :param widget: the widget to look in
        :param event: the event containing the mouse coordinates
        :param findRoot: if we should make this relative to the parent
        """

        # first we have to get the real master
        master = widget
        while findRoot:
            if isinstance(master, (SubWindow, Tk)):
                findRoot = False
            else:
                master = master.master

        # subtract the widget's top left corner from the root window's top corner
        x = event.x_root - master.winfo_rootx()
        y = event.y_root - master.winfo_rooty()
        gui.trace("<<MOUSE_POS_IN_WIDGET>> %s %s,%s", widget, x, y)
        return (x, y)

#####################################
#####################################
# CONSTRUCTOR - creates the GUI
#####################################
    def __init__(
                    self, title=None, geom=None, handleArgs=True, language=None,
                    startWindow=None, useTtk=False, useSettings=False, showIcon=True, **kwargs
                ):
        """ constructor - sets up the empty GUI window, and inits the various properties """

        if self.__class__.instantiated:
            raise Exception("You cannot have more than one instance of gui, try using a subWindow.")
        else:
            self.__class__.instantiated = True

        self.alive = True

        # first up, set the logger
        def _logForLevel(self, message, *args, **kwargs):
            if self.isEnabledFor(logging.DEBUG-5):
                self._log(logging.DEBUG-5, message, args, **kwargs)
        def _logToRoot(message, *args, **kwargs):
            logging.log(logging.DEBUG-5, message, *args, **kwargs)

        logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(name)s:%(levelname)s %(message)s')
        logging.addLevelName(logging.DEBUG - 5, 'TRACE')
        setattr(logging, 'TRACE', logging.DEBUG -5)
        setattr(logging.getLoggerClass(), "trace", _logForLevel)
        setattr(logging, "trace", _logToRoot)

        logFile = kwargs.pop("file", kwargs.pop("logFile", None))
        logLevel = kwargs.pop("log", kwargs.pop("logLevel", None))

        self._language = language
        self.useSettings = useSettings
        self.settingsFile = "appJar.ini"
        self.externalSettings = {}

        self.startWindow = startWindow

        # check any command line arguments
        if argparse is None: handleArgs = False
        args = self._handleArgs() if handleArgs else None

        # warn if we're in an untested mode
        self._checkMode()
        # first out, verify the platform
        self.platform = gui.GET_PLATFORM()

        # process any command line arguments
        self.ttkFlag = False
        selectedTtkTheme = None
        if handleArgs:
            if args.f:
                gui.setLogFile(args.f)
                logFile = None # don't use any param logFile

            tmplevel, logLevel = logLevel, None
            if args.c: gui.setLogLevel("CRITICAL")
            elif args.e: gui.setLogLevel("ERROR")
            elif args.w: gui.setLogLevel("WARNING")
            elif args.i: gui.setLogLevel("INFO")
            elif args.d: gui.setLogLevel("DEBUG")
            elif args.t: gui.setLogLevel("TRACE")
            else: loglevel = tmplevel

        if logFile is not None: gui.setLogFile(logFile)
        if logLevel is not None: gui.setLogLevel(logLevel)

        if handleArgs:
            if args.l: self._language = args.l
            if args.ttk:
                useTtk = True
                if args.ttk is not True:
                    selectedTtkTheme = args.ttk

            if args.s:
                self.useSettings = True
                if args.s is not True:
                    self.settingsFile = args.s

        # configure as ttk
        if useTtk:
            self._useTtk()
            if useTtk is not True:
                selectedTtkTheme = useTtk

        # a stack to hold containers as being built
        # done here, as initArrays is called elsewhere - to reset the gubbins
        self.containerStack = []
        self.translations = {"POPUP":{}, "SOUND":{}, "EXTERNAL":{}}
        # first up, set up all the data stores
        self.widgetManager = WidgetManager()
        self.accessMade = False # accessibility subWindow
        self.splashConfig = None # splash screen?
        self.dnd = None # the dnd manager
        self.doFlash = False # set up flash variable
        self.hasTitleBar = True # used to hide/show title bar

        # validate function callbacks - used by numeric texts
        # created first time a widget is used
        self.validateNumeric = None
        self.validateSpinBox = None

        # dynamically create lots of functions for configuring stuff
        self._buildConfigFuncs()
        # language parser
        self.configParser = None

        # set up some default path locations
        # this fails if in interactive mode....
        try:
            gui.exe_file = str(os.path.basename(theMain.__file__))
            gui.exe_path = str(os.path.dirname(theMain.__file__))
        except:
            pass

        gui.lib_file = os.path.abspath(__file__)
        gui.lib_path = os.path.dirname(gui.lib_file)

        # location of appJar
        self.resource_path = os.path.join(gui.lib_path, "resources")
        self.icon_path = os.path.join(self.resource_path, "icons")
        self.sound_path = os.path.join(self.resource_path, "sounds")
        self.appJarIcon = os.path.join(self.icon_path, "favicon.ico")

        # user configurable
        self.userImages = gui.exe_path
        self.userSounds = gui.exe_path

        # create the main window - topLevel
        self.topLevel = Tk()
        self.topLevel.bind('<Configure>', self._windowEvent)

        def _setFocus(e):
            try: e.widget.focus_set()
            except: pass

        # these are specifically to make right-click menus disapear on linux
        self.topLevel.bind('<Button-1>', lambda e: _setFocus(e))
        self.topLevel.bind('<Button-2>', lambda e: _setFocus(e))
        self.topLevel.bind('<Button-3>', lambda e: _setFocus(e))
        # override close button
        self.topLevel.protocol("WM_DELETE_WINDOW", self.stop)
        # temporarily hide it
        self.topLevel.withdraw()

        # used to keep a handle on the last pop-up dialog
        # allows the dialog to be closed remotely
        # mainly for test-automation
        self.topLevel.POP_UP = None

        # create a frame to store all the widgets
        # now a canvas to allow animation...
        self.appWindow = CanvasDnd(self.topLevel)
        self.appWindow.pack(fill=BOTH, expand=True)
        self.topLevel.canvasPane = self.appWindow

        # set the windows title
        if title is None:
            title = "appJar" if gui.exe_file is None else gui.exe_file

        self.setTitle(title)
        self.topLevel.winIcon = None # will store the path to any icon

        # configure the geometry of the window
        self.topLevel.escapeBindId = None  # used to exit fullscreen
        self.topLevel.stopFunction = None  # used to exit fullscreen
        self.topLevel.startFunction = None

        # set the resize status - default to True
        self.topLevel.locationSet = False
        self.topLevel.ignoreSettings = False
        self.topLevel.isFullscreen = False # records if we're in fullscreen - stops hideTitle from breaking
        self.topLevel.displayed = True
        if geom is not None: self.setSize(geom)
        self.setResizable(True)

        self.Widgets = WIDGET_NAMES

        # 3 fonts used for most widgets
        self._buttonFont = tkFont.Font(family="Helvetica", size=12,)
        self._labelFont = tkFont.Font(family="Helvetica", size=12)
        self._inputFont = tkFont.Font(family="Helvetica", size=12)
        self._statusFont = tkFont.Font(family="Helvetica", size=12)

        # dedicated font for access widget
        self._accessFont = tkFont.Font(family="Arial", size=11,)
        # dedicated font for links - forces bold & underlined, but updated with label fonts
        self._linkFont = tkFont.Font(family="Helvetica", size=12, weight='bold', underline=1)

        self.tableFont = tkFont.Font(family="Helvetica", size=12)

        # create a menu bar - only shows if populated
        # now created in menu functions, as it generated a blank line...
        self.hasMenu = False
        self.hasStatus = False
        self.copyAndPaste = CopyAndPaste(self.topLevel, self)

        class Toolbar(frameBase, object):
            def __init__(self, master, **kwargs):
                super(Toolbar, self).__init__(master, **kwargs)
                self.BG_COLOR = None
                self.pinned = True
                self.pinBut = None
                self.inUse = False
                self.toolbarMin = None
                self.location = None

            def makeMinBar(self):
                if self.toolbarMin is None:
                    self.toolbarMin = Frame(self.master, bd=1, relief=RAISED)
                    self.toolbarMin.config(bg="gray", height=3)
                    self.bind("<Leave>", self._minToolbar)
                    self.toolbarMin.bind("<Enter>", self._maxToolbar)

            def hide(self):
                if self.inUse:
                    self.pack_forget()
                    if self.toolbarMin is not None:
                        self.toolbarMin.pack_forget()

            def show(self):
                if self.inUse:
                    self.pack(before=self.location, side=TOP, fill=X)
                    if self.toolbarMin is not None:
                        self.toolbarMin.pack_forget()

            def _minToolbar(self, e=None):
                if not self.pinned:
                    if self.toolbarMin is not None:
                        self.toolbarMin.config(width=self.winfo_reqwidth())
                        self.toolbarMin.pack(before=self.location, side=TOP, fill=X)
                    self.pack_forget()

            def _maxToolbar(self, e=None):
                self.pack(before=self.location, side=TOP, fill=X)
                if self.toolbarMin is not None:
                    self.toolbarMin.pack_forget()

        class WidgetContainer(frameBase, object):
            def __init__(self, master, **kwargs):
                super(WidgetContainer, self).__init__(master, **kwargs)

        # create the main container for this GUI
        container = WidgetContainer(self.appWindow)
        # container = Label(self.appWindow) # made as a label, so we can set an
        # image
        if not self.ttkFlag:
            container.config(padx=2, pady=2, background=self.topLevel.cget("bg"))
        container.pack(fill=BOTH, expand=True)
        self._addContainer("root", WIDGET_NAMES.RootPage, container, 0, 1)

        self.tb = Toolbar(self.appWindow)
        if not self.ttkFlag:
            self.tb.config(bd=1, relief=RAISED)
        else:
            self.tb.config(style="Toolbar.TFrame")


        # set up the main container to be able to host an image
        self._configBg(container)

        if self.platform == self.WINDOWS and showIcon:
            try:
                self.setIcon(self.appJarIcon)
            except: # file not found
                gui.trace("Error setting Windows default icon")

        # set the ttk theme
        if self.ttkFlag:
            self.setTtkTheme(selectedTtkTheme)

        # for configuting event processing
        self.EVENT_SIZE = 1000
        self.EVENT_SPEED = 100
        self.preloadAnimatedImageId = None
        self.processQueueId = None

        # an array to hold any threaded events....
        self.events = []
        self.pollTime = 250
        self._fastStop = False
        self.configure(**kwargs)

        # special bindings
        self._globalBindings()

        self.built = True

    def _globalBindings(self):
        def _selectEntry(event):
            event.widget.select_range(0, 'end')

        def _selectText(event):
            event.widget.tag_add("sel","1.0","end")

        def _scrollPaste(event):
            event.widget.event_generate('<<Paste>>')
            event.widget.see(END)

        if self.GET_PLATFORM() == self.MAC:
            self.topLevel.bind_class("Text", "<Command-a>", _selectText)
            self.topLevel.bind_class("Entry", "<Command-a>", _selectEntry)
            self.topLevel.bind_class("Text", "<Command-v>", _scrollPaste)
        else:
            self.topLevel.bind_class("Text", "<Control-a>", _selectText)
            self.topLevel.bind_class("Entry", "<Control-a>", _selectEntry)
            self.topLevel.bind_class("Text", "<Control-v>", _scrollPaste)

    def _handleArgs(self):
        """ internal function to handle command line arguments """
        parser = argparse.ArgumentParser(
            description="appJar - the easiest way to create GUIs in python",
            epilog="For more information, go to: http://appJar.info"
        )
        parser.add_argument("-v", "--version", action="version", version=gui.SHOW_VERSION(), help="show version information and exit")
        logGroup = parser.add_mutually_exclusive_group()
        logGroup.add_argument("-c", action="store_const", const=True, help="only log CRITICAL messages")
        logGroup.add_argument("-e", action="store_const", const=True, help="log ERROR messages and above")
        logGroup.add_argument("-w", action="store_const", const=True, help="log WARNING messages and above")
        logGroup.add_argument("-i", action="store_const", const=True, help="log INFO messages and above")
        logGroup.add_argument("-d", action="store_const", const=True, help="log DEBUG messages and above")
        logGroup.add_argument("-t", action="store_const", const=True, help="log TRACE messages and above")
        parser.add_argument("-l", metavar="LANGUAGE.ini", help="set a language file to use")
        parser.add_argument("-f", metavar="file.log", help="set a log file to use")
        parser.add_argument("-s", metavar="SETTINGS", const=True, nargs="?", help="load settings, from an optional file name")
        parser.add_argument("--ttk", metavar="THEME", const=True, nargs="?", help="enable ttk, with an optional theme")
        return parser.parse_args()

    # function to check on mode
    def _checkMode(self):
        """ internal function to warn about issues in certain modes """
        # detect if we're in interactive mode
        if hasattr(sys, 'ps1'):
            self.warn("Interactive mode is not fully tested, some features might not work.")
        else:
            if sys.flags.interactive:
                self.warn("Postmortem Interactive mode is not fully tested, some features might not work.")
        # also, check for iPython
        try:
            __IPYTHON__
        except NameError:
            #no iPython - ignore
            pass
        else:
            self.warn("iPython is not fully tested, some features might not work.")

    def _configBg(self, container):
        """ internal function to set up a label as the BG """
        # set up a background image holder
        # alternative to label option above, as label doesn't update widgets
        # properly

        class BgLabel(labelBase, object):
            def __init__(self, master, **kwargs):
                super(BgLabel, self).__init__(master, **kwargs)

        if not self.ttkFlag:
            self.bgLabel = BgLabel(container, anchor=CENTER, font=self._getContainerProperty('labelFont'), background=self._getContainerBg())
        else:
            self.bgLabel = ttk.Label(container)
        self.bgLabel.place(x=0, y=0, relwidth=1, relheight=1)
        container.image = None

#####################################
# TTK functions
#####################################

    def _useTtk(self):
        """ enables use of ttk """
        global ttk, frameBase, labelBase, scaleBase, entryBase
        try:
            import ttk
        except:
            try:
                from tkinter import ttk
            except:
                gui.error("ttk not available")
                return
        self.ttkFlag = True
        frameBase = ttk.Frame
        labelBase = ttk.Label
        scaleBase = ttk.Scale
        entryBase = ttk.Entry

        gui.trace("Mode switched to ttk")

    def _loadTtkThemes(self):
        global ThemedStyle
        if ThemedStyle is None:
            try:
                from ttkthemes import ThemedStyle
                self.ttkStyle = ThemedStyle(self.topLevel)
            except:
                ThemedStyle = False

    def getTtkThemes(self, loadThemes=False):
        if loadThemes:
            self._loadTtkThemes()
            if not ThemedStyle:
                self.error("Custom ttkThemes not available")

        return self.ttkStyle.theme_names()

    def getTtkTheme(self):
        return self.ttkStyle.theme_use()

    # only call this after the main tk has been created
    # otherwise we get two windows!
    def setTtkTheme(self, theme=None):
        """ sets the ttk theme to use """
        self.ttkStyle = ttk.Style()

        gui.trace("Switching ttk theme to: %s", theme)
        if theme is not None:
            try:
                self.ttkStyle.theme_use(theme)
            except:
                gui.trace("no basic ttk theme named %s found, searching for additional themes", theme)
                self._loadTtkThemes()
                if not ThemedStyle:
                    self.error("ttk theme: %s unavailable. Try one of: %s", theme, str(self.ttkStyle.theme_names()))
                else:
                    self.ttkStyle.set_theme(theme)

        # set up our ttk styles
        self.ttkStyle.configure("DefaultText.TEntry", foreground="grey")
        self.ttkStyle.configure("ValidationEntryValid.TEntry", foreground="#4CC417", highlightbackground="#4CC417", highlightcolor="#4CC417", highlightthickness='20')
        self.ttkStyle.configure("ValidationEntryInvalid.TEntry", foreground="#FF0000", highlightbackground="#FF0000", highlightcolor="#FF0000", highlightthickness='20')
        self.ttkStyle.configure("ValidationEntryWait.TEntry", foreground="#000000", highlightbackground="#000000", highlightcolor="#000000", highlightthickness='20')

        self.ttkStyle.configure("ValidationEntryValid.TLabel", foreground="#4CC417")
        self.ttkStyle.configure("ValidationEntryInvalid.TLabel", foreground="#FF0000")
        self.ttkStyle.configure("ValidationEntryWait.TLabel", foreground="#000000")

        self.ttkStyle.configure("Link.TLabel", foreground="#0000ff")
        self.ttkStyle.configure("LinkOver.TLabel", foreground="#3366ff")

        #toolbars
        self.ttkStyle.configure("Toolbar.TFrame")
        self.ttkStyle.configure("Toolbar.TLabel")
        self.ttkStyle.configure("Toolbar.TButton", compound=CENTER, padding=0, expand=0)

#        self.fgColour = self.topLevel.cget("foreground")
#        self.buttonFgColour = self.topLevel.cget("foreground")
#        self.labelFgColour = self.topLevel.cget("foreground")

    # set a property for ttk theme
    ttkTheme = property(getTtkTheme, setTtkTheme)

###############################################################
# library loaders - on demand loading of different classes
###############################################################

    def _loadRandom(self):
        """ loasd random libraries """
        global random
        if random is None:
            import random

    def _loadTurtle(self):
        """ loasd turtle libraries """
        global turtle
        try:
            import turtle
        except:
            turtle = False
            self.error("Turtle not available")

    def _loadConfigParser(self):
        """ loads the ConfigParser, used by internationalisation & settings """
        global ConfigParser, ParsingError, codecs
        if ConfigParser is None:
            try:
                from configparser import ConfigParser
                from configparser import ParsingError
                import codecs
            except:
                try:
                    from ConfigParser import ConfigParser
                    from ConfigParser import ParsingError
                    import codecs
                except:
                    ConfigParser = ParsingError = codecs = False
                    self.configParser = None
                    return
            self.configParser = ConfigParser()
            self.configParser.optionxform = str

    def _loadHashlib(self):
        """ loads hashlib - used by text area """
        global hashlib
        if hashlib is None:
            try:
                import hashlib
            except:
                hashlib = False

    def _loadTooltip(self):
        """ loads tooltips - used all over """
        global ToolTip
        if ToolTip is None:
            try:
                from appJar.lib.tooltip import ToolTip
            except:
                ToolTip = False

    def _loadMatplotlib(self):
        """ loads matPlotLib """
        global PlotCanvas, PlotNav, PlotFig

        if PlotCanvas is None:
            try:
                from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg as PlotCanvas
                try: from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk as PlotNav
                except: from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg as PlotNav
                from matplotlib.figure import Figure as PlotFig
            except:
                PlotCanvas = PlotNav = PlotFig = False

    def _loadExternalDnd(self):
        """ loads external dnd - from other applications """
        global EXTERNAL_DND
        if EXTERNAL_DND is None:
            try:
                tkdndlib = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib", "tkdnd2.8")
                os.environ['TKDND_LIBRARY'] = tkdndlib
                from appJar.lib.TkDND_wrapper import TkDND as EXTERNAL_DND
                self.dnd = EXTERNAL_DND(self.topLevel)
            except:
                EXTERNAL_DND = False

    def _loadInternalDnd(self):
        """ loads the internal dnd libraries """
        global INTERNAL_DND, types
        if INTERNAL_DND is None:
            try:
                import Tkdnd as INTERNAL_DND
                import types as types
            except:
                try:
                    from tkinter import dnd as INTERNAL_DND
                    import types as types
                except:
                    INTERNAL_DND = False
                    types = False

    def _loadURL(self):
        """ loads ibraries used by googlemaps widget """
        global base64, urlencode, urlopen, urlretrieve, quote_plus, json, Queue
        self._loadThreading()
        if Queue:
            if urlencode is None:
                try: # python 2
                    from urllib import urlencode, urlopen, urlretrieve, quote_plus
                    import json
                    import base64
                except ImportError: # python 3
                    try:
                        from urllib.parse import urlencode
                        from urllib.parse import quote_plus
                        from urllib.request import urlopen
                        from urllib.request import urlretrieve
                        import json
                        import base64
                    except:
                        base64 = urlencode = urlopen = urlretrieve = quote_plus = json = Queue = False
        else:
            base64 = urlencode = urlopen = urlretrieve = quote_plus = json = Queue = False

    def _loadThreading(self):
        """ loads threading classes, and sets up queue """
        global Thread, Queue
        if Thread is None:
            try:
                from threading import Thread
                import Queue
            except ImportError: # python 3
                try:
                    from threading import Thread
                    import queue as Queue
                except:
                    Thread = Queue = False
                    return

            self.eventQueue = Queue.Queue(maxsize=self.EVENT_SIZE)
            self._processEventQueue()

    def _loadNanojpeg(self):
        """ loads jpeg support """
        global nanojpeg, array
        if nanojpeg is None:
            try:
                from appJar.lib import nanojpeg
                import array
            except:
                nanojpeg = False
                array = False

    def _loadWinsound(self):
        """ loads winsound support on Windows """
        global winsound
        if winsound is None:
            if platform() in ["win32", "Windows"]:
                import winsound
            else:
                winsound = False

    def _importPngimagetk(self):
        """ loads PNG support """
        global PngImageTk
        if PngImageTk is None:
            try:
                from appJar.lib.tkinter_png import PngImageTk
            except:
                PngImageTk = False

    def _importAjtree(self):
        """ loads tree support - and creates tree classes """
        global parseString, TreeItem, TreeNode

        if TreeNode is None:
            try:
                from idlelib.TreeWidget import TreeItem, TreeNode
            except:
                try:
                    from idlelib.tree import TreeItem, TreeNode
                except:
                    gui.warning("no trees")
                    TreeItem = TreeNode = parseString = False

            if TreeNode is not False:
                try:
                    from xml.dom.minidom import parseString
                except:
                    gui.warning("no parse string")
                    TreeItem = TreeNode = parseString = False
                    return

    def _importSqlite3(self):
        """ loads sqlite3 """
        global sqlite3
        if sqlite3 is None:
            try:
                import sqlite3
            except:
                sqlite3 = False

    def _importWebBrowser(self):
        """ loads webbrowser """
        global webbrowser
        if webbrowser is None:
            try:
                import webbrowser
            except:
                webbrowser = False

#####################################
# FUNCTIONS FOR UNIVERSAL DND
#####################################

    def _registerExternalDragSource(self, title, widget, function=None):
        """ register a widget to start external drag events """
        self._loadExternalDnd()

        if EXTERNAL_DND is not False:
            try:
                self.dnd.bindsource(widget, self._startExternalDrag, 'text/uri-list')
                self.dnd.bindsource(widget, self._startExternalDrag, 'text/plain')
                widget.dndFunction = function
                widget.dragData = None
            except:
                # dnd not working on this platform
                raise Exception("Failed to register external Drag'n Drop for: " + str(title))
        else:
            raise Exception("External Drag'n Drop not available on this platform")

    def _registerExternalDropTarget(self, title, widget, function=None, replace=True):
        """ register a widget to receive external drag events """
        self._loadExternalDnd()

        if EXTERNAL_DND is not False:
            try:
                self.dnd.bindtarget(widget, self._receiveExternalDrop, 'text/uri-list')
                self.dnd.bindtarget(widget, self._receiveExternalDrop, 'text/plain')
                # cater for new drop parameter in new setters
                if function == True: function = None
                widget.dndFunction = function
                widget.dropData = None
                widget.dropReplace = replace
            except:
                # dnd not working on this platform
                raise Exception("Failed to register external Drag'n Drop for: " + str(title))
        else:
            raise Exception("External Drag'n Drop not available on this platform")

    def _registerInternalDragSource(self, kind, title, widget, function=None):
        """ register a widget to start internal drag events """
        self._loadInternalDnd()

        name = None
        if kind == WIDGET_NAMES.Label:
            name = self.getLabel(title)

        if INTERNAL_DND is not False:
            try:
                widget.bind('<ButtonPress>', lambda e: self._startInternalDrag(e, title, name, widget))
                widget.dnd_canvas = self._getCanvas().canvasPane
                gui.trace("DND drag source created: %s on canvas %s", widget, widget.dnd_canvas)
            except:
                raise Exception("Failed to register internal Drag'n Drop for: " + str(title))
        else:
            raise Exception("Internal Drag'n Drop not available on this platform")

    def _registerInternalDropTarget(self, widget, function):
        """ register a widget to receive internal drag events """
        gui.trace("<<WIDGET._registerInternalDropTarget>> %s", widget)
        self._loadInternalDnd()
        if not INTERNAL_DND:
            raise Exception("Internal Drag'n Drop not available on this platform")

        # called by DND class, when looking for a DND target
        def dnd_accept(self, source, event):
            gui.trace("<<WIDGET.dnd_accept>> %s - %s", widget, self.dnd_canvas)
            return self

        # This is called when the mouse pointer goes from outside the
        # Target Widget to inside the Target Widget.
        def dnd_enter(self, source, event):
            gui.trace("<<WIDGET.dnd_enter>> %s", widget)
            XY = gui.MOUSE_POS_IN_WIDGET(self,event)
            source.appear(self, XY)

        # This is called when the mouse pointer goes from inside the
        # Target Widget to outside the Target Widget.
        def dnd_leave(self, source, event):
            gui.trace("<<WIDGET.dnd_leave>> %s", widget)
            # hide the dragged object
            source.vanish()

        #This is called if the DraggableWidget is being dropped on us.
        def dnd_commit(self, source, event):
            source.vanish(all=True)
            gui.trace("<<WIDGET.dnd_commit>> %s Object received=%s", widget, source)

        #This is called when the mouse pointer moves within the TargetWidget.
        def dnd_motion(self, source, event):
            gui.trace("<<WIDGET.dnd_motion>> %s", widget)
            XY = gui.MOUSE_POS_IN_WIDGET(self,event)
            # move the dragged object
            source.move(self, XY)

        def keepWidget(self, title, name):
            if self.drop_function is not None:
                return self.drop_function(title, name)
            else:
                self.configParser(text=name)
                return True

        widget.dnd_accept = types.MethodType(dnd_accept, widget)
        widget.dnd_enter = types.MethodType(dnd_enter, widget)
        widget.dnd_leave = types.MethodType(dnd_leave, widget)
        widget.dnd_commit = types.MethodType(dnd_commit, widget)
        widget.dnd_motion = types.MethodType(dnd_motion, widget)
        widget.keepWidget = types.MethodType(keepWidget, widget)
        # save the underlying canvas
        widget.dnd_canvas = self._getCanvas().canvasPane
        widget.drop_function = function

        gui.trace("DND target created: %s on canvas %s", widget, widget.dnd_canvas)

    def _startInternalDrag(self, event, title, name, widget):
        """ called when the user initiates an internal drag event """
        gui.trace("Internal drag started for %s on %s", title, widget)

        x, y = gui.MOUSE_POS_IN_WIDGET(widget, event, False)
        width = x / widget.winfo_width()
        height = y / widget.winfo_height()

        thingToDrag = DraggableWidget(widget.dnd_canvas, title, name, (width, height))
        INTERNAL_DND.dnd_start(thingToDrag, event)

    def _startExternalDrag(self, event):
        """ starts external drags - not yet supported """
        widgType = gui.GET_WIDGET_CLASS(event.widget)
        self.warn("Unable to initiate drag events: %s", widgType)

    def _receiveExternalDrop(self, event):
        """ receives external drag events """
        widgType = gui.GET_WIDGET_CLASS(event.widget)
        event.widget.dropData = event.data
        if not hasattr(event.widget, 'dndFunction'):
            self.warn("Error - external drop target not correctly configured: %s", widgType)
        elif event.widget.dndFunction is not None:
            event.widget.dndFunction(event.data)
        else:
            if widgType in ["Entry", "AutoCompleteEntry", "SelectableLabel"]:
                if widgType == "SelectableLabel": event.widget.configure(state="normal")
                if event.widget.dropReplace:
                    event.widget.delete(0, END)
                event.widget.insert(END, event.data)
                event.widget.focus_set()
                event.widget.icursor(END)
                if widgType == "SelectableLabel": event.widget.configure(state="readonly")
            elif widgType in ["TextArea", "AjText", "ScrolledText", "AjScrolledText"]:
                if event.widget.dropReplace:
                    event.widget.delete(1.0, END)
                event.widget.insert(END, event.data)
                event.widget.focus_set()
                event.widget.see(END)
            elif widgType in ["Label"]:
                for k, v in self.widgetManager.group(WIDGET_NAMES.Image).items():
                    if v == event.widget:
                        try:
                            imgTemp = self.userImages
                            image = self._getImage(event.data, False)
                            self._populateImage(k, image)
                            self.userImages = imgTemp
                        except:
                            self.errorBox("Error loading image", "Unable to load image: " + str(event.data))
                        return
                for k, v in self.widgetManager.group(WIDGET_NAMES.Label).items():
                    if v == event.widget:
                        self.setLabel(k, event.data)
                        return
            elif widgType in ["Listbox"]:
                for k, v in self.widgetManager.group(WIDGET_NAMES.ListBox).items():
                    if v == event.widget:
                        self.addListItem(k, event.data)
                        return
            elif widgType in ["Message"]:
                for k, v in self.widgetManager.group(WIDGET_NAMES.Message).items():
                    if v == event.widget:
                        self.setMessage(k, event.data)
                        return
            else:
                self.warn("Unable to receive drop events: %s", widgType)


#####################################
# Language/Translation functions
#####################################
    def translate(self, key, default=None):
        """ returns a translated version of the key, using the current language
            if none found, returns the default value """
        return self._translate(key, "EXTERNAL", default)

    def _translateSound(self, key):
        """ internal wrapper to translate sounds """
        return self._translate(key, "SOUND", key)

    def _translatePopup(self, key, value):
        """ internal wrapper to translate popups """
        pop = self._translate(key, "POPUP")
        if pop is None:
            return (key, value)
        else:
            return (pop[0], pop[1])

    def _translate(self, key, section, default=None):
        """ returns the relevant key from the relevant section in the internally
            held translation dictionary - prepopulated when language was set """
        if key in self.translations[section]:
            return self.translations[section][key]
        else:
            return default

    def getLanguage(self):
        ''' returns the current language '''
        return self._language

    def setLanguage(self, language):
        """ wrapper for changeLanguage() """
        self.changeLanguage(language)

    # function to update languages
    def changeLanguage(self, language):
        """ changes the language used by the GUI
            will iterate through all widgets and update their text
            as well as populate a translation dictionary for later lookups """
        self._loadConfigParser()
        if not ConfigParser:
            self.error("Internationalisation not supported")
            return

        fileName = language.upper() + ".ini"
        gui.trace("Loading language file: %s", fileName)
        if not PYTHON2:
            try:
                with codecs.open(fileName, "r", "utf8") as langFile:
                    self.configParser.read_file(langFile)
            except FileNotFoundError:
                self.error("Invalid language, file not found: %s", fileName)
                return
        else:
            try:
                try:
                    with codecs.open(fileName, "r", "utf8") as langFile:
                        self.configParser.read_file(langFile)
                except AttributeError:
                    with codecs.open(fileName, "r", "utf8") as langFile:
                        self.configParser.readfp(langFile)
            except IOError:
                self.error("Invalid language, file not found: %s", fileName)
                return
            except ParsingError:
                self.error("Translation failed - language file contains errors, ensure there is no whitespace at the beginning of any lines.")
                return

        gui.trace("Switching to: %s", language)
        self._language = language
        self.translations = {"POPUP":{}, "SOUND":{}, "EXTERNAL":{}}
        # loop through each section, get the relative set of widgets
        # change the text
        for section in self.configParser.sections():
            getWidgets = True
            section = section.upper()
            gui.trace("\tSection: %s", section)

            # convert the section title to its code
            if section == "CONFIG":
                # skip the config section (for now)
                gui.trace("\tSkipping CONFIG")
                continue
            elif section == "TITLE":
                kind = WIDGET_NAMES.SubWindow
            elif section.startswith("TOOLTIP-"):
                kind = "TOOLTIP"
                getWidgets = False
            elif section in ["SOUND", "EXTERNAL", "POPUP"]:
                for (key, val) in self.configParser.items(section):
                    if section == "POPUP": val = val.strip().split("\n")
                    self.translations[section][key] = val
                    gui.trace("\t\t%s: %s", key, val)
                continue
            elif section == "MENUBAR":
                for (key, val) in self.configParser.items(section):
                    key = key.strip().split("-")
                    gui.trace("\t\t%s: %s", key, val)
                    if len(key) == 1:
                        try:
                            self.renameMenu(key[0], val)
                        except:
                            self.warn("Invalid key")
                    elif len(key) == 2:
                        try:
                            self.renameMenuItem(key[0], key[1], val)
                        except:
                            self.warn("Invalid key")
                continue
            else:
                try:
                    kind = WIDGET_NAMES.getIgnoreCase(section)
                except Exception:
                    self.warn("Invalid config section: %s", section)
                    continue

            # if necessary, use the code to get the widget list
            if getWidgets:
                widgets = self.widgetManager.group(kind)

            if kind in [WIDGET_NAMES.Scale]:
                self.warn("No text is displayed in %s. Maybe it has a Label?", section)
                continue
            elif kind in [WIDGET_NAMES.TextArea, WIDGET_NAMES.Meter, WIDGET_NAMES.PieChart, WIDGET_NAMES.Tree]:
                self.warn("No text is displayed in %s", section)
                continue
            elif kind in [WIDGET_NAMES.name(WIDGET_NAMES.SubWindow)]:
                for (key, val) in self.configParser.items(section):
                    gui.trace("\t\t%s: %s", key, val)

                    if key.lower() == "appjar":
                        self.setTitle(val)
                    elif key.lower() == "splash":
                        if self.splashConfig is not None:
                            gui.trace("\t\t Updated SPLASH to: %s", val)
                            self.splashConfig['text'] = val
                        else:
                            gui.trace("\t\t No SPLASH to update")
                    elif key.lower() == "statusbar":
                        gui.trace("\tSetting STATUSBAR: %s", val)
                        self.setStatusbarHeader(val)
                    else:
                        try:
                            widgets[key].title(val)
                        except KeyError:
                            self.warn("Invalid SUBWINDOW: %s", key)

            elif kind in [WIDGET_NAMES.ListBox]:
                for k in widgets.keys():
                    lb = widgets[k]

                    # convert data to a list
                    if self.configParser.has_option(section, k):
                        data = self.configParser.get(section, k)
                    else:
                        data = lb.DEFAULT_TEXT
                    data = data.strip().split("\n")

                    # tidy up the list
                    data = [item.strip() for item in data if len(item.strip()) > 0]
                    self.updateListBox(k, data)

            elif kind in [WIDGET_NAMES.SpinBox]:
                for k in widgets.keys():
                    sb = widgets[k]

                    # convert data to a list
                    if self.configParser.has_option(section, k):
                        data = self.configParser.get(section, k)
                    else:
                        data = sb.DEFAULT_TEXT
                    data = data.strip().split("\n")

                    # tidy up the list
                    data = [item.strip() for item in data if len(item.strip()) > 0]
                    self.changeSpinBox(k, data)

            elif kind in [WIDGET_NAMES.OptionBox]:
                for k in widgets.keys():
                    ob = widgets[k]

                    # convert data to a list
                    if self.configParser.has_option(section, k):
                        data = self.configParser.get(section, k)
                    else:
                        data = ob.DEFAULT_TEXT
                    data = data.strip().split("\n")

                    # tidy up the list
                    data = [item.strip() for item in data if len(item.strip()) > 0]
                    self.changeOptionBox(k, data)

            elif kind in [WIDGET_NAMES.RadioButton]:
                for (key, val) in self.configParser.items(section):
                    gui.trace("\t\t%s: %s", key, val)
                    keys = key.split("-")
                    if len(keys) != 2:
                        self.warn("Invalid RADIOBUTTON key: %s", key)
                    else:
                        try:
                            rbs = self.widgetManager.get(WIDGET_NAMES.RadioButton, keys[0])
                        except KeyError:
                            self.warn("Invalid RADIOBUTTON key: %s", keys[0])
                            continue
                        for rb in rbs:
                            if rb.DEFAULT_TEXT == keys[1]:
                                rb["text"] = val
                                break

            elif kind in [WIDGET_NAMES.TabbedFrame]:
                for (key, val) in self.configParser.items(section):
                    gui.trace("\t\t%s: %s", key, val)
                    keys = key.split("-")
                    if len(keys) != 2:
                        self.warn("Invalid TABBEDFRAME key: %s", key)
                    else:
                        try:
                            self.setTabText(keys[0], keys[1], val)
                        except ItemLookupError:
                            self.warn("Invalid TABBEDFRAME: %s with TAB: %s" , keys[0], keys[1])

            elif kind in [WIDGET_NAMES.Properties]:
                for (key, val) in self.configParser.items(section):
                    gui.trace("\t\t%s: %s", key, val)
                    keys = key.split("-")
                    if len(keys) != 2:
                        self.warn("Invalid PROPERTIES key: %s", key)
                    else:
                        try:
                            self.setPropertyText(keys[0], keys[1], val)
                        except ItemLookupError:
                            self.warn("Invalid PROPERTIES: %s", keys[0])
                        except KeyError:
                            self.warn("Invalid PROPERTY: %s", keys[1])

            elif kind == WIDGET_NAMES.Tree:
                for (key, val) in self.configParser.items(section):
                    gui.trace("\t\t%s: %s", key, val)
                    keys = key.split("-")
                    if len(keys) != 2:
                        self.warn("Invalid GRID key: %s", key)
                    else:
                        if keys[1] not in ["actionHeading", "actionButton", "addButton"]:
                            self.warn("Invalid GRID label: %s for GRID: %s", keys[1], keys[0])
                        else:
                            try:
                                self.confGrid(keys[0], keys[1], val)
                            except ItemLookupError:
                                self.warn("Invalid GRID: %s", keys[0])

            elif kind == self.PAGEDWINDOW:
                for (key, val) in self.configParser.items(section):
                    gui.trace("\t\t%s: %s", key, val)
                    keys = key.split("-")
                    if len(keys) != 2:
                        self.warn("Invalid PAGEDWINDOW key: %s", key)
                    else:
                        if keys[1] not in ["prevButton", "nextButton", "title"]:
                            self.warn("Invalid PAGEDWINDOW label: %s for PAGEDWINDOW: %s", keys[1], keys[0])
                        else:
                            try:
                                widgets[keys[0]].config(**{keys[1]:val})
                            except KeyError:
                                self.warn("Invalid PAGEDWINDOW: %s", keys[0])

            elif kind == WIDGET_NAMES.Entry:
                for k in widgets.keys():
                    ent = widgets[k]

                    if self.configParser.has_option(section, k):
                        data = self.configParser.get(section, k)
                    else:
                        data = ent.DEFAULT_TEXT

                    gui.trace("\t\t%s: %s", k, data)
                    self.setEntryDefault(k, data)

            elif kind in [WIDGET_NAMES.Image]:
                for k in widgets.keys():
                    if self.configParser.has_option(section, k):
                        data = str(self.configParser.get(section, k))

                        try:
                            self.setImage(k, data)
                            gui.trace("\t\t%s: %s", k, data)
                        except:
                            self.error("Failed to update image: %s to: %s", k, data)
                    else:
                        gui.trace("No translation for: %s", k)

            elif kind in [WIDGET_NAMES.Label, WIDGET_NAMES.Button, WIDGET_NAMES.CheckBox, WIDGET_NAMES.Message,
                            WIDGET_NAMES.Link, WIDGET_NAMES.LabelFrame, self.TOGGLEFRAME]:
                for k in widgets.keys():
                    widg = widgets[k]

                    # skip validation labels - we don't need to translate them
                    try:
                        if kind == WIDGET_NAMES.Label and widg.isValidation:
                            gui.trace("\t\t%s: skipping, validation label", k)
                            continue
                    except:
                        pass

                    if self.configParser.has_option(section, k):
                        data = str(self.configParser.get(section, k))
                    else:
                        data = widg.DEFAULT_TEXT

                    gui.trace("\t\t%s: %s", k, data)
                    widg.config(text=data)

            elif kind == WIDGET_NAMES.Toolbar:
                for k in widgets.keys():
                    but = widgets[k]
                    if but.image is None:
                        if self.configParser.has_option(section, k):
                            data = str(self.configParser.get(section, k))
                        else:
                            data = but.DEFAULT_TEXT

                        gui.trace("\t\t%s: %s", k, data)
                        but.config(text = data)

            elif kind == "TOOLTIP":
                try:
                    kind = WIDGET_NAMES.name(WIDGET_NAMES.getIgnoreCase(section.split("-")[1]))
                    func = getattr(self, "set"+kind+"Tooltip")
                except KeyError:
                    self.warn("Invalid config section: TOOLTIP-%s", section)
                    return
                gui.trace("Parsing TOOLTIPs for: %s", kind)

                for (key, val) in self.configParser.items(section):
                    try:
                        func(key, val)
                    except ItemLookupError:
                        self.warn("Invalid TOOLTIP for: %s, with key: %s", kind, key)
                        continue
            else:
                self.warn("Unsupported widget: %s", section)
                continue

    language = property(getLanguage, changeLanguage)

    def showSplash(self, text="appJar", fill="#FF0000", stripe="#000000", fg="#FFFFFF", font=44):
        """ creates a splash screen to show at start up """
        self.splashConfig= {'text':text, 'fill':fill, 'stripe':stripe, 'fg':fg, 'font':font}


##################################################
### Stuff for logging
##################################################

    @staticmethod
    def setLogFile(fileName):
        """ sets the filename for logging messages """
        # Remove all handlers associated with the root logger object.
        for handler in logging.root.handlers[:]:
            logging.root.removeHandler(handler)
        logging.basicConfig(level=logging.INFO, filename=fileName, format='%(asctime)s %(name)s:%(levelname)s: %(message)s')
        gui.info("Switched to logFile: %s", fileName)

    def _setLogFile(self, fileName):
        ''' necessary so we can access this as a property '''
        gui.setLogFile(fileName)

    def getLogFile(self):
        return logging.root.handlers[0].baseFilename

    logFile = property(getLogFile, _setLogFile)

    @staticmethod
    def setLogLevel(level):
        """ main function for setting the logging level
            provide one of: INFO, DEBUG, WARNING, ERROR, CRITICAL, EXCEPTION, None """
        logging.getLogger("appJar").setLevel(getattr(logging, level.upper()))
        gui.info("Log level changed to: %s", level)


    def getLogLevel(self):
        return logging.getLevelName(logging.getLogger("appJar").getEffectiveLevel())

    def _setLogLevel(self, level):
        ''' necessary so we can access this as a property '''
        gui.setLogLevel(level)

    logLevel = property(getLogLevel, _setLogLevel)

    @staticmethod
    def exception(message, *args):
        """ wrapper for logMessage - setting level to EXCEPTION """
        gui.logMessage(message, "EXCEPTION", *args)

    @staticmethod
    def critical(message, *args):
        """ wrapper for logMessage - setting level to CRITICAL """
        gui.logMessage(message, "CRITICAL", *args)

    @staticmethod
    def error(message, *args):
        """ wrapper for logMessage - setting level to ERROR """
        gui.logMessage(message, "ERROR", *args)

    @staticmethod
    def warn(message, *args):
        """ wrapper for logMessage - setting level to WARNING """
        gui.logMessage(message, "WARNING", *args)

    @staticmethod
    def debug(message, *args):
        """ wrapper for logMessage - setting level to DEBUG """
        gui.logMessage(message, "DEBUG", *args)

    @staticmethod
    def trace(message, *args):
        """ wrapper for logMessage - setting level to TRACE """
        gui.logMessage(message, "TRACE", *args)

    @staticmethod
    def info(message, *args):
        """ wrapper for logMessage - setting level to INFO """
        gui.logMessage(message, "INFO", *args)

    @staticmethod
    def logMessage(msg, level, *args):
        """ allows user to log a message - provide a message and a log level
            any %s tags in the message will be replaced by the relevant positional *args """
        frames = inspect.stack()
        # try to ensure we only log extras if we're called from above functions
        if frames[1][3] in ("exception", "critical", "error", "warn", "debug", "trace", "info"):

            callFrame = ""
            try:
                progName = gui.exe_file
                for s in frames:
                    if progName in s[1]:
                        callFrame = s
                        break
            except: pass

            if callFrame != "":
                callFrame = "Line " + str(callFrame[2])

            # user generated call
            if "appjar.py" not in frames[2][1] or frames[2][3] == "handlerFunction":
                if callFrame != "":
                    msg = "[" + callFrame + "]: "+str(msg)

            # appJar logging
            else:
                if callFrame != "":
                    msg = "["+callFrame + "->" + str(frames[2][2]) +"/"+str(frames[2][3])+"]: "+str(msg)
                else:
                    msg = "["+str(frames[2][2]) +"/"+str(frames[2][3])+"]: "+str(msg)

        logger = logging.getLogger("appJar")
        level = level.upper()

        if level == "EXCEPTION": logger.exception(msg, *args)
        elif level == "CRITICAL": logger.critical(msg, *args)
        elif level == "ERROR": logger.error(msg, *args)
        elif level == "WARNING": logger.warning(msg, *args)
        elif level == "INFO": logger.info(msg, *args)
        elif level == "DEBUG": logger.debug(msg, *args)
        elif level == "TRACE": logger.trace(msg, *args)

##############################################################
# Event Loop - must always be called at end
##############################################################
    def __enter__(self):
        """ allows gui to be used as a ContextManager """
        gui.trace("ContextManager: initialised")
        return self

    def __exit__(self, eType, eValue, eTrace):
        """ allows gui to be used as a ContextManager
            - calls the go() function """
        if eType is not None:
            self.error("ContextManager failed: %s", eValue)
            return False
        else:
            gui.trace("ContextManager: starting")
            self.go(startWindow=self.startWindow)
            return True

    def go(self, language=None, startWindow=None):
        """ Most important function! starts the GUI """

        # check if we have a command line language
        if self._language is not None:
            language = self._language

        # if language is populated, we are in internationalisation mode
        # call the changeLanguage function - to re-badge all the widgets
        if language is not None:
            self.changeLanguage(language)

        if self.splashConfig is not None:
            gui.trace("SPLASH: %s", self.splashConfig)
            splash = SplashScreen(
                            self.topLevel,
                            self.splashConfig['text'],
                            self.splashConfig['fill'],
                            self.splashConfig['stripe'],
                            self.splashConfig['fg'],
                            self.splashConfig['font']
                            )
            self.topLevel.withdraw()
            self._bringToFront(splash)

        # check the containers have all been stopped
        if len(self.containerStack) > 1:
            for i in range(len(self.containerStack) - 1, 0, -1):
                kind = self.containerStack[i]['type']
                if kind != WIDGET_NAMES.Pane:
                    self.warn("No stopContainer called on: %s", WIDGET_NAMES.name(kind))

        # update any trees
        for k in self.widgetManager.group(WIDGET_NAMES.Tree):
            self.generateTree(k)

        # create appJar menu, if no menuBar created
        if not self.hasMenu:
            self.addAppJarMenu()

        if self.platform == self.WINDOWS:
            self.menuBar.add_cascade(menu=self.widgetManager.get(WIDGET_NAMES.Menu, "WIN_SYS"))
        self.topLevel.config(menu=self.menuBar)

        if startWindow is not None:
            self.startWindow = startWindow
            gui.trace("startWindow parameter: %s", startWindow)

        # pack it all in & make sure it's drawn
        self.appWindow.pack(fill=BOTH)
        if self.useSettings:
            self.loadSettings(self.settingsFile)
        self.topLevel.update_idletasks()

        # check geom is set and set a minimum size, also positions the window if necessary
        if not self.topLevel.locationSet:
            self.setLocation('CENTER')

        if not hasattr(self.topLevel, 'ms'):
            self.setMinSize()

        if self.splashConfig is not None:
            time.sleep(3)
            splash.destroy()

        # user hasn't specified anything
        if self.startWindow is None:
            if not self.topLevel.displayed:
                gui.trace("topLevel has been manually hidden - not showing in go()")
            else:
                gui.trace("Showing topLevel")
                self._bringToFront()
                self.topLevel.deiconify()
        else:
            gui.trace("hiding main window")
            self.hide()
            sw = self.widgetManager.get(WIDGET_NAMES.SubWindow, startWindow)
            if sw.blocking:
                raise Exception("Unable to start appjar with a blocking subWindow")

            self.showSubWindow(startWindow)

        # required to make the gui reopen after minimising
        if self.GET_PLATFORM() == self.MAC:self.topLevel.createcommand('tk::mac::ReopenApplication', self._macReveal)

        # start the call back & flash loops
        self._poll()
        self._flash()

        # register start-up function
        if self.topLevel.startFunction is not None:
            self.topLevel.after_idle(self.topLevel.startFunction)

        # start the main loop
        try:
            self.topLevel.mainloop()
        except(KeyboardInterrupt, SystemExit) as e:
            gui.trace("appJar stopped through ^c or exit()")
            self.stop()
        except Exception as e:
            self.exception(e)
            self.stop()

    def setStartFunction(self, func):
        f = self.MAKE_FUNC(func, "start")
        self.topLevel.startFunction = f

    startFunction = property(fset=setStartFunction)

    def _macReveal(self):
        """ internal function to deiconify GUIs on mac """
        if self.topLevel.state() != "withdrawn":
            self.topLevel.deiconify()
        for k, v in self.widgetManager.group(WIDGET_NAMES.SubWindow).items():
            if v.state() == "normal":
                self.showSubWindow(k)

    def setStopFunction(self, function):
        """ Set a function to call when the GUI is quit. Must return True or False """
        tl = self._getTopLevel()
        tl.stopFunction = function
        # link to exit item in topMenu
        # only if in root
        if self._getContainerProperty('type') != WIDGET_NAMES.SubWindow:
            tl.createcommand('exit', self.stop)

    stopFunction = property(fset=setStopFunction)

    def setSetting(self, name, value):
        """ adds a setting to the settings file """
        self.externalSettings[name] = value

    def getSetting(self, name, default=None):
        """ gets a setting form the settings file """
        try: return self.externalSettings[name]
        except: return default

    def saveSettings(self, fileName="appJar.ini"):
        """ saves the current settings to a file
            called automatically by stop() of settings were loaded at start """
        self._loadConfigParser()
        if not ConfigParser:
            self.error("Unable to save config file - no configparser")
            return

        settings = ConfigParser()
        settings.optionxform = str
        settings.add_section('GEOM')
        geom = self.topLevel.geometry()
        ms = self.topLevel.minsize()
        ms = "%s,%s" % (ms[0], ms[1])
        settings.set('GEOM', 'geometry', geom)
        gui.trace("Save geom as: %s", geom)
        settings.set('GEOM', 'minsize', ms)
        settings.set('GEOM', "fullscreen", str(self.topLevel.attributes('-fullscreen')))
        settings.set('GEOM', "state", str(self.topLevel.state()))

        # get toolbar setting
        if self.tb.inUse:
            gui.trace("Saving toolbar settings")
            settings.add_section("TOOLBAR")
            settings.set("TOOLBAR", "pinned", str(self.tb.pinned))

        # get container settings
        for k, v in self.widgetManager.group(WIDGET_NAMES.ToggleFrame).items():
            gui.trace("Saving toggle %s", k)
            if "TOGGLES" not in settings.sections(): settings.add_section("TOGGLES")
            settings.set("TOGGLES", k, str(v.isShowing()))

        for k, v in self.widgetManager.group(WIDGET_NAMES.TabbedFrame).items():
            gui.trace("Saving tab %s", k)
            if "TABS" not in settings.sections(): settings.add_section("TABS")
            settings.set("TABS", k, str(v.getSelectedTab()))

        for k, v in self.widgetManager.group(WIDGET_NAMES.PagedWindow).items():
            gui.trace("Saving page %s", k)
            if "PAGES" not in settings.sections(): settings.add_section("PAGES")
            settings.set("PAGES", k, str(v.getPageNumber()))

        for k, v in self.widgetManager.group(WIDGET_NAMES.SubWindow).items():
            if "SUBWINDOWS" not in settings.sections(): settings.add_section("SUBWINDOWS")
            if v.shown:
                v.update()
                settings.set("SUBWINDOWS", k, "True")
                settings.add_section(k)
                settings.set(k, "geometry", v.geometry())
                ms = v.minsize()
                settings.set(k, 'minsize', "%s,%s" % (ms[0], ms[1]))
                settings.set(k, "state", v.state())
                gui.trace("Saving subWindow %s: geom=%s, state=%s, minsize=%s", k, v.geometry(), v.state(), ms)
            else:
                settings.set("SUBWINDOWS", k, "False")
                gui.trace("Skipping subwindow: %s", k)

        for k, v in self.externalSettings.items():
            if "EXTERNAL" not in settings.sections(): settings.add_section("EXTERNAL")
            settings.set("EXTERNAL", k, str(v))

        # pane positions?
        # sub windows geom & visibility
        # scrollpane x & y positions
        # language
        # ttk
        # debug level

        with open(fileName, 'w') as theFile:
            settings.write(theFile)

    def loadSettings(self, fileName="appJar.ini", useSettings=True):
        """ loads setting from a settings file, and adjusts the GUI to match
            called by go() function, if user has requested settings """
        self._loadConfigParser()
        if not ConfigParser:
            self.error("Unable to save config file - no configparser")
            return

        self.useSettings = useSettings

        settings = ConfigParser()
        settings.optionxform = str
        settings.read(fileName)

        if settings.has_option("GEOM", "geometry"):
            geom = settings.get("GEOM", "geometry")
            if not self.topLevel.ignoreSettings:
                size, loc = gui.SPLIT_GEOM(geom)
                gui.trace("Setting topLevel geom: %s as size: %s, loc: %s", geom, size, loc)
                if size[0] > 1:
                    self.setSize(*size)
                if loc[0] != -1:
                    self.setLocation(*loc)
            else:
                gui.trace("Ignoring topLevel geom: %s", geom)

        # not finished
        if settings.has_option("GEOM", "fullscreen"):
            fs = settings.getboolean('GEOM', "fullscreen")
            gui.trace("Set fullscreen to: %s", fs)
            if fs: self.setFullscreen()
            else: self.exitFullscreen()

        if settings.has_option("GEOM", "minsize"):
            self.topLevel.ms = settings.get('GEOM', "minsize").split(",")
            self._getTopLevel().minsize(self.topLevel.ms[0], self.topLevel.ms[1])
            gui.trace("Set minsize to: %s", self.topLevel.ms)

        if settings.has_option("GEOM", "state"):
            state = settings.get('GEOM', "state")
            if state in ["withdrawn", "zoomed"]:
                self._getTopLevel().state(state)

        if settings.has_option("TOOLBAR", "pinned") and self.tb.inUse:
            tb = settings.getboolean("TOOLBAR", "pinned")
            self.setToolbarPinned(tb)
            gui.trace("Set toolbar to: %s", tb)

        if "TOGGLES" in settings.sections():
            for k in settings.options("TOGGLES"):
                try:
                    if self.getToggleFrameState(k) != settings.getboolean("TOGGLES", k):
                        self.toggleToggleFrame(k)
                except ItemLookupError:
                    gui.error("Settings error, invalid TOGGLES name: %s - discarding", k)

        if "TABS" in settings.sections():
            for k in settings.options("TABS"):
                try:
                    self.setTabbedFrameSelectedTab(k, settings.get("TABS", k))
                except ItemLookupError:
                    gui.error("Settings error, invalid TABS name: %s - discarding", k)

        if "PAGES" in settings.sections():
            for k in settings.options("PAGES"):
                try:
                    self.setPagedWindowPage(k, settings.getint("PAGES", k))
                except ItemLookupError:
                    gui.error("Settings error, invalid PAGES name: %s - discarding", k)

        if "SUBWINDOWS" in settings.sections():
            for k in settings.options("SUBWINDOWS"):
                if settings.getboolean("SUBWINDOWS", k):
                    gui.trace("Loading settings for %s", k)
                    try:
                        tl = self.widgetManager.get(WIDGET_NAMES.SubWindow, k)
                        # process the geom settings
                        if settings.has_option(k, "geometry"):
                            geom = settings.get(k, "geometry")
                            size, loc = gui.SPLIT_GEOM(geom)
                            if size[0] > 1:
                                gui.trace("Setting size: %s", size)
                                tl.geometry("%sx%s" % (size[0], size[1]))
                                tl.shown = True
                            else:
                                gui.trace("Skipping size: %s", size)
                            if loc[0] > -1:
                                gui.trace("Setting location: %s", loc)
                                self.setSubWindowLocation(k, *loc)
                            else:
                                gui.trace("Skipping location: %s", loc)
                        else:
                            gui.trace("No location found")

                        if settings.has_option(k, "minsize"):
                            ms = settings.get(k, "minsize").split(",")
                            self.setMinSize(tl, ms)

                        # set the state - if there' no startWindow
                        if self.startWindow is None:
                            try:
                                tl.state(settings.get(k, "state"))
                                gui.trace("Set state=%s", tl.state())
                            except: pass # no state found
                    except ItemLookupError:
                        gui.error("Settings error, invalid SUBWINDOWS name: %s - discarding.", k)
                else:
                    gui.trace("Skipping settings for %s", k)

        if "EXTERNAL" in settings.sections():
            for k in settings.options("EXTERNAL"):
                self.externalSettings[k] = settings.get("EXTERNAL", k)

    def stop(self, event=None):
        """ Closes the GUI. If a stop function is set, will only close the GUI if True """
        theFunc = self._getTopLevel().stopFunction
        if theFunc is None or theFunc():

            if self.useSettings:
                self.saveSettings(self.settingsFile)

            # stop the after loops
            self.alive = False
            self.topLevel.after_cancel(self.pollId)
            self.topLevel.after_cancel(self.flashId)
            if self.preloadAnimatedImageId:
                self.topLevel.after_cancel(self.preloadAnimatedImageId)
            if self.processQueueId:
                self.topLevel.after_cancel(self.processQueueId)

            # stop any animations
            for key in self.widgetManager.group(WIDGET_NAMES.AnimationID):
                self.topLevel.after_cancel(self.widgetManager.get(WIDGET_NAMES.AnimationID, key))

            # stop any maps
            for key in self.widgetManager.group(WIDGET_NAMES.Map):
                self.widgetManager.get(WIDGET_NAMES.Map, key).stopUpdates()

            # stop any sounds, ignore error when not on Windows
            try:
                self.stopSound()
            except:
                pass

            self.topLevel.quit()
            if not self.fastStop: self.topLevel.destroy()
            self.__class__.instantiated = False
            gui.info("--- GUI stopped ---")

    def setFastStop(self, fast=True):
        self._fastStop = fast

    def getFastStop(self):
        return self._fastStop

    fastStop = property(getFastStop, setFastStop)

#####################################
# Functions for configuring polling events
#####################################
    def setPollTime(self, time):
        """ Set a frequency for executing queued functions
            events will fire in order of being added, after sleeping for time """
        self.pollTime = time

    def registerEvent(self, func):
        """ Queue a function, to be executed every poll time """
        self.events.append(func)

    def after(self, delay_ms, callback=None, *args):
        """ wrapper for topLevel after function
            schedules the callback function to happen in x seconds
            returns an ID, allowing the event to be cancelled """
        return self.topLevel.after(delay_ms, callback, *args)

    def afterIdle(self, callback, *args):
        """ wrapper for topLevel after_idle function
            schedules the callback function to happen in x seconds
            returns an ID, allowing the event to be cancelled """
        return self.after_idle(callback, *args)

    def after_idle(self, callback, *args):
        """ wrapper for topLevel after_idle function
            schedules the callback function to happen in x seconds
            returns an ID, allowing the event to be cancelled """
        return self.topLevel.after_idle(callback, *args)

    def afterCancel(self, afterId):
        """ wrapper for topLevel after_cancel function
            tries to cancel the specified callback """
        return self.after_cancel(afterId)

    def after_cancel(self, afterId):
        """ wrapper for topLevel after_cancel function
            tries to cancel the specified callback """
        return self.topLevel.after_cancel(afterId)

    def queueFunction(self, func, *args, **kwargs):
        """ adds the specified function & arguments to the event queue
        Functions in the event queue are actioned by the gui's main thread

        :param func: the function to call
        :param *args: any number of ordered arguments
        :param **kwargs: any number of named arguments
        :raises Full: if unable to add the function to the queue
        """
        self._loadThreading()
        if Queue is False:
            gui.warn("Unable to queueFunction - threading not possible.")
        else:
            self.eventQueue.put((5, func, args, kwargs), block=False)

    def queuePriorityFunction(self, func, *args, **kwargs):
        """ queues the function with a higher priority - not working yet """
        self._loadThreading()
        if Queue is False:
            gui.warn("Unable to queueFunction - threading not possible.")
        else:
            self.eventQueue.put((1, func, args, kwargs), block=False)

    def _processEventQueue(self):
        """ internal function to process events in the event queue
            put there by queue function """
        if not self.alive: return
        if not self.eventQueue.empty():
            priority, func, args, kwargs = self.eventQueue.get()
            gui.trace("FUNCTION: %s(%s)", func, args)
            func(*args, **kwargs)

        self.processQueueId = self.after(self.EVENT_SPEED, self._processEventQueue)

    def thread(self, func, *args, **kwargs):
        """ will run the supplied function in a separate thread

        param func: the function to run
        """
        self._loadThreading()
        if Queue is False:
            gui.warn("Unable to queueFunction - threading not possible.")
        else:
            t = Thread(group=None, target=func, name=None, args=args, kwargs=kwargs)
            t.daemon = True
            t.start()

    def callback(self, *args, **kwargs):
        """Shortner for threadCallback."""
        return self.threadCallback(*args, **kwargs)

    def threadCallback(self, func, callback, *args, **kwargs):
        """Run a given method in a new thread with passed arguments.
           When func completes call the callback with the result.

           :param func: Method that returns the result.
           :param callback: Method that receives the result.
           :param args: Positional arguments for func.
           :param kwargs: Keyword args for func.
        """
        def innerThread(func, callback, *args, **kwargs):
            result = func(*args, **kwargs)
            self.queueFunction(callback, result)

        if not callable(func) or not callable(callback):
            gui.error("Function (or callback) method isn't callable!")
            return
        self.thread(innerThread, func, callback, *args, **kwargs)

    # internal function, called by 'after' function, after sleeping
    def _poll(self):
        """ internal function, called by 'after' function, after sleeping """
        if not self.alive: return
        # run any registered actions
        for e in self.events:
            # execute the event
            e()
        self.pollId = self.topLevel.after(self.pollTime, self._poll)

    def _windowEvent(self, event):
        """ called whenever the GUI updates - does nothing """
        new_width = self.topLevel.winfo_width()
        new_height = self.topLevel.winfo_height()

    def enableEnter(self, func, replace=False):
        """ Binds <Return> to the specified function - all widgets """
        self.bindKey("Return", func, replace)

    def disableEnter(self):
        """ unbinds <Return> from all widgets """
        self.unbindKey("Return")

    def _enterWrapper(self, func):
        if func is None:
            self.disableEnter()
        else:
            self.enableEnter(func, replace=True)

    enterKey = property(fset=_enterWrapper)

    def bindKeys(self, keys, func):
        """ bind the specified keys, to the specified function, for all widgets """
        for key in keys:
            self.bindKey(key, func)

    def bindKey(self, key, func, replace=False):
        """ bind the specified key, to the specified function, for all widgets """
        if replace:
            try: self.unbindKey(key)
            except: pass
        # for now discard the Event...
        myF = self.MAKE_FUNC(func, key)
        binding = EventBinding(key, myF, self._getTopLevel(), menuBinding=False)
        try:
            self.widgetManager.add(WIDGET_NAMES.Bindings, binding.displayName, binding)
            binding.createBindings()
        except ItemLookupError:
            raise ItemLookupError('Unable to bind key ' + binding.displayName + ' - binding already exists')

    def unbindKeys(self, keys):
        """ unbinds the specified keys from whatever functions they are bound to """
        for key in keys:
            self.unbindKey(key)

    def unbindKey(self, key):
        """ unbinds the specified key from whatever functions it is bound to """
        if key[0] == "<":
            gui.warn("Shortcuts should not include chevrons: %s", key)
            key= key[1:-1]
        self.widgetManager.get(WIDGET_NAMES.Bindings, key).removeBindings()
        self.widgetManager.remove(WIDGET_NAMES.Bindings, key)

    def _isMouseInWidget(self, w):
        """ helper - returns True if the mouse is in the specified widget """
        l_x = w.winfo_rootx()
        l_y = w.winfo_rooty()

        if l_x <= w.winfo_pointerx() <= l_x + \
                w.winfo_width() and l_y <= w.winfo_pointery() <= l_y + w.winfo_height():
            return True
        else:
            return False

    # function to give a clicked widget the keyboard focus
    def _grabFocus(self, e):
        """ gives the specified widget the focus """
        e.widget.focus_set()


#####################################
# FUNCTIONS for configuring GUI settings
#####################################

    def setSize(self, geom, height=None, ignoreSettings=None):
        """ called to update screen geometry
            can take a geom string, or a width & height
            can override ignoreSettings if desired """
        container = self._getTopLevel()
        if ignoreSettings is not None:
            container.ignoreSettings = ignoreSettings

        try:
            geom = geom.lower()
        except:
            # ignore - other data types allowed
            pass

        if geom == "fullscreen":
            self.setFullscreen()
        elif geom is not None:
            if height is not None:
                geom=(geom, height)
            elif not isinstance(geom, list) and not isinstance(geom, tuple):
                geom, loc = gui.SPLIT_GEOM(geom)

            size = "%sx%s" % (int(geom[0]), int(geom[1]))
            gui.trace("Setting size: %s", size)

            # warn the user that their geom is not big enough
            dims = gui.GET_DIMS(container)
            if geom[0] < dims["b_width"] or geom[1] < dims["b_height"]:
                self.trace("Specified dimensions (%s, %s) less than requested dimensions (%s, %s)",
                        geom[0], geom[1], dims["b_width"], dims["b_height"])

            # and set it as the minimum size
            if not hasattr(container, 'ms'):
                self.setMinSize(container, geom)

            self.exitFullscreen()
            container.geometry(size)

    def getSize(self):
        container = self._getTopLevel()
        size, loc = gui.SPLIT_GEOM(container.geometry())
        return size

    size = property(getSize, setSize)

    def setMinSize(self, container=None, size=None):
        """ sets a minimum size for the specified container - defaults to the whole GUI """
        if container is None: container = self.topLevel
        if size is None: size = (gui.GET_DIMS(container)["r_width"], gui.GET_DIMS(container)["r_height"])
        container.ms = size
        container.minsize(size[0], size[1])
        gui.trace("Minsize set to: %s", size)

    def setLocation(self, x, y=None, ignoreSettings=None, win=None, up=0):
        """ called to set the GUI's position on screen """
        if win is None:
            win = self._getTopLevel()
        gui.SET_LOCATION(x, y, ignoreSettings, win, up)

    def getLocation(self):
        container = self._getTopLevel()
        size, loc = gui.SPLIT_GEOM(container.geometry())
        return loc

    location = property(getLocation, setLocation)

    def _bringToFront(self, win=None):
        """ called to make sure this window is on top of other windows """
        if win is None:
            win = self.topLevel
            top = self.top
        else:
            top = win.attributes('-topmost')

        if self.platform == self.MAC:
            import subprocess
            tmpl = 'tell application "System Events" to set frontmost of every process whose unix id is {0} to true'
            script = tmpl.format(os.getpid())
            subprocess.check_call(['/usr/bin/osascript', '-e', script])
            win.after( 0, lambda: win.attributes("-topmost", top))
#            val=os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "''' + PY_NAME + '''" to true' ''')
            win.lift()
        elif self.platform == self.WINDOWS:
            win.lift()
        elif self.platform == self.LINUX:
            win.lift()

    def setFullscreen(self, title=None):
        """ sets the specified window to be fullscreen
            if no title, will set the main GUI """
        try:
            container = self.widgetManager.get(WIDGET_NAMES.SubWindow, title)
        except:
            container = self._getTopLevel()

        if not container.isFullscreen:
            container.isFullscreen = True
            container.attributes('-fullscreen', True)
            container.escapeBindId = container.bind('<Escape>', self.MAKE_FUNC(self.exitFullscreen, container), "+")

    def getFullscreen(self, title=None):
        if title is None:
            container = self._getTopLevel()
        else:
            container = self.widgetManager.get(WIDGET_NAMES.SubWindow, title)

        return container.isFullscreen

    def setOnTop(self, stay=True):
        self._getTopLevel().attributes("-topmost", stay)
        gui.trace("Staying on top set to: %s", stay)

    def getOnTop(self):
        return self._getTopLevel().attributes("-topmost") == 1

    top = property(getOnTop, setOnTop)

    def _changeFullscreen(self, flag):
        if flag: self.setFullscreen()
        else: self.exitFullscreen()

    fullscreen = property(getFullscreen, _changeFullscreen)

    def exitFullscreen(self, container=None):
        """ turns off fullscreen mode for the specified window """
        if container is None or isinstance(container, UNIVERSAL_STRING):
            try:
                container = self.widgetManager.get(WIDGET_NAMES.SubWindow, container)
            except:
                container = self._getTopLevel()

        if container.isFullscreen:
            container.isFullscreen = False
            container.attributes('-fullscreen', False)
            if container.escapeBindId is not None:
                container.unbind('<Escape>', container.escapeBindId)
            with PauseLogger():
                self._doTitleBar()
            return True
        else:
            return False

    def setPadX(self, x=0):
        """ set the current container's external grid padding """
        self.containerStack[-1]['padx'] = x

    def setPadY(self, y=0):
        """ set the current container's external grid padding """
        self.containerStack[-1]['pady'] = y

    def setPadding(self, x, y=None):
        """ sets the padding around the border of the current container """
        x, y = gui.PARSE_TWO_PARAMS(x, y)
        self.containerStack[-1]['padx'] = x
        self.containerStack[-1]['pady'] = y

    def getPadding(self):
        return self._getContainerProperty('padx'), self._getContainerProperty('pady')

    padding = property(getPadding, setPadding)

    def config(self, **kwargs):
        self.configure(**kwargs)

    def configure(self, **kwargs):
        title = kwargs.pop("title", None)
        icon = kwargs.pop("icon", None)
        transparency = kwargs.pop("transparency", None)
        visible = kwargs.pop("visible", None)
        top = kwargs.pop("top", None)

        padding = kwargs.pop("padding", None)
        inPadding = kwargs.pop("inPadding", None)
        guiPadding = kwargs.pop("guiPadding", None)

        size = kwargs.pop("size", None)
        location = kwargs.pop("location", None)
        fullscreen = kwargs.pop("fullscreen", None)
        resizable = kwargs.pop("resizable", None)

        sticky = kwargs.pop("sticky", None)
        stretch = kwargs.pop("stretch", None)
        expand = kwargs.pop("expand", None)
        row = kwargs.pop("row", None)
        colspan = kwargs.pop("colspan", None)
        rowspan = kwargs.pop("rowspan", None)

        fg = kwargs.pop("fg", None)
        bg = kwargs.pop("bg", None)
        font = kwargs.pop("font", None)
        buttonFont = kwargs.pop("buttonFont", None)
        labelFont = kwargs.pop("labelFont", None)
        inputFont = kwargs.pop("inputFont", None)
        statusFont = kwargs.pop("statusFont", None)
        ttkTheme = kwargs.pop("ttkTheme", None)

        editMenu = kwargs.pop("editMenu", None)
        # two possible names
        stopFunction = kwargs.pop("stop", kwargs.pop("stopFunction", None))
        startFunction = kwargs.pop("start", kwargs.pop("startFunction", None))
        fastStop = kwargs.pop("fastStop", None)
        enterKey = kwargs.pop("enterKey", None)
        logLevel = kwargs.pop("log", kwargs.pop("logLevel", None))
        logFile = kwargs.pop("file", kwargs.pop("logFile", None))
        language = kwargs.pop("language", None)

        for k, v in kwargs.items():
            gui.error("Invalid config parameter: %s, %s", k, v)

        if title is not None: self.title = title
        if icon is not None: self.icon = icon
        if transparency is not None: self.transparency = transparency
        if visible is not None: self.visible = visible
        if top is not None: self.top = top

        if padding is not None: self.padding = padding
        if inPadding is not None: self.inPadding = inPadding
        if guiPadding is not None: self.guiPadding = guiPadding

        if size is not None: self.size = size
        if location is not None: self.location = location
        if fullscreen is not None: self.fullscreen = fullscreen
        if resizable is not None: self.resizable = resizable

        if sticky is not None: self.sticky = sticky
        if expand is not None: self.expand = expand
        if stretch is not None: self.stretch = stretch
        if row is not None: self.row = row
        if rowspan is not None: self.rowspan = rowspan
        if colspan is not None: self.colspan = colspan

        if fg is not None: self.fg = fg
        if bg is not None: self.bg = bg

        if font is not None: self.font = font
        if labelFont is not None: self.labelFont = labelFont
        if buttonFont is not None: self.buttonFont = buttonFont
        if inputFont is not None: self.inputFont = inputFont
        if statusFont is not None: self.statusFont = statusFont
        if ttkTheme is not None: self.ttkTheme = ttkTheme

        if editMenu is not None: self.editMenu = editMenu
        if stopFunction is not None: self.stopFunction = stopFunction
        if startFunction is not None: self.startFunction = startFunction
        if fastStop is not None: self.fastStop = fastStop
        if enterKey is not None: self.enterKey = enterKey
        if logLevel is not None: self.logLevel = logLevel
        if logFile is not None: self.logFile = logFile
        if language is not None: self.language = language

    def setGuiPadding(self, x, y=None):
        """ sets the padding around the border of the GUI """
        x, y = gui.PARSE_TWO_PARAMS(x, y)
        self.containerStack[0]['container'].config(padx=x, pady=y)

    def getGuiPadding(self):
        return int(str(self.containerStack[0]['container'].cget('padx'))), int(str(self.containerStack[0]['container'].cget('pady')))

    guiPadding = property(getGuiPadding, setGuiPadding)

    # sets the current containers internal padding
    def setIPadX(self, x=0):
        self.setInPadX(x)

    def setIPadY(self, y=0):
        self.setInPadY(y)

    def setIPadding(self, x, y=None):
        self.setInPadding(x, y)

    def setInPadX(self, x=0):
        self.containerStack[-1]['ipadx'] = x

    def setInPadY(self, y=0):
        self.containerStack[-1]['ipady'] = y

    def setInPadding(self, x, y=None):
        x, y = gui.PARSE_TWO_PARAMS(x, y)
        self.containerStack[-1]['ipadx'] = x
        self.containerStack[-1]['ipady'] = y

    def getInPadding(self):
        return self._getContainerProperty('ipadx'), self._getContainerProperty('ipady')

    inPadding = property(getInPadding, setInPadding)

    # set an override sticky for this container
    def setSticky(self, sticky):
        self.containerStack[-1]['sticky'] = sticky

    def getSticky(self):
        return self._getContainerProperty('sticky')

    # property for setTitle
    sticky = property(getSticky, setSticky)

    # this tells widgets what to do when GUI is resized
    def setStretch(self, exp):
        self.setExpand(exp)

    def getStretch(self):
        return self.getExpand()

    stretch = property(getStretch, setStretch)

    def getExpand(self):
        return self._getContainerProperty('expand')

    def setExpand(self, exp):
        if exp is None or exp.lower() == "none":
            self.containerStack[-1]['expand'] = "NONE"
        elif exp.lower() == "row":
            self.containerStack[-1]['expand'] = "ROW"
        elif exp.lower() == "column":
            self.containerStack[-1]['expand'] = "COLUMN"
        else:
            self.containerStack[-1]['expand'] = "ALL"

    expand = property(getExpand, setExpand)

    def RANDOM_COLOUR(self):
        return self.getRandomColour()

    def getRandomColour(self):
        """ generates a random colour """
        self._loadRandom()
        de=("%02x"%random.randint(0,255))
        re=("%02x"%random.randint(0,255))
        we=("%02x"%random.randint(0,255))
        return "#"+de+re+we

    randomColour = property(getRandomColour)

    def getFonts(self):
        fonts = list(tkFont.families())
        fonts.sort()
        return fonts

    fonts = property(getFonts)

    def increaseFont(self):
        self.increaseLabelFont()
        self.increaseButtonFont()

    def decreaseFont(self):
        self.decreaseLabelFont()
        self.decreaseButtonFont()

    def increaseButtonFont(self):
        self.setButtonFont(size=self._buttonFont['size'] + 1)

    def decreaseButtonFont(self):
        self.setButtonFont(size=self._buttonFont['size'] - 1)

    def increaseLabelFont(self):
        self.setLabelFont(size=self._labelFont['size'] + 1)

    def decreaseLabelFont(self):
        self.setLabelFont(size=self._labelFont['size'] - 1)

    def setFont(self, *args, **kwargs):
        self.setInputFont(*args, **kwargs)
        self.setLabelFont(*args, **kwargs)
        self.setButtonFont(*args, **kwargs)

    def getFont(self):
        return self._getContainerProperty('labelFont').actual()

    font = property(getFont, setFont)

    def _fontHelper(self, font, *args, **kwargs):
        if len(args) > 0:
            if isinstance(args[0], int):
                kwargs={'size':args[0]}
            elif isinstance(args[0], dict):
                kwargs=args[0]
            elif isinstance(args[0], tkFont.Font):
                gui.trace("%s set to new object", font)
                if font != "statusFont": self.containerStack[-1][font]=args[0]
                else: self._statusFont=args[0]
                return None
        if font != "statusFont":
            self._getContainerProperty(font).config(**kwargs)
            f = self._getContainerProperty(font).actual()['family'].lower()
        else:
            self._statusFont.config(**kwargs)
            f = self._statusFont.actual()['family'].lower()

        if 'family' in kwargs and kwargs['family'].lower() != f:
            gui.error("Failed to adjust %s to %s.", font, kwargs['family'])
        return kwargs

    def setInputFont(self, *args, **kwargs):
        self._fontHelper('inputFont', *args, **kwargs)

    def getInputFont(self):
        return self._getContainerProperty('inputFont').actual()

    inputFont = property(getInputFont, setInputFont)

    def setStatusFont(self, *args, **kwargs):
        self._fontHelper('statusFont', *args, **kwargs)

    def getStatusFont(self):
        return self._statusFont.actual()

    statusFont = property(getStatusFont, setStatusFont)

    def setButtonFont(self, *args, **kwargs):
        self._fontHelper('buttonFont', *args, **kwargs)

    def getButtonFont(self):
        return self._getContainerProperty('buttonFont').actual()

    buttonFont = property(getButtonFont, setButtonFont)

    def setLabelFont(self, *args, **kwargs):
        kwargs = self._fontHelper('labelFont', *args, **kwargs)
        if kwargs is not None:
            self.tableFont.config(**kwargs)

            # need better way to register font change events on tables
            for k, v in self.widgetManager.group(WIDGET_NAMES.Table).items():
                v.config(font=self.tableFont)

            linkArgs = kwargs.copy()
            linkArgs['underline'] = True
            linkArgs['weight'] = 'bold'
            self._linkFont.config(**linkArgs)

    def getLabelFont(self):
        return self._getContainerProperty('labelFont').actual()

    labelFont = property(getLabelFont, setLabelFont)

    # need to set a default colour for container
    # then populate that field
    # then use & update that field accordingly
    # all widgets will then need to use it
    # and here we update all....
    def setFg(self, colour, override=False):
        if not self.ttkFlag:
            self.containerStack[-1]['fg']=colour
            gui.SET_WIDGET_FG(self._getContainerProperty('container'), colour, override)

            for child in self._getContainerProperty('container').winfo_children():
                if not self._isWidgetContainer(child):
                    gui.SET_WIDGET_FG(child, colour, override)
        else:
            gui.trace("In ttk mode - trying to set FG to %s", colour)
            self.ttkStyle.configure("TLabel", foreground=colour)
            self.ttkStyle.configure("TFrame", foreground=colour)

    def getBg(self):
        if self._getContainerProperty('type') == WIDGET_NAMES.RootPage:
            if not self.ttkFlag:
                return self.bgLabel.cget("bg")
            else:
                return self.bgLabel.cget("background")
        else:
            if not self.ttkFlag:
                return self._getContainerProperty('container').cget("bg")
            else:
                return None

    def getFg(self):
        return self._getContainerProperty("fg")

    fg = property(getFg, setFg)

    # self.topLevel = Tk()
    # self.appWindow = CanvasDnd, fills all of self.topLevel
    # self.tb = Frame, at top of appWindow
    # self.container = Frame, at bottom of appWindow => C_ROOT container
    # self.bglabel = Label, filling all of container
    def setBg(self, colour, override=False, tint=False):
        if not self.ttkFlag:
            if self._getContainerProperty('type') == WIDGET_NAMES.RootPage:
# removed this - it makes the screen do funny stuff
#                self.appWindow.config(background=colour)
                self.bgLabel.config(background=colour)

            self._getContainerProperty('container').config(background=colour)

            for child in self._getContainerProperty('container').winfo_children():
                if not self._isWidgetContainer(child):

                    # horrible hack to deal with weird ScrolledText
                    # winfo_children returns ScrolledText as a Frame
                    # therefore can't call some functions
                    # this gets the ScrolledText version
                    if gui.GET_WIDGET_CLASS(child) == "Frame":
                        for val in self.widgetManager.group(WIDGET_NAMES.TextArea).values():
                            if str(val) == str(child):
                                child = val
                                break

                    gui.SET_WIDGET_BG(child, colour, override, tint)
        else:
            gui.trace("In ttk mode - trying to set BG to %s", colour)
            self.ttkStyle.configure(".", background=colour)

    bg = property(getBg, setBg)

    @staticmethod
    def _isWidgetContainer(widget):
        try:
            if widget.isContainer:
                return True
        except:
            pass
        return False

    def setResizable(self, canResize=True):
        self._getTopLevel().isResizable = canResize
        if self._getTopLevel().isResizable:
            self._getTopLevel().resizable(True, True)
        else:
            self._getTopLevel().resizable(False, False)

    def getResizable(self):
        return self._getTopLevel().isResizable

    resizable = property(getResizable, setResizable)

    def _doTitleBar(self):
        if self.platform == self.MAC:
            self.warn("Title bar hiding doesn't work on MAC - app may become unresponsive.")
        elif self.platform == self.LINUX:
            self.warn("Title bar hiding doesn't work on LINUX - app may become unresponsive.")
        self._getTopLevel().overrideredirect(not self.hasTitleBar)

    def hideTitleBar(self):
        self.hasTitleBar = False
        self._doTitleBar()

    def showTitleBar(self):
        self.hasTitleBar = True
        self._doTitleBar()

    # function to set the window's title
    def setTitle(self, title):
        self._getTopLevel().title(title)

    # function to get the window title
    def getTitle(self):
        return self._getTopLevel().title()

    # property for setTitle
    title = property(getTitle, setTitle)

    # set an icon
    def setIcon(self, image):
        container = self._getTopLevel()
        container.winIcon = image
        if image.endswith('.ico'):
            container.wm_iconbitmap(image)
        else:
            icon = self._getImage(image)
            container.iconphoto(True, icon)

    def getIcon(self):
        container = self._getTopLevel()
        return container.winIcon

    # property for setTitle
    icon = property(getIcon, setIcon)

    def _getCanvas(self, param=-1):
        if len(self.containerStack) > 1 and self.containerStack[param]['type'] == WIDGET_NAMES.SubWindow:
            return self.containerStack[param]['container']
        elif len(self.containerStack) > 1:
            return self._getCanvas(param-1)
        else:
            return self.topLevel

    def _getTopLevel(self):
        if len(self.containerStack) > 1 and self._getContainerProperty('type') == WIDGET_NAMES.SubWindow:
            return self._getContainerProperty('container')
        else:
            return self.topLevel

    # make the window transparent (between 0 & 1)
    def setTransparency(self, percentage):
        if self.platform == self.LINUX:
            self.warn("Transparency not supported on LINUX")
        else:
            if percentage > 1:
                percentage = float(percentage) / 100
            self._getTopLevel().attributes("-alpha", percentage)

    def getTransparency(self):
        return self._getTopLevel().attributes("-alpha") * 100

    # property for setTransparency
    transparency = property(getTransparency, setTransparency)

##############################
# functions to deal with tabbing and right clicking
##############################
    def _focusNextWindow(self, event):
        event.widget.tk_focusNext().focus_set()
        nowFocus = self.topLevel.focus_get()
        if isinstance(nowFocus, Entry):
            nowFocus.select_range(0, END)
        return("break")

    def _focusLastWindow(self, event):
        event.widget.tk_focusPrev().focus_set()
        nowFocus = self.topLevel.focus_get()
        if isinstance(nowFocus, Entry):
            nowFocus.select_range(0, END)
        return("break")

    # creates relevant bindings on the widget
    def _addRightClickMenu(self, widget):
        if self.platform in [self.WINDOWS, self.LINUX]:
            widget.bind('<Button-3>', self._rightClick)
        else:
            widget.bind('<Button-2>', self._rightClick)

    def _rightClick(self, event, menu="EDIT"):
        event.widget.focus()
        if menu == "EDIT":
            if self._prepareCopyAndPasteMenu(event):
                self.widgetManager.get(WIDGET_NAMES.Menu, menu).focus_set()
                self.widgetManager.get(WIDGET_NAMES.Menu, menu).post(event.x_root - 10, event.y_root - 10)
        else:
            self.widgetManager.get(WIDGET_NAMES.Menu, menu).focus_set()
            self.widgetManager.get(WIDGET_NAMES.Menu, menu).post(event.x_root - 10, event.y_root - 10)
        return "break"

#####################################
# FUNCTION to configure widgets
#####################################

    def configureAllWidgets(self, kind, option, value):
        items = list(self.widgetManager.group(kind))
        self.configureWidgets(kind, items, option, value)

    def configureWidgets(self, kind, names, option, value):
        if not isinstance(names, list):
            self.configureWidget(kind, names, option, value)
        else:
            for widg in names:
                # incase 2D array, eg. buttons
                if isinstance(widg, list):
                    for widg2 in widg:
                        self.configureWidget(kind, widg2, option, value)
                else:
                    self.configureWidget(kind, widg, option, value)

    def getWidget(self, kind, name, val=None):
        # if val is set (RadioButtons) - append it
        if val is not None: name+= "-" + val
        return self.widgetManager.get(kind, name)

    def getWidgetProperty(self, kind, name, val, prop):
        return self.getWidget(kind, name, val).cget(prop)

    def addWidget(self, title, widg, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a generic widget to the appJar grid manager '''
        self.widgetManager.verify(WIDGET_NAMES.Widget, title)
        self._positionWidget(widg, row, column, colspan, rowspan)
        self.widgetManager.add(WIDGET_NAMES.Widget, title, widg)

    def _getWidgetList(self, kind, name, limit):
        # gets a list of items of this type
        # limit is used to only get a single radio button - for events
        if kind == WIDGET_NAMES.RadioButton:
            items = self.widgetManager.group(kind)
            new_items = []
            for k, v in items.items():
                if k.startswith(name+"-"):
                    new_items.append(v)

            if len(new_items) == 0:
                raise Exception("No RadioButtons found with that name " + name)
            else:
                items = new_items
                # stops multiple events...
                if limit: items = [items[0]]
        else:
            # get the list of items for this type, and validate the widget is in the list
            self.widgetManager.check(kind, name)
            items = self.widgetManager.group(kind)
            items = [items[name]]
        return items

    def configureWidget(self, kind, name, option, value, key=None, deprecated=False):
        gui.trace("Configuring: %s of %s with %s of %s", name, kind, option, value)
        # warn about deprecated functions
        if deprecated:
            self.warn("Deprecated config function (%s) used for %s -> %s use %s deprecated", option, WIDGET_NAMES.name(kind), name, deprecated)

        # will return multiple items if radio button...
        items = self._getWidgetList(kind, name, limit=option in ['change', 'command'])

        # loop through each item, and try to reconfigure it
        # this will often fail - widgets have varied config options
        for item in items:
            try:
                if option == 'background':
                    gui.SET_WIDGET_BG(item, value, True)
                elif option == 'foreground':
                    gui.SET_WIDGET_FG(item, value, True)
                elif option == 'disabledforeground':
                    item.config(disabledforeground=value)
                elif option == 'disabledbackground':
                    item.config(disabledbackground=value)
                elif option == 'activeforeground':
                    item.config(activeforeground=value)
                elif option == 'activebackground':
                    item.config(activebackground=value)
                elif option == 'inactiveforeground':
                    if kind in [WIDGET_NAMES.TabbedFrame, WIDGET_NAMES.Table]:
                        item.config(inactiveforeground=value)
                    else:
                        self.warn("Error configuring %s: can't set inactiveforeground", name )
                elif option == 'inactivebackground':
                    if kind in [WIDGET_NAMES.TabbedFrame, WIDGET_NAMES.Table]:
                        item.config(inactivebackground=value)
                    else:
                        self.warn("Error configuring %s: can't set inactivebackground", name)
                elif option == 'width':
                    item.config(width=value)
                elif option == 'height':
                    item.config(height=value)
                elif option == 'state':
                    # make entries readonly - can still copy/paste
                    but = None
                    if kind == WIDGET_NAMES.Entry:
                        if value == "disabled" and hasattr(item, 'but'):
                            but = item.but
                            item.unbind("<Button-1>")
                            value = "readonly"
                        elif value == 'normal' and hasattr(item, 'but') and item.cget('state') != 'normal':
                            but = item.but
                            item.bind("<Button-1>", item.click_command, "+")

                    if self.ttkFlag:
                        gui.trace("%s configured with ttk state %s", name, value)
                        item.state([value])
                        if but is not None: but.state([value])
                    else:
                        item.config(state=value)
                        if but is not None: but.config(state=value)

                elif option == 'relief':
                    item.config(relief=value)
                elif option == 'style':
                    if self.ttkFlag:
                        gui.trace("%s configured with ttk style %s", name, value)
                        item.config(style=value)
                    else:
                        self.warn("Error configuring %s: can't set ttk style, not in ttk mode.", name)
                elif option in ['align', 'anchor']:
                    if kind == WIDGET_NAMES.Entry or gui.GET_WIDGET_CLASS(item) == 'SelectableLabel':
                        if value == W: value = LEFT
                        elif value == E: value = RIGHT
                        item.config(justify=value)
                    elif kind == WIDGET_NAMES.LabelFrame:
                        item.config(labelanchor=value)
                    else:
                        if value == LEFT: value = "w"
                        elif value == RIGHT: value = "e"
                        item.config(anchor=value)
                elif option == 'cursor':
                    item.config(cursor=value)
                elif option == 'tooltip':
                    self._addTooltip(item, value)
                elif option == 'disableTooltip':
                    self._disableTooltip(item)
                elif option == 'enableTooltip':
                    self._enableTooltip(item)
                elif option == "focus":
                    item.focus_set()
                    if kind == WIDGET_NAMES.Entry:
                        if not self.ttkFlag:
                            item.icursor(END)
                            item.xview(END)
                        else:
                            item.icursor(END)
                            item.xview(len(item.get()))

                # event bindings
                elif option == 'over':
                    self._bindOverEvent(kind, name, item, value, option, key)
                elif option == 'drag':
                    self._bindDragEvent(kind, name, item, value, option, key)
                elif option in ['command', "change", "submit"]:
                    self._bindEvent(kind, name, item, value, option, key)

                elif option == 'sticky':
                    info = {}
                    # need to reposition the widget in its grid
                    if self._widgetHasContainer(kind, item):
                        # pack uses LEFT & RIGHT & BOTH
                        info["side"] = value
                        if value.lower() == "both":
                            info["expand"] = 1
                            info["side"] = "right"
                        else:
                            info["expand"] = 0
                    else:
                        # grid uses E+W
                        if value.lower() == "left":
                            side = W
                        elif value.lower() == "right":
                            side = E
                        elif value.lower() == "both":
                            side = W + E
                        else:
                            side = value.upper()
                        info["sticky"] = side
                    self._repackWidget(item, info)
                elif option == 'padding':
                    if value[1] is None:
                        item.config(padx=value[0][0], pady=value[0][1])
                    else:
                        item.config(padx=value[0], pady=value[1])
                elif option == 'ipadding':
                    if value[1] is None:
                        item.config(ipadx=value[0][0], ipady=value[0][1])
                    else:
                        item.config(ipadx=value[0], ipady=value[1])
                elif option == 'rightClick':
                    self._bindRightClick(item, value)

                elif option == 'internalDrop':
                    self._registerInternalDropTarget(item, value)

                elif option == 'internalDrag':
                    self._registerInternalDragSource(kind, name, item, value)

                elif option == 'externalDrop':
                    self._registerExternalDropTarget(name, item, value[0], value[1])

                elif option == 'externalDrag':
                    self._registerExternalDragSource(name, item, value)

            except TclError as e:
                self.warn("Error configuring %s: %s", name, str(e))

    # generic function for over events
    def _validateFunctionList(self, functions, mode):
        if type(functions) == tuple:
            functions = list(functions)
        elif type(functions) != list:
            functions = [functions]

        if len(functions) == 1:
            functions.append(None)
        if len(functions) != 2:
            raise Exception("Invalid arguments, set<widget> %s Function requires 1 or 2 functions to be passed in.", mode)

        return functions

    def _bindOverEvent(self, kind, name, widget, functions, eventType, key=None):
        functions = self._validateFunctionList(functions, "Over")

        if functions[0] is not None:
            widget.bind("<Enter>", self.MAKE_FUNC(functions[0], name), add="+")
        if functions[1] is not None:
            widget.bind("<Leave>", self.MAKE_FUNC(functions[1], name), add="+")

    # generic function for drag events
    def _bindDragEvent(self, kind, name, widget, functions, eventType, key=None):
        functions = self._validateFunctionList(functions, "Drag")

        if kind == WIDGET_NAMES.Label:
            widget.config(cursor="fleur")

            def getLabel(f):
                # loop through all labels
                items = self.widgetManager.group(kind)
                for key, value in items.items():
                    if self._isMouseInWidget(value):
                        self.MAKE_FUNC(f,key)()
                        return

            if functions[0] is not None:
                widget.bind("<ButtonPress-1>", self.MAKE_FUNC(functions[0], name), add="+")
            if functions[1] is not None:
                widget.bind("<ButtonRelease-1>", self.MAKE_FUNC(getLabel, functions[1]), add="+")
        else:
            self.error("Only able to bind drag events to labels")

    # generic function for change/submit/events
    def _bindEvent(self, kind, name, widget, function, eventType, key=None):
        # this will discard the scale value, as default function
        # can't handle it
        if kind == WIDGET_NAMES.Scale:
            cmd = self.MAKE_FUNC(function, name)
            widget.cmd_id = widget.var.trace('w', cmd)
            widget.cmd = cmd
        elif kind == WIDGET_NAMES.OptionBox:
            if widget.kind == "ticks":
                vals = self.widgetManager.get(WIDGET_NAMES.TickOptionBox, name, group=WidgetManager.VARS)
                for o in vals:
                    cmd = self.MAKE_FUNC(function, name)
                    vals[o].cmd_id = vals[o].trace('w', cmd)
                    vals[o].cmd = cmd
            else:
                cmd = self.MAKE_FUNC(function, name)
                # need to trace the variable??
                widget.cmd_id = widget.var.trace('w', cmd)
                widget.cmd = cmd
        elif kind in [WIDGET_NAMES.Entry, WIDGET_NAMES.FileEntry, WIDGET_NAMES.DirectoryEntry]:
            if eventType == "change":
                # not populated by change/submit
                if key is None:
                    key = name
                cmd = self.MAKE_FUNC(function, key)
                # get Entry variable
                var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
                var.cmd_id = var.trace('w', cmd)
                var.cmd = cmd
            else:
                # not populated by change/submit
                if key is None:
                    key = name
                sbm = self.MAKE_FUNC(function, key)
                widget.sbm_id = widget.bind('<Return>', sbm)
                widget.sbm = sbm
        elif kind == WIDGET_NAMES.TextArea:
            if eventType == "change":
                # get Entry variable
                cmd = self.MAKE_FUNC(function, name)
                widget.bindChangeEvent(cmd)
        elif kind == WIDGET_NAMES.Button:
            if eventType == "change":
                self.warn("Error configuring %s : can't set a change function", name)
            else:
                widget.config(command=self.MAKE_FUNC(function, name))
                widget.bind('<Return>', self.MAKE_FUNC(function, name))
        # make labels clickable, add a cursor, and change the look
        elif kind == WIDGET_NAMES.Label or kind == WIDGET_NAMES.Image:
            if eventType in ["command", "submit"]:
                if self.platform == self.MAC:
                    widget.config(cursor="pointinghand")
                elif self.platform in [self.WINDOWS, self.LINUX]:
                    widget.config(cursor="hand2")

                cmd = self.MAKE_FUNC(function, name)
                widget.bind("<Button-1>", cmd, add="+")
                widget.cmd = cmd
                # these look good, but break when dialogs take focus
                #up = widget.cget("relief").lower()
                # down="sunken"
                # make it look like it's pressed
                #widget.bind("<Button-1>",lambda e: widget.config(relief=down), add="+")
                #widget.bind("<ButtonRelease-1>",lambda e: widget.config(relief=up))
            elif eventType == "change":
                self.warn("Error configuring %s : can't set a change function", name)
        elif kind == WIDGET_NAMES.ListBox:
            cmd = self.MAKE_FUNC(function, name)
            widget.bind('<<ListboxSelect>>', cmd)
            widget.cmd = cmd
        elif kind in [WIDGET_NAMES.RadioButton]:
            cmd = self.MAKE_FUNC(function, name)
            # get rb variable
            var = self.widgetManager.get(WIDGET_NAMES.RadioButton, name, group=WidgetManager.VARS)

            # only allow one trace to be bound
            # users are more likely to call multiple binds on radios
            # because they all share one var
            if hasattr(var, "cmd_id"):
                var.trace_vdelete('w', var.cmd_id)

            var.cmd_id = var.trace('w', cmd)
            var.cmd = cmd
        elif kind in [WIDGET_NAMES.Properties, WIDGET_NAMES.FrameStack, WIDGET_NAMES.Table]:
            cmd = self.MAKE_FUNC(function, name)
            widget.setChangeFunction(cmd)
        elif kind == WIDGET_NAMES.SpinBox:
            widget.cmd = self.MAKE_FUNC(function, name)
            widget.cmd_id = widget.var.trace("w", widget.cmd)
        elif kind == WIDGET_NAMES.PanedFrame:
            widget.cmd = self.MAKE_FUNC(function, name)
            widget.bind("<Configure>", widget.cmd)
        else:
            if kind not in [WIDGET_NAMES.CheckBox]:
                self.warn("Unmanaged binding of %s to %s", eventType, name)
            cmd = self.MAKE_FUNC(function, name)
            widget.config(command=cmd)
            widget.cmd = cmd

    # dynamic way to create the configuration functions
    def _buildConfigFuncs(self):
        # loop through all the available widgets
        # and make all the below functons for each one
        for v in WIDGET_NAMES.funcs():
            k = WIDGET_NAMES.get(v)
            exec( "def set" + v +
                "Bg(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'background', val)")
            exec("gui.set" + v + "Bg=set" + v + "Bg")
            exec( "def set" + v +
                "Fg(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'foreground', val)")
            exec("gui.set" + v + "Fg=set" + v + "Fg")

            exec( "def set" + v +
                "DisabledFg(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'disabledforeground', val)")
            exec("gui.set" + v + "DisabledFg=set" + v + "DisabledFg")
            exec( "def set" + v +
                "DisabledBg(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'disabledbackground', val)")
            exec("gui.set" + v + "DisabledBg=set" + v + "DisabledBg")

            exec( "def set" + v +
                "ActiveFg(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'activeforeground', val)")
            exec("gui.set" + v + "ActiveFg=set" + v + "ActiveFg")
            exec( "def set" + v +
                "ActiveBg(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'activebackground', val)")
            exec("gui.set" + v + "ActiveBg=set" + v + "ActiveBg")

            exec( "def set" + v +
                "InactiveFg(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'inactiveforeground', val)")
            exec("gui.set" + v + "InactiveFg=set" + v + "InactiveFg")
            exec( "def set" + v +
                "InactiveBg(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'inactivebackground', val)")
            exec("gui.set" + v + "InactiveBg=set" + v + "InactiveBg")

            exec( "def set" + v +
                "Width(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'width', val)")
            exec("gui.set" + v + "Width=set" + v + "Width")
            exec( "def set" + v +
                "Height(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'height', val)")
            exec("gui.set" + v + "Height=set" + v + "Height")
            exec( "def set" + v +
                "State(self, name, val): self.configureWidgets(" +
                str(k) + ", name, 'state', val)")
            exec("gui.set" + v + "State=set" + v + "State")
            exec( "def set" + v +
                "Padding(self, name, x, y=None): self.configureWidgets(" +
                str(k) + ", name, 'padding', [x, y])")
            exec("gui.set" + v + "Padding=set" + v + "Padding")

            exec( "def set" + v +
                "IPadding(self, name, x, y=None): self.configureWidgets(" +
                str(k) + ", name, 'ipadding', [x, y])")
            exec("gui.set" + v + "IPadding=set" + v + "IPadding")

            exec( "def set" + v +
                "InPadding(self, name, x, y=None): self.configureWidgets(" +
                str(k) + ", name, 'ipadding', [x, y])")
            exec("gui.set" + v + "InPadding=set" + v + "InPadding")

            # drag and drop stuff
            exec( "def set" + v +
                "DropTarget(self, name, function=None, replace=True): self.configureWidgets(" +
                str(k) + ", name, 'externalDrop', [function, replace])")
            exec("gui.set" + v + "DropTarget=set" + v + "DropTarget")

            exec( "def set" + v +
                "DragSource(self, name, function=None): self.configureWidgets(" +
                str(k) + ", name, 'externalDrag', function)")
            exec("gui.set" + v + "DragSource=set" + v + "DragSource")

            exec( "def register" + v +
                "Draggable(self, name, function=None): self.configureWidgets(" +
                str(k) + ", name, 'internalDrag', function)")
            exec("gui.register" + v + "Draggable=register" + v + "Draggable")

            exec( "def register" + v +
                "Droppable(self, name, function=None): self.configureWidgets(" +
                str(k) + ", name, 'internalDrop', function)")
            exec("gui.register" + v + "Droppable=register" + v + "Droppable")

            exec( "def set" + v +
                "Style(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'style', val)")
            exec("gui.set" + v + "Style=set" + v + "Style")

            # might not all be necessary, could make exclusion list
            exec( "def set" + v +
                "Relief(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'relief', val)")
            exec("gui.set" + v + "Relief=set" + v + "Relief")
            exec( "def set" + v +
                "Align(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'align', val)")
            exec("gui.set" + v + "Align=set" + v + "Align")
            exec( "def set" + v +
                "Anchor(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'anchor', val)")
            exec("gui.set" + v + "Anchor=set" + v + "Anchor")

            exec( "def set" + v +
                "Tooltip(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'tooltip', val)")
            exec("gui.set" + v + "Tooltip=set" + v + "Tooltip")

            exec( "def disable" + v +
                "Tooltip(self, name): self.configureWidget(" +
                str(k) + ", name, 'disableTooltip', None)")
            exec("gui.disable" + v + "Tooltip=disable" + v + "Tooltip")

            exec( "def enable" + v +
                "Tooltip(self, name): self.configureWidget(" +
                str(k) + ", name, 'enableTooltip', None)")
            exec("gui.enable" + v + "Tooltip=enable" + v + "Tooltip")

            # function setters
            exec( "def set" + v +
                "ChangeFunction(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'change', val)")
            exec("gui.set" + v + "ChangeFunction=set" + v + "ChangeFunction")
            exec( "def set" + v +
                "SubmitFunction(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'submit', val)")
            exec("gui.set" + v + "SubmitFunction=set" + v + "SubmitFunction")
            exec( "def set" + v +
                "DragFunction(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'drag', val)")
            exec("gui.set" + v + "DragFunction=set" + v + "DragFunction")
            exec( "def set" + v +
                "OverFunction(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'over', val)")
            exec("gui.set" + v + "OverFunction=set" + v + "OverFunction")

            # http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/cursors.html
            exec( "def set" + v +
                "Cursor(self, name, val): self.configureWidget(" +
                str(k) + ", name, 'cursor', val)")
            exec("gui.set" + v + "Cursor=set" + v + "Cursor")
            exec( "def set" + v +
                "Focus(self, name): self.configureWidget(" +
                str(k) + ", name, 'focus', None)")
            exec("gui.set" + v + "Focus=set" + v + "Focus")

            # change the stickyness
            exec( "def set" + v +
                "Sticky(self, name, pos): self.configureWidget(" +
                str(k) + ", name, 'sticky', pos)")
            exec("gui.set" + v + "Sticky=set" + v + "Sticky")

            # add right click
            exec( "def set" + v +
                "RightClick(self, name, menu): self.configureWidget(" +
                str(k) + ", name, 'rightClick', menu)")
            exec("gui.set" + v + "RightClick=set" + v + "RightClick")

            # functions to manage widgets
            exec( "def show" + v +
                "(self, name): self.showWidgetType(" +
                str(k) + ", name)")
            exec("gui.show" + v + "=show" + v)
            exec( "def hide" + v +
                "(self, name, collapse=False): self.hideWidgetType(" +
                str(k) + ", name, collapse)")
            exec("gui.hide" + v + "=hide" + v)
            exec( "def remove" + v +
                "(self, name, collapse=False): self.removeWidgetType(" +
                str(k) + ", name, collapse)")
            exec("gui.remove" + v + "=remove" + v)
            exec( "def move" + v +
                "(self, name, row=None, column=0, colspan=0, rowspan=0, sticky=W+E): self.moveWidgetType(" +
                str(k) + ", name, row, column, colspan, rowspan, sticky)")
            exec("gui.move" + v + "=move" + v)

            exec( "def empty" + v +
                "(self, name): self._emptyContainerType(" +
                str(k) + ", name)")
            exec("gui.empty" + v + "=empty" + v)

            # convenience functions for enable/disable
            # might not all be necessary, could make exclusion list
            exec( "def enable" + v +
                "(self, name): self.configureWidget(" +
                str(k) + ", name, 'state', 'normal')")
            exec("gui.enable" + v + "=enable" + v)
            exec( "def disable" + v +
                "(self, name): self.configureWidget(" +
                str(k) + ", name, 'state', 'disabled')")
            exec("gui.disable" + v + "=disable" + v)

            # group functions
            exec( "def set" + v +
                "Widths(self, names, val): self.configureWidgets(" +
                str(k) + ", names, 'width', val)")
            exec("gui.set" + v + "Widths=set" + v + "Widths")
            exec( "def setAll" + v +
                "Widths(self, val): self.configureAllWidgets(" +
                str(k) + ", 'width', val)")
            exec("gui.setAll" + v + "Widths=setAll" + v + "Widths")

            exec( "def set" + v +
                "Heights(self, names, val): self.configureWidgets(" +
                str(k) + ", names, 'height', val)")
            exec("gui.set" + v + "Heights=set" + v + "Heights")
            exec( "def setAll" + v +
                "Heights(self, val): self.configureAllWidgets(" +
                str(k) + ", 'height', val)")
            exec("gui.setAll" + v + "Heights=setAll" + v + "Heights")

            exec( "def get" + v +
                "Widget(self, name, val=None): return self.getWidget(" +
                str(k) + ", name, val)")
            exec("gui.get" + v + "Widget=get" + v + "Widget")

            exec( "def get" + v +
                "Bg(self, name, val=None): return self.getWidgetProperty(" +
                str(k) + ", name, val, 'bg')")
            exec("gui.get" + v + "Bg=get" + v + "Bg")

#####################################
#  FUNCTION to hide/show/remove widgets
#####################################
    def _widgetHasContainer(self, kind, item):
        if kind in (
            WIDGET_NAMES.Scale,
            WIDGET_NAMES.Entry,
            WIDGET_NAMES.SpinBox,
            WIDGET_NAMES.OptionBox,
            WIDGET_NAMES.Label) and item.inContainer:
            return True
        else:
            return False

    def _cloneWidget(self, widget, parent):
        clone = widget.__class__(parent)
        for key in widget.configure():
            clone.configure({key: widget.cget(key)})

        origProps = widget.__dict__
        for x in origProps:
            if x not in ['_w', '_tclCommands', '_name', 'master', 'tk']:
                setattr(clone, x, origProps[x])

        return clone

    def moveWidgetType(self, kind, name, row=None, column=0, colspan=0, rowspan=0, sticky=W+E):
        self.hideWidgetType(kind, name, collapse=True)
        widget = self.widgetManager.get(kind, name)

        container = self.getContainer()
        if container != widget.master:
            widget = self._cloneWidget(widget, container)
            self.widgetManager.update(kind, name, widget)

        self._positionWidget(widget, row, column, colspan, rowspan, sticky, updateBg=False)
        return widget


    def hideWidgetType(self, kind, name, collapse=False):
        items = self._getWidgetList(kind, name, limit=False)

        for item in items:
            if self._widgetHasContainer(kind, item):
                gui.trace("Hiding widget in container: %s", name)
                widget = item.master
                if hasattr(widget, "inContainer") and widget.inContainer:
                    gui.trace("Have container in container")
                    widget = widget.master
                try: self.widgetManager.get(WIDGET_NAMES.FrameLabel, name).hidden = True
                except: pass
            else:
                gui.trace("Hiding widget: %s", name)
#                if kind in [WIDGET_NAMES.RadioButton]:
#                    for rb in item:
#                        if rb.text == name:
#                            widget = rb
                widget = item

            if "in" in widget.grid_info():
                gui.trace("Widget hidden: %s", name)
                info = widget.grid_info()
                widget.grid_remove()

                if collapse:
                    widget.master.grid_rowconfigure(info["row"], minsize=0, weight=0)
            else:
                gui.trace("Hiding failed - %s not showing", name)

    def showWidgetType(self, kind, name):
        items = self._getWidgetList(kind, name, limit=False)

        for item in items:
            if self._widgetHasContainer(kind, item):
                gui.trace("Showing widget in container: %s", name)
                widget = item.master
                if hasattr(widget, "inContainer") and widget.inContainer:
                    gui.trace("Have container in container")
                    widget = widget.master
                try: self.widgetManager.get(WIDGET_NAMES.FrameLabel, name).hidden = False
                except: pass
            else:
                msg = "Showing widget"
                widget = item

            # only show the widget, if it's not already showing
            if "in" not in widget.grid_info():
                gui.trace("Widget shown: %s", name)
                widget.grid()
    #            self._updateLabelBoxes(name, widget.grid_info()['column'])
            else:
                gui.trace("Showing failed - %s already showing", name)

    def emptySubWindow(self, title):
        # not generated by function generator
        self._emptyContainerType(WIDGET_NAMES.SubWindow, title)

    def emptyCurrentContainer(self):
        cConf = self.containerStack[-1]
        kind = WIDGET_NAMES.name(cConf['type'])
        title = cConf['title']
        gui.trace('Emptying current container %s: %s', kind, title)

        if not self._emptyContainerObj(cConf['container']):
            gui.trace('No widgets found in current container %s: %s to empty', kind, title)

        cConf = self._prepContainer(cConf["title"], cConf["type"], cConf["container"], 0, 1)
        self.containerStack[-1] = cConf

    def _emptyContainerType(self, kind, title):
        kind = WIDGET_NAMES.name(kind)
        gui.trace('Emptying %s: %s', kind, title)
        cName = kind + "__" + title
        try:
            cConf = self.widgetManager.get(WIDGET_NAMES.ContainerLog, cName)
        except KeyError:
            raise Exception("Attempted to empty invalid " + kind + ": " + str(title))

        if not self._emptyContainerObj(cConf['container']):
            gui.trace('No widgets found in %s: %s to empty', kind, title)

    def removeAllWidgets(self, current=False, sub=False):
        if current:
            self.emptyCurrentContainer()
        else:
            gui.trace('Removing all widgets from appJar')

            if sub: self.destroyAllSubWindows()

            containerData = self.containerStack[0]
            container = containerData['container']
            self._emptyContainerObj(container)

            # reset container values
            containerData = self._prepContainer(containerData["title"], containerData["type"], containerData["container"], 0, 1)
            self.containerStack[0] = containerData

#            self.widgetManager.reset(WIDGET_NAMES.keepers)
#            self.setSize(None)

    def _emptyContainerObj(self, container):
        widgs = False
        for child in container.winfo_children():
            self.cleanseWidgets(child)
            widgs = True

        # reset the grid measurements
        for i in range(Grid.grid_size(container)[0]):
            container.columnconfigure(i, minsize=0, weight=0, pad=0)
        for i in range(Grid.grid_size(container)[1]):
            container.rowconfigure(i, minsize=0, weight=0, pad=0)

        return widgs

    def removeWidgetType(self, kind, name, collapse=False):
        if kind == WIDGET_NAMES.RadioButton:
            gui.error("Can't remove widget %s - %s", kind, name)
            return
            
        item = self.widgetManager.get(kind, name)

        # if it's a flasher, remove it
        if item in self.widgetManager.group(WIDGET_NAMES.FlashLabel):
            gui.trace("Remove flash label: %s", name)
            self.widgetManager.remove(WIDGET_NAMES.FlashLabel, item)
            if len(self.widgetManager.group(WIDGET_NAMES.FlashLabel)) == 0:
                self.doFlash = False

        # animated images...

        if self._widgetHasContainer(kind, item):
            gui.trace("Remove widget (%s) in container: %s", kind, name)
            parent = item.master

            # is it a container in a labelBox?
            # if so - remove & destroy the labelBox
            if hasattr(parent, "inContainer") and parent.inContainer:
                gui.trace("Container in container")
                labParent = parent.master

                self.widgetManager.remove(WIDGET_NAMES.FrameBox, labParent)
                self.widgetManager.remove(WIDGET_NAMES.Label, name)
                self.widgetManager.remove(WIDGET_NAMES.FrameLabel, name)
                labParent.grid_forget()
                labParent.destroy()
            # otherwise destroy this container & a label if we have one
            else:
                parent.grid_forget()
                parent.destroy()
                try:
                    self.widgetManager.remove(WIDGET_NAMES.Label, name)
                    self.widgetManager.remove(WIDGET_NAMES.FrameLabel, name)
                except: pass

            self.widgetManager.remove(WIDGET_NAMES.FrameBox, parent)
        else:
            gui.trace("Remove widget: %s", name)
            item.grid_forget()
            self.cleanseWidgets(item)

#####################################
# FUNCTION for managing commands
#####################################
    @staticmethod
    def MAKE_FUNC(funcName, param):
        ''' function to automate lambdas '''
        # make sure we get a function
        if not callable(funcName) and not hasattr(funcName, '__call__'):
            raise Exception("Invalid function: " + str(funcName))

        # check if the function requires arguments
        argsList = getArgs(funcName)
        # if no args, or 1 arg in a bound function
        noArgs = len(argsList[0])==0 or (len(argsList[0])==1 and inspect.ismethod(funcName))

        # if no args/varargs/kwargs then don't give the param
        if noArgs and argsList[1] is None and argsList[2] is None:
            return lambda *args: funcName()
        else:
            return lambda *args: funcName(param)

    def _checkFunc(self, names, funcs):
        singleFunc = None
        if funcs is None:
            return None
        elif callable(funcs):
            singleFunc = funcs
        elif len(names) != len(funcs):
            raise Exception("List sizes don't match")
        return singleFunc

#####################################
# FUNCTIONS to position a widget
#####################################

    # properties for setting container's default rowspan/colspan
    def setColspan(self, colspan):
        self.containerStack[-1]['colspan'] = colspan

    def getColspan(self):
        return self.containerStack[-1]['colspan']

    colspan = property(getColspan, setColspan)

    def setRowspan(self, rowspan):
        self.containerStack[-1]['rowspan'] = rowspan

    def getRowspan(self):
        return self.containerStack[-1]['rowspan']

    rowspan = property(getRowspan, setRowspan)

    def getRow(self):
        return self._getContainerProperty('emptyRow')

    def gr(self):
        return self.getRow()

    def setRow(self, row):
        self.containerStack[-1]['emptyRow'] = row

    row = property(getRow, setRow)

    def _repackWidget(self, widget, params):
        if widget.winfo_manager() == "grid":
            ginfo = widget.grid_info()
            ginfo.update(params)
            widget.grid(ginfo)
        elif widget.winfo_manager() == "pack":
            pinfo = widget.pack_info()
            pinfo.update(params)
            widget.pack(pinfo)
        else:
            raise Exception("Unknown geometry manager: " + widget.winfo_manager())

    # convenience function to set RCS, referencing the current container's
    # settings
    def _getRCS(self, row, column, colspan, rowspan):
        if row in[-1, 'previous', 'p', 'pr']:
            row = self._getContainerProperty('emptyRow') - 1
        else:
            # this is the default,
            if row is None or row in ['next', 'n']:
                row = self._getContainerProperty('emptyRow')
            self.containerStack[-1]['emptyRow'] = row + 1

        if column >= self._getContainerProperty('colCount'):
            self.containerStack[-1]['colCount'] = column + 1
        # if column == 0 and colspan == 0 and self._getContainerProperty('colCount') > 1:
        #      colspan = self._getContainerProperty('colCount')

        return row, column, colspan, rowspan

    @staticmethod
    def GET_WIDGET_CLASS(widget):
        return widget.__class__.__name__

    @staticmethod
    def SET_WIDGET_FG(widget, fg, external=False):
        widgType = gui.GET_WIDGET_CLASS(widget)
        gui.trace("SET_WIDGET_FG: %s - %s", widgType, fg)

        # only configure these widgets if external
        if widgType in ["Link", "Spinbox", "AjText", "AjScrolledText", "Button", "Entry", "AutoCompleteEntry"]:
            if external:
                try: # entry specific settings
                    if not widget.showingDefault:
                        widget.oldFg = fg
                        widget.config(fg=fg)
                    else:
                        widget.oldFg = fg
                except: # other widgets
                    widget.config(fg=fg)
        # handle flash labels
        elif widgType == "Label":
            widget.config(fg=fg)
            widget.origFg=fg
            try: widget.config(bg=widget.origBg)
            except: pass # not a flash label

        elif widgType == "OptionMenu":
            if external:
                widget.config(fg=fg)
                widget["menu"].config(fg=fg)

        # deal with generic groupers
        elif widgType in ["Frame", "LabelFrame", "PanedFrame", "Pane", "ajFrame"]:
            for child in widget.winfo_children():
                gui.SET_WIDGET_FG(child, fg, external)

        # deal with specific containers
        elif widgType == "LabelBox":
            try:
                if not widget.isValidation:
                    gui.SET_WIDGET_FG(widget.theLabel, fg, external)
            except Exception as e:
                gui.SET_WIDGET_FG(widget.theLabel, fg, external)
            gui.SET_WIDGET_FG(widget.theWidget, fg, external)
        elif widgType == "ButtonBox":
            gui.SET_WIDGET_FG(widget.theWidget, fg, external)
            gui.SET_WIDGET_FG(widget.theButton, fg, external)
        elif widgType == "WidgetBox":
            for child in widget.theWidgets:
                gui.SET_WIDGET_FG(child, fg, external)
        elif widgType == "ListBoxContainer":
            if external:
                gui.SET_WIDGET_FG(widget.lb, fg, external)

        # skip these widgets
        elif widgType in ["PieChart", "MicroBitSimulator", "Scrollbar"]:
            pass

        # always try these widgets
        else:
            try:
                widget.config(fg=fg)
            except Exception as e:
                pass

    @staticmethod
    def TINT(widget, colour):
        col = []
        for a, b in enumerate(widget.winfo_rgb(colour)):
            t = int(min(max(0, b / 256 + (255 - b / 256) * .3), 255))
            t = str(hex(t))[2:]
            if len(t) == 1:
                t = '0' + t
            elif len(t) == 0:
                t = '00'
            col.append(t)

        if int(col[0], 16) > 210 and int(col[1], 16) > 210 and int(col[2], 16) > 210:
            if gui.GET_PLATFORM() == gui.LINUX:
                return "#c3c3c3"
            else:
                return "systemHighlight"
        else:
            return "#" + "".join(col)

    # convenience method to set a widget's bg
    @staticmethod
    def SET_WIDGET_BG(widget, bg, external=False, tint=False):

        if bg is None: # ignore empty colours
            return

        widgType = gui.GET_WIDGET_CLASS(widget)
        isDarwin = gui.GET_PLATFORM() == gui.MAC
        isLinux = gui.GET_PLATFORM() == gui.LINUX

        gui.trace("Config %s BG to %s", widgType, bg)

        # these have a highlight border to remove
        hideBorders = [ "Text", "AjText",
            "ScrolledText", "AjScrolledText",
            "Scale", "AjScale",
            "OptionMenu",
            "Entry", "AutoCompleteEntry",
            "Radiobutton", "Checkbutton",
            "Button"]

        # these shouldn't have their BG coloured by default
        noBg = [ "Button",
            "Scale", "AjScale",
            "Spinbox", "Listbox", "OptionMenu",
            "SplitMeter", "DualMeter", "Meter",
            "Entry", "AutoCompleteEntry",
            "Text", "AjText",
            "ScrolledText", "AjScrolledText",
            "ToggleFrame"]

        # remove the highlight borders
        if widgType in hideBorders:
            if widgType == "Entry" and widget.isValidation:
                pass
            elif widgType == "OptionMenu":
                widget["menu"].config(borderwidth=0)
                widget.config(highlightbackground=bg)
                if isDarwin:
                    widget.config(background=bg)
            elif widgType in ["Radiobutton", "Checkbutton"]:
                widget.config(activebackground=bg, highlightbackground=bg)
            else:
                widget.config(highlightbackground=bg)

        # do some fancy tinting
        if external or tint:
            if widgType in ["Button", "Scale", "AjScale"]:
                widget.config(activebackground=gui.TINT(widget, bg))
            elif widgType in ["Entry", "Text", "AjText", "ScrolledText", "AjScrolledText", "AutoCompleteEntry", "Spinbox"]:
                widget.config(selectbackground=gui.TINT(widget, bg))
                widget.config(highlightcolor=gui.TINT(widget, bg))
                if widgType in ["Text", "AjText", "ScrolledText", "AjScrolledText"]:
                    widget.config(inactiveselectbackground=gui.TINT(widget, bg))
                elif widgType == "Spinbox":
                    widget.config(buttonbackground=bg)
            elif widgType == "Listbox":
                widget.config(selectbackground=gui.TINT(widget, bg))
            elif widgType == "OptionMenu":
                widget.config(activebackground=gui.TINT(widget, bg))
                widget["menu"].config(activebackground=gui.TINT(widget, bg))
            elif widgType in ["Radiobutton", "Checkbutton"]:
                widget.config(activebackground=gui.TINT(widget, bg))

        # if this is forced - change everything
        if external:
            widget.config(bg=bg)
            if widgType == "OptionMenu":
                widget["menu"].config(bg=bg)
        # otherwise only colour un-excluded widgets
        elif widgType not in noBg:
            widget.config(bg=bg)

        # deal with flash labels
        if widgType == "Label":
            widget.origBg=bg
            try: widget.config(fg=widget.origFg)
            except: pass # not a flash label

        # now do any of the below containers
        if widgType in ["LabelFrame", "PanedFrame", "Pane", "ajFrame"]:
            for child in widget.winfo_children():
                gui.SET_WIDGET_BG(child, bg, external, tint)
        elif widgType == "LabelBox": # widget with label, in frame
            if widget.theLabel is not None:
                gui.SET_WIDGET_BG(widget.theLabel, bg, external, tint)
            gui.SET_WIDGET_BG(widget.theWidget, bg, external, tint)
        elif widgType == "ButtonBox": # widget with button, in frame
            gui.SET_WIDGET_BG(widget.theWidget, bg, external, tint)
            gui.SET_WIDGET_BG(widget.theButton, bg, external, tint)
        elif widgType == "ListBoxContainer": # list box container
            gui.SET_WIDGET_BG(widget.lb, bg, external, tint)
        elif widgType == "WidgetBox": # group of buttons or labels
            for widg in widget.theWidgets:
                gui.SET_WIDGET_BG(widg, bg, external, tint)

    def _getContainerProperty(self, prop=None):
        if prop is not None:
            return self.containerStack[-1][prop]
        else:
            return self.containerStack[-1]

    def _getContainerBg(self):
        if not self.ttkFlag:
            return self.getContainer()["bg"]
        else:
            return None

    def _getContainerFg(self):
        try:
            return self._getContainerProperty('fg')
        except:
            return "#000000"

    # two important things here:
    # grid - sticky: position of widget in its space (side or fill)
    # row/columns configure - weight: how to grow with GUI
    def _positionWidget( self, widget, row, column=0, colspan=0, rowspan=0, sticky=W+E, updateBg=True):
        # allow item to be added to container
        container = self.getContainer()
        if updateBg and not self.ttkFlag:
            gui.SET_WIDGET_FG(widget, self._getContainerFg())
            gui.SET_WIDGET_BG(widget, self._getContainerBg())

        # alpha paned window placement
        if self._getContainerProperty('type') == WIDGET_NAMES.PanedFrame:
            container.add(widget)
            self.containerStack[-1]['widgets'] = True
            return

        # else, add to grid
        row, column, colspan, rowspan = self._getRCS(row, column, colspan, rowspan)

        # build a dictionary for the named params
        iX = self._getContainerProperty('ipadx')
        iY = self._getContainerProperty('ipady')
        cX = self._getContainerProperty('padx')
        cY = self._getContainerProperty('pady')
        params = {
            "row": row,
            "column": column,
            "ipadx": iX,
            "ipady": iY,
            "padx": cX,
            "pady": cY}

        # sort out rowspan & colspan
        cColspan = self._getContainerProperty("colspan")
        cRowspan = self._getContainerProperty("rowspan")

        if colspan != 0: params["columnspan"] = colspan
        elif cColspan != 0: params["columnspan"] = cColspan

        if rowspan != 0: params["rowspan"] = rowspan
        elif cRowspan != 0: params["rowspan"] = cRowspan

        # 1) if param has sticky, use that
        # 2) if container has sticky - override
        # 3) else, none
        if self._getContainerProperty("sticky") is not None:
            params["sticky"] = self._getContainerProperty("sticky")
        elif sticky is not None:
            params["sticky"] = sticky
        else:
            pass

        # make colspanned widgets expand to fill height of cell
        if rowspan != 0:
            if "sticky" in params:
                if "n" not in params["sticky"]:
                    params["sticky"] += "n"
                if "s" not in params["sticky"]:
                    params["sticky"] += "s"
            else:
                params["sticky"] = "ns"

        # expand that dictionary out as we pass it as a value
        widget.grid(**params)
        self.containerStack[-1]['widgets'] = True
        # if we're in a PANEDFRAME - we need to set parent...
        if self._getContainerProperty('type') == WIDGET_NAMES.Pane:
            self.containerStack[-2]['widgets'] = True

        # configure the row/column to expand equally
        if self._getContainerProperty('expand') in ["ALL", "COLUMN"]:
            Grid.columnconfigure(container, column, weight=1)
        else:
            Grid.columnconfigure(container, column, weight=0)
        if self._getContainerProperty('expand') in ["ALL", "ROW"]:
            Grid.rowconfigure(container, row, weight=1)
        else:
            Grid.rowconfigure(container, row, weight=0)

#        self._getContainerProperty('container').columnconfigure(0, weight=1)
#        self._getContainerProperty('container').rowconfigure(0, weight=1)

#####################################
# FUNCTION to manage containers
#####################################
    # prepares a new empty container dict
    def _prepContainer(self, cTitle, cType, container, row, col, sticky=None):
        containerData = {'type': cType,
                    'title': cTitle,
                    'container': container,
                    'emptyRow': row,
                    'colCount': col,
                    'sticky': sticky,
                    'padx': 0,
                    'pady': 0,
                    'ipadx': 0,
                    'ipady': 0,
                    'expand': "ALL",
                    'widgets': False,
                    'inputFont': self._inputFont,
                    'labelFont': self._labelFont,
                    'buttonFont': self._buttonFont,
                    "fg": self._getContainerFg(),
                    "colspan":0,
                    "rowspan":0,
                    }
        return containerData

    # adds the container to the container stack - makes this the current working container
    def _addContainer(self, cTitle, cType, container, row, col, sticky=None):
        containerData = self._prepContainer(cTitle, cType, container, row, col, sticky)
        self.containerStack.append(containerData)

    def openFrameStack(self, title):
        return self._openContainer(WIDGET_NAMES.FrameStack, title)

    def openSubFrame(self, frameTitle, frameNumber):
        return self._openContainer(WIDGET_NAMES.SubFrame, frameTitle+"__"+str(frameNumber))

    def openRootPage(self, title):
        return self._openContainer(WIDGET_NAMES.RootPage, title)

    def openLabelFrame(self, title):
        return self._openContainer(WIDGET_NAMES.LabelFrame, title)

    def openFrame(self, title):
        try: return self._openContainer(WIDGET_NAMES.Frame, title)
        except: return self._openContainer(WIDGET_NAMES.SubFrame, title)

    def openToggleFrame(self, title):
        return self._openContainer(WIDGET_NAMES.ToggleFrame, title)

    def openPagedWindow(self, title):
        return self._openContainer(WIDGET_NAMES.PagedWindow, title)

    def openPage(self, windowTitle, pageNumber):
        return self._openContainer(WIDGET_NAMES.Page, windowTitle+"__"+str(pageNumber))

    def openTabbedFrame(self, title):
        return self._openContainer(WIDGET_NAMES.TabbedFrame, title)

    def openTab(self, frameTitle, tabTitle):
        return self._openContainer(WIDGET_NAMES.Tab, frameTitle+"__"+tabTitle)

    def openNotebook(self, title):
        return self._openContainer(WIDGET_NAMES.Notebook, title)

    def openNote(self, frameTitle, tabTitle):
        return self._openContainer(WIDGET_NAMES.Notebook, frameTitle+"__"+tabTitle)

    def openPanedFrame(self, title):
        return self._openContainer(WIDGET_NAMES.PanedFrame, title)

    def openPane(self, title):
        return self._openContainer(WIDGET_NAMES.Pane, title)

    def openSubWindow(self, title):
        return self._openContainer(WIDGET_NAMES.SubWindow, title)

    def openScrollPane(self, title):
        return self._openContainer(WIDGET_NAMES.ScrollPane, title)

    # function to reload the specified container
    def _openContainer(self, kind, title):
        # get the cached container config for this container
        kind = WIDGET_NAMES.name(kind)
        cName = kind + "__" + title
        try:
            cConf = self.widgetManager.get(WIDGET_NAMES.ContainerLog, cName)
        except KeyError:
            raise Exception("Attempted to open invalid " + kind + ": " + str(title))

        self.containerStack.append(cConf)
        return cConf['container']

    # returns the current working container
    def getContainer(self):
        container = self._getContainerProperty('container')
        if self._getContainerProperty('type') == WIDGET_NAMES.ScrollPane:
            return container.interior
        elif self._getContainerProperty('type') == WIDGET_NAMES.PagedWindow:
            return container.getPage()
        elif self._getContainerProperty('type') == WIDGET_NAMES.ToggleFrame:
            return container.getContainer()
        elif self._getContainerProperty('type') == WIDGET_NAMES.SubWindow:
            return container.canvasPane
        else:
            return container

    # if possible, removes the current container
    def _removeContainer(self):
        if len(self.containerStack) == 1:
            raise Exception("Can't remove container, already in root window.")
        else:
            container = self.containerStack.pop()
            if not container['widgets']:
                self.warn("Closing empty container: %s", container['title'])

            # store the container so that it can be re-opened later
            name = WIDGET_NAMES.name(container["type"]) + "__" + container["title"]
            try:
                self.widgetManager.add(WIDGET_NAMES.ContainerLog, name, container)
            except:
                pass # we'll ignore, as that means we already added it...
            return container

    # functions to start the various containers
    def startContainer(self, fType, title, row=None, column=0, colspan=0, rowspan=0, sticky=None, name=None):
        if name is None: name = title
        if fType == WIDGET_NAMES.LabelFrame:
            # first, make a LabelFrame, and position it correctly
            self.widgetManager.verify(WIDGET_NAMES.LabelFrame, title)
            if not self.ttkFlag:
                container = LabelFrame(self.getContainer(), text=name, relief="groove")
                container.config(background=self._getContainerBg(), font=self._getContainerProperty('labelFont'))
            else:
                container = ttk.LabelFrame(self.getContainer(), text=name, relief="groove")

            container.DEFAULT_TEXT = name
            container.isContainer = True
            self.setPadX(5)
            self.setPadY(5)
            self._positionWidget(container, row, column, colspan, rowspan, "nsew")
            self.widgetManager.add(WIDGET_NAMES.LabelFrame, title, container)

            # now, add to top of stack
            self._addContainer(title, WIDGET_NAMES.LabelFrame, container, 0, 1, sticky)
            return container
        elif fType == WIDGET_NAMES.Canvas:
            # first, make a canvas, and position it correctly
            self.widgetManager.verify(WIDGET_NAMES.Canvas, title)
            container = Canvas(self.getContainer())
            container.isContainer = True
            self._positionWidget(container, row, column, colspan, rowspan, "nsew")
            self.widgetManager.add(WIDGET_NAMES.Canvas, title, container)

            # now, add to top of stack
            self._addContainer(title, WIDGET_NAMES.Canvas, container, 0, 1, "")
            return container
        elif fType == WIDGET_NAMES.TabbedFrame:
            self.widgetManager.verify(WIDGET_NAMES.TabbedFrame, title)
            tabbedFrame = self._tabbedFrameMaker(self.getContainer(), self.ttkFlag, font=self._getContainerProperty('labelFont'))
            if not self.ttkFlag:
                tabbedFrame.config(bg=self._getContainerBg())
#            tabbedFrame.isContainer = True
            self._positionWidget(
                tabbedFrame,
                row,
                column,
                colspan,
                rowspan,
                sticky=sticky)
            self.widgetManager.add(WIDGET_NAMES.TabbedFrame, title, tabbedFrame)

            # now, add to top of stack
            self._addContainer(title, WIDGET_NAMES.TabbedFrame, tabbedFrame, 0, 1, sticky)
            return tabbedFrame
        elif fType == WIDGET_NAMES.Tab:
            # add to top of stack
            self.containerStack[-1]['widgets'] = True
            tabTitle = self._getContainerProperty('title') + "__" + title
            tab = self._getContainerProperty('container').addTab(title)
            self._addContainer(tabTitle, WIDGET_NAMES.Tab, tab, 0, 1, sticky)
            return tab
        elif fType == WIDGET_NAMES.Notebook:
            if not self.ttkFlag:
                raise Exception("Cannot create a ttk Notebook, unless ttk is enabled.")
            self.widgetManager.verify(WIDGET_NAMES.Notebook, title)
            notebook = ttk.Notebook(self.getContainer())
#            tabbedFrame.isContainer = True
            self._positionWidget(
                notebook,
                row,
                column,
                colspan,
                rowspan,
                sticky=sticky)
            self.widgetManager.add(WIDGET_NAMES.Notebook, title, notebook)

            # now, add to top of stack
            self._addContainer(title, WIDGET_NAMES.Notebook, notebook, 0, 1, sticky)
            return notebook
        elif fType == WIDGET_NAMES.Note:
            # add to top of stack
            self.containerStack[-1]['widgets'] = True
            noteTitle = self._getContainerProperty('title') + "__" + title
            frame = ttk.Frame(self._getContainerProperty('container'))
            self._getContainerProperty('container').add(frame, text=title)
            self._addContainer(noteTitle, WIDGET_NAMES.Note, frame, 0, 1, sticky)
            return frame
        elif fType == WIDGET_NAMES.PanedFrame:
            # if we previously put a frame for widgets
            # remove it
            if self._getContainerProperty('type') == WIDGET_NAMES.Pane:
                self.stopContainer()

            # now, add the new pane
            self.widgetManager.verify(WIDGET_NAMES.PanedFrame, title)
            pane = PanedWindow(
                self.getContainer(),
                showhandle=True,
                sashrelief="groove",
                bg=self._getContainerBg())

            pane.isContainer = True
            self._positionWidget(
                pane, row, column, colspan, rowspan, sticky=sticky)
            self.widgetManager.add(WIDGET_NAMES.PanedFrame, title, pane)

            # now, add to top of stack
            self._addContainer(title, WIDGET_NAMES.PanedFrame, pane, 0, 1, sticky)

            # now, add a frame to the pane
            self.startContainer(WIDGET_NAMES.Pane, title)
            return pane
        elif fType == WIDGET_NAMES.Pane:
            # create a frame, and add it to the pane
            pane = Pane(self.getContainer(), bg=self._getContainerBg())
            pane.isContainer = True
            self._getContainerProperty('container').add(pane)
            self.widgetManager.add(WIDGET_NAMES.Pane, title, pane)

            # now, add to top of stack
            self._addContainer(title, WIDGET_NAMES.Pane, pane, 0, 1, sticky)
            return pane
        elif fType == WIDGET_NAMES.ScrollPane:
            self.widgetManager.verify(WIDGET_NAMES.ScrollPane, title)
            # naned used to diabled sctollbars
            if name not in ["horizontal", "vertical", ""]:
                gui.warn("ScrollPane %s: Invalid value for disabled, must be one of 'horizontal' or 'vertical'", title)
            scrollPane = ScrollPane(self.getContainer(), disabled=name)
            if not self.ttkFlag:
                scrollPane.config(bg=self._getContainerBg())
            scrollPane.isContainer = True
            self._positionWidget(
                scrollPane,
                row,
                column,
                colspan,
                rowspan,
                sticky=sticky)
            self.widgetManager.add(WIDGET_NAMES.ScrollPane, title, scrollPane)

            # now, add to top of stack
            self._addContainer(title, WIDGET_NAMES.ScrollPane, scrollPane, 0, 1, sticky)
            return scrollPane
        elif fType == WIDGET_NAMES.ToggleFrame:
            self.widgetManager.verify(WIDGET_NAMES.ToggleFrame, title)
            toggleFrame = ToggleFrame(self.getContainer(), title=title, bg=self._getContainerBg())
            toggleFrame.configure(font=self._getContainerProperty('labelFont'))
            toggleFrame.isContainer = True
            self._positionWidget(
                toggleFrame,
                row,
                column,
                colspan,
                rowspan,
                sticky=sticky)
            self._addContainer(title, WIDGET_NAMES.ToggleFrame, toggleFrame, 0, 1, "nw")
            self.widgetManager.add(WIDGET_NAMES.ToggleFrame, title, toggleFrame)
            return toggleFrame
        elif fType == WIDGET_NAMES.PagedWindow:
            # create the paged window
            pagedWindow = PagedWindow(self.getContainer(), title=title, bg=self._getContainerBg(), width=200, height=400, buttonFont=self._getContainerProperty('buttonFont'), titleFont=self._getContainerProperty('labelFont'))
            # bind events
            self.topLevel.bind("<Left>", pagedWindow.showPrev)
            self.topLevel.bind("<Control-Left>", pagedWindow.showFirst)
            self.topLevel.bind("<Right>", pagedWindow.showNext)
            self.topLevel.bind("<Control-Right>", pagedWindow.showLast)
            # register it as a container
            pagedWindow.isContainer = True
            self._positionWidget(pagedWindow, row, column, colspan, rowspan, sticky=sticky)
            self._addContainer(title, WIDGET_NAMES.PagedWindow, pagedWindow, 0, 1, "nw")
            self.widgetManager.add(WIDGET_NAMES.PagedWindow, title, pagedWindow)
            return pagedWindow
        elif fType == WIDGET_NAMES.Page:
            page = self._getContainerProperty('container').addPage()
            page.isContainer = True
            self._addContainer(title, WIDGET_NAMES.Page, page, 0, 1, sticky)
            self.containerStack[-1]['expand'] = "None"
            return page
        elif fType == WIDGET_NAMES.FrameStack:
            # create the paged window
            frameStack = FrameStack(self.getContainer(), bg=self._getContainerBg())
            self.widgetManager.add(WIDGET_NAMES.FrameStack, title, frameStack)
            # register it as a container
            frameStack.isContainer = True
            self._positionWidget(frameStack, row, column, colspan, rowspan, sticky=sticky)
            self._addContainer(title, WIDGET_NAMES.FrameStack, frameStack, 0, 1, "news")
            return frameStack
        elif fType == WIDGET_NAMES.Frame:
            # first, make a Frame, and position it correctly
            self.widgetManager.verify(WIDGET_NAMES.Frame, title)
            container = self._makeAjFrame()(self.getContainer())
            container.isContainer = True
            container.config(background=self._getContainerBg())
            self._positionWidget( container, row, column, colspan, rowspan, "nsew")
            self.widgetManager.add(WIDGET_NAMES.Frame, title, container)

            # now, add to top of stack
            self._addContainer(title, WIDGET_NAMES.Frame, container, 0, 1, sticky)
            return container
        elif fType == WIDGET_NAMES.SubFrame:
            subFrame = self._getContainerProperty('container').addFrame()
            subFrame.isContainer = True
            self._addContainer(title, WIDGET_NAMES.SubFrame, subFrame, 0, 1, "news")
            self.widgetManager.add(WIDGET_NAMES.Frame, title, subFrame)
            return subFrame
        else:
            raise Exception("Unknown container: " + fType)

#####################################
# Notebooks
#####################################

    @contextmanager
    def notebook(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW", **kwargs):
        try:
            note = self.startNotebook(title, row, column, colspan, rowspan, sticky)
        except ItemLookupError:
            note = self.openNotebook(title)
        self.configure(**kwargs)
        try: yield note
        finally: self.stopNotebook()

    def startNotebook(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW"):
        return self.startContainer(WIDGET_NAMES.Notebook, title, row, column, colspan, rowspan, sticky)

    def stopNotebook(self):
        # auto close the existing TAB - keep it?
        if self._getContainerProperty('type') == WIDGET_NAMES.Note:
            self.warn("You didn't STOP the previous NOTE")
            self.stopContainer()
        self.stopContainer()

    @contextmanager
    def note(self, title, tabTitle=None, **kwargs):
        if tabTitle is None:
            note = self.startNote(title)
        else:
            note = self.openNote(title, tabTitle)
        self.configure(**kwargs)
        try: yield note
        finally: self.stopNote()

    def startNote(self, title):
        # auto close the previous TAB - keep it?
        if self._getContainerProperty('type') == WIDGET_NAMES.Note:
            self.warn("You didn't STOP the previous NOTE")
            self.stopContainer()
        elif self._getContainerProperty('type') != WIDGET_NAMES.Notebook:
            raise Exception(
                "Can't add a Note to the current container: ", self._getContainerProperty('type'))
        return self.startContainer(WIDGET_NAMES.Note, title)

    def stopNote(self):
        if self._getContainerProperty('type') != WIDGET_NAMES.Note:
            raise Exception("Can't stop a NOTE, currently in:",
                            self._getContainerProperty('type'))
        self.stopContainer()
    """
    def startCanvas(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="news"):
        return self.startContainer(WIDGET_NAMES.Canvas, title)

    def stopCanvas(self):
        if self._getContainerProperty('type') != WIDGET_NAMES.Canvas:
            raise Exception("Can't stop a CANVAS, currently in:", self._getContainerProperty('type'))
        self.stopContainer()

    @contextmanager
    def canvas(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW"):
        try:
            canvas = self.startCanvas(title, row, column, colspan, rowspan, sticky)
        except ItemLookupError:
            canvas = self.openCanvas(title)
        try: yield canvas
        finally: self.stopCanvas()
    """

#####################################
# Tabbed Frames
#####################################

    #################################
    # TabbedFrame Class
    #################################
    def _tabbedFrameMaker(self, master, useTtk=False, **kwargs):
        global OrderedDict
        if OrderedDict is None:
            from collections import OrderedDict

        class TabBorder(Frame, object):
            def __init__(self, master, **kwargs):
                super(TabBorder, self).__init__(master, **kwargs)
                self.config(borderwidth=0, highlightthickness=0, bg='darkGray')

        class TabContainer(frameBase, object):
            def __init__(self, master, **kwargs):
                super(TabContainer, self).__init__(master, **kwargs)
                TabBorder(self, height=2).pack(side=TOP, expand=True, fill=X)
                TabBorder(self, width=2).pack(side=LEFT, fill=Y, expand=0)

        class TabText(labelBase, object):
            def __init__(self, master, func, text, **kwargs):
                super(TabText, self).__init__(master, text=text, **kwargs)
                self.disabled = False
                self.DEFAULT_TEXT = text
                self.hidden = False
                self.bind("<Button-1>", lambda *args: func(text))
                self.border = TabBorder(master, width=2)

            def rename(self, newName):
                # use the DEFAULT_TEXT if necessary
                if newName is None: newName = self.DEFAULT_TEXT
                self.config(text=newName)

            def hide(self):
                self.hidden = True
                self.border.pack_forget()
                self.pack_forget()

            def display(self, fill=False, beforeTab=None, afterTab=None):
                self.border.pack_forget()
                self.pack_forget()
                if not self.hidden:
                    if fill: self.pack(side=LEFT, ipady=4, ipadx=4, expand=True, fill=BOTH, before=beforeTab, after=afterTab)
                    else: self.pack(side=LEFT, ipady=4, ipadx=4, before=beforeTab, after=afterTab)
                    self.border.pack(side=LEFT, fill=Y, expand=0, before=beforeTab, after=afterTab)

        class TabbedFrame(frameBase, object):
            def __init__(self, master, fill=False, changeOnFocus=True, font=None, **kwargs):
                # main frame & tabContainer inherit BG colour
                super(TabbedFrame, self).__init__(master, **kwargs)
                self.fill = fill
                self.selectedTab = None
                self.changeOnFocus = changeOnFocus
                self.changeEvent = None
                self.beforeTab = None
                self.afterTab = None

                # layout the grid
                Grid.columnconfigure(self, 0, weight=1)
                Grid.rowconfigure(self, 1, weight=1)

                # create two containers
                self.tabContainer = TabContainer(self, **kwargs)
                self.panes = FrameStack(self)
                self.panes.SKIP_CLEANSE = True

                # now grid minimised or stretched
                if self.fill: self.tabContainer.grid(row=0, sticky=W + E)
                else: self.tabContainer.grid(row=0, sticky=W)
                self.panes.grid(row=1, sticky="NESW")
                self.EMPTY_PANE = self.panes.addFrame()

                # nain store dictionary: name = [tab, pane]
                self.widgetStore = OrderedDict()

                # looks
                self.tabFont = font
                if gui.GET_PLATFORM() == gui.MAC: self.inactiveCursor="pointinghand"
                elif gui.GET_PLATFORM() in [gui.WINDOWS, gui.LINUX]: self.inactiveCursor="hand2"
                # selected tab & all panes
                self.activeFg = "#000000"
                self.activeBg = "#F6F6F6"
                # other tabs
                self.inactiveFg = "#000000"
                self.inactiveBg = "#DADADA"
                # disabled tabs
                self.disabledFg = "gray"
                self.disabledBg = "darkGray"

                if useTtk:
                    self.ttkStyle = ttk.Style()
                    self.ttkStyle.configure("ActiveTab.TLabel", foreground=self.activeFg, background=self.activeBg)
                    self.ttkStyle.configure("InactiveTab.TLabel", foreground=self.inactiveFg, background=self.inactiveBg)
                    self.ttkStyle.configure("DisabledTab.TLabel", foreground=self.disabledFg, background=self.disabledBg)
                    self.ttkStyle.configure("DisabledTab.TFrame", background=self.disabledBg)
                    self.EMPTY_PANE.config(style="DisabledTab.TFrame")
                else:
                    self.EMPTY_PANE.config(bg=self.disabledBg)

            def config(self, cnf=None, **kw):
                self.configure(cnf, **kw)

            def setBeforeTab(self, tab=None):
                if tab is not None:
                    self.beforeTab = self.widgetStore[tab][0]
                else:
                    self.beforeTab = None

            def setAfterTab(self, tab=None):
                if tab is not None:
                    self.afterTab = self.widgetStore[tab][0]
                else:
                    self.afterTab = None

            def configure(self, cnf=None, **kw):
                kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
                if "activeforeground" in kw: self.activeFg = kw.pop("activeforeground")
                if "activebackground" in kw: self.activeBg = kw.pop("activebackground")
                if "fg" in kw: self.inactiveFg = kw.pop("fg")
                if "inactivebackground" in kw: self.inactiveBg = kw.pop("inactivebackground")
                if "inactiveforeground" in kw: self.inactiveFg = kw.pop("inactiveforeground")
                if "disabledforeground" in kw: self.disabledFg = kw.pop("disabledforeground")
                if "disabledbackground" in kw: self.disabledBg = kw.pop("disabledbackground")
                if "bg" in kw: self.tabContainer.configure(bg=kw["bg"])
                if "font" in kw: self.tabFont.config(kw.pop("font"))
                if "command" in kw: self.changeEvent = kw.pop("command")

                # just in case
                if not useTtk:
                    self.EMPTY_PANE.config(bg=self.disabledBg)
                else:
                    self.ttkStyle.configure("ActiveTab.TLabel", foreground=self.activeFg, background=self.activeBg)
                    self.ttkStyle.configure("InactiveTab.TLabel", foreground=self.inactiveFg, background=self.inactiveBg)
                    self.ttkStyle.configure("DisabledTab.TLabel", foreground=self.disabledFg, background=self.disabledBg)
                    self.ttkStyle.configure("DisabledTab.TFrame", background=self.disabledBg)

                # update tabs if we have any
                self._configTabs()

                # propagate any left over confs
                super(TabbedFrame, self).config(cnf, **kw)

            def hideTab(self, title):
                if title not in self.widgetStore.keys(): raise ItemLookupError("Invalid tab name: " + title)
                self.widgetStore[title][0].hide()
                if self.selectedTab == title:
                    self.selectedTab = None
                    self._findNewTab()
                self._configTabs()

            def deleteTab(self, title):
                self.hideTab(title)

                tab = self.widgetStore[title][0]
                tab.border.destroy()
                tab.destroy()

                pane = self.widgetStore[title][1]
                pane.grid_forget()
                pane.destroy()

                del self.widgetStore[title]

            def showTab(self, title):
                if title not in self.widgetStore.keys(): raise ItemLookupError("Invalid tab name: " + title)
                self.widgetStore[title][0].hidden = False
                self.expandTabs(self.fill)
                if self.selectedTab == None:
                    self.changeTab(title)

            def disableAllTabs(self, disabled=True):
                for tab in self.widgetStore.keys():
                    self.disableTab(tab, disabled, refresh=False)
                self._configTabs()
                if disabled:
                    self.selectedTab = None
                    self.EMPTY_PANE.lift()

            def disableTab(self, tabName, disabled=True, refresh=True):
                if tabName not in self.widgetStore.keys(): raise ItemLookupError("Invalid tab name: " + tabName)

                tab = self.widgetStore[tabName][0]
                tab.disabled = disabled

                if not disabled and not tab.hidden and self.selectedTab is None:
                    self.selectedTab = tabName
                elif disabled and self.selectedTab == tabName:
                    self.selectedTab = None
                    if refresh: self._findNewTab()

                if refresh:
                    self._configTabs()

            def addTab(self, text, **kwargs):
                # check for duplicates
                if text in self.widgetStore: raise ItemLookupError("Duplicate tabName: " + text)

                tab = TabText(self.tabContainer, text=text, func=self.changeTab, font=self.tabFont, **kwargs)
                tab.display(self.fill, beforeTab=self.beforeTab, afterTab=self.afterTab)

                # create the pane
                pane = self.panes.addFrame()
                if not useTtk:
                    pane.config(bg=self.activeBg)

                # log the first tab as the selected tab
                if self.selectedTab is None:
                    self.selectedTab = text

                # log the widgets
                self.widgetStore[text] = [tab, pane]
                self._configTabs()

                return pane

            def getTab(self, title):
                if title not in self.widgetStore.keys(): raise ItemLookupError("Invalid tab name: " + title)
                else: return self.widgetStore[title][1]

            def expandTabs(self, fill=True):
                self.fill = fill

                # update the tabConatiner
                self.tabContainer.grid_forget()
                if self.fill: self.tabContainer.grid(row=0, sticky=W + E)
                else: self.tabContainer.grid(row=0, sticky=W)

                for key in list(self.widgetStore.keys()):
                    tab = self.widgetStore[key][0]
                    tab.display(self.fill)

            def renameTab(self, tabName, newName=None):
                if tabName not in self.widgetStore.keys():
                    raise ItemLookupError("Invalid tab name: " + tabName)

                self.widgetStore[tabName][0].rename(newName)

            def changeTab(self, tabName, callFunction=True):
                if tabName not in self.widgetStore.keys(): raise ItemLookupError("Invalid tab name: " + tabName)
                # stop if already selected or disabled
                if self.selectedTab == tabName or self.widgetStore[tabName][0].disabled or self.widgetStore[tabName][0].hidden:
                    return

                self.selectedTab = tabName
                self._configTabs()
                if self.changeEvent is not None and callFunction: self.changeEvent(tabName)

            def getSelectedTab(self):
                return self.selectedTab

            def setFont(self, **kwargs):
                self.tabFont.config(**kwargs)

            def _findNewTab(self):
                for key in list(self.widgetStore.keys()):
                    if not self.widgetStore[key][0].disabled and not self.widgetStore[key][0].hidden:
                        self.changeTab(key)
                        return

                # if we're here - all tabs are disabled
                self.selectedTab = None
                self.EMPTY_PANE.lift()

            def _configTabs(self):
                for key in list(self.widgetStore.keys()):
                    if self.widgetStore[key][0].disabled:
                        if not useTtk:
                            self.widgetStore[key][0].config(bg=self.disabledBg, fg=self.disabledFg, cursor="")
                        else:
                            self.widgetStore[key][0].config(style="DisabledTab.TLabel", cursor="")
                    else:
                        if key == self.selectedTab:
                            if not useTtk:
                                self.widgetStore[key][0].config(bg=self.widgetStore[key][1].cget('bg'), fg=self.activeFg, cursor="")
                            else:
                                self.widgetStore[key][0].config(style="SelectedTab.TLabel", cursor="")
                            self.widgetStore[key][1].lift()
                        else:
                            if not useTtk:
                                self.widgetStore[key][0].config(bg=self.inactiveBg, fg=self.inactiveFg, cursor=self.inactiveCursor)
                            else:
                                self.widgetStore[key][0].config(style="InactiveTab.TLabel", cursor=self.inactiveCursor)

        return TabbedFrame(master, **kwargs)

    @contextmanager
    def tabbedFrame(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW", **kwargs):
        try:
            tabs = self.startTabbedFrame(title, row, column, colspan, rowspan, sticky)
        except ItemLookupError:
            tabs = self.openTabbedFrame(title)
        command = kwargs.pop("change", None)
        if command is not None: self.setTabbedFrameChangeCommand(title, command)
        self.configure(**kwargs)
        try: yield tabs
        finally: self.stopTabbedFrame()

    def startTabbedFrame(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW"):
        return self.startContainer(WIDGET_NAMES.TabbedFrame, title, row, column, colspan, rowspan, sticky)

    def stopTabbedFrame(self):
        # auto close the existing TAB - keep it?
        if self._getContainerProperty('type') == WIDGET_NAMES.Tab:
            self.warn("You didn't STOP the previous TAB")
            self.stopContainer()
        self.stopContainer()

    def setTabbedFrameChangeCommand(self, title, func):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        command = self.MAKE_FUNC(func, title)
        nb.config(command=command)

    def setTabbedFrameTabExpand(self, title, expand=True):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        nb.expandTabs(expand)

    def setTabbedFrameSelectedTab(self, title, tab, callFunction=True):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        try:
            nb.changeTab(tab, callFunction)
        except KeyError:
            raise ItemLookupError("Invalid tab name: " + str(tab))

    def setTabbedFrameDisabledTab(self, title, tab, disabled=True):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        nb.disableTab(tab, disabled)

    def setTabbedFrameDisableAllTabs(self, title, disabled=True):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        nb.disableAllTabs(disabled)

    def deleteTabbedFrameTab(self, title, tab):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        self.cleanseWidgets(nb.getTab(tab))
        nb.deleteTab(tab)

    def showTabbedFrameTab(self, title, tab):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        nb.showTab(tab)

    def hideTabbedFrameTab(self, title, tab):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        nb.hideTab(tab)

    def setTabText(self, title, tab, newText=None):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        nb.renameTab(tab, newText)

    def setTabFont(self, title, **kwargs):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        nb.setFont(**kwargs)

    def setTabBg(self, title, tab, colour):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        tab = nb.getTab(tab)
        gui.SET_WIDGET_BG(tab, colour)
        # tab.config(bg=colour)
        #gui.SET_WIDGET_BG(tab, colour)
        for child in tab.winfo_children():
            gui.SET_WIDGET_BG(child, colour)

    @contextmanager
    def tab(self, title, tabTitle=None, **kwargs):
        beforeTab = kwargs.pop("beforeTab", None)
        afterTab = kwargs.pop("afterTab", None)
        if tabTitle is None:
            try:
                tab = self.startTab(title, beforeTab, afterTab)
            except ItemLookupError:
                if self._getContainerProperty('type') != WIDGET_NAMES.TabbedFrame:
                    raise Exception("Can't open a Tab in the current container: ", self._getContainerProperty('type'))
                else:
                    tabTitle = self._getContainerProperty('title')
                    tab = self.openTab(tabTitle, title)
        else:
            tab = self.openTab(title, tabTitle)
        self.configure(**kwargs)
        try: yield tab
        finally: self.stopTab()

    def startTab(self, title, beforeTab=None, afterTab=None):

        if beforeTab is not None and afterTab is not None:
            self.warn("You can't specify a before and after value for tab: %s", title)
            beforeTab = afterTab = None

        # auto close the previous TAB - keep it?
        if self._getContainerProperty('type') == WIDGET_NAMES.Tab:
            self.warn("You didn't STOP the previous TAB")
            self.stopContainer()
        elif self._getContainerProperty('type') != WIDGET_NAMES.TabbedFrame:
            raise Exception("Can't add a Tab to the current container: ", self._getContainerProperty('type'))

        tf = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, self._getContainerProperty("title"))
        tf.setBeforeTab(beforeTab)
        tf.setAfterTab(afterTab)
        tab = self.startContainer(WIDGET_NAMES.Tab, title)
        tf.setBeforeTab()
        tf.setAfterTab()
        return tab

    def getTabbedFrameSelectedTab(self, title):
        nb = self.widgetManager.get(WIDGET_NAMES.TabbedFrame, title)
        return nb.getSelectedTab()

    def stopTab(self):
        if self._getContainerProperty('type') != WIDGET_NAMES.Tab:
            raise Exception("Can't stop a TAB, currently in:",
                            self._getContainerProperty('type'))
        self.stopContainer()

#####################################
# Simple Tables
#####################################

    def _getDbTables(self, db):
        ''' query the specified database, and get a list of table names '''
        self._importSqlite3()
        if not sqlite3:
            self.error("Unable to load DB tables - can't load sqlite3")
            return []

        query = "SELECT DISTINCT tbl_name FROM sqlite_master ORDER BY tbl_name COLLATE NOCASE"
        data = []

        with sqlite3.connect(db) as conn:
            cursor = conn.cursor()
            cursor.execute(query)
            for row in cursor:
                data.append(row[0])
        return data

    def replaceDbTable(self, title, db, table):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.db = db
        grid.dbTable = table
        self._importSqlite3()
        if not sqlite3:
            self.error("Unable to load DB data - can't load sqlite3")
            return

        with sqlite3.connect(db) as conn:
            cursor = conn.cursor()
            dataQuery = 'SELECT * from ' + table

            # select all data
            cursor.execute(dataQuery)
            self.setTableHeaders(title, cursor)
            self.replaceAllTableRows(title, cursor)
        self.topLevel.update_idletasks()

    def disableTableEntry(self, title, entryPos, disabled=True):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.disableEntry(entryPos, disabled=disabled)

    def refreshDbTable(self, title):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        self._importSqlite3()
        if not sqlite3:
            self.error("Unable to load DB data - can't load sqlite3")
            return

        with sqlite3.connect(grid.db) as conn:
            cursor = conn.cursor()
            dataQuery = 'SELECT * from ' + grid.dbTable

            # select all data
            cursor.execute(dataQuery)
            self.replaceAllTableRows(title, cursor)

    def refreshDbOptionBox(self, title, selected=None):
        opt = self.widgetManager.get(WIDGET_NAMES.OptionBox, title)
        data = self._getDbTables(opt.db)
        self.changeOptionBox(title, data)
        if selected is not None:
            self.setOptionBox(title, selected)

    def table(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets tables all in one go """
        widgKind = WIDGET_NAMES.Table
        kind = kwargs.pop("kind", 'normal')

        action=kwargs.pop('action', None)
        addRow=kwargs.pop('addRow', None)
        actionHeading=kwargs.pop('actionHeading', "Action")
        actionButton=kwargs.pop('actionButton', "Press")
        addButton=kwargs.pop('addButton', "Add")
        showMenu=kwargs.pop('showMenu', False)
        horiz=kwargs.pop('horizontal', True)
        change=kwargs.pop('change', None)
        edit=kwargs.pop('edit', None)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            if value is not None: self.replaceAllTableRows(title, value)
            table = self.getTableEntries(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if kind == 'normal':
                table = self.addTable(title, value, *args,
                            action=action, addRow=addRow, actionHeading=actionHeading, actionButton=actionButton,
                            addButton=addButton, showMenu=showMenu, horizontal=horiz, **kwargs
                        )
            else:
                table = self.addDbTable(title, value, *args,
                            action=action, addRow=addRow, actionHeading=actionHeading, actionButton=actionButton,
                            addButton=addButton, showMenu=showMenu, horizontal=horiz, **kwargs
                        )
        if change is not None: self.setTableChangeFunction(title, change)
        if edit is not None: self.setTableEditFunction(title, edit)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return table

    def addDbTable(self, title, value, table, row=None, column=0, colspan=0, rowspan=0,
                action=None, addRow=None, actionHeading="Action", actionButton="Press",
                addButton="Add", showMenu=False, border="solid", **kwargs):
        ''' creates a new Table, displaying the specified database & table '''

        horiz=kwargs.pop('horizontal', True)

        self._importSqlite3()
        if not sqlite3:
            self.error("Unable to load DB data - can't load sqlite3")
            return

        with sqlite3.connect(value) as conn:
            cursor = conn.cursor()
            dataQuery = 'SELECT * from ' + table

            # select all data
            cursor.execute(dataQuery)

            grid = self.addTable(title, cursor, row, column, colspan, rowspan,
                        action, addRow, actionHeading, actionButton,
                        addButton, showMenu, border=border, horizontal=horiz
                    )
        grid.db = value
        grid.dbTable = table
        return grid

    def addTable(self, title, data, row=None, column=0, colspan=0, rowspan=0, action=None, addRow=None,
                actionHeading="Action", actionButton="Press", addButton="Add", showMenu=False, border="solid", **kwargs):
        ''' creates a new table, displaying the specified data '''
        self.widgetManager.verify(WIDGET_NAMES.Table, title)
        wrap=kwargs.pop('wrap', 250)
        horiz=kwargs.pop('horizontal', True)
        if not self.ttkFlag:
            grid = SimpleTable(self.getContainer(), title, data,
                        action, addRow,
                        actionHeading, actionButton, addButton,
                        showMenu, buttonFont=self._getContainerProperty('buttonFont'),
                        font=self.tableFont, background=self._getContainerBg(),
                        queueFunction=self.queueFunction, border=border, wrap=wrap, horizontal=horiz
                    )
        else:
            grid = SimpleTable(self.getContainer(), title, data,
                        action, addRow,
                        actionHeading, actionButton, addButton,
                        showMenu, buttonFont=self._getContainerProperty('buttonFont'),
                        queueFunction=self.queueFunction, border=border, wrap=wrap, horizontal=horiz
                    )
        self._positionWidget(grid, row, column, colspan, rowspan, N+E+S+W)
        self.widgetManager.add(WIDGET_NAMES.Table, title, grid)
        return grid

    def getTableEntries(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Table, title).getEntries()

    def getTableLastChange(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Table, title).getLastChange()

    def getTableSelectedCells(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Table, title).getSelectedCells()

    def selectTableRow(self, title, row, highlight=None):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.selectRow(row, highlight)

    def setTableEditFunction(self, title, func):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        cmd = self.MAKE_FUNC(func, title)
        grid.config(edit=cmd)

    def selectTableColumn(self, title, col, highlight=None):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.selectColumn(col, highlight)

    def addTableRow(self, title, data):
        ''' adds a new row of data to the specified table '''
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.addRow(data)

    def addTableRows(self, title, data):
        ''' adds multiple rows of data to the specified table '''
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.addRows(data, scroll=True)

    def addTableColumn(self, title, columnNumber, data):
        ''' adds a new column of data, in the specified position, to the specified table '''
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.addColumn(columnNumber, data)

    def deleteTableColumn(self, title, columnNumber):
        ''' deletes the specified column from the specified table '''
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.deleteColumn(columnNumber)

    def setTableHeaders(self, title, data):
        ''' change the headers in the specified table '''
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.setHeaders(data)

    def deleteTableRow(self, title, rowNum):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.deleteRow(rowNum)

    def deleteAllTableRows(self, title):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.deleteAllRows()

    def sortTable(self, title, columnNumber, descending=False):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.sort(columnNumber, descending)

    def getTableRowCount(self, title):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        return grid.getRowCount()

    def getTableRow(self, title, rowNumber):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        return grid.getRow(rowNumber)

    def confTable(self, title, field, value):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        kw = {field:value}
        grid.config(**kw)

    def replaceTableRow(self, title, rowNum, data):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.replaceRow(rowNum, data)

    def replaceAllTableRows(self, title, data, deleteHeader=True):
        grid = self.widgetManager.get(WIDGET_NAMES.Table, title)
        grid.deleteAllRows(deleteHeader=deleteHeader)
        grid.addRows(data, scroll=False)

    # temporary deprecated functions
    def addGrid(self, title, data, row=None, column=0, colspan=0, rowspan=0, action=None, addRow=None,
                actionHeading="Action", actionButton="Press", addButton="Add", showMenu=False):
        ''' DEPRECATED - adds a new grid widget with the specified data '''
        gui.warn("Deprecated - grids renamed to tables")
        return self.addTable(title, data, row, column, colspan, rowspan, action, addRow, actionHeading, actionButton, addButton, showMenu)
    def addDbGrid(self, title, db, table, row=None, column=0, colspan=0, rowspan=0, action=None, addRow=None,
                actionHeading="Action", actionButton="Press", addButton="Add", showMenu=False):
        ''' DEPRECATED - adds a new table widget, with the specified database and table '''
        gui.warn("Deprecated - grids renamed to tables")
        return self.addDbTable(title, db, table, row, column, colspan, rowspan, action, addRow, actionHeading, actionButton, addButton, showMenu)
    def replaceDbGrid(self, title, db, table):
        gui.warn("Deprecated - grids renamed to tables")
        return self.replaceDbTable(title, db, table)
    def refreshDbGrid(self, title):
        gui.warn("Deprecated - grids renamed to tables")
        return self.refreshDbTable(title)
    def selectGridRow(self, title, row, highlight=None):
        gui.warn("Deprecated - grids renamed to tables")
        return self.selectTableRow(title, row, highlight)
    def getGridEntries(self, title):
        gui.warn("Deprecated - grids renamed to tables")
        return self.getTableEntries(title)
    def getGridSelectedCells(self, title):
        gui.warn("Deprecated - grids renamed to tables")
        return self.getTableSelectedCells(title)
    def selectGridColumn(self, title, col, highlight=None):
        return self.selectTableColumn(title, col, highlight)
    def addGridRow(self, title, data):
        ''' DEPRECATED - adds a row of data to the specified grid '''
        return self.addTableRow(title, data)
    def addGridRows(self, title, data):
        ''' DEPRECATED - adds new rows of data to the specified grid '''
        return self.addTableRows(title, data)
    def addGridColumn(self, title, columnNumber, data):
        ''' DEPRECATED - adds a column of data to the specified grid '''
        return self.addTableColumn(title, columnNumber, data)
    def deleteGridColumn(self, title, columnNumber):
        return self.deleteTableColumn(title, columnNumber)
    def setGridHeaders(self, title, data):
        return self.setTableHeaders(title, data)
    def deleteGridRow(self, title, rowNum):
        return self.deleteTableRow(title, rowNum)
    def deleteAllGridRows(self, title):
        return self.deleteAllTableRows(title)
    def sortGrid(self, title, columnNumber, descending=False):
        return self.sortTable(title, columnNumber, descending)
    def getGridRowCount(self, title):
        return self.getTableRowCount(title)
    def getGridRow(self, title, rowNumber):
        return self.getTableRow(title, rowNumber)
    def confGrid(self, title, field, value):
        return self.confTable(title, field, value)
    def replaceGridRow(self, title, rowNum, data):
        return self.replaceTableRow(title, rowNum, data)
    def replaceAllGridRows(self, title, data):
        return self.replaceAllTableRows(title, data)

#####################################
# Paned Frames
#####################################

    @contextmanager
    def panedFrame(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW", **kwargs):
        vertical = kwargs.pop("vertical", False)
        sash = kwargs.pop("sash", 50)
        reOpen = False
        try:
            pane = self.startPanedFrame(title, row, column, colspan, rowspan, sticky)
        except ItemLookupError:
            reOpen = True
            pane = self.openPane(title)
        if vertical: self.setPanedFrameVertical(title)
        self.configure(**kwargs)
        try: yield pane
        finally:
            if reOpen:
                self.stopContainer()
            else:
                self.stopPanedFrame()
                self.setPaneSashPosition(sash, pane)

    @contextmanager
    def panedFrameVertical(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW", **kwargs):
        gui.warn('Setting panedFrameVertical(%s) is deprecated, please use panedFrame(vertical=True)', title)
        reOpen = False
        sash = kwargs.pop("sash", 50)
        try:
            pane = self.startPanedFrameVertical(title, row, column, colspan, rowspan, sticky)
        except ItemLookupError:
            reOpen = True
            pane = self.openPane(title)
        self.configure(**kwargs)
        try: yield pane
        finally:
            if reOpen:
                self.stopContainer()
            else:
                self.stopPanedFrame()
                self.setPaneSashPosition(sash, pane)

    def startPanedFrame(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW"):
        p = self.startContainer(WIDGET_NAMES.PanedFrame, title, row, column, colspan, rowspan, sticky)
        return p

    def startPanedFrameVertical( self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW"):
        p = self.startPanedFrame(title, row, column, colspan, rowspan, sticky)
        self.setPanedFrameVertical(title)
        return p

    def stopPanedFrame(self):
        if self._getContainerProperty('type') == WIDGET_NAMES.Pane:
            self.stopContainer()
        if self._getContainerProperty('type') != WIDGET_NAMES.PanedFrame:
            raise Exception("Can't stop a PANEDFRAME, currently in:",
                            self._getContainerProperty('type'))
        self.stopContainer()

    # make a PanedFrame align vertically
    def setPanedFrameVertical(self, window):
        pane = self.widgetManager.get(WIDGET_NAMES.PanedFrame, window)
        pane.config(orient=VERTICAL)

    def setPaneSashPosition(self, pos, pane=None):
        # convert to a percentage if needed
        if pos > 1: pos = pos / 100.0

        if pane is None:
            if self._getContainerProperty('type') == WIDGET_NAMES.PanedFrame:
                pane = self._getContainerProperty('container')
            elif self.containerStack[-2]['type'] == WIDGET_NAMES.PanedFrame:
                pane = self.containerStack[-2]['container']
            elif self._getContainerProperty('type') == WIDGET_NAMES.Pane:
                pane = self._getContainerProperty('container').parent
            else:
                gui.error("Unable to set sash position - can't find a pane")
                return
        elif type(pane) == str:
            pane = self.widgetManager.get(WIDGET_NAMES.PanedFrame, pane)

        if pane.cget('orient') == 'horizontal':
            w = int(pane.winfo_width() * pos)
            try:
                pane.sash_place(0, w, 0)
                gui.trace('Set horizontal pane: %s to position: %s', pane, pos)
            except TclError as e:
                # no sash to configure - last pane
                pass
        else:
            h = int(pane.winfo_height() * pos)
            try:
                pane.sash_place(0, 0, h)
                gui.trace('Set vertical pane: %s to position: %s', pane, pos)
            except TclError as e:
                # no sash to configure - last pane
                pass

#####################################
# Label Frames
#####################################

    @contextmanager
    def labelFrame(self, title, row=None, column=0, colspan=0, rowspan=0, sticky=W, hideTitle=False, **kwargs):
        name = kwargs.pop("label", kwargs.pop("name", None))
        labelFg = kwargs.pop("labelFg", self.fg)
        try:
            lf = self.startLabelFrame(title, row, column, colspan, rowspan, sticky, hideTitle, name)
        except ItemLookupError:
            lf = self.openLabelFrame(title)
        self.configure(**kwargs)
        if not self.ttkFlag:
            lf.config(fg=labelFg)
        try: yield lf
        finally: self.stopLabelFrame()

    # sticky is alignment inside frame
    # frame will be added as other widgets
    def startLabelFrame(self, title, row=None, column=0, colspan=0, rowspan=0, sticky=W, hideTitle=False, label=None, name=None):
        if label is not None: name = label
        if hideTitle: name = ''
        lf = self.startContainer(WIDGET_NAMES.LabelFrame, title, row, column, colspan, rowspan, sticky, name)
        return lf

    def stopLabelFrame(self):
        if self._getContainerProperty('type') != WIDGET_NAMES.LabelFrame:
            raise Exception("Can't stop a LABELFRAME, currently in:",
                            self._getContainerProperty('type'))
        self.stopContainer()

    # function to set position of title for label frame
    def setLabelFrameTitle(self, title, newTitle):
        frame = self.widgetManager.get(WIDGET_NAMES.LabelFrame, title)
        frame.config(text=newTitle)

#####################################
# Toggle Frames
#####################################

    @contextmanager
    def toggleFrame(self, title, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        try:
            tog = self.startToggleFrame(title, row, column, colspan, rowspan)
        except ItemLookupError:
            tog = self.openToggleFrame(title)
        self.configure(**kwargs)
        try: yield tog
        finally: self.stopToggleFrame()

    ###### TOGGLE FRAMES #######
    def startToggleFrame(self, title, row=None, column=0, colspan=0, rowspan=0):
        return self.startContainer(WIDGET_NAMES.ToggleFrame, title, row, column, colspan, rowspan, sticky="new")

    def stopToggleFrame(self):
        if self._getContainerProperty('type') != WIDGET_NAMES.ToggleFrame:
            raise Exception("Can't stop a TOGGLEFRAME, currently in:",
                            self._getContainerProperty('type'))
        self._getContainerProperty('container').stop()
        self.stopContainer()

    def toggleToggleFrame(self, title):
        toggle = self.widgetManager.get(WIDGET_NAMES.ToggleFrame, title)
        toggle.toggle()

    def setToggleFrameText(self, title, newText):
        toggle = self.widgetManager.get(WIDGET_NAMES.ToggleFrame, title)
        toggle.config(text=newText)

    def getToggleFrameState(self, title):
        toggle = self.widgetManager.get(WIDGET_NAMES.ToggleFrame, title)
        return toggle.isShowing()

#####################################
# Paged Windows
#####################################

    @contextmanager
    def pagedWindow(self, title, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        try:
            pw = self.startPagedWindow(title, row, column, colspan, rowspan)
        except ItemLookupError:
            pw = self.openPagedWindow(title)
        self.configure(**kwargs)
        try: yield pw
        finally: self.stopPagedWindow()

    ###### PAGED WINDOWS #######
    def startPagedWindow(self, title, row=None, column=0, colspan=0, rowspan=0):
        return self.startContainer( WIDGET_NAMES.PagedWindow, title, row, column, colspan, rowspan, sticky="nsew")

    def setPagedWindowPage(self, title, page):
        pager = self.widgetManager.get(WIDGET_NAMES.PagedWindow, title)
        pager.showPage(page)

    def setPagedWindowButtonsTop(self, title, top=True):
        pager = self.widgetManager.get(WIDGET_NAMES.PagedWindow, title)
        pager.setNavPositionTop(top)

    def setPagedWindowButtons(self, title, buttons):
        pager = self.widgetManager.get(WIDGET_NAMES.PagedWindow, title)
        if not isinstance(buttons, list) or len(buttons) != 2:
            raise Exception(
                "You must provide a list of two strings for setPagedWinowButtons()")
        pager.setPrevButton(buttons[0])
        pager.setNextButton(buttons[1])

    def setPagedWindowFunction(self, title, func):
        pager = self.widgetManager.get(WIDGET_NAMES.PagedWindow, title)
        command = self.MAKE_FUNC(func, title)
        pager.registerPageChangeEvent(command)

    def getPagedWindowPageNumber(self, title):
        pager = self.widgetManager.get(WIDGET_NAMES.PagedWindow, title)
        return pager.getPageNumber()

    def showPagedWindowPageNumber(self, title, show=True):
        pager = self.widgetManager.get(WIDGET_NAMES.PagedWindow, title)
        pager.showPageNumber(show)

    def showPagedWindowTitle(self, title, show=True):
        pager = self.widgetManager.get(WIDGET_NAMES.PagedWindow, title)
        pager.showTitle(show)

    def setPagedWindowTitle(self, title, pageTitle):
        pager = self.widgetManager.get(WIDGET_NAMES.PagedWindow, title)
        pager.setTitle(pageTitle)

    @contextmanager
    def page(self, windowTitle=None, pageNumber=None, sticky="nw", **kwargs):
        if windowTitle is None:
            pg = self.startPage(sticky)
        else:
            pg = self.openPage(windowTitle, pageNumber)
        self.configure(**kwargs)
        try: yield pg
        finally: self.stopPage()


    def startPage(self, sticky="nw"):
        if self._getContainerProperty('type') == WIDGET_NAMES.Page:
            self.warn("You didn't STOP the previous PAGE")
            self.stopPage()
        elif self._getContainerProperty('type') != WIDGET_NAMES.PagedWindow:
            raise Exception("Can't start a PAGE, currently in:",
                            self._getContainerProperty('type'))

        self.containerStack[-1]['widgets'] = True

        # generate a page title
        pageNum = self._getContainerProperty('container').frameStack.getNumFrames() + 1
        pageTitle = self._getContainerProperty('title') + "__" + str(pageNum)

        return self.startContainer(WIDGET_NAMES.Page, pageTitle, row=None, column=None, colspan=None, rowspan=None, sticky=sticky)

    def stopPage(self):
        if self._getContainerProperty('type') == WIDGET_NAMES.Page:
            self.stopContainer()
        else:
            raise Exception("Can't stop PAGE, currently in:",
                            self._getContainerProperty('type'))

    def stopPagedWindow(self):
        if self._getContainerProperty('type') == WIDGET_NAMES.Page:
            self.warn("You didn't STOP the previous PAGE")
            self.stopPage()

        if self._getContainerProperty('type') != WIDGET_NAMES.PagedWindow:
            raise Exception("Can't stop a PAGEDWINDOW, currently in:",
                            self._getContainerProperty('type'))

        self._getContainerProperty('container').stopPagedWindow()
        self.stopContainer()

#####################################
# Scrolled Panes
#####################################

    @contextmanager
    def scrollPane(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW", **kwargs):
        disabled = kwargs.pop("disabled", "")
        try:
            sp = self.startScrollPane(title, row, column, colspan, rowspan, sticky, disabled)
        except ItemLookupError:
            sp = self.openScrollPane(title)
        self.configure(**kwargs)
        try: yield sp
        finally: self.stopScrollPane()


    def startScrollPane(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW", disabled=""):
        return self.startContainer(WIDGET_NAMES.ScrollPane, title, row, column, colspan, rowspan, sticky, disabled)

    # functions to stop the various containers
    def stopContainer(self): self._removeContainer()

    def stopScrollPane(self):
        if self._getContainerProperty('type') != WIDGET_NAMES.ScrollPane:
            raise Exception("Can't stop a SCROLLPANE, currently in:",
                            self._getContainerProperty('type'))
        self.stopContainer()

    def stopAllPanedFrames(self):
        while True:
            try:
                self.stopPanedFrame()
            except:
                break

#####################################
# Frames
#####################################

    @contextmanager
    def frame(self, title=None, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW", **kwargs):
        if title is None: # new subFrame
            fr = self.startFrame(title, row, column, colspan, rowspan, sticky)
        else:
            frameNumber = kwargs.pop('frameNumber', None)
            try:
                if frameNumber is not None: fr = self.openSubFrame(title, frameNumber)
                else: fr = self.openFrame(title)
            except: # no widget
                fr = self.startFrame(title, row, column, colspan, rowspan, sticky)
        self.configure(**kwargs)
        try: yield fr
        finally: self.stopFrame()

    def startFrame(self, title=None, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW"):
        frameType = WIDGET_NAMES.Frame
        if self._getContainerProperty('type') == WIDGET_NAMES.FrameStack:
            # generate a frame title
            frameNum = self._getContainerProperty('container').getNumFrames()
            title = self._getContainerProperty('title') + "__" + str(frameNum)
            gui.trace("Adding new subFrame: %s", title)

            self.containerStack[-1]['widgets'] = True
            frameType = WIDGET_NAMES.SubFrame
        else:
            if title is None:
                raise Exception("All frames must have a title")
            gui.trace("Adding new frame: %s", title)

        return self.startContainer(frameType, title, row, column, colspan, rowspan, sticky)

    def stopFrame(self):
        if self._getContainerProperty('type') not in [WIDGET_NAMES.Frame, WIDGET_NAMES.SubFrame]:
            raise Exception("Can't stop a FRAME, currently in:",
                            self._getContainerProperty('type'))
        self.stopContainer()

    def raiseFrame(self, title):
        ''' will bring the named frame in front of any others '''
        gui.trace("Raising frame: %s", title)
        self.widgetManager.get(WIDGET_NAMES.Frame, title).lift()

#####################################
# FrameStack
#####################################

    @contextmanager
    def frameStack(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="NSEW", **kwargs):
        change = kwargs.pop("change", None)
        start = kwargs.pop("start", -1)
        try:
            fr = self.startFrameStack(title, row, column, colspan, rowspan, sticky, change=change, start=start)
        except ItemLookupError:
            fr = self.openFrameStack(title)
        self.configure(**kwargs)
        try: yield fr
        finally:
            self.stopFrameStack()

    def startFrameStack(self, title, row=None, column=0, colspan=0, rowspan=0, sticky="news", change=None, start=-1):
        fs = self.startContainer(WIDGET_NAMES.FrameStack, title, row, column, colspan, rowspan, sticky)
        fs.setChangeFunction(change)
        fs.setStartFrame(start)
        return fs

    def stopFrameStack(self):
        if self._getContainerProperty('type') != WIDGET_NAMES.FrameStack:
            raise Exception("Can't stop a FRAMESTACK, currently in:",
                            self._getContainerProperty('type'))
        self.stopContainer()

    def setStartFrame(self, title, num):
        self.widgetManager.get(WIDGET_NAMES.FrameStack, title).setStartFrame(num)

    def nextFrame(self, title, callFunction=True):
        self.widgetManager.get(WIDGET_NAMES.FrameStack, title).showNextFrame(callFunction)
    def prevFrame(self, title, callFunction=True):
        self.widgetManager.get(WIDGET_NAMES.FrameStack, title).showPrevFrame(callFunction)
    def firstFrame(self, title, callFunction=True):
        self.widgetManager.get(WIDGET_NAMES.FrameStack, title).showFirstFrame(callFunction)
    def lastFrame(self, title, callFunction=True):
        self.widgetManager.get(WIDGET_NAMES.FrameStack, title).showLastFrame(callFunction)
    def selectFrame(self, title, num, callFunction=True):
        if type(num) in (list, tuple): num = num[0]
        num = int(num)
        self.widgetManager.get(WIDGET_NAMES.FrameStack, title).showFrame(num, callFunction)

    def countFrames(self, title):
        return self.widgetManager.get(WIDGET_NAMES.FrameStack, title).getNumFrames()
    def getCurrentFrame(self, title):
        return self.widgetManager.get(WIDGET_NAMES.FrameStack, title).getCurrentFrame()
    def getPreviousFrame(self, title):
        return self.widgetManager.get(WIDGET_NAMES.FrameStack, title).getPreviousFrame()

    def frameStackAtStart(self, title):
        return self.widgetManager.get(WIDGET_NAMES.FrameStack, title).atStart()
    def frameStackAtEnd(self, title):
        return self.widgetManager.get(WIDGET_NAMES.FrameStack, title).atEnd()

#####################################
# SubWindows
#####################################

    @contextmanager
    def subWindow(self, name, title=None, modal=False, blocking=False, transient=False, grouped=True, **kwargs):
        visible = kwargs.pop("visible", None)
        try:
            sw = self.startSubWindow(name, title, modal, blocking, transient, grouped)
        except ItemLookupError:
            sw = self.openSubWindow(name)
        self.configure(**kwargs)

        try:
            yield sw
        finally:
            self.stopSubWindow()
        if visible is True: self.showSubWindow(name)

    def startSubWindow(self, name, title=None, modal=False, blocking=False, transient=False, grouped=True):
        self.widgetManager.verify(WIDGET_NAMES.SubWindow, name)
        gui.trace("Starting subWindow %s", name)

        top = SubWindow(self, self.topLevel, name, title=title, stopFunc = self.confirmHideSubWindow,
                        modal=modal, blocking=blocking, transient=transient, grouped=grouped)

        ico = self._getTopLevel().winIcon

        self.widgetManager.add(WIDGET_NAMES.SubWindow, name, top)

        # now, add to top of stack
        self._addContainer(name, WIDGET_NAMES.SubWindow, top, 0, 1, "")

        # add an icon if required
        if ico is not None:
            self.setIcon(ico)
        else:
            top.winIcon = None

        return top

    def stopSubWindow(self):
        container = self.containerStack[-1]
        if container['type'] == WIDGET_NAMES.SubWindow:
            if not hasattr(container["container"], 'ms'):
                self.setMinSize(container["container"])
            self.stopContainer()
        else:
            raise Exception("Can't stop a SUBWINDOW, currently in:",
                            self._getContainerProperty('type'))

    def setSubWindowLocation(self, title, x, y):
        self.widgetManager.get(WIDGET_NAMES.SubWindow, title).setLocation(x, y)

    def showAllSubWindows(self):
        for sub in self.widgetManager.group(WIDGET_NAMES.SubWindow):
            self.showSubWindow(sub)

    # functions to show/hide/destroy SubWindows
    def showSubWindow(self, title, hide=False, follow=False):
        tl = self.widgetManager.get(WIDGET_NAMES.SubWindow, title)
        if hide:
            self.hideAllSubWindows()
        gui.trace("Showing subWindow %s", title)

        tl.show()
        self._bringToFront(tl)
        tl.block()

        return tl

    def hideAllSubWindows(self, useStopFunction=False):
        for sub in self.widgetManager.group(WIDGET_NAMES.SubWindow):
            self.hideSubWindow(sub, useStopFunction)

    def hideSubWindow(self, title, useStopFunction=False):
        self.widgetManager.get(WIDGET_NAMES.SubWindow, title).hide(useStopFunction)

    def confirmHideSubWindow(self, title):
        self.hideSubWindow(title, True)

    def destroySubWindow(self, title):
        gui.trace("Destroying SubWindow %s", title)
        tl = self.widgetManager.get(WIDGET_NAMES.SubWindow, title)
        tl.prepDestroy()
        # get rid of all the kids!
        self.cleanseWidgets(tl)

    def destroyAllSubWindows(self):
        gui.trace("Destroying all SubWindows")
        keys = list(self.widgetManager.group(WIDGET_NAMES.SubWindow).keys())
        for k in keys:
            gui.trace("Destroying SubWindow: %s", k)
            wi = self.widgetManager.get(WIDGET_NAMES.SubWindow, k)
            self.cleanseWidgets(wi)

        # access has widgets stored in the standard widget store
        self.accessMade = False

#####################################
# END containers
#####################################

    # function to destroy widget & all children
    # will also attempt to remove all trace from config dictionaries
    def cleanseWidgets(self, widget):
        widgType = gui.GET_WIDGET_CLASS(widget)
        gui.trace("Attempting to cleanse: %s", widgType)

        # make sure we've cleansed any children first
        for child in widget.winfo_children():
            self.cleanseWidgets(child)


        if hasattr(widget, 'APPJAR_TYPE'):
            widgType = widget.APPJAR_TYPE
            widgName = WIDGET_NAMES.name(widgType)
            gui.trace("Cleansing: %s", widgName)

            if widgType not in [WIDGET_NAMES.Tab, WIDGET_NAMES.Page]:
                if not self.widgetManager.destroyWidget(widgType, widget):
                    self.warn("Unable to destroy %s, during cleanse - destroy returned False", widgName)

                # must clear the frameLabel's label as well
                if widgType == WIDGET_NAMES.FrameLabel:
                    gui.trace("Also Cleansing: %s", WIDGET_NAMES.name(WIDGET_NAMES.Label))
                    if not self.widgetManager.destroyWidget(WIDGET_NAMES.Label, widget):
                        self.warn("Unable to destroy %s, during cleanse - destroy returned False", WIDGET_NAMES.Label)
            else:
                self.trace("Skipped %s, cleansed by parent", widgType)

            # need to remove if a container
            if widgName in WIDGET_NAMES.containers:
                self.trace("Destroying container: %s", widgName)
                self.widgetManager.destroyContainer(WIDGET_NAMES.ContainerLog, widget)

#        elif widgType in ('CanvasDnd', 'ValidationLabel', 'TabBorder', 'TabContainer', 'TabText', 'BgLabel') or hasattr(widget, 'SKIP_CLEANSE'):
        elif widgType in ('CanvasDnd', 'ValidationLabel', 'Grip',
                            'TabBorder', 'TabContainer', 'TabText', 'BgLabel') \
                            or widget.__dict__.get('SKIP_CLEANSE', False):
            pass # not logged in WidgetManager
        else:
            self.warn("Unable to destroy %s, during cleanse - NO APPJAR TYPE", gui.GET_WIDGET_CLASS(widget))

    # functions to hide & show the main window
    def hide(self, btn=None):
        self._getTopLevel().displayed = False
        self._getTopLevel().withdraw()

    def show(self, btn=None):
        self._getTopLevel().displayed = True
        self._getTopLevel().deiconify()

    def setVisible(self, visible=True):
        if visible: self.show()
        else: self.hide()

    def getVisible(self):
        return self.topLevel.displayed

    visible = property(getVisible, setVisible)


#####################################
# warn when bad functions called...
#####################################
    def __getattr__(self, name):
        def handlerFunction(*args, **kwargs):
            self.warn("Unknown function: <%s> Check your spelling, do you need more camelCase?", name)
        return handlerFunction

    def __setattr__(self, name, value):
        # would this create a new attribute?
        if self.built and not hasattr(self, name):
            raise AttributeError("Creating new attributes is not allowed!")
        super(gui, self).__setattr__(name, value)

#####################################
# LabelBox Functions
#####################################

    # this will build a frame, with a label on the left hand side
    def _getLabelBox(self, title, **kwargs):
        self.widgetManager.verify(WIDGET_NAMES.Label, title)

        label = kwargs.pop('label', title)
        if label is True: label = title
        font = kwargs.pop('font', self._getContainerProperty('labelFont'))

        # first, make a frame
        frame = self._makeLabelBox()(self.getContainer())
        if not self.ttkFlag:
            frame.config(background=self._getContainerBg())
        self.widgetManager.log(WIDGET_NAMES.FrameBox, frame)

        # next make the label
        if self.ttkFlag:
            lab = ttk.Label(frame)
        else:
            lab = Label(frame, background=self._getContainerBg())

        frame.theLabel = lab
        lab.hidden = False
        lab.inContainer = True
        lab.config(
            text=label,
            anchor=W,
            justify=LEFT,
            font=font
        )

        if not self.ttkFlag:
            lab.config(background=self._getContainerBg())
        lab.DEFAULT_TEXT = label

        self.widgetManager.add(WIDGET_NAMES.Label, title, lab)
        self.widgetManager.add(WIDGET_NAMES.FrameLabel, title, lab)

        # now put the label in the frame
        lab.pack(side=LEFT, fill=Y)

        return frame

    # this is where we add the widget to the frame built above
    def _packLabelBox(self, frame, widget):
        widget.pack(side=LEFT, fill=BOTH, expand=True)
        widget.inContainer = True
        frame.theWidget = widget
        #widget.grid( row=0, column=1, sticky=W+E )
        #Grid.columnconfigure(frame, 1, weight=1)
        #Grid.rowconfigure(frame, 0, weight=1)

    # function to resize labels, if they are hidden or shown
    # not using this for two reasons:
    # - doesn't really work when font size changes
    # - breaks when things in containers

# this can be made into a container property

#    def _updateLabelBoxes(self, title, column):
#        if len(title) >= self.labWidth.get(column, -1):
#            self.labWidth[column] = len(title)
#            # loop through other labels and resize
#            for na, wi in self.widgetManager.group(WIDGET_NAMES.FrameLabel).items():
#                col = wi.master.grid_info().get("column", wi.master.master.grid_info().get("column", -1))
#                if int(col) == column:
#                    wi.config(width=self.labWidth[column])

#####################################
# FUNCTION for check boxes
#####################################

    def tick(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for checkBox() """
        return self.checkBox(title, value, *args, **kwargs)

    def check(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for checkBox() """
        return self.checkBox(title, value, *args, **kwargs)

    def checkBox(self, title, value=None, *args, **kwargs):
        """ adds, sets & gets checkBoxes all in one go """
        widgKind = WIDGET_NAMES.CheckBox
        callFunction = kwargs.pop("callFunction", True)
        text = kwargs.pop("text", None)

        try: self.widgetManager.verify(widgKind, title)
        except: #widget exists
            if value is not None: self.setCheckBox(title, ticked=value, callFunction=callFunction)
            check = self.getCheckBox(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            check = self._checkBoxMaker(title, *args, **kwargs)
            if value is not None: self.setCheckBox(title, value)
        if text is not None: self.setCheckBoxText(title, text)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return check

    def _checkBoxMaker(self, title, value=None, kind="cb", row=None, column=0, colspan=0, rowspan=0, **kwargs):
        """ internal wrapper to hide kwargs from original add functions """
        name = kwargs.pop("name", kwargs.pop('label', None))
        return self.addCheckBox(title, row, column, colspan, rowspan, name)

    def addCheckBox(self, title, row=None, column=0, colspan=0, rowspan=0, name=None):
        ''' adds a new check box, at the specified position '''
        self.widgetManager.verify(WIDGET_NAMES.CheckBox, title)
        var = IntVar(self.topLevel)
        if name is None:
            name = title

        if not self.ttkFlag:
            cb = Checkbutton(self.getContainer(), text=name, variable=var)
            cb.config(
                font=self._getContainerProperty('labelFont'),
                background=self._getContainerBg(),
                activebackground=self._getContainerBg(),
                anchor=W)
        else:
            cb = ttk.Checkbutton(self.getContainer(), text=name, variable=var)

        cb.DEFAULT_TEXT = name
        cb.bind("<Button-1>", self._grabFocus)
        self.widgetManager.add(WIDGET_NAMES.CheckBox, title, cb)
        self.widgetManager.add(WIDGET_NAMES.CheckBox, title, var, group=WidgetManager.VARS)
        self._positionWidget(cb, row, column, colspan, rowspan, EW)
        return cb

    def setCheckBoxText(self, title, text):
        cb = self.widgetManager.get(WIDGET_NAMES.CheckBox, title)
        cb.DEFAULT_TEXT = text
        cb.config(text=text)

    def addNamedCheckBox(self, name, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a new check box, at the specified position, with the name as the text '''
        return self.addCheckBox(title, row, column, colspan, rowspan, name)

    def getCheckBox(self, title):
        bVar = self.widgetManager.get(WIDGET_NAMES.CheckBox, title, group=WidgetManager.VARS)
        if bVar.get() == 1:
            return True
        else:
            return False

    def getAllCheckBoxes(self):
        cbs = {}
        for k in self.widgetManager.group(WIDGET_NAMES.CheckBox):
            cbs[k] = self.getCheckBox(k)
        return cbs

    def setCheckBox(self, title, ticked=True, callFunction=True):
        cb = self.widgetManager.get(WIDGET_NAMES.CheckBox, title)
        bVar = self.widgetManager.get(WIDGET_NAMES.CheckBox, title, group=WidgetManager.VARS)
        bVar.set(ticked)
        if ticked:
            if not self.ttkFlag:
                cb.select()
            else:
                cb.state(['selected'])
        else:
            if not self.ttkFlag:
                cb.deselect()
            else:
                cb.state(['!selected'])
        # now call function
        if callFunction:
            if hasattr(cb, 'cmd'):
                cb.cmd()

    def setCheckBoxBoxBg(self, title, newCol):
        self.setCheckBoxSelectColour(title, newCol)

    def setCheckBoxSelectColour(self, title, newCol):
        cb = self.widgetManager.get(WIDGET_NAMES.CheckBox, title)
        cb.config(selectcolor=newCol)

    def clearAllCheckBoxes(self, callFunction=False):
        for cb in self.widgetManager.group(WIDGET_NAMES.CheckBox):
            self.setCheckBox(cb, ticked=False, callFunction=callFunction)

#####################################
# FUNCTION for scales
#####################################

    def slider(self, title, *args, **kwargs):
        """ simpleGUI - alternative for scale() """
        return self.scale(title, *args, **kwargs)

    def scale(self, title, *args, **kwargs):
        """ simpleGUI - adds, sets & gets scales all in one go """
        widgKind = WIDGET_NAMES.Scale

        vert = kwargs.pop("direction", "horizontal").lower() == "vertical"
        increment = kwargs.pop("increment", None)
        value = kwargs.pop("value", None)
        interval = kwargs.pop("interval", None)
        show = kwargs.pop("show", False)
        _range = kwargs.pop("range", None)
        callFunction = kwargs.pop("callFunction", True)
        label = kwargs.pop("label", False)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            scale = self.getScale(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            scale = self._scaleMaker(title, label, *args, **kwargs)

        if _range is not None: self.setScaleRange(title, _range[0], _range[1])
        if vert: self.setScaleVertical(title)
        if increment is not None: self.setScaleIncrement(title, increment)
        if interval is not None: self.showScaleIntervals(title, interval)
        if show: self.showScaleValue(title)
        if value is not None: self.setScale(title, value, callFunction)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return scale

    def _buildScale(self, title, frame):
        self.widgetManager.verify(WIDGET_NAMES.Scale, title)
        var = DoubleVar(self.topLevel)
        if not self.ttkFlag:
            scale = self._makeAjScale()(frame, increment=10, variable=var, repeatinterval=10, orient=HORIZONTAL, font=self._getContainerProperty('inputFont'))
            scale.config(digits=1, showvalue=False, highlightthickness=1)
        else:
            scale = self._makeAjScale()(frame, increment=10, variable=var, orient=HORIZONTAL)

        scale.bind("<Button-1>", self._grabFocus, "+")
        scale.var = var
        scale.inContainer = False
        self.widgetManager.add(WIDGET_NAMES.Scale, title, scale)
        return scale

    def _scaleMaker(self, title, label, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        if label: return self.addLabelScale(title, row, column, colspan, rowspan, label)
        else: return self.addScale(title, row, column, colspan, rowspan)

    def addScale(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a slidable scale at the specified position '''
        scale = self._buildScale(title, self.getContainer())
        self._positionWidget(scale, row, column, colspan, rowspan)
        return scale

    def addLabelScale(self, title, row=None, column=0, colspan=0, rowspan=0, label=True):
        ''' adds a slidable scale, with a label showing the title  at the specified position '''
        frame = self._getLabelBox(title, label=label)
        scale = self._buildScale(title, frame)
        self._packLabelBox(frame, scale)
        self._positionWidget(frame, row, column, colspan, rowspan)
        return scale

    def getScale(self, title):
        sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
        return sc.get()

    def getAllScales(self):
        scales = {}
        for k in self.widgetManager.group(WIDGET_NAMES.Scale):
            scales[k] = self.getScale(k)
        return scales

    def setScale(self, title, pos, callFunction=True):
        sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
        with PauseCallFunction(callFunction, sc):
            sc.set(pos)

    def clearAllScales(self, callFunction=False):
        for sc in self.widgetManager.group(WIDGET_NAMES.Scale):
            self.setScale(sc, self.widgetManager.get(WIDGET_NAMES.Scale, sc).cget("from"), callFunction=callFunction)

    def setScaleIncrement(self, title, increment):
        sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
        sc.increment = increment

    def setScaleLength(self, title, length):
        if not self.ttkFlag:
            sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
            sc.config(sliderlength=length)
        else:
            self.warn("ttk: setScaleLength() not supported: %s", title)

    # this will make the scale show interval numbers
    # set to 0 to remove
    def showScaleIntervals(self, title, intervals):
        if not self.ttkFlag:
            sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
            sc.config(tickinterval=intervals)
        else:
            self.warn("ttk: showScaleIntervals() not supported: %s", title)

    # this will make the scale show its value
    def showScaleValue(self, title, show=True):
        if not self.ttkFlag:
            sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
            sc.config(showvalue=show)
        else:
            self.warn("ttk: showScaleValue() not supported: %s", title)

    # change the orientation (Hor or Vert)
    def setScaleVertical(self, title):
        sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
        sc.config(orient=VERTICAL)

    def setScaleHorizontal(self, title):
        sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
        sc.config(orient=HORIZONTAL)

    def setScaleRange(self, title, start, end, curr=None):
        if curr is None:
            curr = start
        sc = self.widgetManager.get(WIDGET_NAMES.Scale, title)
        sc.config(from_=start, to=end)
        self.setScale(title, curr)

        # set the increment as 10%
        try:
            res = sc.cget("resolution")
            diff = int((((end - start)/res)/10)+0.99) # add 0.99 to round up...
            sc.increment = diff
        except:
            pass # resolution not supported in ttk

#####################################
# FUNCTION for optionMenus
#####################################

    def combo(self, title, value=None, *args, **kwargs):
        """ shortner for optionBox() """
        return self.optionBox(title, value, *args, **kwargs)

    def option(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for optionBox() """
        return self.optionBox(title, value, *args, **kwargs)

    def optionbox(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for optionBox() """
        return self.optionBox(title, value, *args, **kwargs)

    def optionBox(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets optionBoxes all in one go """
        widgKind = WIDGET_NAMES.OptionBox
        kind = kwargs.pop("kind", "standard").lower().strip()
        label = kwargs.pop("label", False)
        callFunction = kwargs.pop("callFunction", True)
        override = kwargs.pop("override", False)
        checked = kwargs.pop("checked", True)
        selected = kwargs.pop("selected", None)
        disabled = kwargs.pop("disabled", "-")

        # select=set, replace=change, rename=rename, clear=clear, delete=delete
        if value is None: mode = 'get'
        else: mode = 'select'
        mode = kwargs.pop("mode", mode)
        index = kwargs.pop("index", None)
        newName = kwargs.pop("newName", None)

        try: self.widgetManager.verify(WIDGET_NAMES.OptionBox, title)
        except: # widget exists
            if mode == "select":
                if value is not None: self.setOptionBox(title, index=value, value=True, callFunction=callFunction, override=override)
                else: gui.error("No item specified to select in optionBox: %s", title)
            elif mode == "deselect":
                if value is not None: self.setOptionBox(title, index=value, value=False, callFunction=callFunction, override=override)
                else:
                    self.clearOptionBox(title, callFunction=callFunction)
                    gui.info("optionBox set back to its original state: %s", title)
            elif mode == "toggle":
                gui.error("Toggling optionboxes not supported: %s", title)
            elif mode == "clear":
                if value is not None: gui.error("No value should be specified wen clearing optionBox: %s", title)
                else: self.clearOptionBox(title, callFunction=callFunction)
            elif mode == "rename":
                if value is not None: self.renameOptionBox(title, item=value, newName=newName, callFunction=callFunction)
                else: gui.error("No item specified to rename in optionBox: %s", title)
            elif mode == "replace":
                if value is not None: self.changeOptionBox(title, options=value, index=index, callFunction=callFunction)
                else: gui.error("No values specified to replace in optionBox: %s", title)
            elif mode == "delete":
                if value is not None: self.deleteOptionBox(title, index=value)
                else: gui.error("No item specified to delete in optionBox: %s", title)
            elif mode == "get":
                pass
            else:
                gui.error("Invalid mode (%s) specified in optionBox: %s", mode, title)

            opt =  self.getOptionBox(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if kind == "ticks":
                if label: opt = self.addLabelTickOptionBox(title, value, *args, label=label, disabled=disabled, **kwargs)
                else: opt = self.addTickOptionBox(title, value, *args, disabled=disabled, **kwargs)
            else:
                if label: opt = self.addLabelOptionBox(title, value, *args, label=label, disabled=disabled, **kwargs)
                else: opt = self.addOptionBox(title, value, *args, disabled=disabled, **kwargs)
                if selected is not None: self.setOptionBox(title, selected)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return opt

    def addDbOptionBox(self, title, db, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        ''' adds an option box, with a list of tables form the specified database '''
        data = self._getDbTables(db)
        opt = self.option(title, data, row, column, colspan, rowspan, **kwargs)
        opt.db = db
        return opt

    def _buildOptionBox(self, frame, title, options, kind="normal", disabled='-'):
        """ Internal wrapper, used for building OptionBoxes.
        It will use the kind to choose either a standard OptionBox or a TickOptionBox.
        ref: http://stackoverflow.com/questions/29019760/how-to-create-a-combobox-that-includes-checkbox-for-each-item

        :param frame: this should be a container, used as the parent for the OptionBox
        :param title: the key used to reference this OptionBox
        :param options: a list of values to put in the OptionBox, can be len 0
        :param kind: the style of OptionBox: notmal or ticks
        :returns: the created OptionBox
        :raises ItemLookupError: if the title is already in use
        """
        self.widgetManager.verify(WIDGET_NAMES.OptionBox, title)

        # create a string var to hold selected item
        var = StringVar(self.topLevel)
        self.widgetManager.add(WIDGET_NAMES.OptionBox, title, var, group=WidgetManager.VARS)

        maxSize, options = self._configOptionBoxList(title, options, kind)

        if len(options) > 0 and kind == "normal":
            option = ajOption(frame, var, *options)
            var.set(options[0])
            option.kind = "normal"

        elif kind == "ticks":
            option = ajOption(frame, variable=var, value="")
            self._buildTickOptionBox(title, option, options)
        else:
            option = ajOption(frame, var, [])
            option.kind = "normal"

        option.config(
            justify=LEFT,
            font=self._getContainerProperty('inputFont'),
#            background=self._getContainerBg(),
            highlightthickness=0,
            width=maxSize,
            takefocus=1)
        option.bind("<Button-1>", self._grabFocus)

        # compare on windows & mac
        #option.config(highlightthickness=12, bd=0, highlightbackground=self._getContainerBg())
        option.var = var
        option.maxSize = maxSize
        option.inContainer = False
        option.options = options
        option.disabled = disabled

        option.DEFAULT_TEXT=""
        if options is not None:
            option.DEFAULT_TEXT='\n'.join(str(x) for x in options)

#        if self.platform == self.MAC:
#            option.config(highlightbackground=self._getContainerBg())

        option.bind("<Tab>", self._focusNextWindow)
        option.bind("<Shift-Tab>", self._focusLastWindow)

        # add a right click menu
        self._addRightClickMenu(option)

        # disable any separators
        self._disableOptionBoxSeparators(option)

        # add to array list
        self.widgetManager.add(WIDGET_NAMES.OptionBox, title, option)
        return option

    def _buildTickOptionBox(self, title, option, options):
        """ Internal wrapper, used for building TickOptionBoxes.
        Called by _buildOptionBox & changeOptionBox.
        Will add each of the options as a tick box, and use the title as a disabled header.

        :param title: the key used to reference this OptionBox
        :param option: an existing OptionBox that will be emptied & repopulated
        :param options: a list of values to put in the OptionBox, can be len 0
        :returns: None - the option param is modified
        :raises ItemLookupError: if the title can't be found
        """
        # delete any items - either the initial one when created, or any existing ones if changing
        option['menu'].delete(0, 'end')
        var = self.widgetManager.get(WIDGET_NAMES.OptionBox, title, group=WidgetManager.VARS)
        var.set(title)
        vals = {}
        for o in options:
            vals[o] = BooleanVar()
            option['menu'].add_checkbutton( label=o, onvalue=True, offvalue=False, variable=vals[o])
        self.widgetManager.update(WIDGET_NAMES.TickOptionBox, title, vals, group=WidgetManager.VARS)
        option.kind = "ticks"

    def addOptionBox(self, title, options, row=None, column=0, colspan=0, rowspan=0, disabled='-', **kwargs):
        """ Adds a new standard OptionBox.
        Simply calls internal function _buildOptionBox.

        :param title: the key used to reference this OptionBox
        :param options: a list of values to put in the OptionBox, can be len 0
        :returns: the created OptionBox
        :raises ItemLookupError: if the title is already in use
        """
        option = self._buildOptionBox(self.getContainer(), title, options, disabled=disabled)
        self._positionWidget(option, row, column, colspan, rowspan)
        return option

    def addLabelOptionBox(self, title, options, row=None, column=0, colspan=0, rowspan=0, disabled="-", **kwargs):
        """ Adds a new standard OptionBox, with a Label before it.
        Simply calls internal function _buildOptionBox, placing it in a LabelBox.

        :param title: the key used to reference this OptionBox and text for the Label
        :param options: a list of values to put in the OptionBox, can be len 0
        :returns: the created OptionBox (not the LabelBox)
        :raises ItemLookupError: if the title is already in use
        """
        frame = self._getLabelBox(title, **kwargs)
        option = self._buildOptionBox(frame, title, options, disabled=disabled)
        self._packLabelBox(frame, option)
        self._positionWidget(frame, row, column, colspan, rowspan)
        return option

    def addTickOptionBox(self, title, options, row=None, column=0, colspan=0, rowspan=0, disabled="-", **kwargs):
        """ Adds a new TickOptionBox.
        Simply calls internal function _buildOptionBox.

        :param title: the key used to reference this TickOptionBox
        :param options: a list of values to put in the TickOptionBox, can be len 0
        :returns: the created TickOptionBox
        :raises ItemLookupError: if the title is already in use
        """
        tick = self._buildOptionBox(self.getContainer(), title, options, kind="ticks", disabled=disabled)
        self._positionWidget(tick, row, column, colspan, rowspan)
        return tick

    def addLabelTickOptionBox(self, title, options, row=None, column=0, colspan=0, rowspan=0, disabled="-", **kwargs):
        """ Adds a new TickOptionBox, with a Label before it
        Simply calls internal function _buildOptionBox, placing it in a LabelBox

        :param title: the key used to reference this TickOptionBox, and text for the Label
        :param options: a list of values to put in the TickOptionBox, can be len 0
        :returns: the created TickOptionBox (not the LabelBox)
        :raises ItemLookupError: if the title is already in use
        """
        frame = self._getLabelBox(title, **kwargs)
        tick = self._buildOptionBox(frame, title, options, kind="ticks", disabled=disabled)
        self._packLabelBox(frame, tick)
        self._positionWidget(frame, row, column, colspan, rowspan)
        return tick

    def getOptionBox(self, title):
        """ Gets the selected item from the named OptionBox

        :param title: the OptionBox to check
        :returns: the selected item in an OptionBox or a dictionary of all items and their status for a TickOptionBox
        :raises ItemLookupError: if the title can't be found
        """
        box = self.widgetManager.get(WIDGET_NAMES.OptionBox, title)

        if box.kind == "ticks":
            val = self.widgetManager.get(WIDGET_NAMES.TickOptionBox, title, group=WidgetManager.VARS)
            retVal = {}
            for k, v in val.items():
                retVal[k] = bool(v.get())
            return retVal
        else:
            val = self.widgetManager.get(WIDGET_NAMES.OptionBox, title, group=WidgetManager.VARS)
            val = val.get().strip()
            # set to None if it's a divider
            if val.startswith("-") or len(val) == 0:
                val = None
            return val

    def getAllOptionBoxes(self):
        """ Convenience function to get the selected items for all OptionBoxes in the GUI.

        :returns: a dictionary containing the result of calling getOptionBox for every OptionBox/TickOptionBox in the GUI
        """
        boxes = {}
        for k in self.widgetManager.group(WIDGET_NAMES.OptionBox):
            boxes[k] = self.getOptionBox(k)
        return boxes

    def _disableOptionBoxSeparators(self, box):
        """ Loops through all items in box and if they start with a dash, disables them

        :param box: the OptionBox to process
        :returns: None
        """
        for pos, item in enumerate(box.options):
            if item.startswith(box.disabled):
                box["menu"].entryconfigure(pos, state="disabled")
            else:
                box["menu"].entryconfigure(pos, state="normal")

    def _configOptionBoxList(self, title, options, kind):
        """ Tidies up the list provided when an OptionBox is created/changed

        :param title: the title for the OptionBox - only used by TickOptionBox to calculate max size
        :param options: the list to tidy
        :param kind: The kind of option box (normal or ticks)
        :returns: a tuple containing the maxSize (width) and tidied list of items
        """

        # deal with a dict_keys object - messy!!!!
        if not isinstance(options, list):
            options = list(options)

        # make sure all options are strings
        options = [str(i) for i in options]

        # check for empty strings, replace first with message, remove rest
        found = False
        newOptions = []
        for pos, item in enumerate(options):
            if str(item).strip() == "":
                if not found:
                    newOptions.append("- options -")
                    found = True
            else:
                newOptions.append(item)

        options = newOptions

        # get the longest string length
        try:
            maxSize = len(str(max(options, key=len)))
        except:
            try:
                maxSize = len(str(max(options)))
            except:
                maxSize = 0

        # increase if ticks
        if kind == "ticks":
            if len(title) > maxSize:
                maxSize = len(title)

        # new bug?!? - doesn't fit anymore!
        if self.platform == self.MAC:
            maxSize += 3
        return maxSize, options

    def changeOptionBox(self, title, options, index=None, callFunction=False):
        """ Changes the entire contents of the named OptionBox
        ref: http://www.prasannatech.net/2009/06/tkinter-optionmenu-changing-choices.html

        :param title: the OptionBox to change
        :param options: the new values to put in the OptionBox
        :param index: an optional initial value to select
        :param callFunction: whether to generate an event to notify that the widget has changed
        :returns: None
        :raises ItemLookupError: if the title can't be found
        """
        # get the optionBox & associated var
        box = self.widgetManager.get(WIDGET_NAMES.OptionBox, title)

        # tidy up list and get max size
        maxSize, options = self._configOptionBoxList(title, options, "normal")

        # warn if new options bigger
        if maxSize > box.maxSize:
            self.warn("The new options are wider then the old ones: %s > %s", maxSize, box.maxSize)

        if box.kind == "ticks":
            self._buildTickOptionBox(title, box, options)
        else:
            # delete the current options
            box['menu'].delete(0, 'end')

            # add the new items
            for option in options:
                box["menu"].add_command(
                    label=option, command=lambda temp=option: box.setvar(
                        box.cget("textvariable"), value=temp))

            with PauseCallFunction(callFunction, box):
                box.var.set(options[0])

        box.options = options

        # disable any separators
        self._disableOptionBoxSeparators(box)
        # select the specified option
        self.setOptionBox(title, index, callFunction=False, override=True)

    def deleteOptionBox(self, title, index):
        """ Deleted the specified item from the named OptionBox

        :param title: the OptionBox to change
        :param inde: the value to delete - either a numeric index, or the text of an item
        :returns: None
        :raises ItemLookupError: if the title can't be found
        """
        self.widgetManager.check(WIDGET_NAMES.OptionBox, title, group=WidgetManager.VARS)
        self.setOptionBox(title, index, value=None, override=True)

    def renameOptionBoxItem(self, title, item, newName=None, callFunction=False):
        """ Changes the text of the specified item in the named OptionBox
        :param title: the OptionBox to change
        :param item: the item to rename
        :param newName: the value to rename it with
        :param callFunction: whether to generate an event to notify that the widget has changed
        :returns: None
        :raises ItemLookupError: if the title can't be found
        """
        self.widgetManager.check(WIDGET_NAMES.OptionBox, title, group=WidgetManager.VARS)
        self.setOptionBox(title, item, value=newName, callFunction=callFunction)

    def clearOptionBox(self, title, callFunction=True):
        """ Deselects any items selected in the named OptionBox
        If a TickOptionBox, all items will be set to False (unticked)

        :param title: the OptionBox to change
        :param callFunction: whether to generate an event to notify that the widget has changed
        :returns: None
        :raises ItemLookupError: if the title can't be found
        """
        box = self.widgetManager.get(WIDGET_NAMES.OptionBox, title)
        if box.kind == "ticks":
            # loop through each tick, set it to False
            ticks = self.widgetManager.get(WIDGET_NAMES.TickOptionBox, title, group=WidgetManager.VARS)
            for k in ticks:
                self.setOptionBox(title, k, False, callFunction=callFunction)
        else:
            self.setOptionBox(title, 0, callFunction=callFunction, override=True)

    def clearAllOptionBoxes(self, callFunction=False):
        """ Convenience function to clear all OptionBoxes in the GUI
        Will simply call clearOptionBox on each OptionBox/TickOptionBox

        :param callFunction: whether to generate an event to notify that the widget has changed
        :returns: None
        """
        for k in self.widgetManager.group(WIDGET_NAMES.OptionBox):
            self.clearOptionBox(k, callFunction)

    def setOptionBoxDisabledChar(self, title, disabled="-"):
        box = self.widgetManager.get(WIDGET_NAMES.OptionBox, title)
        box.disabled = disabled
        self._disableOptionBoxSeparators(box)

    def setOptionBox(self, title, index, value=True, callFunction=True, override=False):
        """ Main purpose is to select/deselect the item at the specified position
        But will also: delete an item if value is set to None or rename an item if value is set to a String

        :param title: the OptionBox to change
        :param index: the position or value of the item to select/delete
        :param value: determines what to do to the item: if set to None, will delete the item, else it sets the items state
        :param callFunction: whether to generate an event to notify that the widget has changed
        :param override: if set to True, allows a disabled item to be selected
        :returns: None
        :raises ItemLookupError: if the title can't be found
        """
        box = self.widgetManager.get(WIDGET_NAMES.OptionBox, title)

        if box.kind == "ticks":
            gui.trace("Updating tickOptionBox")
            ticks = self.widgetManager.get(WIDGET_NAMES.TickOptionBox, title, group=WidgetManager.VARS)
            if index is None:
                gui.trace("Index empty - nothing to update")
                return
            elif index in ticks:
                gui.trace("Updating: %s", index)

                tick = ticks[index]
                try:
                    index_num = box.options.index(index)
                except:
                    self.warn("Unknown tick: %s in OptionBox: %s", index, title)
                    return

                with PauseCallFunction(callFunction, tick, useVar=False):
                    if value is None: # then we need to delete it
                        gui.trace("Deleting tick: %s from OptionBox %s", index, title)
                        box['menu'].delete(index_num)
                        del(box.options[index_num])
                        self.widgetManager.remove(WIDGET_NAMES.TickOptionBox, title, index, group=WidgetManager.VARS)
                    elif isinstance(value, bool):
                        gui.trace("Updating tick: %s from OptionBox: %s to: %s", index, title, value)
                        tick.set(value)
                    else:
                        gui.trace("Renaming tick: %s from OptionBox: %s to: %s", index, title, value)
                        ticks = self.widgetManager.get(WIDGET_NAMES.TickOptionBox, title, group=WidgetManager.VARS)
                        ticks[value] = ticks.pop(index)
                        box.options[index_num] = value
                        self.changeOptionBox(title, box.options)
                        for tick in ticks:
                            self.widgetManager.get(WIDGET_NAMES.TickOptionBox, title, group=WidgetManager.VARS)[tick].set(ticks[tick].get())
            else:
                if value is None:
                    self.warn("Unknown tick in deleteOptionBox: %s in OptionBox: %s" , index, title)
                else:
                    self.warn("Unknown tick in setOptionBox: %s in OptionBox: %s", index, title)
        else:
            gui.trace("Updating regular optionBox: %s at: %s to: %s", title, index, value)
            count = len(box.options)
            if count > 0:
                if index is None:
                    index = 0
                if not isinstance(index, int):
                    try:
                        index = box.options.index(index)
                    except:
                        if value is None:
                            self.warn("Unknown option in deleteOptionBox: %s in OptionBox: %s", index, title)
                        else:
                            self.warn("Unknown option in setOptionBox: %s in OptionBox: %s", index, title)
                        return

                gui.trace("--> index now: %s", index)

                if index < 0 or index > count - 1:
                    self.warn("Invalid option: %s. Should be between 0 and %s." , count-1, index)
                else:
                    if value is None: # then we can delete it...
                        gui.trace("Deleting option: %s from OptionBox: %s", index, title)
                        box['menu'].delete(index)
                        del(box.options[index])
                        self.setOptionBox(title, 0, callFunction=False, override=override)
                    elif isinstance(value, bool):
                        gui.trace("Updating: OptionBox: %s to: %s", title, index)
                        with PauseCallFunction(callFunction, box):
                            if not box['menu'].invoke(index):
                                if override:
                                    gui.trace("Setting OptionBox: %s to disabled option: %s", title, index)
                                    box["menu"].entryconfigure(index, state="normal")
                                    box['menu'].invoke(index)
                                    box["menu"].entryconfigure(index, state="disabled")
                                else:
                                    self.warn("Unable to set disabled option: %s in OptionBox %s. Try setting 'override=True'", index, title)
                            else:
                                gui.trace("Invoked item: %s", index)
                    else:
                        gui.trace("Renaming: %s from OptionBox: %s to: %s", index, title, value)
                        pos = box.options.index(self.widgetManager.get(WIDGET_NAMES.OptionBox, title, group=WidgetManager.VARS).get())
                        box.options[index] = value
                        self.changeOptionBox(title, box.options, pos)

            else:
                self.widgetManager.get(WIDGET_NAMES.OptionBox, title, group=WidgetManager.VARS).set("")
                self.warn("No items to select from: %s", title)

#####################################
# FUNCTION for GoogleMaps
#####################################

    def map(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets maps all in one go """
        widgKind = WIDGET_NAMES.Map
        zoom = kwargs.pop("zoom", None)
        size = kwargs.pop("size", None)
        terrain = kwargs.pop("terrain", None)
        proxy = kwargs.pop("proxy", None)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            gMap = self.getLabel(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            gMap = self.addGoogleMap(title, *args, **kwargs)

        if value is not None: self.setGoogleMapLocation(title, value)
        if zoom is not None: self.setGoogleMapZoom(title, zoom)
        if size is not None: self.setGoogleMapSize(title, size)
        if terrain is not None: self.setGoogleMapTerrain(title, terrain)
        if proxy is not None: self.setGoogleMapProxy(title, proxy)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return gMap

    def addGoogleMap(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a GoogleMap widget at the specified position '''
        self._loadURL()
        self._loadTooltip()
        if urlencode is False:
            raise Exception("Unable to load GoogleMaps - urlencode library not available")
        self.widgetManager.verify(WIDGET_NAMES.Map, title)
        gMap = GoogleMap(self.getContainer(), self, useTtk = self.ttkFlag, font=self._getContainerProperty('labelFont'))
        self._positionWidget(gMap, row, column, colspan, rowspan)
        self.widgetManager.add(WIDGET_NAMES.Map, title, gMap)
        return gMap

    def setGoogleMapProxy(self, title, proxyString):
        gMap = self.widgetManager.get(WIDGET_NAMES.Map, title)
        gMap.setProxyString(proxyString)

    def setGoogleMapLocation(self, title, location):
        self.searchGoogleMap(title, location)

    def searchGoogleMap(self, title, location):
        gMap = self.widgetManager.get(WIDGET_NAMES.Map, title)
        gMap.changeLocation(location)

    def setGoogleMapTerrain(self, title, terrain):
        gMap = self.widgetManager.get(WIDGET_NAMES.Map, title)
        if terrain not in gMap.TERRAINS:
            raise Exception("Invalid terrain. Must be one of " + str(gMap.TERRAINS))
        gMap.changeTerrain(terrain)

    def setGoogleMapZoom(self, title, mod):
        self. zoomGoogleMap(title, mod)

    def zoomGoogleMap(self, title, mod):
        gMap = self.widgetManager.get(WIDGET_NAMES.Map, title)
        if mod in ["+", "-"]:
            gMap.zoom(mod)
        elif isinstance(mod, int) and 0 <= mod <= 22:
            gMap.setZoom(mod)

    def setGoogleMapSize(self, title, size):
        gMap = self.widgetManager.get(WIDGET_NAMES.Map, title)
        gMap.setSize(size)

    def setGoogleMapMarker(self, title, location, size=None, colour=None, label=None, replace=False):
        gMap = self.widgetManager.get(WIDGET_NAMES.Map, title)
        if len(location) == 0:
            gMap.removeMarkers()
        else:
            gMap.addMarker(location, size, colour, label, replace)

    def removeGoogleMapMarker(self, title, label):
        gMap = self.widgetManager.get(WIDGET_NAMES.Map, title)
        if len(label) == 0:
            gMap.removeMarkers()
        else:
            gMap.removeMarker(label)

    def getGoogleMapZoom(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Map, title).params["zoom"]

    def getGoogleMapTerrain(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Map, title).params["maptype"].title()

    def getGoogleMapLocation(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Map, title).params["center"]

    def getGoogleMapSize(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Map, title).params["size"]

    def saveGoogleMap(self, title, fileLocation):
        gMap = self.widgetManager.get(WIDGET_NAMES.Map, title)
        return gMap.saveTile(fileLocation)

#####################################
# FUNCTION for matplotlib
#####################################

    def plot(self, title, t=None, s=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets plots all in one go """
        widgKind = WIDGET_NAMES.Plot
        nav = kwargs.pop("nav", kwargs.pop("showNav", False))

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            keepLabels = kwargs.pop("keepLabels", False)
            self.updatePlot(title, t, s, keepLabels=keepLabels)
            plot = self.widgetManager.get(WIDGET_NAMES.Plot, title).axes
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if t is not None:
                if s is not None:
                    plot = self.addPlot(title, t, s, *args, showNav=nav, **kwargs)
                else:
                    gui.warn("Invalid parameters for plot: must provide t & s")
                    return None
            else:
                plot = self.addPlotFig(title, *args, showNav=nav, **kwargs)

        return plot

    def addPlot(self, title, t, s, row=None, column=0, colspan=0, rowspan=0, width=None, height=None, showNav=False):
        ''' adds a MatPlotLib, with t/s plotted '''
        canvas, fig = self._addPlotFig(title, row, column, colspan, rowspan, width, height, showNav)
        axes = fig.add_subplot(111)
        axes.plot(t,s)
        canvas.axes = axes
        return axes

    def addPlotFig(self, title, row=None, column=0, colspan=0, rowspan=0, width=None, height=None, showNav=False):
        canvas, fig = self._addPlotFig(title, row, column, colspan, rowspan, width, height, showNav)
        return fig

    def _addPlotFig(self, title, row=None, column=0, colspan=0, rowspan=0, width=None, height=None, showNav=False):
        self.widgetManager.verify(WIDGET_NAMES.Plot, title)
        self._loadMatplotlib()
        if PlotCanvas is False:
            raise Exception("Unable to load MatPlotLib - plots not available")
        else:
            fig = PlotFig(tight_layout=True)

            if width is not None and height is not None:
                fig.set_size_inches(width,height,forward=True)

            frame = frameBase(self.getContainer())

            canvas = PlotCanvas(fig, frame)
            canvas._tkcanvas.config(background="#c0c0c0", borderwidth=0, highlightthickness=0)
            canvas.fig = fig
            canvas.draw()
            if showNav:
                navBar = PlotNav(canvas, frame)
                navBar.pack(side=TOP, fill=X, expand=0)
            canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)

#            self._positionWidget(canvas.get_tk_widget(), row, column, colspan, rowspan)
            self._positionWidget(frame, row, column, colspan, rowspan, sticky='news')
            self.widgetManager.add(WIDGET_NAMES.Plot, title, canvas)
            return canvas, fig

    def refreshPlot(self, title):
        canvas = self.widgetManager.get(WIDGET_NAMES.Plot, title)
        canvas.draw()

    def updatePlot(self, title, t, s, keepLabels=False):
        axes = self.widgetManager.get(WIDGET_NAMES.Plot, title).axes

        if keepLabels:
            xLab = axes.get_xlabel()
            yLab = axes.get_ylabel()
            pTitle = axes.get_title()
            handles, legends = axes.get_legend_handles_labels()

        axes.clear()
        axes.plot(t, s)

        if keepLabels:
            axes.set_xlabel(xLab)
            axes.set_ylabel(yLab)
            axes.set_title(pTitle)
            axes.legend(handles, legends)

        self.refreshPlot(title)
        return axes


#####################################
# FUNCTION to manage Properties Widgets
#####################################

    def properties(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets properties all in one go """
        widgKind = WIDGET_NAMES.Properties
        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
#            if value is not None:
# need to work out args...
#                self.setProperty(title, prop=value)
            props = self.getProperties(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            props = self.addProperties(title, value, *args, **kwargs)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return props

    def addProperties(self, title, values=None, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        ''' adds a new properties widget, displaying the dictionary of booleans as tick boxes '''
        self.widgetManager.verify(WIDGET_NAMES.Properties, title)
        haveTitle = True
        if self._getContainerProperty('type') == WIDGET_NAMES.ToggleFrame:
            self.containerStack[-1]['sticky'] = "ew"
            haveTitle = False

        props = Properties(self.getContainer(), title, values, haveTitle,
                            font=self._getContainerProperty('labelFont'), background=self._getContainerBg())
        self._positionWidget(props, row, column, colspan, rowspan)
        self.widgetManager.add(WIDGET_NAMES.Properties, title, props)
        return props

    def getProperties(self, title):
        props = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        return props.getProperties()

    def getAllProperties(self):
        props = {}
        for k in self.widgetManager.group(WIDGET_NAMES.Properties):
            props[k] = self.getProperties(k)
        return props

    def getProperty(self, title, prop):
        props = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        return props.getProperty(prop)

    def setProperty(self, title, prop, value=False, callFunction=True):
        props = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        props.addProperty(prop, value, callFunction=callFunction)

    def setProperties(self, title, props, callFunction=True):
        p = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        p.addProperties(props, callFunction=callFunction)

    def deleteProperty(self, title, prop):
        props = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        props.addProperty(prop, None, callFunction=False)

    def setPropertyText(self, title, prop, newText=None):
        props = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        props.renameProperty(prop, newText)

    def setPropertiesBoxBg(self, title, newCol):
        self.setPropertiesSelectColour(title, newCol)

    def setPropertiesSelectColour(self, title, newCol):
        props = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        props.config(selectcolor=newCol)

    def clearProperties(self, title, callFunction=True):
        props = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        props.clearProperties(callFunction)

    def clearAllProperties(self, callFunction=False):
        props = {}
        for k in self.widgetManager.group(WIDGET_NAMES.Properties):
            self.clearProperties(k, callFunction)

    def resetProperties(self, title, callFunction=True):
        props = self.widgetManager.get(WIDGET_NAMES.Properties, title)
        props.resetProperties(callFunction)

    def resetAllProperties(self, callFunction=False):
        props = {}
        for k in self.widgetManager.group(WIDGET_NAMES.Properties):
            self.resetProperties(k, callFunction)

#####################################
# FUNCTION to add spin boxes
#####################################

    def spin(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for spinBox() """
        return self.spinBox(title, value, *args, **kwargs)

    def spinbox(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for spinBox() """
        return self.spinBox(title, value, *args, **kwargs)

    def spinBox(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets spinBoxes all in one go """
        widgKind = WIDGET_NAMES.SpinBox

        endValue = kwargs.pop("endValue", None)
        selected = kwargs.pop("selected", None)
        item = kwargs.pop("item", None)
        label = kwargs.pop("label", False)

        # select=select, deselect=<RESET>, toggle=<NONE>, clear=??, rename=set, replace=update, delete=remov
        if value is None: mode = 'get'
        else: mode = 'select'
        mode = kwargs.pop("mode", mode)
        callFunction = kwargs.pop("callFunction", True)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            if mode == "select":
                if value is not None: self.setSpinBoxPos(title, value, *args, **kwargs)
                else: gui.error("No item specified to select in spinbox: %s", title)
            elif mode == "toggle":
                gui.error("%s not available on spinbox: %s", mode, title)
            elif mode in["clear", "deselect"]:
                self.clearSpinBox(title)
            elif mode == "rename":
                gui.error("%s not implemented yet in spinbox: %s", mode, title)
            elif mode == "replace":
                if value is not None: self.changeSpinBox(title, vals=value)
                else: gui.error("No values specified to replace in spinbox: %s", title)
            elif mode == "delete":
                gui.error("%s not implemented yet in spinbox: %s", mode, title)
            elif mode == "get":
                pass
            else:
                gui.error("Invalid mode (%s) specified in spinbox: %s", mode, title)

            spinBox =  self.getSpinBox(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if endValue is not None:
                if label: spinBox = self.addLabelSpinBoxRange(title, value, endValue, *args, label=label, **kwargs)
                else: spinBox = self.addSpinBoxRange(title, value, endValue, *args, **kwargs)
            else:
                if label: spinBox = self.addLabelSpinBox(title, value, *args, label=label, **kwargs)
                else: spinBox = self.addSpinBox(title, value, *args, **kwargs)

        if selected is not None: self.setSpinBoxPos(title, selected)
        if item is not None: self.setSpinBox(title, item)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return spinBox

    def _buildSpinBox(self, frame, title, vals):
        self.widgetManager.verify(WIDGET_NAMES.SpinBox, title)
        if type(vals) not in [list, tuple]:
            raise Exception("Can't create SpinBox " + title + ". Invalid values: " + str(vals))

        spin = Spinbox(frame)
        spin.var = StringVar(self.topLevel)
        spin.config(textvariable=spin.var)
        spin.inContainer = False
        spin.isRange = False
        spin.config(font=self._getContainerProperty('inputFont'), highlightthickness=0)

# adds bg colour under spinners
#        if self.platform == self.MAC:
#              spin.config(highlightbackground=self._getContainerBg())

        spin.bind("<Tab>", self._focusNextWindow)
        spin.bind("<Shift-Tab>", self._focusLastWindow)

        # store the vals in DEFAULT_TEXT
        spin.DEFAULT_TEXT=""
        if vals is not None:
            spin.DEFAULT_TEXT='\n'.join(str(x) for x in vals)

        self._populateSpinBox(spin, vals)

        # prevent invalid entries
        if self.validateSpinBox is None:
            self.validateSpinBox = (
                self.containerStack[0]['container'].register(
                    self._validateSpinBox), '%P', '%W')

        spin.config(validate='all', validatecommand=self.validateSpinBox)

        self.widgetManager.add(WIDGET_NAMES.SpinBox, title, spin)
        return spin

    def _populateSpinBox(self, spin, vals, reverse=True):
        # make sure it's a list
        # reverse it, so the spin box functions properly
        if reverse:
            vals = list(vals)
            vals.reverse()
        vals = tuple(vals)
        spin.config(values=vals)

    def _addSpinBox(self, title, values, row=None, column=0, colspan=0, rowspan=0):
        spin = self._buildSpinBox(self.getContainer(), title, values)
        self._positionWidget(spin, row, column, colspan, rowspan)
        self.setSpinBoxPos(title, 0)
        return spin

    def addSpinBox(self, title, values, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        ''' adds a spinbox, with the specified values '''
        return self._addSpinBox(title, values, row, column, colspan, rowspan)

    def addLabelSpinBox(self, title, values, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        ''' adds a spinbox, with the specified values, and a label displaying the title '''
        frame = self._getLabelBox(title, **kwargs)
        spin = self._buildSpinBox(frame, title, values)
        self._packLabelBox(frame, spin)
        self._positionWidget(frame, row, column, colspan, rowspan)
        self.setSpinBoxPos(title, 0)
        return spin

    def addSpinBoxRange(self, title, fromVal, toVal, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        ''' adds a spinbox, with a range of whole numbers '''
        vals = list(range(fromVal, toVal + 1))
        spin = self._addSpinBox(title, vals, row, column, colspan, rowspan)
        spin.isRange = True
        return spin

    def addLabelSpinBoxRange(self, title, fromVal, toVal, row=None, column=0, colspan=0, rowspan=0, label=True, **kwargs):
        ''' adds a spinbox, with a range of whole numbers, and a label displaying the title '''
        vals = list(range(fromVal, toVal + 1))
        spin = self.addLabelSpinBox(title, vals, row, column, colspan, rowspan, label=label)
        spin.isRange = True
        return spin

    def getSpinBox(self, title):
        spin = self.widgetManager.get(WIDGET_NAMES.SpinBox, title)
        return spin.get()

    def getAllSpinBoxes(self):
        boxes = {}
        for k in self.widgetManager.group(WIDGET_NAMES.SpinBox):
            boxes[k] = self.getSpinBox(k)
        return boxes

    # validates that an item in the named spinbox starts with the user_input
    def _validateSpinBox(self, user_input, widget_name):
        spin = self.containerStack[0]['container'].nametowidget(widget_name)

        vals = spin.cget("values")  # .split()
        vals = self._getSpinBoxValsAsList(vals)
        for i in vals:
            if i.startswith(user_input):
                return True

        self.containerStack[0]['container'].bell()
        return False

    # expects a valid spin box widget, and a valid value
    def _setSpinBoxVal(self, spin, val, callFunction=True):
        # now call function
        with PauseCallFunction(callFunction, spin):
            spin.var.set(val)

    # is it going to be a hash or list??
    def _getSpinBoxValsAsList(self, vals):
        vals.replace("{", "")
        vals.replace("}", "")
#        if "{" in vals:
#            vals = vals[1:-1]
#            vals = vals.split("} {")
#        else:
        vals = vals.split()
        return vals

    def setSpinBox(self, title, value, callFunction=True):
        spin = self.widgetManager.get(WIDGET_NAMES.SpinBox, title)
        vals = spin.cget("values")  # .split()
        vals = self._getSpinBoxValsAsList(vals)
        val = str(value)
        if val not in vals:
            raise Exception( "Invalid value: " + val + ". Not in SpinBox: " +
                        title + "=" + str(vals))
        self._setSpinBoxVal(spin, val, callFunction)

    def clearSpinBox(self, title, callFunction=False):
        self.setSpinBoxPos(title, 0, callFunction=callFunction)

    def clearAllSpinBoxes(self, callFunction=False):
        for sb in self.widgetManager.group(WIDGET_NAMES.SpinBox):
            self.setSpinBoxPos(sb, 0, callFunction=callFunction)

    def setSpinBoxPos(self, title, pos, callFunction=True):
        spin = self.widgetManager.get(WIDGET_NAMES.SpinBox, title)
        vals = spin.cget("values")  # .split()
        vals = self._getSpinBoxValsAsList(vals)
        pos = int(pos)
        if pos < 0 or pos >= len(vals):
            raise Exception( "Invalid position: " + str(pos) + ". No position in SpinBox: " +
                        title + "=" + str(vals))
        pos = len(vals) - 1 - pos
        val = vals[pos]
        self._setSpinBoxVal(spin, val, callFunction)

    def changeSpinBox(self, title, vals, reverse=True):
        spin = self.widgetManager.get(WIDGET_NAMES.SpinBox, title)
        if spin.isRange:
            self.warn("Can't convert %s RangeSpinBox to SpinBox", title)
        else:
            self._populateSpinBox(spin, vals, reverse)
            self.setSpinBoxPos(title, 0)

#####################################
# FUNCTION to add images
#####################################

    def image(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets images all in one go """

        widgKind = WIDGET_NAMES.Image
        kind = kwargs.pop("kind", "standard").lower().strip()
        speed = kwargs.pop("speed", None)
        drop = kwargs.pop("drop", None)
        over = kwargs.pop("over", None)
        submit = kwargs.pop("submit", None)
        _map = kwargs.pop("map", None)

        try: self.widgetManager.verify(widgKind, title)
        except: # already exists
            if value is not None:
                if kind == "data":
                    self.setImageData(title, value, **kwargs)
                elif kind == "icon":
                    gui.warn("Changing image icons not yet supported: %s.", title)
                else:
                    self.setImage(title, value)
            image =  self.getImage(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if kind == "icon":
                image = self.addIcon(title, value, *args, **kwargs)
            elif kind == "data":
                image = self.addImageData(title, value, *args, **kwargs)
            else:
                image = self.addImage(title, value, *args, **kwargs)


        if speed is not None: self.setAnimationSpeed(title, speed)
        if over is not None: self.setImageMouseOver(title, over)
        if submit is not None:
            if _map is not None: self.setImageMap(title, submit, _map)
            else: self.setImageSubmitFunction(title, submit)
        elif submit is None and _map is not None:
            gui.warn("Must specify a submit function when setting an image map: %s", title)
        if drop is not None: self.setImageDropTarget(title, drop)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return image

    # looks up label containing image
    def _animateImage(self, title, firstTime=False):
        if not self.alive: return
        try:
            lab = self.widgetManager.get(WIDGET_NAMES.Image, title)
        except ItemLookupError:
            # image destroyed...
            try: self.widgetManager.remove(WIDGET_NAMES.AnimationID, title)
            except: pass
            return
        if not lab.image.animating:
            self.widgetManager.remove(WIDGET_NAMES.AnimationID, title)
            return
        if firstTime and lab.image.alreadyAnimated:
            return

        lab.image.alreadyAnimated = True
        try:
            if lab.image.cached:
                pic = lab.image.pics[lab.image.anim_pos]
            else:
                pic = PhotoImage(file=lab.image.path,
                                 format="gif - {0}".format(lab.image.anim_pos))
                lab.image.pics.append(pic)
            lab.image.anim_pos += 1
            lab.config(image=pic)
            anim_id = self.topLevel.after(int(lab.image.anim_speed), self._animateImage, title)
            self.widgetManager.update(WIDGET_NAMES.AnimationID, title, anim_id)
        except IndexError:
            # will be thrown when we reach end of anim images
            lab.image.anim_pos = 0
            lab.image.cached = True
            self._animateImage(title)
        except TclError:
            # will be thrown when all images cached
            lab.image.anim_pos = 0
            lab.image.cached = True
            self._animateImage(title)

    def _preloadAnimatedImage(self, img):
        if not self.alive: return
        if img.cached:
            return
        try:
            pic = PhotoImage(file=img.path,
                             format="gif - {0}".format(img.anim_pos))
            img.pics.append(pic)
            img.anim_pos += 1
            self.preloadAnimatedImageId = self.topLevel.after(
                0, self._preloadAnimatedImage, img)
        # when all frames have been processed
        except TclError as e:
            # expected - when all images cached
            img.anim_pos = 0
            img.cached = True

    def _configAnimatedImage(self, img):
        img.alreadyAnimated = False
        img.isAnimated = True
        img.pics = []
        img.cached = False
        img.anim_pos = 0
        img.anim_speed = 150
        img.animating = True

    # simple way to check if image is animated
    def _checkIsAnimated(self, name):
        if imghdr.what(name) == "gif":
            try:
                PhotoImage(file=name, format="gif - 1")
                return True
            except:
                pass
        return False

    def setAnimationSpeed(self, name, speed):
        img = self.widgetManager.get(WIDGET_NAMES.Image, name).image
        if speed < 1:
            speed = 1
            self.warn("Setting %s speed to 1. Minimum animation speed is 1.", name)
        img.anim_speed = int(speed)

    def stopAnimation(self, name):
        img = self.widgetManager.get(WIDGET_NAMES.Image, name).image
        img.animating = False

    def startAnimation(self, name):
        img = self.widgetManager.get(WIDGET_NAMES.Image, name).image
        if not img.animating:
            img.animating = True
            anim_id = self.topLevel.after(img.anim_speed, self._animateImage, name)
            self.widgetManager.update(WIDGET_NAMES.AnimationID, name, anim_id)

    # function to set an alternative image, when a mouse goes over
    def setImageMouseOver(self, title, overImg):
        lab = self.widgetManager.get(WIDGET_NAMES.Image, title)

        # first check over image & cache it
        fullPath = self.getImagePath(overImg)
        self.topLevel.after(0, self._getImage, fullPath)

        leaveImg = lab.image.path
        lab.bind("<Leave>", lambda e: self.setImage(title, leaveImg, True))
        lab.bind("<Enter>", lambda e: self.setImage(title, fullPath, True))
        lab.hasMouseOver = True

    # function to set an image location
    def setImageLocation(self, location):
        if os.path.isdir(location):
            self.userImages = location
        else:
            raise Exception("Invalid image location: " + location)

    # get the full path of an image (including image folder)
    def getImagePath(self, imagePath):
        if imagePath is None:
            return None

        if self.userImages is not None:
            imagePath = os.path.join(self.userImages, imagePath)

        absPath = os.path.abspath(imagePath)
        return absPath

    # function to see if an image has changed
    def hasImageChanged(self, originalImage, newImage):
        newAbsImage = self.getImagePath(newImage)

        if originalImage is None:
            return True

        # filename has changed
        if originalImage.path != newAbsImage:
            return True

        # modification time has changed
        if originalImage.modTime != os.path.getmtime(newAbsImage):
            return True

        # no changes
        return False

    # function to remove image objects form cache
    def clearImageCache(self):
        self.widgetManager.clear(WIDGET_NAMES.ImageCache)

    # internal function to build an image function from a string
    def _getImageData(self, imageData, fmt="gif"):
        if fmt=="png":
            self._importPngimagetk()
            if PngImageTk is False:
                raise Exception("TKINTERPNG library not found, PNG files not supported: imageData")
            if sys.version_info >= (2, 7):
                self.warn("Image processing for .PNGs is slow. .GIF is the recommended format")
#                png = PngImageTk(imagePath)
#                png.convert()
#                photo = png.image
            else:
                raise Exception("PNG images only supported in python 3: imageData")

        elif fmt == "gif":
            imgObj = PhotoImage(data=imageData)

        else:
            # expect we already have a PhotoImage object, for example created by PIL
            imgObj = imageData


        imgObj.path = None
        imgObj.modTime = datetime.datetime.now()
        imgObj.isAnimated = False
        imgObj.animating = False
        return imgObj

    # internal function to check/build image object
    def _getImage(self, imagePath, checkCache=True, addToCache=True):
        if imagePath is None:
            return None

        # get the full image path
        imagePath = self.getImagePath(imagePath)

        # if we're caching, and we have a non-None entry in the cache - get it...
        photo = None
        if checkCache and imagePath in self.widgetManager.group(WIDGET_NAMES.ImageCache) and self.widgetManager.get(WIDGET_NAMES.ImageCache, imagePath) is not None:
            photo = self.widgetManager.get(WIDGET_NAMES.ImageCache, imagePath)

        # if the image hasn't changed, use the cache
        if not self.hasImageChanged(photo, imagePath):
            pass
        # else load a new one
        elif os.path.isfile(imagePath):
            if os.access(imagePath, os.R_OK):
                imgType = imghdr.what(imagePath)
                if imgType is None:
                    raise Exception( "Invalid file: " + imagePath + " is not a valid image")
                elif not imagePath.lower().endswith(imgType) and not (
                        imgType == "jpeg" and imagePath.lower().endswith("jpg")):
                        # the image has been saved with the wrong extension
                    raise Exception(
                        "Invalid image extension: " +
                        imagePath +
                        " should be a ." +
                        imgType)
                elif imagePath.lower().endswith('.gif'):
                    photo = PhotoImage(file=imagePath)
                elif imagePath.lower().endswith('.ppm') or imagePath.lower().endswith('.pgm'):
                    photo = PhotoImage(file=imagePath)
                elif imagePath.lower().endswith('jpg') or imagePath.lower().endswith('jpeg'):
                    self.warn("Image processing for .JPGs is slow. .GIF is the recommended format")
                    photo = self.convertJpgToBmp(imagePath)
                elif imagePath.lower().endswith('.png'):
                    # known issue here, some PNGs lack IDAT chunks
                    # also, PNGs seem broken on python<3, maybe around the map
                    # function used to generate pixel maps
                    self._importPngimagetk()
                    if PngImageTk is False:
                        raise Exception(
                            "TKINTERPNG library not found, PNG files not supported: " + imagePath)
                    if sys.version_info >= (2, 7):
                        self.warn("Image processing for .PNGs is slow. .GIF is the recommended format")
                        png = PngImageTk(imagePath)
                        png.convert()
                        photo = png.image
                    else:
                        raise Exception("PNG images only supported in python 3: " + imagePath)
                else:
                    raise Exception("Invalid image type: " + imagePath)
            else:
                raise Exception("Can't read image: " + imagePath)
        else:
            raise Exception("Image " + imagePath + " does not exist")

        # store the full path to this image
        photo.path = imagePath
        # store the modification time
        photo.modTime = os.path.getmtime(imagePath)

        # sort out if it's an animated image
        if self._checkIsAnimated(imagePath):
            self._configAnimatedImage(photo)
            self._preloadAnimatedImage(photo)
        else:
            photo.isAnimated = False
            photo.animating = False
            if addToCache:
                self.widgetManager.update(WIDGET_NAMES.ImageCache, imagePath, photo)

        return photo

    def getImageDimensions(self, name):
        img = self.widgetManager.get(WIDGET_NAMES.Image, name).image
        return img.width(), img.height()

    # force replace the current image, with a new one
    def reloadImage(self, name, imageFile):
        label = self.widgetManager.get(WIDGET_NAMES.Image, name)
        image = self._getImage(imageFile, False)
        self._populateImage(name, image)

    def reloadImageData(self, name, imageData, fmt="gif"):
        self.setImageData(name, imageData, fmt)

    def setImageData(self, name, imageData, fmt="gif"):
        label = self.widgetManager.get(WIDGET_NAMES.Image, name)
        image = self._getImageData(imageData, fmt=fmt)
        self._populateImage(name, image)

    # replace the current image, with a new one
    def getImage(self, name):
        label = self.widgetManager.get(WIDGET_NAMES.Image, name)
        return label.image.path

    def setImage(self, name, imageFile, internal=False):
        label = self.widgetManager.get(WIDGET_NAMES.Image, name)
        imageFile = self.getImagePath(imageFile)

        # only set the image if it's different
        if label.image is not None and label.image.path == imageFile:
            self.warn("Not updating %s, %s hasn't changed." , name, imageFile)
            return
        elif imageFile is None:
            return
        else:
            image = self._getImage(imageFile)
            self._populateImage(name, image, internal)

    # internal function to update the image in a label
    def _populateImage(self, name, image, internal=False):
        label = self.widgetManager.get(WIDGET_NAMES.Image, name)

        if label.image is not None: label.image.animating = False
        label.config(image=image)
        label.config(anchor=CENTER, font=self._getContainerProperty('labelFont'))
        if not self.ttkFlag:
            label.config(background=self._getContainerBg())
        label.image = image  # keep a reference!

        if image.isAnimated:
            anim_id = self.topLevel.after(
                image.anim_speed + 100,
                self._animateImage,
                name,
                True)
            self.widgetManager.update(WIDGET_NAMES.AnimationID, name, anim_id)

        if not internal and label.hasMouseOver:
            leaveImg = label.image.path
            label.bind("<Leave>", lambda e: self.setImage(name, leaveImg, True))

        # removed - keep the label the same size, and crop images
        #h = image.height()
        #w = image.width()
        #label.config(height=h, width=w)
        self.topLevel.update_idletasks()

    # function to configure an image map
    def setImageMap(self, name, func, coords):
        self._setWidgetMap(name, WIDGET_NAMES.Image, func, coords)

    def _setWidgetMap(self, name, _type, func, coords):
        widget = self.widgetManager.get(_type, name)
        rectangles = []
        if len(coords) > 0:
            for k, v in coords.items():
                rect = AjRectangle(k, AjPoint(v[0], v[1]), v[2]-v[0], v[3]-v[1])
                rectangles.append(rect)

        widget.MAP_COORDS = rectangles
        widget.MAP_FUNC = func
        widget.bind("<Button-1>", lambda e: self._widgetMap(_type, name, e), add="+")

    # function called when an image map is clicked
    def _widgetMap(self, _type, name, event):
        widget = self.widgetManager.get(_type, name)
        for rect in widget.MAP_COORDS:
            if rect.contains(AjPoint(event.x, event.y)):
                widget.MAP_FUNC(rect.name)
                return

        widget.MAP_FUNC("UNKNOWN: " + str(event.x) + ", " + str(event.y))

    def addImage(self, name, imageFile, row=None, column=0, colspan=0, rowspan=0, compound=None):
        ''' Adds an image at the specified position '''
        self.widgetManager.verify(WIDGET_NAMES.Image, name)
        imgObj = self._getImage(imageFile)
        self._addImageObj(name, imgObj, row, column, colspan, rowspan, compound=compound)
        self.widgetManager.get(WIDGET_NAMES.Image, name).hasMouseOver = False
        return imgObj

    def addIcon(self, name, iconName, row=None, column=0, colspan=0, rowspan=0, compound=None):
        ''' adds one of the built-in  icons at the specified position '''
        icon = os.path.join(self.icon_path, iconName.lower()+".png")
        with PauseLogger():
            return self.addImage(name, icon, row, column, colspan, rowspan, compound=compound)

    def addImageData(self, name, imageData, row=None, column=0, colspan=0, rowspan=0, fmt="gif", compound=None):
        ''' load image from base-64 encoded GIF
            use base64 module to convert binary data to base64 '''
        self.widgetManager.verify(WIDGET_NAMES.Image, name)
        imgObj = self._getImageData(imageData, fmt)
        self._addImageObj(name, imgObj, row, column, colspan, rowspan, compound=compound)
        self.widgetManager.get(WIDGET_NAMES.Image, name).hasMouseOver = False
        return imgObj

    def _addImageObj(self, name, img, row=None, column=0, colspan=0, rowspan=0, compound=None):
        if not self.ttkFlag:
            label = Label(self.getContainer())
            label.config(background=self._getContainerBg())
        else:
            label = ttk.Label(self.getContainer())

        label.config(anchor=CENTER, font=self._getContainerProperty('labelFont'),image=img)
        label.image = img  # keep a reference!

        if compound is not None:
            label.config(text=name, compound=compound)

        if img is not None and compound is None and not self.ttkFlag:
            h = img.height()
            w = img.width()
            label.config(height=h, width=w)

        self.widgetManager.add(WIDGET_NAMES.Image, name, label)
        self._positionWidget(label, row, column, colspan, rowspan)
        if img is not None and img.isAnimated:
            anim_id = self.topLevel.after(
                img.anim_speed, self._animateImage, name, True)
            self.widgetManager.update(WIDGET_NAMES.AnimationID, name, anim_id)

    def setImageSize(self, name, width, height):
        img = self.widgetManager.get(WIDGET_NAMES.Image, name)
        img.config(height=height, width=width)

#      def rotateImage(self, name, image):
#            img = self.widgetManager.get(WIDGET_NAMES.Image, name)

    # if +ve then grow, else shrink...
    def zoomImage(self, name, x, y=''):
        if x <= 0:
            self.shrinkImage(name, x * -1, y * -1)
        else:
            self.growImage(name, x, y)

    # get every nth pixel (must be an integer)
    # 0 will return an empty image, 1 will return the image, 2 will be 1/2 the
    # size ...
    def shrinkImage(self, name, x, y=''):
        label = self.widgetManager.get(WIDGET_NAMES.Image, name)
        image = label.image.subsample(x, y)

        label.config(image=image)
        label.config(anchor=CENTER, font=self._getContainerProperty('labelFont'))

        if not self.ttkFlag:
            label.config(background=self._getContainerBg())
            label.config(width=image.width(), height=image.height())
        label.modImage = image  # keep a reference!

    # get every nth pixel (must be an integer)
    # 0 won't work, 1 will return the original size
    def growImage(self, name, x, y=''):
        label = self.widgetManager.get(WIDGET_NAMES.Image, name)
        image = label.image.zoom(x, y)

        label.config(image=image)
        label.config(anchor=CENTER, font=self._getContainerProperty('labelFont'))

        if not self.ttkFlag:
            label.config(background=self._getContainerBg())
            label.config(width=image.width(), height=image.height())
        label.modImage = image  # keep a reference!

    def convertJpgToBmp(self, image):
        self._loadNanojpeg()
        if nanojpeg is False:
            raise Exception(
                "nanojpeg library not found, unable to display jpeg files: " + image)
        elif sys.version_info < (2, 7):
            raise Exception(
                "JPG images only supported in python 2.7+: " + image)
        else:
            # read the image into an array of bytes
            with open(image, 'rb') as inFile:
                buf = array.array(str('B'), inFile.read())

            # init the translator, and decode the array of bytes
            nanojpeg.njInit()
            nanojpeg.njDecode(buf, len(buf))

            # determine a file name & type
            if nanojpeg.njIsColor():
#                fileName = image.split('.jpg', 1)[0] + '.ppm'
                param = 6
            else:
#                fileName = image.split('.jpg', 1)[0] + '.pgm'
#                fileName = "test3.pgm"
                param = 5

            # create a string, starting with the header
            val = "P%d\n%d %d\n255\n" % (
                param, nanojpeg.njGetWidth(), nanojpeg.njGetHeight())
            # append the bytes, converted to chars
            val = str(val) + str('').join(map(chr, nanojpeg.njGetImage()))

            # release any stuff
            nanojpeg.njDone()

            photo = PhotoImage(data=val)
            return photo

            # write the chars to a new file, if python3 we need to encode them first
#            with open(fileName, "wb") as outFile:
#                  if sys.version_info[0] == 2: outFile.write(val)
#                  else: outFile.write(val.encode('ISO-8859-1'))
#
#            return fileName

    # function to set a background image
    # make sure this is done before everything else, otherwise it will cover
    # other widgets
    def setBgImage(self, image):
        image = self._getImage(image, False, False)  # make sure it's not using the cache
        # self.containerStack[0]['container'].config(image=image) # window as a
        # label doesn't work...
        self.bgLabel.config(image=image)
        self.containerStack[0]['container'].image = image  # keep a reference!

    def removeBgImage(self):
        self.bgLabel.config(image="")
        # self.containerStack[0]['container'].config(image=None) # window as a
        # label doesn't work...
        # remove the reference - shouldn't be cached
        self.containerStack[0]['container'].image = None

    def resizeBgImage(self):
        if self.containerStack[0]['container'].image is None:
            return
        else:
            pass

#####################################
# FUNCTION to play sounds
#####################################

    # function to set a sound location
    def setSoundLocation(self, location):
        if os.path.isdir(location):
            self.userSounds = location
        else:
            raise Exception("Invalid sound location: " + location)

    # internal function to manage sound availability
    def _soundWrap(self, sound, isFile=False, repeat=False, wait=False):
        self._loadWinsound()
        if self.platform == self.WINDOWS and winsound is not False:
            sound = self._translateSound(sound)
            if self.userSounds is not None and sound is not None:
                sound = os.path.join(self.userSounds, sound)
            if isFile:
                if os.path.isfile(sound) is False:
                    raise Exception("Can't find sound: " + sound)
                if not sound.lower().endswith('.wav'):
                    raise Exception("Invalid sound format: " + sound)
                kind = winsound.SND_FILENAME
                if not wait:
                    kind = kind | winsound.SND_ASYNC
            else:
                if sound is None:
                    kind = winsound.SND_FILENAME
                else:
                    kind = winsound.SND_ALIAS
                    if not wait:
                        kind = kind | winsound.SND_ASYNC

            if repeat:
                kind = kind | winsound.SND_LOOP

            winsound.PlaySound(sound, kind)
        else:
            # sound not available at this time
            raise Exception(
                "Sound not supported on this platform: " +
                platform())

    def playSound(self, sound, wait=False):
        self._soundWrap(sound, True, False, wait)

    def stopSound(self):
        self._soundWrap(None)

    def loopSound(self, sound):
        self._soundWrap(sound, True, True)

    def soundError(self):
        self._soundWrap("SystemHand")

    def soundWarning(self):
        self._soundWrap("SystemAsterisk")

    def bell(self):
        self.containerStack[0]['container'].bell()

    def playNote(self, note, duration=200):
        self._loadWinsound()
        if self.platform == self.WINDOWS and winsound is not False:
            try:
                if isinstance(note, UNIVERSAL_STRING):
                    freq = self.NOTES[note.lower()]
                else:
                    freq = note
            except KeyError:
                raise Exception("Error: cannot play note - " + note)
            try:
                if isinstance(duration, UNIVERSAL_STRING):
                    length = self.DURATIONS[duration.upper()]
                else:
                    length = duration
            except KeyError:
                raise Exception("Error: cannot play duration - " + duration)

            try:
                winsound.Beep(freq, length)
            except RuntimeError:
                raise Exception(
                    "Sound not available on this platform: " +
                    platform())
        else:
            # sound not available at this time
            raise Exception(
                "Sound not supported on this platform: " +
                platform())

#####################################
# FUNCTION for radio buttons
#####################################

    def radio(self, title, name=None, *args, **kwargs):
        """ simpleGUI - shortner for radioButton() """
        return self.radioButton(title, name, *args, **kwargs)

    def radioButton(self, title, name=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets radioButtons all in one go """
        widgKind = WIDGET_NAMES.RadioButton
        selected = kwargs.pop("selected", False)
        callFunction = kwargs.pop("callFunction", True)
        change = kwargs.pop("change", None)
        kind = kwargs.pop('kind', 'standard')

        # need slightly different approach, as use two params
        if name is None: return self.getRadioButton(title) # no name = get
        else:
            ident = title + "-" + name
            try: self.widgetManager.verify(widgKind, ident)
            except:
                self.setRadioButton(title, name, callFunction=callFunction)
                rb = self.getRadioButton(title)
                selected = False
            else:
                kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
                rb = self._radioButtonMaker(title, name, *args, **kwargs)

            if selected: self.setRadioButton(title, name)
            if change is not None: self.setRadioButtonChangeFunction(title, change)
            if kind == "square":
                if self.platform == self.MAC:
                    gui.warn("Square radiobuttons not available on Mac, for radiobutton %s", title)
                elif not self.ttkFlag:
                    rb.config(indicatoron=0)
                else:
                    gui.warn("Square radiobuttons not available in ttk, for radiobutton %s", title)

            if len(kwargs) > 0:
                self._configWidget(ident, widgKind, **kwargs)

            return rb

    def _radioButtonMaker(self, title, name, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        return self.addRadioButton(title, name, row, column, colspan, rowspan)

    def addRadioButton(self, title, name, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a radio button, to thr group 'title' with the text 'name' '''
        ident = title + "-" + name
        self.widgetManager.verify(WIDGET_NAMES.RadioButton, ident)

        var = None
        newRb = False
        # title - is the grouper
        # so, if we already have an entry in n_rbVars - get it
        if (title in self.widgetManager.group(WIDGET_NAMES.RadioButton, group=WidgetManager.VARS)):
            var = self.widgetManager.get(WIDGET_NAMES.RadioButton, title, group=WidgetManager.VARS)
        else:
            # if this is a new grouper - set it all up
            var = StringVar(self.topLevel)
            self.widgetManager.add(WIDGET_NAMES.RadioButton, title, var, group=WidgetManager.VARS)
            newRb = True

        # finally, create the actual RadioButton
        if not self.ttkFlag:
            rb = Radiobutton(self.getContainer(), text=name, variable=var, value=name)
            rb.config(anchor=W, background=self._getContainerBg(), indicatoron=1,
                activebackground=self._getContainerBg(), font=self._getContainerProperty('labelFont')
            )
        else:
            rb = ttk.Radiobutton(self.getContainer(), text=name, variable=var, value=name)

        rb.bind("<Button-1>", self._grabFocus)
        rb.DEFAULT_TEXT = name

        self.widgetManager.add(WIDGET_NAMES.RadioButton, ident, rb)
        #rb.bind("<Tab>", self._focusNextWindow)
        #rb.bind("<Shift-Tab>", self._focusLastWindow)

        # and select it, if it's the first item in the list
        if newRb:
            rb.select() if not self.ttkFlag else rb.invoke()
            var.startVal = name # so we can reset it...
        self._positionWidget(rb, row, column, colspan, rowspan, EW)
        return rb

    def getRadioButton(self, title):
        var = self.widgetManager.get(WIDGET_NAMES.RadioButton, title, group=WidgetManager.VARS)
        return var.get()

    def getAllRadioButtons(self):
        rbs = {}
        for k in self.widgetManager.group(WIDGET_NAMES.RadioButton, group=WidgetManager.VARS):
            rbs[k] = self.getRadioButton(k)
        return rbs

    def setRadioButton(self, title, value, callFunction=True):
        ident = title + "-" + value
        self.widgetManager.get(WIDGET_NAMES.RadioButton, ident)

        # now call function
        var = self.widgetManager.get(WIDGET_NAMES.RadioButton, title, group=WidgetManager.VARS)
        with PauseCallFunction(callFunction, var, False):
            var.set(value)

    def clearAllRadioButtons(self, callFunction=False):
        for rb in self.widgetManager.group(WIDGET_NAMES.RadioButton, group=WidgetManager.VARS):
            self.setRadioButton(rb, self.widgetManager.get(WIDGET_NAMES.RadioButton, rb, group=WidgetManager.VARS).startVal, callFunction=callFunction)

    def setRadioTick(self, title, tick=True):
        self.warn("Deprecated function (%s) used for %s -> %s use %s instead", 'setRadioTick', 'radioButton', title, 'setRadioSquare')
        self.setRadioSquare(title, square=tick)

    def setRadioSquare(self, title, square=True):
        if self.platform == self.MAC:
            gui.warn("Square radiobuttons not available on Mac, for radiobutton %s", title)
        elif not self.ttkFlag:
            for k, v in self.widgetManager.group(WIDGET_NAMES.RadioButton).items():
                if k.startswith(title+"-"):
                    if square:
                        v.config(indicatoron=1)
                    else:
                        v.config(indicatoron=0)
        else:
            gui.warn("Square radiobuttons not available in ttk mode, for radiobutton %s", title)

#####################################
# FUNCTION for list box
#####################################

    def listbox(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for listBox() """
        return self.listBox(title, value, *args, **kwargs)

    def listBox(self, title, value=None, *args, **kwargs):
        """ simpleGUI -- adds, sets & gets listBoxes all in one go """
        widgKind = WIDGET_NAMES.ListBox

        rows = kwargs.pop("rows", None)
        multi = kwargs.pop("multi", False)
        group = kwargs.pop("group", False)
        selected = kwargs.pop("selected", None)
        first = kwargs.pop("first", False)
        callFunction = kwargs.pop("callFunction", True)

        # select=select, deselect=??, toggle=??, clear=??, rename=set, replace=update, delete=remove
        if value is None: mode = 'get'
        else: mode = 'select'
        mode = kwargs.pop("mode", mode)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            if mode == "select":
                if value is not None:
                    if isinstance(value, int):
                        self.selectListItemAtPos(title, value, *args, **kwargs)
                    else:
                        self.selectListItem(title, value, *args, **kwargs)
                else: gui.error("No item specified to select in listbox: %s", title)
            elif mode == "deselect":
                if value is not None:
                    if isinstance(value, int):
                        self.deselectListItemAtPos(title, value, *args, **kwargs)
                    else:
                        self.deselectListItem(title, value, *args, **kwargs)
                else: gui.error("No item specified to deselect in listbox: %s", title)
            elif mode == "toggle":
                gui.error("%s not implemented yet in listbox: %s", mode, title)
            elif mode == "clear":
                self.deselectAllListItems(title)
            elif mode == "rename":
                gui.error("%s not implemented yet in listbox: %s", mode, title)
            elif mode == "replace":
                if value is not None: self.updateListBox(title, items=value, callFunction=callFunction)
                else: gui.error("No values specified to replace in listbox: %s", title)
            elif mode == "delete":
                if value is not None:
                    if isinstance(value, int):
                        self.removeListItemAtPos(title, value)
                    else:
                        self.removeListItem(title, value)
                else: gui.error("No value specified to delete in listbox: %s", title)
            elif mode == "add":
                if value is not None:
                    select = True if selected is None else selected
                    if type(value) in (list, tuple):
                        self.addListItems(title, items=value, select=select)
                    else:
                        self.addListItem(title, item=value, select=select)
                else: gui.error("No value specified to add in listbox: %s", title)
            elif mode == "get":
                pass
            else:
                gui.error("Invalid mode (%s) specified in listbox: %s", mode, title)

            listBox = self.getListBox(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            listBox = self._listBoxMaker(title, value, *args, **kwargs)

        if rows is not None: self.setListBoxRows(title, rows)
        if multi: self.setListBoxMulti(title)
        if group: self.setListBoxGroup(title)
        if selected is not None: self.selectListItemAtPos(title, selected, callFunction=False)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return listBox

    def _listBoxMaker(self, name, values=None, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        """ internal wrapper to hide kwargs from original add functions """
        return self.addListBox(name, values, row, column, colspan, rowspan)

    def addListBox(self, name, values=None, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a list box, with the the specified list of values '''
        self.widgetManager.verify(WIDGET_NAMES.ListBox, name)
        container = self.makeListBoxContainer()(self.getContainer())
        vscrollbar = AutoScrollbar(container)
        hscrollbar = AutoScrollbar(container, orient=HORIZONTAL)

        container.lb = Listbox(container,
            yscrollcommand=vscrollbar.set,
            xscrollcommand=hscrollbar.set)

        vscrollbar.grid(row=0, column=1, sticky=N + S)
        hscrollbar.grid(row=1, column=0, sticky=E + W)

        container.lb.grid(row=0, column=0, sticky=N + S + E + W)

        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        vscrollbar.config(command=container.lb.yview)
        hscrollbar.config(command=container.lb.xview)

        container.lb.config(font=self._getContainerProperty('inputFont'))
        self.widgetManager.add(WIDGET_NAMES.ListBox, name, container.lb)

        container.lb.DEFAULT_TEXT=""
        if values is not None:
            container.lb.DEFAULT_TEXT='\n'.join(str(x) for x in values)
            for name in values:
                container.lb.insert(END, name)

        self._positionWidget(container, row, column, colspan, rowspan)
        return container.lb

    # enable multiple listboxes to be selected at the same time
    def setListBoxGroup(self, name, group=True):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, name)
        group = not group
        lb.config(exportselection=group)

    # set how many rows to display
    def setListBoxRows(self, name, rows):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, name)
        lb.config(height=rows)

    # make the list single/multi select
    # default is single
    def setListBoxMulti(self, title, multi=True):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        if multi:
            lb.config(selectmode=EXTENDED)
        else:
            lb.config(selectmode=BROWSE)

    # select the specified item in the list
    def selectListItem(self, title, item, callFunction=True):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        positions = self._getListPositions(title, item)
        if len(positions) > 1 and lb.cget("selectmode") == EXTENDED:
            allOk = True
            for pos in positions:
                if not self.selectListItemAtPos(title, pos, callFunction):
                    allOk = False
            return allOk
        elif len(positions) > 1:
            gui.warn("Unable to select multiple items for list: %s. Selecting first item: %s", title, item[0])
            return self.selectListItemAtPos(title, positions[0], callFunction)
        elif len(positions) == 1:
            return self.selectListItemAtPos(title, positions[0], callFunction)
        else:
            gui.warn("Invalid list item(s): %s for list: %s", item, title)
            return False

    def deselectListItemAtPos(self, title, pos, callFunction=False):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        if lb.size() == 0:
            gui.warn("No items in list: %s, unable to deselect item at pos: %s", title, pos)
            return False
        if pos < 0 or pos > lb.size() - 1:
            gui.warn("Invalid list position: %s for list: %s (max: %s)", pos, title, lb.size()-1)
            return False

        lb.selection_clear(pos)
        if callFunction and hasattr(lb, 'cmd'):
            lb.cmd()
        self.topLevel.update_idletasks()
        return True

    def selectListItemAtPos(self, title, pos, callFunction=False):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        if lb.size() == 0:
            gui.warn("No items in list: %s, unable to select item at pos: %s", title, pos)
            return False
        if pos < 0 or pos > lb.size() - 1:
            gui.warn("Invalid list position: %s for list: %s (max: %s)", pos, title, lb.size()-1)
            return False

        # clear previous selection if we're not multi
        if lb.cget("selectmode") != EXTENDED:
            lb.selection_clear(0, END)

        # show & select this item
        lb.see(pos)
        lb.activate(pos)
        lb.selection_set(pos)
        # now call function
        if callFunction and hasattr(lb, 'cmd'):
            lb.cmd()
        self.topLevel.update_idletasks()
        return True

    # replace the list items in the list box
    def updateListBox(self, title, items, select=False, callFunction=True):
        self.clearListBox(title, callFunction=callFunction)
        self.addListItems(title, items, select=select)

    def addListItems(self, title, items, select=True):
        ''' adds the list of items to the specified list box '''
        for i in items:
            self.addListItem(title, i, select=select)

    def addListItem(self, title, item, pos=None, select=True):
        ''' add the item to the end of the specified list box '''
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        # add it at the end
        if pos is None: pos = END
        lb.insert(pos, item)

        # show & select the newly added item
        if select:
            # clear any selection
            items = lb.curselection()
            if len(items) > 0:
                lb.selection_clear(items)

            self.selectListItemAtPos(title, lb.size() - 1)

    def deselectAllListItems(self, title, callFunction=False):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        lb.selection_clear(0, END)

        if callFunction and hasattr(lb, 'cmd'):
            lb.cmd()

    # returns a list containing 0 or more elements
    # all that are in the selected range
    def getListBox(self, title):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        items = lb.curselection()
        values = []
        for loop in range(len(items)):
            values.append(lb.get(items[loop]))
        return values

    def getAllListBoxes(self):
        boxes = {}
        for k in self.widgetManager.group(WIDGET_NAMES.ListBox):
            boxes[k] = self.getListBox(k)
        return boxes

    def getAllListItems(self, title):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        items = lb.get(0, END)
        return list(items)

    def getListBoxPos(self, title):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        # bug in tkinter 1.160 returns these as strings
        items = [int(i) for i in lb.curselection()]

        return items

    def removeListItemAtPos(self, title, pos):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        items = lb.get(0, END)
        if pos >= len(items):
            raise Exception("Invalid position: " + str(pos) + " must be between 0 and " + str(len(items)-1))
        lb.delete(pos)

        # show & select this item
        if pos >= lb.size():
            pos -= 1
        self.selectListItemAtPos(title, pos)

    # remove a specific item from the listBox
    # will only remove the first item that matches the String
    def removeListItem(self, title, item):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        positions = self._getListPositions(title, item)
        if len(positions) > 0:
            lb.delete(positions[0])

        # show & select this item
        if positions[0] >= lb.size():
            positions[0] -= 1
        self.selectListItemAtPos(title, positions[0])

    def setListItemAtPos(self, title, pos, newVal):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        lb.delete(pos)
        lb.insert(pos, newVal)

    def setListItem(self, title, item, newVal, first=False):
        for pos in self._getListPositions(title, item):
            self.setListItemAtPos(title, pos, newVal)
            if first:
                break

    # functions to config
    def setListItemAtPosBg(self, title, pos, col):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        lb.itemconfig(pos, bg=col)

    def setListItemAtPosFg(self, title, pos, col):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        lb.itemconfig(pos, fg=col)

    def _getListPositions(self, title, item):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        if not isinstance(item, list):
            item = [item]
        vals = lb.get(0, END)
        positions = []
        for pos, val in enumerate(vals):
            if val in item:
                positions.append(pos)
        return positions

    def setListItemBg(self, title, item, col):
        for pos in self._getListPositions(title, item):
            self.setListItemAtPosBg(title, pos, col)

    def setListItemFg(self, title, item, col):
        for pos in self._getListPositions(title, item):
            self.setListItemAtPosFg(title, pos, col)

    def clearListBox(self, title, callFunction=True):
        lb = self.widgetManager.get(WIDGET_NAMES.ListBox, title)
        lb.selection_clear(0, END)
        lb.delete(0, END)  # clear
        if callFunction and hasattr(lb, 'cmd'):
            lb.cmd()

    def clearAllListBoxes(self, callFunction=False):
        for lb in self.widgetManager.group(WIDGET_NAMES.ListBox):
            self.clearListBox(lb, callFunction)

#####################################
# FUNCTION for buttons
#####################################

    def button(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets buttons all in one go """
        widgKind = WIDGET_NAMES.Button
        image = kwargs.pop("image", None)
        icon = kwargs.pop("icon", None)
        name = kwargs.pop("label", kwargs.pop("name", None))

        try: self.widgetManager.verify(WIDGET_NAMES.Button, title)
        except: # widget exists
            if value is not None: self.setButton(title, value)
            button = self.getButton(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if image is not None: button = self._buttonMaker(title, value, "image", image, *args, **kwargs)
            elif icon is not None: button = self._buttonMaker(title, value, "icon", icon, *args, **kwargs)
            elif name is not None: button = self._buttonMaker(title, value, "named", name, *args, **kwargs)
            else: button = self._buttonMaker(title, value, "button", None, *args, **kwargs)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return button

    def _buttonMaker(self, title, func, kind, extra=None, row=None, column=0, colspan=0, rowspan=0, *args, **kwargs):
        """ internal wrapper to hide kwargs from original add functions """
        align = kwargs.pop("align", None)
        if kind == "button": return self.addButton(title, func, row, column, colspan, rowspan)
        elif kind == "named": return self.addNamedButton(extra, title, func, row, column, colspan, rowspan)
        elif kind == "image": return self.addImageButton(title, func, extra, row, column, colspan, rowspan, align=align)
        elif kind == "icon": return self.addIconButton(title, func, extra, row, column, colspan, rowspan, align=align)

    def _configWidget(self, title, kind, **kwargs):
        widget = self.widgetManager.get(kind, title)
        # remove any unwanted keys
        for key in ["row", "column", "colspan", "rowspan", "label", "name"]:
            kwargs.pop(key, None)

        # ignore these for now as well
        for key in ["pad", "inpad"]:
            val = kwargs.pop(key, None)
            if val is not None:
                gui.error("Invalid argument for %s %s - %s:%s", WIDGET_NAMES.name(kind), title, key, val)

        tooltip = kwargs.pop("tip", kwargs.pop("tooltip", None))
        change = kwargs.pop("change", None)
        submit = kwargs.pop("submit", None)
        over = kwargs.pop("over", None)
        drag = kwargs.pop("drag", None)
        drop = kwargs.pop("drop", None)
        right = kwargs.pop("right", None)
        focus = kwargs.pop('focus', False)
        _font = kwargs.pop('font', None)

        if tooltip is not None: self._addTooltip(widget, tooltip, None)
        if focus: widget.focus_set()

        if change is not None: self._bindEvent(kind, title, widget, change, "change", key=None)
        if submit is not None: self._bindEvent(kind, title, widget, submit, "submit", key=None)
        if over is not None: self._bindOverEvent(kind, title, widget, over, None, None)
        if drag is not None: self._bindDragEvent(kind, title, widget, drag, None, None)
        if drop is not None: self._registerExternalDropTarget(title, widget, drop)
        if right is not None: self._bindRightClick(widget, right)

        # allow fonts to be passed in as either a dictionary or a single integer or a font object
        if _font is not None:
            if isinstance(_font, tkFont.Font):
                widget.config(font=_font)
            else:
                if not isinstance(_font, dict): # assume int
                    _font = {"size":_font}
                custFont = tkFont.Font(**_font)
                widget.config(font=custFont)

        # now pass the kwargs to the config function, ignore any baddies
        errorMsg = ""
        while True:
            try: widget.config(**kwargs)
            except TclError as e:
                try:
                    key=str(e).split()[2][2:-1]
                    errorMsg = "".join([errorMsg, key, ":", kwargs.pop(key), ", "])
                except:
                    gui.error("Invalid argument for %s %s: %s", WIDGET_NAMES.name(kind), title, e)
                    break
            else:
                break
        if len(errorMsg) > 0:
            gui.error("Invalid arguments for %s %s - %s", WIDGET_NAMES.name(kind), title, errorMsg)


    def _buildButton(self, title, func, frame, name=None):
        if name is None:
            name = title
        if isinstance(title, list):
            raise Exception("Can't add a button using a list of names: " + str(title) + " - you should use .addButtons()")
        self.widgetManager.verify(WIDGET_NAMES.Button, title)
        if not self.ttkFlag:
            but = Button(frame, text=name)
            but.config(font=self._getContainerProperty('buttonFont'))
            if self.platform in [self.MAC, self.LINUX]:
                but.config(highlightbackground=self._getContainerBg())
        else:
            but = ttk.Button(frame, text=name)

        but.DEFAULT_TEXT = name

        if func is not None:
            command = self.MAKE_FUNC(func, title)
            but.config(command=command)

        #but.bind("<Tab>", self._focusNextWindow)
        #but.bind("<Shift-Tab>", self._focusLastWindow)
        self.widgetManager.add(WIDGET_NAMES.Button, title, but)

        return but

    def addNamedButton(self, name, title, func, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a button, displaying the name as its text '''
        but = self._buildButton(title, func, self.getContainer(), name)
        self._positionWidget(but, row, column, colspan, rowspan, None)
        return but

    def addButton(self, title, func, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a button with the title as its text '''
        but = self._buildButton(title, func, self.getContainer())
        self._positionWidget(but, row, column, colspan, rowspan, None)
        return but

    def addImageButton(self, title, func, imgFile, row=None, column=0, colspan=0, rowspan=0, align=None):
        ''' adds a button, displaying the specified image file '''
        but = self._buildButton(title, func, self.getContainer())
        self._positionWidget(but, row, column, colspan, rowspan, None)
        self.setButtonImage(title, imgFile, align)
        return but

    def addIconButton(self, title, func, iconName, row=None, column=0, colspan=0, rowspan=0, align=None):
        ''' adds a button displaying the specified icon '''
        icon = os.path.join(self.icon_path, iconName.lower()+".png")
        with PauseLogger():
            return self.addImageButton(title, func, icon, row, column, colspan, rowspan, align)

    def setButton(self, name, text):
        but = self.widgetManager.get(WIDGET_NAMES.Button, name)
        try: # try to bind a function
            command = self.MAKE_FUNC(text, name)
            but.config(command=command)
        except: # otherwise change the text
            but.config(text=text)

    def getButton(self, name):
        but = self.widgetManager.get(WIDGET_NAMES.Button, name)
        return but.cget("text")

    def setButtonImage(self, name, imgFile, align=None):
        but = self.widgetManager.get(WIDGET_NAMES.Button, name)
        image = self._getImage(imgFile)
        # works on Mac & Windows :)
        if align == None:
            but.config(image=image, text="")
            if not self.ttk:
                but.config(justify=LEFT, compound=TOP)
            else:
                but.config(compound=CENTER)
        else:
            but.config(image=image, compound=align)
        # but.config(image=image, compound=None, text="") # works on Windows, not Mac

        but.image = image

    # adds a set of buttons, in the row, spannning specified columns
    # pass in a list of names & a list of functions (or a single function to
    # use for all)
    def buttons(self, names, funcs, **kwargs):
        kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
        self._addButtons(names, funcs, **kwargs)
        kwargs.pop('fill', False)
        if not isinstance(names[0], list):
            names = [names]
        for row in names:
            for title in row:
                self._configWidget(title, WIDGET_NAMES.Button, **kwargs)

    def _addButtons(self, names, funcs, row=None, column=0, colspan=0, rowspan=0, fill=False, **kwargs):
        self.addButtons(names, funcs, row, column, colspan, rowspan, fill)

    def addButtons(self, names, funcs, row=None, column=0, colspan=0, rowspan=0, fill=False):
        ''' adds a 1D/2D list of buttons '''
        if not isinstance(names, list):
            raise Exception(
                "Invalid button: " +
                names +
                ". It must be a list of buttons.")

        singleFunc = self._checkFunc(names, funcs)

        frame = self._makeWidgetBox()(self.getContainer())
        if not self.ttk:
            frame.config(background=self._getContainerBg())

        # make them into a 2D array, if not already
        if not isinstance(names[0], list):
            names = [names]
            # won't be used if single func
            if funcs is not None:
                funcs = [funcs]

        sticky = None
        if fill: sticky=E+W

        for bRow in range(len(names)):
            for i in range(len(names[bRow])):
                t = names[bRow][i]
                if funcs is None:
                    tempFunc = None
                elif singleFunc is None:
                    tempFunc = funcs[bRow][i]
                else:
                    tempFunc = singleFunc
                but = self._buildButton(t, tempFunc, frame)

                but.grid(row=bRow, column=i, sticky=sticky)
                Grid.columnconfigure(frame, i, weight=1)
                Grid.rowconfigure(frame, bRow, weight=1)
                frame.theWidgets.append(but)

        self._positionWidget(frame, row, column, colspan, rowspan)
        self.widgetManager.log(WIDGET_NAMES.FrameBox, frame)

#####################################
# FUNCTIONS for links
#####################################

    def link(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets links all in one go """
        widgKind = WIDGET_NAMES.Link

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            if value is not None: self.setLink(title, value)
            link = self.getLink(title)
        else: # new widget
            if value is None:
                gui.warn("Can't create link: %s, with no value", title)
                return None
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            link = self._linkMaker(title, value, *args, **kwargs)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return link

    def _linkMaker(self, title, value, row=None, column=0, colspan=0, rowspan=0, *args, **kwargs):
        if not callable(value) and not hasattr(value, '__call__'):
            return self.addWebLink(title, value, row, column, colspan, rowspan)
        else:
            return self.addLink(title, value, row, column, colspan, rowspan)

    def _buildLink(self, title):
        self._importWebBrowser()
        if not webbrowser:
            self.error("Unable to load webbrowser - can't create links")
        link = self._makeLink()(self.getContainer(), useTtk=self.ttkFlag)
        link.config(text=title, font=self._linkFont)
        if not self.ttk:
            link.config(background=self._getContainerBg())
        self.widgetManager.add(WIDGET_NAMES.Link, title, link)
        return link

    # launches a browser to the specified page
    def addWebLink(self, title, page, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a hyperlink to the specified web page '''
        link = self._buildLink(title)
        link.registerWebpage(page)
        self._positionWidget(link, row, column, colspan, rowspan)
        return link

    # executes the specified function
    def addLink(self, title, func, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a hyperlink to the specified function '''
        link = self._buildLink(title)
        if func is not None:
            myF = self.MAKE_FUNC(func, title)
            link.registerCallback(myF)
        self._positionWidget(link, row, column, colspan, rowspan)
        return link

    def getLink(self, title):
        link = self.widgetManager.get(WIDGET_NAMES.Link, title)
        return link.cget("text")

    def setLink(self, title, func):
        link = self.widgetManager.get(WIDGET_NAMES.Link, title)
        if not callable(func) and not hasattr(func, '__call__'):
            link.registerWebpage(func)
        else:
            myF = self.MAKE_FUNC(func, title)
            link.registerCallback(myF)

#####################################
# FUNCTIONS for grips
#####################################

    def grip(self, *args, **kwargs):
        """ simpleGUI - adds grip """
        kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
        return self.addGrip(*args, **kwargs)

    # adds a simple grip, used to drag the window around
    def addGrip(self, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a grip, for dragging the GUI around '''
        grip = self._makeGrip()(self.getContainer())
        self._positionWidget(grip, row, column, colspan, rowspan)
        self._addTooltip(grip, "Drag here to move", True)
        return grip


#####################################
# FUNCTIONS for dnd
#####################################

    def addTrashBin(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' NOT IN USE - adds a trashbin, for discarding dragged items '''
        trash = TrashBin(self.getContainer())
        self._positionWidget(trash, row, column, colspan, rowspan)
        return trash

#####################################
# FUNCTIONS for turtle
#####################################

    def addTurtle(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a turtle widget at the specified position '''
        self._loadTurtle()
        if turtle is False:
            raise Exception("Unable to load turtle")
        self.widgetManager.verify(WIDGET_NAMES.Turtle, title)
        canvas = Canvas(self.getContainer())
        canvas.screen = turtle.TurtleScreen(canvas)
        self._positionWidget(canvas, row, column, colspan, rowspan)
        self.widgetManager.add(WIDGET_NAMES.Turtle, title, canvas)
        canvas.turtle = turtle.RawTurtle(canvas.screen)
        return canvas.turtle

    def getTurtleScreen(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Turtle, title).screen

    def getTurtle(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Turtle, title).turtle

#####################################
# FUNCTIONS for canvas
#####################################

    def addCanvas(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a canvas at the specified position '''
        self.widgetManager.verify(WIDGET_NAMES.Canvas, title)
        canvas = Canvas(self.getContainer())
        canvas.config(bd=0, highlightthickness=0)
        canvas.imageStore = []
        self._positionWidget(canvas, row, column, colspan, rowspan, "news")
        self.widgetManager.add(WIDGET_NAMES.Canvas, title, canvas)
        return canvas

    def getCanvas(self, title):
        return self.widgetManager.get(WIDGET_NAMES.Canvas, title)

    def clearCanvas(self, title):
        self.widgetManager.get(WIDGET_NAMES.Canvas, title).delete("all")

    # function to configure a canvas map
    def setCanvasMap(self, name, func, coords):
        self._setWidgetMap(name, WIDGET_NAMES.Canvas, func, coords)

    def addCanvasCircle(self, title, x, y, diameter, **kwargs):
        ''' adds a circle to the specified canvas '''
        return self.addCanvasOval(title, x, y, diameter, diameter, **kwargs)

    def addCanvasOval(self, title, x, y, xDiam, yDiam, **kwargs):
        ''' adds a oval to the specified canvas '''
        return self.widgetManager.get(WIDGET_NAMES.Canvas, title).create_oval(x, y, x+xDiam, y+yDiam, **kwargs)

    def addCanvasLine(self, title, x, y, x2, y2, **kwargs):
        ''' adds a line to the specified canvas '''
        return self.widgetManager.get(WIDGET_NAMES.Canvas, title).create_line(x, y, x2, y2, **kwargs)

    def addCanvasRectangle(self, title, x, y, w, h, **kwargs):
        ''' adds a rectangle to the specified canvas '''
        return self.widgetManager.get(WIDGET_NAMES.Canvas, title).create_rectangle(x, y, x+w, y+h, **kwargs)

    def addCanvasText(self, title, x, y, text=None, **kwargs):
        ''' adds text to the specified canvas '''
        return self.widgetManager.get(WIDGET_NAMES.Canvas, title).create_text(x, y, text=text, **kwargs)

    def addCanvasImage(self, title, x, y, image=image, **kwargs):
        ''' adds an image to the specified canvas '''
        canv = self.widgetManager.get(WIDGET_NAMES.Canvas, title)
        if isinstance(image, UNIVERSAL_STRING):
            image = self._getImage(image)
        canv.imageStore.append(image)
        return self.widgetManager.get(WIDGET_NAMES.Canvas, title).create_image(x, y, image=image, **kwargs)

    def setCanvasEvent(self, title, item, event, function, add=None):
        canvas = self.widgetManager.get(WIDGET_NAMES.Canvas, title)
        canvas.tag_bind(item, event, function, add)

    def _canvasMaker(self, title, row=None, column=0, colspan=0, rowspan=0, **kwargs):
        return self.addCanvas(title, row, column, rowspan)

    def canvas(self, title, *args, **kwargs):
        """ simpleGUI - adds, sets & gets canases all in one go """
        widgKind = WIDGET_NAMES.Canvas
        submit = kwargs.pop("submit", None)
        _map = kwargs.pop("map", None)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            # NB. no SETTER
            canvas = self.getCanvas(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            canvas = self._canvasMaker(title, *args, **kwargs)

        if submit is not None and _map is not None:
            self.setCanvasMap(title, submit, _map)
        else:
            gui.warn("Must specify a submit function when setting a canvas map: %s", title)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        self._configWidget(title, widgKind, **kwargs)
        return canvas

#####################################
# FUNCTIONS for Microbits
#####################################

    def microbit(self, title, *args, **kwargs):
        '''simpleGUI - adds, sets & gets microbits all in one go'''
        widgKind = WIDGET_NAMES.MicroBit
        image = kwargs.pop("image", None)
        brightness = kwargs.pop("brightness", None)
        x = kwargs.pop("x", None)
        y = kwargs.pop("y", None)
        clear = kwargs.pop("clear", False)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            mb = self.getMicroBit(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            mb = self.addMicroBit(title, *args, **kwargs)

        if image is not None: self.setMicroBitImage(title, image)
        if brightness is not None: self.setMicroBitPixel(title, x, y, brightness)
        if clear: self.clearMicroBit(title)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return mb

    def addMicroBit(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a simple microbit widget
             used with permission from Ben Goodwin '''
        self.widgetManager.verify(WIDGET_NAMES.MicroBit, title)
        mb = MicroBitSimulator(self.getContainer())
        self._positionWidget(mb, row, column, colspan, rowspan)
        self.widgetManager.add(WIDGET_NAMES.MicroBit, title, mb)
        return mb

    def setMicroBitImage(self, title, image):
        self.widgetManager.get(WIDGET_NAMES.MicroBit, title).show(image)

    def setMicroBitPixel(self, title, x, y, brightness):
        self.widgetManager.get(WIDGET_NAMES.MicroBit, title).set_pixel(x, y, brightness)

    def clearMicroBit(self, title):
        self.widgetManager.get(WIDGET_NAMES.MicroBit, title).clear()

#####################################
# DatePicker Widget - using Form Container
#####################################

    def date(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for datePicker() """
        return self.datePicker(title, value, *args, **kwargs)

    def datePicker(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets datePickers all in one go """
        widgKind = WIDGET_NAMES.DatePicker
        change = kwargs.pop("change", None)
        toValue = kwargs.pop("toValue", None)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            dp = self.getDatePicker(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            dp = self.addDatePicker(title, *args, **kwargs)

        if value is not None:
            if toValue is None: self.setDatePicker(title, value)
            else: self.setDatePickerRange(title, startYear=value, endYear=toValue)
        if change is not None: self.setDatePickerChangeFunction(title, change)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return dp

    def addDatePicker(self, name, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a date picker at the specified position '''
        self.widgetManager.verify(WIDGET_NAMES.DatePicker, name)
        # initial DatePicker has these dates
        days = range(1, 32)
        self.MONTH_NAMES = calendar.month_name[1:]
        years = range(1970, 2021)

        # create a frame, and add the widgets
        frame = self.startFrame(name, row, column, colspan, rowspan)
        self.setExpand("none")
        self.addLabel(name + "_DP_DayLabel", "Day:", 0, 0)
        self.setLabelAlign(name + "_DP_DayLabel", "w")
        self.addOptionBox(name + "_DP_DayOptionBox", days, 0, 1)
        self.addLabel(name + "_DP_MonthLabel", "Month:", 1, 0)
        self.setLabelAlign(name + "_DP_MonthLabel", "w")
        self.addOptionBox(name + "_DP_MonthOptionBox", self.MONTH_NAMES, 1, 1)
        self.addLabel(name + "_DP_YearLabel", "Year:", 2, 0)
        self.setLabelAlign(name + "_DP_YearLabel", "w")
        self.addOptionBox(name + "_DP_YearOptionBox", years, 2, 1)
        self.setOptionBoxChangeFunction(
            name + "_DP_MonthOptionBox",
            self._updateDatePickerDays)
        self.setOptionBoxChangeFunction(
            name + "_DP_YearOptionBox",
            self._updateDatePickerDays)
        self.stopFrame()
        frame.isContainer = False
        self.widgetManager.add(WIDGET_NAMES.DatePicker, name, frame)

    def setDatePickerFg(self, name, fg):
        self.widgetManager.get(WIDGET_NAMES.DatePicker, name)
        self.setLabelFg(name + "_DP_DayLabel", fg)
        self.setLabelFg(name + "_DP_MonthLabel", fg)
        self.setLabelFg(name + "_DP_YearLabel", fg)

    def setDatePickerChangeFunction(self, title, function):
        self.widgetManager.get(WIDGET_NAMES.DatePicker, title)
        cmd = self.MAKE_FUNC(function, title)
        self.setOptionBoxChangeFunction(title + "_DP_DayOptionBox", cmd)
        self.widgetManager.get(WIDGET_NAMES.OptionBox, title + "_DP_DayOptionBox").function = cmd

    # function to update DatePicker dropDowns
    def _updateDatePickerDays(self, title):
        if title.find("_DP_MonthOptionBox") > -1:
            title = title.split("_DP_MonthOptionBox")[0]
        elif title.find("_DP_YearOptionBox") > -1:
            title = title.split("_DP_YearOptionBox")[0]
        else:
            self.warn("Can't update days in DatePicker:%s", title)
            return

        day = self.getOptionBox(title + "_DP_DayOptionBox")
        month = self.MONTH_NAMES.index(self.getOptionBox(title + "_DP_MonthOptionBox")) + 1
        year = int(self.getOptionBox(title + "_DP_YearOptionBox"))
        days = range(1, calendar.monthrange(year, month)[1] + 1)
        self.changeOptionBox(title + "_DP_DayOptionBox", days)

        # keep previous day if possible
        with PauseLogger():
            self.setOptionBox(title + "_DP_DayOptionBox", day, callFunction=False)

        box = self.widgetManager.get(WIDGET_NAMES.OptionBox, title + "_DP_DayOptionBox")
        if hasattr(box, 'function'):
            box.function()

    # set a date for the named DatePicker
    def setDatePickerRange(self, title, startYear, endYear=None):
        self.widgetManager.get(WIDGET_NAMES.DatePicker, title)
        if endYear is None:
            endYear = datetime.date.today().year
        years = range(startYear, endYear + 1)
        self.changeOptionBox(title + "_DP_YearOptionBox", years)

    def setDatePicker(self, title, date="today"):
        self.widgetManager.get(WIDGET_NAMES.DatePicker, title)
        if date == "today":
            date = datetime.date.today()
        self.setOptionBox(title + "_DP_YearOptionBox", str(date.year))
        self.setOptionBox(title + "_DP_MonthOptionBox", date.month - 1)
        self.setOptionBox(title + "_DP_DayOptionBox", date.day - 1)

    def clearDatePicker(self, title, callFunction=True):
        self.widgetManager.get(WIDGET_NAMES.DatePicker, title)
        self.setOptionBox(title + "_DP_YearOptionBox", 0, callFunction)
        self.setOptionBox(title + "_DP_MonthOptionBox", 0, callFunction)
        self.setOptionBox(title + "_DP_DayOptionBox", 0, callFunction)

    def clearAllDatePickers(self, callFunction=False):
        for k in self.widgetManager.group(WIDGET_NAMES.DatePicker):
            self.clearDatePicker(k, callFunction)

    def getDatePicker(self, title):
        self.widgetManager.get(WIDGET_NAMES.DatePicker, title)
        day = int(self.getOptionBox(title + "_DP_DayOptionBox"))
        month = self.MONTH_NAMES.index(
            self.getOptionBox(
                title + "_DP_MonthOptionBox")) + 1
        year = int(self.getOptionBox(title + "_DP_YearOptionBox"))
        date = datetime.date(year, month, day)
        return date

    def getAllDatePickers(self):
        dps = {}
        for k in self.widgetManager.group(WIDGET_NAMES.DatePicker):
            dps[k] = self.getDatePicker(k)
        return dps

#####################################
# FUNCTIONS for ACCESSABILITY
#####################################

    def _makeAccess(self):
        if not self.accessMade:
            def _close(): self.hideSubWindow("access_access_subwindow")
            def _changeFg(): self.label("access_fg_colBox", bg=self.colourBox(self.getLabelBg("access_fg_colBox")))
            def _changeBg(): self.label("access_bg_colBox", bg=self.colourBox(self.getLabelBg("access_bg_colBox")))
            def _settings():
                font = {"underline":self.check("access_underline_check"), "overstrike":self.check("access_overstrike_check")}
                font["weight"] = "bold" if self.check("access_bold_check") is True else "normal"
                font["slant"] = "roman" if self.radio("access_italic_radio") == "Normal" else "italic"
                if len(self.listbox("access_family_listbox")) > 0: font["family"] = self.listbox("access_family_listbox")[0]
                if self.option("access_size_option") is not None: font["size"] = self.option("access_size_option")

                if self.check('access_label_check'): self.labelFont = font
                if self.check('access_input_check'): self.inputFont = font
                if self.check('access_button_check'): self.buttonFont = font

                self.bg = self.getLabelBg("access_bg_colBox")
                self.fg = self.getLabelBg("access_fg_colBox")

            self.accessOrigFont = self.accessOrigBg = self.accessOrigFg = None
            with self.subWindow("access_access_subwindow", sticky = "news", title="Accessibility", resizable=False) as sw:
                if not self.ttk:
                    sw.config(padx=5, pady=1)
                with self.labelFrame("access_font_labelframe", sticky="news", name="Font") as lf:
                    if not self.ttk:
                        lf.config(padx=5, pady=5, font=self._accessFont)

                    with self.frame("access_ticks_frame", colspan=2):
                        self.check("access_label_check", True, label="Labels", pos=(0,0), font=self._accessFont, tip="Set label fonts")
                        self.check("access_input_check", label="Inputs", pos=(0,1), font=self._accessFont, tip="Set input fonts")
                        self.check("access_button_check", label="Buttons", pos=(0,2), font=self._accessFont, tip="Set button fonts")

                    self.listbox("access_family_listbox", self.fonts, rows=6, tip="Choose a font", colspan=2, font=self._accessFont)
                    self.option("access_size_option", [7, 8, 9, 10, 12, 13, 14, 16, 18, 20, 22, 25, 29, 34, 40], label="Size:", tip="Choose a font size", font=self._accessFont)
                    self.check("access_bold_check", name="Bold", pos=('p',1), tip="Check this to make all font bold", font=self._accessFont)
                    self.radio("access_italic_radio", "Normal", tip="No italics", font=self._accessFont)
                    self.radio("access_italic_radio", "Italic", pos=('p',1), tip="Set font italic", font=self._accessFont)
                    self.check("access_underline_check", name="Underline", tip="Underline all text", font=self._accessFont)
                    self.check("access_overstrike_check", name="Overstrike", pos=('p',1), tip="Strike out all text", font=self._accessFont)
                with self.labelFrame("access_colour_labelframe", sticky="news", name="Colours") as lf:
                    if not self.ttk:
                        lf.config(padx=5, pady=5, font=self._accessFont)
                    self.label("access_fg_text", "Foreground:", sticky="ew", anchor="w", font=self._accessFont)
                    self.label("access_fg_colBox", "", pos=('p',1), sticky="ew", submit=_changeFg, relief="ridge", tip="Click here to set the foreground colour", font=self._accessFont, width=14)
                    self.label("access_bg_text", "Background:", sticky="ew", anchor="w", font=self._accessFont)
                    self.label("access_bg_colBox", "", pos=('p',1), sticky="ew", submit=_changeBg, relief="ridge", tip="Click here to set the background colour", font=self._accessFont, width=14)
                self.sticky="se"
                with self.frame("access_button_box"):
                    self.button("access_apply_button", _settings, name="Apply", pos=(0,0), font=self._accessFont)
                    self.button("access_reset_button", self._resetAccess, name="Reset", pos=(0,1), font=self._accessFont)
                    self.button("access_close_button", _close, name="Close", pos=(0,2), font=self._accessFont)
            self.accessMade = True

    def _resetAccess(self):
        if self.accessMade:
            self.check("access_label_check", True)
            self.check("access_input_check", False)
            self.check("access_button_check", False)

            self.listbox("access_family_listbox", self.accessOrigFont["family"])
            self.option("access_size_option", str(self.accessOrigFont["size"]))

            if self.accessOrigFont["weight"] == "normal": self.check("access_bold_check", False)
            else: self.check("access_bold_check", True)

            if self.accessOrigFont["slant"] == "roman": self.radio("access_italic_radio", "Normal")
            else: self.radio("access_italic_radio", "Italic")

            self.check("access_overstrike_check", self.accessOrigFont["overstrike"])
            self.check("access_underline_check", self.accessOrigFont["underline"])

            self.label("access_fg_colBox", bg=self.accessOrigFg)
            self.label("access_bg_colBox", bg=self.accessOrigBg)
        else:
            gui.warn("Accessibility not set up yet.")


    def showAccess(self, location=None):
        self._makeAccess()
        # update current settings
        self.accessOrigFont = self.font
        self.accessOrigBg = self.bg
        self.accessOrigFg = self.fg
        self._resetAccess()
        self.showSubWindow("access_access_subwindow")

#####################################
# FUNCTIONS for labels
#####################################

    def _parsePos(self, pos, kwargs):
        # alternative for specifying position
        if type(pos) != list and type(pos) != tuple: pos = (pos,)
        if len(pos) > 0: kwargs["row"] = pos[0]
        if len(pos) > 1: kwargs["column"] = pos[1]
        if len(pos) > 2: kwargs["colspan"] = pos[2]
        if len(pos) > 3: kwargs["rowspan"] = pos[3]

        # allow an alternative kwarg
        if "col" in kwargs: kwargs["column"]=kwargs.pop("col")

        # let user specify stickt/stretch/expan
        sticky = kwargs.pop("sticky", None)
        if sticky is not None: self.setSticky(sticky)
        stretch = kwargs.pop("stretch", None)
        if stretch is not None: self.setStretch(stretch)
        expand = kwargs.pop("expand", None)
        if expand is not None: self.setExpand(expand)

        return kwargs

    def label(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets labels all in one go """
        widgKind = WIDGET_NAMES.Label
        kind = kwargs.pop("kind", "standard").lower().strip()

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            if value is not None: self.setLabel(title, value)
            label = self.getLabel(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if kind == "flash": label = self._labelMaker(title, value, kind, *args, **kwargs)
            elif kind == "selectable": label = self._labelMaker(title, value, kind, *args, **kwargs)
            else: label = self._labelMaker(title, value, "label", *args, **kwargs)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return label

    def _labelMaker(self, title, text=None, kind="label", row=None, column=0, colspan=0, rowspan=0, **kwargs):
        """ Internal wrapper, to hide kwargs from original add functions """
        if kind == "flash": return self.addFlashLabel(title, text, row, column, colspan, rowspan)
        elif kind == "selectable": return self.addSelectableLabel(title, text, row, column, colspan, rowspan)
        elif kind == "label": return self.addLabel(title, text, row, column, colspan, rowspan)

    def _flash(self):
        if not self.alive: return
        if self.doFlash:
            for lab in self.widgetManager.group(WIDGET_NAMES.FlashLabel):
                bg = lab.cget("background")
                fg = lab.cget("foreground")
                lab.config(background=fg, foreground=bg)
        self.flashId = self.topLevel.after(250, self._flash)

    def addFlashLabel(self, title, text=None, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a label with flashing text '''
        lab = self.addLabel(title, text, row, column, colspan, rowspan)
        self.widgetManager.log(WIDGET_NAMES.FlashLabel, lab)
        self.doFlash = True
        return lab

    def addSelectableLabel(self, title, text=None, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a label with selectable text '''
        return self.addLabel(title, text, row, column, colspan, rowspan, selectable=True)

    def addLabel(self, title, text=None, row=None, column=0, colspan=0, rowspan=0, selectable=False):
        """Add a label to the GUI.
        :param title: a unique identifier for the Label
        :param text: optional text for the Label
        :param row/column/colspan/rowspan: the row/column to position the label in & how many rows/columns to strecth across
        :raises ItemLookupError: raised if the title is not unique
        """
        self.widgetManager.verify(WIDGET_NAMES.Label, title)
        if text is None:
            gui.trace("Not specifying text for labels (%s) now uses the title for the text. If you want an empty label, pass an empty string ''", title)
            text = title

        if not selectable:
            if not self.ttkFlag:
                lab = Label(self.getContainer(), text=text)
                lab.config(justify=LEFT, font=self._getContainerProperty('labelFont'), background=self._getContainerBg())
                lab.origBg = self._getContainerBg()
            else:
                lab = ttk.Label(self.getContainer(), text=text)
        else:
            lab = SelectableLabel(self.getContainer(), text=text)
            lab.config(justify=CENTER, font=self._getContainerProperty('labelFont'), background=self._getContainerBg())
            lab.origBg = self._getContainerBg()

        lab.inContainer = False
        lab.DEFAULT_TEXT = text

        self.widgetManager.add(WIDGET_NAMES.Label, title, lab)
        self._positionWidget(lab, row, column, colspan, rowspan)
        return lab

    def addEmptyLabel(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds an empty label '''
        return self.addLabel(title=title, text='', row=row, column=column, colspan=colspan, rowspan=rowspan)

    def addLabels(self, names, row=None, colspan=0, rowspan=0):
        ''' adds a set of labels, in the row, spannning specified columns '''
        frame = self._makeWidgetBox()(self.getContainer())
        if not self.ttkFlag:
            frame.config(background=self._getContainerBg())
        for i in range(len(names)):
            self.widgetManager.verify(WIDGET_NAMES.Label, names[i])
            if not self.ttkFlag:
                lab = Label(frame, text=names[i])
                lab.config(font=self._getContainerProperty('labelFont'), justify=LEFT, background=self._getContainerBg())
            else:
                lab = ttk.Label(frame, text=names[i])
            lab.DEFAULT_TEXT = names[i]
            lab.inContainer = False

            self.widgetManager.add(WIDGET_NAMES.Label, names[i], lab)
            lab.grid(row=0, column=i)
            Grid.columnconfigure(frame, i, weight=1)
            Grid.rowconfigure(frame, 0, weight=1)
            frame.theWidgets.append(lab)

        self._positionWidget(frame, row, 0, colspan, rowspan)
        self.widgetManager.log(WIDGET_NAMES.FrameBox, frame)

    def setLabel(self, name, text):
        lab = self.widgetManager.get(WIDGET_NAMES.Label, name)
        lab.config(text=text)

    def getLabel(self, name):
        lab = self.widgetManager.get(WIDGET_NAMES.Label, name)
        return lab.cget("text")

    def clearLabel(self, name):
        self.setLabel(name, "")

    def clearAllLabels(self):
        for lb in self.widgetManager.group(WIDGET_NAMES.Label):
            self.clearLabel(lb)

#####################################
# FUNCTIONS to add Text Area
#####################################

    def text(self, title, value=None, *args, **kwargs):
        """ simpleGUI - shortner for textArea() """
        return self.textArea(title, value, *args, **kwargs)

    def textArea(self, title, value=None, *args, **kwargs):
        """ adds, sets & gets textAreas all in one go """
        widgKind = WIDGET_NAMES.TextArea
        scroll = kwargs.pop("scroll", False)
        end = kwargs.pop("end", True)
        replace = kwargs.pop("replace", False)
        callFunction = kwargs.pop("callFunction", True)
        disabled = kwargs.pop("disabled", False)
        tag = kwargs.pop("tag", None)
        tags = kwargs.pop("tags", [])

        try: self.widgetManager.verify(WIDGET_NAMES.TextArea, title)
        except: # widget exists
            text = self.getTextArea(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if scroll: text = self._textMaker(title, "scroll", *args, **kwargs)
            else: text = self._textMaker(title, "text", *args, **kwargs)
            callFunction = False

        # create any tags
        for _tag in tags:
            self.textAreaCreateTag(title, _tag[0], **_tag[1])

        if replace: self.clearTextArea(title)
        if value is not None: self.setTextArea(title, value, end=end, callFunction=callFunction, tag=tag)
        if disabled: self.disableTextArea(title)
        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return text

    def _textMaker(self, title, kind="text", row=None, column=0, colspan=0, rowspan=0, *args, **kwargs):
        if kind == "scroll": return self.addScrolledTextArea(title, row, column, colspan, rowspan)
        elif kind == "text": return self.addTextArea(title, row, column, colspan, rowspan)

    def _buildTextArea(self, title, frame, scrollable=False):
        """ Internal wrapper, used for building TextAreas.

        :param title: the key used to reference this TextArea
        :param frame: this should be a container, used as the parent for the OptionBox
        :param scrollable: the key used to reference this TextArea
        :returns: the created TextArea
        :raises ItemLookupError: if the title is already in use
        """
        self.widgetManager.verify(WIDGET_NAMES.TextArea, title)
        if scrollable:
            text = AjScrolledText(frame)
        else:
            text = AjText(frame)
        text.config(width=20, height=10, undo=True, wrap=WORD)

        if not self.ttkFlag:
            if self.platform in [self.MAC, self.LINUX]:
                text.config(highlightbackground=self._getContainerBg())

        text.bind("<Tab>", self._focusNextWindow)
        text.bind("<Shift-Tab>", self._focusLastWindow)

        # add a right click menu
        text.var = None
        self._addRightClickMenu(text)

        self.widgetManager.add(WIDGET_NAMES.TextArea, title, text)
        self.logTextArea(title)

        return text

    def addTextArea(self, title, row=None, column=0, colspan=0, rowspan=0, text=None):
        """ Adds a TextArea with the specified title
        Simply calls internal _buildTextArea function before positioning the widget

        :param title: the key used to reference this TextArea
        :returns: the created TextArea
        :raises ItemLookupError: if the title is already in use
        """
        txt = self._buildTextArea(title, self.getContainer())
        self._positionWidget(txt, row, column, colspan, rowspan, N+E+S+W)
        if text is not None: self.setTextArea(title, text, callFunction=False)
        return txt

    def addScrolledTextArea(self, title, row=None, column=0, colspan=0, rowspan=0, text=None):
        """ Adds a Scrollable TextArea with the specified title
        Simply calls internal _buildTextArea functio, specifying a ScrollabelTextArea before positioning the widget

        :param title: the key used to reference this TextArea
        :returns: the created TextArea
        :raises ItemLookupError: if the title is already in use
        """
        txt = self._buildTextArea(title, self.getContainer(), True)
        self._positionWidget(txt, row, column, colspan, rowspan, N+E+S+W)
        if text is not None: self.setTextArea(title, text, callFunction=False)
        return txt

    def getTextArea(self, title):
        """ Gets the text in the specified TextArea

        :param title: the TextArea to check
        :returns: the text in the specified TextArea
        :raises ItemLookupError: if the title can't be found
        """
        return self.widgetManager.get(WIDGET_NAMES.TextArea, title).getText()

    def getAllTextAreas(self):
        """ Convenience function to get the text for all TextAreas in the GUI.

        :returns: a dictionary containing the result of calling getTextArea for every TextArea in the GUI
        """
        areas = {}
        for k in self.widgetManager.group(WIDGET_NAMES.TextArea):
            areas[k] = self.getTextArea(k)
        return areas

    def textAreaCreateTag(self, title, name, **kwargs):
        """ creates a new tag on the specified text area """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        ta.tag_config(name, **kwargs)

    def textAreaChangeTag(self, title, name, **kwargs):
        """ changes a tag on the specified text area """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        ta.tag_config(name, **kwargs)

    def textAreaDeleteTag(self, title, *tags):
        """ deletes the specified tag """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        ta.tag_delete(*tags)

    def textAreaTagPattern(self, title, tag, pattern, regexp=False):
        """ applies the tag to the specified text """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        ta.highlightPattern(pattern, tag, regexp=regexp)

    def textAreaTagRange(self, title, tag, start, end=END):
        """ applies the tag to the specified range """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        ta.tag_add(tag, start, end)

    def textAreaTagSelected(self, title, tag):
        if self.widgetManager.get(WIDGET_NAMES.TextArea, title).tag_ranges(SEL):
            self.textAreaTagRange(title, tag, SEL_FIRST, SEL_LAST)
        self.widgetManager.get(WIDGET_NAMES.TextArea, title).focus_set()

    def textAreaUntagRange(self, title, tag, start, end=END):
        """removes the tag from the specified range """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        ta.tag_remove(tag, start, end)

    def textAreaToggleFontRange(self, title, tag, start, end=END):
        """ will toggle the tag at the specified range """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        tag = ta.verifyFontTag(tag)

        if tag in ta.tag_names(start):
            ta.tag_remove("AJ_"+tag, start, end)
        else:
            self.textAreaApplyFontRange(title, tag, start, end)

    def textAreaToggleFontSelected(self, title, tag):
        if self.widgetManager.get(WIDGET_NAMES.TextArea, title).tag_ranges(SEL):
            self.textAreaToggleFontRange(title, tag, SEL_FIRST, SEL_LAST)
        self.widgetManager.get(WIDGET_NAMES.TextArea, title).focus_set()

    def textAreaApplyFontSelected(self, title, tag):
        if self.widgetManager.get(WIDGET_NAMES.TextArea, title).tag_ranges(SEL):
            self.textAreaApplyFontRange(title, tag, SEL_FIRST, SEL_LAST)
        self.widgetManager.get(WIDGET_NAMES.TextArea, title).focus_set()

    def textAreaApplyFontRange(self, title, tag, start, end=END):
        """removes the tag from the specified range """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        tag = ta.verifyFontTag(tag)
        if tag != "UNDERLINE":
            ta.tag_remove("AJ_BOLD", start, end)
            ta.tag_remove("AJ_ITALIC", start, end)
            ta.tag_remove("AJ_BOLD_ITALIC", start, end)
        ta.tag_add("AJ_" + tag, start, end)

    def textAreaUntagSelected(self, title, tag):
        if self.widgetManager.get(WIDGET_NAMES.TextArea, title).tag_ranges(SEL):
            self.textAreaUntagRange(title, tag, SEL_FIRST, SEL_LAST)
        self.widgetManager.get(WIDGET_NAMES.TextArea, title).focus_set()

    def textAreaToggleTagRange(self, title, tag, start, end=END):
        """ will toggle the tag at the specified range """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        if tag in ta.tag_names(start): self.textAreaUntagRange(title, tag, start, end)
        else: self.textAreaTagRange(title, tag, start, end)

    def textAreaToggleTagSelected(self, title, tag):
        if self.widgetManager.get(WIDGET_NAMES.TextArea, title).tag_ranges(SEL):
            self.textAreaToggleTagRange(title, tag, SEL_FIRST, SEL_LAST)
        self.widgetManager.get(WIDGET_NAMES.TextArea, title).focus_set()

    def searchTextArea(self, title, pattern, start=None, stop=None, nocase=True, backwards=False):
        """ will find and highlight the specified text, returning the position """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        if start is None: start = ta.index(INSERT)
        pos = ta.search(pattern, start, stopindex=stop, nocase=nocase, backwards=backwards)
        ta.focus_set()
        if pos == "":
            return None
        else:
            end = str(pos) + " + " + str(len(pattern)) + " c"
            ta.see(pos)
            ta.tag_add(SEL, pos, end)
            ta.mark_set("insert", pos)
            return pos

    def getTextAreaTag(self, title, tag):
        """ returns all details about the specified tag """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        return ta.tag_config(tag)

    def getTextAreaTags(self, title):
        """ returns a list of all tags in the text area """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        return ta.tag_names()

    def setTextAreaFont(self, title, **kwargs):
        """ changes the font of a text area """
        self.widgetManager.get(WIDGET_NAMES.TextArea, title).setFont(**kwargs)

    def setTextArea(self, title, text, end=True, callFunction=True, tag=None):
        """ Add the supplied text to the specified TextArea

        :param title: the TextArea to change
        :param text: the text to add to the TextArea
        :param end: where to insert the text, by default it is added to the end. Set end to False to add to the beginning.
        :param callFunction: whether to generate an event to notify that the widget has changed
        :returns: None
        :raises ItemLookupError: if the title can't be found
        """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)

        ta.pauseCallFunction(callFunction)
        # in case it's disabled
        _state = ta.cget('state')
        ta.config(state='normal')

        if end:
            pos = ta.index('end -1c linestart')
            ta.insert(END, text)
            ta.see(END)
#            if tag is not None: self.textAreaTagRange(title, tag, pos)
        else:
            ta.insert('1.0', text)
            ta.see('1.0')
#            if tag is not None: ta.textAreaTagPattern(title, tag, text)

        ta.config(state=_state)
        ta.resumeCallFunction()

    def clearTextArea(self, title, callFunction=True):
        """ Removes all text from the specified TextArea

        :param title: the TextArea to change
        :param callFunction: whether to generate an event to notify that the widget has changed
        :returns: None
        :raises ItemLookupError: if the title can't be found
        """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        ta.pauseCallFunction(callFunction)
        # in case it's disabled
        _state = ta.cget('state')
        ta.config(state='normal')
        ta.delete('1.0', END)
        ta.config(state=_state)
        ta.resumeCallFunction()

    def clearAllTextAreas(self, callFunction=False):
        """ Convenience function to clear all TextAreas in the GUI
        Will simply call clearTextArea on each TextArea

        :param callFunction: whether to generate an event to notify that the widget has changed
        :returns: None
        """
        for ta in self.widgetManager.group(WIDGET_NAMES.TextArea):
            self.clearTextArea(ta, callFunction=callFunction)

    def highlightTextArea(self, title, start, end=END):
        """ selects text in the specified range """
        ta = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
        ta.tag_add(SEL, start, end)

    def logTextArea(self, title):
        """ Creates an md5 hash - can be used later to check if the TextArea has changed
        The hash is stored in the widget

        :param title: the TextArea to hash
        :returns: None
        :raises ItemLookupError: if the title can't be found
        """
        self._loadHashlib()
        if hashlib is False:
            self.warn("Unable to log TextArea, hashlib library not available")
        else:
            text = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
            text.__hash = text.getTextAreaHash()

    def textAreaChanged(self, title):
        """ Creates a temporary md5 hash - and compares it with a previously generated & stored hash
        The previous hash has to be generated manually, by calling logTextArea

        :param title: the TextArea to hash
        :returns: bool - True if the TextArea has changed or False if it hasn't
        :raises ItemLookupError: if the title can't be found
        """
        self._loadHashlib()
        if hashlib is False:
            self.warn("Unable to log TextArea, hashlib library not available")
        else:
            text = self.widgetManager.get(WIDGET_NAMES.TextArea, title)
            return text.__hash != text.getTextAreaHash()

#####################################
# FUNCTIONS to add Tree Widgets
#####################################

    def tree(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets trees all in one go """
        widgKind = WIDGET_NAMES.Tree

        click = kwargs.pop("click", None)
        dblClick = kwargs.pop("dbl", None)
        edit = kwargs.pop("edit", None)
        editable = kwargs.pop("editable", None)
        showAttr = kwargs.pop("attributes", None)
        showMenu = kwargs.pop("menu", None)

        fg = kwargs.pop("fg", None)
        bg = kwargs.pop("bg", None)
        fgH = kwargs.pop("fgH", None)
        bgH = kwargs.pop("bgH", None)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            tree = self.getTree(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            tree = self.addTree(title, value, *args, **kwargs)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        self.setTreeColours(title, fg, bg, fgH, bgH)

        if click is not None: self.setTreeClickFunction(title, click)
        if edit is not None: self.setTreeEditFunction(title, edit)
        if dblClick is not None: self.setTreeDoubleClickFunction(title, dblClick)
        if editable is not None: self.setTreeEditable(title, editable)
        if showAttr is not None: self.showTreeAttributes(title, showAttr)
        if showMenu is not None: self.showTreeMenu(title, showMenu)
        return tree

    def addTree(self, title, data, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a navigatable tree, displaying the specified xml text '''
        self.widgetManager.verify(WIDGET_NAMES.Tree, title)
        self._importAjtree()
        if parseString is False:
            self.warn("Unable to parse xml files. .addTree() not available")
            return

        if isinstance(data, UNIVERSAL_STRING):
            data = parseString(data)
        else:
            pass # assume xml object
            
        return self._buildTree(title, data, row, column, colspan, rowspan)

    def _buildTree(self, title, xmlDoc, row=None, column=0, colspan=0, rowspan=0):
        self.widgetManager.verify(WIDGET_NAMES.Tree, title)

        frame = ScrollPane(
            self.getContainer(),
            relief=RAISED,
            borderwidth=2,
            bg="#FFFFFF",
            highlightthickness=0,
            takefocus=1)
        self._positionWidget(frame, row, column, colspan, rowspan, "NSEW")

        treeData = self._makeAjTreeData()(xmlDoc)
        gui.trace("TreeData populated: %s", title)

        treeNode = self._makeAjTreeNode()(frame.getPane(), None, treeData)
        gui.trace("TreeNode created: %s", title)

        self.widgetManager.add(WIDGET_NAMES.Tree, title, treeNode)
        # update() & expand() called in go() function
        return treeNode

    # not complete yet...
    def clearTree(self, title):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        tree.destroy()
        tree.update()

    def showTreeAttributes(self, title, show=True):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        self._loadTooltip()
        tree.showAttributes(show)

    # not complete yet...
    def showTreeMenu(self, title, show=True):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        tree.showMenu(show)

    # not complete yet...
    def addTreeChild(self, title, data):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        if isinstance(data, UNIVERSAL_STRING):
            data = parseString(data)
        treeData = self._makeAjTreeData()(data)
        tree.addChild(treeData)

    def setTreeEditable(self, title, value=True):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        tree.item.setCanEdit(value)

    def setTreeBg(self, title, colour):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        tree.setBgColour(colour)

    def setTreeFg(self, title, colour):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        tree.setFgColour(colour)

    def setTreeHighlightBg(self, title, colour):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        tree.setBgHColour(colour)

    def setTreeHighlightFg(self, title, colour):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        tree.setFgHColour(colour)

    def setTreeColours(self, title, fg=None, bg=None, fgH=None, bgH=None):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        tree.setAllColours(bg, fg, bgH, fgH)

    def setTreeDoubleClickFunction(self, title, func):
        if func is not None:
            tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
            tree.item.registerDblClick(title, func)

    def setTreeClickFunction(self, title, func):
        if func is not None:
            tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
            tree.item.registerClick(title, func)

    def setTreeEditFunction(self, title, func):
        if func is not None:
            tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
            command = self.MAKE_FUNC(func, title)
            tree.registerEditEvent(command)

    # get whole tree as XML
    def getTreeXML(self, title):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        return tree.item.node.toxml()

    # get selected node as a string
    def getTreeSelected(self, title):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        return tree.getSelectedText()

    # get selected node (and children) as XML
    def getTreeSelectedXML(self, title):
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        item = tree.getSelected()
        if item is not None:
            return item.node.toxml()
        else:
            return None

    def generateTree(self, title):
        """ displays data inside tree """
        tree = self.widgetManager.get(WIDGET_NAMES.Tree, title)
        gui.trace("Generating Tree: %s", title)
        tree.update()
        gui.trace("Tree updated: %s", title)
        tree.expand()
        gui.trace("Tree expanded: %s", title)

#####################################
# FUNCTIONS to add Message Box
#####################################

    def message(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets messages all in one go """
        widgKind = WIDGET_NAMES.Message

        try: self.widgetManager.verify(WIDGET_NAMES.Message, title)
        except: # widget exists
            if value is not None: self.setMessage(title, value)
            msg = self.getMessage(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            msg = self._messageMaker(title, value, *args, **kwargs)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return msg

    def _messageMaker(self, title, text, row=None, column=0, colspan=0, rowspan=0, *args, **kwargs):
        return self.addMessage(title, text, row, column, colspan, rowspan)

    def addMessage(self, title, text=None, row=None, column=0, colspan=0, rowspan=0):
        ''' adds a message box, to display text across multiple lines '''
        self.widgetManager.verify(WIDGET_NAMES.Message, title)

        if text is None:
            text = title
            gui.trace("Not specifying text for messages (%s) now uses the title for the text. If you want an empty message, pass an empty string ''", title)
        mess = Message(self.getContainer())
        mess.config(text=text)
        mess.config(font=self._getContainerProperty('labelFont'))
        mess.config(justify=LEFT, background=self._getContainerBg())
        mess.DEFAULT_TEXT = text

        if self.platform in [self.MAC, self.LINUX]:
            mess.config(highlightbackground=self._getContainerBg())

        self.widgetManager.add(WIDGET_NAMES.Message, title, mess)

        self._positionWidget(mess, row, column, colspan, rowspan)
#            mess.bind("<Configure>", lambda e: mess.config(width=e.width-10))
        return mess

    def addEmptyMessage(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds an empty message box '''
        return self.addMessage(title, "", row, column, colspan, rowspan)

    def setMessage(self, title, text):
        mess = self.widgetManager.get(WIDGET_NAMES.Message, title)
        mess.config(text=text)

    def setMessageAspect(self, title, aspect):
        """ set a new aspect ratio for the text in this widget """
        mess = self.widgetManager.get(WIDGET_NAMES.Message, title)
        mess.config(aspect=aspect)

    def clearMessage(self, title):
        self.setMessage(title, "")

    def getMessage(self, title):
        mess = self.widgetManager.get(WIDGET_NAMES.Message, title)
        return mess.cget("text")

#####################################
# FUNCTIONS for entry boxes
#####################################

    def entry(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets entries all in one go """
        widgKind = WIDGET_NAMES.Entry
        default = kwargs.pop("default", None)
        limit = kwargs.pop("limit", None)
        case = kwargs.pop("case", None)
        rows = kwargs.pop("rows", None)
        secret = kwargs.pop("secret", False)
        kind = kwargs.pop("kind", "standard").lower().strip()
        labBg = kwargs.pop("labBg", None)

        try: self.widgetManager.verify(WIDGET_NAMES.Entry, title)
        except: # widget exists
            if value is not None: self.setEntry(title, value, *args, **kwargs)
            ent = self.getEntry(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            # create the entry widget
            if kind == "auto":
                if value is None: value = []
                ent = self._entryMaker(title, *args, secret=secret, kind=kind, words=value, **kwargs)
            else:
                ent = self._entryMaker(title, *args, secret=secret, kind=kind, **kwargs)
                if not ent: return

        # apply any setter values
        if limit is not None: self.setEntryMaxLength(title, limit)
        if case == "upper": self.setEntryUpperCase(title)
        elif case == "lower": self.setEntryLowerCase(title)

        if default is not None: self.setEntryDefault(title, default)

        if kind != "auto":
            if value is not None: self.setEntry(title, value)
        else:
            if rows is not None: self.setAutoEntryNumRows(title, rows)

        if labBg is not None and self.widgetManager.get(WIDGET_NAMES.Entry, title).isValidation:
            self.setValidationEntryLabelBg(title, labBg)

        # used by file entries
        kwargs.pop("text", None)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return ent

    def setValidationEntryLabelBg(self, title, bg):
        ent = self.widgetManager.get(WIDGET_NAMES.Entry, title)
        if not ent.isValidation:
            raise Exception("You can only set label BGs on validation entries")
        ent.lab.config(bg=bg)

    def _entryMaker(self, title, row=None, column=0, colspan=0, rowspan=0, secret=False, label=False, kind="standard", words=None, **kwargs):
        # used by file entries
        text = kwargs.pop("text", None) 
        default = kwargs.pop("default", None) 

        if not label:
            frame = self.getContainer()
        else:
            frame = self._getLabelBox(title, label=label, **kwargs)

        if kind == "standard":
            ent = self._buildEntry(title, frame, secret)
        elif kind == "numeric":
            ent = self._buildEntry(title, frame, secret)
            if self.validateNumeric is None:
                self.validateNumeric = (self.containerStack[0]['container'].register(
                    self._validateNumericEntry), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')

            ent.isNumeric = True
            ent.config(validate='key', validatecommand=self.validateNumeric)
            self.setEntryTooltip(title, "Numeric data only.")
        elif kind == "auto":
            ent = self._buildEntry(title, frame, secret=False, words=words)
        elif kind in ["file", "open", "save", "directory"]:
            ent = self._buildFileEntry(title, frame, kind=kind, text=text, default=default)
        elif kind == "validation":
            ent = self._buildValidationEntry(title, frame, secret)
        else:
            raise Exception("Invalid entry kind: %s", kind)

        if not label:
            self._positionWidget(ent, row, column, colspan, rowspan)
        else:
            self._packLabelBox(frame, ent)
            self._positionWidget(frame, row, column, colspan, rowspan)
        return ent

    def addEntry(self, title, row=None, column=0, colspan=0, rowspan=0, secret=False):
        ''' adds an entry box for capturing text '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=secret, label=False, kind="standard")

    def addLabelEntry(self, title, row=None, column=0, colspan=0, rowspan=0, secret=False, label=True):
        ''' adds an entry box for capturing text, with the title as a label '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret, label=label)

    def addSecretEntry(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds an entry box for capturing text, where the text is displayed as stars '''
        return self._entryMaker(title, row, column, colspan, rowspan, True)

    def addLabelSecretEntry(self, title, row=None, column=0, colspan=0, rowspan=0, label=True):
        ''' adds an entry box for capturing text, where the text is displayed as stars, with the title as a label '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=True, label=label)

    def addSecretLabelEntry(self, title, row=None, column=0, colspan=0, rowspan=0, label=True):
        ''' adds an entry box for capturing text, where the text is displayed as stars, with the title as a label '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=True, label=label)

    def addFileEntry(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds an entry box with a button, that pops-up a file dialog '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=False, kind="file")

    def addLabelFileEntry(self, title, row=None, column=0, colspan=0, rowspan=0, label=True):
        ''' adds an entry box with a button, that pops-up a file dialog, with a label that displays the title '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=label, kind="file")

    def addOpenEntry(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds an entry box with a button, that pops-up a open dialog '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=False, kind="open")

    def addLabelOpenEntry(self, title, row=None, column=0, colspan=0, rowspan=0, label=True):
        ''' adds an entry box with a button, that pops-up a open dialog, with a label that displays the title '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=label, kind="open")

    def addSaveEntry(self, title, row=None, column=0, colspan=0, rowspan=0):
        ''' adds an entry box with a button, that pops-up a save dialog '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=False, kind="save")

    def addLabelSaveEntry(self, title, row=None, column=0, colspan=0, rowspan=0, label=True):
        ''' adds an entry box with a button, that pops-up a save dialog, with a label that displays the title '''
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=label, kind="save")

    def addDirectoryEntry(self, title, row=None, column=0, colspan=0, rowspan=0):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=False, kind="directory")

    def addLabelDirectoryEntry(self, title, row=None, column=0, colspan=0, rowspan=0, label=True):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=label, kind="directory")

    def addValidationEntry(self, title, row=None, column=0, colspan=0, rowspan=0, secret=False):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=False, kind="validation")

    def addLabelValidationEntry(self, title, row=None, column=0, colspan=0, rowspan=0, secret=False, label=True):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=label, kind="validation")

    def addAutoEntry(self, title, words, row=None, column=0, colspan=0, rowspan=0):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=False, kind="auto", words=words)

    def addLabelAutoEntry(self, title, words, row=None, column=0, colspan=0, rowspan=0, secret=False, label=True):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=False, label=label, kind="auto", words=words)

    def addNumericEntry(self, title, row=None, column=0, colspan=0, rowspan=0, secret=False):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=secret, label=False, kind="numeric")

    def addLabelNumericEntry(self, title, row=None, column=0, colspan=0, rowspan=0, secret=False, label=True):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=secret, label=label, kind="numeric")

    def addNumericLabelEntry(self, title, row=None, column=0, colspan=0, rowspan=0, secret=False, label=True):
        return self._entryMaker(title, row, column, colspan, rowspan, secret=secret, label=label, kind="numeric")

    def _getDirName(self, title):
        self._getFileName(title, kind='directory')

    def _getSaveName(self, title):
        self._getFileName(title, kind='save')

    def _getFileName(self, title, kind='open'):
        if kind in ['open', 'file']:
            fileName = self.openBox()
        elif kind == 'save':
            fileName = self.saveBox()
        elif kind == 'directory':
            fileName = self.directoryBox()

        if fileName is not None and fileName != "":
            self.setEntry(title, fileName)

        self.topLevel.after(250, self.setEntryFocus, title)

    def _checkDirName(self, title):
        if len(self.getEntry(title)) == 0:
            self._getFileName(title, kind='directory')

    def _checkSaveName(self, title):
        if len(self.getEntry(title)) == 0:
            self._getFileName(title, kind='save')

    def _checkFileName(self, title):
        if len(self.getEntry(title)) == 0:
            self._getFileName(title, kind='open')

    def _buildEntry(self, title, frame, secret=False, words=[]):
        self.widgetManager.verify(WIDGET_NAMES.Entry, title)
        # if we are an autocompleter
        if len(words) > 0:
            ent = self._makeAutoCompleteEntry()(words, self._getTopLevel(), frame)
        else:
            var = StringVar(self.topLevel)
            ent = entryBase(frame, textvariable=var)
            ent.var = var
            ent.var.auto_id = None

            # for now - suppress UP/DOWN arrows
            if self.platform in [self.MAC]:
                def suppress(event):
                    if event.keysym == "Up":
                        # move home
                        event.widget.icursor(0)
                        event.widget.xview(0)
                        return "break"
                    elif event.keysym == "Down":
                        # move end
                        if not self.ttkFlag:
                            event.widget.icursor(END)
                            event.widget.xview(END)
                        else:
                            event.widget.icursor(END)
                            event.widget.xview(len(event.widget.get()))
                        return "break"

                ent.bind("<Key>", suppress)

        if not self.ttkFlag:
            ent.config(font=self._getContainerProperty('inputFont'))
            if self.platform in [self.MAC, self.LINUX]:
                ent.config(highlightbackground=self._getContainerBg())

        # vars to store any limit traces
        ent.var.uc_id = None
        ent.var.lc_id = None
        ent.var.ml_id = None

        ent.inContainer = False
        ent.showingDefault = False  # current status of entry
        ent.default = ""  # the default value to show (if set)
        ent.DEFAULT_TEXT = ""  # the default value for language support
        ent.myTitle = title  # the title of the entry
        ent.isNumeric = False  # if the entry is numeric
        ent.isValidation = False  # if the entry is validation
        ent.isSecret = False  # if the entry is secret

        # configure it to be secret
        if secret:
            ent.config(show="*")
            ent.isSecret = True

        ent.bind("<Tab>", self._focusNextWindow)
        ent.bind("<Shift-Tab>", self._focusLastWindow)

        # add a right click menu
        self._addRightClickMenu(ent)

        self.widgetManager.add(WIDGET_NAMES.Entry, title, ent)
        self.widgetManager.add(WIDGET_NAMES.Entry, title, ent.var, group=WidgetManager.VARS)
        return ent

    def _buildFileEntry(self, title, frame, kind='save', text=None, default=None):

        vFrame = self._makeButtonBox()(frame)
        self.widgetManager.log(WIDGET_NAMES.FrameBox, vFrame)

        if not self.ttkFlag:
            vFrame.config(background=self._getContainerBg())

        vFrame.theWidget = self._buildEntry(title, vFrame)
        vFrame.theWidget.inContainer = True
        vFrame.theWidget.pack(expand=True, fill=X, side=LEFT)

        if kind in ['open', "file"]:
            command = self.MAKE_FUNC(self._getFileName, title)
            vFrame.theWidget.click_command = self.MAKE_FUNC(self._checkFileName, title)
            if text is None: text = "File"
            if default is None: default = "-- enter a filename --"
        elif kind == 'save':
            command = self.MAKE_FUNC(self._getSaveName, title)
            vFrame.theWidget.click_command = self.MAKE_FUNC(self._checkSaveName, title)
            if text is None: text = "File"
            if default is None: default = "-- enter a filename --"
        else:
            command = self.MAKE_FUNC(self._getDirName, title)
            vFrame.theWidget.click_command = self.MAKE_FUNC(self._checkDirName, title)
            if text is None: text = "Directory"
            if default is None: default = "-- enter a directory --"

        self.setEntryDefault(title, default)
        vFrame.theWidget.bind("<Button-1>", vFrame.theWidget.click_command, "+")

        if not self.ttkFlag:
            vFrame.theButton = Button(vFrame, font=self._getContainerProperty('buttonFont'))
        else:
            vFrame.theButton = ttk.Button(vFrame)

        vFrame.theButton.config(text=text)
        vFrame.theButton.config(command=command)
        vFrame.theButton.pack(side=RIGHT, fill=X)
        vFrame.theButton.inContainer = True
        vFrame.theButton.SKIP_CLEANSE = True
        vFrame.theWidget.but = vFrame.theButton

        if not self.ttkFlag and self.platform in [self.MAC, self.LINUX]:
            vFrame.theButton.config(highlightbackground=self._getContainerBg())

        return vFrame

    def _buildValidationEntry(self, title, frame, secret):
        vFrame = self._makeLabelBox()(frame)
        self.widgetManager.log(WIDGET_NAMES.FrameBox, vFrame)
        vFrame.isValidation = True

        ent = self._buildEntry(title, vFrame, secret)
        if not self.ttkFlag:
            vFrame.config(background=self._getContainerBg())
            ent.config(highlightthickness=2)
        ent.pack(expand=True, fill=X, side=LEFT)
        ent.isValidation = True
        ent.inContainer = True

        class ValidationLabel(labelBase, object):
            def __init__(self, parent, *args, **options):
                super(ValidationLabel, self).__init__(parent, *args, **options)

        lab = ValidationLabel(vFrame)
        lab.pack(side=RIGHT, fill=Y)
        lab.config(font=self._getContainerProperty('labelFont'))
        if not self.ttkFlag:
            lab.config(background=self._getContainerBg())
        lab.inContainer = True
        lab.isValidation = True
        ent.lab = lab

        vFrame.theWidget = ent
        vFrame.theLabel = lab
        self.setEntryWaitingValidation(title)

        return vFrame

    def setEntryValid(self, title):
        self.setValidationEntry(title, "valid")

    def setEntryInvalid(self, title):
        self.setValidationEntry(title, "invalid")

    def setEntryWaitingValidation(self, title):
        self.setValidationEntry(title, "wait")

    def setValidationEntry(self, title, state="valid"):

        entry = self.widgetManager.get(WIDGET_NAMES.Entry, title)
        if not entry.isValidation:
            self.warn("Entry %s is not a validation entry. Unable to set WAITING VALID.", title)
            return

        if state == "wait":
            col = "#000000"
            text = '\u2731'
            eStyle="ValidationEntryWaiting.TEntry"
            lStyle="ValidationEntryWaiting.TLabel"
        elif state == "invalid":
            col = "#FF0000"
            text = '\u2716'
            eStyle="ValidationEntryInvalid.TEntry"
            lStyle="ValidationEntryInvalid.TLabel"
        elif state == "valid":
            col = "#4CC417"
            text = '\u2714'
            eStyle="ValidationEntryValid.TEntry"
            lStyle="ValidationEntryValid.TLabel"
        else:
            self.warn("Invalid validation state: %s", state)
            return

        if not self.ttkFlag:
            if not entry.showingDefault:
                entry.config(fg=col)
            entry.config(highlightbackground=col, highlightcolor=col)
            entry.config(highlightthickness=1)
            entry.lab.config(text=text, fg=col)
            entry.oldFg = col
        else:
            if not entry.showingDefault:
                entry.configure(style=eStyle)
            entry.lab.config(text=text, style=lStyle)
            entry.oldFg = eStyle

        entry.lab.DEFAULT_TEXT = entry.lab.cget("text")

    def appendAutoEntry(self, title, value):
        entry = self.widgetManager.get(WIDGET_NAMES.Entry, title)
        try:
            entry.addWords(value)
        except AttributeError:
            gui.error("You can only append items to an AutoEntry, %s is not an AutoEntry.", title)

    def removeAutoEntry(self, title, value):
        entry = self.widgetManager.get(WIDGET_NAMES.Entry, title)
        try:
            entry.removeWord(value)
        except AttributeError:
            gui.error("You can only remove items from an AutoEntry, %s is not an AutoEntry.", title)

    def changeAutoEntry(self, title, value):
        entry = self.widgetManager.get(WIDGET_NAMES.Entry, title)
        try:
            entry.changeWords(value)
        except AttributeError:
            gui.error("You can only change items in an AutoEntry, %s is not an AutoEntry.", title)

    def setAutoEntryNumRows(self, title, rows):
        entry = self.widgetManager.get(WIDGET_NAMES.Entry, title)
        try:
            entry.setNumRows(rows)
        except AttributeError:
            gui.error("You can only change the number of rows in an AutoEntry, %s is not an AutoEntry.", title)

    def _validateNumericEntry(self, action, index, value_if_allowed, prior_value, text, validation_type, trigger_type, widget_name):
        if action == "1":
            if str(text) in '0123456789.-+':
                try:
                    if len(str(value_if_allowed)) == 1 and str(value_if_allowed) in '.-':
                        return True
                    elif len(str(value_if_allowed)) == 2 and str(value_if_allowed) == '-.':
                        return True
                    else:
                        float(value_if_allowed)
                        return True
                except ValueError:
                    self.containerStack[0]['container'].bell()
                    return False
            else:
                self.containerStack[0]['container'].bell()
                return False
        else:
            return True

    def getEntry(self, name):
        entry = self.widgetManager.get(WIDGET_NAMES.Entry, name)
        if entry.showingDefault:
            if entry.isNumeric:
                return None
            else:
                return ""
        else:
            val = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS).get()
            if entry.isNumeric:
                if len(val) == 0 or (len(val) == 1 and val in '.-') or (len(val) == 2 and val == "-."):
                    return None
                else:
                    return float(val)
            else:
                return val

    def getAllEntries(self):
        entries = {}
        for k in self.widgetManager.group(WIDGET_NAMES.Entry):
            entries[k] = self.getEntry(k)
        return entries

    def setEntry(self, name, text, callFunction=True):
        ent = self.widgetManager.get(WIDGET_NAMES.Entry, name)
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
        self._updateEntryDefault(name, mode="set")

        # now call function
        with PauseCallFunction(callFunction, var, False):
            if not ent.isNumeric or self._validateNumericEntry("1", None, text, None, "1", None, None, None):
                var.set(text)

    def setEntryMaxLength(self, name, length):
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
        var.maxLength = length
        if var.ml_id is not None:
            var.trace_vdelete('w', var.ml_id)
        var.ml_id = var.trace('w', self.MAKE_FUNC(self._limitEntry, name))

    def setEntryUpperCase(self, name):
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
        if var.uc_id is not None:
            var.trace_vdelete('w', var.uc_id)
        var.uc_id = var.trace('w', self.MAKE_FUNC(self._upperEntry, name))

    def setEntryLowerCase(self, name):
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
        if var.lc_id is not None:
            var.trace_vdelete('w', var.lc_id)
        var.lc_id = var.trace('w', self.MAKE_FUNC(self._lowerEntry, name))

    def _limitEntry(self, name):
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
        if len(var.get()) > var.maxLength:
            self.containerStack[0]['container'].bell()
            var.set(var.get()[0:var.maxLength])

    def _upperEntry(self, name):
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
        chars = var.get().upper()
        var.set(chars)

    def _lowerEntry(self, name):
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
        chars = var.get().lower()
        var.set(chars)

    def _entryIn(self, name):
        self._updateEntryDefault(name, "in")

    def _entryOut(self, name):
        self._updateEntryDefault(name, "out")

    def _updateEntryDefault(self, name, mode=None):
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)
        entry = self.widgetManager.get(WIDGET_NAMES.Entry, name)

        # ignore this if no default to apply
        if entry.default == "":
            return

        # disable any limits
        if var.lc_id is not None:
            var.trace_vdelete('w', var.lc_id)
        if var.uc_id is not None:
            var.trace_vdelete('w', var.uc_id)
        if var.ml_id is not None:
            var.trace_vdelete('w', var.ml_id)

        # disable any auto completion
        if var.auto_id is not None:
            var.trace_vdelete('w', var.auto_id)

        current = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS).get()

        # disable any change function
        with PauseCallFunction(False, var, False):

            # clear & remove default
            if mode == "set" or (mode in [ "in", "clear"] and entry.showingDefault):
                var.set("")
                entry.showingDefault = False
                entry.config(justify=entry.oldJustify)
                if not self.ttkFlag:
                    entry.config(foreground=entry.oldFg)
                else:
                    entry.configure(style=entry.oldFg)
                if entry.isSecret:
                    entry.config(show="*")
            elif mode == "out" and (current == "" or entry.showingDefault):
                if entry.isSecret:
                    entry.config(show="")
                var.set(entry.default)
                entry.config(justify='center')
                if not self.ttkFlag:
                    entry.config(foreground='grey')
                else:
                    entry.configure(style="DefaultText.TEntry")

                entry.showingDefault = True
            elif mode == "update" and entry.showingDefault:
                if entry.isSecret:
                    entry.config(show="")
                var.set(entry.default)

        # re-enable any limits
        if var.lc_id is not None:
            var.lc_id = var.trace('w', self.MAKE_FUNC(self._lowerEntry, name))
        if var.uc_id is not None:
            var.uc_id = var.trace('w', self.MAKE_FUNC(self._upperEntry, name))
        if var.ml_id is not None:
            var.ml_id = var.trace('w', self.MAKE_FUNC(self._limitEntry, name))

        # re-enable auto completion
        if var.auto_id is not None:
            var.auto_id = var.trace('w', entry.textChanged)

    def setEntryDefault(self, name, text="default"):
        entry = self.widgetManager.get(WIDGET_NAMES.Entry, name)
        self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)

        # remember current settings - to return to
        if not hasattr(entry, "oldJustify"):
            entry.oldJustify = entry.cget('justify')
        if not hasattr(entry, "oldFg"):
            if not self.ttkFlag:
                entry.oldFg = entry.cget('foreground')
            else:
                entry.oldFg = entry.cget("style")

        # configure default stuff
        entry.default = text
        entry.DEFAULT_TEXT = text

        # only show new text if empty
        self._updateEntryDefault(name, "out")

        # bind commands to show/remove the default
        if hasattr(entry, "defaultInEvent"):
            entry.unbind(entry.defaultInEvent)
            entry.unbind(entry.defaultOutEvent)

        in_command = self.MAKE_FUNC(self._entryIn, name)
        out_command = self.MAKE_FUNC(self._entryOut, name)
        entry.defaultInEvent = entry.bind("<FocusIn>", in_command, add="+")
        entry.defaultOutEvent = entry.bind("<FocusOut>", out_command, add="+")

    def clearEntry(self, name, callFunction=True, setFocus=True):
        var = self.widgetManager.get(WIDGET_NAMES.Entry, name, group=WidgetManager.VARS)

        # now call function
        with PauseCallFunction(callFunction, var, False):
            var.set("")

        self._updateEntryDefault(name, mode="clear")
        if setFocus: self.setFocus(name)

    def clearAllEntries(self, callFunction=False):
        for entry in self.widgetManager.group(WIDGET_NAMES.Entry, group=WidgetManager.VARS):
            self.clearEntry(entry, callFunction=callFunction, setFocus=False)

    def setFocus(self, name):
        entry = self.widgetManager.get(WIDGET_NAMES.Entry, name)
        entry.focus_set()

    def getFocus(self):
        widg = self.topLevel.focus_get()
        return self.widgetManager.getName(widg)

####################################
## Functions to get widget details
####################################

    def _lookupValue(self, myDict, val):
        for name in myDict:
            if isinstance(myDict[name], type([])):  # array of cbs
                for rb in myDict[name]:
                    if rb == val:
                        return name
            else:
                if myDict[name] == val:
                    return name
        return None


#####################################
# FUNCTIONS for progress bars (meters)
#####################################

    def meter(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets meters all in one go """
        widgKind = WIDGET_NAMES.Meter
        kind = kwargs.pop("kind","'meter")
        fill = kwargs.pop("fill", None)
        text = kwargs.pop("text", None)

        try: self.widgetManager.verify(WIDGET_NAMES.Meter, title)
        except: # widget exists
            meter =  self.getMeter(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            if kind == "split": meter = self._addMeter(title, "SPLIT", **kwargs)
            elif kind == "dual":  meter = self._addMeter(title, "DUAL", **kwargs)
            else: meter = self._addMeter(title, "METER", **kwargs)

        if value is not None: self.setMeter(title, value, text=text)
        if fill is not None: self.setMeterFill(title, fill)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)

        return meter

    def _addMeter(self, name, kind="METER", row=None, column=0, colspan=0, rowspan=0, **kwargs):
        self.widgetManager.verify(WIDGET_NAMES.Meter, name)

        if kind == "SPLIT":
            meter = SplitMeter(self.getContainer(), font=self._getContainerProperty('labelFont'))
        elif kind == "DUAL":
            meter = DualMeter(self.getContainer(), font=self._getContainerProperty('labelFont'))
        else:
            meter = Meter(self.getContainer(), font=self._getContainerProperty('labelFont'))

        self.widgetManager.add(WIDGET_NAMES.Meter, name, meter)
        self._positionWidget(meter, row, column, colspan, rowspan)
        return meter

    def addMeter(self, name, row=None, column=0, colspan=0, rowspan=0):
        return self._addMeter(name, "METER", row, column, colspan, rowspan)

    def addSplitMeter(self, name, row=None, column=0, colspan=0, rowspan=0):
        return self._addMeter(name, "SPLIT", row, column, colspan, rowspan)

    def addDualMeter(self, name, row=None, column=0, colspan=0, rowspan=0):
        return self._addMeter(name, "DUAL", row, column, colspan, rowspan)

    # update the value of the specified meter
    # note: expects a value between 0 (-100 for split/dual) & 100
    def setMeter(self, name, value=0.0, text=None):
        item = self.widgetManager.get(WIDGET_NAMES.Meter, name)
        item.set(value, text)

    def getMeter(self, name):
        item = self.widgetManager.get(WIDGET_NAMES.Meter, name)
        return item.get()

    def getAllMeters(self):
        meters = {}
        for k in self.widgetManager.group(WIDGET_NAMES.Meter):
            meters[k] = self.getMeter(k)
        return meters

    # a single colour for meters, a list of 2 colours for splits & duals
    def setMeterFill(self, name, colour):
        item = self.widgetManager.get(WIDGET_NAMES.Meter, name)
        item.configure(fill=colour)

#####################################
# FUNCTIONS for seperators
#####################################

    def separator(self, *args, **kwargs):
        """ simpleGUI - adds horizontal/vertical separators """
        direction = kwargs.pop("direction", "horizontal").lower()
        kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)

        if direction == "vertical":
            return self.addVerticalSeparator(*args, **kwargs)
        else:
            return self.addHorizontalSeparator(*args, **kwargs)

    def addHorizontalSeparator(self, row=None, column=0, colspan=0, rowspan=0, colour=None):
        return self._addSeparator("horizontal", row, column, colspan, rowspan, colour)

    def addVerticalSeparator(self, row=None, column=0, colspan=0, rowspan=0, colour=None):
        return self._addSeparator("vertical", row, column, colspan, rowspan, colour)

    def _addSeparator(self, orient, row=None, column=0, colspan=0, rowspan=0, colour=None):
        sep = self._makeSeparator()(self.getContainer(), orient)
        if colour is not None:
            sep.configure(fg=colour)
        self.widgetManager.log(WIDGET_NAMES.Separator, sep)
        self._positionWidget(sep, row, column, colspan, rowspan)
        return sep

#####################################
# FUNCTIONS for pie charts
#####################################
    def pie(self, title, value=None, *args, **kwargs):
        """ simpleGUI - adds, sets & gets pies all in one go """
        widgKind = WIDGET_NAMES.PieChart
        name = kwargs.pop("name", None)

        try: self.widgetManager.verify(widgKind, title)
        except: # widget exists
            if name is not None: self.setPieChart(title, name, value)
            pie = self.getPieChart(title)
        else: # new widget
            kwargs = self._parsePos(kwargs.pop("pos", []), kwargs)
            pie = self.addPieChart(title, value, *args, **kwargs)

        if len(kwargs) > 0:
            self._configWidget(title, widgKind, **kwargs)
        return pie

    def addPieChart(self, name, fracs, row=None, column=0, colspan=0, rowspan=0):
        self.widgetManager.verify(WIDGET_NAMES.PieChart, name)
        self._loadTooltip()
        pie = PieChart(self.getContainer(), fracs, self._getContainerBg())
        self.widgetManager.add(WIDGET_NAMES.PieChart, name, pie)
        self._positionWidget(pie, row, column, colspan, rowspan, sticky=None)
        return pie

    def setPieChart(self, title, name, value):
        pie = self.widgetManager.get(WIDGET_NAMES.PieChart, title)
        pie.setValue(name, value)

#####################################
# FUNCTIONS for toolbar
#####################################
    # adds a list of buttons along the top - like a tool bar...
    def addToolbarButton(self, name, func, findIcon=False):
        self.addToolbar([name], func, findIcon)

    def toolbar(self, names, funcs, **kwargs):
        """ simpleGUI - shortener for toolbar """
        icons = kwargs.pop('icons', kwargs.pop('findIcon', False))
        pinned = kwargs.pop('pinned', None)
        disabled = kwargs.pop('disabled', None)
        hidden = kwargs.pop('hidden', None)
        status = kwargs.pop('status', None)

        bg = kwargs.pop('bg', None)
        if bg is not None:
            self.setToolbarBg(bg)

        self.addToolbar(names, funcs, findIcon=icons is not False)

        # allow status and icon name to be passed in a list
        for x, n in enumerate(names):
            if icons is not None:
                try: self.setToolbarIcon(n, icons[x])
                except: pass
            if status is not None:
                try: self.setToolbarButtonDisabled(n, not status[x])
                except: pass

        if pinned is not None: self.setToolbarPinned(pinned=pinned)
        if disabled is not None: self.setToolbarDisabled(disabled=disabled)
        if hidden is True: self.hideToolbar()

    def addToolbar(self, names, funcs, findIcon=False, **kwargs):
        # hide the toolbarMin bar
        if self.tb.toolbarMin is not None:
            self.tb.toolbarMin.pack_forget()
        # make sure the toolbar is showing
        try:
            self.tb.pack_info()
        except:
            self.tb.location = self.containerStack[0]['container']
            self.tb.pack(before=self.tb.location, side=TOP, fill=X)
        if not self.tb.inUse:
            self.tb.inUse = True

        image = None
        singleFunc = self._checkFunc(names, funcs)
        if not isinstance(names, list):
            names = [names]

        for i in range(len(names)):
            t = names[i]
            if (t in self.widgetManager.group(WIDGET_NAMES.Toolbar)):
                raise Exception(
                    "Invalid toolbar button name: " +
                    t +
                    " already exists")

            if findIcon:
                # turn off warnings about PNGs
                with PauseLogger():
                    imgFile = os.path.join(self.icon_path, t.lower() + ".png")
                    try:
                        image = self._getImage(imgFile)
                    except Exception as e:
                        image = None

            if not self.ttkFlag:
                but = Button(self.tb)
                but.config(relief=FLAT, font=self._buttonFont)
                if gui.GET_PLATFORM() == gui.MAC and self.tb.BG_COLOR is not None:
                    but.config(highlightbackground=self.tb.BG_COLOR)
            else:
                but = ttk.Button(self.tb)
            self.widgetManager.add(WIDGET_NAMES.Toolbar, t, but)

            if singleFunc is not None:
                u = self.MAKE_FUNC(singleFunc, t)
            else:
                u = self.MAKE_FUNC(funcs[i], t)

            but.config(command=u)
            if image is not None:
                # works on Mac & Windows :)
                but.config(image=image)
                but.image = image
                if not self.ttkFlag:
                    but.config(justify=LEFT, compound=TOP)
                else:
                    but.config(style="Toolbar.TButton")
            else:
                but.config(text=t)
            but.pack(side=LEFT, padx=2, pady=2)
            but.tt_var = self._addTooltip(but, t.title(), True)
            but.DEFAULT_TEXT=t

    def _setPinBut(self):

        # only call this once
        if self.tb.pinBut is not None:
            return

        # try to get the icon, if none - then set but to None, and ignore from now on
        imgFile = os.path.join(self.icon_path, "pin.gif")
        try:
            imgObj = self._getImage(imgFile)
            if not self.ttkFlag:
                self.tb.pinBut = Label(self.tb)
                if self.tb.BG_COLOR is not None:
                    self.tb.pinBut.config(bg=self.tb.BG_COLOR)
            else:
                self.tb.pinBut = ttk.Label(self.tb)
                self.tb.pinBut.config(style="Toolbar.TLabel")
        except:
            return

        # if image found, then set up the label
        if self.tb.pinBut is not None:
            self.tb.pinBut.config(image=imgObj)#, compound=TOP, text="", justify=LEFT)
            self.tb.pinBut.image = imgObj  # keep a reference!
            self.tb.pinBut.pack(side=RIGHT, anchor=NE, padx=0, pady=0)

            if gui.GET_PLATFORM() == gui.MAC:
                self.tb.pinBut.config(cursor="pointinghand")
            elif gui.GET_PLATFORM() in [gui.WINDOWS, gui.LINUX]:
                self.tb.pinBut.config(cursor="hand2")

            self.tb.pinBut.eventId = self.tb.pinBut.bind("<Button-1>", self._toggletb)
            self._addTooltip(self.tb.pinBut, "Click here to pin/unpin the toolbar.", True)

    # called by pinBut, to toggle the pin status of the toolbar
    def _toggletb(self, event=None):
        self.setToolbarPinned(not self.tb.pinned)

    def setToolbarPinned(self, pinned=True):
        self.tb.pinned = pinned
        self._setPinBut()
        if not self.tb.pinned:
            if self.tb.pinBut is not None:
                try:
                    self.tb.pinBut.image = self._getImage(os.path.join(self.icon_path, "unpin.gif"))
                except:
                    pass
            self.tb.makeMinBar()
            self.tb._minToolbar()
        else:
            if self.tb.pinBut is not None:
                try:
                    self.tb.pinBut.image = self._getImage(os.path.join(self.icon_path, "pin.gif"))
                except:
                    pass
            self.tb._maxToolbar()

        if self.tb.pinBut is not None:
            self.tb.pinBut.config(image=self.tb.pinBut.image)

    def setToolbarIcon(self, name, icon):
        if (name not in self.widgetManager.group(WIDGET_NAMES.Toolbar)):
            raise Exception("Unknown toolbar name: " + name)
        imgFile = os.path.join(self.icon_path, icon.lower() + ".png")
        with PauseLogger():
            self.setToolbarImage(name, imgFile)
#        self.widgetManager.get(WIDGET_NAMES.Toolbar, name).tt_var.set(icon)

    def setToolbarBg(self, bg):
        self.tb.BG_COLOR = bg
        if not self.ttkFlag:
            self.tb.config(bg=self.tb.BG_COLOR)
            if gui.GET_PLATFORM() == gui.MAC:
                for name, val in self.widgetManager.group(WIDGET_NAMES.Toolbar).items():
                    val.config(highlightbackground=self.tb.BG_COLOR)
            # config the pin button if exists
            if self.tb.pinBut is not None:
                self.tb.pinBut.config(bg=self.tb.BG_COLOR)
        else:
            self.ttkStyle.configure("Toolbar.TFrame", background=self.tb.BG_COLOR)
            self.ttkStyle.configure("Toolbar.TLabel", background=self.tb.BG_COLOR)

    def setToolbarImage(self, name, imgFile):
        if (name not in self.widgetManager.group(WIDGET_NAMES.Toolbar)):
            raise Exception("Unknown toolbar name: " + name)
        image = self._getImage(imgFile)
        self.widgetManager.get(WIDGET_NAMES.Toolbar, name).config(image=image)
        self.widgetManager.get(WIDGET_NAMES.Toolbar, name).image = image

    def removeToolbarButton(self, name, hide=True):
        if (name not in self.widgetManager.group(WIDGET_NAMES.Toolbar)):
            raise Exception("Unknown toolbar name: " + name)
        self.widgetManager.get(WIDGET_NAMES.Toolbar, name).destroy()
        self.widgetManager.remove(WIDGET_NAMES.Toolbar, name)
        if hide:
            if len(self.widgetManager.group(WIDGET_NAMES.Toolbar)) == 0:
                self.tb.pack_forget()
                self.tb.inUse = False
            if self.tb.toolbarMin is not None:
                self.tb.toolbarMin.pack_forget()

    def removeToolbar(self, hide=True):
        while len(self.widgetManager.group(WIDGET_NAMES.Toolbar)) > 0:
            self.removeToolbarButton(list(self.widgetManager.group(WIDGET_NAMES.Toolbar))[0], hide)

    def setToolbarButtonEnabled(self, name):
        self.setToolbarButtonDisabled(name, False)

    def setToolbarButtonDisabled(self, name, disabled=True):
        if (name not in self.widgetManager.group(WIDGET_NAMES.Toolbar)):
            raise Exception("Unknown toolbar name: " + name)
        if disabled:
            self.widgetManager.get(WIDGET_NAMES.Toolbar, name).config(state=DISABLED)
        else:
            self.widgetManager.get(WIDGET_NAMES.Toolbar, name).config(state=NORMAL)

    def setToolbarEnabled(self):
        self.setToolbarDisabled(False)

    def setToolbarDisabled(self, disabled=True):
        for but in self.widgetManager.group(WIDGET_NAMES.Toolbar).keys():
            if disabled:
                self.widgetManager.get(WIDGET_NAMES.Toolbar, but).config(state=DISABLED)
            else:
                self.widgetManager.get(WIDGET_NAMES.Toolbar, but).config(state=NORMAL)

        if self.tb.pinBut is not None:
            if disabled:
                # this fails if not bound
                if self.tb.pinBut.eventId:
                    self.tb.pinBut.unbind("<Button-1>", self.tb.pinBut.eventId)
                self.tb.pinBut.eventId = None
                self._disableTooltip(self.tb.pinBut)
                self.tb.pinBut.config(cursor="")
            else:
                if gui.GET_PLATFORM() == gui.MAC:
                    self.tb.pinBut.config(cursor="pointinghand")
                elif gui.GET_PLATFORM() in [gui.WINDOWS, gui.LINUX]:
                    self.tb.pinBut.config(cursor="hand2")

                self.tb.pinBut.eventId = self.tb.pinBut.bind("<Button-1>", self._toggletb)
                self._enableTooltip(self.tb.pinBut)

    # functions to hide & show the toolbar
    def hideToolbar(self):
        self.tb.hide()

    def showToolbar(self):
        self.tb.show()

    # Method to get all inputs.
    def getAllInputs(self, **kwargs):
        """Get all values, merge & return as a single dictionary.

        :param kwargs: will be _appended_ to the input list.

        Note, empty pairs from each input is stripped, existing keys
        will not be overridden!
        """

        # used to stop removal of empty inputs
        includeEmptyInputs = kwargs.pop('includeEmptyInputs', False)

        # All available inputs.
        inputs = filter(None, [
                  self.getAllEntries(),
                  self.getAllOptionBoxes(),
                  self.getAllSpinBoxes(),
                  self.getAllListBoxes(),
                  self.getAllProperties(),
                  self.getAllCheckBoxes(),
                  self.getAllRadioButtons(),
                  self.getAllScales(),
                  self.getAllMeters(),
                  self.getAllDatePickers(),
                  kwargs,
        ])
        result = data = dict()
        for pairs in inputs:
            for key, val in pairs.items():
                # Try and strip values.
                try:
                    val = val.strip()
                except AttributeError:
                    pass
                try:
                    # Skip if value is empty or if key already exists.
                    if (not includeEmptyInputs and not val) or result[key]:
                        continue
                except KeyError:
                    pass
                result[key] = val
        return result

#####################################
# FUNCTIONS for menu bar
#####################################
    def _initMenu(self):
        # create a menu bar - only shows if populated
        if not self.hasMenu:
            #            self.topLevel.option_add('*tearOff', FALSE)
            self.hasMenu = True
            self.menuBar = Menu(self.topLevel)
            if self.platform == self.MAC:
                appmenu = Menu(self.menuBar, name='apple')
                self.menuBar.add_cascade(menu=appmenu)
                self.widgetManager.add(WIDGET_NAMES.Menu, "MAC_APP", appmenu)
            elif self.platform == self.WINDOWS:
                # sysMenu must be added last, otherwise other menus vanish
                sysMenu = Menu(self.menuBar, name='system', tearoff=False)
                self.widgetManager.add(WIDGET_NAMES.Menu, "WIN_SYS", sysMenu)

    # add a parent menu, for menu items
    def createMenu(self, title, tearable=False, showInBar=True):
        self.widgetManager.verify(WIDGET_NAMES.Menu, title)
        self._initMenu()

        if title == "WIN_SYS" and self.platform != self.WINDOWS:
            self.warn("The WIN_SYS menu is specific to Windows")
            return None

        if self.platform == self.MAC and tearable:
            self.warn("Tearable menus (%s) not supported on MAC", title)
            tearable = False
        theMenu = Menu(self.menuBar, tearoff=tearable)
        if showInBar:
            self.menuBar.add_cascade(label=title, menu=theMenu)
        self.widgetManager.add(WIDGET_NAMES.Menu, title, theMenu)
        return theMenu

    def createRightClickMenu(self, title, showInBar=False):
        men = self.createMenu(title, False, showInBar)
        men.bind("<FocusOut>", lambda e: men.unpost())
        return men

    def _bindRightClick(self, item, value):
        if self.platform in [self.WINDOWS, self.LINUX]:
            item.bind('<Button-3>', lambda e, menu=value: self._rightClick(e, menu))
        else:
            item.bind('<Button-2>', lambda e, menu=value: self._rightClick(e, menu))

    # add items to the named menu
    def addMenuItem(self, title, item, func=None, kind=None, shortcut=None, underline=-1, rb_id=None, createBinding=True):
        # set the initial menubar
        self._initMenu()

        # get or create an initial menu
        if title is not None:
            try:
                theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, title)
            except:
                theMenu = self.createMenu(title)
                if theMenu is None:
                    gui.warn('Unable to create menu: %s', title)
                    return

        if underline > -1 and self.platform == self.MAC:
            gui.warn("Underlining menu items not available on MAC")

        if func is not None:
            func = self.MAKE_FUNC(func, item)

        acc = None

        if shortcut is not None:
            if kind == 'cb':
                f = lambda e: self._menuCheckButtonBind(title, item, func)
                binding = EventBinding(shortcut, f, self._getTopLevel(), menuBinding=True)
            else:
                binding = EventBinding(shortcut, func, self._getTopLevel(), menuBinding=True)

            try:
                self.widgetManager.add(WIDGET_NAMES.Bindings, binding.displayName, binding)
                if createBinding: binding.createBindings()
                acc = binding.displayName
            except ItemLookupError:
                raise ItemLookupError('Unable to bind menu ' + item + ' to ' + binding.displayName + ' - binding already exists')

        # now, let's create the actual menu item
        if item == "-" or kind == "separator":
            theMenu.add_separator()
        elif kind == "topLevel" or title is None:
            if self.platform == self.MAC:
                self.warn("Unable to make topLevel menus (%s) on Mac", item)
            else:
                self.menuBar.add_command(
                    label=item, command=func, accelerator=acc, underline=underline)
        elif kind == "rb":
            varName = title + "rb" + item
            newRb = False
            if (varName in self.widgetManager.group(WIDGET_NAMES.Menu, group=WidgetManager.VARS)):
                var = self.widgetManager.get(WIDGET_NAMES.Menu, varName, group=WidgetManager.VARS)
            else:
                newRb = True
                var = StringVar(self.topLevel)
                self.widgetManager.add(WIDGET_NAMES.Menu, varName, var, group=WidgetManager.VARS)
            theMenu.add_radiobutton(label=rb_id, command=func, variable=var, value=rb_id, accelerator=acc, underline=underline)
            if newRb:
                self.setMenuRadioButton(title, item, rb_id)
        elif kind == "cb":
            varName = title + "cb" + item
            self.widgetManager.verify(WIDGET_NAMES.Menu, varName, group=WidgetManager.VARS)
            var = BooleanVar(self.topLevel)
            var.set(False)
            self.widgetManager.add(WIDGET_NAMES.Menu, varName, var, group=WidgetManager.VARS)
            theMenu.add_checkbutton(label=item, command=func, variable=var, onvalue=True, offvalue=False, accelerator=acc, underline=underline)
        elif kind == "sub":
            self.widgetManager.verify(WIDGET_NAMES.Menu, item)
            subMenu = Menu(theMenu, tearoff=False)
            self.widgetManager.add(WIDGET_NAMES.Menu, item, subMenu)
            theMenu.add_cascade(label=item, menu=subMenu)
        else:
            theMenu.add_command(label=item, command=func, accelerator=acc, underline=underline)

    # used to wrap check button bindings, so can also toggle
    def _menuCheckButtonBind(self, title, item, func):
        self.setMenuCheckBox(title, item)
        func(item)

    #################
    # wrappers for other menu types

    def addMenuList(self, menuName, names, funcs):
        # deal with a dict_keys object - messy!!!!
        if not isinstance(names, list):
            names = list(names)

        # append some Nones, if it's a list and contains separators
        if funcs is not None:
            if not callable(funcs):
                seps = names.count("-")
                for i in range(seps):
                    funcs.append(None)
            singleFunc = self._checkFunc(names, funcs)

        # add menu items
        for t in names:
            if funcs is None:
                u = None
            elif singleFunc is not None:
                u = singleFunc
            else:
                u = funcs.pop(0)

            self.addMenuItem(menuName, t, u)

    def _prepareCopyAndPasteMenu(self, event, widget=None):
        if self.copyAndPaste.inUse:
            if event is not None:
                widget = event.widget
            self._changeMenuState(self.widgetManager.get(WIDGET_NAMES.Menu, "EDIT"), DISABLED, 'Disabling', 10)
            self.copyAndPaste.setUp(widget)
            if self.copyAndPaste.canCopy:
                self.enableMenuItem("EDIT", "Copy")
            if self.copyAndPaste.canCut:
                self.enableMenuItem("EDIT", "Cut")
            if self.copyAndPaste.canPaste:
                self.enableMenuItem("EDIT", "Paste")
                self.enableMenuItem("EDIT", "Clear Clipboard")
            if self.copyAndPaste.canSelect:
                self.enableMenuItem("EDIT", "Select All")
                self.enableMenuItem("EDIT", "Clear All")
            if self.copyAndPaste.canUndo:
                self.enableMenuItem("EDIT", "Undo")
            if self.copyAndPaste.canRedo:
                self.enableMenuItem("EDIT", "Redo")
            if self.copyAndPaste.canFont:
                self.enableMenuItem("EDIT", "Bold")
                self.enableMenuItem("EDIT", "Italic")
                self.enableMenuItem("EDIT", "Bold & Italic")
                self.enableMenuItem("EDIT", "Underline")
            return True
        else:
            return False

    # called when copy/paste menu items are clicked
    def _copyAndPasteHelper(self, menu):
        if menu == "Cut":
            self.copyAndPaste.cut()
        elif menu == "Copy":
            self.copyAndPaste.copy()
        elif menu == "Paste":
            self.copyAndPaste.paste()
        elif menu == "Select All":
            self.copyAndPaste.selectAll()
        elif menu == "Clear Clipboard":
            self.copyAndPaste.clearClipboard()
        elif menu == "Clear All":
            self.copyAndPaste.clearText()
        elif menu == "Undo":
            self.copyAndPaste.undo()
        elif menu == "Redo":
            self.copyAndPaste.redo()
        elif menu in ["BOLD", "ITALIC", "UNDERLINE", "BOLD_ITALIC"]:
            self.copyAndPaste.font("AJ_"+menu)

    # add a single entry for a menu
    def addSubMenu(self, menu, subMenu):
        self.addMenuItem(menu, subMenu, func=None, kind="sub")

    def addMenu(self, name, func, shortcut=None, underline=-1):
        self.addMenuItem(None, name, func=func, kind="topLevel", shortcut=shortcut, underline=underline)

    def addMenuSeparator(self, menu):
        self.addMenuItem(menu, "-")

    def addMenuCheckBox(self, menu, name, func=None, shortcut=None, underline=-1):
        self.addMenuItem(menu, name, func, "cb", shortcut, underline)

    def addMenuRadioButton(self, menu, name, value, func=None, shortcut=None, underline=-1):
        self.addMenuItem(menu, name, func, "rb", shortcut, underline, value)

    def menu(self, menu, name=None, func=None, **kwargs):
        # kind: menu, sub, button, sep, check/tick, radio
        kind = kwargs.pop('kind', 'button')
        group = kwargs.pop('group', None)
        shortcut = kwargs.pop('shortcut', None)
        underline = kwargs.pop('underline', -1)

        tear = kwargs.pop('tear', False)
        state = kwargs.pop('state', None)
        image = kwargs.pop('image', None)
        icon = kwargs.pop('icon', None)
        align = kwargs.pop('align', 'left')

        if kind == 'menu':
            self.createMenu(menu, tearable=tear, showInBar=True)
        elif kind.startswith('sub'):
            self.addSubMenu(menu, name)
        elif kind.startswith('radio') or group is not None:
            self.addMenuRadioButton(menu, group, value=name, func=func, shortcut=shortcut, underline=underline)
        elif kind == 'button':
            if name is None and func is not None:
                self.addMenu(menu, func=func, shortcut=shortcut, underline=underline)
            elif name is None:
                self.createMenu(menu, tearable=tear, showInBar=True)
            elif isinstance(name, (list, tuple)):
                self.addMenuList(menu, name, func)
            else:
                self.addMenuItem(menu, name, func=func, kind=None, shortcut=shortcut, underline=underline)
        elif kind.startswith('sep'):
            self.addMenuSeparator(menu)
        elif kind.startswith('check') or kind.startswith('tick'):
            self.addMenuCheckBox(menu, name, func=func, shortcut=shortcut, underline=underline)

        if state is not None:
            if kind == 'menu' or kind.startswith('sub'):
                if state == 'disabled': self.disableMenu(menu, name)
                elif state == 'enabled': self.enableMenu(menu, name)
            else:
                if state == 'disabled': self.disableMenuItem(menu, name)
                elif state == 'enabled': self.enableMenuItem(menu, name)

        if image is not None: self.setMenuImage(menu, name, image, align=align)
        if icon is not None: self.setMenuIcon(menu, name, icon, align=align)

    #################
    # wrappers for setters

    def _setMenu(self, menu, title, value, kind):
        title = menu + kind + title
        var = self.widgetManager.get(WIDGET_NAMES.Menu, title, group=WidgetManager.VARS)
        if kind == "rb":
            var.set(value)
        elif kind == "cb":
            if value is None:
                var.set(not var.get())
            else:
                var.set(value)

    def setMenuCheckBox(self, menu, name, value=None):
        self._setMenu(menu, name, value, "cb")

    def setMenuRadioButton(self, menu, name, value):
        self._setMenu(menu, name, value, "rb")

    # set align = "none" to remove text
    def setMenuImage(self, menu, title, image, align="left"):
        theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, menu)
        imageObj = self._getImage(image)
        if 16 != imageObj.width() or imageObj.width() != imageObj.height():
            self.warn("Invalid image resolution for menu item %s (%s) - should be 16x16", title, image)
            #imageObj = imageObj.subsample(2,2)
        try: theMenu.entryconfigure(title, image=imageObj, compound=align)
        except TclError: gui.error("Unable to set image for menu item: %s, in menu: %s - item not found", title, menu)

    def setMenuIcon(self, menu, title, icon, align="left"):
        image = os.path.join(self.icon_path, icon.lower() + ".png")
        with PauseLogger():
            self.setMenuImage(menu, title, image, align)

    def disableMenubar(self):
        gui.trace('Disabling toplevel menubar')
        self._disableMenu(self.menuBar)

    def enableMenubar(self):
        gui.trace('Enabling toplevel menubar')
        self._enableMenu(self.menuBar)

    def disableMenu(self, title):
        gui.trace('Disabling submenu: %s', title)
        theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, title)
        self._disableMenu(theMenu)

    def enableMenu(self, title):
        gui.trace('Enabling submenu: %s', title)
        theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, title)
        self._enableMenu(theMenu)

    def disableMenuItem(self, title, item):
        theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, title)
        try:
            gui.trace("Disabling menu item: %s, in menu: %s", item, title)
            self._changeMenuItemState(theMenu, item, DISABLED) 
        except TclError:
            gui.error("Unable to disable menu item: %s, in menu: %s - item not found", item, title)

    def enableMenuItem(self, title, item):
        theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, title)
        try:
            gui.trace("Enabling menu item: %s, in menu: %s", item, title)
            self._changeMenuItemState(theMenu, item, NORMAL) 
        except TclError:
            gui.error("Unable to enable menu item: %s, in menu: %s - item not found", item, title)

    def _disableMenu(self, menu): self._changeMenuState(menu, DISABLED, 'Disabling')
    def _enableMenu(self, menu): self._changeMenuState(menu, NORMAL, 'Enabling')

    def _changeMenuState(self, menu, state, text, limit=None):
        # changes the specified menu object's state to the new state, using the specified text for logging
        numMenus = menu.index("end")
        if numMenus is not None: # MAC_APP (and others?) returns None
            for item in range(numMenus+1):
                # we can cut-off early if requested internally
                if limit is not None and limit == item:
                    break
                if menu.type(item) == 'cascade':
                    label = menu.entrycget(item, 'label')
                    if len(label) == 0: label = 'SPECIAL MENU'
                    gui.trace('%s submenu: %s', text, label)
                    subMenu = self.topLevel.nametowidget(menu.entrycget(item, 'menu'))
                    self._changeMenuState(subMenu, state, text)
                    menu.entryconfig(item, state=state)
                elif menu.type(item) in ['separator', 'tearoff']:
                    gui.trace('Skipping separator/tearoff')
                else:
                    label = menu.entrycget(item, 'label')
                    gui.trace('%s item: %s', text, label)
                    # use the position - if the label is a number it breaks...
                    self._changeMenuItemState(menu, item, state)

    def _changeMenuItemState(self, menu, item, state):
        menu.entryconfigure(item, state=state)
        bindingName = menu.entrycget(item, 'accelerator')
        if bindingName is not None and len(bindingName) > 0:
            self.widgetManager.get(WIDGET_NAMES.Bindings, bindingName).changeBindings(state)

    def renameMenu(self, title, newName):
        theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, title)
        try:
            self.menuBar.entryconfigure(title, label=newName)
        except TclError:
            gui.error("Unable to rename menu: %s - item not found", title)

    def renameMenuItem(self, title, item, newName):
        theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, title)
        try:
            theMenu.entryconfigure(item, label=newName)
        except TclError:
            gui.error("Unable to rename menu item: %s, in menu: %s - item not found", item, title)

    def deleteMenuItem(self, title, item):
        theMenu = self.widgetManager.get(WIDGET_NAMES.Menu, title)
        try:
            bindingName = theMenu.entrycget(item, 'accelerator')
            theMenu.delete(item)
            self.widgetManager.get(WIDGET_NAMES.Bindings, bindingName).removeBindings()
            self.widgetManager.remove(WIDGET_NAMES.Bindings, bindingName)
        except TclError:
            gui.error("Unable to delete menu item: %s, in menu: %s - item not found", item, title)

    #################
    # wrappers for getters

    def _getMenu(self, menu, title, kind):
        title = menu + kind + title
        var = self.widgetManager.get(WIDGET_NAMES.Menu, title, group=WidgetManager.VARS)
        if kind == 'rb': return var.get()
        else:
            if var.get(): return True
            else: return False

    def getMenuCheckBox(self, menu, title):
        return self._getMenu(menu, title, "cb")

    def getMenuRadioButton(self, menu, title):
        return self._getMenu(menu, title, "rb")

    #################
    # wrappers for platform specific menus

    # enables the preferences item in the app menu
    def addMenuPreferences(self, func):
        if self.platform == self.MAC:
            self._initMenu()
            u = self.MAKE_FUNC(func, "preferences")
            self.topLevel.createcommand('tk::mac::ShowPreferences', u)
        else:
            self.warn("The Preferences Menu is specific to Mac OSX")

    # MAC help menu
    def addMenuHelp(self, func):
        if self.platform == self.MAC:
            self._initMenu()
            helpMenu = Menu(self.menuBar, name='help')
            self.menuBar.add_cascade(menu=helpMenu, label='Help')
            u = self.MAKE_FUNC(func, "help")
            self.topLevel.createcommand('tk::mac::ShowHelp', u)
            self.widgetManager.add(WIDGET_NAMES.Menu, "MAC_HELP", helpMenu)
        else:
            self.warn("The Help Menu is specific to Mac OSX")

    # Shows a Window menu
    def addMenuWindow(self):
        if self.platform == self.MAC:
            self._initMenu()
            windowMenu = Menu(self.menuBar, name='window')
            self.menuBar.add_cascade(menu=windowMenu, label='Window')
            self.widgetManager.add(WIDGET_NAMES.Menu, "MAC_WIN", windowMenu)
        else:
            self.warn("The Window Menu is specific to Mac OSX")

    def disableMenuEdit(self):
        self.copyAndPaste.inUse = False

    # adds an edit menu - by default only as a pop-up
    # if inMenuBar is True - then show in menu too
    def addMenuEdit(self, inMenuBar=False):
        self._initMenu()
        self.copyAndPaste.inUse = True

        # in case we already made the menu - just return
        try: self.widgetManager.verify(WIDGET_NAMES.Menu, "EDIT")
        except: return

        editMenu = Menu(self.menuBar, tearoff=False)
        editMenu.bind("<FocusOut>", lambda e: editMenu.unpost())
        if inMenuBar:
            self.menuBar.add_cascade(menu=editMenu, label='Edit ')
        self.widgetManager.add(WIDGET_NAMES.Menu, "EDIT", editMenu)

        if gui.GET_PLATFORM() == gui.MAC:
            shortcut = "Command-"
        else:
            shortcut = "Control-"

        eList = [
            ('Cut', lambda e: self._copyAndPasteHelper("Cut"), "X", False),
            ('Copy', lambda e: self._copyAndPasteHelper("Copy"), "C", False),
            ('Paste', lambda e: self._copyAndPasteHelper("Paste"), "V", False),
            ('Select All', lambda e: self._copyAndPasteHelper("Select All"), "A", True if gui.GET_PLATFORM() == gui.MAC else False),
            ('Clear Clipboard', lambda e: self._copyAndPasteHelper("Clear Clipboard"), None, False)
            ]

        for (txt, cmd, sc, bind) in eList:
            acc = None if sc is None else shortcut + sc
            self.addMenuItem("EDIT", txt, cmd, shortcut=acc, createBinding=bind)

        # add a clear option
        self.addMenuSeparator("EDIT")
        self.addMenuItem("EDIT", "Clear All", lambda e: self._copyAndPasteHelper("Clear All"))

        self.addMenuSeparator("EDIT")
        self.addMenuItem("EDIT", 'Undo', lambda e: self._copyAndPasteHelper("Undo"), shortcut=shortcut + "Z", createBinding=False)
        self.addMenuItem("EDIT", 'Redo', lambda e: self._copyAndPasteHelper( "Redo"), shortcut=shortcut+"Shift-Z", createBinding=True)

        self.addMenuSeparator("EDIT")
        self.addMenuItem("EDIT", "Bold", lambda e: self._copyAndPasteHelper("BOLD"), shortcut=shortcut+"B")
        self.addMenuItem("EDIT", "Italic", lambda e: self._copyAndPasteHelper("ITALIC"), shortcut=shortcut+"I")
        self.addMenuItem("EDIT", "Underline", lambda e: self._copyAndPasteHelper("UNDERLINE"), shortcut=shortcut+"U")
        self.addMenuItem("EDIT", "Bold & Italic", lambda e: self._copyAndPasteHelper("BOLD_ITALIC"), shortcut=shortcut+"Shift-B")

        self.disableMenu("EDIT")

    def _editMenuSetter(self, enabled=True):
        if enabled:
            self.addMenuEdit()
        else:
            self.disableMenuEdit()

    def _editMenuGetter(self):
        return self.copyAndPaste.inUse

    editMenu = property(_editMenuGetter, _editMenuSetter)

    def appJarAbout(self, menu=None):
        self.infoBox("About appJar",
                        "---\n" +
                        __copyright__ + "\n" +
                        "---\n\t" +
                        gui.SHOW_VERSION().replace("\n", "\n\t") + "\n" +
                        "---\n" +
                        gui.SHOW_PATHS() + "\n" +
                        "---")

    def appJarHelp(self, menu=None):
        self.infoBox("appJar Help", "For help, visit " + __url__)

    def addAppJarMenu(self):
        if self.platform == self.MAC:
            self.addMenuItem("MAC_APP", "About appJar", self.appJarAbout)
            self.addMenuWindow()
            self.addMenuHelp(self.appJarHelp)
        elif self.platform == self.WINDOWS:
            self.addMenuSeparator('WIN_SYS')
            self.addMenuItem("WIN_SYS", "About appJar", self.appJarAbout)
            self.addMenuItem("WIN_SYS", "appJar Help", self.appJarHelp)

#####################################
# FUNCTIONS for status bar
#####################################

    def removeStatusbarField(self, field):
        if self.hasStatus and field < len(self._statusFields):
            self._statusFields[field].pack_forget()
            self._statusFields[field].destroy()
            del self._statusFields[field]
        else:
            raise ItemLookupError("Invalid field number for statusbar: " + str(field))

    def removeStatusbar(self):
        if self.hasStatus:
            while len(self._statusFields) > 0:
                self.removeStatusbarField(0)

            self.statusFrame.pack_forget()
            self.statusFrame.destroy()

            self.hasStatus = False
            self.header = ""

    def status(self, *args, **kwargs):
        self.statusbar(*args, **kwargs)

    def statusbar(self, *args, **kwargs):
        """ simpleGUI - shortener for statusbar """
        bg = kwargs.pop('bg', None)
        fg = kwargs.pop('fg', None)
        width = kwargs.pop('width', None)

        text = kwargs.pop('text', "")
        header = kwargs.pop('header', None)
        fields = kwargs.pop('fields', 1)
        field = kwargs.pop('field', 0)
        side = kwargs.pop('side', None)

        if not self.hasStatus:
            self.addStatusbar(header=header, fields=fields, side=side)
            self.setStatusbar(text=text)
        else:
            if len(args) > 0: text = args[0]
            if len(args) > 1: field = args[1]

            if header is not None: self.setStatusbarHeader(header)
            self.setStatusbar(text=text, field=field)

        if bg is not None: self.setStatusbarBg(bg)
        if fg is not None: self.setStatusbarFg(fg)
        if width is not None: self.setStatusbarWidth(width)

    def addStatusbar(self, header="", fields=1, side=None):
        if not self.hasStatus:
            class Statusbar(Frame, object):
                def __init__(self, master, **kwargs):
                    super(Statusbar, self).__init__(master, **kwargs)

            self.hasStatus = True
            self.header = header
            self.statusFrame = Statusbar(self.appWindow)
            self.statusFrame.config(bd=1, relief=SUNKEN)
            self.statusFrame.pack(side=BOTTOM, fill=X, anchor=S)

            self._statusFields = []
            for i in range(fields):
                self._statusFields.append(Label(self.statusFrame))
                self._statusFields[i].config(
                    bd=1,
                    relief=SUNKEN,
                    anchor=W,
                    font=self._statusFont,
                    width=10)
                self._addTooltip(self._statusFields[i], "Status bar", True)

                if side == "LEFT":
                    self._statusFields[i].pack(side=LEFT)
                elif side == "RIGHT":
                    self._statusFields[i].pack(side=RIGHT)
                else:
                    self._statusFields[i].pack(side=LEFT, expand=1, fill=BOTH)
        else:
            self.error("Statusbar already exists - ignoring")

    def setStatusbarHeader(self, header):
        if self.hasStatus:
            self.header = header

    def setStatusbar(self, text, field=0):
        if self.hasStatus:
            if field is None:
                for status in self._statusFields:
                    status.config(text=self._getFormatStatus(text))
            elif field >= 0 and field < len(self._statusFields):
                self._statusFields[field].config(text=self._getFormatStatus(text))
            else:
                raise Exception("Invalid status field: " + str(field) +
                                ". Must be between 0 and " + str(len(self._statusFields) - 1))

    def setStatusbarBg(self, colour, field=None):
        if self.hasStatus:
            if field is None:
                for status in self._statusFields:
                    status.config(background=colour)
            elif field >= 0 and field < len(self._statusFields):
                self._statusFields[field].config(background=colour)
            else:
                raise Exception("Invalid status field: " + str(field) +
                                ". Must be between 0 and " + str(len(self._statusFields) - 1))

    def setStatusbarFg(self, colour, field=None):
        if self.hasStatus:
            if field is None:
                for status in self._statusFields:
                    status.config(foreground=colour)
            elif field >= 0 and field < len(self._statusFields):
                self._statusFields[field].config(foreground=colour)
            else:
                raise Exception("Invalid status field: " + str(field) +
                                ". Must be between 0 and " + str(len(self._statusFields) - 1))

    def setStatusbarWidth(self, width, field=None):
        if self.hasStatus:
            if field is None:
                for status in self._statusFields:
                    status.config(width=width)
            elif field >= 0 and field < len(self._statusFields):
                self._statusFields[field].config(width=width)
            else:
                raise Exception("Invalid status field: " + str(field) +
                                ". Must be between 0 and " + str(len(self._statusFields) - 1))

    def clearStatusbar(self, field=None):
        if self.hasStatus:
            if field is None:
                for status in self._statusFields:
                    status.config(text=self._getFormatStatus(""))
            elif field >= 0 and field < len(self._statusFields):
                self._statusFields[field].config(text=self._getFormatStatus(""))
            else:
                raise Exception("Invalid status field: " + str(field) +
                                ". Must be between 0 and " + str(len(self._statusFields) - 1))

    # formats the string shown in the status bar
    def _getFormatStatus(self, text):
        text = str(text)
        if len(text) == 0:
            return ""
        elif self.header is None or len(self.header) == 0:
            return text
        else:
            return self.header + ": " + text

#####################################
# TOOLTIPS
#####################################

    def _addTooltip(self, item, text, hideWarn=False):
        self._loadTooltip()
        if not ToolTip:
            if not hideWarn:
                self.warn("ToolTips unavailable - check tooltip.py is in the lib folder")
        elif text == "":
            self._disableTooltip(item)
        else:
            # turn off warnings about tooltips
            with PauseLogger():
                # if there's already  tt, just change it
                if hasattr(item, "tt_var"):
                    item.tt_var.set(text)
                # otherwise create one
                else:
                    var = StringVar(self.topLevel)
                    var.set(text)
                    tip = ToolTip(item, delay=500, follow_mouse=1, textvariable=var)
                    item.tooltip = tip
                    item.tt_var = var

            return item.tt_var

    def _enableTooltip(self, item):
        if hasattr(item, "tooltip"):
            item.tooltip.configure(state="normal")
        else:
            self.warn("Unable to enable tooltip - none present.")

    def _disableTooltip(self, item):
        if hasattr(item, "tooltip"):
            item.tooltip.configure(state="disabled")
        else:
            self.warn("Unable to disable tooltip - none present.")

#####################################
# FUNCTIONS to show pop-up dialogs
#####################################

    def popUp(self, title, message=None, kind="info", parent=None):
        """ simpleGUI - shortener for the various popUps """

        if message is None:
            message = title
            title = kind.capitalize() + " Dialog"

        if kind == "info": return self.infoBox(title, message, parent)
        elif kind == "error": return self.errorBox(title, message, parent)
        elif kind == "warning": return self.warningBox(title, message, parent)
        elif kind == "yesno": return self.yesNoBox(title, message, parent)
        elif kind == "question": return self.questionBox(title, message, parent)
        elif kind == "ok": return self.okBox(title, message, parent)
        elif kind == "retry": return self.retryBox(title, message, parent)
        elif kind == "string": return self.stringBox(title, message, parent)
        elif kind == "integer": return self.integerBox(title, message, parent)
        elif kind == "float": return self.floatBox(title, message, parent)
        elif kind == "text": return self.textBox(title, message, parent)
        elif kind == "number": return self.numberBox(title, message, parent)
        else: gui.error("Invalid popUp kind: %s, with title: %s", kind, title)

    def prompt(self, title, message, kind="string", parent=None):
        return self.popUp(title, message, kind, parent)

    # function to access the last made pop_up
    def getPopUp(self):
        return self.topLevel.POP_UP

    def infoBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            MessageBox.showinfo(title, message)
            if self.topLevel.displayed:
                self._bringToFront()
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            MessageBox.showinfo(title, message, **opts)
            self._bringToFront(parent)


    def errorBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            MessageBox.showerror(title, message)
            if self.topLevel.displayed:
                self._bringToFront()
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            MessageBox.showerror(title, message, **opts)
            self._bringToFront(parent)

    def warningBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            MessageBox.showwarning(title, message)
            if self.topLevel.displayed:
                self._bringToFront()
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            MessageBox.showwarning(title, message, **opts)
            self._bringToFront(parent)

    def yesNoBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            return MessageBox.askyesno(title, message)
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            return MessageBox.askyesno(title=title, message=message, **opts)

    def stringBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            return SimpleDialog.askstring(title, message)
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            return SimpleDialog.askstring(title=title, message=message, **opts)

    def integerBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            return SimpleDialog.askinteger(title, message)
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            return SimpleDialog.askinteger(title=title, message=message, **opts)

    def floatBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            return SimpleDialog.askfloat(title, message)
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            return SimpleDialog.askfloat(title=title, message=message, **opts)

    def questionBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            return True if MessageBox.askquestion(title, message).lower() == "yes" else False
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            return True if MessageBox.askquestion(title, message, **opts).lower() == "yes" else False

    def okBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        title, message = self._translatePopup(title, message)
        if parent is None:
            return MessageBox.askokcancel(title, message)
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            return MessageBox.askokcancel(title, message, **opts)

    def retryBox(self, title, message, parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            return MessageBox.askretrycancel(title, message)
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            return MessageBox.askretrycancel(title, message, **opts)

    def openBox(self, title=None, dirName=None, fileTypes=None, asFile=False, parent=None, multiple=False, mode='r'):

        self.topLevel.update_idletasks()

        # define options for opening
        options = {}

        if title is not None:
            options['title'] = title
        if dirName is not None:
            options['initialdir'] = dirName
        if fileTypes is not None:
            options['filetypes'] = fileTypes
        if parent is not None:
            options["parent"] = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)

        if asFile:
            options["mode"] = mode
            if multiple: files = list(filedialog.askopenfiles(**options))
            else: files = filedialog.askopenfile(**options)

            return files
        # will return "" if cancelled
        else:
            if multiple: files = list(self.topLevel.tk.splitlist(filedialog.askopenfilenames(**options)))
            else: files = filedialog.askopenfilename(**options)

            return files

    def saveBox( self, title=None, fileName=None, dirName=None, fileExt=".txt",
            fileTypes=None, asFile=False, parent=None):
        self.topLevel.update_idletasks()
        if fileTypes is None:
            fileTypes = [('all files', '.*'), ('text files', '.txt')]
        # define options for opening
        options = {}
        options['defaultextension'] = fileExt
        options['filetypes'] = fileTypes
        options['initialdir'] = dirName
        options['initialfile'] = fileName
        options['title'] = title
        if parent is not None:
            options["parent"] = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)

        if asFile:
            return filedialog.asksaveasfile(mode='w', **options)
        # will return "" if cancelled
        else:
            return filedialog.asksaveasfilename(**options)

    def directoryBox(self, title=None, dirName=None, parent=None):
        self.topLevel.update_idletasks()
        options = {}
        options['initialdir'] = dirName
        options['title'] = title
        options['mustexist'] = False
        if parent is not None:
            options["parent"] = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)

        fileName = filedialog.askdirectory(**options)

        if fileName == "":
            return None
        else:
            return fileName

    def colourBox(self, colour='#ff0000', parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            col = askcolor(colour)
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)
            opts = {"parent": parent}
            col = askcolor(colour, **opts)

        if col[1] is None:
            return None
        else:
            return col[1]

    def textBox(self, title="Text Box", question="Enter text", defaultValue=None, parent=None):
        self.topLevel.update_idletasks()
        if defaultValue is not None:
            defaultVar = StringVar(self.topLevel)
            defaultVar.set(defaultValue)
        else:
            defaultVar = None
        if parent is None:
            parent = self.topLevel
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)

        return TextDialog(parent, title, question, defaultVar=defaultVar).result

    def numberBox(self, title="Number Box", question="Enter a number", parent=None):
        return self.numBox(title, question, parent)

    def numBox(self, title="Number Box", question="Enter a number", parent=None):
        self.topLevel.update_idletasks()
        if parent is None:
            parent = self.topLevel
        else:
            parent = self.widgetManager.get(WIDGET_NAMES.SubWindow, parent)

        return NumDialog(parent, title, question).result

############################################################################
####     ******* ------ CLASS MAKERS FROM HERE ------ ***********  #########
############################################################################

    #####################################
    # Named classes for containing groups
    #####################################
    def _makeParentBox(self):
        class ParentBox(frameBase, object):

            def __init__(self, parent, **opts):
                super(ParentBox, self).__init__(parent, **opts)
                self.setup()

            def setup(self):
                pass

            # customised config setters
            def config(self, cnf=None, **kw):
                self.configure(cnf, **kw)

            def configure(self, cnf=None, **kw):
                # properties to propagate to CheckBoxes
                kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)

                if "bg" in kw:
                    for child in self.winfo_children():
                        gui.SET_WIDGET_BG(child, kw["bg"])

                kw = self.processConfig(kw)

                # propagate anything left
                super(ParentBox, self).config(cnf, **kw)

            def processConfig(self, kw):
                return kw
        return ParentBox

    def _makeLabelBox(self):
        ParentBox = self._makeParentBox()
        class LabelBox(ParentBox):
            def setup(self):
                self.theLabel = None
                self.theWidget = None
        return LabelBox

    def _makeButtonBox(self):
        ParentBox = self._makeParentBox()
        class ButtonBox(ParentBox):
            def setup(self):
                self.theWidget = None
                self.theButton = None
        return ButtonBox

    def _makeWidgetBox(self):
        ParentBox = self._makeParentBox()
        class WidgetBox(ParentBox):
            def setup(self):
                self.theWidgets = []
        return WidgetBox

    def makeListBoxContainer(self):
        ParentBox = self._makeParentBox()
        class ListBoxContainer(Frame, object):

            def __init__(self, parent, **opts):
                super(ListBoxContainer, self).__init__(parent)

            # customised config setters
            def config(self, cnf=None, **kw):
                self.configure(cnf, **kw)

            def configure(self, cnf=None, **kw):
                # properties to propagate to CheckBoxes
                kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
                # propagate anything left
                super(ListBoxContainer, self).config(cnf, **kw)
        return ListBoxContainer

    #####################################
    # Simple Separator
    #####################################
    def _makeSeparator(self):
        class Separator(frameBase, object):

            def __init__(self, parent, orient="horizontal", *args, **options):
                super(Separator, self).__init__(parent, *args, **options)
                self.line = frameBase(self)
                self.line.SKIP_CLEANSE = True
                if orient == "horizontal":
                    self.line.config(relief="ridge", height=2, width=100, borderwidth=1)
                    self.line.pack(padx=5, pady=5, fill="x", expand=1)
                else:
                    self.line.config(relief="ridge", height=100, width=2, borderwidth=1)
                    self.line.pack(padx=5, pady=5, fill="y", expand=1)

            def config(self, cnf=None, **kw):
                self.configure(cnf, **kw)

            def configure(self, cnf=None, **kw):
                if "fg" in kw:
                    self.line.config(bg=kw.pop("fg"))

                super(Separator, self).config(cnf, **kw)

        return Separator

    #####################################
    # Drag Grip Label Class
    #####################################

    def _makeGrip(self):
        class Grip(labelBase, object):
            gray25 = BitmapImage(data="""
            #define im_width 16
            #define im_height 16
            static char im_bits[] = {
                0x88, 0x88, 0x22, 0x22, 0x88, 0x88, 0x22, 0x22,
                0x88, 0x88, 0x22, 0x22, 0x88, 0x88, 0x22, 0x22,
                0x88, 0x88, 0x22, 0x22, 0x88, 0x88, 0x22, 0x22,
                0x88, 0x88, 0x22, 0x22, 0x88, 0x88, 0x22, 0x22,
            };
            """)

            def __init__(self, *args, **kwargs):
                super(Grip, self).__init__(image=self.gray25, *args, **kwargs)
                self.config(cursor="fleur", anchor=CENTER)
                self.bind("<ButtonPress-1>", self.StartMove)
                self.bind("<ButtonRelease-1>", self.StopMove)
                self.bind("<B1-Motion>", self.OnMotion)

            def StartMove(self, event):
                self.x = event.x
                self.y = event.y

            def StopMove(self, event):
                self.x = None
                self.y = None

            def OnMotion(self, event):
                parent = self.winfo_toplevel()
                deltax = event.x - self.x
                deltay = event.y - self.y
                x = parent.winfo_x() + deltax
                y = parent.winfo_y() + deltay

                parent.geometry("+%s+%s" % (x, y))
        return Grip

    #####################################
    # Hyperlink Class
    #####################################
    @staticmethod
    def _makeLink():
        class Link(labelBase, object):

            def __init__(self, *args, **kwargs):
                self.useTtk = kwargs.pop('useTtk',False)
                super(Link, self).__init__(*args, **kwargs)
                self.fg = "#0000ff"
                self.overFg="#3366ff"

                if not self.useTtk:
                    self.config(fg=self.fg, takefocus=1)#, highlightthickness=0)
                else:
                    self.config(style="Link.TLabel")

                self.DEFAULT_TEXT = ""

                if gui.GET_PLATFORM() == gui.MAC:
                    self.config(cursor="pointinghand")
                elif gui.GET_PLATFORM() in [gui.WINDOWS, gui.LINUX]:
                    self.config(cursor="hand2")

                self.bind("<Enter>", self.enter)
                self.bind("<Leave>", self.leave)

            def enter(self, e):
                if self.useTtk:
                    self.config(style="LinkOver.TLabel")
                else:
                    super(Link, self).config(fg=self.overFg)

            def leave(self, e):
                if self.useTtk:
                    self.config(style="Over.TLabel")
                else:
                    super(Link, self).config(fg=self.fg)

            def registerCallback(self, callback):
                self.bind("<Button-1>", callback)
                self.bind("<Return>", callback)
                self.bind("<space>", callback)

            def launchBrowser(self, event):
                webbrowser.open_new(r"" + self.page)
                # webbrowser.open_new_tab(self.page)

            def registerWebpage(self, page):
                if not page.startswith("http"):
                    raise InvalidURLError("Invalid URL: " + page + " (it should begin as http://)")

                self.page = page
                self.bind("<Button-1>", self.launchBrowser)
                self.bind("<Return>", self.launchBrowser)
                self.bind("<space>", self.launchBrowser)

            def config(self, **kw):
                self.configure(**kw)

            def configure(self, **kw):
                kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
                if "text" in kw:
                    self.DEFAULT_TEXT = kw["text"]
                if 'fg' in kw:
                    self.fg = kw['fg']
                    self.overFg = gui.TINT(self, self.fg)

                super(Link, self).config(**kw)

            def cget(self, option):
                if option == "text" and hasattr(self, 'page'):
                    return self.page

                return super(Link, self).cget(option)

        return Link


    #######################
    # Upgraded scale - http://stackoverflow.com/questions/42843425/change-trough-increment-in-python-tkinter-scale-without-affecting-slider/
    #######################
    def _makeAjScale(self):
        class AjScale(scaleBase, object):
            '''a scale where a trough click jumps by a specified increment instead of the resolution'''
            def __init__(self, master=None, **kwargs):
                self.increment = kwargs.pop('increment',1)
                super(AjScale, self).__init__(master, **kwargs)
                self.bind('<Button-1>', self.jump)

            def jump(self, event):
                clicked = self.identify(event.x, event.y)
                return self._jump(clicked)

            def _jump(self, clicked):
                if clicked == 'trough1':
                    self.set(self.get() - self.increment)
                elif clicked == 'trough2':
                    self.set(self.get() + self.increment)
                else:
                    return None
                return 'break'

        return AjScale

    #####################################
    # appJar Frame
    #####################################

    def _makeAjFrame(self):
        class ajFrame(frameBase, object):
            def __init__(self, parent, *args, **options):
                super(ajFrame, self).__init__(parent, *args, **options)

        return ajFrame

    #########################
    # Class to provide auto-completion on Entry boxes
    # inspired by: https://gist.github.com/uroshekic/11078820
    #########################
    def _makeAutoCompleteEntry(self):
        ### Create the dynamic class
        class AutoCompleteEntry(entryBase, object):

            def __init__(self, words, tl, *args, **kwargs):
                super(AutoCompleteEntry, self).__init__(*args, **kwargs)
                self.allWords = words
                self.allWords.sort()
                self.topLevel = tl

                # store variable - so we can see when it changes
                self.var = self["textvariable"] = StringVar()
                self.var.auto_id = self.var.trace('w', self.textChanged)

                # register events
                self.bind("<Right>", self.selectWord)
                self.bind("<Return>", self.selectWord)
                self.bind("<Up>", self.moveUp)
                self.bind("<Down>", self.moveDown)
                self.bind("<FocusOut>", self.closeList, add="+")
                self.bind("<Escape>", self.closeList, add="+")

                # no list box - yet
                self.listBoxShowing = False
                self.rows = 10

            # customised config setters
            def config(self, cnf=None, **kw):
                self.configure(cnf, **kw)

            def configure(self, cnf=None, **kw):
                kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)

                if "font" in kw:
                    self.listFont = kw["font"]

                # propagate anything left
                super(AutoCompleteEntry, self).config(cnf, **kw)

            def removeWord(self, word):
                if word in self.allWords:
                    self.allWords.remove(word)

            def addWords(self, words):
                if not hasattr(words, "__iter__"):
                    words = [words]
                for word in words:
                    if word not in self.allWords:
                        self.allWords.append(word)
                self.allWords.sort()

            def changeWords(self, words):
                self.allWords = words
                self.allWords.sort()

            def setNumRows(self, rows):
                self.rows = rows

            # function to see if words match
            def checkMatch(self, fieldValue, acListEntry):
                pattern = re.compile(re.escape(fieldValue) + '.*', re.IGNORECASE)
                return re.match(pattern, acListEntry)

            # function to get all matches as a list
            def getMatches(self):
                return [w for w in self.allWords if self.checkMatch(self.var.get(), w)]

            # called when typed in entry
            def textChanged(self, name, index, mode):
                # if no text - close list
                if self.var.get() == '':
                    self.closeList()
                else:
                    if not self.listBoxShowing:
                        self.makeListBox()
                    self.popListBox()

            # add words to the list
            def popListBox(self):
                if self.listBoxShowing:
                    self.listbox.delete(0, END)
                    shownWords = self.getMatches()
                    if shownWords:
                        for w in shownWords:
                            self.listbox.insert(END, w)
                        self.selectItem(0)

            # function to create & show an empty list box
            def makeListBox(self):
                self.listbox = Listbox(self.topLevel, width=self["width"]-8, height=8)
                self.listbox.config(height=self.rows)
#                self.listbox.config(bg=self.cget("bg"), selectbackground=self.cget("selectbackground"))
#                self.listbox.config(fg=self.cget("fg"))
                if hasattr(self, "listFont"):
                    self.listbox.config(font=self.listFont)
                self.listbox.bind("<Button-1>", self.mouseClickBox)
                self.listbox.bind("<Right>", self.selectWord)
                self.listbox.bind("<Return>", self.selectWord)

                x = self.winfo_rootx() - self.topLevel.winfo_rootx()
                y = self.winfo_rooty() - self.topLevel.winfo_rooty() + self.winfo_height()

                self.listbox.place(x=x, y=y)
                self.listBoxShowing = True

            # function to handle a mouse click in the list box
            def mouseClickBox(self, e=None):
                self.selectItem(self.listbox.nearest(e.y))
                self.selectWord(e)

            # function to close/delete list box
            def closeList(self, event=None):
                if self.listBoxShowing:
                    self.listbox.destroy()
                    self.listBoxShowing = False

            # copy word from list to entry, close list
            def selectWord(self, event):
                if self.listBoxShowing:
                    self.var.set(self.listbox.get(ACTIVE))
                    self.icursor(END)
                    self.closeList()
                return "break"

            # wrappers for up/down arrows
            def moveUp(self, event):
                return self.arrow("UP")

            def moveDown(self, event):
                return self.arrow("DOWN")

            # function for handling up/down keys
            def arrow(self, direction):
                if not self.listBoxShowing:
                    self.makeListBox()
                    self.popListBox()
                    curItem = 0
                    numItems = self.listbox.size()
                else:
                    numItems = self.listbox.size()
                    curItem = self.listbox.curselection()

                    if curItem == ():
                        curItem = -1
                    else:
                        curItem = int(curItem[0])

                    if direction == "UP" and curItem > 0:
                        curItem -= 1
                    elif direction == "UP" and curItem <= 0:
                        curItem = numItems - 1
                    elif direction == "DOWN" and curItem < numItems - 1:
                        curItem += 1
                    elif direction == "DOWN" and curItem == numItems - 1:
                        curItem = 0

                self.selectItem(curItem)

                # stop the event propgating
                return "break"

            # function to select the specified item
            def selectItem(self, position):
                numItems = self.listbox.size()
                self.listbox.selection_clear(0, numItems - 1)
                self.listbox.see(position)  # Scroll!
                self.listbox.selection_set(first=position)
                self.listbox.activate(position)

        # return the dynamic class
        return AutoCompleteEntry

    #####################################
    # Tree Widget Class
    # https://www.safaribooksonline.com/library/view/python-cookbook-2nd/0596007973/ch11s11.html
    # idlelib -> TreeWidget.py
    # https://svn.python.org/projects/python/trunk/Lib/idlelib/TreeWidget.py
    # modify minidom - https://wiki.python.org/moin/MiniDom
    #####################################
    def _makeAjTreeNode(self):
        class AjTreeNode(TreeNode, object):

            def __init__(self, canvas, parent, item):
                super(AjTreeNode, self).__init__(canvas, parent, item)

                self.hasAttr = False
                self.showAttr = False
                self.bgColour = None
                self.fgColour = None
                self.bgHColour = None
                self.fgHColour = None
                # called (if set) when a leaf is edited
                self.editEvent = None

                if self.parent:
                    self.bgColour = self.parent.bgColour
                    self.fgColour = self.parent.fgColour
                    self.bgHColour = self.parent.bgHColour
                    self.fgHColour = self.parent.fgHColour
                    self.editEvent = self.parent.editEvent
                    self.showAttr = self.parent.showAttr
                else:
                    # set this once, in parent
                    self.canvas.menu = None
                    self.canvas.lastSelected = None

                self.menuBound = False

            # customised config setters
            def config(self, cnf=None, **kw):
                self.configure(cnf, **kw)

            def configure(self, cnf=None, **kw):
                # properties to propagate to CheckBoxes
                kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)

                if "bg" in kw:
                    self.setBgColour(kw.pop("bg"))
                if "fg" in kw:
                    self.setFgColour(kw.pop("fg"))

#                # propagate anything left
#                super(AjTreeNode, self).config(cnf, **kw)

            # NOT COMPLETE
            def addChild(self, child):
                child = self.__class__(self.canvas, self, child)
                self.children.append(child)
                self.update()

            def registerEditEvent(self, func):
                self.editEvent = func
                for c in self.children:
                    c.registerEditEvent(func)

            def showAttributes(self, show):
                self.showAttr = show
                for c in self.children:
                    c.showAttributes(show)
                self.update()

            def showMenu(self, show):
                if show:
                    if self.canvas.menu is None:
                        self.canvas.menu = Menu(self.canvas, tearoff=0)
                        self.canvas.menu.add_command(label="delete", command=self._delete)
                        self.canvas.menu.bind("<FocusOut>", lambda e: self.canvas.menu.unpost())
                    self._bindMenu()
                else:
                    # need to go through and unbind...
                    pass

            def setBgColour(self, colour):
                self.canvas.config(background=colour)
                self.bgColour = colour
                self._doUpdateColour()

            def setFgColour(self, colour):
                self.fgColour = colour
                self._doUpdateColour()

            def setBgHColour(self, colour):
                self.bgHColour = colour
                self._doUpdateColour()

            def setFgHColour(self, colour):
                self.fgHColour = colour
                self._doUpdateColour()

            def setAllColours(self, bg=None, fg=None, bgH=None, fgH=None):
                if bg is not None:
                    self.canvas.config(background=bg)
                    self.bgColour = bg
                if fg is not None: self.fgColour = fg
                if bgH is not None: self.bgHColour = bgH
                if fgH is not None: self.fgHColour = fgH
                self._doUpdateColour()

            def _doUpdateColour(self):
                self._updateColours(self.bgColour, self.bgHColour, self.fgColour, self.fgHColour)
                self.update()

            def _updateColours(self, bgCol, bgHCol, fgCol, fgHCol):
                self.bgColour = bgCol
                self.fgColour = fgCol
                self.bgHColour = bgHCol
                self.fgHColour = fgHCol
                for c in self.children:
                    c._updateColours(bgCol, bgHCol, fgCol, fgHCol)

            def draw(self, x, y):
                cy = super(AjTreeNode, self).draw(x, y)
                self._bindMenu()
                return cy

            # override parent function, so that we can change the label's background colour
            def drawtext(self):
                attr=self.item.node.attributes
                self.hasAttr = self.showAttr and attr is not None and len(attr) > 0

                if self.hasAttr:
                    self.attrId = self.canvas.create_text(self.x+20-1, self.y-1, anchor="nw", text='*')
                    self.x += 7
                super(AjTreeNode, self).drawtext()
                if self.hasAttr: self.x -= 7
                self.colourLabels()

                # add a tooltip for attributes
                if ToolTip is not False and self.hasAttr:
                    text = "Attributes\n"
                    for key, val in attr.items():
                        text += "  " + key + ":" + val + "\n"
                    text = text[:-1]
                    ToolTip(self.label, text, delay=500, follow_mouse=1)
                    ToolTip(self.canvas, text, specId=self.attrId, delay=500, follow_mouse=1)

            def _bindMenu(self):
                if self.canvas.menu is not None and not self.menuBound:
                    self.menuBound = True
                    if gui.GET_PLATFORM() in [gui.WINDOWS, gui.LINUX]:
                        self.canvas.tag_bind(self.image_id, "<Button-3>", self._showMenu)
                        if self.hasAttr: self.canvas.tag_bind(self.attrId, "<Button-3>", self._showMenu)
                        self.label.bind("<Button-3>", self._showMenu)
                    else:
                        self.canvas.tag_bind(self.image_id, "<Button-2>", self._showMenu)
                        if self.hasAttr: self.canvas.tag_bind(self.attrId, "<Button-2>", self._showMenu)
                        self.label.bind("<Button-2>", self._showMenu)

            # override parent function, so that we can change the label's background colour
            def drawicon(self):
                super(AjTreeNode, self).drawicon()

            def _showMenu(self, event=None):
                self.canvas.lastSelected = event.widget
                self.canvas.menu.focus_set()
                self.canvas.menu.post(event.x_root - 10, event.y_root - 10)
                return "break"

            def _delete(self):
                self.update()
                self.canvas.lastSelected.destroy()

            # override parent function, so that we can generate an event on finish editing
            def edit_finish(self, event=None):
                super(AjTreeNode, self).edit_finish(event)
                if self.editEvent is not None:
                    self.editEvent()

            def colourLabels(self):
                if self.showAttr and self.hasAttr:
                    self.canvas.itemconfigure(self.attrId, fill=self.fgColour)
                try:
                    if not self.selected:
                        self.label.config(background=self.bgColour, fg=self.fgColour)
                    else:
                        self.label.config(background=self.bgHColour, fg=self.fgHColour)
                except:
                    pass

            def getSelectedText(self):
                item = self.getSelected()
                if item is not None:
                    return item.GetText(), item.getAttribute()
                else:
                    return None

            def getSelected(self):
                if self.selected:
                    return self.item
                else:
                    for c in self.children:
                        val = c.getSelected()
                        if val is not None:
                            return val
                    return None

        return AjTreeNode

    def _makeAjTreeData(self):
        # implementation of container for XML data
        # functions implemented as specified in skeleton
        class AjTreeData(TreeItem, object):

            def __init__(self, document):
                # handle root node
                try: self.node = document.documentElement
                except AttributeError: self.node = document

                self.dblClickFunc = None
                self.clickFunc = None
                self.treeTitle = None
                self.canEdit = True

        # REQUIRED FUNCTIONS

            # called whenever the tree expands
            def GetText(self):
                node = self.node
                if node.nodeType == node.ELEMENT_NODE:
                    return node.nodeName
                elif node.nodeType == node.TEXT_NODE:
                    return node.nodeValue

            def getAttribute(self, att='id'):
                try: return self.node.attributes[att].value
                except: return None

            def IsEditable(self):
                return self.canEdit and not self.node.hasChildNodes()

            def SetText(self, text):
                self.node.replaceWholeText(text)

            def IsExpandable(self):
                return self.node.hasChildNodes()

            def GetIconName(self):
                if self.clickFunc is not None:
                    self.clickFunc(self.treeTitle, self.getAttribute())
                if not self.IsExpandable():
                    return "python"  # change to file icon

            def GetSubList(self):
                children = self.node.childNodes
                prelist = [AjTreeData(node) for node in children]
                itemList = [item for item in prelist if item.GetText().strip()]
                for item in itemList:
                    item.registerDblClick(self.treeTitle, self.dblClickFunc)
                    item.registerClick(self.treeTitle, self.clickFunc)
                    item.canEdit = self.canEdit
                return itemList

            def OnDoubleClick(self):
                if self.IsEditable():
                    # TO DO: start editing this node...
                    pass
                if self.dblClickFunc is not None:
                    self.dblClickFunc(self.treeTitle, self.getAttribute())

        #  EXTRA FUNCTIONS

            # TODO: can only set before calling go()
            def setCanEdit(self, value=True):
                self.canEdit = value

            # TODO: can only set before calling go()
            def registerDblClick(self, title, func):
                self.treeTitle = title
                self.dblClickFunc = func

            # TODO: can only set before calling go()
            def registerClick(self, title, func):
                self.treeTitle = title
                self.clickFunc = func

            # not used - for DEBUG
            def getSelected(self, spaces=1):
                if spaces == 1:
                    gui.trace("%s", self.node.tagName)
                for c in self.node.childNodes:
                    if gui.GET_WIDGET_CLASS(c) == "Element":
                        gui.trace("%s >> %s", " "*spaces, c.tagName)
                        node = AjTreeData(c)
                        node.getSelected(spaces + 2)
                    elif gui.GET_WIDGET_CLASS(c) == "Text":
                        val = c.data.strip()
                        if len(val) > 0:
                            gui.trace("%s >>>> %s", " "*spaces, val)
        return AjTreeData

############################################################################
#### ******* ------ CLASS DEFINITIONS FROM HERE ------ *********** #########
############################################################################

#####################################
# appJar OptionMenu
# allows dropDown to be configure at the same time
#####################################
class ajOption(OptionMenu, object):
    def __init__(self, parent, *args, **options):
        super(ajOption, self).__init__(parent, *args, **options)
        self.dropDown = self.nametowidget(self.menuname)
        self.dropDown.configure(font=options.pop('font', None))

    def config(self, **args):
        super(ajOption, self).config(**args)
        self.dropDown.configure(font=args.pop('font', None))

#####################################
# ProgressBar Class
# from: http://tkinter.unpythonic.net/wiki/ProgressMeter
# A gradient fill will be applied to the Meter
#####################################
class Meter(Frame, object):

    def __init__(self, master, width=100, height=20,
            bg='#FFFFFF', fillColour='orchid1',
            value=0.0, text=None, font=None,
            fg='#000000', *args, **kw):

        # call the super constructor
        super(Meter, self).__init__(master, bg=bg,
            width=width, height=height, relief='ridge', bd=3, *args, **kw)

        # remember the starting value
        self._value = value
        self._colour = fillColour
        self._midFill = fg

        # create the canvas
        self._canv = Canvas(self, bg=self['bg'],
            width=self['width'], height=self['height'],
            highlightthickness=0, relief='flat', bd=0)
        self._canv.pack(fill='both', expand=1)
        self._canv.SKIP_CLEANSE = True

        # create the text
        width, height = self.getWH(self._canv)
        self._text = self._canv.create_text(
            width / 2, height / 2, text='', fill=fg)

        if font:
            self._canv.itemconfigure(self._text, font=font)

        self.set(value, text)
        self.moveText()

        # bind refresh event
        self.bind('<Configure>', self._update_coords)

    # customised config setters
    def config(self, cnf=None, **kw):
        self.configure(cnf, **kw)

    def configure(self, cnf=None, **kw):
        # properties to propagate to CheckBoxes
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)

        if "fill" in kw:
            self._colour = kw.pop("fill")
        if "fg" in kw:
            col = kw.pop("fg")
            self._canv.itemconfigure(self._text, fill=col)
            self._midFill = col
        if "bg" in kw:
            self._canv.config(bg=kw.pop("bg"))

        if "width" in kw:
            self._canv.config(width=kw.pop("width"))
        if "height" in kw:
            self._canv.config(height=kw.pop("height"))
        if "font" in kw:
            self._canv.itemconfigure(self._text, font=kw.pop("fillColour"))

        super(Meter, self).config(cnf, **kw)

        self.makeBar()

    # called when resized
    def _update_coords(self, event):
        '''Updates the position of the text and rectangle inside the canvas
            when the size of the widget gets changed.'''
        self.makeBar()
        self.moveText()

    # getter
    def get(self):
        val = self._value
        try:
            txt = self._canv.itemcget(self._text, 'text')
        except:
            txt = None
        return val, txt

    # update the variables, then call makeBar
    def set(self, value=0.0, text=None):
        # make the value failsafe:
        value = value / 100.0
        if value < 0.0:
            value = 0.0
        elif value > 1.0:
            value = 1.0
        self._value = value

        # if no text is specified use the default percentage string:
        if text is None:
            text = str(int(round(100 * value))) + ' %'

        # set the new text
        self._canv.itemconfigure(self._text, text=text)
        self.makeBar()

    # draw the bar
    def makeBar(self):
        width, height = self.getWH(self._canv)
        start = 0
        fin = width * self._value

        self.drawLines(width, height, start, fin, self._value, self._colour)
        self._canv.update_idletasks()

    # move the text
    def moveText(self):
        width, height = self.getWH(self._canv)
        if hasattr(self, "_text"):
            self._canv.coords( self._text, width/2, height/2)

    # draw gradated lines, in given coordinates
    # using the specified colour
    def drawLines(self, width, height, start, fin, val, col, tags="gradient"):
        '''Draw a gradient'''
        # http://stackoverflow.com/questions/26178869/is-it-possible-to-apply-gradient-colours-to-bg-of-tkinter-python-widgets

        # remove the lines & midline
        self._canv.delete(tags)
        self._canv.delete("midline")

        # determine start & end colour
        (r1, g1, b1) = self.tint(col, -30000)
        (r2, g2, b2) = self.tint(col, 30000)

        # determine a direction & range
        if val < 0:
            direction = -1
            limit = int(start - fin)
        else:
            direction = 1
            limit = int(fin - start)

        # if lines to draw
        if limit != 0:
            # work out the ratios
            r_ratio = float(r2 - r1) / limit
            g_ratio = float(g2 - g1) / limit
            b_ratio = float(b2 - b1) / limit

            # loop through the range of lines, in the right direction
            modder = 0
            for i in range(int(start), int(fin), direction):
                nr = int(r1 + (r_ratio * modder))
                ng = int(g1 + (g_ratio * modder))
                nb = int(b1 + (b_ratio * modder))

                colour = "#%4.4x%4.4x%4.4x" % (nr, ng, nb)
                self._canv.create_line(
                    i, 0, i, height, tags=(tags,), fill=colour)
                modder += 1
            self._canv.lower(tags)

        # draw a midline
        self._canv.create_line(start, 0, start, height,
            fill=self._midFill, tags=("midline",))

        self._canv.update_idletasks()

    # function to calculate a tint
    def tint(self, col, brightness_offset=1):
        ''' dim or brighten the specified colour by the specified offset '''
        # http://chase-seibert.github.io/blog/2011/07/29/python-calculate-lighterdarker-rgb-colors.html
        rgb_hex = self._canv.winfo_rgb(col)
        new_rgb_int = [hex_value + brightness_offset for hex_value in rgb_hex]
        # make sure new values are between 0 and 65535
        new_rgb_int = [min([65535, max([0, i])]) for i in new_rgb_int]
        return new_rgb_int

    def getWH(self, widg):
        # ISSUES HERE:
        # on MAC & LINUX, w_width/w_height always 1
        # on WIN, w_height is bigger then r_height - leaving empty space

        self._canv.update_idletasks()

        r_width = widg.winfo_reqwidth()
        r_height = widg.winfo_reqheight()
        w_width = widg.winfo_width()
        w_height = widg.winfo_height()

        max_height = max(r_height, w_height)
        max_width = max(r_width, w_width)

        return (max_width, max_height)


#####################################
# SplitMeter Class extends the Meter above
# Will fill in the empty space with a second fill colour
# Two colours should be provided - left & right fill
#####################################
class SplitMeter(Meter):

    def __init__(self, master, width=100, height=20,
            bg='#FFFFFF', leftfillColour='#FF0000', rightfillColour='#0000FF',
            value=0.0, text=None, font=None, fg='#000000', *args, **kw):

        self._leftFill = leftfillColour
        self._rightFill = rightfillColour

        super(SplitMeter, self).__init__(master, width=width, height=height,
                    bg=bg, value=value, text=text, font=font,
                    fg=fg, *args, **kw)

    # override the handling of fill
    # list of two colours
    def configure(self, cnf=None, **kw):
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
        if "fill" in kw:
            cols = kw.pop("fill")
            if not isinstance(cols, list):
                raise Exception("SplitMeter requires a list of two colours")
            else:
                self._leftFill = cols[0]
                self._rightFill = cols[1]

        # propagate any left over confs
        super(SplitMeter, self).configure(cnf, **kw)

    def set(self, value=0.0, text=None):
        # make the value failsafe:
        value = value / 100.0
        if value < 0.0:
            value = 0.0
        elif value > 1.0:
            value = 1.0
        self._value = value
        self.makeBar()

    # override the makeBar function
    def makeBar(self):
        width, height = self.getWH(self._canv)
        mid = width * self._value

        self.drawLines(width, height, 0, mid, self._value, self._leftFill, tags="left")
        self.drawLines(width, height, mid, width, self._value, self._rightFill, tags="right")


#####################################
# SplitMeter Class extends the Meter above
# Used to allow bi-directional metering, starting from a mid point
# Two colours should be provided - left & right fill
# A gradient fill will be applied to the Meter
#####################################
class DualMeter(SplitMeter):

    def __init__(self, master, width=100, height=20, bg='#FFFFFF',
            leftfillColour='#FFC0CB', rightfillColour='#00FF00',
            value=None, text=None,
            font=None, fg='#000000', *args, **kw):

        super(DualMeter, self).__init__(master, width=width, height=height,
                    bg=bg, leftfillColour=leftfillColour,
                    rightfillColour=rightfillColour,
                    value=value, text=text, font=font,
                    fg=fg, *args, **kw)

    def set(self, value=[0,0], text=None):
        if value is None:
            value=[0,0]
        if not hasattr(value, "__iter__"):
            raise Exception("DualMeter.set() requires a list of two arguments")

        # make copy, and reduce to decimal
        vals = [value[0]/100.0, value[1]/100.0]

        # normalise
        if vals[0] < -1: vals[0] = -1.0
        elif vals[0] > 0: vals[0] = vals[0] * -1

        if vals[1] > 1.0: vals[1] = 1.0
        elif vals[1] < 0: vals[1] = 0
        elif vals[1] < -1: vals[1] = -1.0

        self._value = vals

        # if no text is specified use the default percentage string:
        if text is not None:
            # set the new text
            self._canv.itemconfigure(self._text, text=text)

        self.makeBar()

    def makeBar(self):
        # get range to draw lines
        width, height = self.getWH(self._canv)

        start = width / 2
        l_fin = start + (start * self._value[0])
        r_fin = start + (start * self._value[1])

        self.drawLines(width, height, start, l_fin, self._value[0], self._leftFill, tags="left")
        self.drawLines(width, height, start, r_fin, self._value[1], self._rightFill, tags="right")

#####################################
# Properties Widget
#####################################

class Properties(LabelFrame, object):

    def __init__(self, parent, text, props=None, haveLabel=True, *args, **options):

        if haveLabel: theText=text
        else: theText=""

        super(Properties, self).__init__(parent, text=theText, *args, **options)

        self.parent = parent
        self.config(relief="groove")
        self.props = {}
        self.cbs = {}
        self.title = text
        self.cmd = None
        self.changingProps = False
        self.addProperties(props)

    def config(self, cnf=None, **kw):
        self.configure(cnf, **kw)

    def configure(self, cnf=None, **kw):
        # properties to propagate to CheckBoxes
        cbVals = ['activebackground', 'activeforeground',
                  'highlightcolor', 'highlightbackground',
                  'indicatoron', 'state', 'selectcolor',
                  'disabledforeground', 'command']
        vals = ["bg", "fg", "font"]

        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)

        try: kw['selectcolor'] = kw.pop('boxbg')
        except: pass

        # loop through all kw properties received
        for k, v in kw.items():
            if k in vals+cbVals:
                # and set them on all CheckBoxes if desired
                for prop_key in self.cbs:
                    self.cbs[prop_key][k] = v
                    if k == "bg":# and gui.GET_PLATFORM() == gui.LINUX:
                        gui.SET_WIDGET_BG(self.cbs[prop_key], v, True)

        # remove any props the LabelFrame can't handle
        for k in cbVals:
            kw.pop(k, None)

        super(Properties, self).config(cnf, **kw)

    def addProperties(self, props, callFunction=True):

        if props is not None:
            for k in sorted(props):
                self.addProperty(k, props[k], callFunction=False)

        if self.cmd is not None and callFunction:
            self.cmd()

    def renameProperty(self, prop, newName=None):
        if newName is None:
            newName = prop
        if prop in self.cbs:
            self.cbs[prop].config(text=newName)
        else:
            gui.warn("Unknown property: %s", prop)

    def addProperty(self, prop, value=False, callFunction=True):
        self.changingProps = True
        if prop in self.props:
            if value is None:
                del self.props[prop]
                self.cbs[prop].pack_forget()
                del self.cbs[prop]
            else:
                self.props[prop].set(value)
                self.cbs[prop].defaultValue = value
        elif prop is not None:
            var = BooleanVar()
            var.set(value)
            var.trace('w', self._propChanged)
            cb = Checkbutton(self)
            cb.config(
                anchor=W,
                text=prop,
                variable=var,
                bg=self.cget("bg"),
                font=self.cget("font"),
                fg=self.cget("fg"))
            cb.defaultValue = value
            cb.pack(fill="x", expand=1)
            self.props[prop] = var
            self.cbs[prop] = cb
        else:
            self.changingProps = False
            raise Exception("Can't add a None property to: ", prop)
        # if text is not None: lab.config ( text=text )

        if self.cmd is not None and callFunction:
            self.cmd()
        self.changingProps = False

    def _propChanged(self, a,b,c):
        if self.cmd is not None and not self.changingProps:
            self.cmd()

    def getProperties(self):
        vals = {}
        for k, v in self.props.items():
            vals[k] = bool(v.get())
        return vals

    def clearProperties(self, callFunction=False):
        for k, cb in self.cbs.items():
            cb.deselect()

        if self.cmd is not None and callFunction:
            self.cmd()

    def resetProperties(self, callFunction=False):
        for k, cb in self.cbs.items():
            if cb.defaultValue:
                cb.select()
            else:
                cb.deselect()

        if self.cmd is not None and callFunction:
            self.cmd()

    def getProperty(self, prop):
        if prop in self.props:
            return bool(self.props[prop].get())
        else:
            raise Exception("Property: " + str(prop) + " not found in Properties: " + self.title)

    def setChangeFunction(self, cmd):
        self.cmd = cmd

#####################################
# Pie Chart Class
#####################################

class PieChart(Canvas, object):
    # constant for available colours
    COLOURS = [
        "#023fa5", "#7d87b9", "#bec1d4",
        "#d6bcc0", "#bb7784", "#8e063b",
        "#4a6fe3", "#8595e1", "#b5bbe3",
        "#e6afb9", "#e07b91", "#d33f6a",
        "#11c638", "#8dd593", "#c6dec7",
        "#ead3c6", "#f0b98d", "#ef9708",
        "#0fcfc0", "#9cded6", "#d5eae7",
        "#f3e1eb", "#f6c4e1", "#f79cd4"]

    def __init__(self, container, fracs, bg="#00FF00"):
        super(PieChart, self).__init__(container, bd=0, highlightthickness=0, bg=bg)
        self.fracs = fracs
        self.arcs = []
        self._drawPie()
        self.bind("<Configure>", self._drawPie)

    def _drawPie(self, event=None):
        # remove the existing arcs
        for arc in self.arcs:
            self.delete(arc)
        self.arcs = []

        # get the width * height
        w = self.winfo_width()
        h = self.winfo_height()

        # scale h&w - so they don't hit the edges
        min_w = w * .05
        max_w = w * .95
        min_h = h * .05
        max_h = h * .95

        # if we're not in a square
        # adjust them to make sure we get a circle
        if w > h:
            extra = (w * .9 - h * .9) / 2.0
            min_w += extra
            max_w -= extra
        elif h > w:
            extra = (h * .9 - w * .9) / 2.0
            min_h += extra
            max_h -= extra

        coord = min_w, min_h, max_w, max_h

        pos = col = 0
        for key, val in self.fracs.items():
            sliceId = "slice" + str(col)
            arc = self.create_arc(
                coord,
                fill=self.COLOURS[col % len(self.COLOURS)],
                start=self.frac(pos),
                extent=self.frac(val),
                activedash=(3, 5),
                activeoutline="grey",
                activewidth=3,
                tag=(sliceId,),
                width=1)

            self.arcs.append(arc)

            # generate a tooltip
            if ToolTip is not False:
                frac = int(float(val) / sum(self.fracs.values()) * 100)
                tip = key + ": " + str(val) + " (" + str(frac) + "%)"
                tt = ToolTip(self, tip, delay=500, follow_mouse=1, specId=sliceId)

            pos += val
            col += 1

    def frac(self, curr):
        return 360. * curr / sum(self.fracs.values())

    def setValue(self, name, value):
        if value == 0 and name in self.fracs:
            del self.fracs[name]
        else:
            self.fracs[name] = value

        self._drawPie()

#####################################
# errors
#####################################

class ItemLookupError(LookupError):
    '''raise this when there's a lookup error for my app'''
    pass


class InvalidURLError(ValueError):
    '''raise this when there's a lookup error for my app'''
    pass

#####################################
# ToggleFrame - collapsable frame
# http://stackoverflow.com/questions/13141259/expandable-and-contracting-frame-in-tkinter
#####################################

class ToggleFrame(Frame, object):

    def __init__(self, parent, title="", *args, **options):
        super(ToggleFrame, self).__init__(parent, *args, **options)

        self.config(relief="raised", borderwidth=2, padx=2, pady=2)
        self.showing = True

        self.titleFrame = Frame(self)
        self.titleFrame.config(bg="DarkGray")
        self.titleFrame.SKIP_CLEANSE = True

        self.titleLabel = Label(self.titleFrame, text=title)
        self.DEFAULT_TEXT = title
        self.titleLabel.config(font="-weight bold")
        self.titleLabel.SKIP_CLEANSE = True

        self.toggleButton = Button(self.titleFrame, width=2, text='-', command=self.toggle)
        self.toggleButton.SKIP_CLEANSE = True

        self.subFrame = Frame(self, relief="sunken", borderwidth=2)
        self.subFrame.SKIP_CLEANSE = True

        self.configure(bg="DarkGray")

        self.grid_columnconfigure(0, weight=1)
        self.titleFrame.grid(row=0, column=0, sticky=EW)
        self.titleFrame.grid_columnconfigure(0, weight=1)
        self.titleLabel.grid(row=0, column=0)
        self.toggleButton.grid(row=0, column=1)
        self.subFrame.grid(row=1, column=0, sticky=EW)
        self.firstTime = True

    def config(self, cnf=None, **kw):
        self.configure(cnf, **kw)

    def configure(self, cnf=None, **kw):
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
        if "font" in kw:
            self.titleLabel.config(font=kw["font"])
            self.toggleButton.config(font=kw["font"])
            del(kw["font"])
        if "bg" in kw:
            self.titleFrame.config(bg=kw["bg"])
            self.titleLabel.config(bg=kw["bg"])
            self.subFrame.config(bg=kw["bg"])
            if gui.GET_PLATFORM() == gui.MAC:
                self.toggleButton.config(highlightbackground=kw["bg"])
        if "state" in kw:
            if kw["state"] == "disabled":
                if self.showing:
                    self.toggle()
            self.toggleButton.config(state=kw["state"])
            del(kw["state"])

        if "text" in kw:
            self.titleLabel.config(text=kw.pop("text"))

        super(ToggleFrame, self).config(cnf, **kw)


    def cget(self, option):
        if option == "text":
            return self.titleLabel.cget(option)

        return super(ToggleFrame, self).cget(option)

    def toggle(self):
        if not self.showing:
            self.subFrame.grid()
            self.toggleButton.configure(text='-')
        else:
            self.subFrame.grid_remove()
            self.toggleButton.configure(text='+')
        self.showing = not self.showing

    def getContainer(self):
        return self.subFrame

    def stop(self):
        self.update_idletasks()
        self.titleFrame.config(width=self.winfo_reqwidth())
        if self.firstTime:
            self.firstTime = False
            self.toggle()

    def isShowing(self):
        return self.showing

#####################################
# Frame Stack
#####################################
class FrameStack(Frame, object):

    def __init__(self, parent, beep=True, **opts):
        self._change = opts.pop("change", None)
        self._start = opts.pop("start", -1)
        super(FrameStack, self).__init__(parent, **opts)
        # the list of frames
        self._frames = []
        self._prevframe = -1
        self._currFrame = -1
        self._beep = beep

        Grid.rowconfigure(self, 0, weight=1)
        Grid.columnconfigure(self, 0, weight=1)

    def showFrame(self, num, callFunction=True):
        if num < 0 or num >= len(self._frames):
            raise IndexError("The selected frame does not exist")
        tmp = self._prevFrame
        self._prevFrame = self._currFrame
        self._currFrame = num

        if callFunction and self._change is not None:
            if self._change() is False:
                self._currFrame = self._prevFrame
                self._prevFrame = tmp
                return
        self._frames[self._currFrame].lift()

    def atStart(self):
        return self._currFrame == 0

    def atEnd(self):
        return self._currFrame == len(self._frames)-1

    def setStartFrame(self, num):
        self._start = num

    def setChangeFunction(self, func):
        self._change = func

    def showNextFrame(self, callFunction=True):
        if self._currFrame < len(self._frames) - 1:
            self.showFrame(self._currFrame + 1, callFunction)
        else:
            if self._beep: self.bell()

    def showPrevFrame(self, callFunction=True):
        if self._currFrame > 0:
            self.showFrame(self._currFrame - 1, callFunction)
        else:
            if self._beep: self.bell()

    def showFirstFrame(self, callFunction=True):
        if self._currFrame == 0:
            if self._beep: self.bell()
        else:
            self.showFrame(0, callFunction)

    def showLastFrame(self, callFunction=True):
        if self._currFrame == len(self._frames)-1:
            if self._beep: self.bell()
        else:
            self.showFrame(len(self._frames) - 1, callFunction)

    def addFrame(self):
        frame = frameBase(self)
        frame.SKIP_CLEANSE = True
        self._frames.append(frame)
        self._prevFrame = self._currFrame
        self._currFrame = len(self._frames) - 1
        self._frames[self._currFrame].grid(row=0, column=0, sticky=N+S+E+W, padx=0, pady=0)

        if self._start > -1 and self._start < len(self._frames):
            tmp = self._beep
            self._beep = False
            self.showFrame(self._start, callFunction=False)
            self._beep = tmp

        return self._frames[-1]

    def getFrame(self, num=None):
        if num is None: num = self._currFrame
        return self._frames[num]

    def getNumFrames(self):
        return len(self._frames)
    def getCurrentFrame(self):
        return self._currFrame
    def getPreviousFrame(self):
        return self._prevFrame

#####################################
# Paged Window
#####################################

class PagedWindow(Frame, object):

    def __init__(self, parent, title=None, **opts):
        # get the fonts
        buttonFont = opts.pop('buttonFont', None)
        titleFont = opts.pop('titleFont', None)

        # call the super constructor
        super(PagedWindow, self).__init__(parent, **opts)
        self.config(width=300, height=400)

        # globals to hold list of frames(pages) and current page
        self.frameStack = FrameStack(self)
        self.shouldShowPageNumber = True
        self.shouldShowTitle = True
        self.title = title
        self.navPos = 1

        # create the 3 components, including a default container frame
        self.titleLabel = Label(self, font=titleFont)
        self.prevButton = Button(self, text="PREVIOUS", command=self.showPrev, state="disabled", width=10, font=buttonFont)
        self.nextButton = Button(self, text="NEXT", command=self.showNext, state="disabled", width=10, font=buttonFont)
        self.prevButton.bind("<Control-Button-1>", self.showFirst)
        self.nextButton.bind("<Control-Button-1>", self.showLast)
        self.posLabel = Label(self, width=8, font=titleFont)

        # to hide warnings on cleanse
        self.frameStack.SKIP_CLEANSE = True
        self.titleLabel.SKIP_CLEANSE = True
        self.prevButton.SKIP_CLEANSE = True
        self.nextButton.SKIP_CLEANSE = True
        self.posLabel.SKIP_CLEANSE = True

        self.grid_rowconfigure(0, weight=0)
        self.grid_rowconfigure(1, weight=1)
        self.grid_rowconfigure(2, weight=0)
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)
        self.grid_columnconfigure(2, weight=1)

        # grid the navigation components
        self.frameStack.grid(row=int(not self.navPos) + 1, column=0, columnspan=3, sticky=N + S + E + W, padx=5, pady=5)
        self.prevButton.grid(row=self.navPos + 1, column=0, sticky=N + S + W, padx=5, pady=(0, 5))
        self.posLabel.grid(row=self.navPos + 1, column=1, sticky=N + S + E + W, padx=5, pady=(0, 5))
        self.nextButton.grid(row=self.navPos + 1, column=2, sticky=N + S + E, padx=5, pady=(0, 5))

        # show the title
        if self.title is not None and self.shouldShowTitle:
            self.titleLabel.config(text=self.title)
            self.titleLabel.grid(row=0, column=0, columnspan=3, sticky=N + W + E)

        self._updatePageNumber()

    def config(self, cnf=None, **kw):
        self.configure(cnf, **kw)

    def configure(self, cnf=None, **kw):
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)

        if "bg" in kw:
            if gui.GET_PLATFORM() == gui.MAC:
                self.prevButton.config(highlightbackground=kw["bg"])
                self.nextButton.config(highlightbackground=kw["bg"])
            self.posLabel.config(bg=kw["bg"])
            self.titleLabel.config(bg=kw["bg"])
        if "fg" in kw:
            self.posLabel.config(fg=kw["fg"])
            self.titleLabel.config(fg=kw["fg"])
            kw.pop("fg")

        if "prevbutton" in kw:
            self.prevButton.config(text=kw.pop("prevbutton"))

        if "nextbutton" in kw:
            self.nextButton.config(text=kw.pop("nextbutton"))

        if "title" in kw:
            self.title = kw.pop("title")
            self.showTitle()

        if "showtitle" in kw:
            kw.pop("showtitle")

        if "showpagenumber" in kw:
            self.shouldShowPageNumber = kw.pop("showpagenumber")
            self._updatePageNumber()

        if "command" in kw:
            self.registerPageChangeEvent(kw.pop("command"))

        super(PagedWindow, self).config(cnf, **kw)

    # functions to change the labels of the two buttons
    def setPrevButton(self, title):
        self.prevButton.config(text=title)

    def setNextButton(self, title):
        self.nextButton.config(text=title)

    def setNavPositionTop(self, top=True):
        oldNavPos = self.navPos
        pady = (0, 5)
        if top: self.navPos = 0
        else: self.navPos = 1
        if oldNavPos != self.navPos:
            if self.navPos == 0:
                self.grid_rowconfigure(1, weight=0)
                self.grid_rowconfigure(2, weight=1)
                pady = (5, 0)
            else:
                self.grid_rowconfigure(1, weight=1)
                self.grid_rowconfigure(2, weight=0)
            # grid the navigation components
            self.frameStack.grid_remove()
            self.prevButton.grid_remove()
            self.posLabel.grid_remove()
            self.nextButton.grid_remove()

            self.frameStack.grid(row=int(not self.navPos) + 1, column=0, columnspan=3, sticky=N + S + E + W, padx=5, pady=5)
            self.prevButton.grid( row=self.navPos + 1, column=0, sticky=S + W, padx=5, pady=pady)
            self.posLabel.grid( row=self.navPos + 1, column=1, sticky=S + E + W, padx=5, pady=pady)
            self.nextButton.grid( row=self.navPos + 1, column=2, sticky=S + E, padx=5, pady=pady)

    # whether to showPageNumber
    def showPageNumber(self, val=True):
        self.shouldShowPageNumber = val
        self._updatePageNumber()

    def setTitle(self, title):
        self.title = title
        self.showTitle()

    def showTitle(self, val=True):
        self.shouldShowTitle = val
        if self.title is not None and self.shouldShowTitle:
            self.titleLabel.config(text=self.title, font="-weight bold")
            self.titleLabel.grid(row=0, column=0, columnspan=3, sticky=N + W + E)
        else:
            self.titleLabel.grid_remove()

    # function to update the contents of the label
    def _updatePageNumber(self):
        if self.shouldShowPageNumber:
            self.posLabel.config(
                text=str(self.frameStack.getCurrentFrame() + 1) + "/" + str(self.frameStack.getNumFrames()))
        else:
            self.posLabel.config(text="")

        # update the buttons
        if self.frameStack.getNumFrames() == 1:   # only 1 page - no buttons
            self.prevButton.config(state="disabled")
            self.nextButton.config(state="disabled")
        elif self.frameStack.getCurrentFrame() == 0:
            self.prevButton.config(state="disabled")
            self.nextButton.config(state="normal")
        elif self.frameStack.getCurrentFrame() == self.frameStack.getNumFrames() - 1:
            self.prevButton.config(state="normal")
            self.nextButton.config(state="disabled")
        else:
            self.prevButton.config(state="normal")
            self.nextButton.config(state="normal")

    # get current page number
    def getPageNumber(self):
        return self.frameStack.getCurrentFrame() + 1

    # register a function to call when the page changes
    def registerPageChangeEvent(self, event):
        self.frameStack.setChangeFunction(event)

    # adds a new page, making it visible
    def addPage(self):
        f = self.frameStack.addFrame()
        return f

    def stopPagedWindow(self):
        self.showPage(1)


    # function to display the specified page
    def showPage(self, page):
        try:
            self.frameStack.showFrame(page-1)
            self._updatePageNumber()
        except:
            raise Exception("Invalid page number: " + str(page) + ". Must be between 1 and " + str(self.frameStack.getNumFrames()))

    def showFirst(self, event=None):
        self.frameStack.showFirstFrame()
        self._updatePageNumber()

    def showLast(self, event=None):
        self.frameStack.showLastFrame()
        self._updatePageNumber()

    def showPrev(self, event=None):
        self.frameStack.showPrevFrame()
        self._updatePageNumber()

    def showNext(self, event=None):
        self.frameStack.showNextFrame()
        self._updatePageNumber()

class Page(Frame, object):
    def __init__(self, parent, **opts):
        super(Page, self).__init__(parent)
        self.config(relief=RIDGE, borderwidth=2)
        self.container = parent

#########################
# Pane class - used in PanedWindows
#########################

class Pane(Frame, object):

    def __init__(self, parent, **opts):
        super(Pane, self).__init__(parent)
        self.parent = parent

#####################################
# scrollable frame...
# http://effbot.org/zone/tkinter-autoscrollbar.htm
#####################################


class AutoScrollbar(Scrollbar, object):

    def __init__(self, parent, **opts):
        super(AutoScrollbar, self).__init__(parent, **opts)
        self.hidden = None

    # a scrollbar that hides itself if it's not needed
    # only works if you use the grid geometry manager
    def set(self, lo, hi):
        if float(lo) <= 0.0 and float(hi) >= 1.0:
            # grid_remove is currently missing from Tkinter!
            self.tk.call("grid", "remove", self)
            self.hidden = True
        else:
            self.grid()
            self.hidden = False
        super(AutoScrollbar, self).set(lo, hi)

    def pack(self, **kw):
        raise Exception("cannot use pack with this widget")

    def place(self, **kw):
        raise Exception("cannot use place with this widget")

    # customised config setters
    def config(self, cnf=None, **kw):
        self.configure(cnf, **kw)

    def configure(self, cnf=None, **kw):
        # properties to propagate to CheckBoxes
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)

        if "fg" in kw:
            kw.pop("fg")

        # propagate anything left
        super(AutoScrollbar, self).config(cnf, **kw)

#######################
# Widget to give TextArea extra functionality
# http://code.activestate.com/recipes/464635-call-a-callback-when-a-tkintertext-is-modified/
#######################


class TextParent(object):
    def _init(self):
        self.clearModifiedFlag()
        self.bind('<<Modified>>', self._beenModified)
        self.__hash = None
        self.callFunction = True
        self.oldCallFunction = True
        self.TAGS = ["UNDERLINE", "BOLD", "ITALIC", "BOLD_ITALIC"]

        # create default fonts, and assign to tags
        self._normalFont = tkFont.Font(family="Helvetica", size=12, slant="roman", weight="normal")
        self._boldFont = tkFont.Font(family="Helvetica", size=12, weight="bold")
        self._italicFont = tkFont.Font(family="Helvetica", size=12, slant="italic")
        self._boldItalicFont = tkFont.Font(family="Helvetica", size=12, weight="bold", slant="italic")
        self.tag_config("AJ_BOLD", font=self._boldFont)
        self.tag_config("AJ_ITALIC", font=self._italicFont)
        self.tag_config("AJ_BOLD_ITALIC", font=self._boldItalicFont)
        self.tag_config("AJ_UNDERLINE", underline=True)

        self.configure(font=self._normalFont)

    def verifyFontTag(self, tag):
        tag = tag.upper().strip()
        if tag not in self.TAGS:
            raise Exception("Invalid tag: " + tag + ". Must be one of: " + str(self.TAGS))
        else:
            return tag

    def setFont(self, **kwargs):
        """ only looking for size & family params """
        self._normalFont.config(**kwargs)
        self._boldFont.config(**kwargs)
        self._italicFont.config(**kwargs)
        self._boldItalicFont.config(**kwargs)

    def pauseCallFunction(self, callFunction=False):
        self.oldCallFunction = self.callFunction
        self.callFunction = callFunction

    def resumeCallFunction(self):
        self.callFunction = self.oldCallFunction

    def _beenModified(self, event=None):
        # stop recursive calls
        if self._resetting_modified_flag: return
        self.clearModifiedFlag()
        self.beenModified(event)

    def bindChangeEvent(self, function):
        self.function = function

    def beenModified(self, event=None):
        # call the user's function
        if hasattr(self, 'function') and self.callFunction:
            self.function()

    def clearModifiedFlag(self):
        self._resetting_modified_flag = True
        try:
            # reset the modified flag (this raises a modified event!)
            self.tk.call(self._w, 'edit', 'modified', 0)
        finally:
            self._resetting_modified_flag = False

    def getText(self):
        return self.get('1.0', END + '-1c')

    def getTextAreaHash(self):
        text = self.getText()
        m = hashlib.md5()
        if PYTHON2:
            m.update(text)
        else:
            m.update(str.encode(text))
        md5 = m.digest()
#        md5 = hashlib.md5(str.encode(text)).digest()
        return md5

    def highlightPattern(self, pattern, tag, start="1.0", end="end", regexp=False):
        '''Apply the given tag to all text that matches the given pattern
        If 'regexp' is set to True, pattern will be treated as a regular
        expression according to Tcl's regular expression syntax.
        '''

        start = self.index(start)
        end = self.index(end)
        self.mark_set("matchStart", start)
        self.mark_set("matchEnd", start)
        self.mark_set("searchLimit", end)

        count = IntVar()
        while True:
            index = self.search(pattern, "matchEnd","searchLimit", count=count, regexp=regexp)
            if index == "": break
            if count.get() == 0: break # degenerate pattern which matches zero-length strings
            self.mark_set("matchStart", index)
            self.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
            self.tag_add(tag, "matchStart", "matchEnd")

# uses multiple inheritance
class AjText(Text, TextParent):
    def __init__(self, parent, **opts):
        super(AjText, self).__init__(parent, **opts)
        self._init()    # call TextParent initialiser

class AjScrolledText(scrolledtext.ScrolledText, TextParent):
    def __init__(self, parent, **opts):
        super(AjScrolledText, self).__init__(parent, **opts)
        self._init()    # call TextParent initialiser


#######################
# Widget to look like a label, but allow selection...
#######################

class SelectableLabel(Entry, object):
    def __init__(self, parent, **opts):
        super(SelectableLabel, self).__init__(parent)
        self.configure(relief=FLAT, state="readonly", readonlybackground='#FFFFFF', fg='#000000', highlightthickness=0)
        self.var = StringVar(parent)
        self.configure(textvariable=self.var)
        self.configure(**opts)

    def cget(self, kw):
        if kw == "text":
            return self.var.get()
        else:
            return super(SelectableLabel, self).cget(kw)

    def config(self, cnf=None, **kw):
        self.configure(cnf, **kw)

    def configure(self, cnf=None, **kw):
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
        if "text" in kw:
            self.var.set(kw.pop("text"))

        if "bg" in kw:
            kw["readonlybackground"] = kw.pop("bg")

        # propagate anything left
        super(SelectableLabel, self).config(cnf, **kw)

#######################
# Frame with built in scrollbars and canvas for placing stuff on
# http://effbot.org/zone/tkinter-autoscrollbar.htm
# Modified with help from idlelib TreeWidget.py
#######################

class ScrollPane(frameBase, object):
    def __init__(self, parent, resize=False, disabled=None, **opts):
        super(ScrollPane, self).__init__(parent)
#        self.config(padx=1, pady=1, bd=0)
        self.resize = resize

        self.hDisabled = disabled == "horizontal"
        self.vDisabled = disabled == "vertical"

        # make the ScrollPane expandable
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        if not self.vDisabled:
            self.vscrollbar = AutoScrollbar(self)
            opts['yscrollcommand'] = self.vscrollbar.set
            self.vscrollbar.grid(row=0, column=1, sticky=N + S + E)
            self.vscrollbar.SKIP_CLEANSE = True

        if not self.hDisabled:
            self.hscrollbar = AutoScrollbar(self, orient=HORIZONTAL)
            opts['xscrollcommand'] = self.hscrollbar.set
            self.hscrollbar.grid(row=1, column=0, sticky=E + W + S)
            self.hscrollbar.SKIP_CLEANSE = True

        self.canvas = Canvas(self, **opts)
        self.canvas.config(highlightthickness=0, bd=0)
        self.canvas.grid(row=0, column=0, sticky=N + S + E + W)
        self.canvas.SKIP_CLEANSE = True

        if not self.vDisabled:
            self.vscrollbar.config(command=self.canvas.yview)
        if not self.hDisabled:
            self.hscrollbar.config(command=self.canvas.xview)

        self.canvas.bind("<Enter>", self._mouseEnter)
        self.canvas.bind("<Leave>", self._mouseLeave)

        self.b_ids = []
        self.canvas.focus_set()

        self.interior = frameBase(self.canvas)
        self.interior_id = self.canvas.create_window(0, 0, window=self.interior, anchor=NW)
        self.interior.SKIP_CLEANSE = True

        if self.resize:
            self.canvas.bind('<Configure>', self._updateWidth)
        else:
            self.interior.bind('<Configure>', self._updateWidth)

    def _updateWidth(self, event):
        if self.resize:
            canvas_width = event.width
            if canvas_width == 0:
                canvas_width = self.canvas.winfo_width()

            interior_width = self.interior.winfo_reqwidth()
            if canvas_width < interior_width: canvas_width = interior_width
            self.canvas.itemconfig(self.interior_id, width=canvas_width)
        else:
            size = (self.interior.winfo_reqwidth(), self.interior.winfo_reqheight())
            self.canvas.config(scrollregion="0 0 %s %s" % size)

    def config(self, **kw):
        self.configure(**kw)

    def configure(self, **kw):
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
        if "bg" in kw:
            self.canvas.config(bg=kw["bg"])
            self.interior.config(bg=kw["bg"])

        if "width" in kw:
            self.canvas.config(width=kw["width"])

        if "height" in kw:
            self.canvas.config(height=kw["height"])

        super(ScrollPane, self).configure(**kw)

    # unbind any saved bind ids
    def _unbindIds(self):
        if len(self.b_ids) == 0:
            return

        if gui.GET_PLATFORM() == gui.LINUX:
            self.canvas.unbind("<4>", self.b_ids[0])
            self.canvas.unbind("<5>", self.b_ids[1])
            self.canvas.unbind("<Shift-4>", self.b_ids[2])
            self.canvas.unbind("<Shift-5>", self.b_ids[3])
        else:  # Windows and MacOS
            self.canvas.unbind("<MouseWheel>", self.b_ids[0])
            self.canvas.unbind("<Shift-MouseWheel>", self.b_ids[1])

        self.canvas.unbind("<Key-Prior>", self.b_ids[4])
        self.canvas.unbind("<Key-Next>", self.b_ids[5])
        self.canvas.unbind("<Key-Up>", self.b_ids[6])
        self.canvas.unbind("<Key-Down>", self.b_ids[7])
        self.canvas.unbind("<Key-Left>", self.b_ids[8])
        self.canvas.unbind("<Key-Right>", self.b_ids[9])
        self.canvas.unbind("<Home>", self.b_ids[10])
        self.canvas.unbind("<End>", self.b_ids[11])

        self.b_ids = []

    # bind mouse scroll to this widget only when mouse is over
    def _mouseEnter(self, event):
        self._unbindIds()
        if gui.GET_PLATFORM() == gui.LINUX:
            self.b_ids.append(self.canvas.bind_all("<4>", self._vertMouseScroll))
            self.b_ids.append(self.canvas.bind_all("<5>", self._vertMouseScroll))
            self.b_ids.append(self.canvas.bind_all("<Shift-4>", self._horizMouseScroll))
            self.b_ids.append(self.canvas.bind_all("<Shift-5>", self._horizMouseScroll))
        else:  # Windows and MacOS
            self.b_ids.append(self.canvas.bind_all("<MouseWheel>", self._vertMouseScroll))
            self.b_ids.append(self.canvas.bind_all("<Shift-MouseWheel>", self._horizMouseScroll))
            self.b_ids.append(None)
            self.b_ids.append(None)

        self.b_ids.append(self.canvas.bind_all("<Key-Prior>", self._keyPressed))
        self.b_ids.append(self.canvas.bind_all("<Key-Next>", self._keyPressed))
        self.b_ids.append(self.canvas.bind_all("<Key-Up>", self._keyPressed))
        self.b_ids.append(self.canvas.bind_all("<Key-Down>", self._keyPressed))
        self.b_ids.append(self.canvas.bind_all("<Key-Left>", self._keyPressed))
        self.b_ids.append(self.canvas.bind_all("<Key-Right>", self._keyPressed))
        self.b_ids.append(self.canvas.bind_all("<Home>", self._keyPressed))
        self.b_ids.append(self.canvas.bind_all("<End>", self._keyPressed))

    # remove mouse scroll binding, when mouse leaves
    def _mouseLeave(self, event):
        self._unbindIds()

    def _horizMouseScroll(self, event):
        if not self.hDisabled and not self.hscrollbar.hidden:
            self._mouseScroll(True, event)

    def _vertMouseScroll(self, event):
        if not self.vDisabled and not self.vscrollbar.hidden:
            self._mouseScroll(False, event)

    def _mouseScroll(self, horiz, event):
        direction = 0

        # get direction
        if event.num == 4:
            direction = -1
        elif event.num == 5:
            direction = 1
        elif event.delta > 100:
            direction = int(-1 * (event.delta/120))
        elif event.delta > 0:
            direction = -1 * event.delta
        elif event.delta < -100:
            direction = int(-1 * (event.delta/120))
        elif event.delta < 0:
            direction = -1 * event.delta
        else:
            return  # shouldn't happen

        if horiz:
            self.xscroll(direction, "units")
        else:
            self.yscroll(direction, "units")

    def getPane(self):
        return self.canvas

    def _keyPressed(self, event):
        # work out if alt/ctrl/shift are pressed
        # http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/event-handlers.html
        state = event.state
        ctrl  = (state & 0x4) != 0
        alt   = (state & 0x8) != 0 or (state & 0x80) != 0 # buggy
        shift = (state & 0x1) != 0

        if event.type == "2":
            # up and down arrows
            if event.keysym == "Up": # event.keycode == 38
                if ctrl:
                    self.yscroll(-1, "pages")
                else:
                    self.yscroll(-1, "units")
            elif event.keysym == "Down": # event.keycode == 40
                if ctrl:
                    self.yscroll(1, "pages")
                else:
                    self.yscroll(1, "units")

            # left and right arrows
            elif event.keysym == "Left": # event.keycode == 37
                if ctrl:
                    self.xscroll(-1, "pages")
                else:
                    self.xscroll(-1, "units")
            elif event.keysym == "Right": # event.keycode == 39
                if ctrl:
                    self.xscroll(1, "pages")
                else:
                    self.xscroll(1, "units")

            # page-up & page-down keys
            elif event.keysym == "Prior": # event.keycode == 33
                if ctrl:
                    self.xscroll(-1, "pages")
                else:
                    self.yscroll(-1, "pages")
            elif event.keysym == "Next": # event.keycode == 34
                if ctrl:
                    self.xscroll(1, "pages")
                else:
                    self.yscroll(1, "pages")

            # home & end keys
            elif event.keysym == "Home": # event.keycode == 36
                if ctrl:
                    self.scrollLeft()
                else:
                    self.scrollTop()
            elif event.keysym == "End": # event.keycode == 35
                if ctrl:
                    self.scrollRight()
                else:
                    self.scrollBottom()

            return "break"
        else:
            pass # shouldn't happen

    def xscroll(self, direction, value=None):
        if not self.hDisabled and not self.hscrollbar.hidden:
            if value is not None: self.canvas.xview_scroll(direction, value)
            else: self.canvas.xview_moveto(direction)

    def yscroll(self, direction, value=None):
        if not self.vDisabled and not self.vscrollbar.hidden:
            if value is not None: self.canvas.yview_scroll(direction, value)
            else: self.canvas.yview_moveto(direction)

    # functions to scroll to the beginning or end
    def scrollLeft(self):
        self.xscroll(0.0)
    def scrollRight(self):
        self.xscroll(1.0)

    def scrollTop(self):
        self.yscroll(0.0)
    def scrollBottom(self):
        self.yscroll(1.0)


#################################
# Additional Dialog Classes
#################################
# the main dialog class to be extended

class Dialog(Toplevel, object):

    def __init__(self, parent, title=None):
        super(Dialog, self).__init__(parent)
        self.transient(parent)
        self.withdraw()
        parent.POP_UP = self

        if title:
            self.title(title)
        self.parent = parent
        self.result = None

        # create a frame to hold the contents
        body = Frame(self)
        self.initial_focus = self.body(body)
        body.pack(padx=5, pady=5)

        # create the buttons
        self.buttonbox()
        gui.SET_LOCATION(x="CENTER", up=150, win=self)

        self.grab_set()
        if not self.initial_focus:
            self.initial_focus = self

        self.protocol("WM_DELETE_WINDOW", self.cancel)
        self.deiconify()

        self.initial_focus.focus_set()
        self.wait_window(self)

    # override to create the contents of the dialog
    # should return the widget to give focus to
    def body(self, master):
        pass

    # add standard buttons
    # override if you don't want the standard buttons
    def buttonbox(self):
        box = Frame(self)

        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.cancel)

        box.pack()

    # called when ok button pressed
    def ok(self, event=None):
        # only continue if validate() returns True
        if not self.validate():
            self.initial_focus.focus_set()  # put focus back
            return

        self.withdraw()
        self.update_idletasks()

        # call the validate function before calling the cancel function
        self.apply()
        self.cancel()

    # called when cancel button pressed
    def cancel(self, event=None):
        self.grab_release()
        self.parent.focus_set()  # give focus back to the parent
        self.destroy()

    # override this to cancel closing the form
    def validate(self):
        return True

    # override this to do something before closing
    def apply(self):
        pass


class SimpleEntryDialog(Dialog):
    """ a base class for a simple data capture dialog """

    def __init__(self, parent, title, question, defaultvar=None):
        self.error = False
        self.question = question
        self.defaultVar=defaultvar
        super(SimpleEntryDialog, self).__init__(parent, title)

    def clearError(self, e):
        if self.error:
            self.error = False
            self.l1.config(text="")

    def setError(self, message):
        self.parent.bell()
        self.error = True
        self.l1.config(text=message)

    # a label for the question, an entry for the answer
    # a label for an error message
    def body(self, master):
        Label(master, text=self.question).grid(row=0)
        self.e1 = Entry(master)
        if self.defaultVar is not None:
            self.e1.var = self.defaultVar
            self.e1.config(textvariable=self.e1.var)
            self.e1.var.auto_id = None
            self.e1.icursor("end")
        self.l1 = Label(master, fg="#FF0000")
        self.e1.grid(row=1)
        self.l1.grid(row=2)
        self.e1.bind("<Key>", self.clearError)
        return self.e1


class TextDialog(SimpleEntryDialog):
    """ captures a string - must not be empty """

    def __init__(self, parent, title, question, defaultVar=None):
        super(TextDialog, self).__init__(parent, title, question, defaultVar)

    def validate(self):
        res = self.e1.get()
        if len(res.strip()) == 0:
            self.setError("Invalid text.")
            return False
        else:
            self.result = res
            return True

class NumDialog(SimpleEntryDialog):
    """ captures a number - must be a valid float """

    def __init__(self, parent, title, question):
        super(NumDialog, self).__init__(parent, title, question)

    def validate(self):
        res = self.e1.get()
        try:
            self.result = float(res) if '.' in res else int(res)
            return True
        except ValueError:
            self.setError("Invalid number.")
            return False

#####################################
# Toplevel Stuff
#####################################

class SubWindow(Toplevel, object):
    def __init__(self, win, parent, name, title=None, stopFunc=None, modal=False, blocking=False, transient=False, grouped=True):
        super(SubWindow, self).__init__()
        if title is None: title = name
        self.win = self
        self.title(title)
        self._parent = parent
        self.withdraw()
        self.escapeBindId = None  # used to exit fullscreen
        self.stopFunction = None  # used to stop
        self.shown = False
        self.locationSet = False
        self.isFullscreen = False
        self.modal = modal
        self.protocol("WM_DELETE_WINDOW", gui.MAKE_FUNC(stopFunc, name))

        # have this respond to topLevel window style events
        if transient: self.transient(self._parent)

        # group this with the topLevel window
        if grouped: self.group(self._parent)

        self.blocking = blocking
        if self.blocking: self.killLab = None

        self.canvasPane = CanvasDnd(self)
        self.canvasPane.pack(fill=BOTH, expand=True)

    def setLocation(self, x, y):
        x, y = gui.PARSE_TWO_PARAMS(x, y)
        self.geometry("+%d+%d" % (x, y))
        self.locationSet = True

    def hide(self, useStopFunction=False):
        if useStopFunction:
            if self.stopFunction is not None and not self.stopFunction():
                return

        self.withdraw()
        if self.blocking and self.killLab is not None:
            self.killLab.destroy()
            self.killLab = None
        if self.modal:
            self.grab_release()
            self._parent.focus_set()

    def prepDestroy(self):
        if self.stopFunction is None or self.stopFunction():
            if self.blocking and self.killLab is not None:
                self.killLab.destroy()
                self.killLab = None
            self.withdraw()
            self.grab_release()
            self._parent.focus_set()

    def show(self):
        self.shown = True
        if not self.locationSet:
            gui.SET_LOCATION('c', win=self)
            self.locationSet = True
        else:
            gui.trace("Using previous position")

        self.deiconify()
        self.config(takefocus=True)

        # stop other windows receiving events
        if self.modal:
            self.grab_set()
            gui.trace("%s set to MODAL", self.title)

        self.focus_set()

    def block(self):
        # block here - wait for the subwindow to close
        if self.blocking and self.killLab is None:
            gui.trace("%s set to BLOCK", self.title)
            self.killLab = Label(self)
            self._parent.wait_window(self.killLab)

#####################################
# SimpleTable Stuff
#####################################

class GridCell(Label, object):
    def __init__(self, parent, fonts, isHeader=False, wrap=0, **opts):
        super(GridCell, self).__init__(parent, **opts)
        self.selected = False
        self.isHeader = isHeader
        self.config(borderwidth=1, highlightthickness=0, padx=0, pady=0, wraplength=wrap)
        self.updateFonts(fonts)

        if not self.isHeader:
            self.bind("<Enter>", self.mouseEnter)
            self.bind("<Leave>", self.mouseLeave)
            self.bind("<Button-1>", self.toggleSelection)

    def updateFonts(self, fonts):
        self.fonts = fonts
        if self.isHeader:
            self.config(font=self.fonts["headerFont"], background=self.fonts["headerBg"], fg=self.fonts['headerFg'], relief=self.fonts['border'])
        else:
            if self.selected:
                self.config(font=self.fonts["dataFont"], background=self.fonts["selectedBg"], fg=self.fonts['selectedFg'], relief=self.fonts['border'])
            else:
                self.config(font=self.fonts["dataFont"], background=self.fonts["inactiveBg"], fg=self.fonts['inactiveFg'], relief=self.fonts['border'])

    def setText(self, text):
        self.config(text=text)

    def clear(self):
        self.config(text="")

    def mouseEnter(self, event=None):
        self.config(background=self.fonts["overBg"], fg=self.fonts["overFg"])

    def mouseLeave(self, event=None):
        if self.selected:
            self.config(background=self.fonts["selectedBg"], fg=self.fonts["selectedFg"])
        else:
            self.config(background=self.fonts["inactiveBg"], fg=self.fonts["inactiveFg"])

    def select(self):
        self.config(background=self.fonts["selectedBg"], fg=self.fonts["selectedFg"])
        self.selected = True

    def deselect(self):
        self.config(background=self.fonts["inactiveBg"], fg=self.fonts["inactiveFg"])
        self.selected = False

    def toggleSelection(self, event=None):
        if self.selected:
            self.deselect()
        else:
            self.select()

# first row is used as a header
# SimpleTable is a ScrollPane, where a Frame has been placed on the canvas - called GridContainer
class SimpleTable(ScrollPane):

    def __init__(self, parent, title, data, action=None, addRow=None,
                    actionHeading="Action", actionButton="Press",
                    addButton="Add", showMenu=False, queueFunction=None, border='solid', **opts):

        self.title = title
        self.fonts = {
            "dataFont": tkFont.Font(family="Arial", size=11),
            "headerFont": tkFont.Font(family="Arial", size=13, weight='bold'),
            "buttonFont": tkFont.Font(family="Arial", size=10),
            "headerBg": "#6e7274",
            "headerFg": "#FFFFFF",
            "selectedBg": "#D3D3D3",
            "selectedFg": "#000000",
            "inactiveBg": "#FFFFFF",
            "inactiveFg":"#000000",
            "overBg": "#E0E9EE",
            "overFg": "#000000",
            "border": border.lower()
        }

        super(SimpleTable, self).__init__(parent, resize=True, **{})

        # actions
        self.addRowEntries = addRow
        self.action = action
        self.queueFunction = queueFunction
        self.changeFunction = opts.pop("change", None)
        self.editFunction = opts.pop("edit", None)

        # lists to store the data in
        self.cells = []
        self.entries = []
        self.entryProps = []
        self.rightColumn = []

        # database stuff
        self.db = None
        self.dbTable = None

        # to wrap text in cells
        self.wrap = opts.pop("wrap", 0)
        # how do we align buttons in the action box?
        self.horizontalButtons = opts.pop("horizontal", True)

        self.config(**opts)

        # menu stuff
        self.showMenu = showMenu
        self.lastSelected = None
        self.lastAction = None
        self.newText = None
        if self.showMenu: self._buildMenu()

        # how many rows & columns
        self.numColumns = 0
        # find out the max number of cells in a row
        if sqlite3 is not None and sqlite3 is not False and isinstance(data, sqlite3.Cursor):
            self.numColumns = len([description[0] for description in data.description])
        else:
            self.numColumns = len(max(data, key=len))

        # headings
        self.actionHeading = actionHeading
        if type(actionButton) in (list, tuple):
            self.actionButton = actionButton
        else:
            self.actionButton = [actionButton]

        self.addButton= addButton

        # add the grid container to the frame
        self.interior.bind("<Configure>", self._refreshGrids)

        gui.trace("SimpleTable %s constructed, adding rows", title)
        self.addRows(data, scroll=False)

    def config(self, cnf=None, **kw):
        self.configure(cnf, **kw)

    def configure(self, cnf=None, **kw):
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
        updateCells = False

        if "disabledentries" in kw:
            entries = kw.pop("disabledentries")
            list(map(self.disableEntry, entries))

        if "change" in kw:
            self.changeFunction = kw.pop("change")

        if "edit" in kw:
            self.editFunction = kw.pop("edit")

        if "bg" in kw:
            bg = kw.pop("bg")
            self.canvas.config(bg=bg)
            self.interior.config(bg=bg)

        if "activebg" in kw or "activebackground" in kw:
            self.fonts["selectedBg"] = kw.pop("activebg", kw.pop("activebackground", self.fonts['selectedBg']))
            updateCells = True
        if "activefg" in kw or "activeforeground" in kw:
            self.fonts["selectedFg"] = kw.pop("activefg", kw.pop("activeforeground", self.fonts['selectedFg']))
            updateCells = True

        if "inactivebg" in kw or "inactivebackground" in kw:
            self.fonts["inactiveBg"] = kw.pop("inactivebg", kw.pop("inactivebackground", self.fonts['inactiveBg']))
            updateCells = True
        if "inactivefg" in kw or "inactiveforeground" in kw:
            self.fonts["inactiveFg"] = kw.pop("inactivefg", kw.pop("inactiveforeground", self.fonts['inactiveFg']))
            updateCells = True

        if "font" in kw:
            font = kw.pop("font")
            self.fonts["headerFont"].configure(family=font.actual("family"), size=font.actual("size") + 2, weight="bold")
            updateCells = True

        if "buttonfont" in kw:
            buttonFont = kw.pop("buttonfont")
            self.fonts["buttonFont"].configure(family=buttonFont.actual("family"), size=buttonFont.actual("size")-2)
            updateCells = True

        if "border" in kw:
            self.fonts["border"]=kw.pop("border").lower().strip()
            updateCells = True

        if updateCells: self._configCells()

        # allow labels to be updated
        if "actionheading" in kw:
            self.actionHeading = kw.pop("actionheading")
            if len(self.rightColumn) > 0:
                self.rightColumn[0].config(text=self.actionHeading)
        if "actionbutton" in kw:
            self.actionButton = kw.pop("actionbutton")
            if len(self.rightColumn) > 1:
                for pos in range(1, len(self.rightColumn)):
                    self.rightColumn[pos].config(text=self.actionButton)
        if "addbutton" in kw:
            self.addButton = kw.pop("addbutton")
            self.ent_but.config(text=self.addButton)

        super(SimpleTable, self).configure(**kw)

    def setChangeFunction(self, func):
        self.changeFunction = func

    def setEditFunction(self, func):
        self.editFunction = func

    def _configCells(self):
        gui.trace("Config all cells")
        for row in self.cells:
            for cell in row:
                gui.trace("Update Fonts: %s, %s", row, cell)
                cell.updateFonts(self.fonts)

    def addRow(self, rowData, scroll=True):
        self.queueFunction(self._hideEntryBoxes)
        self.queueFunction(self._addRow, rowData)
        self.queueFunction(self._showEntryBoxes)
        self.queueFunction(self.canvas.event_generate, "<Configure>")
        if scroll:
            self.queueFunction(self.scrollBottom)

    def addRows(self, data, scroll=True):
        self._hideEntryBoxes()
        if self.numColumns == -1:
            if sqlite3 is not None and sqlite3 is not False and isinstance(data, sqlite3.Cursor):
                gui.trace('No header exists, using cursor description as header')
                self.numColumns = len([description[0] for description in data.description])
                self._addRow([description[0] for description in data.description])
            else:
                gui.trace('No header exists, using first row of data as header')
                self.numColumns = len(data[0])

        try: gui.trace("Adding %s rows in addRows()", len(data))
        except: gui.trace("Adding cursor in addRows()")
        list(map(self._addRow, data))
        self._showEntryBoxes()
        self.canvas.event_generate("<Configure>")
        if scroll:
            self.scrollBottom()

    # this will include the header row
    def getRowCount(self):
        return len(self.cells)-1

    def getRow(self, rowNumber):
        if 0 > rowNumber >= len(self.cells):
            raise Exception("Invalid row number.")
        else:
            data = []
            for cell in self.cells[rowNumber+1]:
                data.append(str(cell.cget('text')))
            return data

    def setHeaders(self, data):
        if sqlite3 is not None and sqlite3 is not False and isinstance(data, sqlite3.Cursor):
            data = [description[0] for description in data.description]

        cellsLen = len(self.cells[0])
        newCols = len(data) - cellsLen
        if newCols > 0:
            for pos in range(cellsLen, cellsLen + newCols):
                self.addColumn(pos, [])
        elif newCols < 0:
            for pos in range(newCols*-1):
                cellsLen = len(self.cells[0])
                self.deleteColumn(cellsLen-1)

        dataLen = len(data)
        cellsLen = len(self.cells[0])
        for count in range(cellsLen):
            cell = self.cells[0][count]
            if count < dataLen:
                cell.setText(data[count])
            else:
                cell.clear()

    def replaceRow(self, rowNum, data):
        if 0 > rowNum >= len(self.cells):
            raise Exception("Invalid row number.")
        else:
            dataLen = len(data)
            for count in range(len(self.cells[rowNum+1])):
                cell = self.cells[rowNum+1][count]
                if count < dataLen:
                    cell.setText(data[count])
                else:
                    cell.clear()
            self.canvas.event_generate("<Configure>")

    def deleteAllRows(self, deleteHeader=False):
        if deleteHeader:
            end = -2
            gui.trace('Deleting %s rows', len(self.cells))
        else:
            end = -1
            gui.trace('Deleting %s rows', len(self.cells)-1)
        list(map(self._quickDeleteRow, range(len(self.cells)-2, end, -1)))
        self.canvas.event_generate("<Configure>")
        self._deleteEntryBoxes()
        self.numColumns = -1

    def _quickDeleteRow(self, position):
        self.deleteRow(position, True)

    def deleteRow(self, position, pauseUpdate=False, callFunction=False):
        if 0 > position >= len(self.cells):
            raise Exception("Invalid row number.")
        else:
            # forget the specified row & button
            for cell in self.cells[position+1]:
                cell.grid_forget()
            if self.action is not None:
                self.rightColumn[position+1].grid_forget()

            # loop through all rows after, forget them, move them, grid them
            butCount = len(self.actionButton)
            for loop in range(position+1, len(self.cells)-1):
                # forget the next row
                for cell in self.cells[loop+1]:
                    cell.grid_forget()

                # move data
                self.cells[loop] = self.cells[loop+1]

                # add its button
                if self.action is not None:
                    self.rightColumn[loop+1].grid_forget()
                    self.rightColumn[loop] = self.rightColumn[loop+1]
                    self.rightColumn[loop+1].grid(row=loop, column=self.numColumns, sticky=N+E+S+W)

                # update its button
                for but in self.rightColumn[loop].but:
                    if butCount == 1:
                        command=lambda row=loop, *args: self.action(row)
                    else:
                        command=lambda name=but.cget['text'], row=loop, *args: self.action(name, row)

                    but.config(command=command)

                # re-grid them
                for cellNum in range(len(self.cells[loop])):
                    self.cells[loop][cellNum].grid(row=loop, column=cellNum, sticky=N+E+S+W)

            # lose last item from lists
            self.cells = self.cells[:-1]
            self.rightColumn = self.rightColumn[:-1]
            self._updateButtons(position)
            if not pauseUpdate: self.canvas.event_generate("<Configure>")
            if self.changeFunction is not None and callFunction:
                self.changeFunction()

    def _addRow(self, rowData):
        if self.numColumns == 0:
            raise Exception("No columns to add to.")
        else:
            gui.trace(rowData)
            rowNum = len(self.cells)
            numCols = len(rowData)
            newRow = []
            if numCols > self.numColumns:
                gui.warn("New data has more columns (%s) than the table (%s), some columns will be discarded.", numCols, self.numColumns)
            for cellNum in range(self.numColumns):

                # get a val ("" if no val)
                if cellNum >= numCols:
                    val = ""
                else:
                    val = rowData[cellNum]

                lab = self._createCell(rowNum, cellNum, val)
                newRow.append(lab)
            self.cells.append(newRow)

            # add some buttons for each row
            if self.action is not None:
                # add the title
                if rowNum == 0:
                    widg = GridCell(self.interior, self.fonts, isHeader=True, text=self.actionHeading)
                # add a button
                else:
                    widg = GridCell(self.interior, self.fonts, isHeader=True)
                    widg.config(borderwidth=0, bg=self.fonts['headerBg'])
                    widg.but=[]

                    val = rowNum - 1

                    butCount = len(self.actionButton)
                    for row, text in enumerate(self.actionButton):
                        if butCount == 1:
                            command=lambda row=val, *args: self.action(row)
                        else:
                            command=lambda name=text, row=val, *args: self.action(name, row)

                        but = Button(widg, font=self.fonts["buttonFont"], text=text,
                                        bd=0, highlightthickness=0, command=command)

                        if gui.GET_PLATFORM() in [gui.MAC, gui.LINUX]:
                            but.config(highlightbackground=widg.cget("bg"))
                        if self.horizontalButtons:
                            but.grid(row=0, column=row, sticky=N+E+S+W, pady=1)
                            widg.grid_columnconfigure(row, weight=1)
                        else:
                            but.grid(column=0, row=row, sticky=N+E+S+W, pady=1)
                            widg.grid_columnconfigure(0, weight=1)
                        widg.but.append(but)
                self.rightColumn.append(widg)
                widg.grid(row=rowNum, column=cellNum + 1, sticky=N+E+S+W)

    def _updateButtons(self, position=0):
        butCount = len(self.actionButton)
        for pos in range(position+1, len(self.rightColumn)):
            for but in self.rightColumn[pos].but:
                if butCount == 1:
                    command=lambda row=pos-1, *args: self.action(row)
                else:
                    command=lambda name=but.cget['text'], row=pos-1, *args: self.action(name, row)

                but.config(command=command)

    def _createCell(self, rowNum, cellNum, val):
        if rowNum == 0: # adding title row
            lab = GridCell(self.interior, self.fonts, isHeader=True, text=val)
            lab.gridPos = ''.join(["h-", str(cellNum)])
            lab.bind("<Button-1>", self._selectColumn)
        else:
            lab = GridCell(self.interior, self.fonts, text=val, wrap=self.wrap)
            lab.gridPos = ''.join([str(rowNum - 1), "-", str(cellNum)])

        if self.showMenu:
            if gui.GET_PLATFORM() in [gui.WINDOWS, gui.LINUX]:
                lab.bind('<Button-3>', self._rightClick)
            else:
                lab.bind('<Button-2>', self._rightClick)

        lab.grid(row=rowNum, column=cellNum, sticky=N+E+S+W)
        self.interior.columnconfigure(cellNum, weight=1)
        self.interior.rowconfigure(rowNum, weight=1)
        return lab

    def _selectColumn(self, event=None):
        columnNumber = int(event.widget.gridPos.split("-")[1])
        self.selectColumn(columnNumber)

    def selectColumn(self, columnNumber, highlight=None):
        if columnNumber < 0 or columnNumber >= self.numColumns:
            raise Exception("Invalid column number.")
        else:
            try:
                selected = self.cells[1][columnNumber].selected
            except IndexError:
                # no rows to select
                return
            for rowCount in range(1, len(self.cells)):
                if highlight is None:
                    if selected:
                        self.cells[rowCount][columnNumber].deselect()
                    else:
                        self.cells[rowCount][columnNumber].select()
                else:
                    if highlight:
                        self.cells[rowCount][columnNumber].mouseEnter()
                    else:
                        self.cells[rowCount][columnNumber].mouseLeave()

    def _selectRow(self, event=None):
        rowNumber = event.widget.gridPos.split("-")[0]
        self.selectRow(rowNumber)

    def selectRow(self, rowNumber, highlight=None):

        if rowNumber == "h": rowNumber = 0
        else: rowNumber = int(rowNumber) + 1

        if 1 > rowNumber >= len(self.cells)+1:
            raise Exception("Invalid row number.")
        else:
            selected = self.cells[rowNumber][0].selected
            for cell in self.cells[rowNumber]:
                if highlight is None:
                    if selected: cell.deselect()
                    else: cell.select()
                else:
                    if highlight: cell.mouseEnter()
                    else: cell.mouseLeave()

    def _buildMenu(self):
        self.menu = Menu(self, tearoff=0)
        self.menu.add_command(label="Copy", command=lambda: self._menuHelper("copy"))
        self.menu.add_command(label="Paste", command=lambda: self._menuHelper("paste"))
        self.menu.add_command(label="Edit", command=lambda: self._menuHelper("edit"))
        self.menu.add_command(label="Clear", command=lambda: self._menuHelper("clear"))
        self.menu.add_separator()
        self.menu.add_command(label="Delete Column", command=lambda: self._menuHelper("deleteColumn"))
        self.menu.add_command(label="Delete Row", command=lambda: self._menuHelper("deleteRow"))
        self.menu.add_separator()
        self.menu.add_command(label="Sort Ascending", command=lambda: self._menuHelper("sortAscending"))
        self.menu.add_command(label="Sort Descending", command=lambda: self._menuHelper("sortDescending"))
        self.menu.add_separator()
        self.menu.add_command(label="Insert Before", command=lambda: self._menuHelper("columnBefore"))
        self.menu.add_command(label="Insert After", command=lambda: self._menuHelper("columnAfter"))
        self.menu.add_separator()
        self.menu.add_command(label="Select Cell", command=lambda: self._menuHelper("selectCell"))
        self.menu.add_command(label="Select Row", command=lambda: self._menuHelper("selectRow"))
        self.menu.add_command(label="Select Column", command=lambda: self._menuHelper("selectColumn"))
        self.menu.bind("<FocusOut>", lambda e: self.menu.unpost())

    def _configMenu(self, isHeader=False):
        if isHeader:
            self.menu.entryconfigure("Delete Row", state=DISABLED)
            self.menu.entryconfigure("Select Cell", state=DISABLED)
            self.menu.entryconfigure("Select Row", state=DISABLED)
        else:
            self.menu.entryconfigure("Delete Row", state=NORMAL)
            self.menu.entryconfigure("Select Cell", state=NORMAL)
            self.menu.entryconfigure("Select Row", state=NORMAL)

    def _rightClick(self, event):
        if self.lastSelected is None or not self.lastSelected.isHeader == event.widget.isHeader:
            self._configMenu(event.widget.isHeader)
        self.lastSelected = event.widget
        self.menu.focus_set()
        self.menu.post(event.x_root - 10, event.y_root - 10)
        return "break"

    def getLastChange(self):
        data = {
            'title':self.title,
            'gridPos':self.lastSelected.gridPos,
            'action':self.lastAction,
            'cellText':self.lastSelected.cget("text"),
            'newText':self.newText,
            'widget':self.lastSelected,
        }
        return data

    def _menuHelper(self, action):
        self.update_idletasks()
        self.lastAction = action
        vals=self.lastSelected.gridPos.split("-")
        gui.trace('Table Menu Helper: %s-%s', action, vals)

        if action == "deleteColumn":
            if self.editFunction is not None: self.editFunction()
            else: self.deleteColumn(int(vals[1]), callFunction=True)
        elif action == "deleteRow" and vals[0] != "h":
            if self.editFunction is not None: self.editFunction()
            else: self.deleteRow(int(vals[0]), callFunction=True)
        elif action == "columnBefore":
            if self.editFunction is not None: self.editFunction()
            else: self.addColumn(int(vals[1]), [], callFunction=True)
        elif action == "columnAfter":
            if self.editFunction is not None: self.editFunction()
            else: self.addColumn(int(vals[1])+1, [], callFunction=True)
        elif action == "selectCell" and vals[0] != "h":
            self.lastSelected.toggleSelection()
        elif action == "selectRow":
            self.selectRow(int(vals[0]))
        elif action == "selectColumn":
            self.selectColumn(int(vals[1]))
        if action == "sortAscending":
            if self.editFunction is not None:
                self.editFunction()
            else:
                self.sort(int(vals[1]))
        if action == "sortDescending":
            if self.editFunction is not None:
                self.editFunction()
            else:
                self.sort(int(vals[1]), descending=True)
        elif action == "copy":
            val=self.lastSelected.cget("text")
            self.clipboard_clear()
            self.clipboard_append(val)
        elif action == "paste":
            self.newText = None
            try: self.newText = self.clipboard_get()
            except: pass
            self._updateCell()
        elif action == "clear":
            self.newText = ""
            self._updateCell()
        elif action == "edit":
            val=self.lastSelected.cget("text")
            defaultVar = StringVar(self)
            defaultVar.set(val)
            self.newText =  TextDialog(self, "Edit", "Enter the new text", defaultVar=defaultVar).result
            self._updateCell()

    def _updateCell(self):
        if self.newText is not None:
            if self.editFunction is not None:
                self.editFunction()
            else:
                self.lastSelected.config(text=self.newText)
                if self.changeFunction is not None: self.changeFunction()

    def addColumn(self, columnNumber, data, callFunction=False):
        if columnNumber < 0 or columnNumber > self.numColumns:
            raise Exception("Invalid column number.")
        else:
            self._hideEntryBoxes()

            gui.trace('Adding column: %s', columnNumber)
            cellCount = len(self.cells)

            # move the right column, if necessary
            if self.action is not None:
                for rowPos in range(cellCount):
                    self.rightColumn[rowPos].grid_forget()
                    self.rightColumn[rowPos].grid(row=rowPos, column=self.numColumns+1, sticky=N+E+S+W)
                    self.interior.grid_columnconfigure(self.numColumns+1, weight=1)

                # move the button
                self.ent_but.lab.grid_forget()
                self.ent_but.lab.grid(row=cellCount, column=self.numColumns+2, sticky=N+E+S+W)

                # add another entry
                ent = self._createEntryBox(self.numColumns)
                self.entries.append(ent)
                self.entryProps.append({'disabled':False})

            # move all columns including this position right one
            for colPos in range(self.numColumns-1, columnNumber-1, -1):
                gui.trace("Moving col %s right with %s cells", colPos, cellCount)
                for rowPos in range(cellCount):
                    cell = self.cells[rowPos][colPos]
                    cell.grid_forget()
                    cell.grid(row=rowPos, column=colPos+1, sticky=N+E+S+W)
                    self.interior.grid_columnconfigure(colPos+1, weight=1)
                    val = rowPos-1
                    if val == -1: val ='h'
                    else: val = str(val)
                    cell.gridPos = ''.join([val, "-", str(colPos+1)])

            # then add this column
            dataLen = len(data)
            for rowPos in range(cellCount):
                if rowPos < dataLen:
                    val = data[rowPos]
                else:
                    val = ""
                lab = self._createCell(rowPos, columnNumber, val)
                self.cells[rowPos].insert(columnNumber, lab)

            self.numColumns += 1
            self._showEntryBoxes()
            self.canvas.event_generate("<Configure>")
            if self.changeFunction is not None and callFunction:
                self.changeFunction()

    def deleteColumn(self, columnNumber, callFunction=False):

        if columnNumber < 0 or columnNumber >= self.numColumns:
            raise Exception("Invalid column number: %s.", columnNumber)
        else:
            # hide the entries
            self._hideEntryBoxes()
            cellCount = len(self.cells)

            # delete the column
            for row in self.cells:
                row[columnNumber].grid_forget()
                del row[columnNumber]

            # update the entry boxes
            if self.addRowEntries is not None and len(self.entries) >= columnNumber:
                self.entries[columnNumber].grid_forget()
                del self.entries[columnNumber]
                del self.entryProps[columnNumber]

            # move the remaining columns
            for rowCount in range(cellCount):
                row = self.cells[rowCount]
                for colCount in range(columnNumber, len(row)):
                    cell = row[colCount]
                    cell.grid_forget()
                    cell.grid(row=rowCount, column=colCount, sticky=N+E+S+W)

                    # update the cells
                    val = rowCount -1
                    if val == -1: val = 'h'
                    else: val = str(val)
                    cell.gridPos = ''.join([val, "-", str(colCount)])

            # move the buttons
            if self.action is not None:
                for rowPos in range(cellCount):
                    self.rightColumn[rowPos].grid_forget()
                    self.rightColumn[rowPos].grid(row=rowPos, column=self.numColumns-1, sticky=N+E+S+W)

            self.numColumns -= 1
            # show the entry boxes
            self._showEntryBoxes()
            self.canvas.event_generate("<Configure>")
            if self.changeFunction is not None and callFunction:
                self.changeFunction()

    def sort(self, columnNumber, descending=False):
        order = self._getSortedData(columnNumber, descending)
        for k, val in enumerate(order):
            for c, cell in enumerate(self.cells[k+1]):
                cell.config(text=val[c])
                cell.selected=False
                cell.mouseLeave()

    def _getSortedData(self, columnNumber, descending=False):
        data = []
        for pos in range(len(self.cells)-1):
            row = self.getRow(pos)
            data.append(row)
        return  sorted(data,key=lambda l:l[columnNumber], reverse=descending)

    def _hideEntryBoxes(self):
        if self.addRowEntries is None or len(self.entries) == 0:
            return
        for e in self.entries:
            e.lab.grid_forget()
        self.ent_but.lab.grid_forget()

    def _deleteEntryBoxes(self):
        self._hideEntryBoxes()
        self.entries = []
        self.entryProps = []

    def _showEntryBoxes(self):
        if self.addRowEntries is None: return
        if len(self.entries) > 0:
            cellCount = len(self.cells)
            for pos in range(len(self.entries)):
                self.entries[pos].lab.grid(row=cellCount, column=pos, sticky=N+E+S+W)
            self.ent_but.lab.grid(row=cellCount, column=len(self.entries), sticky=N+E+S+W)
        else:
            self._createEntryBoxes()

    def _configEntryBoxes(self):
        if self.addRowEntries is None: return
        # config the entries
        for cellNum in range(self.numColumns):
            if self.entryProps[cellNum]['disabled']:
                self.entries[cellNum].config(state='readonly')

    def disableEntry(self, pos, disabled=True):
        self.entryProps[pos]['disabled'] = disabled
        self._configEntryBoxes()

    def _createEntryBoxes(self):
        if self.addRowEntries is None: return
        # add the entries
        for cellNum in range(self.numColumns):
            ent = self._createEntryBox(cellNum)
            self.entries.append(ent)
            self.entryProps.append({'disabled':False})

        # add a button
        lab = GridCell(self.interior, self.fonts, isHeader=True)
        lab.grid(row=len(self.cells), column=self.numColumns, sticky=N+E+S+W)
        self.ent_but = Button(
            lab, font=self.fonts["buttonFont"],
            text=self.addButton,
            command=gui.MAKE_FUNC(self.addRowEntries, "newRow")
        )
        if gui.GET_PLATFORM() in [gui.MAC, gui.LINUX]:
            self.ent_but.config(highlightbackground=lab.cget("bg"))
        self.ent_but.lab = lab
        self.ent_but.pack(expand=True, fill='both')

    def _createEntryBox(self, cellNum):
        # create the container
        lab = GridCell(self.interior, self.fonts, isHeader=True)
        lab.grid(row=len(self.cells), column=cellNum, sticky=N + E + S + W)

        # create the entry
        ent = Entry(lab, relief=FLAT, borderwidth=1, highlightbackground='black', highlightthickness=1, width=6, disabledbackground='grey')
        ent.pack(expand=True, fill='both')
        ent.lab = lab
        return ent

    def getEntries(self):
        return [e.get() for e in self.entries]

    def getSelectedCells(self):
        selectedCells = []
        for row in self.cells:
            for cell in row:
                if cell.selected:
                    selectedCells.append(cell.gridPos)

        return selectedCells

    def _refreshGrids(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

##########################
# MicroBit Simulator
##########################


class MicroBitSimulator(Frame, object):

    COLOURS = {0:'#000000',1:'#110000',2:'#220000',3:'#440000',4:'#660000',5:'#880000',6:'#aa0000',7:'#cc0000',8:'#ee0000',9:'#ff0000'}
    SIZE = 5
    HEART = "09090:90909:90009:09090:00900"

    def __init__(self, parent, **opts):
        super(MicroBitSimulator, self).__init__(parent, **opts)

        self.matrix = []
        for i in range(self.SIZE):
            self.matrix.append([])
        for i in range(self.SIZE):
            for j in range(self.SIZE):
                self.matrix[i].append('')

        for y in range(self.SIZE):
            for x in range(self.SIZE):
                self.matrix[x][y] = Label(self, bg='#000000', width=5, height=2)
                self.matrix[x][y].grid(column=x, row=y, padx=5, pady=5)

        self.update_idletasks()

    def set_pixel(self, x, y, brightness):
        self.matrix[x][y].config(bg=self.COLOURS[brightness])
        self.update_idletasks()

    def show(self, image):
        rows = image.split(':')
        for y in range(len(rows)):
            for x in range(len(rows[0])):
                self.matrix[x][y].config(bg=self.COLOURS[int(rows[y][x])])
        self.update_idletasks()

    def clear(self):
        for y in range(self.SIZE):
            for x in range(self.SIZE):
                self.matrix[x][y].config(bg='#000000')
        self.update_idletasks()


##########################
# Simple SplashScreen
##########################


class SplashScreen(Toplevel, object):
    def __init__(self, parent, text="appJar", fill="#FF0000", stripe="#000000", fg="#FFFFFF", font=44):
        super(SplashScreen, self).__init__(parent)

        lab = Label(self, bg=stripe, fg=fg, text=text, height=3, width=50)
        lab.config(font=("Courier", font))
        lab.place(relx=0.5, rely=0.5, anchor=CENTER)

        width = str(self.winfo_screenwidth())
        height = str(self.winfo_screenheight())
        self.geometry("%sx%s" % (width, height))
        self.config(bg=fill)

        self.attributes("-alpha", 0.95)
        self.attributes("-fullscreen", True)
        self.overrideredirect(1)

        self.update()

##########################
# CopyAndPaste Organiser
##########################


class CopyAndPaste():

    def __init__(self, topLevel, gui):
        self.topLevel = topLevel
        self.inUse = False
        self.gui = gui

    def setUp(self, widget):
        self.inUse = True
        # store globals
        w = widget
        wt = gui.GET_WIDGET_CLASS(widget)

        if wt != "Menu":
            self.widget = w
            self.widgetType = wt

        # query widget
        self.canCut = False
        self.canCopy = False
        self.canSelect = False
        self.canUndo = False
        self.canRedo = False
        self.canFont = False

        try:
            self.canPaste = len(self.topLevel.clipboard_get()) > 0
        except:
            self.canPaste = False

        try:
            if self.widgetType in ["Entry", "AutoCompleteEntry"]:
                if widget.selection_present():
                    self.canCut = self.canCopy = True
                if not self.widget.showingDefault and widget.index(END) > 0:
                    self.canSelect = True
            elif self.widgetType in ["ScrolledText", "Text", "AjText", "AjScrolledText"]:
                if widget.tag_ranges("sel"):
                    self.canCut = self.canCopy = True
                    self.canFont = True
                if widget.index("end-1c") != "1.0":
                    self.canSelect = True
#                if widget.edit_modified():
                self.canUndo = True
                self.canRedo = True
            elif self.widgetType == "OptionMenu":
                self.canCopy = True
                self.canPaste = False
        except Exception as e:
            gui.warn("Error in EDIT menu: %s", self,widgetType)
            gui.exception(e)

    def copy(self):
        if self.widgetType == "OptionMenu":
            self.topLevel.clipboard_clear()
            self.topLevel.clipboard_append(self.widget.var.get())
        else:
            self.widget.event_generate('<<Copy>>')
            self.widget.selection_clear()

    def cut(self):
        if self.widgetType == "OptionMenu":
            self.topLevel.bell()
        else:
            self.widget.event_generate('<<Cut>>')
            self.widget.selection_clear()

    def paste(self):
        if self.widgetType in ["Entry", "AutoCompleteEntry"]:
            # horrible hack to clear default text
            name = self.gui.widgetManager.getName(self.widget)
            self.gui._updateEntryDefault(name, mode="in")
        self.widget.event_generate('<<Paste>>')
        self.widget.selection_clear()

    def undo(self):
        self.widget.event_generate("<<Undo>>")

    def redo(self):
        self.widget.event_generate("<<Redo>>")

    def clearClipboard(self):
        self.topLevel.clipboard_clear()

    def font(self, tag):
        if tag in self.widget.tag_names(SEL_FIRST):
            self.widget.tag_remove(tag, SEL_FIRST, SEL_LAST)
        else:
            self.widget.tag_add(tag, SEL_FIRST, SEL_LAST)

    def clearText(self):
        try:
            self.widget.delete(0.0, END)  # TEXT
        except:
            try:
                self.widget.delete(0, END)  # ENTRY
            except:
                self.topLevel.bell()

    def selectAll(self):
        try:
            self.widget.select_range(0, END)  # ENTRY
        except:
            try:
                self.widget.tag_add("sel", "1.0", "end")  # TEXT
            except:
                self.topLevel.bell()

    # clear the undo/redo stack
    def resetStack(self):
        self.widget.edit_reset()

#####################################
# class to temporarily pause logging
#####################################
# usage:
# with PauseLogger():
#   doSomething()
#####################################
class PauseLogger():
    def __enter__(self):
        # disable all warning of CRITICAL & below
        logging.disable(logging.CRITICAL)
    def __exit__(self, a, b, c):
        logging.disable(logging.NOTSET)


#####################################
# class to temporarily pause function calling
#####################################
# usage:
# with PauseCallFunction(callFunction, widg):
#   doSomething()
# relies on 3 variables in widg:
# var - the thing being traced
# cmd_id - linking to the trace
# cmd - the function called by the trace
#####################################
class PauseCallFunction():
    def __init__(self, callFunction, widg, useVar=True):
        self.callFunction = callFunction
        self.widg = widg
        if useVar:
            self.tracer = self.widg.var
        else:
            self.tracer = self.widg
        gui.trace("PauseCallFunction: callFunction=%s, useVar=%s", callFunction, useVar)

    def __enter__(self):
        if not self.callFunction and hasattr(self.widg, 'cmd'):
            self.tracer.trace_vdelete('w', self.widg.cmd_id)
            gui.trace("callFunction paused")

    def __exit__(self, a, b, c):
        if not self.callFunction and hasattr(self.widg, 'cmd'):
            self.widg.cmd_id = self.tracer.trace('w', self.widg.cmd)
            gui.trace("callFunction resumed")

#####################################
# classes to work with image maps
#####################################
class AjPoint(object):
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __str__(self):
        return "({},{})".format(self.x, self.y)

class AjRectangle(object):
    def __init__(self, name, posn, w, h):
        self.name = name
        self.corner = posn
        self.width = w
        self.height = h

    def __str__(self):
        return "{3}:({0},{1},{2})".format(self.corner, self.width, self.height, self.name)

    def contains(self, point):
        return (self.corner.x <= point.x <= self.corner.x + self.width and
                    self.corner.y <= point.y <= self.corner.y + self.height)

class GoogleMap(LabelFrame, object):
    """ Class to wrap a GoogleMap tile download into a widget"""

    def __init__(self, parent, app, defaultLocation="Marlborough, UK", proxyString=None, useTtk=False, font=None):
        super(GoogleMap, self).__init__(parent, text="GoogleMaps")
        self.alive = True
        self.API_KEY = ""
        self.parent = parent
        self.imageQueue = Queue.Queue()
        self.defaultLocation = defaultLocation
        self.currentLocation = None
        self.app = app
        self.proxyString = proxyString
        if font is not None:
            self.config(font=font)

        self.TERRAINS = ("Roadmap", "Satellite", "Hybrid", "Terrain")
        self.MAP_URL =  "http://maps.google.com/maps/api/staticmap?"
        self.GEO_URL = "https://maps.googleapis.com/maps/api/geocode/json?"
        self.LOCATION_URL = "http://freegeoip.net/json/"
#        self.LOCATION_URL = "http://ipinfo.io/json"
        self.setCurrentLocation()

        # the parameters that we store
        # keeps getting updated, then sent to GoogleMaps
        self.params = {}
        self._setMapParams()

        imgObj = None
        self.rawData = None
        self.mapData = None
        self.request = None
        self.app.thread(self.getMapData)

        self.updateMapId = self.parent.after(500, self.updateMap)

        # if we got some map data then load it
        if self.mapData is not None:
            try:
                imgObj = PhotoImage(data=self.mapData)
                self.h = imgObj.height()
                self.w = imgObj.width()
            # python 3.3 fails to load data
            except Exception as e:
                gui.exception(e)

        if imgObj is None:
            self.w = self.params['size'].split("x")[0]
            self.h = self.params['size'].split("x")[1]

        self.canvas = Canvas(self, width=self.w, height=self.h)
        self.canvas.pack()#expand = YES, fill = BOTH)
        self.image_on_canvas = self.canvas.create_image(1, 1, image=imgObj, anchor=NW)
        self.canvas.img = imgObj

        # will store the 3 buttons in an array
        # they are actually labels - to hide border
        # maes it easier to configure them
        self.buttons = [
                    Label(self.canvas, text="-"),
                    Label(self.canvas, text="+"),
                    Label(self.canvas, text="H"),
                    gui._makeLink()(self.canvas, text="@", useTtk=useTtk)
                        ]
        B_FONT = tkFont.Font(family='Helvetica', size=10)

        for b in self.buttons:
            b.configure(width=3, relief=GROOVE, font=B_FONT)
            if not useTtk:
                b.configure(width=3, activebackground="#D2D2D2", relief=GROOVE, font=B_FONT)

            if gui.GET_PLATFORM() == gui.MAC:
                b.configure(cursor="pointinghand")
            elif gui.GET_PLATFORM() in [gui.WINDOWS, gui.LINUX]:
                b.configure(cursor="hand2")

        #make it look like it's pressed
        self.buttons[0].bind("<Button-1>",lambda e: self.buttons[0].config(relief=SUNKEN), add="+")
        self.buttons[0].bind("<ButtonRelease-1>",lambda e: self.buttons[0].config(relief=GROOVE), add="+")
        self.buttons[0].bind("<ButtonRelease-1>",lambda e: self.zoom("-"), add="+")

        self.buttons[1].bind("<Button-1>",lambda e: self.buttons[1].config(relief=SUNKEN), add="+")
        self.buttons[1].bind("<ButtonRelease-1>",lambda e: self.buttons[1].config(relief=GROOVE), add="+")
        self.buttons[1].bind("<ButtonRelease-1>",lambda e: self.zoom("+"), add="+")

        self.buttons[2].bind("<Button-1>",lambda e: self.buttons[2].config(relief=SUNKEN), add="+")
        self.buttons[2].bind("<ButtonRelease-1>",lambda e: self.buttons[2].config(relief=GROOVE), add="+")
        self.buttons[2].bind("<ButtonRelease-1>",lambda e: self.changeLocation(""), add="+")

        # an optionMenu of terrains
        self.terrainType = StringVar(self.parent)
        self.terrainType.set(self.TERRAINS[0])
        self.terrainOption = OptionMenu(self.canvas, self.terrainType, *self.TERRAINS, command=lambda e: self.changeTerrain(self.terrainType.get().lower()))
        self.terrainOption.config(highlightthickness=0)

        self.terrainOption.config(font=B_FONT)

        # an entry for searching locations
        self.locationEntry = Entry(self.canvas)
        self.locationEntry.bind('<Return>', lambda e: self.changeLocation(self.location.get()))
        self.location = StringVar(self.parent)
        self.locationEntry.config(textvariable=self.location)
        self.locationEntry.config(highlightthickness=0)

        self._placeControls()

    def setProxyString(self, proxyString):
        self.proxyString = proxyString

    def destroy(self):
        self.stopUpdates()
        super(GoogleMap, self).destroy()

    def _removeControls(self):
        self.locationEntry.place_forget()
        self.terrainOption.place_forget()
        self.buttons[0].place_forget()
        self.buttons[1].place_forget()
        self.buttons[2].place_forget()
        self.buttons[3].place_forget()

    def stopUpdates(self):
        self.alive = False
        self.parent.after_cancel(self.updateMapId)

    def _placeControls(self):
        self.locationEntry.place(rely=0, relx=0, x=8, y=8, anchor=NW)
        self.terrainOption.place(rely=0, relx=1.0, x=-8, y=8, anchor=NE)
        self.buttons[0].place(rely=1.0, relx=1.0, x=-5, y=-20, anchor=SE)
        self.buttons[1].place(rely=1.0, relx=1.0, x=-5, y=-38, anchor=SE)
        self.buttons[2].place(rely=1.0, relx=1.0, x=-5, y=-56, anchor=SE)
        self.buttons[3].place(rely=1.0, relx=1.0, x=-5, y=-74, anchor=SE)

        if self.request is not None:
            self.buttons[3].registerWebpage(self.request)
            self._addTooltip(self.buttons[3], self.request)

    def _addTooltip(self, but, text):
        # generate a tooltip
        if ToolTip is not False:
            tt = ToolTip(
                but,
                text,
                delay=1000,
                follow_mouse=1)

    def _setMapParams(self):
        if "center" not in self.params or self.params["center"] is None or self.params["center"] == "":
            self.params["center"] = self.currentLocation
        if "zoom" not in self.params:
            self.params["zoom"] = 16
        if "size" not in self.params:
            self.params["size"] = "500x500"
        if "format" not in self.params:
            self.params["format"] = "gif"
        if "maptype" not in self.params:
            self.params["maptype"] = self.TERRAINS[0]

#        self.params["mobile"] = "true" # optional: mobile=true will assume the image is shown on a small screen (mobile device)
        self.params["sensor"] = "false"  # must be given, deals with getting loction from mobile device

        self.markers = []

    def removeMarkers(self):
        self.markers = []
        self.app.thread(self.getMapData)

    def removeMarker(self, label):
        for p, v in enumerate(self.markers):
            if v.get("label") == label:
                del self.markers[p]
                self.app.thread(self.getMapData)
                return

    def addMarker(self, location, size=None, colour=None, label=None, replace=False):
        """ function to add markers, format:
            &markers=color:blue|label:Z|size:tiny|location_string
        """
        if size is not None:
            size = size.lower().strip()
            if size not in ["tiny", "mid", "small"]:
                gui.warn("Invalid size: %s, for marker %s, ignoring", size, location)
                size = None

        if label is not None:
            label = label.upper().strip()
            if label not in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789":
                gui.warn("Invalid label: %s, for marker %s, must be a single character.", label, location)
                label = None

        if len(self.markers) == 0 or not replace:
            self.markers.append( {"location":location, "size":size, "colour":colour, "label":label} )
        else:
            self.markers[-1] = {"location":location, "size":size, "colour":colour, "label":label}

        self.app.thread(self.getMapData)

    def saveTile(self, location):
        if self.rawData is not None:
            try:
                with open(location, "wb") as fh:
                    fh.write(self.rawData)
                gui.info("Map data written to file: %s", location)
                return True
            except  Exception as e:
                gui.exception(e)
                return False
        else:
            gui.error("Unable to save map data - no data available")
            return False

    def setSize(self, size):
        if size != self.params["size"]:
            self.params["size"] = str(size).lower()
            self.app.thread(self.getMapData)

    def changeTerrain(self, terrainType):
        terrainType = terrainType.title()
        if terrainType in self.TERRAINS:
            self.terrainType.set(terrainType)
            if self.params["maptype"] != self.terrainType.get().lower():
                self.params["maptype"] = self.terrainType.get().lower()
                self.app.thread(self.getMapData)

    def changeLocation(self, location):
        self.location.set(location) # update the entry
        if self.params["center"] != location:
            self.params["center"] = location
            self.app.thread(self.getMapData)

    def setZoom(self, zoom):
        if 0 <= zoom <= 22:
            self.params["zoom"] = zoom
            self.app.thread(self.getMapData)

    def zoom(self, mod):
        if mod == "+" and self.params["zoom"] < 22:
            self.params["zoom"] += 1
            self.app.thread(self.getMapData)
        elif mod == "-" and self.params["zoom"] > 0:
            self.params["zoom"] -= 1
            self.app.thread(self.getMapData)

    def updateMap(self):
        if not self.alive: return
        if not self.imageQueue.empty():
            self.rawData = self.imageQueue.get()
            self.mapData = base64.encodestring(self.rawData)
            try:
                imgObj = PhotoImage(data=self.mapData)
            except:
                gui.error("Error parsing image data")
            else:
                self.canvas.itemconfig(self.image_on_canvas, image=imgObj)
                self.canvas.img = imgObj

                h = imgObj.height()
                w = imgObj.width()

                if h != self.h or w != self.w:
                    self._removeControls()
                    self.h = h
                    self.w = w
                    self.canvas.config(width=self.w, height=self.h)
                    self._placeControls()
                if self.request is not None:
                    self.buttons[3].registerWebpage(self.request)
                    self._addTooltip(self.buttons[3], self.request)
        self.updateMapId = self.parent.after(200, self.updateMap)

    def _buildQueryURL(self):
        self.request = self.MAP_URL + urlencode(self.params)
        if len(self.markers) > 0:
            m = ""
            for mark in self.markers:
                if mark["colour"] is not None: m += "color:" + str(mark["colour"])
                if mark["size"] is not None: m += "|size:" + str(mark["size"])
                if mark["label"] is not None: m += "|label:" + str(mark["label"])
                m += "|" + str(mark["location"])
                m = quote_plus(m)
                self.request += "&markers=" + m

        gui.trace("GoogleMap search URL: %s", self.request)

    def _buildGeoURL(self, location):
        """ for future use - gets the location
        """
        p = {}
        p["address"] = location
        p["key"] = self.API_KEY
        req = self.GEO_URL + urlencode(p)
        return req

    def getMapData(self):
        """ will query GoogleMaps & download the image data as a blob """
        if self.params['center'] == "":
            self.params["center"] = self.currentLocation
        self._buildQueryURL()
        gotMap = False
        while not gotMap:
            if self.request is not None:
                if self.proxyString is not None:
                    gui.error("Proxy set, but not enabled.")

                try:
                    u = urlopen(self.request)
                    rawData = u.read()
                    u.close()
                    self.imageQueue.put(rawData)
                    gotMap = True
                except Exception as e:
                    gui.error("Unable to contact GoogleMaps")
                    time.sleep(1)
            else:
                gui.trace("No request")
                time.sleep(.25)

    def getMapFile(self, fileName):
        """ will query GoogleMaps & download the image into the named file """
        self._buildQueryURL()
        self.buttons[3].registerWebpage(self.request)
        try:
            urlretrieve(self.request, fileName)
            return fileName
        except Exception as e:
            gui.error("Unable to contact GoogleMaps")
            return None

    def setCurrentLocation(self):
        gui.trace("Location request URL: %s", self.LOCATION_URL)
        try:
            self.currentLocation = self._locationLookup()
        except Exception as e:
            gui.error("Unable to contact location server, using default: %s", self.defaultLocation)
            self.currentLocation = self.defaultLocation

    def _locationLookup(self):
        u =  urlopen(self.LOCATION_URL)
        data = u.read().decode("utf-8")
        u.close()
        gui.trace("Location data: %s", data)
        data = json.loads(data)
#        location = data["loc"]
        location = str(data["latitude"]) + "," + str(data["longitude"])
        return location


#####################################
class CanvasDnd(Canvas, object):
    """
    A canvas to which we have added those methods necessary so it can
        act as both a TargetWidget and a TargetObject.

    Use (or derive from) this drag-and-drop enabled canvas to create anything
        that needs to be able to receive a dragged object.
    """
    def __init__(self, Master, cnf={}, **kw):
        if cnf:
            kw.update(cnf)
        super(CanvasDnd, self).__init__(Master,  kw)
        self.config(bd=0, highlightthickness=0)

    #----- TargetWidget functionality -----

    def dnd_accept(self, source, event):
        #Tkdnd is asking us (the TargetWidget) if we want to tell it about a
        #    TargetObject. Since CanvasDnd is also acting as TargetObject we
        #    return 'self', saying that we are willing to be the TargetObject.
        gui.trace("<<%s .dnd_accept>> %s", type(self), source)
        return self

    #----- TargetObject functionality -----

    # This is called when the mouse pointer goes from outside the
    # Target Widget to inside the Target Widget.
    def dnd_enter(self, source, event):
        gui.trace("<<%s .dnd_enter>> %s", type(self), source)
        XY = gui.MOUSE_POS_IN_WIDGET(self, event)
        # show the dragged object
        source.appear(self ,XY)

    # This is called when the mouse pointer goes from inside the
    # Target Widget to outside the Target Widget.
    def dnd_leave(self, source, event):
        gui.trace("<<%s .dnd_leave>> %s", type(self), source)
        # hide the dragged object
        source.vanish()

    #This is called when the mouse pointer moves withing the TargetWidget.
    def dnd_motion(self, source, event):
        gui.trace("<<%s .dnd_motion>> %s", type(self), source)
        XY = gui.MOUSE_POS_IN_WIDGET(self,event)
        # move the dragged object
        source.move(self, XY)

    #This is called if the DraggableWidget is being dropped on us.
    def dnd_commit(self, source, event):
        gui.trace("<<%s .dnd_commit>> %s", type(self), source)

# A canvas specifically for deleting dragged objects.
class TrashBin(CanvasDnd, object):
    def __init__(self, master, **kw):
        if "width" not in kw:
            kw['width'] = 150
        if "height" not in kw:
            kw['height'] = 25

        super(TrashBin, self).__init__(master, kw)
        self.config(relief="sunken", bd=2)
        x = kw['width'] / 2
        y = kw['height'] / 2
        self.textId = self.create_text(x, y, text='TRASH', anchor="center")

    def dnd_commit(self, source, event):
        gui.trace("<<TRASH_BIN.dnd_commit>> vanishing source")
        source.vanish(True)

    def config(self, **kw):
        self.configure(**kw)

    def configure(self, **kw):
        kw = gui.CLEAN_CONFIG_DICTIONARY(**kw)
        if "fg" in kw:
            fg=kw.pop('fg')
            self.itemconfigure(self.textId, fill=fg)

        super(TrashBin, self).config(**kw)

# This is a prototype thing to be dragged and dropped.
class DraggableWidget(object):
    discardDragged = False

    def dnd_accept(self, source, event):
        return None

    def __init__(self, parent, title, name, XY, widg=None):
        self.parent = parent
        gui.trace("<<DRAGGABLE_WIDGET.__init__>>")

        #When created we are not on any canvas
        self.Canvas = None
        self.OriginalCanvas = None
        self.widg = widg

        #This sets where the mouse cursor will be with respect to our label
        self.OffsetCalculated = False
        self.OffsetX = XY[0]
        self.OffsetY = XY[1]

        # give ourself a name
        self.Name = name
        self.Title = title

        self.OriginalID = None
        self.dropTarget = None

    # this gets called when we are dropped
    def dnd_end(self, target, event):
        gui.trace("<<DRAGGABLE_WIDGET.dnd_end>> %s target=%s", self.Name, target)

        # from somewhere, dropped nowhere - self destruct, or put back
        if self.Canvas is None:
            gui.trace("<<DRAGGABLE_WIDGET.dnd_end>> dropped with Canvas (None)")
            if DraggableWidget.discardDragged:
                gui.trace("<<DRAGGABLE_WIDGET.dnd_end>> DISCARDING under order")
            else:
                if self.OriginalCanvas is not None:
                    gui.trace("<<DRAGGABLE_WIDGET.dnd_end>> RESTORING")
                    self.restoreOldData()
                    self.Canvas.dnd_enter(self, event)
                else:
                    gui.trace("<<DRAGGABLE_WIDGET.dnd_end>> DISCARDING as nowhere to go")

        # have been dropped somewhere
        else:
            gui.trace("<<DRAGGABLE_WIDGET.dnd_end>> dropped with Canvas(%s) Target=%s", self.Canvas, self.dropTarget)
            if not self.dropTarget:
                # make the dragged object re-draggable
                self.Label.bind('<ButtonPress>', self.press)
            else:
                if self.dropTarget.keepWidget(self.Title, self.Name):
                    self.Label.bind('<ButtonPress>', self.press)
                else:
                    self.vanish(True)

            # delete any old widget
            if self.OriginalCanvas:
                self.OriginalCanvas.delete(self.OriginalID)
                self.OriginalCanvas = None
                self.OriginalID = None
                self.OriginalLabel = None

    # put a label representing this DraggableWidget instance on Canvas.
    def appear(self, canvas, XY):

        if not isinstance(canvas, CanvasDnd):
            self.dropTarget = canvas
            canvas = canvas.dnd_canvas
#        else:
#            self.dropTarget = None

        if self.Canvas:
            gui.trace("<<DRAGGABLE_WIDGET.appear> - ignoring, as we already exist?: %s %s", canvas, XY)
            return
        else:
            gui.trace("<<DRAGGABLE_WIDGET.appear> - appearing: %s %s", canvas, XY)

            self.Canvas = canvas
            self.X, self.Y = XY
            self.Label = Label(self.Canvas, text=self.Name, borderwidth=2, relief=RAISED)

            # Offsets are received as percentages from initial button press
            # so calculate Offset from a percentage
            if not self.OffsetCalculated:
                self.OffsetX = self.Label.winfo_reqwidth() * self.OffsetX
                self.OffsetY = self.Label.winfo_reqheight() * self.OffsetY
                self.OffsetCalculated = True

            self.ID = self.Canvas.create_window(self.X-self.OffsetX, self.Y-self.OffsetY, window=self.Label, anchor="nw")
            gui.trace("<<DRAGGABLE_WIDGET.appear> - created: %s %s", self.Label, self.Canvas)

    # if there is a label representing us on a canvas, make it go away.
    def vanish(self, all=False):
        # if we had a canvas, delete us
        if self.Canvas:
            gui.trace("<<DRAGGABLE_WIDGET.vanish> - vanishing")
            self.storeOldData()
            self.Canvas.delete(self.ID)
            self.Canvas = None
            del self.ID
            del self.Label
        else:
            gui.trace("<<DRAGGABLE_WIDGET.vanish>> ignoring")

        if all and self.OriginalCanvas:
            gui.trace("<<DRAGGABLE_WIDGET.vanish>> restore original")
            self.OriginalCanvas.delete(self.OriginalID)
            self.OriginalCanvas = None
            del self.OriginalID
            del self.OriginalLabel

    # if we have a label on a canvas, then move it to the specified location.
    def move(self, widget, XY):
        gui.trace("<<DRAGGABLE_WIDGET.move>> %s %s", self.Canvas, XY)
        if self.Canvas:
            self.X, self.Y = XY
            self.Canvas.coords(self.ID, self.X-self.OffsetX, self.Y-self.OffsetY)
        else:
            gui.error("<<DRAGGABLE_WIDGET.move>> unable to move - NO CANVAS!")

    def press(self, event):
        gui.trace("<<DRAGGABLE_WIDGET.press>>")
        self.storeOldData()

        self.ID = None
        self.Canvas = None
        self.Label = None

        #Ask Tkdnd to start the drag operation
        if INTERNAL_DND.dnd_start(self, event):
            self.OffsetX, self.OffsetY = gui.MOUSE_POS_IN_WIDGET(self.OriginalLabel, event, False)
            XY = gui.MOUSE_POS_IN_WIDGET(self.OriginalCanvas, event, False)
            self.appear(self.OriginalCanvas, XY)

    def storeOldData(self, phantom=False):
        gui.trace("<<DRAGGABLE_WIDGET.storeOldData>>")
        self.OriginalID = self.ID
        self.OriginalLabel = self.Label
        self.OriginalText = self.Label['text']
        self.OriginalCanvas = self.Canvas
        if phantom:
            gui.trace("<<DRAGGABLE_WIDGET.storeOldData>> keeping phantom")
            self.OriginalLabel["text"] = "<Phantom>"
            self.OriginalLabel["relief"] = RAISED
        else:
            gui.trace("<<DRAGGABLE_WIDGET.storeOldData>> hiding phantom")
            self.OriginalCanvas.delete(self.OriginalID)

    def restoreOldData(self):
        if self.OriginalID:
            gui.trace("<<DRAGGABLE_WIDGET.restoreOldData>>")
            self.ID = self.OriginalID
            self.Label = self.OriginalLabel
            self.Label['text'] = self.OriginalText
            self.Label['relief'] = RAISED
            self.Canvas = self.OriginalCanvas
            self.OriginalCanvas.itemconfigure(self.OriginalID, state='normal')
            self.Label.bind('<ButtonPress>', self.press)
        else:
            gui.trace("<<DRAGGABLE_WIDGET.restoreOldData>> unable to restore - NO OriginalID")

#########################################
# Enum & WidgetManager - used to store widget lists
#########################################

class WidgetManager(object):
    """ used to keep track of all widgets in the GUI
        creates a dictionary for each widget type on demand
        provides functions for accessing widgets """

    WIDGETS = "widgets"
    VARS = "vars"

    def __init__(self):
        self.widgets = {}
        self.vars = {}

    def reset(self, keepers):
        newWidg = {}
        newVar = {}

        gui.trace('Resetting WidgetManager')

        for key in keepers:
            if key in self.widgets:
                newWidg[key] = self.widgets[key]
            if key in self.vars:
                newVar[key] = self.vars[key]

        self.widgets = newWidg
        self.vars = newVar

    def group(self, widgetType, group=None, array=False):
        """
        returns the list/dictionary containing the specified widget type
        will create a new group if none exists
        """
        if group is None: container = self.widgets
        elif group == WidgetManager.VARS: container = self.vars

        try:
            widgGroup = container[widgetType]
        except KeyError:
            if array: widgGroup = []
            else: widgGroup = {}
            container[widgetType] = widgGroup

        return widgGroup

    def add(self, widgetType, widgetName, widget, group=None):
        """ adds items to the specified dictionary """
        widgGroup = self.group(widgetType, group)
        if widgetName in widgGroup:
            raise ItemLookupError("Duplicate key: '" + widgetName + "' already exists")
        else:
            widgGroup[widgetName] = widget

        widget.APPJAR_TYPE = widgetType

    def getName(self, widget):
        if widget is not None and hasattr(widget, 'APPJAR_TYPE'):
            widgetType = widget.APPJAR_TYPE
            widgGroup = self.group(widgetType, None)
            if widgGroup is not None:
                for name, obj in widgGroup.items():
                    if obj == widget:
                        return name
        return None

    def log(self, widgetType, widget, group=None):
        """ Used for adding items to an array """
        widgGroup = self.group(widgetType, group, array=True)
        widgGroup.append(widget)

        try: widget.APPJAR_TYPE = widgetType
        except AttributeError: pass # not available on some classes

    def verify(self, widgetType, widgetName, group=None, array=False):
        """ checks for duplicatres """
        if widgetName in self.group(widgetType, group, array):
            raise ItemLookupError("Duplicate widgetName: " + widgetName)

    def get(self, widgetType, widgetName, group=None):
        """ gets the specified item """
        try:
            return self.group(widgetType, group)[widgetName]
        except KeyError:
            raise ItemLookupError("Invalid widgetName: " + widgetName)

    def update(self, widgetType, widgetName, widget, group=None):
        """ replaces the specified item """
        try:
            self.group(widgetType, group)[widgetName] = widget
        except KeyError:
            raise ItemLookupError("Invalid widgetName: '" + widgetName)

    def check(self, widgetType, widgetName, group=None):
        """ used for arrays - checks if the item is in the array """
        try:
            if widgetName in self.group(widgetType, group): return True
            else: raise ItemLookupError("Invalid widgetName: '" + widgetName)
        except KeyError:
            raise ItemLookupError("Invalid widgetName: '" + widgetName)

    def remove(self, widgetType, widgetName, group=None):
        widgGroup = self.group(widgetType, group)
        if type(widgGroup) == list:
            widgGroup.remove(widgetName)
        else:
            del widgGroup[widgetName]
            # delete a linked var
            if group != self.VARS:
                try: del self.group(widgetType, self.VARS)[widgetName]
                except: pass

    def clear(self, widgetType, group=None):
        if group is None: container = self.widgets
        elif group == WidgetManager.VARS: container = self.vars

        if isinstance(self.group(widgetType, group), dict):
            container[widgetType] = {}
        else:
            container[widgetType] = []

    def destroyContainer(self, widgType, widget):
        widgets = self.widgets[widgType]
        for name, obj in widgets.items():
            if widget == obj['container']:
                obj['container'].destroy()
                del widgets[name]
                gui.trace('Matched and destroyed')
                return
        gui.trace('Failed to match and destroy - not a container?')

    # function to loop through a config dict/list and remove matching object
    def destroyWidget(self, widgType, widget):
        widgets = self.widgets[widgType]
        # just a list, remove matching obj - no vars
        if type(widgets) in (list, tuple):
            gui.trace('Removing widget: %s, %s', widgType, widget)
            for obj in widgets:
                if widget == obj:
                    obj.destroy()
                    widgets.remove(obj)
                    gui.trace("Matched and removed")
                    return True
        else:
            gui.trace('Destroying widget: %s, %s', widgType, widget)
            for name, obj in widgets.items():
                if type(obj) in (list, tuple):
                    if self.destroyWidget(widget, obj):
                        if len(obj) == 0:
                            del widgets[name]
                            try: del self.vars[widgType][name]
                            except: pass # no var
                        return True
                elif widget == obj:
                    obj.destroy()
                    del widgets[name]
                    try: del self.vars[widgType][name]
                    except: pass # no var
                    gui.trace("Matched and destroyed")
                    return True

        gui.trace("Failed to destory widget")
        return False

#########################################
# Class for storing a shortcut
#########################################

class EventBinding(object):
    # MODIFIERS=["Control", "Ctrl", "Option", "Opt", "Alt", "Shift", "Command", "Cmd", "Meta"]
    def __init__(self, keyMap, func, win, menuBinding=False):
        gui.trace('Binding %s', keyMap)
        keyMap = self._cleanKeyMap(keyMap)

        self.func = func
        self.win = win
        self.menuBinding = menuBinding
        self.displayName = self._createDisplayName(keyMap)
        self.shortcuts = self._createShortcuts(keyMap)

    def _cleanKeyMap(self, keyMap):
        keyMap = keyMap.title()

        if keyMap[0] == "<":
            gui.warn("Shortcuts should not include chevrons: %s", keyMap)
            keyMap = keyMap[1:-1]

        if gui.GET_PLATFORM() != gui.MAC and 'Command' in keyMap:
            gui.warn("Shortcuts containing <Command> only supported on Mac")
            keyMap = keyMap.replace("Command", "Control")

        if gui.GET_PLATFORM() == gui.MAC and 'Alt' in keyMap:
            keyMap = keyMap.replace("Alt", "Option")
        elif gui.GET_PLATFORM() != gui.MAC and 'Option' in keyMap:
            keyMap = keyMap.replace("Option", "Alt")

        # fix any broken events from calling title... hacky!
        keyMap = keyMap.replace("Buttonpress", "ButtonPress")
        keyMap = keyMap.replace("Buttonrelease", "ButtonRelease")
        keyMap = keyMap.replace("Focusin", "FocusIn")
        keyMap = keyMap.replace("Focusout", "FocusOut")
        keyMap = keyMap.replace("Backspace", "BackSpace")

        gui.trace('Cleaned up to: %s', keyMap)
        return keyMap

    def _createDisplayName(self, keyMap):
        # create a shrunk-down display name (accelerator)
        acc = keyMap.replace("Control", "Ctrl")
        acc = acc.replace("Command", "Cmd")
        acc = acc.replace("Option", "Opt")
        acc = acc.replace("Key-", "")
        acc = acc.replace("-", "+")
        gui.trace('DisplayName made: %s', acc)
        return acc

    def _createShortcuts(self, shortcut):
        # try to fix numerics
        if self.menuBinding and shortcut[-1] in "0123456789" and "Key" not in shortcut:
            shortcut = shortcut[:-1] + "Key-" + shortcut[-1]

        # create two bindings if it ends in a single letter
        bits = shortcut.split('-')
        shortcuts = ['<'+shortcut+'>']

        # create both cases of the shortcut
        if bits[-1].upper() in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
            bits[-1] = bits[-1].swapcase()
            shortcuts.append('<'+'-'.join(bits)+'>')

        gui.trace('Shortcuts made: %s', shortcuts)
        return shortcuts

    def createBindings(self):
        if self.func is not None:
            for s in self.shortcuts:
                # auto created on Mac, so ignore ?!?
                if gui.GET_PLATFORM() == gui.MAC and self.menuBinding and 'Control' in s and 'Shift' in s:
                    gui.trace("Mac - skipping binding: %s", s)
                else:
                    gui.trace("Binding: %s to %s", s, self.func)
                    self.win.bind_all(s, self.func)

    def removeBindings(self):
        for s in self.shortcuts:
            gui.trace('Removing binding: %s', s)
            self.win.unbind_all(s)

    def changeBindings(self, state):
        if state.lower() == 'disabled': self.removeBindings()
        else: self.createBindings()

#####################################
# MAIN - for testing
#####################################
if __name__ == "__main__":
    print("This is a library class and cannot be executed.")
    sys.exit()