org.openide.windows
package. Most module authors will want to look at
TopComponent
to subclass it, or perhaps at
CloneableTopComponent
.
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.
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; and in the future, possibly, other arrangements (i.e. split-windows), or even full MDI. 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.
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.
There are a few general ways in which you can customize the
component besides painting subcomponents on it:
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.
Creating a Top Component
Creating a custom top component, including assigning it to the
proper docking mode, is not generally difficult.
Subclassing
To create a simple top component, it suffices to subclass
TopComponent
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.
TopComponent.getHelpCtx()
should be overridden to provide a help link for the component as a
whole, if you have any context help. Or, subcomponents may have their
own more specific help. By default, context help is taken from the activated
node selection, if there is any to be found.
TopComponent.getUndoRedo()
allows you to associate undo/redo actions with the component, so
that these actions will work properly when your component is
focussed.
TopComponent.getSystemActions()
permits you to provide a list of actions which will appear in a
popup menu, e.g. on a tab for the component. The system should
provide a few standard actions such as "Close"; you may want to add
more (for example, "Save" for an Editor pane). The
Actions API
describes how to write these.
new TopComponent(DataObject)
should be used when the component is intended to act as some sort
of editor or viewer for a data object. If you do this, then the
node corresponding to this data object will be considered selected
in the top component (unless you change the selection), and the top
component will automatically name itself according to the current
node delegate's name.
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:
private static final String WIDGET_MODE_NAME = "MyWidgets"; Workspace ws = TopManager.getDefault ().getWindowManager ().getCurrentWorkspace (); Mode myMode = ws.findMode (WIDGET_MODE_NAME); if (myMode == null) myMode = ws.createMode (WIDGET_MODE_NAME, "Widgets", getClass ().getClassLoader ().getResource ("/com/mycom/widgets/modeIcon.gif")); class MyComponent extends TopComponent { public MyComponent (Object data, Image icon) { setName ("Widget Tree"); setIcon (icon); setLayout (new BorderLayout ()); add (new JTree (createModel (data)), BorderLayout.CENTER); } public SystemAction[] getSystemActions () { SystemAction[] default = super.getSystemActions (); SystemAction[] added = new SystemAction[] { SystemAction.getAction (WidgetReparseAction.class) }; return SystemAction.linkActions (default, added); } } TopComponent myComponent1 = new MyComponent (firstData, firstIcon); myMode.dockInto (myComponent); myComponent.open (); TopComponent myComponent2 = new MyComponent (secondData, secondIcon); myMode.dockInto (myComponent2); myComponent2.open (); myComponent2.requestFocus ();
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.
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
One important option to exercise is to call
You can explicitly request that a top component be activated
(and its containing window focussed) by calling
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
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.
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.
TopComponent.setCloseOperation(...)
(say in the component's constructor). Using
TopComponent.CLOSE_EACH
,
the top component will be closed completely (using
TopComponent.close()
)
on every workspace if any instance of it is closed. 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.
TopComponent.requestFocus()
.
(It should be opened first.)
TopComponent.getRegistry()
and then
TopComponent.Registry.getActivated()
correspondingly finds the last-activated component.
TopComponent.componentActivated()
and
TopComponent.componentDeactivated()
--typically
these are used to enable or disable actions. Most importantly, certain actions such as
CopyAction
or
FindAction
are
CallbackSystemAction
s,
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
Node
s
that it decides to treat as "active". The node selection will have
effects on other parts of the system--for example,
NodeAction
s
and
CookieAction
s
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) { // e.printStackTrace (); 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) { // e.printStackTrace (); } } }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) { // e.printStackTrace (); 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 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 a public default constructor, and also in the It is possible to use To close a top component programmatically, call
The Output Window is organized into tabs, each of which is
capable of handling both input and output.
TopComponent
must have a public default
constructor in order to be deserialized.
readExternal
method.
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
.
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:
WindowManager
, which is obtained by
TopManager.getWindowManager()
.
Then the calls
WindowManager.getCurrentWorkspace()
,
WindowManager.findWorkspace(String)
,
and
WindowManager.getWorkspaces()
may be used.
Workspace.getModes()
or
Workspace.findMode(String)
(or also
Workspace.findMode(TopComponent)
).
Mode.getTopComponents()
.
TopComponent.getRegistry()
followed by
TopComponent.Registry.getActivated()
.
You can also get all opened components with
TopComponent.Registry.getOpened()
.
Giving focus and closing
Any top component may be given focus just by calling
TopComponent.requestFocus()
.
TopComponent.close()
.
Listening to window system events
Most of 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.
WindowManager.addPropertyChangeListener(PropertyChangeListener)
.
TopComponent.Registry.addPropertyChangeListener(PropertyChangeListener)
.
Workspace.addPropertyChangeListener(PropertyChangeListener)
.
Mode
nor in TopComponent
, though both
have modifiable properties which in principle there might be a need
to listen to.
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.
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
Input-Output class diagram
Built on February 22 2001. | Portions Copyright 1997-2000 Sun Microsystems, Inc. All rights reserved.