mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-13 15:57:14 +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:
parent
630da89831
commit
b81c4cf6c5
40
PLUGINS.md
40
PLUGINS.md
@ -93,15 +93,39 @@ You can use `stringFromNumber()` from EDMC's `l10n.Locale` object to format numb
|
||||
this = sys.modules[__name__] # For holding module globals
|
||||
|
||||
def plugin_app(parent):
|
||||
"""
|
||||
Create a pair of TK widgets for the EDMC main window
|
||||
"""
|
||||
label = tk.Label(parent, text="Status:")
|
||||
this.status = tk.Label(parent, anchor=tk.W, text="")
|
||||
return (label, this.status)
|
||||
"""
|
||||
Create a pair of TK widgets for the EDMC main window
|
||||
"""
|
||||
label = tk.Label(parent, text="Status:") # By default widgets inherit the current theme's colors
|
||||
this.status = tk.Label(parent, text="", foreground="yellow") # Override theme's foreground color
|
||||
return (label, this.status)
|
||||
|
||||
# later on your event functions can directly update this.status["text"]
|
||||
this.status["text"] = "Happy!"
|
||||
# later on your event functions can update the contents of these widgets
|
||||
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
|
||||
|
156
theme.py
156
theme.py
@ -11,6 +11,7 @@ from os.path import join
|
||||
import Tkinter as tk
|
||||
import ttk
|
||||
import tkFont
|
||||
from ttkHyperlinkLabel import HyperlinkLabel
|
||||
|
||||
from config import appname, applongname, config
|
||||
|
||||
@ -99,15 +100,70 @@ class _Theme:
|
||||
def __init__(self):
|
||||
self.active = None # Starts out with no theme
|
||||
self.minwidth = None
|
||||
self.widgets = set()
|
||||
self.widgets = {}
|
||||
self.widgets_pair = []
|
||||
self.defaults = {}
|
||||
self.current = {}
|
||||
|
||||
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
|
||||
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):
|
||||
for child in widget.winfo_children():
|
||||
self.register(child)
|
||||
self.widgets.add(widget)
|
||||
|
||||
def register_alternate(self, 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
|
||||
def apply(self, root):
|
||||
|
||||
@ -182,35 +300,13 @@ class _Theme:
|
||||
self._colors(root, theme)
|
||||
|
||||
# Apply colors
|
||||
for widget in self.widgets:
|
||||
if isinstance(widget, tk.BitmapImage):
|
||||
# not a widget
|
||||
widget.configure(foreground = self.current['foreground'],
|
||||
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
|
||||
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'])
|
||||
for widget in set(self.widgets):
|
||||
if isinstance(widget, tk.Widget) and not widget.winfo_exists():
|
||||
self.widgets.pop(widget) # has been destroyed
|
||||
else:
|
||||
self._update_widget(widget)
|
||||
|
||||
# Switch menus
|
||||
for pair, gridopts in self.widgets_pair:
|
||||
for widget in pair:
|
||||
widget.grid_remove()
|
||||
|
Loading…
x
Reference in New Issue
Block a user