# $Id: Style.py,v 1.66.2.9 2006/12/10 11:04:33 marcusva Exp $
#
# Copyright (c) 2004-2006, Marcus von Appen
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Style class for widgets."""
import os.path, copy
from UserDict import IterableUserDict
from Constants import *
from StyleInformation import StyleInformation
# Import the default theme engine path.
import sys
sys.path.append (DEFAULTDATADIR)
from themes import default
class WidgetStyle (IterableUserDict):
"""WidgetStyle (dict=None) -> WidgetStyle
A style dictionary that tracks changes of its items.
The WidgetStyle dictionary class allows one to let a bound method be
executed, whenever a value of the dictionary changed. Additionally,
the WidgetStyle class supports recursive changes, so that a
WidgetStyle object within this WidgetStyle can escalate the value
change and so on.
To set up the value change handler you can bind it using the
set_value_changed() method of the WidgetStyle class:
style.set_value_changed (callback_method)
This will cause it and all values of it, which are WidgetStyle
objects to invoke the callback_method on changes.
To get the actually set value change handler, the
get_value_changed() method can be used:
callback = style.get_value_changed ()
"""
def __init__ (self, dict=None):
# Notifier slot.
self._valuechanged = None
IterableUserDict.__init__ (self, dict)
def __copy__ (self):
"""W.__copy__ () -> WidgetStyle
Creates a shallow copy of the WidgetStyle dictionary.
"""
newdict = WidgetStyle ()
keys = self.keys ()
for k in keys:
newdict[k] = self[k]
newdict.set_value_changed (self._valuechanged)
return newdict
def __deepcopy__(self, memo={}):
"""W.__deepcopy__ (...) -> WidgetStyle.
Creates a deep copy of the WidgetStyle dictionary.
"""
newdict = WidgetStyle ()
keys = self.keys ()
for k in keys:
newdict[copy.deepcopy (k, memo)] = copy.deepcopy (self[k], memo)
newdict.set_value_changed (self._valuechanged)
memo [id (self)] = newdict
return newdict
def __setitem__ (self, i, y):
"""W.__setitem__ (i, y) <==> w[i] = y
"""
IterableUserDict.__setitem__ (self, i, y)
if isinstance (y, WidgetStyle):
y.set_value_changed (self._valuechanged)
if self._valuechanged:
self._valuechanged ()
def __delitem__ (self, y):
"""W.__delitem__ (i, y) <==> del w[y]
"""
IterableUserDict.__delitem__ (self, y)
if self._valuechanged:
self._valuechanged ()
def __repr__ (self):
"""W.__repr__ () <==> repr (W)
"""
return "WidgetStyle %s" % IterableUserDict.__repr__ (self)
def clear (self, y):
"""W.clear () -> None
Remove all items from the WidgetStyle dictionary.
"""
changed = self._valuechanged
self.set_value_changed (None)
IterableUserDict.clear (self)
self.set_value_changed (changed)
if changed:
changed ()
def pop (self, k, d=None):
"""W.pop (k, d=None) -> object
Remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError
is raised.
"""
changed = IterableUserDict.has_key (self, k)
v = IterableUserDict.pop (self, k, d)
if changed and self._valuechanged:
self._valuechanged ()
return v
def popitem (self):
"""W.popitem () -> (k, v)
Remove and return some (key, value) pair as a 2-tuple
Raises a KeyError if D is empty.
"""
v = IterableUserDict.popitem (self)
if self._valuechanged:
self._valuechanged ()
return v
def setdefault (self, k, d=None):
"""W.setdefault (k,d=None) -> W.get (k, d), also set W[k] = d if k not in W
"""
changed = not IterableUserDict.has_key (self, k)
v = IterableUserDict.setdefault (self, k, d)
if changed and self._valuechanged:
self._valuechanged ()
return v
def update (self, E, **F):
"""W.update (E, **F) -> None
Update W from E and F.
for k in E: W[k] = E[k] (if E has keys else: for (k, v) in E:
W[k] = v) then: for k in F: W[k] = F[k]
"""
amount = len (self)
IterableUserDict.update (self, E, **F)
if self._valuechanged and (len (self) != amount):
self._valuechanged ()
def get_value_changed (self):
"""W.get_value_changed (...) -> callable
Gets the set callback method for the dictionary changes.
"""
return self._valuechanged
def set_value_changed (self, method):
"""W.set_value_changed (...) -> None
Connects a method to invoke, when an item of the dict changes.
Raises a TypeError, if the passed argument is not callable.
"""
if method and not callable (method):
raise TypeError ("method must be callable")
values = self.values ()
for val in values:
if isinstance (val, WidgetStyle):
val.set_value_changed (method)
self._valuechanged = method
class Style (object):
"""Style () -> Style
Style class for drawing objects.
Style definitions
-----------------
Styles are used to change the appearance of the widgets. The drawing
methods of the Style class will use the specific styles of the
'styles' attribute to change the fore- and background colors, font
information and border settings of the specific widgets. The styles
are set using the lower lettered classname of the widget to draw.
Additionally the Style class supports cascading styles by falling
back to the next available class name in the widget its __mro__
list. If no specific set style could be found for the specific
widget type, the Style class will use the 'default' style entry of
its 'styles' dictionary.
Any object can register its own style definition and request it
using the correct key. A Button widget for example, could register
its style definition this way:
Style.styles['button'] = WidgetStyle ({ .... })
Any other button widget then will use this style by default, so
their look is all the same.
It is also possible to pass an own type dictionary to the drawing
methods of the attached engine class in order to get a surface,
which can be shown on the screen then:
own_style = WidgetStyle ({ ... })
surface = style.engine.draw_rect (width, height, own_style)
If the style should be based on an already existing one, the
copy_style() method can be used to retrieve a copy to work with
without touching the orignal one:
own_style = style.copy_style (my_button.__class__)
The BaseWidget class offers a get_style() method, which will copy
the style of the widget class to the specific widget instance. Thus
you can safely modify the instance specific style for the widget
without touching the style for all widgets of that class.
The style dictionaries registered within the 'styles' dictionary of
the Style class need to match some prerequisites to be useful. On
the one hand they need to incorporate specific key-value pairs,
which can be evaluated by the various drawing functions, on the
other they need to have some specific key-value pairs, which are
evaluated by the Style class.
The following lists give an overview about the requirements the
style dictionaries have to match, so that the basic Style class can
work with them as supposed.
Style entries
-------------
The registered style WidgetStyle dictionaries for the widgets need
to contain key-value pairs required by the functions of the
referenced modules. The following key-value pairs are needed to
create the surfaces:
bgcolor = WidgetStyle ({ STATE_TYPE : color, ... })
The background color to use for the widget surface.
fgcolor = WidgetStyle ({ STATE_TYPE : color, ... })
The foreground color to use for the widget. This is also the text
color for widgets, which will display text.
lightcolor = WidgetStyle ({ STATE_TYPE : color, ... })
darkcolor = WidgetStyle ({ STATE_TYPE : color, ... })
Used to create shadow border effects on several widgets. The color
values usually should be a bit brighter or darker than the bgcolor
values.
bordercolor = WidgetStyle ({ STATE_TYPE : color, ... })
Also used to create border effects on several widgets. In contrast
to the lightcolor and darkcolor entries, this is used, if flat
borders (BORDER_FLAT) have to drawn.
shadowcolor = (color1, color2)
The colors to use for dropshadow effects. The first tuple entry will
be used as inner shadow (near by the widget), the second as outer
shadow value.
image = WidgetStyle ({ STATE_TYPE : string })
Pixmap files to use instead of the background color. If the file is
not supplied or cannot be loaded, the respective bgcolor value is
used.
font = WidgetStyle ({ 'name' : string, 'size' : integer,
'alias' : integer, 'style' : integer })
Widgets, which support the display of text, make use of this
key-value pair. The 'name' key denotes the font name ('Helvetica')
or the full path to a font file ('/path/to/Helvetica.ttf'). 'size'
is the font size to use. 'alias' is interpreted as boolean value for
antialiasing. 'style' is a bit-wise combination of FONT_STYLE_TYPES
values as defined in ocempgui.draw.Constants and denotes additional
rendering styles to use.
shadow = integer
The size of the 3D border effect for a widget.
IMPORTANT: It is important to know, that the Style class only
supports a two-level hierarchy for styles. Especially the
copy_style() method is not aware of style entries of more than two
levels. This means, that a dictionary in a style dictionary is
possible (as done in the 'font' or the various color style entries),
but more complex style encapsulations are unlikely to work
correctly.
Some legal examples for user defined style entries:
style['ownentry'] = 99 # One level
style['ownentry'] = { 'foo' : 1, 'bar' : 2 } # Two levels: level1[level2]
This one however is not guaranteed to work correctly and thus should
be avoided:
style['ownentry'] = { 'foo' : { 'bar' : 1, 'baz' : 2 }, 'foobar' : { ... }}
Dicts and WidgetStyle
---------------------
OcempGUI uses a WidgetStyle dictionary class to keep track of
changes within styles and to update widgets on th fly on changing
those styles. When you are creating new style files (as explained in
the next section) you do not need to explicitly use the
WidgetStyle() dictionary, but can use plain dicts instead. Those
will be automatically replaced by a WidgetStyle on calling load().
You should however avoid using plain dicts if you are modifying
styles of widgets or the Style class at run time and use a
WidgetStyle instead:
# generate_new_style() returns a plain dict.
style = generate_new_style ()
button.style = WidgetStyle (style)
Style files
-----------
A style file is a key-value pair association of style entries for
widgets. It can be loaded and used by the Style.load() method to set
specific themes and styles for the widgets. The style files use the
python syntax and contain key-value pairs of style information for
the specific widgets. The general syntax looks like follows:
widgetclassname = WidgetStyle ({ style_entry : { value } })
An example style file entry for the Button widget class can look
like the following:
button = { 'bgcolor' :{ STATE_NORMAL : (200, 100, 0) },
'fgcolor' : { STATE_NORMAL : (255, 0, 0) },
'shadow' : 5 }
The above example will set the bgcolor[STATE_NORMAL] color style
entry for the button widget class to (200, 100, 0), the
fgcolor[STATE_NORMAL] color style entry to (255, 0, 0) and the
'shadow' value for the border size to 5. Any other value of the
style will remain untouched.
Loading a style while running an application does not have any
effect on widgets
* with own styles set via the BaseWidget.get_style() method,
* already drawn widgets using the default style.
The latter ones need to be refreshed via the set_dirty()/update()
methods explicitly to make use of the new style.
Style files allow user-defined variables, which are prefixed with an
underscore. A specific color thus can be stored in a variable to allow
easier access of it.
_red = (255, 0, 0)
___myown_font = 'foo/bar/font.ttf'
Examples
--------
The OcempGUI module contains a file named 'default.rc' in the themes
directory of the installation, which contains several style values
for the default appearance of the widgets. Additional information
can be found in the manual of OcempGUI, too.
Attributes:
styles - A dictionary with the style definitions of various elements.
engine - The drawing engine, which takes care of drawing elements.
"""
__slots__ = ["styles", "_engine"]
def __init__ (self):
# Initialize the default style.
self.styles = {
"default" : WidgetStyle ({
"bgcolor" : WidgetStyle ({ STATE_NORMAL : (234, 228, 223),
STATE_ENTERED : (239, 236, 231),
STATE_ACTIVE : (205, 200, 194),
STATE_INSENSITIVE : (234, 228, 223) }),
"fgcolor" : WidgetStyle ({ STATE_NORMAL : (0, 0, 0),
STATE_ENTERED : (0, 0, 0),
STATE_ACTIVE : (0, 0, 0),
STATE_INSENSITIVE : (204, 192, 192) }),
"lightcolor" : WidgetStyle ({ STATE_NORMAL : (245, 245, 245),
STATE_ENTERED : (245, 245, 245),
STATE_ACTIVE : (30, 30, 30),
STATE_INSENSITIVE : (240, 240, 240)
}),
"darkcolor" : WidgetStyle ({ STATE_NORMAL : (30, 30, 30),
STATE_ENTERED : (30, 30, 30),
STATE_ACTIVE : (245, 245, 245),
STATE_INSENSITIVE : (204, 192, 192)
}),
"bordercolor" : WidgetStyle ({ STATE_NORMAL : (0, 0, 0),
STATE_ENTERED : (0, 0, 0),
STATE_ACTIVE : (0, 0, 0),
STATE_INSENSITIVE : (204, 192, 192)
}),
"shadowcolor" : ((100, 100, 100), (130, 130, 130)),
"image" : WidgetStyle ({ STATE_NORMAL : None,
STATE_ENTERED : None,
STATE_ACTIVE : None,
STATE_INSENSITIVE : None }),
"font" : WidgetStyle ({ "name" : None,
"size" : 16,
"alias" : True,
"style" : 0 }),
"shadow" : 2 })
}
# Load the default style and theme engine.
self.load (os.path.join (DEFAULTDATADIR, "themes", "default",
default.RCFILE))
self.set_engine (default.DefaultEngine (self))
def get_style (self, cls):
"""S.get_style (...) -> WidgetStyle
Returns the style for a specific widget class.
Returns the style for a specific widget class. If no matching
entry was found, the method searches for the next upper
entry of the class's __mro__. If it reaches the end of the
__mro__ list without finding a matching entry, the
default style will be returned.
"""
classes = [c.__name__.lower () for c in cls.__mro__]
for name in classes:
if name in self.styles:
return self.styles[name]
return self.styles.setdefault (cls.__name__.lower (), WidgetStyle ())
def get_style_entry (self, cls, style, key, subkey=None):
"""S.get_style_entry (...) -> value
Gets a style entry from the style dictionary.
Gets a entry from the style dictionary. If the entry could not
be found, the method searches for the next upper entry of the
__mro__. If it reaches the end of the __mro__ list without
finding a matching entry, it will try to return the entry from
the 'default' style dictionary.
"""
deeper = subkey != None
if key in style:
if deeper:
if subkey in style[key]:
return style[key][subkey]
else:
return style[key]
styles = self.styles
classes = [c.__name__.lower () for c in cls.__mro__]
for name in classes:
if name in styles:
style = styles[name]
# Found a higher level class style, check it.
if key in style:
if deeper:
if subkey in style[key]:
return style[key][subkey]
else:
return style[key]
# None found, refer to the default.
if deeper:
return styles["default"][key][subkey]
return styles["default"][key]
def copy_style (self, cls):
"""S.copy_style (...) -> WidgetStyle
Creates a plain copy of a specific style.
Due to the cascading ability of the Style class, an existing
style will be filled with the entries of the 'default' style
dictionary which do not exist in it.
"""
style = copy.deepcopy (self.get_style (cls))
default = self.styles["default"]
for key in default:
if key not in style:
style[key] = copy.deepcopy (default[key])
else:
sub = default[key]
# No dicts anymore
for subkey in default[key]:
style[key] = copy.deepcopy (sub[subkey])
return style
def load (self, file):
"""S.load (...) -> None
Loads style definitions from a file.
Loads style definitions from a file and adds them to the
'styles' attribute. Already set values in this dictionary will
be overwritten.
"""
glob_dict = {}
loc_dict = {}
execfile (file, glob_dict, loc_dict)
setdefault = self.styles.setdefault
for key in loc_dict:
# Skip the Constants import directive and
# any user-defined variable.
if key.startswith ("_") or (key == "Constants"):
continue
# Search the style or create a new one from scratch.
entry = setdefault (key, WidgetStyle ())
# Look up all entries of our style keys and add them to the
# style.
widget = loc_dict[key]
for key in widget:
if type (widget[key]) == dict:
if key not in entry:
entry[key] = WidgetStyle ()
for subkey in widget[key]:
entry[key][subkey] = widget[key][subkey]
else:
entry[key] = widget[key]
def create_style_dict (self):
"""Style.create_style_dict () -> dict
Creates a new style dictionary.
Creates a new unfilled style dictionary with the most necessary
entries needed by the Style class specifications.
"""
style = WidgetStyle ({
"bgcolor" : WidgetStyle ({ STATE_NORMAL : (0, 0, 0),
STATE_ENTERED : (0, 0, 0),
STATE_ACTIVE : (0, 0, 0),
STATE_INSENSITIVE : (0, 0, 0) }),
"fgcolor" : WidgetStyle ({ STATE_NORMAL : (0, 0, 0),
STATE_ENTERED : (0, 0, 0),
STATE_ACTIVE : (0, 0, 0),
STATE_INSENSITIVE : (0, 0, 0) }),
"lightcolor" : WidgetStyle ({ STATE_NORMAL : (0, 0, 0),
STATE_ENTERED : (0, 0, 0),
STATE_ACTIVE : (0, 0, 0),
STATE_INSENSITIVE : (0, 0, 0) }),
"darkcolor" : WidgetStyle ({ STATE_NORMAL : (0, 0, 0),
STATE_ENTERED : (0, 0, 0),
STATE_ACTIVE : (0, 0, 0),
STATE_INSENSITIVE : (0, 0, 0) }),
"bordercolor" : WidgetStyle ({ STATE_NORMAL : (0, 0, 0),
STATE_ENTERED : (0, 0, 0),
STATE_ACTIVE : (0, 0, 0),
STATE_INSENSITIVE : (0, 0, 0) }),
"shadowcolor": ((0, 0, 0), (0, 0, 0)),
"image" : WidgetStyle ({ STATE_NORMAL : None,
STATE_ENTERED : None,
STATE_ACTIVE : None,
STATE_INSENSITIVE : None }),
"font" : WidgetStyle ({ "name" : None,
"size" : 0,
"alias" : False,
"style" : 0 }),
"shadow" : 0
})
return style
def get_border_size (self, cls=None, style=None, bordertype=BORDER_FLAT):
"""S.get_border_size (...) -> int
Gets the border size for a specific border type and style.
Gets the size of a border in pixels for the specific border type
and style. for BORDER_NONE the value will be 0 by
default. BORDER_FLAT will always return a size of 1.
The sizes of other border types depend on the passed style.
If no style is passed, the method will try to retrieve a style
using the get_style() method.
Raises a ValueError, if the passed bordertype argument is
not a value of the BORDER_TYPES tuple.
"""
if bordertype not in BORDER_TYPES:
raise ValueError ("bordertype must be a value from BORDER_TYPES")
if not style:
style = self.get_style (cls)
if bordertype == BORDER_FLAT:
return 1
elif bordertype in (BORDER_SUNKEN, BORDER_RAISED):
return self.get_style_entry (cls, style, "shadow")
elif bordertype in (BORDER_ETCHED_IN, BORDER_ETCHED_OUT):
return self.get_style_entry (cls, style, "shadow") * 2
return 0
def set_engine (self, engine):
"""S.set_engine (...) -> None
Sets the drawing engine to use for drawing elements.
"""
if engine == None:
raise TypeError ("engine must not be None")
self._engine = engine
engine = property (lambda self: self._engine,
lambda self, var: self.set_engine (var),
doc = "The drawing engine, which draws elements.")
syntax highlighted by Code2HTML, v. 0.9.1