<> This page contains information on my ongoing Google Summer of Code project. You can check the [[http://code.google.com/soc/2007/wxpython/appinfo.html?csaid=1CCBF57D92653BE6|Application]] page for the details. [[XRCed Component Plugins]] = Up-to-date information = '''Latest ne{{{}}}ws:''' '''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 [[http://svn.wxwidgets.org/viewvc/wx/wxPython/branches/SOC2007_XRCED/wx/tools/XRCed/ChangeLog?view=markup|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''', '''Pull{{{}}}Down{{{}}}Menu''', '''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 Tree{{{}}}Ctrl 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. [[http://www.wxwidgets.org/manuals/2.8.3/wx_docviewoverview.html#docviewoverview|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). {{attachment:components.png}} '''_Component{{{}}}Manager''' class is represented by a singleton object '''Manager''' which is used to register components. Example of a component definition: {{{ #!python from component import * import images c = Container('wxPanel', ['window','top_level','control'], ['pos', 'size'], image=images.getTreePanelImage()) c.addStyles('wxNO_3D', 'wxTAB_TRAVERSAL') c.addExStyles('wxWS_EX_VALIDATE_RECURSIVELY') Manager.register(c) Manager.setMenu(c, 'root', 'Panel', 'Panel window', 30) Manager.setMenu(c, 'container', 'Panel', 'Panel window', 10) 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 Grid{{{}}}Bag{{{}}}Sizer. 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''. There are special cases, of course, such as repeated attributes or attributes using XML element node attributes ( 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 '''Param''Something''''' for historical reasons). There is a dictionary of standard param classes to be used by default with common attributes, such as '''Param{{{}}}Pos{{{}}}Size''' 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 tag 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. {{{ control pos size value 1 wxLED_ALIGN_LEFT wxLED_ALIGN_RIGHT wxLED_ALIGN_CENTER wxLED_ALIGN_FADED xh_gizmos LEDNumberCtrlXmlHandler gizmo LED-style control Gizmos }}} Note the and tags. 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 == [[http://twistedmatrix.com/projects/core/documentation/howto/plugin.html|The Twisted Plugin System]] [[http://www.codeproject.com/cs/design/razorpt1.asp|The Razor Framework]] by Mark (Code6) Belles [[http://en.wikipedia.org/wiki/Plugin|WikiPedia Plugin page]] [[http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html|Notes on the Eclipse Plug-in Architecture]] and [[http://www.acmqueue.com/modules.php?name=Content&pa=showpage&pid=286&page=1|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: {{{ #!XRCED:begin-block:xrcFRAME.OnMenu_item1 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 '''Pre{{{}}}Create()''' 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.My{{{}}}Control'''), pywxrc generates subclass code with user-editable '''_Post{{{}}}Init()'''. 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 '''Tool{{{}}}Bar''' at the moment) need the parent object argument to be passed to the constructor when creating a top-level object. == Example == [[attachment: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 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 elements in XML tree. '''xxxNode''' is the base class of property elements (such as and