print('Loading widgets utility functions...')

from omfit_classes.utils_base import *
from omfit_classes.utils_math import array_info
from tkinter import Tk as tk
from tkinter import ttk
import tkinter.font as tkFont
import numpy as np
import pandas
import xarray
import collections
import platform
from uncertainties.unumpy import nominal_values, std_devs

# -------------
# Tk utils
# -------------

if platform.system() == 'Darwin':
    rightClick = 'Button-2'
    middleClick = 'Button-3'
    rightClickIndex = 2
    middleClickIndex = 3

else:
    rightClick = 'Button-3'
    middleClick = 'Button-2'
    rightClickIndex = 3
    middleClickIndex = 2

hide_ptrn = re.compile(r'^__.*__$')


def treeText(inv, strApex=False, n=-1, escape=True):
    """
    Returns the string that is shown in the OMFITtree for the input object `inv`

    :param inv: input object

    :param strApex: should the string be returned with apexes

    :param n: maximum string length

    :param escape: escape backslash characters

    :return: string representation of object `inv`
    """
    if isinstance(inv, str):
        if strApex:
            return short_str(repr(inv), n, escape)
        elif isinstance(inv, str):
            return short_str(repr(inv)[1:-1], n, escape)

    elif isinstance(inv, list):

        def listType(x):
            lst = []
            for k in x[:10]:
                if isinstance(k, str):
                    kr = repr(k)
                    if len(kr) > 12:
                        k = kr[:11] + '..'
                elif hasattr(k, '__iter__'):
                    kr = '<%s>' % k.__class__.__name__
                elif isinstance(k, (int, np.integer, float, np.float, complex, np.complex)):
                    kr = repr(k)
                else:
                    kr = '<object>'
                lst.append(kr)
            if len(x) > 10:
                lst.append('...')
            return lst

        return short_str('[%s]' % ', '.join(listType(inv)), n, escape)

    elif is_float(inv):
        return format(inv, '3.8g')

    elif isinstance(inv, np.ndarray):
        return array_info(inv)

    elif pandas is not None and isinstance(inv, pandas.DataFrame):
        ncol = len(inv.columns)
        if ncol > 5:
            return ', '.join(k for k in inv.columns[:5]) + '...'
        else:
            return ', '.join(k for k in inv.columns)

    elif pandas is not None and isinstance(inv, pandas.Series):
        return (', '.join(l for l in str(inv.describe()).split('\n')[:2] if '%' not in l)).replace('\n', '')

    elif xarray is not None and isinstance(inv, xarray.DataArray):
        return ', '.join('%s: %s' % (k, v) for k, v in zip(inv.dims, inv.shape))

    elif xarray is not None and isinstance(inv, xarray.Dataset):
        return ', '.join(['%s: %s' % (k, inv.dims[k]) for k in inv.dims])

    elif isinstance(inv, (dict, collections.abc.MutableMapping)):
        if hasattr(inv, 'dynaLoad') and inv.dynaLoad:
            return '--{\t?\t}--'

        vshow = [k for k in inv if not isinstance(k, str) or not re.match(hide_ptrn, k)]
        vhide = [k for k in inv if isinstance(k, str) and re.match(hide_ptrn, k)]
        values = '--{\t'
        if vshow:
            values += '%d' % len(vshow)
        if vshow and vhide:
            values += ' '
        if vhide:
            values += '(%d)' % len(vhide)
        values += '\t}--'

        for k in ['modifyOriginal', 'readOnly']:
            if hasattr(inv, k) and getattr(inv, k):
                values += ' ' + k + ' '

        return values

    elif inspect.isroutine(inv):
        if inv.__doc__ is not None:
            tmp = inv.__doc__.strip().split('\n')
            tmp = [line for line in map(lambda x: x.strip(), tmp) if line and not line.startswith(':') and not line.startswith('>')]
            if tmp:
                return short_str(' | '.join(tmp), n, escape)
        try:
            return short_str('(' + function_arguments(inv, [], True).replace('\n', '').replace('self,', '') + ')', n, escape)
        except TypeError:
            return short_str('built-in method', n, escape)

    else:
        return short_str(repr(inv), n, escape)


def ctrlCmd():
    return 'Control'
    # return 'Command'


def short_str(inv, n=-1, escape=True, snip='[...]'):
    """
    :param inv: input string

    :param n: maximum length of the string (negative is unlimited)

    :param escape: escape backslash characters

    :param snip: string to replace the central part of the input string

    :return: new string
    """
    l = len(inv)
    s = len(snip)
    if not l:
        inv = "\'\'"
    elif l > n and n > s + 1:
        nn = (n - s) / 2.0
        a = int(round(nn))
        b = int(nn)
        inv = inv[:a] + snip + inv[-b:]
    if escape:
        inv = re.sub(r'\\', r'\\\\', inv)
    return inv


def tkStringEncode(inv):
    r"""
    Tk requires ' ', '\', '{' and '}' characters to be escaped
    Use of this function dependes on the system and the behaviour can be
    set on a system-by-system basis using the OMFIT_ESCAPE_TK_SPACES environmental variable

    :param inv: input string

    :return: escaped string
    """
    if int(os.environ.get('OMFIT_ESCAPE_TK_SPACES', '1')) and len(inv):
        inv = re.sub(r'([ \\\{\}])', r'\\\1', treeText(inv, strApex=False, n=-1, escape=False))
    return inv


_defaultFont = {}


def OMFITfont(weight='', size=0, family='', slant=''):
    """
    The first time that OMFITfont is called, the system defaults are gathered

    :param weight: 'normal' or 'bold'

    :param size: positive or negative number relative to the tkinter default

    :param family: family font

    :return: tk font object to be used in tkinter calls
    """

    font = {'family': 'Helvetica', 'size': 11, 'weight': 'normal', 'slant': 'roman'}

    # save default font if not done yet
    if not len(_defaultFont):
        ttk_style = ttk.Style()
        # generate font
        f = tkFont.Font(font=ttk_style.lookup('TLabel', 'font'))
        _defaultFont.update(f.metrics())

    # make sure the default font has all the categories
    for key, val in list(font.items()):
        _defaultFont.setdefault(key, val)
    if not _defaultFont['family']:  # bad things happen if this somehow gets set to ''
        _defaultFont['family'] = 'Helvetica'
    if _defaultFont['size'] < 6:  # bad things happen if this somehow gets set to 0
        _defaultFont['size'] = 6
    # modifications to default font
    font = {}
    font['size'] = abs(_defaultFont['size']) + size
    if weight:
        font['weight'] = weight
    elif _defaultFont['weight']:
        font['weight'] = _defaultFont['weight']
    if slant:
        font['slant'] = slant
    elif _defaultFont['slant']:
        font['slant'] = _defaultFont['slant']
    if family:
        font['family'] = family
    elif _defaultFont['family']:
        font['family'] = _defaultFont['family']

    return (font['family'], font['size'], font['weight'], font['slant'])


def tk_center(win, parent=None, width=None, height=None, xoff=None, yoff=None, allow_negative=False):
    """
    Function used to center a tkInter GUI

    Note: by using this function tk does not manage the GUI geometry
    which is beneficial when switching between desktop on some window
    managers, which would otherwise re-center all windows to the desktop.

    :param win: window to be centered

    :param parent: window with respect to be centered (center with respect to screen if `None`)

    :width: set this window width

    :height: set this window height

    :param xoff: x offset in pixels

    :param yoff: y offset in pixels

    :param allow_negative: whether window can be off the left/upper part of the screen
    """
    win.update_idletasks()

    if parent is None:
        utils_tk._winfo_screen(reset=True)
        swidth, sheight, x0, y0 = screen_geometry()
    else:
        swidth, sheight, x0, y0 = _parse_tk_geometry(TKtopGUI(parent).geometry())

    if xoff is None:
        xoff = 0
    if yoff is None:
        yoff = 0

    _width = width
    if width is None:
        _width = win.winfo_reqwidth()

    _height = height
    if height is None:
        _height = win.winfo_reqheight()

    x = (swidth - _width) // 2 + x0 + xoff
    y = (sheight - _height) // 2 + y0 + yoff

    if not allow_negative:
        x = np.max([xoff, x])
        y = np.max([yoff, y])

    if width or height:
        win.geometry('%dx%d+%d+%d' % (_width, _height, x, y))
    else:
        win.geometry('+%d+%d' % (x, y))


# ----------------
# extra Tk widgets
# ---------------
class HelpTip(object):
    def __init__(self, widget=None, move=False):
        self.widget = widget
        self.tipwindow = None
        self.mode = 0
        self.location = None
        self.text = None

    def showtip(self, text, location=None, mode=None, move=True, strip=False):
        "Display text in helptip window"
        self.hidetip()
        if location is not None:
            self.location = location
        if mode is not None:
            self.mode = mode
        if text:
            if strip:
                text = text.strip()
            self.text = text

            if isinstance(text, list):
                text = '\n'.join(text)
            text = wrap(text, 100)

            self.tipwindow = tk.Toplevel(self.widget)
            self.tipwindow.withdraw()
            color = "#ffffe0"

            if self.mode == 1:
                # editor mode
                self.tipwindow.wm_transient(self.widget)
                wmax = np.max([max(np.array([len(line) for line in text.split('\n')])), 50])
                hmax = 11
                self.tipwindow.wm_title('Tip')
                self.widget.winfo_containing(self.widget.winfo_pointerx(), self.widget.winfo_pointery()).unbind("<Leave>")
                self.widget.bind_all("<Escape>", self.hidetip)
                self.widget.unbind_all("<F4>")
            else:
                # view mode
                if platform.system() != 'Darwin':
                    self.tipwindow.wm_overrideredirect(1)
                wmax = np.max(np.array([len(line) for line in text.split('\n')]))
                hmax = np.min([text.count('\n') + 1, 11])
                if hmax > 10 and move:
                    ttk.Label(self.tipwindow, text='Press F4 to detach', font=OMFITfont('bold', -1)).pack()
                    self.widget.winfo_containing(self.widget.winfo_pointerx(), self.widget.winfo_pointery()).bind("<Leave>", self.hidetip)
                self.widget.unbind_all("<Escape>")
                if not move:
                    self.widget.bind_all("<F4>", self.changeMode)

            frm = ttk.Frame(self.tipwindow, relief=tk.SOLID, borderwidth=1)
            frm.pack(side=tk.TOP, expand=tk.YES, fill=tk.BOTH)
            label = tk.Text(frm, width=wmax, height=hmax, undo=tk.TRUE, maxundo=-1, wrap='word')
            label.config(borderwidth=0, font=OMFITfont('normal', 0, 'Courier'))
            label.insert(1.0, text)
            label.delete('end -1 chars', 'end')
            label.grid(column=0, row=0, sticky='nsew')
            sb = ttk.Scrollbar(frm, command=label.yview, orient='vertical')
            if hmax > 10:
                sb.grid(column=1, row=0, sticky='ns')

            if self.mode == 1 and self.location:
                label.insert(1.0, '')
                label.focus_set()

                def onButton(event=None):
                    self.text = label.get(1.0, tk.END)
                    exec((self.location + '=' + repr(self.text)), globals(), locals())
                    self.changeMode()
                    return "break"

                bt = ttk.Button(frm, text='Update variables description <{ctrlCmd()}-Return>', command=onButton)
                bt.grid(column=0, row=1, sticky='nsew')
                label.bind(f'<{ctrlCmd()}-Return>', onButton)
            else:
                label.config(state=tk.DISABLED)

            label['yscrollcommand'] = sb.set
            frm.columnconfigure(0, weight=1)
            frm.rowconfigure(0, weight=1)

            if not move:
                x = self.widget.winfo_pointerx()
                y = self.widget.winfo_pointery()
                self.tipwindow.update_idletasks()

                # place window smack in the middle of the cursor
                self.x = int(x - self.tipwindow.winfo_reqwidth() / 2.0)
                self.y = int(y - self.tipwindow.winfo_reqheight() / 2.0)

                # move window off the edges
                if (self.x + self.tipwindow.winfo_reqwidth()) > screen_geometry()[0]:
                    self.x = self.x - self.tipwindow.winfo_reqwidth()
                if (self.y + self.tipwindow.winfo_reqheight()) > screen_geometry()[1]:
                    self.y = self.y - self.tipwindow.winfo_reqheight()
                if self.x < 0:
                    self.x = 0
                if self.y < 0:
                    self.y = 0
                self.tipwindow.wm_geometry("+%d+%d" % (self.x, self.y))
                self.tipwindow.update_idletasks()

                # place cursor smack in the middle of the window
                x = int(self.x + self.tipwindow.winfo_reqwidth() / 2.0)
                y = int(self.y + self.tipwindow.winfo_reqheight() / 2.0)
                self.tipwindow.event_generate('<Motion>', warp=True, x=x, y=y)
                self.tipwindow.update_idletasks()

                self.tipwindow.deiconify()
                self.tipwindow.bind("<Leave>", self.hidetip)

            else:
                self.movetip()
                self.tipwindow.deiconify()

    def hidetip(self, event=None):
        try:
            self.mode = 0
            self.tipwindow.destroy()
            self.tipwindow = None
        except Exception:
            pass

    def movetip(self, event=None):
        try:
            if self.tipwindow is not None:
                if event is None:
                    x = self.widget.winfo_pointerx()
                    y = self.widget.winfo_pointery()
                else:
                    x = event.x_root
                    y = event.y_root
                self.tipwindow.update_idletasks()
                self.x = x + 10
                self.y = y + 12
                if self.x > screen_geometry()[0] - (self.tipwindow.winfo_reqwidth() + 10):
                    self.x = self.x - self.tipwindow.winfo_reqwidth() - 20
                if self.y > screen_geometry()[1] - (self.tipwindow.winfo_reqheight() + 12):
                    self.y = self.y - self.tipwindow.winfo_reqheight() - 24
                self.tipwindow.wm_geometry("+%d+%d" % (self.x, self.y))
                if self.mode == 0:
                    self.tipwindow.after(25, self.movetip)
        except Exception as _excp:
            warnings.warn(repr(_excp))

    def changeMode(self, event=None):
        self.showtip(self.text, mode=np.mod(self.mode + 1, 2))


helpTip = HelpTip()


class ToolTip(object):
    """
    Tooltip recipe from
    http://www.voidspace.org.uk/python/weblog/arch_d7_2006_07_01.shtml#e387
    """

    @staticmethod
    def createToolTip(widget, text):
        toolTip = ToolTip(widget)

        def enter(event):
            toolTip.showtip(text)

        def leave(event):
            toolTip.hidetip()

        widget.bind('<Enter>', enter)
        widget.bind('<Leave>', leave)

    def __init__(self, widget):
        self.widget = widget
        self.tipwindow = None
        self.id = None
        self.x = self.y = 0

    def showtip(self, text):
        "Display text in tooltip window"
        self.text = text
        if self.tipwindow or not self.text:
            return
        x, y, _, _ = self.widget.bbox("insert")
        x = x + self.widget.winfo_rootx() + 27
        y = y + self.widget.winfo_rooty()
        self.tipwindow = tw = tk.Toplevel(self.widget)
        tw.wm_overrideredirect(1)
        tw.wm_geometry("+%d+%d" % (x, y))
        try:
            # For Mac OS
            tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, "help", "noActivates")
        except tk.TclError:
            pass
        label = tk.Label(tw, text=self.text, justify=tk.LEFT, background="#ffffe0", relief=tk.SOLID, borderwidth=1)
        label.pack(ipadx=1)

    def hidetip(self):
        tw = self.tipwindow
        self.tipwindow = None
        if tw:
            tw.destroy()


def dialog(message='Are You Sure?', answers=['Yes', 'No'], icon='question', title=None, parent=None, options=None, entries=None, **kw):
    """
    Display a dialog box and wait for user input

    Note:

    * The first answer is highlighted (for users to quickly respond with `<Return>`)

    * The last answer is returned for when users press `<Escape>` or close the dialog

    :param message: the text to be written in the label

    :param answers: list of possible answers

    :param icon: "question", "info", "warning", "error"

    :param title: title of the frame

    :param parent: tkinter window to which the dialog will be set as transient

    :param options: dictionary of True/False options that are displayed as checkbuttons in the dialog

    :param entries: dictionary of string options that are displayed as entries in the dialog

    :return: return the answer chosen by the user (a dictionary if options keyword was passed)
    """

    class _Dialog(object):
        def __init__(self, top, message='Are You Sure?', answers=['Yes', 'No'], icon='question', title=None, options=None, entries=None):
            self.top = top
            self.result = None
            self.options_var = {}
            self.entries_var = {}
            top = ttk.Frame(self.top)
            top.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES, padx=10, pady=10)

            frm = ttk.Frame(top)
            frm.pack(side=tk.TOP, fill=tk.X, expand=tk.YES, padx=0, pady=0)
            if icon in ["question", "info", "warning", "error"]:
                if title is None:
                    title = icon
                OMFITsrc = str(os.path.abspath(os.path.dirname(__file__)))
                img = tk.PhotoImage(file=os.path.join(OMFITsrc, 'extras', 'graphics', icon + ".gif"))
                icon_canvas = tk.Canvas(frm, width=64, height=64)
                icon_canvas.copy_image = img
                icon_canvas.create_image(32, 32, image=icon_canvas.copy_image)
                icon_canvas.pack(side=tk.LEFT, fill=tk.NONE, expand=tk.NO)

            ttk.Label(frm, text=message, justify=tk.LEFT).pack(side=tk.LEFT, fill=tk.X, expand=tk.YES)

            if options is not None:
                for item in list(options.keys()):
                    frm = ttk.Frame(top)
                    frm.pack(side=tk.TOP, fill=tk.X, expand=tk.YES, padx=0, pady=0)
                    self.options_var[item] = var = tk.BooleanVar()
                    tmp = ttk.Checkbutton(frm, variable=var, text=item)
                    tmp.var = var
                    tmp.pack(side=tk.LEFT, padx=5, pady=2)
                    var.set(options[item])

            if entries is not None:
                for item in list(entries.keys()):
                    frm = ttk.Frame(top)
                    frm.pack(side=tk.TOP, fill=tk.X, expand=tk.YES, padx=5, pady=2)
                    self.entries_var[item] = var = tk.StringVar()
                    ttk.Label(frm, text=item + ' ').pack(side=tk.LEFT)
                    tmp = ttk.Entry(frm, textvariable=var)
                    tmp.pack(side=tk.LEFT, fill=tk.X, expand=tk.YES)
                    var.set(entries[item])

            frm = ttk.Frame(top)
            frm.pack(side=tk.TOP, fill=tk.X, expand=tk.YES, padx=0, pady=0)
            for ii, answer in enumerate(answers):
                tmp = ttk.Button(frm, text=answer, command=lambda answer=answer: self.ok(answer))
                tmp.bind('<Return>', lambda event: event.widget.invoke())
                tmp.pack(side=tk.LEFT, fill=tk.X, expand=tk.YES)
                if ii == 0:
                    tmp.focus()
            self.top.bind('<Escape>', lambda event=None, answer=answers[-1]: self.ok(answer))
            self.top.protocol("WM_DELETE_WINDOW", lambda event=None, answer=answers[-1]: self.ok(answer))

            if title is None:
                title = 'message'
            self.top.wm_title(title[0].upper() + title[1:])

        def ok(self, x=None):
            if not len(self.options_var) and not len(self.entries_var):
                self.result = x
            else:
                self.result = [x]
                kw = {}
                for item in list(self.options_var.keys()):
                    kw[item] = bool(self.options_var[item].get())
                for item in list(self.entries_var.keys()):
                    kw[item] = self.entries_var[item].get()
                self.result.append(kw)
            self.top.destroy()

    if parent is None:
        parent = OMFITaux['rootGUI']
    try:
        frm_top = tk.Toplevel(parent)
    except Exception:
        parent = OMFITaux['rootGUI']
        frm_top = tk.Toplevel(parent)
    frm_top.withdraw()
    frm_top.transient(parent)
    d = _Dialog(frm_top, message=message, answers=answers, icon=icon, title=title, options=options, entries=entries)
    frm_top.resizable(False, False)
    tk_center(frm_top, parent)
    frm_top.deiconify()
    try:
        frm_top.grab_set()
    except tk.TclError:
        pass
    frm_top.update_idletasks()
    frm_top.wait_window(d.top)
    return d.result


def pickTreeLocation(startLocation=None, title='Pick tree location ...', warnExists=True, parent=None):
    """
    Function which opens a blocking GUI to ask user to pick a tree location

    :param startLocation: starting location

    :param title: title to show in the window

    :param warnExists: warn user if tree location is already in use

    :param parent: tkinter window to which the dialog will be set as transient

    :return: the location picked by the user
    """

    def onEscape(event=None):
        top.destroy()

    if parent is None:
        parent = OMFITaux['rootGUI']
    top = tk.Toplevel(parent)
    top.withdraw()
    top.transient(parent)
    frm = ttk.Frame(top)
    frm.pack(side=tk.TOP, expand=tk.YES, fill=tk.X, padx=5, pady=5)
    frm.grid_columnconfigure(1, weight=1)
    top.wm_title(title)
    outcome = [None]

    def onReturn(event=None):
        v2 = newLocation.get()
        try:
            eval(v2)
            if warnExists and 'No' == dialog(
                title='Location already exists', message='Proceed anyways?', answers=['Yes', 'No'], parent=top
            ):
                return
        except Exception:
            pass
        top.destroy()
        outcome[0] = v2

    ttk.Label(frm, text='To: ').grid(row=1, sticky=tk.E)
    newLocation = tk.OneLineText(frm, width=50, percolator=True)
    if startLocation is None:
        newLocation.set(OMFITaux['GUI'].focusRoot)
    else:
        newLocation.set(startLocation)
    tk_center(top, parent)
    e1 = newLocation
    e1.grid(row=1, column=1, sticky=tk.E + tk.W, columnspan=2)
    e1.focus_set()
    top.bind('<Return>', onReturn)
    top.bind('<KP_Enter>', onReturn)
    top.bind('<Escape>', onEscape)
    top.protocol("WM_DELETE_WINDOW", top.destroy)
    top.update_idletasks()
    top.deiconify()
    top.wait_window(top)
    return outcome[0]


stored_passwords = {}


def password_gui(title='Password', parent=None, key=None):
    """
    Present a password dialog box

    :param title: The title for the dialog box

    :param parent: The GUI parent

    :param key: A key for caching the password in this session
    """
    if key and key in stored_passwords:
        return stored_passwords[key]

    def onEscape(event=None):
        top.destroy()

    if parent is None:
        parent = OMFITaux['rootGUI']

    import tkinter as tk
    from tkinter import ttk

    top = tk.Toplevel(parent)
    top.withdraw()
    top.transient(parent)
    frm = ttk.Frame(top)
    frm.pack(side=tk.TOP, expand=tk.YES, fill=tk.X, padx=5, pady=5)
    frm.grid_columnconfigure(1, weight=1)
    top.wm_title(title)
    outcome = [None]

    def onReturn(event=None):
        outcome[0] = pass_phrase.get()
        top.destroy()

    ttk.Label(frm, text='Pass_phrase').grid(row=1, sticky=tk.E)
    pass_phrase = tk.Entry(frm, width=50, show='*')
    tk_center(top, parent)
    pass_phrase.grid(row=1, column=1, sticky=tk.E + tk.W, columnspan=2)
    pass_phrase.focus_set()
    top.bind('<Return>', onReturn)
    top.bind('<KP_Enter>', onReturn)
    top.bind('<Escape>', onEscape)
    top.protocol("WM_DELETE_WINDOW", top.destroy)
    top.update_idletasks()
    top.deiconify()
    top.wait_window(top)
    if key:
        stored_passwords[key] = outcome[0]
    return outcome[0]


# ----------------
# Tk events
# ---------------
class eventBindings(object):
    def __init__(self):
        self.descs = []
        self.widgets = []
        self.events = []
        self.callbacks = []
        self.tags = []
        self.default_desc_event = {}

    def add(self, description, widget, event, callback, tag=None):
        if '_' in description:
            raise ValueError('The saving/restoring of key bindings cannot handle _ in the description')

        # set event bindings for more than one widget
        if description in self.descs:
            ind = self.descs.index(description)
            if tag:
                widget.tag_bind(tag + '_action', self.events[ind], callback)
            else:
                try:
                    widget.bind(self.events[ind], callback)
                except tk.TclError:
                    if 'ISO' in event:
                        pass
                    else:
                        raise
            self.widgets[ind].append(widget)
            return

        # bind
        if tag:
            widget.tag_bind(tag + '_action', event, callback)
            if platform.system() == 'Darwin' and 'Control' in event:
                widget.tag_bind(tag + '_action', re.sub('Control', 'Command', event), callback)
        else:
            try:
                widget.bind(event, callback)
            except tk.TclError:
                if 'ISO' in event:
                    pass
                else:
                    raise
            if platform.system() == 'Darwin' and 'Control' in event:
                widget.bind(re.sub('Control', 'Command', event), callback)

        # add to list
        self.descs.append(description)
        self.widgets.append([widget])
        self.events.append(event)
        self.callbacks.append(callback)
        if tag:
            self.tags.append(tag + '_action')
        else:
            self.tags.append(tag)
        self.default_desc_event[description.replace(' ', '_')] = event

    def remove_widget(self, widget):
        for wi in range(len(self.widgets)):
            if widget in self.widgets[wi]:
                self.widgets[wi].remove(widget)

    def set(self, description, event):
        try:
            ind = self.descs.index(description)
        except ValueError:
            raise ValueError('Unable to set %s because it is not yet bound to a widget' % description)
        if event == self.events[ind]:
            return
        for w in list(self.widgets[ind]):
            try:
                if self.tags[ind]:
                    w.tk.call(w._w, 'tag', 'bind', self.tags[ind], self.events[ind], '')
                    # Doesn't exist -> w.tag_unbind(self.tags[ind],self.events[ind])
                else:
                    w.unbind(self.events[ind])
            except tk.TclError as _excp:
                if 'bad window path name' in str(_excp) or "application has been destroyed" in str(_excp):
                    self.remove_widget(w)
                    continue
            if self.tags[ind]:
                w.tag_bind(self.tags[ind], event, self.callbacks[ind])
            else:
                w.bind(event, self.callbacks[ind])
            self.events[ind] = event

    def get(self, description):
        for di, d in enumerate(self.descs):
            if d == description:
                return self.events[di]

    def print_event_in_bindings(self, w, event, ind):
        bindings = set()
        for cls in w.bindtags():
            bindings |= set(w.bind_class(cls))  # s |= t means: update set s, adding elements from t
        bindings = [s.replace('Key-', '') for s in list(bindings)]
        printi(event in bindings, self.events[ind] in bindings)

    def printAll(self):
        from utils import printi

        for di, d in enumerate(self.descs):
            printi('%s: %s' % (d, self.events[di]))  # ,self.callbacks[di],self.widgets[di]


global_event_bindings = eventBindings()

# ----------------
# import tk elements
# ---------------
from utils_tk import *
import utils_tk

# ----------------
# screen geometry
# ---------------
def _parse_tk_geometry(geom):
    """
    Function that parses string returned by tk geometry()

    :param geom: string in the format width, height, x, y `400x300+30+55`

    :return: tuple with 4 integers for width, height, x, y
    """
    geometry = []
    geometry.append(int(re.sub(r'([0-9]*)x([0-9]*)\+([\-0-9]*)\+([\-0-9]*)', r'\1', geom)))
    geometry.append(int(re.sub(r'([0-9]*)x([0-9]*)\+([\-0-9]*)\+([\-0-9]*)', r'\2', geom)))
    geometry.append(int(re.sub(r'([0-9]*)x([0-9]*)\+([\-0-9]*)\+([\-0-9]*)', r'\3', geom)))
    geometry.append(int(re.sub(r'([0-9]*)x([0-9]*)\+([\-0-9]*)\+([\-0-9]*)', r'\4', geom)))
    return geometry


_screen_geometry = []


def screen_geometry():
    """
    Function returns the screen geometry

    :return: tuple with 4 integers for width, height, x, y
    """
    if not len(_screen_geometry):
        t = tk.Tk()  # new window
        t.withdraw()

        t.attributes("-alpha", 0)
        try:
            t.state('zoomed')
        except Exception:
            t.attributes('-fullscreen', True)
        t.update_idletasks()
        geom = _parse_tk_geometry(t.geometry())
        geom[0] = t.winfo_reqwidth()
        geom[1] = t.winfo_reqheight()

        _screen_geometry.extend(geom)

        screen = [t.winfo_screenwidth(), t.winfo_screenheight()]
        _screen_geometry[0] = screen[0] - _screen_geometry[2]
        _screen_geometry[1] = screen[1] - _screen_geometry[3]

        t.destroy()

    return _screen_geometry


_displays = [0]


def wmctrl():
    """
    This function is useful when plugging in a new display in OSX and the OMFIT window disappear
    To fix the issue, go to the XQuartz application and select the OMFIT window from the menu `Window > OMFIT ...`
    Then press F8 a few times until the OMFIT GUI appears on one of the screens.
    """
    # screen sizes
    with open(os.devnull, 'w') as nul_f:
        child = subprocess.Popen([OMFITsrc + '/../bin/hmscreens', "-info"], stderr=nul_f, stdout=subprocess.PIPE)
    hms_out, std_err = map(b2s, child.communicate())

    # parse output of hmscreens
    screens = []
    for ks, s in enumerate(hms_out.strip().split('\n\n')):
        s = re.sub(r'\}', ']', re.sub(r'\{', '[', s))
        s = re.sub(r'([\w \(\)]+):(.*)\n*', r'"\1":"\2",', s)
        s = re.sub(r'\n', ',', s)
        screens.append(eval('{%s}' % s))
        for item in screens[-1]:
            try:
                screens[-1][item] = eval(screens[-1][item])
                if isinstance(screens[-1][item], list):
                    screens[-1][item] = np.array(screens[-1][item])
            except (TypeError, NameError):
                pass

    # loop through displays
    display = _displays[0] = np.mod(_displays[0] + 1, len(screens))

    # find geometry according to Tk inter
    minx = 0
    maxx = 0
    miny = 0
    maxy = 0
    for s in screens:
        minx = np.min([minx, s['Global Position'][0, 0]])
        miny = np.min([miny, s['Global Position'][0, 1]])
        maxx = np.max([maxx, s['Global Position'][1, 0]])
        maxy = np.max([maxy, s['Global Position'][1, 1]])

    # change window geometry accordingly
    g = [
        screens[display]['Size'][0],
        screens[display]['Size'][1] - 44 * int(screens[display]['Resolution(dpi)'][1] // 72),
        screens[display]['Global Position'][0, 0] - minx,
        screens[display]['Global Position'][1, 1] - maxy - 44 * int(screens[display]['Resolution(dpi)'][1] // 72),
    ]
    g = '%dx%d+%d+%d' % (g[0], g[1], g[2], g[3])
    OMFITaux['rootGUI'].geometry(g)
    printi('Switched to display #%d: %s' % (display, g))
    OMFITaux['rootGUI'].update_idletasks()


def TKtopGUI(item):
    """
    Function to reach the TopLevel tk from a GUI element

    :param item: tk GUI element

    :return: TopLevel tk GUI
    """
    while True:
        try:
            if item.master is None:
                break
            item = item.master
        except Exception:
            return item
    return item


if __name__ == '__main__':

    main = tk.Tk()
    password_gui(parent=main)
    main.mainloop()
