This page contains information on my ongoing Google Summer of Code project. You can check the Application page for the details.
Up-to-date information
Latest news:
SOC2007_XRCED branch is no longer used for development, instead 3rdParty directory is used:
https://svn.wxwidgets.org/svn/wx/wxPython/3rdParty/XRCed
To run XRCed locally (using main xrced.py module), the directory where XRCed directory is located must be in PYTHONPATH.
On-line documentation is in progress. Some epydoc-generated documentation for using component classes is here: http://xrced.sourceforge.net/api_doc/
I am currently working on the documentation and examples on how to use the new XRCed, as well as testing it. I will post an announcement on wx-dev when ready. Stay tuned.
This page is not updated every time I implement a new feature. Here are some links for those who like to follow the development more closely.
XRCed is getting ready to be merged into the wxPython trunk.
Volunteers for beta-testing can download the SOC brunch:
svn co https://svn.wxwidgets.org/svn/wx/wxPython/branches/SOC2007_XRCED/wx/tools SOC2007_XRCED
(tools directory should be checked out because it contains new pywxrc tool.)
The latest ChangeLog can be browsed in SVN repository using viewvc.
Refactoring
Before starting to implement nice features such as plugins, it is necessary to unroll some of the duct tape that holds parts of XRCed together and replace it with kevlar joints. In particular, this requires replacing hard-coded data by dynamically generated data structures in the classes working with interface elements, such as XML_Tree, PullDownMenu, Tools, and centralizing all definitions of interface elements in one module, which need to be edited for adding new XRC classes. Ideally, each interface element must be defined by some data structure and registered with XRCed using interface methods. The difficulty here lies is the appropriate definition of the relations between interface elements needed to keep the XRC resource file sane, by not permitting to create impossibe inheritances (such as a wxFrame inside a wxToolbar for example) which will be rejected by XRC subsystem. Currently the checking is performed on a case-by-case basis using class names and some xxx-object properties such as isChildContainer.
Another direction of the refactoring is to use Document-View framework or(and?) ModelViewController design pattern. If would simplify the bloated Frame and some other classes. At the moment I'm not completely sure on the best method to use and reading some papers on this topic, learning many new things in the process (for example that MVC can be bad for some projects :-]).
Preview ("test") window functions must be sawed off XML_Tree into a separate class. Parallel tree data structure should be used to hold the references to TreeCtrl items and xxx-objects to speed up some data manipulations (instead of using wxTreeCtrl. A tree can be represented in Python in lisp-like fashion by enclosed lists.
A new panel for event handler selection will be added and API developed for passing this data to code generators and to/from IDEs. Selection can be saved using XML comments inside interface element subtree or in a separate file.
XRCed Data Model describes the future data model.
Resources
ModelViewController page.
Document/view overview (from wxWidgets manual)
Plugin framework
Plugin frameworks can be built using two models, which we can call a pure-plugin model and a feature-plugin model. In pure-plugin model the system is built entirely of plugins, except a small kernel part containing bootstrap code and plugin management code. In feature-plugin model plugins are seen as optional external parts used to extend basic functions. An example of a pure-plugin approach is Eclipse framework.
For the purpose of a resource editor like XRCed two different plugin types can be considered. First type, which we will call component plugin, is a definition of an interface element. It defines object's class name, lists of attributes needed to generate the viewing interface for editing the element's data, optional XML handler class in a form of a python module or a DLL/shared library, some resources like menu label and icons, and optionally code needed for specific operations for updating the data or interface parts, responding to the user's input, or generating application code. Second type is a plugin extending functionality of the editor in some way, for example by providing an interface to a code generation tool or an external icon editor. We will call them extension plugins. This kind of plugin is expected to register some menu commands or interface elements for user interaction, and can access resource data and application objects in arbitrary fashion. Component plugins are implemented in the first stage of the refactoring project, and extension plugins will be dealt with later on.
Features to consider:
- dependencies between plugins and versions of wxPython
- auto-update (using http), user-friendly plugin manager
- installing from a zip archive
- enabling/disabling some plugins for a particular project using an optional config file
- using efficient data structure to cache installed plugins in the user's home directory
- plugin templates for creating new plugins
Component Plugins
Component plugin can be defined in two ways. First one is using python code in a plugin module which creates a component object. Second one is using an XML manifest file.
Components are created and registered when XRCed scans standard or user-defined plugin directories on startup for *.py files. It is possible to protect a module from being imported automatically by starting its name with an underscore.
Basic component classes are defined in component.py module (see diagram).
_ComponentManager class is represented by a singleton object Manager which is used to register components.
Example of a component definition:
1 from component import *
2 import images
3 c = Container('wxPanel', ['window','top_level','control'],
4 ['pos', 'size'],
5 image=images.getTreePanelImage())
6 c.addStyles('wxNO_3D', 'wxTAB_TRAVERSAL')
7 c.addExStyles('wxWS_EX_VALIDATE_RECURSIVELY')
8 Manager.register(c)
9 Manager.setMenu(c, 'root', 'Panel', 'Panel window', 30)
10 Manager.setMenu(c, 'container', 'Panel', 'Panel window', 10)
11 Manager.setTool(c, 'Windows', bitmaps.getwxPanelBitmap(), (0,2))
Here setMenu() method is called twice to add Panel to two different menus: the root node menu and 'container' submenu. Container is the base class used for container (interface elements which can have children). Component class constructors take three required positional arguments: name, groups, and attributes. name is the name of the component class (the value of "class" attribute of corresponding XML element node). groups is the list of groups the component belongs. First item of the list is the principal group. Third argument is the list of attribute names, in the order in which they should appear on the attribute panel. image is an optional icon for tree items. setTool method is used to add a bitmap button to a tool pane 'Windows'. Third positional argument is the bitmap, by default a bitmap class_name.png found in 'bitmaps' directory will be looked up, and if not found a default bitmap will be used. (0,2) tuple is the pos argument defining the position of the bitmap in the pane's GridBagSizer. In this case it is first row, third column. If not specified, the sizer is searched for an available place. If two components try to use the same position, the one which call setTool first will be positioned correctly, and the second will be positioned somewhere else.
Attributes of an interface element are normally defined in XRC file as <attribute>attribute_value</attribute>. There are special cases, of course, such as repeated attributes or attributes using XML element node attributes (<bitmap stock_id="some_id"/> for example). To deal with this, attribute.py module is used. It contains a number of predefined attribute classes which must implement add and get static methods. A component definition can use specials named argument to the constructor or setSpecial method to set the attribute class to be used for special attributes.
Attributes are edited using the attribute panel containing a notebook with name/value controls. Depending on the type of the attribute, a different control can be used. params.py module contains a number of predefined classes (they are called ParamSomething for historical reasons). There is a dictionary of standard param classes to be used by default with common attributes, such as ParamPosSize for the pos and size attributes. Non-default classes can be assigned using setParamClass method.
Registration with the component manager is performed simply by passing a component object to the register method of the Manager object.
Parents and children
The list of groups is used for determining parent/child compatibility, by searching the parentChildGroups dictionary defined in component.py. Elements of parentChildGroups are parent_group: child_groups key-value pairs, where child_groups is the list of groups which can be children of parent group parent_group. For example a line key-value pair
'frame': ['toolbar','menubar']
says that a component which belongs to the "frame" group can have children which belong to "toolbar" or "menubar" groups. However, there are situations where it is necessary to "ban" some components from being children. This is also possible, by specifying the principal child group preceded by '!', for example:
'window': ['control', 'window', '!frame']
says that a frame cannot be a child of a window. In this case "frame" must be the principal group of the banned component (first item of the groups list). It will not ban other components which belong to the group "frame" where it is not the principal group. Banning has higher priority than allowing. It is also the default case if the component group is not present in the parentChildGroups dictionary.
Manifest files and XML handlers
The second method to define components is using an XML manifest file. It must contain all required information for the component registration in the form of XML element nodes. The format used for XRCed manifest files is called CRX and it is actually very similar to XRC format, with the difference that instead of <object> tag <component> tag is used for named top-level objects, class attribute is the name of one of the base component classes, and name is the name of the defined interface element class.
The reason of using XRC-like structure is very simple: manifest files must not be created by hand, but can be easily edited using XRCed "meta" mode, which is activated by '--meta' command-line option. The top level pulldown menu then contains additional commands to create component objects.
This is an example of a manifest file defining LEDNumberCtrl component from wx.gizmos module.
<?xml version="1.0" encoding="ISO-8859-1"?> <resource> <component class="Component" name="LEDNumberCtrl" provider="Roman Rolinsky <genericsoma@gmail.com>"> <groups> <item>control</item> </groups> <attributes> <item>pos</item> <item>size</item> <item>value</item> </attributes> <has-name>1</has-name> <styles> <item>wxLED_ALIGN_LEFT</item> <item>wxLED_ALIGN_RIGHT</item> <item>wxLED_ALIGN_CENTER</item> <item>wxLED_ALIGN_FADED</item> </styles> <module>xh_gizmos</module> <handler>LEDNumberCtrlXmlHandler</handler> <menu>gizmo</menu> <label>LED number</label> <help>LED-style control</help> <panel>Gizmos</panel> </component> </resource>
Note the <module> and <handler> tags. <module> defined the module which contains the XML handler class needed to parse this component through the XRC subsystem. The module can be situated in the same directory as the manifest file, or can be somewhere in PYTHONPATH. In this example gizmos module is imported and LEDNumberCtrlHandler class is assigned as the handler for this component. This class is then passed to AddHandler method of the XmlResource object each time the test window resources are loaded.
Resources about plugin framework design
The Razor Framework by Mark (Code6) Belles
Notes on the Eclipse Plug-in Architecture and On Plug-ins and Extensible Architectures
Code generation
When Code panel is used, XRCed stores some additional information using special XRCED tag. It is used by the new pywxrc tool to generate event handler stubs and to switch creating member variables for window children based on "assign variable" flag. In order to create a top-level class, child member variable or an event handler, an object must be named. Unnamed objects are simply ignored by pywxrc. Special comment blocks are used in the generated source file in order to delimit the parts the user can edit.
Here's an example of an event handler generated by pywxrc for a menu item:
def OnMenu_item1(self, evt): # Replace with event handler code print "OnMenu_item1()" #!XRCED:end-block:xrcFRAME.OnMenu_item1
Everything between the comments can be modified to implement the application-specific behaviour. For some classes there is also an editable block surrounding PreCreate() which can be useful to include some additional initialization code.
Using subclasses
For subclassed controls using the same module name as the generated module (e.g. foo_xrc.MyControl), pywxrc generates subclass code with user-editable _PostInit(). You have to write subclass code yourself if using an external module.
Each subclass is defined only once, event if you have multiple subclassed objects. Their base class must be the same, or the first one will be used. Event handlers and member variables are not created for the child objects to avoid ambiguity for the subclassed objects with the same class name and different children. You can use the editable block in order to define event handlers manually.
Using non-default components
If your interface includes a component requiring additional XML handler, you have to import the corresponding module and pass the handler object to the XRC subsystem, for example you can use something like this in your main module:
from wx.tools.XRCed.plugins.xh_gizmos import LEDNumberCtrlXmlHandler # Somewhere after wx.App is created, but before the interface is created get_resources().AddHandler(LEDNumberCtrlXmlHandler())
ctor arguments
Some classes (only ToolBar at the moment) need the parent object argument to be passed to the constructor when creating a top-level object.
Example
foo_xrc.zip contains an example of using different top-level objects, a subclassed window and a gizmo component.
Glossary
Interface element is the general concept defining GUI element corresponding to a particular wxPython class implementing a control, menu or window. It is usually represented by the <object> tag in XRC file, containing a number of properties defining element's appearance.
xxx-class is a class used to interface DOM objects containing actual data and in addition contains interface element complete property list and some flags (isContainer, isSizer, etc.). Sometimes a constructor and member functions are provided to implement special functionality. xxxObject is the common base class of all xxx-classes corresponding to <object> elements in XML tree. xxxNode is the base class of property elements (such as <size> and <style>). xxx-classes for bundled interface elements are in the xxx.py source file.
attribute is a named data structure controlling some aspect of the interface element and enclosed in a corresponding XML tag. Properties with the same name are normally of the same data type.
component plugin is a definition of an interface element data structure and methods needed for introducing the data in the resource editor and other tasks.
extension plugin is a plugin adding some functionality to the application.
manifest file is an XML file containing plugin definition data. In XRCed CRX manifest files can be used to define component plugins without the need to program a python component plugin module.