Widgets
Sept 29, 2020 7:04:47 GMT -8
Post by Uncle Buddy on Sept 29, 2020 7:04:47 GMT -8
import tkinter as tk
from tkinter import ttk
import sqlite3
from tkinter import messagebox
from styles import make_formats_dict
from PIL import Image, ImageTk
from utes import create_tooltip
import dev_tools as dt
formats = make_formats_dict()
# print('formats is', formats)
# formats is {
# 'bg': '#232931', 'highlight_bg': '#393e46', 'table_head_bg': '#2E5447',
# 'fg': '#eeeeee', 'output_font': ('courier', 14), 'input_font': ('tahoma', 14),
# 'heading1': ('courier', 28, 'bold'), 'heading2': ('courier', 21, 'bold'),
# 'heading3': ('courier', 15, 'bold'), 'heading4': ('courier', 11, 'bold'),
# 'status': ('tahoma', 11), 'boilerplate': ('tahoma', 9)}
class Framex(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
pass
def winfo_subclass(self):
'''
Like built-in tkinter method
w.winfo_class() except it gets subclass names.
'''
subclass = type(self).__name__
return subclass
class FrameStay(Framex):
'''
Frame background color will not change when color scheme changes.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
pass
class Frame(Framex):
'''
Frame background color changes when color scheme changes.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg=formats['bg'])
class FrameTest(Framex):
'''
Frame background color can be altered for testing/visibility.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg='orange')
class FrameTest2(Framex):
'''
Frame background color can be altered for testing/visibility.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg='green')
class FrameHilited(Framex):
'''
Frame hilited by groove border and background color.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'], bd=3, relief='groove')
class FrameHilited1(Framex):
'''
Used for narrow resizing sash on left edge of
attributes table. Could be used as vertical
separator.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'], bd=6, relief='ridge')
class FrameHilited2(Framex):
'''
Frame hilited by border and a different background color.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg=formats['table_head_bg'])
class FrameHilited3(Framex):
'''
Frame hilited by different background color but not border.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'])
class FrameHilited4(Framex):
'''
Frame hilited by sunken border and background color.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'], bd=2, relief='sunken')
class FrameHilited5(Framex):
'''
Frame hilited by sunken border and background color.
'''
def __init__(self, master, *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'], bd=1, relief='solid')
class LabelFramex(tk.LabelFrame):
def __init__(self, master, *args, **kwargs):
tk.LabelFrame.__init__(self, master, *args, **kwargs)
pass
def winfo_subclass(self):
'''
Like built-in tkinter method
w.winfo_class() except it gets subclass names.
'''
subclass = type(self).__name__
return subclass
class LabelFrame(LabelFramex):
def __init__(self, master, *args, **kwargs):
LabelFramex.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['bg'], font=formats['output_font'], fg=formats['fg'])
class Separator(Framex):
'''
Horizontal separator like ttk.Separator but
can be sized and utilize the user pref colors.
'''
def __init__(
self, master, height,
color1=formats['table_head_bg'],
color2=formats['highlight_bg'],
color3=formats['bg'], *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=0)
self.grid_rowconfigure(2, weight=0)
self.height = int(height/5)
self.color1 = color1
self.color2 = color2
self.color3 = color3
if self.height > 0:
self.line1 = FrameStay(
self, bg=self.color1, height=self.height)
self.line1.grid(column=0, row=0, sticky='ew')
self.line2 = FrameStay(
self, bg=self.color2, height=self.height)
self.line2.grid(column=0, row=1, sticky='ew')
self.line3 = FrameStay(
self, bg=self.color3, height=self.height)
self.line3.grid(column=0, row=2, sticky='ew')
self.line4 = FrameStay(
self, bg=self.color2, height=self.height)
self.line4.grid(column=0, row=4, sticky='ew')
self.line5 = FrameStay(
self, bg=self.color1, height=self.height)
self.line5.grid(column=0, row=5, sticky='ew')
else:
self.line1 = FrameStay(
self, bg=self.color1, height=self.height)
self.line1.grid(column=0, row=0, sticky='ew')
self.line2 = FrameStay(
self, bg=self.color2, height=self.height)
self.line2.grid(column=0, row=1, sticky='ew')
self.line3 = FrameStay(
self, bg=self.color3, height=self.height)
self.line3.grid(column=0, row=2, sticky='ew')
def colorize(self):
formats = make_formats_dict()
self.color1=formats['table_head_bg'],
self.color2=formats['highlight_bg'],
self.color3=formats['bg']
if self.height > 0:
self.line1.config(bg=self.color1)
self.line2.config(bg=self.color2)
self.line3.config(bg=self.color3)
self.line4.config(bg=self.color2)
self.line5.config(bg=self.color1)
else:
self.line1.config(bg=self.color1)
self.line2.config(bg=self.color2)
self.line3.config(bg=self.color3)
class Labelx(tk.Label):
def __init__(self, master, *args, **kwargs):
tk.Label.__init__(self, master, *args, **kwargs)
def winfo_subclass(self):
''' a method that works like built-in tkinter method
w.winfo_class() except it gets subclass names
of widget classes custom-made by inheritance '''
subclass = type(self).__name__
return subclass
class Label(Labelx):
'''
If this subclass is detected it will be reconfigured
according to user preferences.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['bg'],
fg=formats['fg'],
font=formats['output_font'])
class LabelEntrylike(Labelx):
'''
Normal Label that uses the same font as an Entry.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['bg'],
fg=formats['fg'],
font=formats['input_font'])
class LabelTest(Labelx):
'''
Color can be changed for testing/visibility.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg='purple',
fg=formats['fg'],
font=formats['output_font'])
class LabelItalic(Labelx):
'''
Uses input font and italics to display errors & such.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(font=formats['show_font'])
class LabelItalicHilited(Labelx):
'''
Uses input font and italics to display errors & such.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
font=formats['show_font'],
bg=formats['table_head_bg'])
class LabelHilited(Label):
'''
Like Label with a different background.
'''
def __init__(self, master, *args, **kwargs):
Label.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'])
class LabelHilited2(Label):
'''
Like Label with a different background.
'''
def __init__(self, master, *args, **kwargs):
Label.__init__(self, master, *args, **kwargs)
self.config(bg=formats['table_head_bg'])
class LabelTip(LabelHilited):
'''
Like Label with a different background. For tooltips.
'''
def __init__(self, master, *args, **kwargs):
LabelHilited.__init__(self, master, *args, **kwargs)
self.config(font=formats['status'], padx=0, pady=0, bd=1, relief='solid')
class LabelTip2(LabelHilited2):
'''
Like Label with a different background. For tooltips.
'''
def __init__(self, master, *args, **kwargs):
LabelHilited2.__init__(self, master, *args, **kwargs)
self.config(font=formats['status'], padx=0, pady=0, bd=1, relief='solid')
class LabelTipBold(LabelHilited):
'''
Like Label with a different background.
'''
def __init__(self, master, *args, **kwargs):
LabelTip.__init__(self, master, *args, **kwargs)
self.config(font=formats['titlebar_1'])
class LabelNegative(Labelx):
'''
Usual bg and fg reversed.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(bg=formats['fg'], fg=formats['bg'])
class LabelH2(Label):
'''
For large subheadings.
'''
def __init__(self, master, *args, **kwargs):
Label.__init__(self, master, *args, **kwargs)
self.config(
font=formats['heading3'])
class LabelH3(Label):
'''
For small subheadings.
'''
def __init__(self, master, *args, **kwargs):
Label.__init__(self, master, *args, **kwargs)
self.config(font=formats['heading3'])
class LabelColumn(Labelx):
'''
For column headings in tables.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['bg'],
font=formats['heading3'],
anchor='w')
class LabelColumnCenter(LabelColumn):
'''
Like LabelColumn for centered table column headings.
'''
def __init__(self, master, *args, **kwargs):
LabelColumn.__init__(self, master, *args, **kwargs)
class KinTip(FrameHilited5):
'''
Display kin name when user points to a kin_type button in kin column
on events table.
'''
def __init__(self, master, text='', *args, **kwargs):
FrameHilited5.__init__(self, master, *args, **kwargs)
instrux1 = LabelTip(
self, text='Click to make')
instrux2 = LabelTipBold(
self,
text=text)
instrux3 = LabelTip(
self, text='the current person')
instrux1.grid(padx=1, pady=1, ipadx=3, ipady=3)
instrux2.grid(padx=1, pady=1, ipadx=3, ipady=3)
instrux3.grid(padx=1, pady=1, ipadx=3, ipady=3)
class LabelSearch(Labelx):
'''
For search results column cells. Since this
widget responds to so many different events, and
then still has to respond to the colorizer,
the code is simpler if the colorizer can
treat this class separately from other labels.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.formats = make_formats_dict()
self.config(anchor='w')
class LabelBoilerplate(Labelx):
'''
Like Label for fine print.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(bg=formats['bg'])
class LabelTitleBar(Labelx):
'''
Like Label for fine print. Can be sized independently
of other font sizes so users who want larger fonts
elsewhere can keep titles tiny if they want. Used for
window titlebar and menu strip since people are
so used to Windows' tiny fonts on these widgets that some
people will not want to see the font get bigger even if
they can't read it.
'''
def __init__(self, master, size='tiny', *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['highlight_bg'])
if size == 'tiny':
self.config(font=formats['titlebar_0'])
elif size == 'small':
self.config(font=formats['titlebar_1'])
elif size == 'medium':
self.config(font=formats['titlebar_2'])
elif size == 'large':
self.config(font=formats['titlebar_3'])
class LabelMenuBarTest(LabelTitleBar):
'''
Color can be changed for testing/visibility.
'''
def __init__(self, master, size='tiny', *args, **kwargs):
LabelTitleBar.__init__(self, master,**options)
self.config(bg='blue')
self.bind('<Enter>', self.enrise)
self.bind('<Leave>', self.flatten)
self.bind('<Button-1>', self.sink)
if size == 'tiny':
self.config(font=formats['titlebar_hilited_0'])
elif size == 'small':
self.config(font=formats['titlebar_hilited_1'])
elif size == 'medium':
self.config(font=formats['titlebar_hilited_2'])
elif size == 'large':
self.config(font=formats['titlebar_hilited_3'])
def enrise(self, evt):
evt.widget.config(relief='raised')
def flatten(self, evt):
evt.widget.config(relief='flat')
def sink(self, evt):
evt.widget.config(relief='sunken')
class LabelMenuBar(Labelx):
'''
Like LabelTitleBar but normal font weight.
'''
def __init__(self, master, size='tiny', *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(bg=formats['table_head_bg'])
if size == 'tiny':
self.config(font=formats['titlebar_hilited_0'])
elif size == 'small':
self.config(font=formats['titlebar_hilited_1'])
elif size == 'medium':
self.config(font=formats['titlebar_hilited_2'])
elif size == 'large':
self.config(font=formats['titlebar_hilited_3'])
class LabelTitleBarHilited(Labelx):
'''
Like LabelTitleBar but instead of using a highlight
color as its normal background, it uses the normal
background color so it will be highlighted.
'''
def __init__(self, master, size='tiny', *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'])
if size == 'tiny':
self.config(font=formats['titlebar_hilited_0'])
elif size == 'small':
self.config(font=formats['titlebar_hilited_1'])
elif size == 'medium':
self.config(font=formats['titlebar_hilited_2'])
elif size == 'large':
self.config(font=formats['titlebar_hilited_3'])
class LabelStay(Labelx):
'''
If this subclass is detected it won't be reconfigured.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
pass
class LabelButtonImage(Labelx):
'''
A label that looks and works like a button. Good for
images since it sizes itself to its contents, so don't
add width and height to this class or change its color.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.bind('<FocusIn>', self.show_focus)
self.bind('<FocusOut>', self.unshow_focus)
self.bind('<Button-1>', self.on_press)
self.bind('<ButtonRelease-1>', self.on_release)
self.bind('<Enter>', self.on_hover)
self.bind('<Leave>', self.on_unhover)
def show_focus(self, evt):
self.config(borderwidth=2)
def unshow_focus(self, evt):
self.config(borderwidth=1)
def on_press(self, evt):
formats = make_formats_dict()
self.config(relief='sunken', bg=formats['table_head_bg'])
def on_release(self, evt):
formats = make_formats_dict()
self.config(relief='raised', bg=formats['bg'])
def on_hover(self, evt):
self.config(relief='groove')
def on_unhover(self, evt):
self.config(relief='raised')
class LabelButtonText(LabelButtonImage):
'''
A label that looks and works like a button. Displays Text.
'''
def __init__(self, master, width=8, *args, **kwargs):
LabelButtonImage.__init__(self, master, *args, **kwargs)
self.config(
anchor='center',
borderwidth=1,
relief='raised',
takefocus=1,
bg=formats['bg'],
width=width,
font=formats['input_font'],
fg=formats['fg'])
class LabelDots(LabelButtonText):
'''
Display clickable dots if more info, no dots
if no more info.
'''
def __init__(
self,
master,
current_file,
dialog_class,
*args, **kwargs):
LabelButtonText.__init__(self, master, *args, **kwargs)
self.master = master
self.root = master.master
self.dialog_class = dialog_class
self.finding_id = None
self.header = None
conn = sqlite3.connect(current_file)
cur = conn.cursor()
cur.execute(
'''
SELECT person_id FROM current WHERE current_id = 1
''')
self.current_person = cur.fetchone()[0]
self.config(width=5, font=formats['heading3'])
self.bind('<Button-1>', self.open_dialog)
def open_dialog(self, evt):
dialog = self.dialog_class(
self.root,
self.current_person,
finding_id=self.finding_id,
header=self.header,
pressed=evt.widget)
class LabelMovable(LabelHilited):
'''
A label that can be moved to a different grid position
by trading places with another widget on press of an
arrow key. The master can't contain anything but LabelMovables.
The ipadx, ipady, padx, pady, and sticky grid options can
be used as long as they're the same for every LabelMovable in
the master. With some more coding, columnspan and rowspan
could be set too but as is the spans should be left at
their default values which is 1.
'''
def __init__(self, master, first_column=0, first_row=0, *args, **kwargs):
LabelHilited.__init__(self, master, *args, **kwargs)
self.formats = make_formats_dict()
self.master = master
self.first_column = first_column
self.first_row = first_row
self.config(
takefocus=1,
bg=formats['highlight_bg'],
fg=formats['fg'],
font=formats['output_font'])
self.bind('<FocusIn>', self.highlight_on_focus)
self.bind('<FocusOut>', self.unhighlight_on_unfocus)
self.bind('<Key>', self.locate)
self.bind('<Key>', self.move)
def locate(self, evt):
'''
Get the grid position of the two widgets that will
trade places.
'''
self.mover = evt.widget
mover_dict = self.mover.grid_info()
self.old_col = mover_dict['column']
self.old_row = mover_dict['row']
self.ipadx = mover_dict['ipadx']
self.ipady = mover_dict['ipady']
self.pady = mover_dict['pady']
self.padx = mover_dict['padx']
self.sticky = mover_dict['sticky']
self.less_col = self.old_col - 1
self.less_row = self.old_row - 1
self.more_col = self.old_col + 1
self.more_row = self.old_row + 1
self.last_column = self.master.grid_size()[0] - 1
self.last_row = self.master.grid_size()[1] - 1
def move(self, evt):
'''
Determine which arrow key was pressed and make the trade.
'''
def move_left():
if self.old_col > self.first_column:
for child in self.master.winfo_children():
if (child.grid_info()['column'] == self.less_col and
child.grid_info()['row'] == self.old_row):
movee = child
movee.grid_forget()
movee.grid(
column=self.old_col, row=self.old_row,
ipadx=self.ipadx, ipady=self.ipady, padx=self.padx,
pady=self.pady, sticky=self.sticky)
self.mover.grid_forget()
self.mover.grid(
column=self.less_col, row=self.old_row, ipadx=self.ipadx,
ipady=self.ipady, padx=self.padx, pady=self.pady,
sticky=self.sticky)
def move_right():
if self.old_col < self.last_column:
for child in self.master.winfo_children():
if (child.grid_info()['column'] == self.more_col and
child.grid_info()['row'] == self.old_row):
movee = child
movee.grid_forget()
movee.grid(
column=self.old_col, row=self.old_row,
ipadx=self.ipadx, ipady=self.ipady, padx=self.padx,
pady=self.pady, sticky=self.sticky)
self.mover.grid_forget()
self.mover.grid(
column=self.more_col, row=self.old_row, ipadx=self.ipadx,
ipady=self.ipady, padx=self.padx, pady=self.pady,
sticky=self.sticky)
def move_up():
if self.old_row > self.first_row:
for child in self.master.winfo_children():
if (child.grid_info()['column'] == self.old_col and
child.grid_info()['row'] == self.less_row):
movee = child
movee.grid_forget()
movee.grid(
column=self.old_col, row=self.old_row,
ipadx=self.ipadx, ipady=self.ipady, padx=self.padx,
pady=self.pady, sticky=self.sticky)
self.mover.grid_forget()
self.mover.grid(
column=self.old_col, row=self.less_row, ipadx=self.ipadx,
ipady=self.ipady, padx=self.padx, pady=self.pady,
sticky=self.sticky)
def move_down():
if self.old_row < self.last_row:
for child in self.master.winfo_children():
if (child.grid_info()['column'] == self.old_col and
child.grid_info()['row'] == self.more_row):
movee = child
movee.grid_forget()
movee.grid(
column=self.old_col, row=self.old_row,
ipadx=self.ipadx, ipady=self.ipady, padx=self.padx,
pady=self.pady, sticky=self.sticky)
self.mover.grid_forget()
self.mover.grid(
column=self.old_col, row=self.more_row, ipadx=self.ipadx,
ipady=self.ipady, padx=self.padx, pady=self.pady,
sticky=self.sticky)
self.locate(evt)
keysyms = {
'Left' : move_left,
'Right' : move_right,
'Up' : move_up,
'Down' : move_down}
for k,v in keysyms.items():
if evt.keysym == k:
v()
self.fix_tab_order()
def fix_tab_order(self):
new_order = []
for child in self.master.winfo_children():
new_order.append((
child,
child.grid_info()['column'],
child.grid_info()['row']))
new_order.sort(key=lambda i: (i[1], i[2]))
for tup in new_order:
widg = tup[0]
widg.lift()
def highlight_on_focus(self, evt):
evt.widget.config(bg=self.formats['table_head_bg'])
def unhighlight_on_unfocus(self, evt):
evt.widget.config(bg=self.formats['highlight_bg'])
class Buttonx(tk.Button):
def __init__(self, master, *args, **kwargs):
tk.Button.__init__(self, master, *args, **kwargs)
pass
def winfo_subclass(self):
''' a method that works like built-in tkinter method
w.winfo_class() except it gets subclass names
of widget classes custom-made by inheritance '''
subclass = type(self).__name__
return subclass
# BUTTONS should not use a medium background color because the highlightthickness
# and highlightcolor options don't work and the button highlight focus might not
# be visible since Tkinter or Windows is choosing the color of the focus highlight
# and it can't be made thicker.
class Button(Buttonx):
''' Includes tk.Button in the colorizer scheme. '''
def __init__(self, master, *args, **kwargs):
Buttonx.__init__(self, master, *args, **kwargs)
self.config(
font=(formats['output_font']),
overrelief=tk.GROOVE,
activebackground=formats['table_head_bg'],
bg=formats['bg'],
fg=formats['fg'])
class ButtonFlatHilited(Buttonx):
'''
A button with no relief or border.
'''
def __init__(self, master, *args, **kwargs):
Buttonx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['highlight_bg'],
relief='flat',
fg=formats['fg'],
activebackground=formats['fg'], # bg color while pressed
activeforeground=formats['bg'], # fg color while pressed
overrelief='flat', # relief when hovered by mouse
bd=0) # prevents sunken relief while pressed
self.grid_configure(sticky='ew')
class ButtonQuiet(Buttonx):
''' Same color as background, no text. '''
def __init__(self, master, *args, **kwargs):
Buttonx.__init__(self, master, *args, **kwargs)
self.config(
text='',
width=3,
overrelief=tk.GROOVE,
activebackground=formats['table_head_bg'],
bg=formats['bg'],
fg=formats['fg'])
class ButtonPlain(Buttonx):
''' Sans serif font. '''
def __init__(self, master, *args, **kwargs):
Buttonx.__init__(self, master, *args, **kwargs)
self.config(
font=(formats['input_font']),
bd=0,
activebackground=formats['table_head_bg'],
bg=formats['bg'],
fg=formats['fg'])
self.bind('<Enter>', self.highlight)
def highlight(self, evt):
self.config(cursor='hand2')
class Entryx(tk.Entry):
def __init__(self, master, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
pass
def winfo_subclass(self):
''' a method that works like built-in tkinter method
w.winfo_class() except it gets subclass names
of widget classes custom-made by inheritance '''
subclass = type(self).__name__
return subclass
class Entry(Entryx):
def __init__(self, master, *args, **kwargs):
Entryx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['highlight_bg'],
fg=formats['fg'],
font=formats['input_font'],
insertbackground=formats['fg'])
class EntryUnhilited(Entryx):
'''
Background same as Label, Frame, etc.
'''
def __init__(self, master, *args, **kwargs):
Entryx.__init__(self, master, *args, **kwargs)
self.config(
bd=0,
bg=formats['bg'],
fg=formats['fg'],
font=formats['input_font'],
insertbackground=formats['fg'],
disabledbackground=formats['bg'],
disabledforeground=formats['fg'])
class EntryAutofill(EntryUnhilited):
'''
Simple case-insensitive autofill entry with no dropdown
list, lets you type as fast as you want. Values option
is not a real tkinter option, so you can't use
instance.config(values=new_values). Change values list
like this: instance.values = [5, 15, 19, 42]. Autofills
nothing till you type up to the first unique character.
Example: If the list has "Bill" and "Bilbo", nothing
will autofill till you type the second b or the l. You can
backspace and keep typing a different word with no extra
key strokes or controls and it still fills correctly.
Width is set to fit the longest item in the values list.
instance.config(textvariable=instance.var) is required
in the instance to turn on the autofill functionality.
'''
def __init__(self, master, *args, **kwargs):
EntryUnhilited.__init__(self, master, *args, **kwargs)
self.values = ['red', 'black', 'blue']
self.autofill = False
self.var = tk.StringVar()
self.filled = False
self.bind('<KeyRelease>', self.get_typed)
self.bind('<Key>', self.detect_pressed)
def match_string(self):
hits = []
got = self.var.get()
for item in self.values:
if item.lower().startswith(got.lower()):
hits.append(item)
return hits
def get_typed(self, event):
if self.autofill is False:
return
if len(event.keysym) == 1:
hits = self.match_string()
self.show_hit(hits)
def show_hit(self, lst):
if len(lst) == 1:
self.var.set(lst[0])
self.filled = True
def detect_pressed(self, event):
if self.autofill is False:
return
key = event.keysym
if len(key) == 1 and self.filled is True:
pos = self.index('insert')
self.delete(pos, 'end')
class EntryAutofillHilited(EntryAutofill):
'''
Same as EntryAutofill but has a highlighted background
like a typical Entry.
'''
def __init__(self, master, *args, **kwargs):
EntryAutofill.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'])
class EntryDefaultText(Entry):
def __init__(self, master, default_text, *args, **kwargs):
Entry.__init__(self, master, *args, **kwargs)
'''
For entries that need to have instructions/default text.
Can't use this with a widget that automatically
comes into focus since the default text would be cleared.
'''
self.default_text = default_text
self.formats = make_formats_dict()
var = tk.StringVar()
var.set(self.default_text)
self.config(
fg=self.formats['table_head_bg'],
bg=self.formats['highlight_bg'],
font=self.formats['show_font'],
textvariable=var)
self.textvariable = var
self.bind('<Button-1>', self.clear_default_text)
self.bind('<FocusIn>', self.clear_default_text)
self.bind('<FocusOut>', self.clear_selection)
def clear_default_text(self, evt=None):
if self.cget('state') == 'disabled':
print('disabled')
return
if self.get() == self.default_text:
self.delete(0, 'end')
self.config(
bg=self.formats['highlight_bg'],
font=self.formats['input_font'])
else:
self.config(
bg=self.formats['highlight_bg'],
font=self.formats['input_font'],
fg=self.formats['fg'])
def clear_selection(self, evt):
if len(self.get()) == 0:
self.insert(0, self.default_text)
self.config(
font=self.formats['show_font'],
fg=formats['table_head_bg'])
self.select_clear()
def replace_default_text(self):
self.insert(0, self.default_text)
self.config(fg=formats['table_head_bg'], font=self.formats['show_font'])
class LabelCopiable(Entryx):
'''
To use as a Label whose text can be selected
with mouse, set the state to disabled after
constructing the widget and giving it text.
Enable temporarily to change color or text, for example.
'''
def __init__(self, master, *args, **kwargs):
Entryx.__init__(self, master, *args, **kwargs)
self.config(
readonlybackground=self.cget('background'),
justify='center',
bd=0,
takefocus=0)
class LabelGoTo(Labelx):
'''
Ctrl+click runs code relevant to the entity named in
the clicked Label. For example, if label says John
Doe, Ctrl+click label can be used to make John Doe the
current person. The subject_id parameter can be used for
any entity with an ID such as person, place, citation.
The EntryLabel/LabelGoTo in dialogs can't be used with Ctrl+click to
change the current person. Probably could be done once but
not twice because the findings table that existed when the
dialog was made would be destroyed upon making a new table
for a new current person. So trying to change the current
person wouldn't work a 2nd time, so I'm not going to allow
it at all.
'''
def __init__(
self,
master,
table=None,
change_person=None,
subject_id=None,
place_id=None,
source_id=None,
citation_id=None,
*args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.formats = make_formats_dict()
self.table = table
self.change_person = change_person
self.subject_id = subject_id
self.bind('<Enter>', self.highlight_on_enter)
self.bind('<Leave>', self.unhighlight_on_leave)
self.bind('<Button-1>', self.set_focus, add='+')
self.bind('<Control-Button-1>', self.go_to_entity)
self.config(
takefocus=1,
anchor='w',
bg=self.formats['bg'],
fg=self.formats['fg'],
font=self.formats['input_font'])
self.grid_configure(sticky='ew')
def go_to_entity(self, evt):
'''
self.change_person is for the name of the function passed
to this widget when it's made which changes the current
person displayed on the persons tab.
'''
if self.table is None:
return
self.change_person(
self.table.master,
self.table.main.persons.attributes_content,
self.table.main.new_person_fill,
self.table.main.persons.top_pic_button,
self.table.main,
self.table.main.tabs.store['person'],
self.subject_id)
def set_focus(self, evt):
self.focus_set()
def highlight_on_enter(self, evt):
self.config(bg=self.formats['highlight_bg'])
def unhighlight_on_leave(self, evt):
self.config(bg=self.formats['bg'])
# for demo of LabelCopiable see label_with_selectable_text.py
class Textx(tk.Text):
def __init__(self, master, *args, **kwargs):
tk.Text.__init__(self, master, *args, **kwargs)
def winfo_subclass(self):
''' '''
subclass = type(self).__name__
return subclass
class Text(Textx):
def __init__(self, master, *args, **kwargs):
Textx.__init__(self, master, *args, **kwargs)
self.config(
wrap='word',
bg=formats['bg'],
fg=formats['fg'],
insertbackground=formats['fg'])
class LabelStylable(Textx):
def __init__(self, master, *args, **kwargs):
Textx.__init__(self, master, *args, **kwargs)
self.master = master
self.bind('<Map>', lambda event: self.set_height())
self.tag_config('bold', font="Helvetica 12 bold")
self.tag_config('italic', font="Helvetica 12 italic")
self.config(wrap='word', padx=12, pady=12)
def set_height(self):
height = self.count(1.0, 'end', 'displaylines')
self.config(height=height)
self.configure(state="disabled")
# # to use LabelStylable:
# stylin = LabelStylable(root, width=75)
# stylin.insert("end", "Hello, ")
# stylin.insert("end", "silly ", "italic")
# stylin.insert("end", "world", "bold")
class MessageCopiable(Textx):
'''
To use as a Label whose text can be selected
with mouse, set the state to disabled after
constructing the widget and giving it text.
Enable temporarily to change color or text, for example.
'''
def __init__(self, master, *args, **kwargs):
Textx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['bg'],
fg=formats['fg'],
borderwidth=0,
wrap='word',
state='disabled',
font=(formats['output_font']),
takefocus=0)
def set_height(self):
# answer is wrong first time thru mainloop so update:
self.update_idletasks()
lines = self.count('1.0', 'end', 'displaylines')
self.config(height=lines)
self.tag_configure('center', justify='center')
self.tag_add('center', '1.0', 'end')
self.config(state='disabled')
# How to use above by example:
# www = wdg.MessageCopiable(root, width=32)
# www.insert(1.0,
# 'Maecenas quis elit eleifend, lobortis turpis at, iaculis '
# 'odio. Phasellus congue, urna sit amet posuere luctus, mauris '
# 'risus tincidunt sapien, vulputate scelerisque ipsum libero at '
# 'neque. Nunc accumsan pellentesque nulla, a ultricies ex '
# 'convallis sit amet. Etiam ut sollicitudi felis, sit amet '
# 'dictum lacus. Mauris sed mattis diam. Pellentesque eu malesuada '
# 'ipsum, vitae sagittis nisl Morbi a mi vitae nunc varius '
# 'ullamcorper in ut urna. Maecenas auctor ultrices orci. '
# 'Donec facilisis a tortor pellentesque venenatis. Curabitur '
# 'pulvinar bibendum sem, id eleifend lorem sodales nec. Mauris '
# 'eget scelerisque libero. Lorem ipsum dolor sit amet, consectetur '
# 'adipiscing elit. Integer vel tellus nec orci finibus ornare. '
# 'Praesent pellentesque aliquet augue, nec feugiat augue posuere ')
# www.grid()
# www.set_height()
class Checkbuttonx(tk.Checkbutton):
def __init__(self, master, *args, **kwargs):
tk.Checkbutton.__init__(self, master, *args, **kwargs)
pass
def winfo_subclass(self):
''' '''
subclass = type(self).__name__
return subclass
class Checkbutton(Checkbuttonx):
def __init__(self, master, *args, **kwargs):
Checkbuttonx.__init__(self, master, *args, **kwargs)
'''
To see selection set the selectcolor
option to either bg or highlight_bg.
'''
self.config(
bg=formats['bg'],
fg=formats['fg'],
activebackground=formats['highlight_bg'],
selectcolor=formats['bg'],
padx=6, pady=6)
class Radiobuttonx(tk.Radiobutton):
def __init__(self, master, *args, **kwargs):
tk.Radiobutton.__init__(self, master, *args, **kwargs)
pass
def winfo_subclass(self):
''' '''
subclass = type(self).__name__
return subclass
class Radiobutton(Radiobuttonx):
def __init__(self, master, *args, **kwargs):
Radiobuttonx.__init__(self, master, *args, **kwargs)
'''
To see selection set the selectcolor
option to either bg or highlight_bg.
'''
self.config(
bg=formats['bg'],
fg=formats['fg'],
activebackground=formats['highlight_bg'],
selectcolor=formats['bg'],
padx=6, pady=6)
class RadiobuttonHilited(Radiobuttonx):
def __init__(self, master, *args, **kwargs):
Radiobuttonx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['highlight_bg'],
activebackground=formats['bg'],
highlightthickness=3,
overrelief='sunken',
font=formats['output_font'],
fg=formats['fg'],
selectcolor=formats['highlight_bg'],
padx=6, pady=6)
class Toplevelx(tk.Toplevel):
'''
All my toplevels have to declare a parent whether they need one or not.
This keeps the code consistent and symmetrical across all widgets,
even though Tkinter doesn't require a parent for its Toplevel.
'''
def __init__(self, master, *args, **kwargs):
tk.Toplevel.__init__(self, master, *args, **kwargs)
def winfo_subclass(self):
''' '''
subclass = type(self).__name__
return subclass
class Toplevel(Toplevelx):
def __init__(self, master, *args, **kwargs):
Toplevelx.__init__(self, master, *args, **kwargs)
self.config(bg=formats['bg'])
class ToplevelHilited(Toplevelx):
def __init__(self, *args, **kwargs):
Toplevelx.__init__(self, *args, **kwargs)
self.config(bg=formats['highlight_bg'])
class ToolTip(Toplevelx):
'''
I think I stopped working on this for some reason so it's
probably not finished.
Text tips that show full text when some text doesn't show
due to the column being too narrow. Part of my
never-ending quest to eradicate resizable columns from GUI
applications that wouldn't need resizable columns if they
were designed for their space to hold the information they
are supposed to display.
To use:
instance = ToolTip(parent, overwidthtip=True)
instance.hoverees.extend([ent2, ent1, lab3, radio2])
instance.bind_widgets()
'''
def __init__(self, master, overwidthtip=False, *args, **kwargs):
Toplevelx.__init__(self, master, *args, **kwargs)
self.withdraw()
self.overwidthtip = overwidthtip
self.widget = None
self.text = ''
self.hoverees = []
self.x = self.y = 0
self.wm_overrideredirect(1)
self.make_widgets()
def make_widgets(self):
self.label = LabelNegative(
self,
justify='left',
relief='solid',
bd=1)
self.label.pack(ipadx=6, ipady=3)
def bind_widgets(self):
for widg in self.hoverees:
widg.bind('<Enter>', self.enter)
widg.bind('<Leave>', self.leave)
def do_the_geometry(self):
maxvert = self.widget.winfo_screenheight()
x, y, cx, cy = self.widget.bbox('insert')
mouse_at = self.widget.winfo_pointerxy()
tip_shift = 32
if mouse_at[1] < maxvert - tip_shift * 2:
x = mouse_at[0] + tip_shift
y = mouse_at[1] + tip_shift
else:
x = mouse_at[0] + tip_shift
y = mouse_at[1] - tip_shift
self.wm_geometry('+{}+{}'.format(x, y))
def show_tip(self):
self.do_the_geometry()
self.deiconify()
def enter(self, evt):
self.widget = evt.widget
self.text = self.widget.get()
self.config_tip()
def leave(self, evt):
self.hide_tip()
def hide_tip(self):
self.withdraw()
self.label.config(text='')
def config_tip(self):
if self.overwidthtip is True:
self.config_overwidthtip()
return
self.label.config(text=self.text)
self.show_tip()
def config_overwidthtip(self):
if len(self.text) > self.widget.cget('width'):
self.label.config(text=self.text)
self.show_tip()
class Canvasx(tk.Canvas):
def __init__(self, master, *args, **kwargs):
tk.Canvas.__init__(self, master, *args, **kwargs)
pass
def winfo_subclass(self):
''' '''
subclass = type(self).__name__
return subclass
class Canvas(Canvasx):
def __init__(self, master, *args, **kwargs):
Canvasx.__init__(self, master, *args, **kwargs)
self.config(bg=formats['bg'], bd=0, highlightthickness=0)
class CanvasHilited(Canvasx):
def __init__(self, master, *args, **kwargs):
Canvasx.__init__(self, master, *args, **kwargs)
self.config(bg=formats['highlight_bg'], bd=0, highlightthickness=0)
# ************** statusbar tooltips sizegrip **************
'''
Statusbar messages on focus-in to individual widgets,
non-obtrusive tooltips, and non-ttk replacement for ttk.Sizegrip.
'''
def run_statusbar_tooltips(visited, status_label, tooltip_label):
'''
Uses lambda to add args to event
since tkinter expects only one arg in a callback.
'''
def handle_statusbar_tooltips(event):
for tup in visited:
if tup[0] is event.widget:
if event.type == '9': # FocusIn
status_label.config(text=tup[1])
elif event.type == '10': # FocusOut
status_label.config(text='')
elif event.type == '7': # Enter
tooltip_label.grid(
column=1, row=0,
sticky='e', padx=(6,24))
tooltip_label.config(
text=tup[2],
bg='black',
fg='white',
font=formats['status'])
elif event.type == '8': # Leave
tooltip_label.grid_remove()
tooltip_label.config(
bg=formats['bg'], text='', fg=formats['bg'])
statusbar_events = ['<FocusIn>', '<FocusOut>', '<Enter>', '<Leave>']
for tup in visited:
widg = tup[0]
status = tup[1]
tooltip = tup[2]
for event_pattern in statusbar_events:
# error if tup[0] has been destroyed
# so don't use these with destroyable widgets
# different tooltips are available in utes.py
widg.bind(event_pattern, handle_statusbar_tooltips, add='+')
status_label.config(font=formats['status'])
class StatusbarTooltips(Frame):
'''
To use this:
In self.make_widgets()...
some_statusbar = wdg.StatusbarTooltips(self)
some_statusbar.grid(column=0, row=2, sticky='ew') # use last row in toplevel
visited = (
(self.widget1,
'status bar message on focus in',
'tooltip message on mouse hover.'),
(self.widget2,
'status bar message on focus in',
'tooltip message on mouse hover.'))
wdg.run_statusbar_tooltips(
visited,
roles_statusbar.status_label,
roles_statusbar.tooltip_label)
If parent is a Toplevel and you don't want the Toplevel to be resizable,
use resizer=False when instantiating the Statusbar and add this:
dialog.resizable(False, False) --that's width and height in that order.
'''
def __init__(self, master, resizer=True, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.master = master # root or toplevel
self.sizer = Sizer(self.master)
self.grid_columnconfigure(0, weight=1)
relief = Frame(self, bd=2, relief='sunken')
relief.grid(column=0, row=0, sticky='news')
relief.grid_columnconfigure(0, weight=1)
self.status_label = Label(
relief, cursor='arrow', anchor='w', )
self.tooltip_label = Label(
relief, bd=2, relief='sunken', anchor='e')
if resizer is True:
self.sizer.place(relx=1.0, rely=1.0, anchor='se')
self.sizer.bind('<B1-Motion>', self.sizer.resize_se_corner)
self.status_label.grid(
column=0, row=0, sticky='w', padx=3, ipadx=6)
class Sizer(Label):
def __init__(self, master, icon='sizer_15_dark', *args, **kwargs):
Label.__init__(self, master, *args, **kwargs)
'''
SE corner gripper/resizer. Replaces ttk.Sizegrip.
The master has to be a root or toplevel window
which is easy because it's placed not gridded.
But it will overlap other things so for example
the statusbar tooltips had to be moved to the left
with padding. See StatusbarTooltips class in widgets.py
for an example of how to place() and bind() this.
'''
self.master = master
file = 'images/icons/{}.png'.format(icon)
img = Image.open(file)
self.tk_img = ImageTk.PhotoImage(img)
self.config(
bg=formats['bg'],
bd=0,
cursor='size_nw_se',
image=self.tk_img)
def resize_se_corner(self, evt):
'''
So that mouse will start dragging from exactly where it's
clicked instead of jumping somewhere before dragging:
Adjust dx and dy to be equal to
click coordinates re: screen
minus window coordinates re: screen
plus sizer dimensions.
'''
self.new_w =(
evt.x_root -
self.master.winfo_rootx() +
evt.widget.winfo_reqwidth())
self.new_h = (
evt.y_root -
self.master.winfo_rooty() +
evt.widget.winfo_reqheight())
if self.new_w < 10:
self.new_w = 10
if self.new_h < 10:
self.new_h = 10
self.master.geometry('{}x{}'.format(self.new_w, self.new_h))
class TabBook(Framex):
def __init__(
self, master, root=None, side='nw', bd=0, tabwidth=9,
selected='', tabs=[], minx=0.90,
miny=0.85, case='title', *args, **kwargs):
Framex.__init__(self, master, *args, **kwargs)
'''
The tab is the part that sticks out with the title
you click to activate the page which holds that
tab's content. To add widgets grid them with
instance.store[page] as the master. For example:
inst.store['place'] where 'place' is a string from
the original tabs parameter (list of tuples).
'''
self.master = master
self.side = side
self.bd = bd
self.tabwidth = tabwidth
self.selected = selected
self.minx = self.master.winfo_screenwidth() * minx
self.miny = self.master.winfo_screenheight() * miny
self.case = case
self.formats = make_formats_dict()
self.tabdict = {}
for tab in tabs:
self.tabdict[tab[0]] = [tab[1]]
# key is 'title', value is ['acceLerator']
# value will have page appended to it
self.store = {}
self.active = None
self.make_widgets()
self.open_tab_alt(root)
def make_widgets(self):
''' '''
self.tab_base = Frame(self)
self.border_base = FrameHilited2(self)
self.notebook = Frame(self.border_base)
self.tab_frame = FrameHilited2(self.tab_base)
self.tabless = Frame(self.tab_base)
self.spacer = Frame(self.tabless)
self.top_border = FrameHilited2(self.tabless, height=1)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)
self.notebook.grid_columnconfigure(0, weight=1, minsize=self.minx)
self.notebook.grid_rowconfigure(0, weight=1, minsize=self.miny)
self.grid_tabs()
c = 0
for tab in self.tabdict:
lab = Label(
self.tab_frame,
width=int(self.tabwidth),
takefocus=1)
if self.case == 'title':
lab.config(text=tab.title())
elif self.case == 'lower':
lab.config(text=tab.lower())
elif self.case == 'upper':
lab.config(text=tab.upper())
self.tabdict[tab].append(lab)
if self.side in ('ne', 'nw'):
lab.grid(column=c, row=0, padx=1, pady=(1, 0))
elif self.side in ('se', 'sw'):
lab.grid(column=c, row=0, padx=1, pady=(0, 1))
lab.bind('<Button-1>', self.make_active)
lab.bind('<FocusIn>', self.highlight_tab)
lab.bind('<FocusOut>', self.unhighlight_tab)
lab.bind('<Key-space>', self.make_active)
lab.bind('<Key-Return>', self.make_active)
lab.bind('<ButtonRelease-1>', self.unhighlight_tab)
create_tooltip(lab, 'Alt + {}'.format(self.tabdict[tab][0]))
page = Frame(self.notebook)
page.grid(column=0, row=0, sticky='news')
page.grid_remove()
self.tabdict[tab].append(page)
# simplify tab references w/ a dict whose value is just a widget
self.store[tab] = page
c += 1
selected_page = self.tabdict[self.selected][2] # page
selected_page.grid()
self.active = self.tabdict[self.selected][1] # tab
self.make_active()
def grid_tabs(self):
if self.side in ('nw', 'ne'):
pady = (0, 1)
tab_row = 0
body_row = 1
spacer_row = 0
border_row = 1
elif self.side in ('sw', 'se'):
pady=(1, 0)
tab_row = 1
body_row = 0
spacer_row = 1
border_row = 0
if self.side in ('nw', 'sw'):
tab_col = 0
tabless_col = 1
elif self.side in ('ne', 'se'):
tab_col = 1
tabless_col = 0
# self.notebook switches pady
self.notebook.grid(column=0, row=0, padx=1, pady=pady, sticky='news')
# self.tab_base and borderbase switch rows
self.tab_base.grid(column=0, row=tab_row, sticky='news')
self.tab_base.grid_columnconfigure(tabless_col, weight=1)
self.tab_base.grid_rowconfigure(0, weight=1)
self.border_base.grid(column=0, row=body_row, sticky='news')
# self.tab_frame and self.tabless switch cols
self.tab_frame.grid(column=tab_col, row=0, sticky='ew')
self.tabless.grid(column=tabless_col, row=0, sticky='news')
self.tabless.grid_columnconfigure(0, weight=1)
self.tabless.grid_rowconfigure(spacer_row, weight=1)
# self.spacer and self.top_border switch rows
self.spacer.grid(column=0, row=tab_row, sticky='ns')
self.top_border.grid(column=0, row=border_row, sticky='ew')
def highlight_tab(self, evt):
# accelerators don't work if notebook not visible
if evt.widget in self.store.values():
evt.widget.config(fg='yellow')
def unhighlight_tab(self, evt):
# accelerators don't work if notebook not visible
if evt.widget in self.store.values():
evt.widget.config(fg=self.formats['fg'])
def make_active(self, evt=None):
''' Open the selected tab & reconfigure it to look open. '''
self.formats = make_formats_dict()
# position attributes are needed in the instance
self.posx = self.winfo_rootx()
self.posy = self.winfo_rooty()
# if not running on load
if evt:
self.active = evt.widget
self.active.focus_set()
# if evt was alt key accelerator
if (evt.widget is self.master or
evt.keysym not in ('space', 'Return')):
for k,v in self.tabdict.items():
if evt.keysym in (v[0], v[0].lower()):
self.active = v[1]
self.active.focus_set()
# if evt was spacebar, return key, or mouse button
elif evt.type in ('2', '4'):
self.active.config(fg=self.formats['fg'])
# remove all pages and regrid the right one
for k,v in self.tabdict.items():
if self.active == v[1]:
for widg in self.tabdict.values():
widg[2].grid_remove()
v[2].grid()
# unhighlight all tabs
for tab in self.tabdict.values():
tab[1].config(
bg=self.formats['highlight_bg'])
# highlight active tab
self.active.config(
bg=self.formats['bg'],
font=self.formats['heading3'])
def open_tab_alt(self, root_window):
''' Bindings for notebook tab accelerators. '''
for k,v in self.tabdict.items():
key_combo_upper = '<Alt-Key-{}>'.format(v[0])
root_window.bind(key_combo_upper, self.make_active)
key_combo_lower = '<Alt-Key-{}>'.format(v[0].lower())
root_window.bind(key_combo_lower, self.make_active)
unkey_combo_upper = '<Alt-KeyRelease-{}>'.format(v[0])
root_window.bind_all(unkey_combo_upper, self.unhighlight_tab)
unkey_combo_lower = '<Alt-KeyRelease-{}>'.format(v[0].lower())
root_window.bind_all(unkey_combo_lower, self.unhighlight_tab)
# ttk only below this point *********************************************
class Notebookx(ttk.Notebook):
def __init__(self, master, *args, **kwargs):
ttk.Notebook.__init__(self, master, *args, **kwargs)
def winfo_subclass(self):
subclass = type(self).__name__
return subclass
class Tabs(Notebookx):
def __init__(self, master, tab_names, *args, **kwargs):
Notebookx.__init__(self, master, *args, **kwargs)
self.master = master
self.tab_names = tab_names
self.store = {}
self.enable_traversal()
for tup in self.tab_names:
tab_frm = Frame(self, name='{}_tab'.format(tup[0]))
self.add(tab_frm)
self.tab(tab_frm, text=tup[0].title(), underline=tup[1], padding='16')
self.store[tup[0]] = tab_frm
class Comboboxx(ttk.Combobox):
'''
Provides a way of changing colors in the combobox listbox
dropdown/popdown as well as the ability to detect subclass.
'''
def __init__(self, master, *args, **kwargs):
ttk.Combobox.__init__(self, master, *args, **kwargs)
def winfo_subclass(self):
subclass = type(self).__name__
return subclass
def config_popdown(self, **kwargs):
self.tk.eval(
'[ttk::combobox::PopdownWindow {}].f.l configure {}'.format(
self,
' '.join(self._options(kwargs))))
class ClickAnywhereCombo(Comboboxx):
'''
User can ctrl+click in entry box or arrow--not just arrow--
to make dropdown fly down. Not needed for readonly combobox
which already does this. Problem with using plain click for
this: it prevents dragging in the entry to select text.
So ctrl+click is a compromise, so that dragging to highlight
will still work normally, and the arrow works normally.
'''
def __init__(self, master, *args, **kwargs):
Comboboxx.__init__(self, master, *args, **kwargs)
self.bind('<Control-Button-1>', self.fly_down_on_click)
self.config(font=formats['input_font'])
def fly_down_on_click(self, evt):
if int(evt.type) == 4:
w = evt.widget
w.event_generate('<Down>', when='head')
class ClearableReadonlyCombobox(Comboboxx):
'''
User can clear mistaken selection by pressing Delete,
Escape, or Backspace.
'''
def __init__(self, master, *args, **kwargs):
Comboboxx.__init__(self, master, *args, **kwargs)
self.state(['readonly'])
self.bind("<Key-Delete>", self.allow_manual_deletion)
self.bind("<Key-Escape>", self.allow_manual_deletion)
self.bind("<Key-BackSpace>", self.allow_manual_deletion)
def allow_manual_deletion(self, evt):
if self.get() != "":
if evt.keysym in ("Escape", "Delete", "BackSpace"):
self.delete_content()
def delete_content(self):
self.state(['!readonly', 'selected'])
self.set("")
self.state(['readonly'])
class GromboReadonly(ClearableReadonlyCombobox):
'''
Provides grayed-out italic default text. User cannot add values
by typing into the entry part of the combobox.
'''
def __init__(self, master, default_text, *args, **kwargs):
ClearableReadonlyCombobox.__init__(self, master, *args, **kwargs)
self.default_text = default_text
var = tk.StringVar()
var.set(self.default_text)
self.config(
foreground=formats['table_head_bg'],
font=formats['show_font'],
textvariable=var)
self.textvariable = var
self.bind('<Button-1>', self.clear_default_text, add='+')
self.bind('<FocusIn>', self.clear_default_text)
self.bind('<FocusOut>', self.clear_selection)
def clear_default_text(self, evt=None):
if 'disabled' in self.state():
return
if self.get() == self.default_text:
self.delete(0, 'end')
self.config(foreground=formats['fg'])
self.config(font=formats['input_font'])
else:
self.config(foreground=formats['fg'])
self.config(font=formats['input_font'])
def clear_selection(self, evt):
if len(self.get()) == 0:
self.insert(0, self.default_text)
self.config(foreground='gray', font=formats['show_font'])
self.select_clear()
def show_error(self):
self.config(font=formats['input_font'])
self.config(foreground='red')
class Grombo(ClickAnywhereCombo):
'''
Provides grayed-out italic default text. User can add values
by typing into the entry part of the combobox.
'''
def __init__(self, master, default_text, *args, **kwargs):
ClickAnywhereCombo.__init__(self, master, *args, **kwargs)
self.default_text = default_text
var = tk.StringVar()
var.set(self.default_text)
self.config(
foreground=formats['table_head_bg'],
font=formats['show_font'],
textvariable=var)
self.textvariable = var
self.bind('<Button-1>', self.clear_default_text, add='+')
self.bind('<FocusIn>', self.clear_default_text)
self.bind('<FocusOut>', self.clear_selection)
def clear_default_text(self, evt=None):
if 'disabled' in self.state():
return
if self.get() == self.default_text:
self.delete(0, 'end')
self.config(foreground=formats['fg'])
self.config(font=formats['input_font'])
else:
self.config(foreground=formats['fg'])
self.config(font=formats['input_font'])
def clear_selection(self, evt=None):
if len(self.get()) == 0:
self.insert(0, self.default_text)
self.config(foreground='gray', font=formats['show_font'])
self.select_clear()
def show_error(self):
self.config(font=formats['input_font'])
self.config(foreground='red')