Overview

Javadoc

The Javadoc is present in the org.openide.windows package. Most module authors will want to look at TopComponent to subclass it, or perhaps at CloneableTopComponent.

Contents

Window System API

The Window System API provides fairly abstract support for creating windows or window-like container components that may be handled smoothly by the NetBeans window manager implementation.

Overview of the Window System

As a rule, modules should not create their own top-level windows (e.g. java.awt.Window), since these would exist without the knowledge of the IDE's window manager. This window manager is capable of manipulating application windows and panels, including docking and undocking them into tabbed frames, assigning them to user-customizable workspaces, and making the window configuration persistent across sessions. Well-behaved modules will use the API so as to integrate nicely into the feel of the rest of the IDE.

Top components and docking

A top component is a Swing component (usually a panel or the like, though not necessarily) which might exist in its own window; or it might be docked along with other top components into one tab of a multi-tabbed window. The Editor panes are the most apparent example of this behavior - they can be undocked into their own windows, or left as tabs in a single window, according to the user's preference. Almost everything visible in the IDE is a top component, except the Main Window (which is treated specially because of its centrality), and dialog boxes.

Each top component is at any given time in a particular docking mode. (But it can be in different modes on different workspaces.) A docking mode first of all specifies whether the component should be docked or undocked; it may also specify which windows may be docked into it. The IDE implementation is responsible for presenting modes visually, e.g. as individual windows; multitabbed windows with one component per tab; MDI frames attached to some side of the screen; or floating in the center of the MDI desktop; or split windows; and so on. Most of the presentation is invisible to users of the API, so as to ensure this flexibility.

The API provides access to a set of modes for each workspace, each of which contains some top components. The API user can create new modes using a provided factory, but this only permits code to associate top components into a single mode and set the mode name and icon - the implementation automatically displays the modes in an appropriate fashion, possibly with user interaction.

Top components have some special trimmings which work in various modes - for example, they may have a popup menu of actions (e.g. Save, Close, ...) which can be displayed in the tab of a multi-window, or the window menu when undocked. Top components may provide special actions in this list as well.

At any given time, one top component is active. It will be contained in the focussed window, and in a multi-tab view, will be the selected tab. This component serves as the base for a number of things in the IDE; for example, the current node selection is controlled by the active component.

Cloning

Some top components can be cloned, meaning that a new top component created which initially shares the same contents. Exactly how the cloning works depends on the top component, but typically the same data will be referred to and the cloned component will simply be a new view on it. For example, Editor panes can be cloned with the effect that the same file will be open in each view.

Workspaces

The IDE starts off with the workspaces "Editing", "Browsing", "Running", and "Debugging" to group windows according to their expected applicability. The user may add to these workspaces, and in any workspace the user may add or remove windows so as to customize their appearance.

Each workspace groups modes, not top components directly, so users desiring that similar multi-tabbed windows with different contents appear in their own workspaces, should create a new user-defined docking mode to hold them.

The IDE tries to save the current window configuration when it exits, including the positions of all modes, the arrangement of modes on workspaces, and the components present in each mode; and then restore this configuration when next started. A similar mechanism allows different projects to have their own configurations.

Creating a Top Component

Creating a custom top component, including assigning it to the proper docking mode, is not generally difficult.

Subclassing TopComponent

To create a simple top component, it suffices to subclass TopComponent. This is a subclass of JComponent, which means that it is possible to draw on it using a variety of mechanisms; typically, it is treated as a container, and its layout may be set and components added to it. For example, ExplorerPanel is a subclass of TopComponent that uses a BorderLayout and may have arbitrary components placed on its surface.

There are a few general ways in which you can customize the component besides painting subcomponents on it:

Accepting and creating modes

Creating new modes is straightforward and is recommended for many modules.

To create a mode, first decide which workspace it will live on (a mode cannot span workspaces). Typically you will use WindowManager.getCurrentWorkspace(), but you may also want to check for an existing workspace using WindowManager.findWorkspace(String) or create your own using WindowManager.createWorkspace(String). Now you may call Workspace.createMode(...) which accepts the code name of the mode (used to find it later via Workspace.findMode(String)); the display name (shown to the user in some situations); and a URL to an icon for it (displayed e.g. as the window icon). You can keep a reference to the mode for future use in docking top components, or just refer to it by code name (which is generally better).

To add top components to a mode, just call Mode.dockInto(TopComponent). For example:

public static class MyComponent extends TopComponent {
    public MyComponent(Object data, Image icon) {
        setName(NbBundle.getMessage(This.class, "LBL_widget_tree"));
        setIcon(icon);
        setLayout(new BorderLayout());
        add(new JTree(createModel(data)), BorderLayout.CENTER);
    }
    public SystemAction[] getSystemActions() {
        SystemAction[] deflt = super.getSystemActions();
        SystemAction[] added = new SystemAction[] {
            SystemAction.getAction(WidgetReparseAction.class),
        };
        return SystemAction.linkActions(deflt, added);
    }
}
private static final String WIDGET_MODE_NAME = "MyWidgets"; // NOI18N
// ...
Workspace ws = TopManager.getDefault().getWindowManager().getCurrentWorkspace();
Mode myMode = ws.findMode(WIDGET_MODE_NAME);
if (myMode == null) {
    myMode = ws.createMode(WIDGET_MODE_NAME,
                           NbBundle.getMessage(This.class, "LBL_widgets_mode"),
                           new URL("nbresloc:/com/mycom/widgets/modeIcon.gif"));
}
TopComponent myComponent1 = new MyComponent(firstData, firstIcon);
myMode.dockInto(myComponent);
myComponent.open();
TopComponent myComponent2 = new MyComponent(secondData, secondIcon);
myMode.dockInto(myComponent2);
myComponent2.open();
myComponent2.requestFocus();

Creating a cloneable component

Just by subclassing CloneableTopComponent, you can create a component which is capable of cloning itself, like the Editor does with the popup action "Clone View". If Object.clone() takes care of all of your instance state satisfactorily, then you need do little more; otherwise, you can override CloneableTopComponent.createClonedObject() to specify exactly what fields should be shared with the original top component. Typically all clones of a component should share any underlying data object (e.g. edited files), but they may each have different display parameters or other noncritical settings.

You may specify how these cloned windows act when they are closed, by overriding CloneableTopComponent.closeLast(). (There are more general methods for all top components pertaining to closing them; this method is specific to cloneable top components.) It will be called when the last clone of a component is about to be closed. Components keeping some sort of user data, such as the Editor, should offer to save it here, and also shut down the editing system for that file. If you do not wish to close the last clone (for example, Cancel was pressed on a save dialog), just return false.

Other methods allow you to keep track of when new clones are created, and to find the sister clones of a top component, if that is needed. As an example, you could provide a component action which would cause all visible clones to display a different part of the content of the data simultaneously, so the user could use the screen more effectively.

Handling workspaces, focus, and the node selection

There are several more ways in which top components can interact with the desktop smoothly.

Workspace interactions

Generally top components should not try to interact explicitly with workspaces - the user should be able to move them about freely for organizational purposes.

If you need to create a new workspace, because there is a clear criterion for grouping a number of windows - i.e., a major new area of functionality, such as "Analysis" if you are writing a profiler - then you may create a workspace using WindowManager.createWorkspace(String), and you may at some later time switch to this workspace using Workspace.activate(). When creating modes on a fresh workspace, you may wish to place the modes (typically implemented as windows) in particular positions on the screen to implement a logical layout. For this purpose, you should first get the screen size using Toolkit.getScreenSize() and then to position modes, call Mode.setBounds(Rectangle) with the desired absolute bounds. You may also want to call WindowManager.getMainWindow() to get the bounds of the main window's frame.

One important option to exercise is to call TopComponent.setCloseOperation(...) (say in the component's constructor). Using TopComponent.CLOSE_EACH, the top component will be closed completely on every workspace if any instance of it is closed (using TopComponent.close() or via user interaction). This is suitable for some types of components, such as Editor panes, which the user expects to do something (save) upon closing. Other components which might be useful left open on other workspaces should use TopComponent.CLOSE_LAST (the default). For cloneable top components, special behavior is usually desired which is described separately.

Focus/activation

Focus works naturally with top components: the focussed single window has the single activated component; the focussed multitabbed window has the activated component on its selected tab.

You can explicitly request that a top component be activated (and its containing window focussed) by calling TopComponent.requestFocus(). (It should be opened first.) TopComponent.getRegistry() and then TopComponent.Registry.getActivated() correspondingly finds the last-activated component.

Sometimes a top component should be programmatically activated as part of an action (for example, asking to Open a file should focus its Editor window, whether a new one is created for the purpose or not); also, a component may passively pay attention to when it is selected or deselected by overriding the hooks TopComponent.componentActivated() and TopComponent.componentDeactivated() - typically these are used to enable or disable actions. Most importantly, certain actions such as CopyAction or FindAction are CallbackSystemActions, which means that they must be explicitly enabled with an ActionPerformer. This is usually done by registering the performer when a component is activated, and deregistering it when the component is deactivated. Common types of top components such as Explorer windows and Editor windows automatically handle actions which are appropriate to them.

The node selection

Finally, each top component may have associated with it a node selection, which is simply a list of Nodes that it decides to treat as "active". The node selection will have effects on other parts of the system - for example, NodeActions and CookieActions pay attention to it.

The selection may be set using TopComponent.setActivatedNodes(...). Not all components will need to explicitly set the node selection, however. Some top components do not select any, and actions may handle this condition gracefully using e.g. NodeAction.surviveFocusChange().

Explorer views embedded in an ExplorerPanel automatically make the node selection track user selections in the view, as you would expect. Also, any top component associated with a data object will automatically select that object's node delegate, which is usually the intuitive behavior as well.

Special support for serialization

If you are writing a top component with a complex configuration or other instance state not associated with a data object, you may want to specially support its serialization, so that the same configuration will be restored after a project switch or IDE restart. This is not difficult to do; you just need to hook into the externalization methods of TopComponent:
public class FlippableView extends TopComponent {
    public static int HORIZ_ORIENT = 1;
    public static int VERT_ORIENT = 2;
    private int orient = HORIZ_ORIENT;
    public int getOrientation() { /* ... */ }
    public void setOrientation(int ornt) { /* ... */ }
    public void writeExternal(ObjectOutput oo) throws IOException {
        super.writeExternal(oo);
        oo.writeInt(getOrientation());
    }
    public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException {
        super.readExternal(oi);
        setOrientation(oi.readInt());
    }
}
It is desirable to store as much of your component's configuration as possible (if you can do so safely, i.e. without triggering an exception typically during readExternal). For example, Editor windows will store the open file and cursor position; Explorer windows, the current node selection and expanded path; etc. If part of the configuration you wish to store is some piece of serializable data that you are not completely confident can be deserialized without error, please instead store a NbMarshalledObject wrapping the data, which will protect the object streams from being corrupted just because of one component. For example:
// Possible that you may break serialization of this class accidentally:
private MySerObject state;
// ...
public void writeExternal(ObjectOutput oo) throws IOException {
    super.writeExternal(oo);
    Object toWrite;
    try {
        toWrite = new NbMarshalledObject(state);
    } catch (Exception e) {
        TopManager.getDefault().getErrorManager().notify(ErrorManager.WARNING, e);
        toWrite = null;
    }
    oo.writeObject(toWrite);
}
public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException {
    super.readExternal(oi);
    NbMarshalledObject read =(NbMarshalledObject)oi.readObject();
    if (read != null) {
        try {
            state = (MySerObject)read.get();
        } catch (Exception e) {
            TopManager.getDefault().getErrorManager().notify(ErrorManager.WARNING, e);
        }
    }
}
This example assumes that your component can survive a restart even without setting this piece of its state correctly (it can just use some default settings). If the component cannot be validly recreated without this information, still use an NbMarshalledObject, but do not throw the original exception from it to the writeExternal or readExternal callers - this will make the whole window system stream be treated as corrupt and discarded! Instead, use:
try {
    // new NbMarshalledObject(obj) or nbmo.get()
} catch (Exception e) {
    throw new SafeException(e);
}
This will cause your top component to not be stored or loaded, but other components in the system will be unaffected.

The default implementation of the read and write methods must always be called. It stores the name and some internal information pertaining to the Window System. You must save the icon yourself, though most users will set the icon in the constructor. Remember that a TopComponent must have a default constructor in order to be deserialized. In older versions of the IDE this needed to be public; this is no longer necessary.

Singleton implementations (i.e. where only one instance of the class should exist in the IDE) are possible; just remember to assign the default instance both in the default constructor, and also in the readExternal method.

It is possible to use writeReplace and readResolve as well for some advanced uses - but be very careful to resolve to a subclass of TopComponent, and to always invoke the default implementations of readExternal and writeExternal.

Serialization hints

In NetBeans 3.3, there is an additional hint that you can give to the window system to help control unwanted serialization of top components. It is not guaranteed that this hint will always be honored in future releases, so top components must assume that an attempt to serialize them might be made despite this hint and act reasonably if so.

You may set the JComponent client property PersistenceType on your top component to one of the following:

Never
The system will not try to serialize your component. Suitable for transient windows that the user would not generally wish to save across project switches, IDE restarts, and so on, as well as for windows containing data which cannot by its nature be persisted correctly.
OnlyOpened
The system will only try to serialize your component if it was open at the time. Suitable for many types of windows that should be saved on their workspaces if visible, but for which there is no compelling reason to keep information about the window state (docked mode and so on) when closed.

By default all top components are persisted.

Manipulating Existing Windows

Most manipulation of existing windows should be kept to a minimum, of course, to avoid disrupting the user needlessly. Occasionally it makes sense to do a few things to top components externally.

Finding workspaces, modes, and top components

A few method calls may be used to find workspaces, modes, and top components:

Giving focus and closing

Any top component may be given focus just by calling TopComponent.requestFocus().

To close a top component programmatically, call TopComponent.close().

Listening to window system events

Most interesting aspects of the Window System may be listened to using the Java Event model. Most commonly, this is done to listen to changes in the activated node selection, but other things are possible too.

XML Persistence Format and Installation

Overview

The window system used by the IDE is based on workspaces. The layout of individual workspaces can become relatively complex, as was learned by experience. User requirements are varied, and so workspace layout needs to be flexible and customizable. Using the original strictly procedural Java API with a GUI implementation in the core, the end-user situation as far as customizability and flexibility of workspaces was acceptable.

However, better support was needed in two areas - modules and initial layout. What does this mean? Modules had limited possibilities to control layout of their own workspaces. Also, the initial layout of system workspaces was hardcoded in core sources, which was not sufficient due to the nature of this information, which is likely to change frequently. Modifications of initial layout needed to be easier.

Justification

Main goals comes directly from the issues that were mentioned in motivation section:

  1. Enhance module control over workspace layout
    Modules will have full control over layout of own workspaces, including frame MDI constraints.

  2. Easy initial layout modifications
    Initial layout modifications should be as easy as possible, without changes in source code.

  3. Less hard-coding in core
    Core implementation currently specifies nearly all initial layout. However, right way to go is to let modules specify complete layout of their workspaces, such as Visual Editing, Browsing, Debugging.

  4. Expert users
    Allow expert users to read, understand, modify, backup and make versions of stored window system content.

From above points, it's clear that description of workspaces layout should cover all features that window system offers (point 1.) and should be expressed in some human readable form (point 2, 4.). Mentioned requirements fits well into XML layers architecture, which is already supported by Netbeans' Open APIs.

Solution backbone reads like this:

Example of simple window system content expressed as merge of workspace layouts from different modules is shown on picture below:

Workspaces in layering schema


Above picture shows simple situation when modules (in this case form and debugger) define just their workspaces and nothing more. In reality, modules will want to influence other workspaces and frames as well, especially workspaces and frames created by core implementation.
To support such features, workspace layout description format has to be well organized and granularized, as described in next paragraph.

Physical Layout and File Formats

In first part of this chapter, overall structure of layout configuration is discussed. XML layers architecture uses directory-like structure for storing information. Directory structure itself is expressed in XML syntax, creating picture of 'virtual file system' stored in XML file.
Further, exact format of folders and files which hold layout information is specified, together with examples.

Storage of Configurations

Physical storage of layout configuration documents is determined by layers support. Until modified, layout configuration files lives in modules that defined them. More exactly, they are read from files which are stored in module jar archives, together with other module classes.

However, workspaces layout configuration is likely to change frequently during runtime of the system, so then modifications will be stored under (for example) the /system subdirectory of current project, as shows example directory tree below. Described mechanism is fully driven by XML layers support provided by FileSystems API and we needn't care about it more from the perspective of window system. (The precise details of where customizations are stored is dependent on the implementation; suffice it to say that the default file system of the repository is responsible for holding the files and applying any changes made.)

Directory structure

To effectively use XML layers, it's necessary to define directory structure for workspaces layout description. Following directory structure is fixed and in fact it represents part of API modules can deal with. It means that this structure will not change in incompatible manner and modules can rely on it. Again, the specification is of resource paths in the system file system rather than details of how this file system is composed and modified.

As of NetBeans release 3.3, winsys configurations are part of individual projects, which means files are stored under "Projects/project_name/system" subdirectory of user directory, as shown below. It is likely that in next release, definition of projects will be more or less modified and window system won't be part of individual projects anymore. Change will be fully compatible with current state, provided that clients use the FileSystems API to access the objects.

   "user directory home"
     |- system
     |   |- Projects
         |   |- Project_A
             |   |- system
                 |   |- Windows
                     |   |- WindowManager.wswmgr
                         |- WindowManager
                         |   |- Workspace_1.wswksp
                         |   |- Workspace_1
                         |   |      ...
                         |   |- Workspace_2.wswksp
                         |   |- Workspace_2
                         |       |- mode_1.wsmode
                         |       |- mode_1
                         |       |   ...
                         |       |- mode_2.wsmode
                         |       |- mode_2
                         |           |- comp1_ID.wstcref
                         |           |- comp2_ID.wstcref
                         |             ...
                         |
                         |- Components
                             |- comp1_ID.ser
                             |- comp2_ID.settings
                                ...
Workspaces and Modes

There exist direct mapping between instances of classes which are defined in traditional winsys API and folders and files shown above. Folders and files named "Workspace_X" under Windows/WindowManager/ define workspaces and map to instances of org.openide.windows.Workspace classes, while "mode_X" files under the workspace folders define modes (= frames) belonging to workspaces, and map to instances of org.openide.windows.Mode (by which it is meant that the corresponding data objects have an appropriate instance cookie).

Naming of folders and files is important, because folder and file names are used as unique identifiers for workspaces and modes. Basically it means that winsys API methods Workspace.getName() and Mode.getName() will return the same names as used for folders and files.

Folder - data file pairs

Folders for window manager, for each workspace and for each mode have to be stored together with their properties, for example display name or icon URL. This is done using "folder - file pair", as evident from example directory tree above. Folders cannot hold their properties directly, that's why each folder is accompanied by file on the same directory level, which has the same name as folder and stores property information for its folder.
Note, window system will recognize only folder - file pairs with exactly same names.

Top Components

Components introduce a bit of complexity to the directory structure, because one top component can be shared among several modes(=frames) on different workspaces. To handle sharing correctly, modes contains only references to components, component's data are stored in separate folder named Windows/Components/. Example of top component reference is file comp1_ID.wstcref shown above in directory tree. Top component data needs to be referenced through some unique identifiers, which is not that straightforward as for workspaces and frames, because Open API itself currently doesn't require and define unique identifier for top components. In this situation, data object names of top component data files (*.settings or *.ser) are treated as unique IDs.

Structure Depth

Directory structure of winsys configuration is fine-grained in the sense that each workspace, each frame (=mode) is represented by a folder, while each presence of top component is represented by special file. Such design will enhance module possibilities of configuring workspaces and frames defined by other modules or by core implementation.

Detailed explanation: Imagine that debugger module wants to specify size, location, constraints of debugger frame not only on its own Debugging workspace, but also on Running workspace. So debugger module specifies folder 'DebuggerFrame', with proper 'DebuggerFrame.wsmode' file content, under folder 'Running'. Note that debugger module must not define 'Running.wswksp', because debugger module is not defining Running workspace, debugger is only completing Running workspace if present. As a result of layers merge done by system, 'DebuggerFrame' will appear as subfolder of 'RunningWorkspace'.

If there were no folders for frames, this operation would not be possible, because layering is done on folder and file level, not on file contents level.

Modules now can:
  1. Create own workspace
    Module builds its own workspace and layout from the scratch by creating new folder and data file for workspace. Module can put own modes and components on such workspace. Other modules can 'complete' such workspace by their own modes and components.

  2. Add modes to foreign workspaces
    Module can install a mode into workspace that was defined by other module or core implementation and set mode properties accordingly. To achieve this, module is required to create folder named by foreign workspace, together with subfolder for mode folder - data file pair, and fill mode's *.wsmode data file properly.
    Note that module which is using other module's workspace must not define *.wswksp data file for such 'foreign' workspace, because module which define folder and wswksp file for workspace then owns the workspace and there cannot be multiple modules owning one workspace.

  3. Add components references to foreign frames
    It's even possible to create folders for foreign workspace and frame (again without data file specification, we're just using them, not define them) and insert reference to top component into specific frame. End user will see component added to specific frame on specific workspace.

  4. Override winsys elements defined by other modules
    As layers can overlap and hide information between each other, modules can also modify and/or hide winsys layout elements defined in other modules. However, module must depend on other module whose winsys layout it overrides, as is generally required when overriding layer-provided files.

Global properties

Window system as a whole stores several properties which have global nature. They are always defined by a folder Windows/WindowManager/ and data file Windows/WindowManager.wswmgr. Core implementation defines default values of global properties for win sys. Format of data file is simple:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE windowmanager PUBLIC
          "-//NetBeans//DTD Window Manager Properties 1.0//EN"
          "http://www.netbeans.org/dtds/windowmanager-properties1_0.dtd">
<windowmanager version="1.0">
    <!-- size and location of the main window -->
    <main-window x="0" y="0" width="300" height="200"/>
    <!-- size of the screen last displayed on -->
    <screen width="1024" height="768"/>
    <!-- may also be "mdi" -->
    <ui-mode ui="sdi"/>
    <!-- reference to active workspace -->
    <active workspace="Editing"/>
</windowmanager>
Modules will typically never need to access these global properties, and modules should never try to change them.

Workspaces

Workspaces can be either defined or referenced by a module. Module which defines workspace is a 'owner' of the workspace, module which references workspace is 'user' of such workspace.

Workspace definition

Module defines workspace in case it wants to build it from scratch. Workspace is defined by folder Windows/WindowManager/WORKSPACE_UID/ and data file Windows/WindowManager/WORKSPACE_UID.wswksp.

Format of workspace data is defined by its DTD file. Example is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workspace PUBLIC
          "-//NetBeans//DTD Workspace Properties 1.0//EN"
          "http://www.netbeans.org/dtds/workspace-properties1_0.dtd">
<workspace version="1.0">
    <name unique="editing" display="CTL_Workspace_Editing" from-bundle="true" bundle="org.netbeans.core.windows.Bundle"/>
    <ui-type type="mdi">
        <active mode="explorer"/>
        <mode z-order="explorer, editor, output_window"/>
        <toolbar configuration="Standard"/>
    </ui-type>
    <ui-type type="any">
        <cascade origin-x="0" origin-y="0" step-x="20" step-y="20" count="4" current-x="150" current-y="210"/>
        <active mode="explorer"/>
        <mode z-order="explorer, editor, output_window"/>
        <toolbar configuration="Standard"/>
    </ui-type>
</workspace>

Workspace ordering is defined using DataFolder order, so it may for example be defined in a layer. Following example shows order of Editing and Running workspaces, and says "Editing workspace will be ordered somewhere before Running workspace":

<!-- ... -->
    <folder name="WindowManager">
        <file name="Editing.wswksp" url="windowmanager/Editing.wswksp"/>
        <folder name="Editing">
            <!-- ... -->
        </folder>
        <attr name="Editing/Running" boolvalue="true"/>
        <file name="Running.wswksp" url="windowmanager/Running.wswksp"/>
        <folder name="Running">
            <!-- ... -->
        </folder>
<!-- ... -->

Note that ordering is done on parent folder of workspaces, which is WindowManager/ folder, by defining ordering attributes.

The same method is used for ordering of top component references in specific mode and for ordering of modes in workspace.

Workspace referencing

A module can reference a workspace defined by another module, to complete layout of such workspace with own modes and components. In such case, module specifies only workspace folder Windows/WindowManager/WORKSPACE_UID/ filled with definition of own modes (see below). The workspace data file Windows/WindowManager/WORKSPACE_UID/.wswksp would not be used.

Modes

Situation is similar like for workspaces, modules can either define own mode from scratch or reference mode defined by other modules.

Mode definition

Module defines mode by creating proper mode folder Windows/WindowManager/WORKSPACE_UID/MODE_UID/ and data file Windows/WindowManager/WORKSPACE_UID/MODE_UID.wsmode.

Here is example of valid mode data file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mode PUBLIC
          "-//NetBeans//DTD Mode Properties 1.0//EN"
          "http://www.netbeans.org/dtds/mode-properties1_0.dtd">
<mode version="1.0">
    <name unique="explorer" display="Explorer" from-bundle="false"/>
    <ui-type type="mdi">
        <relative-bounds x="0" y="0" width="30" height="100"/>
        <frame type="desktop" constraints="left" state="normal"/>
        <container type="split" active-tc="topc1">
            <area constraint="center" relative-x="0" relative-y="0" relative-width="100" relative-height="60"/>
            <area constraint="bottom" relative-x="0" relative-y="60" relative-width="100" relative-height="40"/>
        </container>
        <icon url="nbresloc:/some/resources/explorer.gif"/>
        <other defined-by="module"/>
    </ui-type>
    <ui-type type="any">
        <relative-bounds x="10" y="10" width="50" height="100"/>
        <frame type="window" state="maximized"/>
        <container type="split" active-tc="topc2">
            <area constraint="center" relative-x="25" relative-y="0" relative-width="75" relative-height="100"/>
            <area constraint="left" relative-x="0" relative-y="0" relative-width="25" relative-height="100"/>
        </container>
        <icon url="nbresloc:/some/resources/explorer.gif"/>
        <other defined-by="module"/>
    </ui-type>
</mode>
Mode referencing

Module references mode defined by other module in order to place own components into such mode to complete its layout. Module specifies only mode folder Windows/WindowManager/WORKSPACE_UID/MODE_UID/ filled with own top component reference files. The mode data file should not be given.

Top component references in modes

For top component to appear in mode, reference file needs to be put into mode folder, for example file Windows/WindowManager/WORKSPACE_UID/MODE_UID/COMP_UID.wstcref. Keep in mind that component reference file is only a part of the story, to specify complete link to top component through COMP_UID, you have to provide file COMP_UID.settings or COMP_UID.ser and place it into Windows/Components/ folder.

Example top component reference file content:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tc-ref PUBLIC
          "-//NetBeans//DTD Top Component in Mode Properties 1.0//EN"
          "http://www.netbeans.org/dtds/tc-ref1_0.dtd">
<tc-ref version="1.0" id="compID">
    <ui-type type="mdi" state="opened" constraint="center" selected="true"/>
    <ui-type type="any" state="closed" constraint="left" selected="false"/>
</tc-ref>

Top component data

Stored in folder Windows/Components/ as *.settings or *.ser files, which holds data of top component. Top components are instantiated using information stored in these files. If top component is created dynamically in the code, system will automatically create a new file containing proper information during save operation.

Names of files in Windows/Components/ (without extensions) are used to link top components and their references in modes.

In the future other or even arbitrary file extensions and types may be supported for storing top component information, provided that the result of DataObject.getName() matches the ID. Also currently all settings for newly created components will be stored in *.settings files.

Top component can be divided from instantiation perspective to following types:

  1. "Singleton", static top components
    These components are created only once, typically there exist only one instance of component per class. Examples of such components are Filesystems, Runtime, Execution.
  2. Dynamic top components
    Some components have dynamic character, which means that new instances of such components are needed during IDE runtime. Another typical characteristic is cloneability. All editors like java source, html, xml or form editors fall into this category.
For static components, specifying their data file and link it with component reference in mode is well enough to describe all life of such component.
However, for dynamic top components, situation is more complicated.

Defined locations

The window system API does not define any particular workspace or mode names; it only defines the structure given names. So the only files whose names are defined here are:

  1. Windows/WindowManager/
  2. Windows/WindowManager.wswmgr
  3. Windows/Components/

Automatic deinstallation support

Modules that define workspaces, modes or component references can "mark" these items so that the system can perform automatic removal of these items when the module is uninstalled.

All three types of window system XML files may have module information defined in the same way, using the optional <module/> element, which has two attributes name and spec.

Normally a module will define just the name attribute, with its code name base (for example, org.domain.module). The windowing elements will be removed if the module thus named is uninstalled (or removed). name may also be a full code name (for example, org.domain.module/1) and spec may be given with the specification version (for example, 1.0) in which case the windowing element will also be removed if the module is present and enabled, but in an older version than was specified - though this feature is unlikely to be used commonly.

The window system may at its discretion just ignore XML files corresponding to missing modules; or it may actually delete them. In either case, removal of windowing components applies also to subcomponents: so for example a missing workspace implies that any contained modes are not available either, even if they otherwise would have been.

Visual Display

Defining layout for multiple interfaces of the system

System currently supports both Single Document Interface (SDI) and Multiple Document Interface (MDI). It's possible to define special property set for each interface using XML data files for window system elements.

Workspace, mode and top component reference data files all have required XML element named ui-type with attribute type, whose possible values are:

sdi
All properties specified inside "sdi" ui-type element will be applicable only in SDI mode and ignored for other interface modes.
mdi
All properties specified inside "mdi" ui-type element will be applicable only in MDI mode and ignored for other interface modes.
any
Marks properties as kind of default, applicable to any interface mode, including any modes which may be added in the future.
System uses trivial algorithm for finding right property set:
  1. Try to find special property set for the interface mode system is currently in. If search fails, continue, otherwise finish.
  2. Search for "any" property set. If found, use it, otherwise ignore given window system item.

Note that it's perfectly legal to define window system element to be active only for one type of interface mode. In fact, this technique is used in core's Editing workspace layout for the mode of property sheet - property sheet has its own mode only in SDI, in MDI it is part of explorer mode.

Playing with bounds of modes

Bounds of modes or mode areas can be specified either in absolute or relative form, and coordination space is different for SDI and MDI modes. Absolute bounds are specified in pixels, relative bounds are specified in percentage of area to which bounds are relative to.

Most convenient way for modules is to specify bounds in relative form, so that right layout is assured for various screen resolutions.

Bounds in SDI

Coordination space for modes in SDI is whole screen, so absolute bounds of modes are specified relative to the screen.

Relative bounds are relative to "working space of the workspace", which is defined as rectangular area which starts right below main window, down to bottom of the screen, width equal to screen width. If main window in SDI mode is placed to bottom part of the screen, then working space changes to occupy upper part of the screen above main window.

Bounds in MDI

Coordination space is inner part of main window, except toolbar area.

"working space of the workspace" is in MDI the same as coordination space, although there are some anomalies:

Bounds of mode areas

Bounds of mode areas are always relative to the mode itself, areas cannot exceed borders of their parent mode. Again it's wise to use bounds specified using percentage values.

Precise interpretation of these boundaries is left up to the window system implementation in case of significant future GUI changes, however the basic layout mechanism given here should continue to be honored.

Localization support

Candidates for localization are display names of workspaces and modes. Both kind of names are highly visible in running system, so if you expect that your module can be run under various locales (which is usual), you should assure right localization.

Localization support is represented by "name" elements in XML describing workspaces and modes. Name element for workspace is designed as follows:

Attribute unique
Represents non-localizable unique name of the workspace, which is used as identification of the workspace. Duplicate names are not allowed, system will refuse installation of workspace with duplicated name.
Attribute display
Localized display name. Usually contains string bundle key like CTL_My_Workspace_Name. Can also contain direct string display name in the case when attribute from-bundle is false (see below).
Attribute from-bundle
This attribute is a flag which can have values true and false. If true, then attribute display must contain string bundle key and attribute bundle must contain class name of the localization bundle.
Attribute bundle
Contains class name of the bundle used for localization of this workspace, for example org.netbeans.modules.mymodule.Bundle. This attribute is used only if attribute from-bundle has value true.

Name element for modes has the same syntax and semantics as for workspaces, just think of modes instead of workspaces.

Programmatic Manipulation

From the first look at XML workspace layout, module developer can think that it may be useful only for initial layout, but not during runtime.

That's not completely true, because workspace layout changes are saved into system file system of the IDE, under system/ subdirectory.

Modules can work with system file system at runtime using the File Systems API. Moreover, the window system defines special data objects for workspaces, modes, and top component references, so that workspace layout modifications can be done using Data Systems API on a higher level. Top component data is stored according to general settings mechanisms which also permits both modes of access.

Overall mechanism will behave like in desktops and menus in many current OS implementations. Desktop and menus content information is stored in files on persistent storage and there are basically two ways how to change the information. Either modify it from the GUI as the user (move icons on desktop etc.) or modify files on disk, which can be done by user or by application. Our approach here is quite similar, module writers can consider Data Systems API on system filesystem together with xml property files modification as specialized API for runtime access and modifications of workspace layout.

Reading into the Java-level API

A module is not permitted to read the entire window system configuration as this is a singleton.

Reading of top component data is handled by mechanisms specific to the Services API. For example, *.settings files will have an InstanceCookie which will produce the correct TopComponent.

Reading of workspace and mode data may be accomplished using InstanceCookie. Using the naming rules given previously, you might for example retrieve a mode as follows:

FileSystem sfs = TopManager.getDefault().getRepository().getDefaultFileSystem();
// Assume these two were known somehow:
String wsName = "Editing";
String modeName = "editor";
FileObject modeFolder = sfs.findResource("Windows/WindowManager/" + wsName + "/" + modeName);
// deal with null result...
// this would also work with the .wsmode file, but the folder is the primary file so:
DataObject modeObj = DataObject.find(modeFolder);
InstanceCookie instance = (InstanceCookie)modeObj.getCookie(InstanceCookie.class);
// instance could be null if .wsmode was missing...
Mode m = (Mode)instance.instanceCreate();
// now use it as desired...

Writing from the Java-level API

As for reading, you may not write the entire window to disk from module code; and handling of top component data is determined by the semantics of the data object providing that instance: for example, *.settings files may write new state at some point after receiving a property change event on the component.

For forcing saves of workspaces and modes, you may look for SaveCookie:

DataObject modeObj = /* as above */;
SaveCookie save = (SaveCookie)modeObj.getCookie(SaveCookie.class);
if (save != null) {
    save.save();
} else {
    // was already up-to-date on disk
}

You could also use calls such as DataObject.delete() to remove a mode or workspace, and so on.

Reading and writing at the XML level

You may alternately read or write the file objects constituting workspaces, modes, and top component references using the DTDs and file structures given above. Modification of top components at this level again is up to the semantics of the data object storing the top component.

Note that only file- and data object-level access is provided for top component references. Whether these have an instance cookie, and if so what it is an instance of, is undefined.

Synchronization issues

Currently there is no defined synchronization mechanism guaranteeing when changes made at the FileSystems level will be synchronized with those made at the Java API level. That is, changes in either direction will probably be made asynchronously according to the discretion of the window system implementation, so no module should rely on the exact timing. In fact the window system may decide to defer writing structural changes back to files until it is necessary (for example at project switch time or during IDE shutdown).

To improve safety, changes made at the file level ought to be run in a filesystem atomic action. An additional guard is to look for and use any existing SaveCookie immediately before reading or writing files, to force the system to synchronize pending changes. This leaves open possible race conditions.

Since there are not guarantees made about synchronization, reading and writing window system data procedurally should be done with heavy error-checking and may be considered an expert task. Normal modules should only declare initial setup using layers, and perform changes such as docking new top components in modes using the Java-level API.

Common Scenarios

Following chapter summarizes procedures that module can do in order to use features of XML workspace layout. Each procedure is detailed into steps which module writers are required or supposed to do.

Creating new workspace

Following section shows creation of new workspace defined by module. Workspace will be shown in workspace switcher component, if module will be installed.
  1. Specify module layer
    To use module layer properly, specify reference to module layer in module's manifest file using OpenIDE-Module-Layer entry, with relative patch to the xml file describing module's xml layer. In other words, add to the manifest line like this:

    OpenIDE-Module-Layer: org/your_company/your_module/resources/YourModuleLayer.xml
  2. Describe workspace
    Example of adding new workspace, with id "my_own_workspace", without any frames contained in workspace yet:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE filesystem PUBLIC
              "-//NetBeans//DTD Filesystem 1.0//EN"
              "http://www.netbeans.org/dtds/filesystem-1_0.dtd">
    <filesystem>
        <folder name="Windows">
            <folder name="WindowManager">
                <!-- configuration file is stored separately -->
                <file name="my_own_workspace.wswksp" url="my_own_workspace.wswksp"/>
                <folder name="my_own_workspace"/>
            </folder>
        </folder>
    </filesystem>
    

    What example shows? Creates folder Windows/WindowManager/my_own_workspace and inserts description of its properties into file Windows/WindowManager/my_own_workspace.xml, which has format defined in its DTD (see above). Folder name must be unique, fixed name for workspace identification.

Add frames to foreign workspaces

Suppose that your module would not want to create own workspace, but would like to insert own frame into workspace created by someone else. Foreign workspace unique name must be known for this. Following part of layer file inserts frame "map" into workspace "our_gis":
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC
          "-//NetBeans//DTD Filesystem 1.0//EN"
          "http://www.netbeans.org/dtds/filesystem-1_0.dtd">
<filesystem>
    <folder name="Windows">
        <folder name="WindowManager">
            <!-- no data file: this is only a reference, not a definition -->
            <!-- (the creator of the workspace supplied the data file already) -->
            <folder name="our_gis">
                <!-- configuration data file is stored separately -->
                <file name="map.wsmode" url="map.wsmode"/>
                <folder name="map"/>
            </folder>
        </folder>
    </folder>
</filesystem>

Add components to foreign frames

It's even possible to add own top components into frames defined by other modules or core. To add top component "shower" into frame "bathroom", specify layer like this (ser or settings file shower.ser or shower.settings is expected in folder Windows/Components):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC
          "-//NetBeans//DTD Filesystem 1.0//EN"
          "http://www.netbeans.org/dtds/filesystem-1_0.dtd">
<filesystem>
    <folder name="Windows">
        <folder name="WindowManager">
            <!-- foreign workspace, don't specify data file -->
            <folder name="house">
                <!-- foreign frame, don't specify data file -->
                <folder name="bathroom">
                    <!-- configuration file stored separately -->
                    <file name="shower.wstcref" url="shower.wstcref">
                </folder>
            </folder>
        </folder>
    </folder>
</filesystem>
There are several other notes that developer should be aware of:
  1. Don't insert configuration files directly using CDATA sections, keep them in separate files and refer through url (like in above examples). The reason is performance, CDATA sections are not maintained so efficiently by xml parsers system currently uses.
  2. Link modes and components together using names of files where data of top components are stored (see section 3.7). For example, if you create file "foo.setting" or "foo.ser" in Windows/Components directory for your top component, then use name "foo" in top component references.

Overriding components and modes defined by other modules

In some cases, it's useful to override mode and components layout defined by other module. To achieve this, the modules in question must have dependency defined.

So let's say we have following example situation:

  1. Two modules, A and B
  2. Module B depends on module A
  3. Module A defined its own workspace, called "a_workspace" with two modes "a_mode1" and "a_mode2". There is one component reference per mode, with names "a_comp1" (in mode 1) and "a_comp2" (in mode 2)
  4. Module B wants to override layout of "a_workspace", so that mode "a_mode1" will be replaced by its own mode and mode "a_mode2" will vanish.

Solution:
Module A needs no changes, module B has to assure following:

  1. Define mode "a_mode1" in foreign workspace "a_workspace", which will override properties of original mode specified in module A. Important thing is, that file name of new mode must be exactly the same as old one. Layered architecture of xml file system assures that file defined by module B will "hide" or "override" original file.
  2. Specify special "hiding" file for "a_comp1" in folder "a_workspace/a_mode1" so that it doesn't appear in module B's mode:
    <file name="a_comp1.wstcref_hidden" />
  3. Create own content of mode "a_mode1". It means create proper XML for top component references in "a_mode1" folder.
  4. Remove original mode "a_mode2" by specifying "hiding" file for mode itself:
    <file name="a_mode2.wsmode_hidden" />
    It's not needed to explicitly hide mode folder or mode components, because mode will not be recognized if *.wsmode file is missing, so hiding *.wsmode is enough.

Similar procedures can be applied when module needs to override only one top component reference entry or hide whole workspace.

Working with Output Tabs

A few other parts of the IDE use the Output Window to display messages from a running process or operation of some sort - for example, the progress of compilation, or status messages from the Java parser.

The Output Window is organized into tabs, each of which is capable of handling both input and output. TopManager.getIO(String) will get the InputOutput handle for one tab of the output window. Frequently, though, such a tab will be created by IDE implementation code and passed to your code automatically.

UML Diagrams

Overall structure class diagram

general UML

Input-Output class diagram

I/O UML

Built on December 12 2001.  |  Portions Copyright 1997-2001 Sun Microsystems, Inc. All rights reserved.