r6 - 27 Mar 2007 - 14:30:16 - PhilippeBossutYou are here: OSAF >  Projects Web  >  DevelopmentHome > ApplicationProject > CpiaFramework > CpiaZeroPointThreeStatus

CPIA and the 0.3 Chandler release

CPIA, Chandler Presentation and Interaction Architecture, is a big part of the 0.3 release. CPIA is used to construct almost all of the application interface that you see. I'd like to begin with a quick overview of what CPIA is, then describe the parts of CPIA that are implemented in the 0.3 release and give you some background that's necessary for understanding the CPIA code in the 0.3 release. Keep in mind that CPIA is a work in progress, which will unfold as we proceed. I'll try to describe which changes you can expect in the future.

What is CPIA?

CPIA evolved from brainstorming and design meetings with Mitch Kapor, Andy Hertzfeld and myself (John Anderson) during the summer of 2003. Some of the main goals that motivated the design were 1) An application construction environment which lets advanced users, without programming experience, customize or build completely new applications. 2) The desire to provide high-level user interface widgets tailored especially to the needs of Chandler, e.g. powerful tables and calendar views. 3) And finally, to represent user interface in a way that is abstracted from the particular platform it runs on, e.g. PCs, Web browsers, PDAs. (Throughout this document I'll never use platform to refer to PC platforms, e.g. Linux and Macintosh and Windows).

CPIA has several new concepts that make it a little difficult to understand. Perhaps the best way to understand CPIA is to compare it with existing UI toolkits. Most UI toolkits consist of objects, often written in an object-oriented programming language, that handle the display of a variety of widgets, for example tree controls, text boxes, and list views. To use these objects, you have to be a programmer and write code that adds behavior, sends data to the widget, receives data from the widget, and saves the data in some sort of persistent storage, e.g. a file or database. Even though the UI toolkit displays the widget for you, there's still quite a bit of programming necessary to make it work properly.

CPIA, in contrast, provides more complete user interface objects, which we call Blocks, that already contain the necessary code to load data, display data, and save data. In addition, they automatically remember their state, for example a scroll position, selection, or window size. Rather than writing code to provide behavior, you typically set properties of a CPIA Block. For example, to specify the data to display in a list, you set the query attribute of the Block which is used to obtain the data to display from the Chandler repository, instead of writing code to load data from a file or database. So the goal of CPIA is to come up with more complete user interface objects that are designed to fit together more like Lego Blocks, and require little or no programming. This sets CPIA apart from traditional UI toolkits and breaks important new ground.

Blocks in CPIA communicate with one another using Block events -- high-level events that describe changes that happen in one Block that affect other Blocks. Block events are typically more high-level than the events in many UI toolkits. For example a Block might internally handle mouse moved and key events, automatically changing the data displayed in the Block, but only emit a Block event to notify other Blocks that the selected item has changed. In the 0.3 release, with the exception of events sent by menus, the SelectionChanged Block event provides almost all the behavior you see on the screen. In future releases we will explore a more diverse set of Block events, but we still haven't had enough experience with Block events to know the limits they impose to possible behaviors.

Finally, like all other data in Chandler, CPIA Blocks are stored in the Chandler repository. This lets us take advantage of all of the features of the repository, which include:

  • The ability to access data as Python objects, which makes programming easier
  • Transparent persistence which automatically loads data as the data is accessed and automatically saves the modified object
  • A rich set of data types, that map conveniently onto Python data types
  • A uniform mechanism for describing the shema of all data as data
  • A transaction system that allows for undo/redo
  • A powerful query mechanism

All these features of the repository are used by CPIA today, with the exception of undo/redo and query, which are still in development.

For another view of CPIA, you might want to look at Ducky Sherwood's document:

http://wiki.osafoundation.org/twiki/bin/view/Chandler/Journal.ChandlerPresentationAndInteractionArchitectureSep2003

which is a little older, but provides information about CPIA that isn't yet in the 0.3 release but will potentially be in later releases.

0.3 CPIA Blocks

I'd like to describe some of the Blocks that made it into the 0.3 release. For those so inclined, you can follow along in the code, which lives in osaf/chandler/Chandler/parcels/OSAF/framework/Blocks directory. Each Block I describe will be followed with its filename in parenthesis.

Block (Block.py)

The abstract base class of all Blocks. Blocks are organized into trees, collectively called Views, with each Block having a parent and zero or more children. For example, a BoxContainer Block lays out Blocks in a rectangular area of the screen and contains children blocks which are laid out in the box. Each Block may also contain a query, style, a boolean for whether or not the Block is open, a boolean that indicates whether or not Block events that are broadcast inside the Block propagate outside the Block (more on that later), and Block events that a View subscribes to.

Blocks, like all items in the repository, appear as objects in Python, so besides data, they also contain methods. Blocks currently contain methods to post a Block event and render themselves into a platform-specific form. For 0.3, the only platform-specific form is a wxWidgets object (wxWindows was renamed wxWidgets because Microsoft objected that it would be confused with Microsoft Windows) . Someday, we hope to extend this for browsers and PDAs. Finally, there are two wxWidgets specific methods, getwxID, which returns a wxWidgets ID corresponding to this Block, and wxIDToObject, which returns the wxWidgets object for a given wxWidgets ID. This is particularly useful, since wxWidgets often uses IDs, which are small integers to associate with events and user interface elements.

In some ways, perhaps you'd wish that the Block and their platform-specific counterpart could be the same object. This turns out to be difficult since Blocks can only contain data that can be persisted and wxWidgets Blocks contain pointers, which when persisted (saved and reloaded from disk) don't make sense. Maybe with the magic of Python, we could have synthesized a class at runtime, that had two superclasses, one for the Block and one for the counterpart (which we have actually done elsewhere), and wire the Block to it at load time. However, this leads to code that mixes platform-specific code with platform independent code. This makes it more difficult to add a new platform in the future, so we decided it would be better to keep the two objects distinct.

Often in the CPIA code, Blocks and their rendered counterpart need to refer to each other. For example, a platform-specific counterpart needs to access the data necessary to display the Block or save the state of the selection as it changes in the Block. Likewise the Block needs to tell the platform counterpart to change the selected item. This is a little more difficult than you might think. Storing a Python reference to a Python object, like a wxWidgets object, that itself is not an Item in the repository is currently a problem -- different threads need different references. To get around this repository limitation, for each wxWidgets counterpart we store the UUID of the counterpart Item in a attribute called counterpartUUID. You'll notice throughout the code when in a wxWidgets object, we often lookup its counterpart as follows:

counterpart = Globals.repository.find (self.counterpartUUID)

since the find method on the repository can look up any item quickly by UUID. Similarly, an Item can't persist a wxWidgets object because it contains pointers. So, for every Block that has a platform counterpart we store its counterpart in a dictionary named "association" indexed by the UUID of the Block. You'll notice thoughout the code when in a Block, we often lookup its counterpart as follows:

counterpart = Globals.association [self.counterpartUUID]

Eventually, most rendered counterparts of Blocks will have a method called SynchronizeFramework. It makes sure the rendered counterpart matches the data stored in the Block. For example, you should be able to modify the data in the Block and then call SynchronizeFramework on its counterpart and the changes will be reflected on the screen.

ContainerChild (Block.py)

ContainerChild is the base class of a Block that can be contained inside a container Block. In the original CPIA design we envisioned the following kinds of containers Blocks:

  • BoxContainers: A rectangular box that contains child Blocks, which might also be containers, and lays them out horizontally or vertically. Box containers are implemented in Chandler with sizers in wxWindow and have similar layout properties.
  • TabbedContainers: A tabbed dialog where each pane is a child Block.
  • EmbeddedContainers: A container that can refer to another View (or tree of Blocks).
  • XYContainers: A rectangular box that contains child Blocks which might also be containers, and lays them out by x, y position.
  • BarContainer: A toolbar that lays out its children Blocks in one dimension.
  • SplitWindow: A splitter window that has two children, one for each split

0.3 Chandler implements all types of containers except XYContainers. Also ToolbarSpec should be a BarContainer, but currently isn't.

RectangularChild (Block.py)

RectangularChild is a rectangular child of a container. It currently has methods, like Calculate_wxFlag and Calculate_wxBorder, which calculate information necessary to conveniently render them in wxWidgets. Most of the containers derive from RectangularChild, which allows them to nest into containers.

BoxContainer (ContainerBlock.py)

BoxContainers, which were described above, implement only renderOneBlock, addToContainer and removeFromContainer. A Block overrides renderOneBlock to construct the platform-specific counterpart object. addToContainer and removeFromContainer are overridden to add or remove the counterpart from the counterpart's parent (not the Block's parent).

EmbeddedContainer (ContainerBlock.py)

EmbeddedContainers, as described above, reference something else, typically another View, that is displayed in a rectangular area. In 0.3 we use this to display the contents of URLs in Chandler. Currently we allow any Block to be refered to by an EmbeddedContainer, however, in the future we may restrict the kinds of Blocks that can be referenced by an EmbeddedContainer to be View Blocks. In the future we'd like EmbeddedContainers to be able to refer to a wide variety of content, including web pages, documents, etc.

SplitWindow, TabbedContainer, Toolbar (ContainerBlock.py)

SplitWindow and TabbedContainer are described under ContainerChild. Toolbar will be re-factored and become BarContainer.

Button, Choice, ComboBox, EditText, HTML, List, RadioBox, ScrolledWindow, StaticText ToolbarItem, Tree, ItemDetail, (ControlBlocks.py)

All the control Blocks except Tree differ significantly from our original specification and need to be re-factored.

Menu, MenuItem (MenuBlock.py)

Menu Blocks and MenuItem Blocks are both derived from MenuEntry. Menus contain lists of menu entries, that is, Menus or MenuItems. MenuItems are the items in a menu that you choose to do a command, for example, the "Cut" command. So Menus are effectively a tree, where the leaves are MenuItems. The topmost Menu is the menubar. Each MenuItem has all the information necessary to display it and, when chosen, posts its associated Block event.

Which menus are displayed is determined by finding the Block with a focus, looking at it and all it's parents for all the Menu and MenuItems they contain. Collectively these Menu Blocks make up the menus that are displayed. MenuItems are quite powerful, in that Menus deep in the tree of Blocks can specify where they should be inserted relative to Menus higher in the tree. They can also replace or delete Menus above themselves. This allows nested views to have complete control over where their menu items appear.

In the future we would like to include similar flexibility for which buttons go inside bar containers.

ToolbarSpec, BookmarksBar (NavigationBlock.py)

The code in NavigationBlocks.py needs to be re-factored. Both the ToolbarSpec and BookmarksBar? should be BarContainers which should work much like Menu Blocks, or more like the other container Blocks.

View (View.py)

A View is the Block at the root of a tree of Blocks. The highest level Block displayed in Chandler is a View. It implements a lot of the behavior associated with an application, for example several menu commands and the event dispatch mechanism. For the 0.3 release, View contains most of the commands in Chandler. We need to take them out of View and put them in a new subclass of View that implements the Chandler specific commands.

The dispatchEvent method of View is responsible for dispatching Block events to particular Blocks. There are three ways Block events can be dispatched: they can be sent to a particular Block, they can be sent to the Block that has the focus, and bubbled up to the first parent Block containing it that can handle the event, or they can be broadcast to all Blocks contained in a particular parent Block of the Block with the focus. Which parent bounds the event broadcast is determined by the first parent Block above the focus Block that has the attribute eventBoundary = True. Likewise, events broadcast within this parent Block don't get sent into child Blocks that contain the attribute eventBoundary = True. This allows the views to be nested and have complete control of their particular events.

We implemented a mechanism similar to wxWidget's wxUpdateUIEvent. It sends an special event just before a U/I widget is displayed that gives the application a chance to specify the state of the widget: checked, enabled, or the string to display. This simplifies the application design because it doesn't need to keep all the U/I in it's correct state, it only needs to set the state of a widget when it's necessary. To use this mechanism, just implement a methods with the same name as the event's method appended with "UpdateUI". For example, if the Undo BlockEvent has a methodName attribute "OnUndoEvent", it will call a method named OnUndoEventUpdateUI just before the menu is displayed. You can return in the notification's data dictionary a boolean for the key "Enable" to enable the menu; a boolean for the key "Check" to check the menu, and a string for the key "String"that will used to display the text of the menu.


Contributors

  • JohnAnderson - 24 Feb 2004

Comments Welcome


Edit | WYSIWYG | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r6 < r5 < r4 < r3 < r2 | More topic actions
 
Open Source Applications Foundation
Except where otherwise noted, this site and its content are licensed by OSAF under an Creative Commons License, Attribution Only 3.0.
See list of page contributors for attributions.