Files
lazy-dsi-file-downloader/tkmacosx/basewidget.py
2020-12-19 11:31:25 +00:00

1533 lines
68 KiB
Python

# 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 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 tkmacosx.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', ("#4b91fe", "#055be5")))
cls.delete(tag)
ids = []
# This is the default color for mac
cr = kw.get('color', ("#4b91fe", "#055be5"))
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_color = self.cnf.get('bordercolor', get_shade(self['bg'], 0.04, 'auto-120'))
if self._type == 'circle':
pad = 2
width = r = int(self.cnf.get('width', 87)/2) # radius = x = y (in pixels)
_bd_points = (pad-width, pad-width, r*2+width-pad, r*2+width-pad)
kw_bd_color = {
'tag': '_bd_color',
'state': 'hidden' if self['state'] in ('pressed', 'active') else 'normal',
'width': width*2,'outline': bd_color}
ids.append(self._create('oval', _bd_points, kw_bd_color))
else:
_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=bd_color))
# Border item.
if check_tag('_border'):
bo_color = get_shade(self['bg'], 0.1, 'auto-120')
if self._type == 'circle':
pad = 2
r = int(self.cnf.get('width', 87)/2) # radius = x = y
_bo_points = (pad, pad, r*2-(pad+1), r*2-(pad+1))
ids.append(self._create('oval', _bo_points, {
'tag': '_border', 'outline': self.cnf.get('bordercolor', bo_color) }))
else:
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'):
if self._type == 'circle':
pad = 1
width = self.cnf.get('focusthickness', 2)
r = int(self.cnf.get('width', 87)/2) # radius = x = y
_tk_points = (pad+width, pad+width, r*2-width-pad, r*2-width-pad)
ids.append(self._create('oval', _tk_points, {
'tag': '_tf', 'width': width, 'outline': self.cnf.get('focuscolor', '#81b3f4')}))
else:
# takefocuswidth can be changed.
# Focus line is not on point the line is off when thickness is changed.
s = w = self.cnf.get('focusthickness', 2)
if s == 1:
s = 2
diff2 = (int(self['highlightthickness'])*2) + (s*2)
diff1 = diff2 + 1
else:
diff1 = diff2 = (int(self['highlightthickness'])*2) + (s*2)
if diff2 == (s*2):
diff2 -= 1
_tk_points = (s+int(self['highlightthickness']),
s+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(),
'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._type == 'circle' and kw.get('radius'):
self.cnf['width'] = self.cnf['height'] = kw.get(
'width', kw.get('height', int(kw['radius']*2)))
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', kw.get('radius')) else False
self._fixed_size['h'] = True if kw.get('height', kw.get('radius')) else 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 = {}
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':
_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':
if not self.cnf.get('width'):
self.cnf['width'] = self.winfo_width()
if not self.cnf.get('height'):
self.cnf['height'] = self.winfo_height()
r1 = self._compound(self.cnf.get('compound'), self.cnf.get('width'), self.cnf.get('height'))
r2 = {cmd: ((self.cnf['width']/2), self.cnf['height']/2)}
return r1 or r2
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', 'radius',
'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] (Fixed) 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.\n
This will resize everything that is in the button"""
if evt.width == self._size[0] and evt.height == self._size[1]:
return
# [issue-8] On resizing the window the circlebutton doesn't resize properly,
# current fix doesn't work properly.
if self._type == 'circle' and evt.width != evt.height:
if evt.width > evt.height:
evt.width = evt.height
else:
evt.height = evt.width
return
self._size = (self.cnf['width'], self.cnf['height']) = (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'))
for t in ('_txt', '_img', '_bit', '_bd_color', '_border', '_tf'):
self.tag_raise(t)
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', self.cnf.get('image', ''))}, None)
_Canvas._configure(self, ('itemconfigure', '_bit'),
{'bitmap': self.cnf.get('activebitmap', self.cnf.get('bitmap', ''))}, 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, _type=None, 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._type = _type # button type (circle, normal)
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')
if self._type == 'circle':
self.cnf['radius'] = int(kw.pop('radius', 35))
ra = int(self.cnf['radius']*2 + 4)
kw['width'] = kw['height'] = kw.get('width', kw.get('height', ra))
else:
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) or \
(i == 'radius' and self._type == 'circle'):
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 == 'radius' and self._type == 'circle':
return self.cnf.get('radius')
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)
def disable_mousewheel(evt=None):
"""Internal function."""
self.unbind_all('<MouseWheel>')
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)