Preferences for default "sizeritem" parameters for new panels and

controls can be configured ("File">"Preferences...").

Implemented comment object for including simple one-line comments and
comment directives as tree nodes. No validation is performed for a
valid XML string so comments must not contain "-->". Comment directive
is a special comment starting with '%' character, followed by a line
of python code. It is executed using 'exec' when the resource file is
opened. This is useful to import plugin modules containing custom
handlers which are specific to the resource file, hovewer this is of
course a security hole if you use foreign XRC files. A warning is
displayed if the preference option 'ask' is selected (by default).

Added support for custom controls and plugin modules. Refer to this
wxPythonWiki for the details:
        http://wiki.wxpython.org/index.cgi/XRCed#custom

Tool panel sections can be collapsed/expanded by clicking on the
label of a tool group.

Some undo/redo and other fixes.

Fixes for wxMSW (notebook highlighting, control sizes, tree Unselect).

Notebook page highlighting fix. Highlight resizes when the window
is resized. ParamUnit spin button detects event handler re-entry
(wxGTK probably has a bug in wxSpinButton with repeated events).

Fix for dealing with empty 'growable' property, using MiniFrame
for properties panel, the panel is restored together with the
main window.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@44949 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2007-03-19 17:55:26 +00:00
parent 459fbbc100
commit 30d91f9ebc
11 changed files with 989 additions and 376 deletions

View File

@@ -7,6 +7,7 @@
from xml.dom import minidom
from globals import *
from params import *
import traceback, types
# Base class for interface parameter classes
class xxxNode:
@@ -189,6 +190,7 @@ class xxxObject:
hasStyle = True # almost everyone
hasName = True # has name attribute?
isSizer = hasChild = False
isElement = True
allParams = None # Some nodes have no parameters
# Style parameters (all optional)
styles = ['fg', 'bg', 'font', 'enabled', 'focused', 'hidden', 'tooltip']
@@ -210,7 +212,7 @@ class xxxObject:
# parent is parent xxx object (or None if none), element is DOM element object
def __init__(self, parent, element, refElem=None):
self.parent = parent
self.element = element
self.node = element
self.refElem = refElem
self.undo = None
# Reference are dereferenced
@@ -230,33 +232,32 @@ class xxxObject:
if self.hasName: self.name = element.getAttribute('name')
# Set parameters (text element children)
self.params = {}
nodes = element.childNodes[:]
for node in nodes:
if node.nodeType == minidom.Node.ELEMENT_NODE:
tag = node.tagName
for n in element.childNodes[:]:
if n.nodeType == minidom.Node.ELEMENT_NODE:
tag = n.tagName
if tag in ['object', 'object_ref']:
continue # do nothing for object children here
elif tag not in self.allParams and tag not in self.styles:
print 'WARNING: unknown parameter for %s: %s' % \
(self.className, tag)
elif tag in self.specials:
self.special(tag, node)
self.special(tag, n)
elif tag == 'content':
if self.className == 'wxCheckListBox':
self.params[tag] = xxxParamContentCheckList(node)
self.params[tag] = xxxParamContentCheckList(n)
else:
self.params[tag] = xxxParamContent(node)
self.params[tag] = xxxParamContent(n)
elif tag == 'font': # has children
self.params[tag] = xxxParamFont(element, node)
self.params[tag] = xxxParamFont(element, n)
elif tag in self.bitmapTags:
# Can have attributes
self.params[tag] = xxxParamBitmap(node)
self.params[tag] = xxxParamBitmap(n)
else: # simple parameter
self.params[tag] = xxxParam(node)
elif node.nodeType == minidom.Node.TEXT_NODE and node.data.isspace():
self.params[tag] = xxxParam(n)
elif n.nodeType == minidom.Node.TEXT_NODE and n.data.isspace():
# Remove empty text nodes
element.removeChild(node)
node.unlink()
element.removeChild(n)
n.unlink()
# Check that all required params are set
for param in self.required:
@@ -281,9 +282,9 @@ class xxxObject:
break
if found:
nextTextElem = self.params[p].node
self.element.insertBefore(elem, nextTextElem)
self.node.insertBefore(elem, nextTextElem)
else:
self.element.appendChild(elem)
self.node.appendChild(elem)
else:
wx.LogWarning('Required parameter %s of %s missing' %
(param, self.className))
@@ -313,7 +314,34 @@ class xxxObject:
if self.hasChild: obj = self.child
else: obj = self
obj.name = name
obj.element.setAttribute('name', name)
obj.node.setAttribute('name', name)
# Set normal (text) params
def set(self, param, value):
try:
self.params[param].update(value)
except KeyError:
elem = g.tree.dom.createElement(param)
p = xxxParam(elem)
p.update(value)
self.params[param] = p
self.node.appendChild(elem)
# Special processing for growablecols-like parameters
# represented by several nodes
def special(self, tag, node):
if not self.params.has_key(tag):
# Create new multi-group
self.params[tag] = xxxParamMulti(node)
self.params[tag].append(xxxParamInt(node))
def setSpecial(self, param, value):
# Straightforward implementation: remove, add again
self.params[param].remove()
del self.params[param]
for i in value:
node = g.tree.dom.createElement(param)
text = g.tree.dom.createTextNode(str(i))
node.appendChild(text)
self.node.appendChild(node)
self.special(param, node)
# Imitation of FindResource/DoFindResource from xmlres.cpp
def DoFindResource(parent, name, classname, recursive):
@@ -360,7 +388,7 @@ class xxxParamFont(xxxObject, xxxNode):
self.data = v
def update(self, value):
# `value' is a list of strings corresponding to all parameters
elem = self.element
elem = self.node
# Remove old elements first
childNodes = elem.childNodes[:]
for node in childNodes: elem.removeChild(node)
@@ -638,6 +666,16 @@ class xxxDateCtrl(xxxObject):
winStyles = ['wxDP_DEFAULT', 'wxDP_SPIN', 'wxDP_DROPDOWN',
'wxDP_ALLOWNONE', 'wxDP_SHOWCENTURY']
class xxxGrid(xxxObject):
allParams = ['pos', 'size', 'style']
class xxxFilePickerCtrl(xxxObject):
allParams = ['value', 'message', 'wildcard', 'pos', 'size', 'style']
winStyles = ['wxFLP_OPEN', 'wxFLP_SAVE', 'wxFLP_OVERWRITE_PROMPT',
'wxFLP_FILE_MUST_EXIST', 'wxFLP_CHANGE_DIR',
'wxFLP_DEFAULT_STYLE']
################################################################################
# Buttons
@@ -771,45 +809,11 @@ class xxxFlexGridSizer(xxxGridSizer):
specials = ['growablecols', 'growablerows']
allParams = ['cols', 'rows', 'vgap', 'hgap'] + specials
paramDict = {'growablecols': ParamIntList, 'growablerows': ParamIntList}
# Special processing for growable* parameters
# (they are represented by several nodes)
def special(self, tag, node):
if not self.params.has_key(tag):
# Create new multi-group
self.params[tag] = xxxParamMulti(node)
self.params[tag].append(xxxParamInt(node))
def setSpecial(self, param, value):
# Straightforward implementation: remove, add again
self.params[param].remove()
del self.params[param]
for i in value:
node = g.tree.dom.createElement(param)
text = g.tree.dom.createTextNode(str(i))
node.appendChild(text)
self.element.appendChild(node)
self.special(param, node)
class xxxGridBagSizer(xxxSizer):
specials = ['growablecols', 'growablerows']
allParams = ['vgap', 'hgap'] + specials
paramDict = {'growablecols':ParamIntList, 'growablerows':ParamIntList}
# Special processing for growable* parameters
# (they are represented by several nodes)
def special(self, tag, node):
if not self.params.has_key(tag):
# Create new multi-group
self.params[tag] = xxxParamMulti(node)
self.params[tag].append(xxxParamInt(node))
def setSpecial(self, param, value):
# Straightforward implementation: remove, add again
self.params[param].remove()
del self.params[param]
for i in value:
node = g.tree.dom.createElement(param)
text = g.tree.dom.createTextNode(str(i))
node.appendChild(text)
self.element.appendChild(node)
self.special(param, node)
paramDict = {'growablecols': ParamIntList, 'growablerows': ParamIntList}
# Container with only one child.
# Not shown in tree.
@@ -843,7 +847,8 @@ class xxxChildContainer(xxxObject):
class xxxSizerItem(xxxChildContainer):
allParams = ['option', 'flag', 'border', 'minsize', 'ratio']
paramDict = {'option': ParamInt, 'minsize': ParamPosSize, 'ratio': ParamPosSize}
#default = {'cellspan': '1,1'}
defaults_panel = {}
defaults_control = {}
def __init__(self, parent, element, refElem=None):
# For GridBag sizer items, extra parameters added
if isinstance(parent, xxxGridBagSizer):
@@ -923,7 +928,143 @@ class xxxUnknown(xxxObject):
winStyles = ['wxNO_FULL_REPAINT_ON_RESIZE']
################################################################################
# Comment
_handlers = [] # custom handler classes/funcs
def getHandlers():
return _handlers
def setHandlers(handlers):
global _handlers
_handlers = handlers
_CFuncPtr = None # ctypes function type
def register(hndlr):
"""Register hndlr function or XmlResourceHandler class."""
if _CFuncPtr and isinstance(hndlr, _CFuncPtr):
_handlers.append(hndlr)
return
if not isinstance(hndlr, type):
wx.LogError('handler is not a type: %s' % hndlr)
elif not issubclass(hndlr, wx.xrc.XmlResourceHandler):
wx.LogError('handler is not a XmlResourceHandler: %s' % hndlr)
else:
_handlers.append(hndlr)
def load_dl(path, localname=''):
"""Load shared/dynamic library into xxx namespace.
If path is not absolute or relative, try to find in the module's directory.
"""
if not localname:
localname = os.path.basename(os.path.splitext(path)[0])
try:
import ctypes
global _CFuncPtr
_CFuncPtr = ctypes._CFuncPtr # use as a flag of loaded ctypes
#if not os.path.dirname(path) and os.path.isfile(path):
except ImportError:
wx.LogError('ctypes module not found')
globals()[localname] = None
return
try:
dl = ctypes.CDLL(path)
globals()[localname] = dl
# Register AddXmlHandlers() if exists
try:
register(dl.AddXmlHandlers)
except:
pass
except:
wx.LogError('error loading dynamic library: %s' % path)
print traceback.print_exc()
# Called when creating test window
def addHandlers():
for h in _handlers:
if _CFuncPtr and isinstance(h, _CFuncPtr):
try:
apply(h, ())
except:
wx.LogError('error calling DL func: "%s"' % h)
print traceback.print_exc()
else:
try:
xrc.XmlResource.Get().AddHandler(apply(h, ()))
except:
wx.LogError('error adding XmlHandler: "%s"' % h)
print traceback.print_exc()
def custom(klassName, klass='unknown'):
"""Define custom control based on xrcClass.
klass: new object name
xrcClass: name of an existing XRC object class or
a class object defining class parameters.
"""
if type(klass) is str:
# Copy correct xxx class under new name
kl = xxxDict[klass]
xxxClass = types.ClassType('xxx' + klassName, kl.__bases__, kl.__dict__)
else:
xxxClass = klass
# Register param IDs
for param in klass.allParams + klass.paramDict.keys():
if not paramIDs.has_key(param):
paramIDs[param] = wx.NewId()
# Insert in dictionaty
xxxDict[klassName] = xxxClass
# Add to menu
g.pullDownMenu.addCustom(klassName)
class xxxParamComment(xxxParam):
locals = {} # namespace for comment directives
allow = None # undefined initial state for current file
def __init__(self, node):
xxxNode.__init__(self, node)
self.textNode = node
# Parse "pragma" comments if enabled
if node.data and node.data[0] == '%' and g.conf.allowExec != 'no' and \
xxxParamComment.allow is not False:
# Show warning
if g.conf.allowExec == 'ask' and xxxParamComment.allow is None:
flags = wx.ICON_EXCLAMATION | wx.YES_NO | wx.CENTRE
dlg = wx.MessageDialog(g.frame, '''
This file contains executable %comment directives. Allow to execute?''',
'Warning', flags)
say = dlg.ShowModal()
dlg.Destroy()
if say == wx.ID_YES:
xxxParamComment.allow = True
else:
xxxParamComment.allow = False
try:
code = node.data[1:]
exec code in globals(), self.locals
except:
wx.LogError('exec error: "%s"' % code)
print traceback.print_exc()
class xxxComment(xxxObject):
hasStyle = hasName = False
allParams = required = ['comment']
def __init__(self, parent, node):
self.parent = parent
self.node = node
self.isElement = False
self.undo = None
self.className = 'comment'
self.ref = self.subclass = None
self.params = {'comment': xxxParamComment(node)}
def treeName(self):
# Replace newlines by \n to avoid tree item resizing
return self.params['comment'].value().replace('\n', r'\n')
################################################################################
# Mapping of XRC names to xxx classes
xxxDict = {
'wxPanel': xxxPanel,
'wxDialog': xxxDialog,
@@ -973,6 +1114,8 @@ xxxDict = {
'wxGenericDirCtrl': xxxGenericDirCtrl,
'wxSpinCtrl': xxxSpinCtrl,
'wxScrolledWindow': xxxScrolledWindow,
'wxGrid': xxxGrid,
'wxFilePickerCtrl': xxxFilePickerCtrl,
'wxDatePickerCtrl': xxxDateCtrl,
'wxBoxSizer': xxxBoxSizer,
@@ -990,13 +1133,15 @@ xxxDict = {
'separator': xxxSeparator,
'unknown': xxxUnknown,
'comment': xxxComment,
}
# Create IDs for all parameters of all classes
paramIDs = {'fg': wx.NewId(), 'bg': wx.NewId(), 'exstyle': wx.NewId(), 'font': wx.NewId(),
'enabled': wx.NewId(), 'focused': wx.NewId(), 'hidden': wx.NewId(),
'tooltip': wx.NewId(), 'encoding': wx.NewId(),
'cellpos': wx.NewId(), 'cellspan': wx.NewId()
'cellpos': wx.NewId(), 'cellspan': wx.NewId(),
'text': wx.NewId()
}
for cl in xxxDict.values():
if cl.allParams:
@@ -1009,25 +1154,29 @@ for cl in xxxDict.values():
# Test for object elements
def IsObject(node):
return node.nodeType == minidom.Node.ELEMENT_NODE and node.tagName in ['object', 'object_ref']
return node.nodeType == minidom.Node.ELEMENT_NODE and \
node.tagName in ['object', 'object_ref'] or \
node.nodeType == minidom.Node.COMMENT_NODE
# Make XXX object from some DOM object, selecting correct class
def MakeXXXFromDOM(parent, element):
if element.tagName == 'object_ref':
ref = element.getAttribute('ref')
def MakeXXXFromDOM(parent, node):
if node.nodeType == minidom.Node.COMMENT_NODE:
return xxxComment(parent, node)
if node.tagName == 'object_ref':
ref = node.getAttribute('ref')
refElem = FindResource(ref)
if refElem: cls = refElem.getAttribute('class')
else: return xxxUnknown(parent, element)
else: return xxxUnknown(parent, node)
else:
refElem = None
cls = element.getAttribute('class')
cls = node.getAttribute('class')
try:
klass = xxxDict[cls]
except KeyError:
# If we encounter a weird class, use unknown template
print 'WARNING: unsupported class:', element.getAttribute('class')
print 'WARNING: unsupported class:', node.getAttribute('class')
klass = xxxUnknown
return klass(parent, element, refElem)
return klass(parent, node, refElem)
# Make empty DOM element
def MakeEmptyDOM(className):
@@ -1051,7 +1200,8 @@ def MakeEmptyDOM(className):
def MakeEmptyXXX(parent, className):
# Make corresponding DOM object first
elem = MakeEmptyDOM(className)
# If parent is a sizer, we should create sizeritem object, except for spacers
# Special handling, e.g. if parent is a sizer, we should create
# sizeritem object, except for spacers, etc.
if parent:
if parent.isSizer and className != 'spacer':
sizerItemElem = MakeEmptyDOM(parent.itemTag)
@@ -1070,7 +1220,16 @@ def MakeEmptyXXX(parent, className):
pageElem.appendChild(elem)
elem = pageElem
# Now just make object
return MakeXXXFromDOM(parent, elem)
xxx = MakeXXXFromDOM(parent, elem)
# Special defaults for new panels and controls
if isinstance(xxx, xxxSizerItem):
if isinstance(xxx.child, xxxContainer) and not xxx.child.isSizer:
for param,v in xxxSizerItem.defaults_panel.items():
xxx.set(param, v)
elif isinstance(xxx.child, xxxObject):
for param,v in xxxSizerItem.defaults_control.items():
xxx.set(param, v)
return xxx
# Make empty DOM element for reference
def MakeEmptyRefDOM(ref):
@@ -1107,3 +1266,15 @@ def MakeEmptyRefXXX(parent, ref):
#xxx.allParams.remove('label')
return xxx
# Make empty comment node
def MakeEmptyCommentDOM():
node = g.tree.dom.createComment('')
return node
# Make empty xxxComment
def MakeEmptyCommentXXX(parent):
node = MakeEmptyCommentDOM()
# Now just make object
xxx = MakeXXXFromDOM(parent, node)
return xxx