1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-17 17:42:20 +03:00

More control over widget colors

Documented mechanism to apply theme to dynamically created widgets.
Allow widgets to override theme colors. Fixes #444
This commit is contained in:
Jonathan Harris 2019-09-24 00:03:33 +01:00
parent 630da89831
commit b81c4cf6c5
2 changed files with 158 additions and 38 deletions

View File

@ -96,12 +96,36 @@ def plugin_app(parent):
""" """
Create a pair of TK widgets for the EDMC main window Create a pair of TK widgets for the EDMC main window
""" """
label = tk.Label(parent, text="Status:") label = tk.Label(parent, text="Status:") # By default widgets inherit the current theme's colors
this.status = tk.Label(parent, anchor=tk.W, text="") this.status = tk.Label(parent, text="", foreground="yellow") # Override theme's foreground color
return (label, this.status) return (label, this.status)
# later on your event functions can directly update this.status["text"] # later on your event functions can update the contents of these widgets
this.status["text"] = "Happy!" this.status["text"] = "Happy!"
this.status["foreground"] = "green"
```
You can dynamically add and remove widgets on the main window by returning a tk.Frame from `plugin_app()` and later creating and destroying child widgets of that frame.
```python
from theme import theme
this = sys.modules[__name__] # For holding module globals
def plugin_app(parent):
"""
Create a frame for the EDMC main window
"""
this.frame = tk.Frame(parent)
return this.frame
# later on your event functions can add or remove widgets
row = this.frame.grid_size()[1]
new_widget_1 = tk.Label(this.frame, text="Status:")
new_widget_1.grid(row=row, column=0, sticky=tk.W)
new_widget_2 = tk.Label(this.frame, text="Unhappy!", foreground="red") # Override theme's foreground color
new_widget_2.grid(row=row, column=1, sticky=tk.W)
theme.update(this.frame) # Apply theme colours to the frame and its children, including the new widgets
``` ```
## Events ## Events

156
theme.py
View File

@ -11,6 +11,7 @@ from os.path import join
import Tkinter as tk import Tkinter as tk
import ttk import ttk
import tkFont import tkFont
from ttkHyperlinkLabel import HyperlinkLabel
from config import appname, applongname, config from config import appname, applongname, config
@ -99,15 +100,70 @@ class _Theme:
def __init__(self): def __init__(self):
self.active = None # Starts out with no theme self.active = None # Starts out with no theme
self.minwidth = None self.minwidth = None
self.widgets = set() self.widgets = {}
self.widgets_pair = [] self.widgets_pair = []
self.defaults = {}
self.current = {}
def register(self, widget): def register(self, widget):
# Note widget and children for later application of a theme. Note if the widget has explicit fg or bg attributes.
assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget
if not self.defaults:
# Can't initialise this til window is created # Windows, MacOS
self.defaults = {
'fg' : tk.Label()['foreground'], # SystemButtonText, systemButtonText
'bg' : tk.Label()['background'], # SystemButtonFace, White
'font' : tk.Label()['font'], # TkDefaultFont
'bitmapfg' : tk.BitmapImage()['foreground'], # '-foreground {} {} #000000 #000000'
'bitmapbg' : tk.BitmapImage()['background'], # '-background {} {} {} {}'
'entryfg' : tk.Entry()['foreground'], # SystemWindowText, Black
'entrybg' : tk.Entry()['background'], # SystemWindow, systemWindowBody
'entryfont' : tk.Entry()['font'], # TkTextFont
'frame' : tk.Frame()['background'], # SystemButtonFace, systemWindowBody
'menufg' : tk.Menu()['foreground'], # SystemMenuText,
'menubg' : tk.Menu()['background'], # SystemMenu,
'menufont' : tk.Menu()['font'], # TkTextFont
}
if widget not in self.widgets:
# No general way to tell whether the user has overridden, so compare against widget-type specific defaults
attribs = set()
if isinstance(widget, tk.BitmapImage):
if widget['foreground'] not in ['', self.defaults['bitmapfg']]:
attribs.add('fg')
if widget['background'] not in ['', self.defaults['bitmapbg']]:
attribs.add('bg')
elif isinstance(widget, tk.Entry) or isinstance(widget, ttk.Entry):
if widget['foreground'] not in ['', self.defaults['entryfg']]:
attribs.add('fg')
if widget['background'] not in ['', self.defaults['entrybg']]:
attribs.add('bg')
if 'font' in widget.keys() and str(widget['font']) not in ['', self.defaults['entryfont']]:
attribs.add('font')
elif isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame) or isinstance(widget, tk.Canvas):
if ('background' in widget.keys() or isinstance(widget, tk.Canvas)) and widget['background'] not in ['', self.defaults['frame']]:
attribs.add('bg')
elif isinstance(widget, HyperlinkLabel):
pass # Hack - HyperlinkLabel changes based on state, so skip
elif isinstance(widget, tk.Menu):
if widget['foreground'] not in ['', self.defaults['menufg']]:
attribs.add('fg')
if widget['background'] not in ['', self.defaults['menubg']]:
attribs.add('bg')
if widget['font'] not in ['', self.defaults['menufont']]:
attribs.add('font')
else: # tk.Button, tk.Label
if 'foreground' in widget.keys() and widget['foreground'] not in ['', self.defaults['fg']]:
attribs.add('fg')
if 'background' in widget.keys() and widget['background'] not in ['', self.defaults['bg']]:
attribs.add('bg')
if 'font' in widget.keys() and widget['font'] not in ['', self.defaults['font']]:
attribs.add('font')
self.widgets[widget] = attribs
if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame): if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame):
for child in widget.winfo_children(): for child in widget.winfo_children():
self.register(child) self.register(child)
self.widgets.add(widget)
def register_alternate(self, pair, gridopts): def register_alternate(self, pair, gridopts):
self.widgets_pair.append((pair, gridopts)) self.widgets_pair.append((pair, gridopts))
@ -175,6 +231,68 @@ class _Theme:
} }
# Apply current theme to a widget and its children, and register it for future updates
def update(self, widget):
assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget
if not self.current:
return # No need to call this for widgets created in plugin_app()
self.register(widget)
self._update_widget(widget)
if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame):
for child in widget.winfo_children():
self._update_widget(child)
# Apply current theme to a single widget
def _update_widget(self, widget):
assert widget in self.widgets, '%s %s "%s"' %(widget.winfo_class(), widget, 'text' in widget.keys() and widget['text'])
attribs = self.widgets.get(widget, [])
if isinstance(widget, tk.BitmapImage):
# not a widget
if 'fg' not in attribs:
widget.configure(foreground = self.current['foreground']),
if 'bg' not in attribs:
widget.configure(background = self.current['background'])
elif 'cursor' in widget.keys() and str(widget['cursor']) not in ['', 'arrow']:
# Hack - highlight widgets like HyperlinkLabel with a non-default cursor
if 'fg' not in attribs:
widget.configure(foreground = self.current['highlight']),
if 'insertbackground' in widget.keys(): # tk.Entry
widget.configure(insertbackground = self.current['foreground']),
if 'bg' not in attribs:
widget.configure(background = self.current['background'])
if 'highlightbackground' in widget.keys(): # tk.Entry
widget.configure(highlightbackground = self.current['background'])
if 'font' not in attribs:
widget.configure(font = self.current['font'])
elif 'activeforeground' in widget.keys():
# e.g. tk.Button, tk.Label, tk.Menu
if 'fg' not in attribs:
widget.configure(foreground = self.current['foreground'],
activeforeground = self.current['activeforeground'],
disabledforeground = self.current['disabledforeground'])
if 'bg' not in attribs:
widget.configure(background = self.current['background'],
activebackground = self.current['activebackground'])
if platform == 'darwin' and isinstance(widget, tk.Button):
widget.configure(highlightbackground = self.current['background'])
if 'font' not in attribs:
widget.configure(font = self.current['font'])
elif 'foreground' in widget.keys():
# e.g. ttk.Label
if 'fg' not in attribs:
widget.configure(foreground = self.current['foreground']),
if 'bg' not in attribs:
widget.configure(background = self.current['background'])
if 'font' not in attribs:
widget.configure(font = self.current['font'])
elif 'background' in widget.keys() or isinstance(widget, tk.Canvas):
# e.g. Frame, Canvas
if 'bg' not in attribs:
widget.configure(background = self.current['background'],
highlightbackground = self.current['disabledforeground'])
# Apply configured theme # Apply configured theme
def apply(self, root): def apply(self, root):
@ -182,35 +300,13 @@ class _Theme:
self._colors(root, theme) self._colors(root, theme)
# Apply colors # Apply colors
for widget in self.widgets: for widget in set(self.widgets):
if isinstance(widget, tk.BitmapImage): if isinstance(widget, tk.Widget) and not widget.winfo_exists():
# not a widget self.widgets.pop(widget) # has been destroyed
widget.configure(foreground = self.current['foreground'], else:
background = self.current['background']) self._update_widget(widget)
elif 'cursor' in widget.keys() and str(widget['cursor']) not in ['', 'arrow']:
# Hack - highlight widgets like HyperlinkLabel with a non-default cursor
widget.configure(foreground = self.current['highlight'],
background = self.current['background'],
font = self.current['font'])
elif 'activeforeground' in widget.keys():
# e.g. tk.Button, tk.Label, tk.Menu
widget.configure(foreground = self.current['foreground'],
background = self.current['background'],
activeforeground = self.current['activeforeground'],
activebackground = self.current['activebackground'],
disabledforeground = self.current['disabledforeground'],
font = self.current['font']
)
elif 'foreground' in widget.keys():
# e.g. ttk.Label
widget.configure(foreground = self.current['foreground'],
background = self.current['background'],
font = self.current['font'])
elif 'background' in widget.keys():
# e.g. Frame
widget.configure(background = self.current['background'])
widget.configure(highlightbackground = self.current['disabledforeground'])
# Switch menus
for pair, gridopts in self.widgets_pair: for pair, gridopts in self.widgets_pair:
for widget in pair: for widget in pair:
widget.grid_remove() widget.grid_remove()