JBuilder Designer/CMT OpenTools concepts

General description

This concept document explains how Designers and the Component Modeling Tool (CMT) works in JBuilder.

Designers are visual tools used for assembling the elements of a user interface (UI) quickly and easily.  The Designer subsystem consists of a component palette, an Inspector, one of three designers, and a component tree.  To access the design tools, open a source file in the content pane, then click its Design tab.  (NOTE: The Design tab appears only if the source file inherits java.awt.Container.)

The JBuilder designers include:

The Designer framework uses CMT to discover the subcomponents and property settings that make up a Java file.  This information is used to determine relationships between subcomponents, to add nodes to a structure tree representing those relationships, and to provide a visual representation of the file's UI subcomponents and relationships in the UI Designer.

The Designer framework can be extended in a number of ways.  The most obvious is that property editors and customizers included with JavaBeans are automatically exposed through the designer.  Developers can use CMT functionality to enhance their custom property editors.

Less obvious ways of extending the Designer subsystem include:


Detailed description

Architecture

The Open Tool that controls the designer subsystem is com.borland.jbuilder.designer.DesignerManager.  Each designer that makes up the system is an Open Tool that must register itself with the DesignerManager.  The DesignerManager registers the four designers included with JBuilder itself rather than having them mentioned in the manifest file; therefore, the initialization can be delayed until the user clicks on the Design Tab for the first time.  The LayoutAssistants used by the UI Designer are also registered in this way as well.

To register a custom designer:

public static void initOpenTool(byte majorVersion, byte minorVersion) 
  MyDesigner myDesigner = new MyDesigner();
  DesignerManager.registerDesigner(myDesigner);
}

Other major pieces of the designer subsystem are the Inspector and all the property editors that it uses, and the component palette.  The Inspector listens to selection change events in the structure view to know which subcomponent needs its CmtPropertyStates displayed.  The ComponentPalette provides a customizable multi-page toolbar of components that can be visually designed.

Component Modeling Tool (CMT)

CMT is the tool by which subcomponents and property settings for a source file are discovered. CMT resides in com.borland.jbuilder.cmt as a collection of interfaces.

CMT uses the Java Object Toolkit (JOT) to find all the subcomponents in the file under design, and to determine the properties and events that each type of subcomponent exposes.  For each subcomponent, the method calls made to it in the file's jbInit() method are discovered and stored in an array in the subcomponent.  In particular, the property settings are processed so that each property setting is associated with a CmtPropertyState.

Through introspection or the information in a BeanInfo file, CmtComponent determines the properties (encapsulated in CmtProperty) that the component supports.  CmtPropertyStates are the value of a particular property for a particular subcomponent, whether or not they have an associated CmtPropertySetting.

The CmtComponent for the file under design is a special case.  Most of the CmtComponent instances are for representing the types of subcomponents used in the file under design.  The file under design will be the only one that uses the methods dealing with CmtComponentSource.  The CmtComponentSource file (the file being designed) uses the CmtComponent of its superclass to determine the properties that it exposes. 

The first call to CmtComponentSource.getSubcomponents() causes buildSubcomponents() to be called.  This method uses JOT to determine all the fields that make up the source file, and then gets the CmtComponent for each type and creates a CmtSubcomponent for each field.  There is a CmtComponentManager that maintains a collection of all the CmtComponent instances this project has processed, so that extra reading of BeanInfo and component class files can be avoided.

Since events are rarely listed explicitly in a BeanInfo, reflection is almost always used to determine the events.  Using reflection to determine a JavaBean's properties or events requires walking the superclass hierarchy, so CmtComponents for all the superclasses are created as well.  Creating a CmtComponent for all the classes used in the bean being designed is one of the most expensive portions of the design process.  As a way to speed up the process of creating CmtComponents, beans that do not do anything out of the ordinary in their BeanInfo have their property and events info written to a .pme (Property, Methods, and Events) file in the [userhome]/.jbuilder/pme directory so that the next time they are encountered, one tiny simple file is processed to create the CmtComponent instead of having to examine the entire superclass hierarchy.

Accessing CMT

Assuming that you have obtained an instance of JBProject, you can retrieve a CmtComponentManager instance that will allow access to CMT functionality through:
JBProject project; // this is a valid non-null instance of JBProject
CmtComponentManager componentManager = CmtComponentManager)project.getComponents();

To obtain the file currently under design, first retrieve the current DesignerViewer node viewer, then:

  CmtComponentSource componentSource = designerViewer.getComponentSource();
  CmtSubcomponent subcomponents[] = designerViewer.getSubcomponents();

You can manipulate the methods, properties, and events of a subcomponent, as well as retrieve a live instantiation of the component or determine how the subcomponent is nested.

Annotation

Designers use the CMT data structures to find the subcomponents of interest to it, usually based on the type of subcomponent.  It can look through a subcomponent's method calls for help in determining how subcomponents are nested.

Component palette

Each DesignerViewer instance creates its own instance of the palette UI.  There is one ComponentPaletteManager (com.borland.jbuilder.designer.palette.ComponentPaletteManager) which owns the models used by the palette and manages the reading/writing of the palette.ini.

Inspector

The Inspector displays the properties for the selected component and provides property editors for changing properties.  There are two PropertyGrids which model the propertyStates and EventStates of the selected component.  The Inspector listens to the TreeSelection from the structure view to know when to refresh its model.

The grid consists of two columns, the first being the name of the property/event and the second being the text representation of the value of the proby the/event as maintained bythe propertyState.  Cell editors for the value column are dynamic.  They are based on the property editor associated with the CmtProperty of each CmtPropertyState.  They all extend DefaultCellEditor

Cell editors use their cell's associated java.beans.PropertyEditor to perform their edit on the appropriate piece of Java code.  The cell editors are also responsible for supplying the various "special knowledge" property editor extensions with the data they require by making the extra method calls provided for in their interfaces.

CmtPropertyEditor provides the CmtPropertyState to the editor so that it can know about the CmtSubcomponent, CmtComponentSource, etc.  This setPropertyState() method is called prior to setValue() and again with null after the cell edit is over.

CmtPropertyEditor2 also provides the DesignerViewer instance via its setContext() method that allows the property editor to know about the project as well as providing a method for setup and cleanup.

CmtPropertyEditors should be careful that they only perform their expensive operations when their propertyState is non-null, which is usually the case unless they store some other data and forget to null it out when their propertyState is reset to null by the cell editor.

PropertyEditors are shared often, therefore they should not attempt to store data from one edit session to another.  The cell editors will make calls to getTags() and getCustomEditor() when CmtPropertyState is null as part of their determination of which cell editor to use.  For a CmtPropertyEditor that is a tagEditor and whose propertyState is null, getTags() should return an empty array, leaving the determination of what to place in tag list to the times that getTags() is called when its propertyState is non-null.

UI Designer

The UI Designer analyzes the subcomponents' add() method calls to decipher the structure of the UI.  The UI Designer also substitutes the real layout property state created CmtComponent with a special layout property state of type LayoutPropertyState.  Containers whose BeanInfo attribute isContainer() is false have their layout property suppressed.  Another synthetic property state that the UI Designer sometimes makes is a buttonGroup property state for subcomponents that extend javax.swing.AbstractButton so that the user can specify the ButtonGroup of the button and get the ButtonGroup.add() method created as a result.

Property states whose name property is in brackets are treated slightly differently by the Inspector - the brackets and the first character are removed when forming the property name, and the property name is rendered in bold type.  All such names appear at the top of the Inspector's grid, with the first character being used to determine the order of these special property states.  This convention is used to distinquish synthetic properties from real ones.

The UI Designer uses LayoutAssistants to govern the use of nibs and component outlines based on the layout to which the container is set.  The LayoutAssistant is responsible for establishing the constraints for newly dropped components, adjusting the constraints of moved components, and establishing constraints for each child of a container when the layout is changed.

Custom layout assistants

Occasionally developers might want to use custom layout managers and want to write a LayoutAssistant to incorporate the availability of the custom layout manager into the UI Designer and Inspector.  Custom layout assistants must register themselves with the UI Designer specifically:
public static void initOpenTool(byte majorVersion, byte minorVersion) { 
  UIDesigner.registerAssistant( {
    MyLayoutManagerAssistant.class,
    "com.borland.samples.opentools.layoutassistant.MyLayoutManager",
    true); 
} 

There is a basic layout assistant implementation available at com.borland.jbuilder.designer.ui.BasicLayoutAssistant that you may wish to extend, or your layout assistant can just implement com.borland.jbuilder.designer.ui.LayoutAssistant which defines the methods for the layout specific UI interaction model.  Within your layout assistant you will have to define a property editor to be used for changing any properties your custom layout may provide such as horizontal and vertical gaps.   You may also wish to define custom behaviour for component outlines when they are being moved within a container that uses your layout manager. For instance, instead of component outlines bouncing from layout position to layout position, you can specify that the component outlines will track the mouse instead.

There is a sample layout assistant available in your samples/OpenToolsAPI/layoutAssistant directory.  This sample illustrates how to write a layout assistant for a custom layout, register it with the UI Designer so that it shows up as a layout option in the Inspector, and customize the component outline behavior.