#                       Copyright 2020 Saad Mairaj
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.

import re
import sys

if sys.version_info.major == 2:
    import ttk
    import Tkinter as _tk
    from Tkinter import _cnfmerge
elif sys.version_info.major == 3:
    from tkinter import ttk
    from tkinter import _cnfmerge
    import tkinter as _tk

try:
    import colour as C
except ImportError as e:
    import os
    if sys.version_info.major == 2:
        import tkMessageBox as ms
    elif sys.version_info.major == 3:
        import tkinter.messagebox as ms
    error_win = _tk.Tk()
    error_win.withdraw()
    message = """
    Module "tkmacosx" is dependent on
    "colour" Module.

    Do you want to install it with pip now?
    Or install it manually later.

    Link to the colour github:
    https://github.com/vaab/colour"""
    if ms._show(e, message, 'warning', 'yesno') == 'yes':
        error_win.destroy()
        os.system('pip install colour')
        import colour as C
    else:
        sys.exit(0)

import stadiumpy.variables as tkv

def delta(evt):
    """Modified delta to work with all platforms."""
    if evt.num == 5 or evt.delta < 0:
        return -1
    return 1


def _agsmerge(args):
    """Internal functions.\n
    Merges lists/tuples."""
    a = []
    if isinstance(args, (tuple, list)):
        for i in args:
            if isinstance(i, (tuple, list)):
                a.extend(i)
            else:
                a.append(i)
    return a or args


def check_function_equality(func1, func2):
    """Checks if two functions are same."""
    return func1.__code__.co_code == func2.__code__.co_code


def _bind(cls=None, *ags, **kw):
    """Internal function.\n
    Binds and unbinds sequences with any name given as className."""
    cls = kw.pop('cls', cls) or (
        ags.pop(0) if ags and isinstance(ags[0], _tk.Misc) else None)
    if not cls:
        raise ValueError('Counld not refer to any class instance "cls".')
    if len(kw) < 1 and len(ags) > 0:
        return [_bind(cls=cls, **i) for i in ags]

    classname = kw['className'] + str(cls)
    bindtags = list(cls.bindtags())
    if kw.get('func'):
        if classname in bindtags:
            bindtags.remove(classname)
            _bind(cls, className=kw['className'], sequence=kw['sequence'])
        bindtags.append(classname)
        cls.bindtags(tuple(bindtags))
        return cls.bind_class(classname, sequence=kw['sequence'],
                              func=kw['func'], add=kw.get('add', '+'))
    else:
        if classname in bindtags:
            bindtags.remove(classname)
        cls.bindtags(tuple(bindtags))
        cls.unbind_class(classname, kw['sequence'])


def _on_press_color(cls=None, cnf={}, **kw):
    """Internal function. Do not call directly.\n
    Give gradient color effect used for activebackground.
    Returns ids"""
    kw = _cnfmerge((cnf, kw))
    cls = kw.get('cls', cls)
    w = cls.cnf.get('height', cls.winfo_width())
    h = cls.cnf.get('height', cls.winfo_height())
    tag = kw.get('tag', 'press')
    state = kw.get('state', 'normal' if cls.cnf.get(
                   'state') in ('active', 'pressed') else 'hidden')
    if not cls:
        raise ValueError('Counld not refer to any class instance "cls".')
    if kw.get('color') is None:
        kw.pop('color', None)
    width = cls.coords(tag) or 0
    if isinstance(width, (list, tuple)) and len(width) > 3:
        width = int(width[2])
    all_activebg_ids = cls.find('withtag', tag)
    cond1 = bool(h == len(all_activebg_ids))
    cond2 = bool(w == width)
    # [issue-1] Need a better approach for getting "cond3"
    cond3 = bool(kw.get('color', (False,)) == cls.cnf.get('activebackground'))
    if cond1 and cond2 and cond3 and not kw.get('force_create', False):
        return
    cls.cnf['activebackground'] = kw.get('color', cls.cnf.get(
        'activebackground', ("#505966", "#505966")))
    cls.delete(tag)
    ids = []
    # This is the default color for mac
    cr = kw.get('color', ("#505966", "#505966"))
    if isinstance(cr, (tuple, list)):
        if None in cr:
            cr = list(cr)
            cr.remove(None)
            cr = cr[0]
    if not isinstance(cr, (tuple, list)):
        cr = (C.Color(cr), C.Color(cr))
    else:
        cr = (C.Color(cr[0]), C.Color(cr[1]))
    for i, j in enumerate(tuple(cr[0].range_to(cr[1], kw.get('height', h)))):
        ags = (0, i, kw.get('width', w), i)
        cnf = {'fill': j, 'tag': tag, 'state': state}
        ids.append(cls._create('line', ags, cnf))
    cls.tag_lower(tag)     # keep the tag last
    return tuple(ids)


def _info_button(master, cnf={}, **kw):
    """Internal Function.\n
    This function takes essentials parameters to give
    the approximate width and height accordingly. \n
    It creates a ttk button and use all the resources given
    and returns width and height of the ttk button, after taking
    width and height the button gets destroyed also the custom style."""
    kw = _tk._cnfmerge((cnf, kw))
    cnf = dict(**kw)
    kw.pop('height', None)
    def _get_height(master, **kw):
        """Creates temp tkinter button to get approximate 
        height when -height parameter is given."""
        y_dif = 2 if kw.get('pady') else 0
        y_dif += -kw.get('pady', 1) + 2 if kw.get('image')\
                    and kw.get('compound') in ('top', 'bottom') else -2
        tmp = _tk.Button(master, **kw)
        height = max(24, tmp.winfo_reqheight()+y_dif)
        tmp.destroy()
        return height
    
    name = '%s.TButton' % master
    _style_tmp = ttk.Style()
    _style_tmp.configure(name, font=kw.pop('font', None))
    _style_tmp.configure(name, padding=(kw.pop('padx', 0), kw.pop('pady', 0)))
    tmp = ttk.Button(master, style=name, **kw)
    if cnf.get('height', None) and False:
        geo = [tmp.winfo_reqwidth(), _get_height(master, **cnf)]
    else:
        geo = [tmp.winfo_reqwidth(), tmp.winfo_reqheight()]
    # [issue-2] Need fix --- doesn't really delete the custom style
    del _style_tmp
    tmp.destroy()
    return geo


def check_appearance(cmd='defaults read -g AppleInterfaceStyle'):
    """### Checks DARK/LIGHT mode of macos. Returns Boolean.
    #### Args:
    - `cmd`: Give commands. Like to check DARK/LIGHT mode \
            the command is `'defaults read -g AppleInterfaceStyle'` .
    """
    import subprocess
    out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, 
                                stderr=subprocess.PIPE,
                                universal_newlines=True, 
                                shell=True).communicate()
    if out:
        return True
    elif err:
        return False


def get_shade(color, shade, mode='auto'):
    """### Darken or Lighten a shade of color.
    #### Args:
    1. `color`: Give a color as either HEX or name of the color.
    2. `shade`: The amount of change required. Takes float \
                between 0.0 to 1.0 eg: shade=0.225.
    3. `mode`:
        - `'-'` for darker shade.
        - `'+'` for lighter shade.
        - `'auto-110'` automatically decide lighter or \
                       darker. where 110 is the intensity.

    return hexcode"""
    op = {'+': lambda x, y: float(x+y),
          '-': lambda x, y: float(x-y)}
    if isinstance(color, str):
        color = list(float(i/65535.0)
                     for i in _tk._default_root.winfo_rgb(color))
    if 'auto' in mode:
        intensity = (110.0 if len(mode) <= 4 else float(
            mode.split('-')[1])) / 1000.0
        color_intensity = float(
            color[0]*0.299 + color[1]*0.587 + color[2]*0.114)
        mode = '-' if color_intensity > intensity else '+'
        if color_intensity > intensity*2 or color_intensity < intensity/2:
            shade += shade
    if mode not in op:
        raise ValueError(
            'Invalid mode "{}", Takes only "-" or "+"'. format(mode))
    for index, c in enumerate((op[mode](c, shade) for c in color)):
        if c > 1.0:
            c = 1.0
        elif c < 0.0:
            c = 0.0
        color[index] = int(c*255.0)
    return '#%02x%02x%02x' % (color[0], color[1], color[2])


class _Canvas(_tk.Widget):
    """Internal Class."""

    def __init__(self, master=None, cnf={}, **kw):
        _tk.Widget.__init__(self, master, 'canvas', cnf, kw)

    def find(self, *args):
        """Internal function."""
        return self._getints(
            self.tk.call((self._w, 'find') + args)) or ()

    def bbox(self, *args):
        """Return a tuple of X1,Y1,X2,Y2 coordinates for a rectangle
        which encloses all items with tags specified as arguments."""
        return self._getints(
            self.tk.call((self._w, 'bbox') + args)) or None

    def coords(self, *args):
        """Return a list of coordinates for the item given in ARGS."""
        return [self.tk.getdouble(x) for x in
                self.tk.splitlist(
            self.tk.call((self._w, 'coords') + args))]

    def _create(self, itemType, args, kw):  # Args: (val, val, ..., cnf={})
        """Internal function."""
        args = _tk._flatten(args)
        cnf = args[-1]
        if isinstance(cnf, (dict, tuple)):
            args = args[:-1]
        else:
            cnf = {}
        return self.tk.getint(self.tk.call(
            self._w, 'create', itemType,
            *(args + self._options(cnf, kw))))

    def delete(self, *args):
        """Delete items identified by all tag or ids contained in ARGS."""
        self.tk.call((self._w, 'delete') + args)

    def itemcget(self, tagOrId, option):
        """Return the resource value for an OPTION for item TAGORID."""
        return self.tk.call(
            (self._w, 'itemcget') + (tagOrId, '-'+option))

    def tag_lower(self, *args):
        """Lower an item TAGORID given in ARGS
        (optional below another item)."""
        self.tk.call((self._w, 'lower') + args)

    def tag_raise(self, *args):
        """Raise an item TAGORID given in ARGS
        (optional above another item)."""
        self.tk.call((self._w, 'raise') + args)

    def rounded_rect(self, ags=(), *args, **kw):
        'Internal function.'
        x, y, w, h, c = _agsmerge((ags, args))
        ids = []
        cnf = dict(kw)
        for i in ('extent', 'start', 'style'):
            cnf.pop(i, None) 
        for i in ('joinstyle', 'smooth', 'slinesteps'):
            kw.pop(i, None)
        points = (  # Arc points:-
            (x, y, x+2*c, y+2*c),
            (x, y+h-2*c, x+2*c, y+h),
            (x+w-2*c, y+h-2*c, x+w, y+h),
            (x+w-2*c, y, x+w, y+2*c),
            # Polygon points:-
            (x+c, y, x+w-c, y),
            (x+c, y+h, x+w-c, y+h),
            (x, y+c, x, y+h-c),
            (x+w, y+c, x+w, y+h-c))

        for i in range(len(points)):
            if i <= 3:
                kw['start'] = 90*(i+1)
                ids.append(self._create('arc', points[i], kw))
            else:
                ids.append(self._create('polygon', points[i], cnf))
        return tuple(ids)

    def _rounded(self, ags=(), *args, **kw):
        x1, y1, x2, y2, c = _agsmerge((ags, args))
        ids = []
        points = (  # Arc points:-
            (x2-c-1, y1, x2-1, y1+c),
            (x1, y1, x1+c, y1+c),
            (x1, y2-c-1, x1+c, y2-1),
            (x2-c, y2-c, x2-1, y2-1),
            # Rectangle points:-
            (x1+c/2, y1, x2-c/2, y2),
            (x1, y1+c/2, x2, y2-c/2))

        kw['start'], kw['outline'] = 0, ''
        for i in range(len(points)):
            if i <= 3:
                ids.append(self._create('arc', points[i], kw))
                kw['start'] += 90
            else:
                kw.pop('start', None)
                kw['width'] = 0
                ids.append(self._create('rectangle', points[i], kw))
        return tuple(ids)


class _BaseWidget(_Canvas):
    """Internal class.\n
    Settings class for Widgets. Do not call directly."""

    _buttons = []  # list of all buttons
    _features = ('overbackground', 'overforeground', 'activeimage', 'activebitmap', 
                 'anchor', 'bitmap', 'bordercolor', 'borderless', 'command', 'compound', 
                 'disabledforeground', 'justify', 'disabledbackground', 'fg', 'font', 
                 'foreground', 'height', 'image', 'overrelief', 'padx', 'pady', 'repeatdelay', 
                 'repeatinterval', 'text', 'textvariable', 'underline', 'width', 'state', 
                 'focusthickness', 'focuscolor', 'highlightbackground', 'activebackground', 
                 'activeforeground')

    def _mouse_state_condition(self):
        """Internal function.\n
        True if state is normal and cursor is on the widget."""
        con1 = bool(self.cnf.get('state') not in 'disabled')
        con2 = bool(self.winfo_containing(*self.winfo_pointerxy()) == self)
        return con1 and con2

    def _make_dictionaries(self, cnf={}, kw={}):
        """Internal function.\n
        Merges kw into cnf and removes None values."""
        kw['bordercolor'] = kw['highlightbackground'] = kw.get('bordercolor', kw.get('highlightbackground'))
        kw = {k: v for k, v in kw.items() if v is not None}
        cnf.update(kw)
        cnf = {k: v for k, v in cnf.items() if v is not None}
        cnf['fg'] = cnf['foreground'] = kw.get('foreground', kw.get('fg', cnf.get('fg', 'black')))
        if cnf.get('textvariable', '') != '':
            cnf['text'] = cnf['textvariable'].get()
        if kw.get('activebackground'):
            cnf.pop('activebackground')
        return cnf, kw

    def _set_trace(self, kw):
        """Internal function."""
        for i in ('overforeground', 'foreground', 'fg', 'activeforeground'):
            if isinstance(kw.get(i), _tk.Variable):
                var = kw[i]
                cbname = var.trace_variable('w', lambda a, b, c, i=i, var=var,
                                cls=self: cls.config({i: var.get()}))
                if (self, i) in tkv._all_traces_colorvar:
                    v, cb = tkv._all_traces_colorvar.get((self, i))
                    v.trace_vdelete('w', cb)
                    tkv._all_traces_colorvar[(self, i)] = (var, cbname)
                else:
                    tkv._all_traces_colorvar[(self, i)] = (var, cbname)
                kw[i] = var.get()
        return kw

    def _create_items(self, cmd, safe_create=False, **kw):
        """Internal function.\n
        Checks and creates (text, image, bitmap, border, 
        bordercolor, takefocus ring*) items."""

        def check_tag(tag):
            """Internal function.\n
            If `cmd="check"` and the tag does not exist then
            the tag is created, but if `cmd="create"` and
            safe_create=True this will delete the tag if exists
            and creates a new tag  with same settings."""
            if cmd == 'check':
                c = False
            elif cmd == 'create':
                c = True
            else:
                raise ValueError(
                    '%s is not a valid command! Takes -create, -check' % cmd)
            cond1 = bool(not self.find('withtag', tag) or c)
            cond2 = bool(tag not in kw.get('avoid', []))
            if safe_create and cond1 and cond2:
                self.delete(tag)
            return cond1 and cond2

        ids = []
        cond_image = bool(self.cnf.get('image',
                        self.cnf.get('activeimage', '') != ''))
        cond_bitmap = bool(self.cnf.get('bitmap',
                        self.cnf.get('activebitmap', '') != ''))

        # Text item.
        if check_tag('_txt') and self.cnf.get('text'):
            fill = self.cnf.get('foreground', 'black')
            if self['state'] in ('pressed', 'active'):
                fill = self.cnf.get('activeforeground', 'white')
            ids.append(self._create('text', (0, 0), {
                       'text': None, 'tag': '_txt', 'fill': fill}))
        # Image item (image and activeimage).
        if check_tag('_img') and cond_image:
            img = self.cnf.get('image', '')
            if self['state'] in ('pressed', 'active'):
                img = self.cnf.get('activeimage', img)
            ids.append(self._create('image', (0, 0), {
                       'image': None, 'tag': '_img', 'image': img}))
        # Bitmap items (bitmap and activebitmap).
        if check_tag('_bit') and cond_bitmap:
            bit = self.cnf.get('image', '')
            if self['state'] in ('pressed', 'active'):
                bit = self.cnf.get('activeimage', bit)
            ids.append(self._create('bitmap', (0, 0), {
                       'bitmap': None, 'tag': '_bit', 'bitmap': bit}))
        # Border color item.
        if check_tag('_bd_color'):
            _bd_points = (0, -1,
                          self.cnf.get('width', 87),
                          self.cnf.get('height', 24)+3, 7)  # was 6
            ids.append(self.rounded_rect(_bd_points, width=6,
                                         tag='_bd_color', style='arc',
                                         outline=self.cnf.get('bordercolor',
                                         get_shade(self['bg'], 0.04, 'auto-120'))))
        # Border item.
        if check_tag('_border'):
            bo_color = get_shade(self['bg'], 0.1, 'auto-120')
            h = 4
            if int(self['highlightthickness']):
                h += 1
            _bo_points = (2, 2,
                          self.cnf.get('width', 87)-5,
                          self.cnf.get('height', 24)-h,
                          4)  # was 3
            ids.append(self.rounded_rect(_bo_points, width=1,
                                         outline=bo_color, smooth=1,
                                         tag='_border', style='arc', 
                                         state='hidden' if self['state'] in (
                                             'pressed', 'active') else 'normal'))
        # Takefocus highlight ring.
        if check_tag('_tf'):
            # takefocuswidth can be changed.
            w = self.cnf.get('focusthickness', 2)
            diff1 = diff2 = (int(self['highlightthickness'])*2)+(w*2)
            if diff2 == (w*2):
                diff2 -= 1
            _tk_points = (w+int(self['highlightthickness']),
                          w+int(self['highlightthickness']),
                          self.cnf.get('width',  87)-diff1,
                          self.cnf.get('height', 24)-diff2, 4)
            ids.append(self.rounded_rect(_tk_points, width=w, style='arc',
                                         outline=self.cnf.get('focuscolor', '#81b3f4'), 
                                         tag='_tf', state='hidden'))
        return tuple(ids)

    def _get_functions(self, cmds, kw={}):
        """Internal function.\n
        Contains all the required functions."""

        def _borderless_support(cnf={}, **kw):
            _return = self.master._configure('configure', cnf, kw)
            kw = _cnfmerge((cnf, kw))
            if kw.get('bg') or kw.get('background'):
                for i in self._buttons:
                    if i['borderless']:
                        i.cnf['highlightbackground'] = i.cnf['bordercolor'] = i.master['bg']
                        _Canvas._configure(i, ('itemconfigure', '_bd_color'), {
                            'outline': i.master['bg']}, None)
                        _Canvas._configure(i, 'configure', {
                            'highlightbackground': i.master['bg']}, None)
            return _return

        def if_state(fn):
            """Runs function only if state is normal."""
            def wrapper(*a, **k):
                if self.cnf.get('state') not in 'disabled':
                    return fn(*a, **k)
            return wrapper

        def over_img_bit(seq, key):
            """Resets coords for text with overimage/overbitmap."""
            if self.cnf['state'] in ('active', 'pressed'):
                return
            tag = '_img' if key == 'image' else '_bit'
            if seq == 'enter':
                _Canvas._configure(self, ('itemconfigure', tag), 
                                   {key: kw.get('over'+key)}, None)
            elif seq == 'leave':
                _Canvas._configure(self, ('itemconfigure', tag), 
                                   {key: self.cnf.get(key, '')}, None)
            self._set_coords(self._get_options(
                                ('_txt', '_img', '_bit'), self.cnf))
        
        def overbg(seq):
            """Implement overbackground properly."""
            if seq == 'enter':
                _Canvas._configure(self, 'configure', {'bg': kw.get('overbackground')}, None)
                _Canvas._configure(self, ('itemconfigure', '_border'), 
                    {'outline': get_shade(kw.get('overbackground'), 0.1, 'auto-120')}, None)
            elif seq == 'leave':
                self._configure('configure', {'bg': self._org_bg}, None)
        
        def overfg(seq):
            """Handle overforeground, 
            foreground and activeforeground."""
            if self.cnf['state'] in ('active', 'pressed'):
                return
            if seq == 'enter':
                _Canvas._configure(self, ('itemconfigure', '_txt'), 
                    {'fill': self.cnf.get('overforeground', 'black')}, None)
            elif seq == 'leave':
                _Canvas._configure(self, ('itemconfigure', '_txt'), 
                    {'fill': self.cnf.get('fg', 'black')}, None)

        binds = {
            'overrelief': {'<Enter>': if_state(lambda _: self._configure(
                                'configure', {'_relief': kw.get('overrelief')})),
                           '<Leave>': if_state(lambda _: self._configure(
                                'configure', {'_relief': self._rel[0]}))},

            # [issue-3] doesn't change if overrelief is on.
            # [issue-3] (FIXED) using after with 0ms delay fixes the issue. To be safe delay is 1ms.
            'overforeground': {'<Enter>': if_state(lambda _: self.after(1, overfg, 'enter')),
                               '<Leave>': if_state(lambda _: overfg('leave'))},
            
            'overbackground': {'<Enter>': if_state(lambda _: overbg('enter')), 
                               '<Leave>': if_state(lambda _: overbg('leave')) },

            'overimage': {'<Enter>': if_state(lambda _: over_img_bit('enter', 'image')),
                            '<Leave>': if_state(lambda _: over_img_bit('leave', 'image'))},

            'overbitmap': {'<Enter>': if_state(lambda _: over_img_bit('enter', 'bitmap')),
                             '<Leave>': if_state(lambda _: over_img_bit('leave', 'bitmap'))},

            'takefocus': {'<FocusIn>': lambda _: _Canvas._configure(self, (
                            'itemconfigure', '_tf'), {'state': 'normal'}, None),
                          '<FocusOut>': lambda _: _Canvas._configure(self, (
                            'itemconfigure', '_tf'), {'state': 'hidden'}, None)},
        }

        other_functions = {
            'borderless': _borderless_support,

            'textvariable': {'w': lambda *a: _Canvas._configure(self, (
                            'itemconfigure', '_txt'), {'text': self.cnf.get(
                                                       'textvariable').get()}, None)},

            '_activebg': lambda: _on_press_color(*self._get_options(
                                                 'activebackground', self.cnf)),
        }
        funcs = _cnfmerge((binds, other_functions))
        if isinstance(cmds, (list, tuple)):
            value = funcs.copy()
            for i in cmds:
                value = value.get(i, {})
            return value
        else:
            return funcs.get(cmds, {})

    def _get_options(self, cmd, cnf={}, **kw):
        """Internal function."""
        kw = _cnfmerge((cnf, kw))
        # If more than one commands are given.
        # returns Union[list, dict, tuple(list, dict)]
        if isinstance(cmd, (list, tuple)):
            ags, cnf = [], {}
            for i in cmd:
                opts = self._get_options(i, kw)
                if isinstance(opts, (list, tuple)):
                    ags.append(opts)
                elif isinstance(opts, dict):
                    cnf.update(opts)
            if isinstance(opts, (list, tuple)) and isinstance(opts, dict):
                return ags, cnf
            return ags or cnf

        elif cmd == 'activebackground':
            color = None
            if kw.get(cmd, '') != '':
                color = kw.get(cmd, self.cnf.get(cmd))
            return [self, {'tag': '_activebg',
                           'width':  self.winfo_width(),#kw.get('width', 
                                        # self.cnf.get('width', self.winfo_width())),
                           'height': self.winfo_height(), #kw.get('height', 
                                        # self.cnf.get('height', self.winfo_height())),
                           'color': color}]
        
        elif cmd == 'activeforeground':
            if self['state'] in ('pressed', 'active'):
                return (('itemconfigure', '_txt'), {
                        'fill': self.cnf.get(cmd, 'white')}, None)

        elif cmd == 'activebitmap' or cmd == 'activeimage':
            tag = '_bit' if cmd == 'activebitmap' else '_img'
            name = cmd.rsplit('active')[-1]
            if self['state'] in ('pressed', 'active'):
                return (('itemconfigure', tag), {
                        name: self.cnf.get(cmd, '')}, None)

        elif cmd == 'bitmap' or cmd == 'image':
            tag = '_bit' if cmd == 'bitmap' else '_img'
            if kw.get(cmd, '') != '':
                state = 'disabled' if self.cnf.get('state') in (
                    'disabled', 'disable') else 'normal'
                cnf = dict(state=state, **{i: kw.get(i, self.cnf.get(i))
                                           for i in ('anchor', cmd)})
                return (('itemconfigure', tag), cnf, None)
            elif kw.get(cmd) == '':
                self.delete(tag)

        elif cmd == 'borderless' or cmd == 'bordercolor' or cmd == 'highlightbackground':
            cnf = {}
            if bool(kw.get('borderless')) or self.cnf.get('borderless'):
                if not check_function_equality(self.master.config, self._get_functions('borderless', kw)):
                    self.master.config = self.master.configure = self._get_functions(
                        'borderless', kw)
                self.cnf['highlightbackground'] = self.cnf['bordercolor'] = self.master['bg']
                cnf[1] = [('itemconfigure', '_bd_color'), {'outline': self.master['bg']}, None]
                cnf[2] = ['configure', {'highlightbackground': self.master['bg']}, None]
                return cnf
            elif not bool(kw.get('borderless', True)) or not self.cnf.get('borderless'):
                if self.cnf.get('bordercolor') == self.master['bg']:
                    self.cnf.pop('bordercolor', None)
                    self.cnf.pop('highlightbackground', None)
                bd_color = self.cnf.get(
                    'bordercolor', get_shade(self['bg'], 0.04, 'auto-120'))
                if bd_color == '':
                    bd_color = get_shade(self['bg'], 0.04, 'auto-120')
                elif bd_color.lower() == 'default':
                    bd_color = get_shade('white', 0.04, 'auto-120')
                self.cnf.update({'bordercolor': bd_color})
                self.cnf['highlightbackground'] = self.cnf['bordercolor'] = bd_color
                if self.itemcget('_bd_color', 'outline') != bd_color:
                    cnf[1] = [('itemconfigure', '_bd_color'), {'outline': bd_color}, None]
                    cnf[2] = ['configure', {'highlightbackground': bd_color}, None]
                    return cnf

        elif cmd == 'foreground' or cmd == 'fg':
            return (('itemconfigure', '_txt'),
                    {'fill': kw.get(cmd, self.cnf.get(cmd))}, None)
        
        elif cmd == 'focuscolor':
            return (('itemconfigure', '_tf'),
                    {'outline': kw.get(cmd, self.cnf.get(cmd))}, None)
        
        elif cmd == 'focusthickness':
            self._create_items('create', True, avoid=(
                    '_txt', '_img', '_bit', '_bd_color', '_border'))
        
        elif cmd == 'overbackground':
            cnf = []
            if kw.get(cmd, '') != '':
                fn = self._get_functions(cmd, kw)
                cnf = [self,
                       {'className': cmd, 'sequence': '<Enter>',
                        'func': fn.get('<Enter>')},
                       {'className': cmd, 'sequence': '<Leave>',
                        'func': fn.get('<Leave>')}]
                if self._mouse_state_condition():
                    _Canvas._configure(self, 'configure', {'bg': kw.get('overbackground')}, None)
                    _Canvas._configure(self, ('itemconfigure', '_border'), 
                        {'outline': get_shade(kw.get('overbackground'), 0.1, 'auto-120')}, None)
            elif kw.get(cmd) == '':
                cnf = [self,
                        {'className': cmd, 'sequence': '<Enter>'},
                        {'className': cmd, 'sequence': '<Leave>'}, 
                        ('configure', {'bg': self._org_bg}, None), 
                        (('itemconfigure', '_border'), {'outline': 
                        get_shade(self._org_bg, 0.1, 'auto-120')}, None)]
            return cnf

        elif cmd == 'overforeground':
            cnf = []
            if kw.get(cmd, '') != '':
                fn = self._get_functions(cmd, kw)
                cnf = [self,
                       {'className': cmd, 'sequence': '<Enter>',
                        'func': fn.get('<Enter>')},
                       {'className': cmd, 'sequence': '<Leave>',
                        'func': fn.get('<Leave>')}]
                if self._mouse_state_condition():
                    # [issue-4] doesn't change if overrelief is on.
                    # [issue-4] (FIXED) using after with 0ms delay
                    #           fixes the issue. To be safe delay is 1ms.
                    self.after(1, lambda: _Canvas._configure(self,
                        ('itemconfigure', '_txt'), {'fill': kw.get(cmd)}, None))
            elif kw.get(cmd) == '':
                cnf = [self,
                       {'className': cmd, 'sequence': '<Enter>'},
                       {'className': cmd, 'sequence': '<Leave>'}]
                cnf.append((('itemconfigure', '_txt'),
                            {'fill': self.cnf.get('fg', 'black')}, None))
            return cnf

        elif cmd == 'overrelief':
            if kw.get(cmd, '') != '':
                if not self._rel[1]:
                    self._rel = ('flat', False)
                if self._mouse_state_condition():
                    self._configure(
                        'configure', {'_relief': kw.get(cmd)}, None)
                fn = self._get_functions(cmd, kw)
                return (self,
                        {'className': cmd, 'sequence': '<Enter>',
                         'func': fn.get('<Enter>')},
                        {'className': cmd, 'sequence': '<Leave>',
                         'func': fn.get('<Leave>')})

            elif kw.get(cmd) == '':
                return (self,
                        {'className': cmd, 'sequence': '<Enter>'},
                        {'className': cmd, 'sequence': '<Leave>'},
                        ('configure', {'relief': self._rel[0]}, None))

        elif cmd == 'size':
            cnf = {}
            for i in ('text', 'font', 'textvariable', 'image', 'bitmap',
                      'compound', 'padx', 'pady', 'activeimage',
                      'activebitmap'): # 'width', 'height'}:
                if self.cnf.get(i, '') != '':
                    if i == 'activeimage':
                        cnf['image'] = self.cnf[i]
                    elif i == 'activebitmap':
                        cnf['bitmap'] = self.cnf[i]
                    elif i in ('width', 'height'):
                        cnf[i] = str(self.cnf[i]) + 'c'
                    else:
                        cnf[i] = self.cnf.get(i)

            if self._fixed_size['w'] and kw.get('width', True):
                kw['width'] = self.cnf['width']
            if self._fixed_size['h'] and kw.get('height', True):
                kw['height'] = self.cnf['height']
            self._fixed_size['w'] = True if kw.get('width') else False
            self._fixed_size['h'] = True if kw.get('height') else False
            # if kw.get('width') or kw.get('height'):
            #     self._fixed_size = True
            # elif kw.get('width') == 0 or kw.get('height') == 0:
            #     self._fixed_size = False
            W, H = _info_button(self, **cnf)
            self.cnf['width'] = self.cnf.get('width') if self._fixed_size['w'] else W
            self.cnf['height'] = self.cnf.get('height') if self._fixed_size['h'] else H
            return ('configure', {'width': self.cnf['width'],
                                  'height': self.cnf['height']}, None)

        elif cmd == 'state':
            cnf = {}
            # and self['bg'] != self.cnf.get('disabledbackground'):
            if kw.get(cmd) in 'disabled':
                cnf[1] = ('configure', {'bg': self.cnf.get('disabledbackground'),
                                        'state': 'disabled'}, None)
                cnf[2] = (('itemconfigure', '_txt'), {
                                'disabledfill': kw.get('disabledforeground', 
                                self.cnf.get('disabledforeground')), 
                                'state': 'disabled'}, None)
                cnf[3] = (('itemconfigure', '_activebg'), {'state': 'hidden'}, None)
                cnf[5] = (('itemconfigure', '_img'), {'state': 'disabled'}, None)
                cnf[6] = (('itemconfigure', '_bit'), {'state': 'disabled'}, None)
            elif kw.get(cmd) == 'normal':  # and self['bg'] != self._org_bg:
                _bg = self._org_bg
                if self._mouse_state_condition() and self.cnf.get('overbackground'):
                    _bg = self.cnf['overbackground']
                cnf[1] = ('configure', {'bg': _bg, 'state': 'normal'}, None)
                cnf[2] = (('itemconfigure', '_txt'), {'state': 'normal', 
                                'fill': self.cnf.get('foreground', 'black')}, None)
                cnf[3] = (('itemconfigure', '_activebg'), {'state': 'hidden'}, None)
                cnf[4] = (('itemconfigure', '_border'), {'state': 'normal'}, None)
                cnf[5] = (('itemconfigure', '_img'), {
                                'image': self.cnf.get('image', '')}, None)
                cnf[6] = (('itemconfigure', '_bit'), {
                                'bitmap': self.cnf.get('bitmap', '')}, None)
            elif kw.get(cmd) in ('active', 'pressed'):
                _bg = self._org_bg
                if self._mouse_state_condition() and self.cnf.get('overbackground'):
                    _bg = self.cnf['overbackground']
                cnf[1] = ('configure', {'bg': _bg, 'state': 'normal'}, None)
                cnf[2] = (('itemconfigure', '_txt'), {'state': 'normal', 
                                'fill': self.cnf.get('activeforeground', 'white')}, None)
                cnf[3] = (('itemconfigure', '_activebg'), {'state': 'normal'}, None)
                cnf[4] = (('itemconfigure', '_border'), {'state': 'hidden'}, None)
                cnf[5] = (('itemconfigure', '_img'), {
                                'image': self.cnf.get('activeimage', '')}, None)
                cnf[6] = (('itemconfigure', '_bit'), {
                                'bitmap': self.cnf.get('activebitmap', '')}, None)
            return cnf

        elif cmd == 'takefocus':
            if int(self[cmd]) and self['state'] in ('normal', 'active', 'pressed'):
                fn = self._get_functions(cmd, kw)
                return [self, {'className': cmd, 'sequence':
                               '<FocusIn>', 'func': fn.get('<FocusIn>')},
                              {'className': cmd, 'sequence':
                               '<FocusOut>', 'func': fn.get('<FocusOut>')}]

            elif not int(self[cmd]) or self['state'] in 'disabled':
                return [self,
                        {'className': cmd, 'sequence': '<FocusIn>'},
                        {'className': cmd, 'sequence': '<FocusOut>'},
                        (('itemconfigure', '_tf'), {'state': 'hidden'}, None)]

        elif cmd == 'text':
            if kw.get('textvariable', '') != '':
                kw['text'] = self.cnf['text'] = self.cnf['textvariable'].get()
                cbn = self.cnf['textvariable'].trace_variable('w',
                        self._get_functions('textvariable', kw).get('w'))
                self._var_cb = (self.cnf['textvariable'], cbn)

            elif kw.get('textvariable') == '' and self._var_cb:
                kw['text'] = self.cnf['text'] = self._var_cb[0].get()
                self.cnf.pop('textvariable', None)
                self._var_cb[0].trace_vdelete('w', self._var_cb[1])
                self._var_cb = []

            state = 'disabled' if self.cnf.get('state') in (
                'disabled', 'disable') else 'normal'
            fill = kw.get('fg', self.cnf.get('fg'))
            if self['state'] in ('pressed', 'active'):
                fill = self.cnf.get('activeforeground', 'white')
            cnf = dict(state=state, fill=fill,
                       disabledfill=kw.get(
                           'disabledforeground', self.cnf.get('disabledforeground')),
                       **{i: kw.get(i, self.cnf.get(i)) for i in ('text', 'anchor', 'font', 'justify')})
            return (('itemconfigure', '_txt'), cnf, None)

        elif cmd == '_border':
            return [('itemconfigure', '_border'),
                    {'outline': get_shade(self['bg'], 0.1, 'auto-120')}, None]

        elif cmd == '_txt' or cmd == '_img' or cmd == '_bit':
            self.cnf['width'] = self.winfo_width()
            self.cnf['height'] = self.winfo_height()
            return self._compound(self.cnf.get('compound'), 
                                self.cnf.get('width'),  
                                self.cnf.get('height')) \
                                or {cmd: ((self.cnf['width']/2), 
                                           self.cnf['height']/2)}

    def _set_coords(self, cnf={}, **kw):
        """Internal function.\n
        Set Coordinates of the items."""
        kw = _cnfmerge((cnf, kw))
        return [self.coords(tag, *kw[tag]) for tag in
                ('_txt', '_img', '_bit', '_bd_color',
                 '_border', '_tf') if kw.get(tag)]

    def _set_configure(self, options):
        """Internal function.\n
        Configures and binds according to the given options."""
        if not options:
            return
        if isinstance(options, dict):
            return [self._set_configure(i) for i in options.values()]
        if len(options) > 1 and isinstance(options[1], dict):
            con1 = bool(isinstance(options[0], tuple))
            if options[0] == 'configure' or (con1 and len(options[0]) > 1):
                # itemconfigure and configure
                _Canvas._configure(self, *options)
            if isinstance(options[0], _tk.Misc):
                if isinstance(options[-1], (tuple, list)):
                    # binds, itemconfigure and configure
                    binds, conf = options[:-2], options[-1]
                    _Canvas._configure(self, *conf)
                    return _bind(*binds)
                elif options[1].get('tag') == '_activebg':
                    # _on_press_color
                    return _on_press_color(*options)
                else:  # binds
                    return _bind(*options)

    def _configure1(self, cnf={}, **kw):
        """Internal Function.
        This function configure all the resources of 
        the Widget and save it to the class dict."""
        self.cnf, kw = self._make_dictionaries(self.cnf, 
                            self._set_trace(_cnfmerge((cnf, kw))))
        # Checks the items
        self._create_items('check')
        # >.<
        for opt in ('overbackground', 'activebitmap', 'activeimage',
                    'bitmap', 'fg', 'bordercolor', 'borderless', 'image',
                    'overrelief', 'foreground', 'state', 'overforeground',
                    'focuscolor', 'focusthickness', 'activebackground', 
                    'activeforeground', 'compound'):
            if kw.get(opt) is not None:
                self._set_configure(self._get_options(opt, kw))
                if (opt == 'state' and kw.get('compound', self.cnf.get('compound'))) \
                        or opt == 'compound':
                    self._set_coords(**self._get_options(
                                     ('_txt', '_img', '_bit'), kw))
        # Size
        if bool({'text', 'font', 'textvariable', 'image', 'bitmap', 'compound',
                 'padx', 'pady', 'width', 'height', 'activeimage',
                 'activebitmap',}.intersection(set(kw))):
            self._set_configure(self._get_options('size', kw))
        # Text
        if {'text', 'anchor', 'font', 'justify', 'textvariable'}.intersection(set(kw)):
            self._set_configure(self._get_options('text', kw))
        # Takefocus
        cond1 = bool(self.itemcget('_tf', 'state') != 'hidden' and
                     self['state'] not in 'disabled')
        cond2 = bool(self.itemcget('_tf', 'state') != self['state'])
        if cond1 or cond2:
            self._set_configure(self._get_options('takefocus', kw))
        # Line border: This will darken the border around the button.
        if get_shade(self['bg'], 0.1, 'auto-120') != self.itemcget('_border', 'outline'):
            self._set_configure(self._get_options('_border', kw))

    def _focus_in_out(self, intensity):
        """Internal function.\n
        Focus in and focus out effect maker."""
        main_win = self.winfo_toplevel()

        def _chngIn(evt):
            """Internal function."""
            try:
                if self.focus_get() is None:
                    color = get_shade(self['bg'], intensity, 'auto-120')
                    _Canvas._configure(self, ('itemconfigure', '_border'),
                            {'outline': color}, None)
                c1 = get_shade(self['bg'], intensity, 'auto-120')
                c2 = self.itemcget('_border', 'outline')
                if self.focus_get() and c1 == c2:
                    color = get_shade(self['bg'], 0.1, 'auto-120')
                    _Canvas._configure(self, ('itemconfigure', '_border'),
                             {'outline': color}, None)
            # [issue-8] tkinter issue with combobox (w = w.children[n])
            except KeyError: pass

        _bind(main_win, 
              {'className': 'focus%s' % str(self),
               'sequence': '<FocusIn>', 'func': _chngIn},
              {'className': 'focus%s' % str(self),
               'sequence': '<FocusOut>', 'func': _chngIn})
        return main_win

    def _relief(self, cnf, kw={}):  # check
        """Internal function.\n
        Make overrelief and Relief work together."""
        # [issue-5] (Fixed) overrelief and Relief not working together properly.
        kw = _cnfmerge((cnf, kw))
        cond1 = kw.get('overrelief', self.cnf.get('overrelief', '')) != ''
        if kw.get('relief') is not None and cond1:
            self._rel = (kw['relief'], True)
        if kw.get('_relief') is not None and kw.get('relief') is not None:
            self._rel = (kw['relief'], True)
            kw.pop('_relief', None)
        elif kw.get('_relief'):
            kw['relief'] = kw.pop('_relief')
        return kw

    def _set_size(self, evt=None):
        """Internal function. This will resize everything that is in the button"""
        if evt.width == self._size[0] and evt.height == self._size[1]:
            return
        self.cnf['width'], self.cnf['height'] = self._size = (evt.width, evt.height)
        self.delete('_activebg')
        # [issue-6] (Fixed) Need fix (laggy on resizing) --> workaround: cancel if still resizing
        for i in self._after_IDs:
            self.after_cancel(self._after_IDs[i])
        self._create_items('create', avoid=(
            '_txt', '_img', '_bit'), safe_create=True)
        self._set_coords(self._get_options(('_txt', '_img', '_bit'), self.cnf))
        self._after_IDs[1] = self.after(1, self._get_functions('_activebg'))
        self.tag_raise('_txt')
        self.tag_raise('_img')
        self.tag_raise('_bit')
        self.tag_raise('_tf')
        self._after_IDs[2] = self.after(1, self._configure1)
        cur_focus = self.master.focus_get()
        if cur_focus and self.master != cur_focus and cur_focus != self:
            cur_focus.focus()
        else:
            self.master.focus()
            # [issue-7] Need a better fix to get focus back to the button if it has it previously
            if cur_focus == self:
                # [issue-7] (fixed) using after with 1ms delay solves the issue.
                self._after_IDs[3] = self.after(1, self.focus)

    def _active(self, value):
        """Internal function.\n 
        Do not call directly. Changes appearance when active."""
        
        if value in ('on_press', 'on_enter') or value == True:
            if self['state'] != 'pressed' :
                if value == 'on_press': 
                    self.cnf['_state'] = self.cnf['state']
                self.cnf['state'] = 'active'
                _Canvas._configure(self, ('itemconfigure', '_activebg'), 
                                   {'state': 'normal'}, None)
            _Canvas._configure(self, ('itemconfigure', '_border'), 
                               {'state': 'hidden'}, None)
            _Canvas._configure(self, ('itemconfigure', '_txt'), 
                               {'fill': self.cnf.get('activeforeground', 'white')}, None)
            _Canvas._configure(self, ('itemconfigure', '_img'), 
                            {'image': self.cnf.get('activeimage', '')}, None)
            _Canvas._configure(self, ('itemconfigure', '_bit'), 
                            {'bitmap': self.cnf.get('activebitmap', '')}, None)

        elif value in ('on_leave', 'on_release') or value == False:  # When not active (False)
            if self['state'] != 'pressed':
                if self.cnf.get('_state'): 
                    self.cnf['state'] = self.cnf['_state']
                if value == 'on_release':
                    self.cnf.pop('_state', None)
                    if self['state'] == 'active': self['state'] = 'normal'
                _Canvas._configure(self, ('itemconfigure', '_activebg'), 
                                   {'state': 'hidden'}, None)
                _Canvas._configure(self, ('itemconfigure', '_border'), 
                                   {'state': 'normal'}, None)
                fill = self.cnf.get('fg', 'black')
                if self._mouse_state_condition() and self.cnf.get('overforeground'):
                    fill = self.cnf['overforeground']
                _Canvas._configure(self, ('itemconfigure', '_txt'), 
                                   {'fill': fill}, None)
                _Canvas._configure(self, ('itemconfigure', '_img'), 
                                {'image': self.cnf.get('image', '')}, None)
                _Canvas._configure(self, ('itemconfigure', '_bit'), 
                                {'bitmap': self.cnf.get('bitmap', '')}, None)
        # if self.cnf.get('compound'):
        self._set_coords(self._get_options(
                            ('_txt', '_img', '_bit'), self.cnf))

    def _on_press(self, *ags):
        """Internal function. When button is pressed <Button-1>"""
        self._rpin = None
        self._rpinloop = True

        def cmd(*a):
            """trigger function callback."""
            _bind(self, className='button_command', sequence='<ButtonRelease-1>')
            if self.cnf.get('repeatdelay', 0) and self.cnf.get('repeatinterval', 0) and self._rpinloop:
                self._rpin = self.after(self.cnf.get('repeatinterval', 0), cmd)
            self.cnf['command']() if self.cnf.get('command') else None

        def on_enter(*a):
            """Internal function.\n
            Enables when pressed and cursor
            is moved back on button."""
            self._active('on_enter')
            if self.cnf.get('repeatdelay', 0) and self.cnf.get('repeatinterval', 0):
                self._rpinloop = True
                cmd()
            _bind(self, className='button_command', sequence='<ButtonRelease-1>',
                  func=lambda *a: self.after(0, cmd), add='+')

        def on_leave(*a):
            """Internal function.\n
            Disables/Cancels when pressed and cursor
            is moved away from the button."""
            self._active('on_leave')                
            if self.cnf.get('repeatdelay', 0) and self.cnf.get('repeatinterval', 1):
                self._rpinloop = False
                self.after_cancel(self._rpin)
            _bind(self, className='button_command', sequence='<ButtonRelease-1>')

        if self['state'] not in 'disabled':
            self.focus_set()
            self._active('on_press')            
            if self.cnf.get('repeatdelay', 0) and self.cnf.get('repeatinterval', 1):
                self._rpin = self.after(self.cnf.get('repeatdelay', 0), cmd)
            _bind(self, {'className': 'on_press_enter', 'sequence': '<Enter>', 'func': on_enter},
                  {'className': 'on_press_leave', 'sequence': '<Leave>', 'func': on_leave},
                  {'className': 'button_command', 'sequence': '<ButtonRelease-1>', 'func': cmd})

    def _on_release(self, *ags):
        """Internal function. When button is released <ButtonRelease-1>"""
        if self['state'] in 'disabled': return
        self._active('on_release')
        self._rpinloop = False
        if getattr(self, '_rpin', None): self.after_cancel(self._rpin)
        _bind(self, {'className': 'on_press_enter', 'sequence': '<Enter>'},
              {'className': 'on_press_leave', 'sequence': '<Leave>'},
              {'className': 'button_command', 'sequence': '<ButtonRelease-1>'})

    def _compound(self, flag, width, height):
        """Internal function.\n
        Use `compound = 'left'/'right'/'top'/'bottom'` to configure."""
        # width -= 2
        _PiTag = ''
        if self.cnf.get('image', self.cnf.get('activeimage')):
            _PiTag = '_img'
        elif self.cnf.get('bitmap', self.cnf.get('activebitmap')):
            _PiTag = '_bit'
        _im_size = self.bbox(_PiTag)
        _txt_size = self.bbox('_txt')
        if _im_size and _txt_size:
            W_im = _im_size[2] - _im_size[0]
            H_im = _im_size[3] - _im_size[1]
            W_txt = _txt_size[2] - _txt_size[0]
            H_txt = _txt_size[3] - _txt_size[1]
            if flag == 'bottom':
                width = (width/2, width/2)
                height = (height/2-H_im/2, height/2+H_txt/2)
            elif flag == 'top':
                width = (width/2, width/2)
                height = (height/2+H_im/2, height/2-H_txt/2)
            elif flag == 'right':
                width = (width/2-W_im/2, width/2+W_txt/2)
                height = (height/2, height/2)
            elif flag == 'left':
                width = (width/2+W_im/2, width/2-W_txt/2)
                height = (height/2, height/2)
            elif flag is not None:
                raise _tk.TclError('bad compound flag "{}", must be \
                    -none, -top, -bottom, -left, or -right'.format(flag))
            if isinstance(height, tuple):
                if _im_size is None:
                    return {'_txt': (width[0], height[0])}
                return {'_txt': (width[0], height[0]),
                            _PiTag: (width[1], height[1])}
        return None


class ButtonBase(_BaseWidget):
    """Internal class used for tkinter macos Buttton"""

    def __init__(self, master=None, cnf={}, **kw):
        kw = self._set_trace(_cnfmerge((cnf, kw)))
        kw = {k: v for k, v in kw.items() if v is not None}
        self._after_IDs = {} # _after_IDs
        self._fixed_size = {'w': False, 'h': False}
        self._var_cb = None
        self.cnf = {}
        for i in kw.copy().keys():
            if i in self._features:
                self.cnf[i] = kw.pop(i, None)

        self.cnf['fg'] = self.cnf['foreground'] = self.cnf.get('fg', self.cnf.get('foreground', 'black'))
        self.cnf['anchor'] = self.cnf.get('anchor', 'center')
        self.cnf['borderless'] = self.cnf.get('borderless', False)
        
        self.cnf['disabledforeground'] = self.cnf.get('disabledforeground', 'grey')
        self.cnf['state'] = self.cnf.get('state', 'normal')
        self.cnf['activeforeground'] = self.cnf.get('activeforeground', 'white')

        kw['width'] = kw.get('width', 87)
        kw['height'] = kw.get('height', 24)
        kw['takefocus'] = kw.get('takefocus', 1)
        kw['bg'] = kw.pop('bg', kw.pop('background', 'white'))
        kw['highlightthickness'] = kw.get('highlightthickness', 0)

        _Canvas.__init__(self, master=master, **kw)
        self.cnf['bordercolor'] = self.cnf['highlightbackground'] = self.cnf.get(
            'bordercolor', self.cnf.get('highlightbackground', get_shade(self['bg'], 0.04, 'auto-120')))

        self._buttons.append(self)
        self._size = (self.winfo_width(), self.winfo_height())
        self._create_items('create', safe_create=True)
        self._org_bg = self['bg']
        if kw.get('relief') is not None:
            self._rel = (kw['relief'], True)
        else:
            self._rel = ('flat', False)

        _bind(self,
              {'className': 'button_release',
                  'sequence': '<ButtonRelease-1>', 'func': self._on_release},
              {'className': 'button_press',
                  'sequence': '<Button-1>', 'func': self._on_press},
              {'className': 'set_size', 'sequence': '<Configure>', 'func': self._set_size})

        self._focus_in_out(0.04)
        self._configure1(self.cnf)

    def _configure(self, cmd, cnf=None, kw=None):
        'Internal function to configure the inherited class'
        kw = self._relief(cnf, kw)
        cnf = {}
        for i in list(kw):
            if i in self._features:
                cnf[i] = kw.pop(i, None)
        _return = _Canvas._configure(self, cmd, None, kw)
        if kw.get('bg') or kw.get('background'):
            self._org_bg = self['bg']
        self._configure1(cnf)
        if _return is not None and isinstance(_return, dict):
            _return.update(self.cnf)
        return _return

    # @tkv._colorvar_patch_cget
    def cget(self, key):
        """Return the resource value for a KEY given as string."""
        if key in self._features:
            return self.cnf.get(key)
        else:
            return _Canvas.cget(self, key)
    __getitem__ = cget

    def keys(self):
        """Return a list of all resource names of this widget."""
        _return = _Canvas.keys(self)
        _return.extend(self._features)
        return sorted(list(set(_return)))

    @tkv._colorvar_patch_destroy
    def destroy(self):
        """Destroy this and all descendants widgets. This will
        end the application of this Tcl interpreter."""
        main_win = self.winfo_toplevel()
        _bind(main_win, 
              {'className': 'focus%s' % str(self), 'sequence': '<FocusIn>'},
              {'className': 'focus%s' % str(self), 'sequence': '<FocusOut>'})
        if self.cnf.get('textvariable', '') != '':
            self.configure(textvariable='')
        if self in self._buttons:
            self._buttons.remove(self)
        return _Canvas.destroy(self)


class SFrameBase(_tk.Frame):
    """Base Class for SFrame."""

    _features = ('scrollbarwidth', 'mousewheel',
                 'avoidmousewheel', 'canvas', 'scrollbar')

    def __init__(self, master=None, cnf={}, **kw):
        kw = _cnfmerge((cnf, kw))
        self.cnf = {}
        self._after_ids = {}
        self.cnf['scrollbarwidth'] = kw.pop('scrollbarwidth', 10)
        self.cnf['mousewheel'] = kw.pop('mousewheel', True)
        self.cnf['avoidmousewheel'] = kw.pop('avoidmousewheel', ())
        self.cnf['canvas'] = kw.pop('canvas', _tk.Canvas(master=master, 
                                                         highlightthickness=0,
                                                         width=kw.pop('width', 250), 
                                                         height=kw.pop('height', 250)))
        self.cnf['scrollbar'] = kw.pop('scrollbar', _tk.Scrollbar(self.cnf['canvas'],
                                                                  orient='vertical', 
                                                                  width=self.cnf['scrollbarwidth']))
        _tk.Frame.__init__(self, self.cnf['canvas'], **kw)
        self.cnf['canvas']['bg'] = self['bg']
        self.cnf['scrollbar'].place(relx=1, rely=0, anchor='ne', relheight=1)
        self.cnf['scrollbar'].configure(command=self.cnf['canvas'].yview)
        self.cnf['canvas'].configure(yscrollcommand=self.cnf['scrollbar'].set)
        self.cnf['canvas'].create_window(0, 0, anchor='nw', tags="window", window=self,
                                         width=self.cnf['canvas'].winfo_reqwidth()-\
                                               self.cnf['scrollbar'].winfo_reqwidth())
        self.cnf['canvas'].bind("<Configure>", self._configure_height, add="+")
        _bind(self, className='configure',
              sequence='<Configure>', func=self._configure_window)
        self._mouse_scrolling(self.cnf['mousewheel'])
        self._avoid_mousewheel(self.cnf.get('avoidmousewheel'))
        self._geometryManager()

    def _avoid_mousewheel(self, widgets):
        """Internal function.\n
        Use this to have multiple scrollable 
        widgets inside of SFrame."""

        def set_widget(wid):
            """Internal function.\n
            Binds <Enter> and <Leave> to the widget 
            to enable/disable mousewheel scrolling."""
            binds = [{'className': 'mw_state_sframe', 'sequence':
                   '<Leave>', 'func': lambda _: self._mouse_scrolling(True)}]
            if not isinstance(wid, SFrameBase):
                binds.append({'className': 'mw_state_sframe', 'sequence': 
                    '<Enter>', 'func': lambda _: self._mouse_scrolling(False)})
            _bind(wid, *binds)
    
        if isinstance(widgets, (list, tuple)):
            for widget in widgets:
                set_widget(widget)
        else:
            set_widget(widgets)

    def _mouse_scrolling(self, state):
        """Internal function."""
        def enable_mousewheel(evt=None):
            """Internal function."""
            self.bind_all('<MouseWheel>', self._on_mouse_scroll)
            self.bind_all("<Button-4>", self._on_mouse_scroll)
            self.bind_all("<Button-5>", self._on_mouse_scroll)

        def disable_mousewheel(evt=None):
            """Internal function."""
            self.unbind_all('<MouseWheel>')
            self.unbind_all("<Button-4>")
            self.unbind_all("<Button-5>")

        if state:
            _bind(self,
                  {'className': 'mousewheel_state', 'sequence':
                   '<Enter>', 'func': enable_mousewheel},
                  {'className': 'mousewheel_state', 'sequence':
                   '<Leave>', 'func': disable_mousewheel})
            enable_mousewheel()
        else:
            _bind(self,
                  {'className': 'mousewheel_state', 'sequence': '<Enter>'},
                  {'className': 'mousewheel_state', 'sequence': '<Leave>'})
            disable_mousewheel()

    def _on_mouse_scroll(self, evt):
        """Internal function."""
        if self.winfo_height() < self.cnf['canvas'].winfo_height(): 
            return 
        if evt.state == 0:
            self.cnf['canvas'].yview_scroll(-1*delta(evt), 'units')

    def _configure_height(self, evt):
        """Internal function."""
        width = self.cnf['canvas'].winfo_width()-self.cnf['scrollbar'].winfo_width()
        self.cnf['canvas'].itemconfig('window', width=width)

    def _configure_window(self, evt):
        """Internal function."""
        # this will update the position of scrollbar when scrolled from mousewheel.
        # fixes some bugs
        # makes scrolling more smoother
        self.after_cancel(self._after_ids.get(0, ' '))
        self._after_ids[0] = self.after(1, lambda: self.cnf['canvas'].configure(
                scrollregion=self.cnf['canvas'].bbox('all')))

    def _geometryManager(self):
        """Internal function."""
        # Use set to support the following in both python 2 and python 3
        geo_methods = [m for m in (set(_tk.Pack.__dict__) | set(_tk.Grid.__dict__) |
                       set(_tk.Place.__dict__)) if m not in _tk.Frame.__dict__]
        for m in geo_methods:
            if m[0] != '_' and 'config' not in m:
                setattr(self, m, getattr(self.cnf['canvas'], m))

    def _configure(self, cmd, cnf=None, kw=None):
        kw = _tk._cnfmerge((cnf, kw))
        self.cnf['scrollbar']['width'] = kw.pop(
            'scrollbarwidth', self.cnf['scrollbar']['width'])
        for key in kw.copy():
            if key in self._features:
                self.cnf[key] = kw.pop(key, self.cnf.get(key))
        self.cnf['canvas']['width'] = kw.pop(
            'width', self.cnf['canvas']['width'])
        self.cnf['canvas']['height'] = kw.pop(
            'height', self.cnf['canvas']['height'])
        self._mouse_scrolling(self.cnf['mousewheel'])
        self._avoid_mousewheel(self.cnf['avoidmousewheel'])
        _return = _tk.Frame._configure(self, cmd, {}, kw)
        if kw.get('bg', kw.get('background')):
            self.cnf['canvas']['bg'] = self['bg']
        if isinstance(_return, dict):
            _return.update(self.cnf)
        return _return

    def cget(self, key):
        """Return the resource value for a KEY given as string."""
        if key in self._features:
            return self.cnf.get(key)
        return _tk.Frame.cget(self, key)
    __getitem__ = cget


class MarqueeBase(_tk.Canvas):
    """Base class for Marquee."""

    def __init__(self, master=None, cnf={}, **kw):
        kw = _cnfmerge((cnf, kw))
        self._stop_state = False
        self.cnf = dict(
            text=kw.pop('text', ''),
            font=kw.pop('font', None),
            fg=kw.pop('fg', 'black') if kw.get(
                'fg') else kw.pop('foreground', 'black'),
            fps=kw.pop('fps', 30),
            left_margin=kw.pop('left_margin', 10),
            initial_delay=kw.pop('initial_delay', 1000),
            end_delay=kw.pop('end_delay', 1000),
            smoothness=kw.pop('smoothness', 1),  # 1 <= smooth < 1
        )
        kw['height'] = kw.get('height', 24)
        kw['highlightthickness'] = kw.get('highlightthickness', 0)
        _tk.Canvas.__init__(self, master=master, **kw)
        self._create('text', (3, 1), dict(anchor='w', tag='text', text=self.cnf.get('text'),
                                          font=self.cnf.get('font'), fill=self.cnf.get('fg')))
        _bind(self, className='configure',
                  sequence='<Configure>', func=self._check)
        self.after_id = ' '
    
    def _set_height(self, evt=None):
        """Internal function."""
        bbox = self.bbox('text')
        height = bbox[3] - bbox[1] + 8
        if int(self['height']) == height: 
            return
        _tk.Canvas._configure(self, 'configure', {'height': height}, None)

    def _reset(self, force_reset=False):
        """Internal function.\n
        Resets the text position, do not call directly."""
        if self.after_id == ' ' and not force_reset:
            return
        self.after_cancel(self.after_id)
        self.coords('text', 3, self.winfo_height()/2)  # RESETS TEXT
        self.after_id = ' '

    def _check(self, evt=None):
        """Internal function.\n
        Sets the text properly in the frame."""
        self._set_height()
        self.coords('text', 3, self.winfo_height()/2)
        text_width = self.bbox('text')[2]   # TEXT WIDTH
        frame_width = self.winfo_width()    # FRAME WIDTH
        if text_width + 1 < frame_width:
            self._reset()
        elif self.after_id == ' ':
            delay = self.cnf.get('initial_delay')  # INITITAL DEALY
            self.after_id = self.after(delay, self._animate)

    def _animate(self, evt=None):
        """Internal function.\n
        Process text and move text."""
        if self._stop_state: return
        self._set_height()
        text_width = self.bbox('text')[2]   # TEXT WIDTH
        frame_width = self.winfo_width()    # FRAME WIDTH
        delay = int(self.cnf.get('smoothness')*1000 / self.cnf.get('fps'))
        if text_width + 1 + self.cnf.get('left_margin') < frame_width:
            self.after(self.cnf.get('end_delay'), self.coords,
                       'text', 3, self.winfo_height()/2)  # RESETS TEXT
            delay = self.cnf.get('initial_delay') + \
                self.cnf.get('end_delay')  # INITITAL DEALY
        else:
            # MOVE -1 PIXEL EVERYTIME
            self.tk.call((self._w, 'move') + (
                'text', -self.cnf.get('smoothness'), 0))
        self.after_id = self.after(delay, self._animate)

    def _configure(self, cmd, cnf=None, kw=None):
        """Internal function."""
        kw = _cnfmerge((cnf, kw))
        self.cnf = dict(
            text=kw.pop('text', self.cnf.get('text')),
            font=kw.pop('font', self.cnf.get('font')),
            fg=kw.pop('fg', self.cnf.get('fg')) if kw.get('fg')
            else kw.pop('foreground', self.cnf.get('foreground')),
            fps=kw.pop('fps', self.cnf.get('fps')),
            left_margin=kw.pop('left_margin', self.cnf.get('left_margin')),
            initial_delay=kw.pop(
                'initial_delay', self.cnf.get('initial_delay')),
            end_delay=kw.pop('end_delay', self.cnf.get('end_delay')),
            smoothness=kw.pop('smoothness', self.cnf.get('smoothness')),
        )
        _tk.Canvas._configure(self, ('itemconfigure','text'), dict(text=self.cnf.get('text'),
                        font=self.cnf.get('font'), fill=self.cnf.get('fg')), None)
        self._set_height()
        return _tk.Canvas._configure(self, cmd, kw, None)

    def cget(self, key):
        """Return the resource value for a KEY given as string."""
        if key in self.cnf.keys():
            return self.cnf[key]
        return _tk.Canvas.cget(self, key)
    __getitem__ = cget

    def destroy(self):
        """Destroy this widget."""
        self.after_cancel(self.after_id)
        return _tk.Canvas.destroy(self)
