FeaturesPluginsDocs & SupportCommunityPartners

Looks API - The design

$Revision: 1.4 $
History: available in CVS

Abstract: Describes the architecture of the Looks, the API and SPI that they provide and gives examples of their usage.

Contents:

  1. The purpose of looks
  2. API
  3. SPI
    1. NamespaceLook
    2. DefaultLook
    3. ProxyLook
    4. CompositeLook

The purpose of looks

The general description of looks has been given in overview, but for the purpose of design, it is good to realize that there are two ways how to use the looks: usage for a presentation of an in-memory object and a definition of how an object should be presented.

Ofcourse, there are examples when one needs to display an object and also defines how it should be presented, but the power of looks is in separation of the display requestor and provider. That is why we have separated the public interfaces of looks into two parts - the API and the SPI.

API

The Looks API is stored in the org.netbeans.api.looks package and is designed for those who want to given an object in a memory some visual presentation.

The main class is the LookNode that serves as a bridge between the looks framework and the established Nodes API used for abstract presentation in tree based maner. One can create its own LookNode by providing an object that should be displayed and the looks framework is responsible for generating the best presentation of given object as possible.

Sometimes the user of LookNode has some preferences of how the node representation should look like and that is why it can find its own Look and ask the LookNode to use it to present the object.

PENDING: Some example how to use the API.

SPI

The Looks SPI (service provider interface) resides in org.netbeans.spi.looks and is designed for people who want to create their own looks. It should simplify common tasks - creation of a look from scratch, extending behaviour of already created look, allow composition of different looks into one and also selection of a look based on the actual representation object.

NamespaceLook

It is expected that there is a huge number of registered looks in the system. In order to effectively and quickly select the most suitable one for given representation object an abstact NamespaceLook is provided.

It allows subclasses to define a mapping function from the representation object to a hierarchical names. The look then locates appropriate look to delegate to by scanning the JNDI registry for available looks.

An example might be a look that searches for the right view of org.w3c.dom.Document by constructing the name based on public id.

By default the system provides a classname look that calculates the list of names according to the type of the object (object.getClass ()). So for java.lang.Thread following names are checked:

  1. java/lang/Thread
  2. java/lang/Runnable
  3. java/lang/Object
This provides general (works for every object) and quick uniform distribution of registered looks, so the time needed to find the right one is minimal.

DefaultLook

The most suitable look for writing new look should be DefaultLook. It implements all methods of the abstract class Look, meaningfully delegates between them (getContextActions delegates to getActions) and provides utility methods to simplify common mechanical tasks (iconBase).

PENDING: NewType, PasteType, specifying Lookup

ProxyLook

If there is an already defined look and one wants to delegate to it and modify some of its behaviour it is time to use ProxyLook. This abstract class offers delegateTo method to be overriden and choose to which look(s) delegate. The method is called with an identification of a looks method (a numeric constant defined in ProxyLook class), so it is possible to enable/disable delegation on a per method granuality.

In some situations it might be useful to change the represented object. For such cases there is a method delegateObject, one can override it and extract a different representation object to pass to delegated to look.

If one needs just to delegate some aspects, there is a predefined subclass of ProxyLook the FilterLook. Its constructor takes a look and a method mask as arguments.

CompositeLook

One of the most powerful features of looks is the ability to merge attributes (actions, properties, etc.) provided by different looks together.

The most of the functionality is again provided by the ProxyLook. Once can override method delegateAll to indicate whether a merge should be done for a given method or not. If so, the results of all looks returned by delegateTo is composed.

There is a specialized implementation of ProxyLook that is designed to solve the problem of communication between different modules. Often two or more modules wants to participate on a view of an object. In such case, one should register a CompositeLook with a given namespace (for example /org/mymodule/composite/beans) as a view for that object type and all modules should then register their own looks into the namespace. As a result the looks provided by all modules will be merged for given object type.

To create an instance of composite look use following syntax in your module layer (the reference to org.netbeans.modules.looks.CompositeLook.create and should be replaced in future):

    <folder name="Looks">
        <folder name="Types">
            <folder name="org">
                <folder name="yourpackage">


                    <file name="YourClass.instance">
                        <attr name="instanceCreate" methodvalue="org.netbeans.modules.looks.CompositeLook.create" />
                        <attr name="context" stringvalue="Looks/Composite/org/yourpackage/somename" />
                    </file>


                </folder>
            </folder>
        </folder>

        <folder name="Composite">
            <folder name="org">
                <folder name="yourpackage">
                    <folder name="somename">
                   
                        <!-- here you and other modules can register looks that will compose view for YourClass -->

                    </folder>
                </folder>
            </folder>

        </folder>

    </folder>


Original parts of the document:

  • Methods of look itself
    Values returned from these methods characterize the Look itself. Here are still some methods missing e.g. getName(), getOptions etc. See the Not implemented yet section for the proposed solution.
  • General methods
    These are general methods which provide values for the basic properties of a Node e.g. Cookies, Handle, name of the Node etc.
    Possible extensions are methods for searching the node for representing object in the hierarchy.
  • Methods for "style"
    Style of the Node. Means: DisplayName, ShortDescripton, Icons, HelpCtx etc.
    This should be extended to provide methods for Icon badging.
  • Methods for children
    Given a node the method getChildObjects() returns the objects which are subobjects of object represented by the node in the hierarchy.
    Nodes for the objects are created automatically in the implemetation of LookChildren. The nodes created are LookNodes with representedObject set to the child objects. The method nodeCreated( LookNode node) is called on the look to notify creation of every node.
    Possible extensions are the methods for grouping, filtering and ordering. See the Issues section for more details.
  • Actions and NewTypes
    These methods specify how actions for nodes and NewTypes which can be created on the nodes.
    The NewTypes could be also listed in the Clipboard operations group. But I like them here and some people I consulted with do too.
  • Properties and Customizer
    These actions return the properties which are shown in the property sheet and a customizer for the represented object if available.
  • Clipboard operations
    These operations should handle actions like cutting, pasting, copying, cutting and dragging.
  • Other methods
    Methods for handling in-place name change of the node and method for handling the destroy.

    4.4 Firing events from LookNodes

    It is necessary to fire events (e.g. PropertyChange, IconChange etc.) from the Node when some properties or state of the represented object change. In current implementation of nodes there usually is a innerclass listening on the underlying object. The innerclass has access to protected methods in the Node for firing events on the Node. As the firing methods are protected only the subclasses of the Node class have access to these methods.
    Variation of this constraint must apply also when using Looks. Only the Look which provides value of some property of given node may fire events on the node. This means that the firing methods can not be simply published in the LookNode. The class LookNode.Interior is used for this purpose. The interior publishes all methods for firing events from LookNode.
    The best way how to create the events bridge between the represented object and the Node is to subclass the utility class EventTranslator so that it implements the some listener on the represented object (e.g. property change listener). Instances of this class can be then created in the Look's method nodeCreated. The adnavtage is that the EventTranstator holds the Interor by weak reference which means that the Node can be garbage collected even if the representedObject still exists.
    Notice that there is no need for setting name, icon or whatever on the node in the body of the event handling method. Just fire the appropriate event from the LookNode and the name will be set automatically by asking the Looks.


    EventFiring using the EventTranslator utility class.

    5. Known issues

    5.1 Not implemented parts

    Management, configuration & persistence of looks

    Settings of various looks have to be serialized (xmlized) into a persistent form. This is not the case now. There is a kind of hack in the demo module which stores just empty instances into the Service folder, all the work with settings is done in the constructors. We are waiting when the design of the XML support is fixed.
    Managing the looks programmatically is very important as to allow module A to modify looks provided by module B is one of the main purposes of the looks concept.
    We will probably add a interface or innerinterface of Look which will manage the properties of the Look itself. The Look interface will be extended by a method for getting this interface. Subclasses of the interface for maintaining the look properties can handle the persistence and configuration of looks. They may also provide additional methods e.g. for managing sublooks of CompositeLook.

    Selecting look on nodes

    First, there is the question of the GUI of the action. So the switching the look is possible in the property sheet, which not very nice. Second, the selection of looks which can be set on given node is not implemented yet, at all. (The method isLookStandalone( LookNode node ) is supposed to do that.) The result is that you can select any look on any node which is not very user friendly, but it shouldn't be problem as the whole thing is meant just like a demo.

    Unresolved concepts - sorting, filtering, grouping of Children

    There is currently no support for sorting, filtering and grouping of children nodes. Although it would be nice to have some, the problem is not easy to solve. If more than one contained Look of CompositeLook returns children then the resulting (merged) set reflects the ordering of sublooks in the CompositeLooks. It would be nice to give the user some general support for resorting the children by some property for example. But how to determine the set of properties and the appropriate Comparators. (There are various ways of solving this problem - additional method in Look interface, separate interface etc.) The problem with filtering and grouping is analogous.

    Icon badging

    We probably will need to add some support for Icon badging into the Look. Of course it is possible to implement badging in the getIcon() method, but some kind of support could be useful in order not to reimplement it in every Look.

    5.2 General Issues

    Should Look rather be an abstract class than interface?

    It is highly probable that we will have to add some methods into the Look interface (e.g for anotationg display names or icon badging), which is obviously impossible. So maybe the look should be represented by an abstract class. Other possibilty would be to add some extension mechnism into the Look interface.

    Naming convention for looks

    There is currently no method in the Look interface which would return name of the look. The question is what should be the naming convention of the looks. This may depend on Lookup.

    How to provide access to LookNode or represened object through a Cookie

    It is necessary to provide some way how to get the represented object for given node (or the node itself) using Cookies. This is needed to be able to implement actions, as the node passed into the actions is usually a FilterNode rather than a LookNode.
    We could create somethng like RepresentedObjectCookie or we could make the LookNode to implement Node.Cookie and return itself when asked.

    Where to put methods for look itself

    Means methods like getLookName() or methods for (de)serialization of the Look etc.

    Registering Looks for DataObjects

    Should we put the restriction on the root node saying that this node must have a CompositeLook for all subnodes set, which is used as default. Or should we rather introduce concept of registration of default looks for DataObjects.

    Exclusion Look

    Some kind of exclusion look could be interessting. Say one would like to set a package to show all Java classes like JavaBeans, but retain the way how all other DataObjects are displayed. Currently we would have to copy the CompositeLook which works for all DataObjects replace the sublook for displaying JavaClasses and set this new look on the package. This only applies to multivalued properties so it may be probably achieved by adding a method setMerge( boolean merge ) into the CompositeLook.

    Icon Manager problem

    This problem is not tied to Looks specifically. The IconManager class, which caches the Icons used in the Explorer is package private. The only way how to access it is to subclass AbstractNode. You can see the hack in the IconBaseSupport class.
    Other way would be to duplicate the code from AbstractNode and to use the caching method in Utility module. Which would make the openidex dependendent on Utilitie module which probably should be avoided.

    5.3 UI Issues

    Question arises how to present looks to user. This question has two main aspects the first one is how to switch the Looks on the Nodes in Explorer. Second is how to present the customization/settings of Looks to the user.

    How to switch looks?

    We see several ways how to switch looks on a Node. How ever some more experienced UI experts may find better ones.
    1. PropertySheetCreate new tab in the property sheet, which lets the user switch the looks. This is the current state. It is not very convenient. The advantage is that if Look would have properties (e.g. sorting, filtering, grouping) values for these properties can be set in the property sheet. It is also possible to create special customizers for these properties which would allow to set the values more convenient way.
    2. Adding an item into the popup menu on every node. Submenu on this item would list applicable Looks. Right click is what I do instinctively when I want to switch the look. (Then I realize that I have to go to the property sheet :-( ). Popup menu
    3. PropertySheetAdding a Look chooser to the ToolBar in MainWindow. Not very nice as the look change only affects Node showed in the explorer. See next point.
    4. Adding a toolbar into the explorer would be an option. This toolbar would allow to switch the Look for current Node. The "..." button would bring up the customizer of current Look.

    How to maintain and customize Looks?

    How to allow user to set properties if a Look e.g. sorting.
    • One question is how to provide the access to the look properties/customizer. This is relatively easy if the look is switched in the property sheet or in the tool bar (extra button). In the popup menu this would probably mean one extra item in the submenu, probably after separator, called customize.
    • Second problem is harder. Should we have only one instance of the Look with for the whole IDE. Or should we allow to create new instances with differently set properties. (Something like "Save Look As"). Where should be the storage of all looks. (Service type?)
    • Should we let the user create new instances of CompositeLooks to create completely new views or should we restrict user on using Looks predefined by modules? If we choose the first option how should we check the correctness of newly created Look. An option is to make his feature accessible only to Expert users of the IDE.
  • Companion
    Projects:
    MySQL Database Server   Open JDK: an Open SourceJDK   GlassFish Community: an Open Source Application Server    Mobile & Embedded Community    Open Solaris   java.net - The Source for Java Technology Collaboration   Virtual Box - full virtualizer  Open ESB - The Open Enterprise Service Bus Powered by