/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.windows;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.*;
import java.beans.*;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.Externalizable;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.lang.reflect.Method;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.text.Keymap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openide.ErrorManager;
import org.openide.awt.UndoRedo;
////import org.openide.loaders.*;
import org.openide.actions.*;
import org.openide.util.actions.SystemAction;
import org.openide.nodes.*;
import org.openide.util.ContextAwareAction;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.HelpCtx;
import org.openide.util.Utilities;
////import org.openide.modules.Dependency;
////import org.openide.modules.SpecificationVersion;
/** Embeddable visual component to be displayed in the IDE.
* This is the basic unit of display in the IDE--windows should not be
* created directly, but rather use this class.
* A top component may correspond to a single window, but may also
* be a tab (e.g.) in a window. It may be docked or undocked,
* have selected nodes, supply actions, etc.
*
* Important serialization note: Serialization of this TopComponent is designed
* in a way that it's not desired to override writeReplace method. If you would
* like to resolve to something, please implement readResolve() method directly
* on your top component.
*
* @author Jaroslav Tulach, Petr Hamernik, Jan Jancura
*/
public class TopComponent extends JComponent
implements Externalizable, Accessible, HelpCtx.Provider, Lookup.Provider {
/** generated Serialized Version UID */
static final long serialVersionUID = -3022538025284122942L;
private final static Log log = LogFactory.getLog(TopComponent.class);
/** Behavior in which a top component closed (by the user) in one workspace
* will be removed from <em>every</em> workspace.
* Also, {@link #close} is called.
* This is appropriate for top components such as Editor panes which
* the user expects to really close (and prompt to save) when closed
* in any workspace.
*/
public static final int CLOSE_EACH = 0;
/** Behavior in which a top component closed (by the user) in one workspace
* may be left in other workspaces.
* Only when the last remaining manifestation in any workspace is closed
* will the object be deleted using {@link #close}.
* Appropriate for components containing no user data, for which closing
* the component is only likely to result from the user's wanting to remove
* it from active view (on the current workspace).
*/
public static final int CLOSE_LAST = 1;
/** a set of actions of this component */
private static SystemAction[] DEFAULT_ACTIONS;
/** a lock for operations in default impl of getLookup */
private static Object defaultLookupLock = new Object ();
/** reference to Lookup with default implementation for the
* component
*/
private java.lang.ref.Reference defaultLookupRef = new WeakReference(null);
/** Listener to the data object's node or null */
private NodeName nodeName;
/** manager for the component */
private final WindowManager.Component manager;
/** constant for desired close operation */
private int closeOperation = CLOSE_LAST;
/** identification of serialization version
* Used in CloneableTopComponent readObject method.
*/
short serialVersion = 1;
/** Create a top component.
*/
public TopComponent () {
log.debug("using stand-alone GP TopComponent");
enableEvents (java.awt.AWTEvent.KEY_EVENT_MASK);
// there is no reason why a top component should have a focus
// => let's disable it
//// if (Dependency.JAVA_SPEC.compareTo(new SpecificationVersion("1.4")) >= 0) {
if (true) ////
try {
Method method = getClass().getMethod("setFocusable", new Class[] { Boolean.TYPE });
method.invoke(this, new Object[] { new Boolean(false) });
}
catch (Exception ex) {
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
}
//// } else {
//// setRequestFocusEnabled (false);
//// }
// request creating of our manager - it's here to avoid
// problems with recreating the connections between top components
// and their managers during deserialization
manager = WindowManager.getDefault().createTopComponentManager(this);
}
/** Create a top component associated with a data object.
* Currently the data object is used to set the component's name
* (which will be updated according to the object's node delegate) by
* installing NodeName inner class and attaching it to the node delegate.
*
* @param obj the data object
*/
//// public TopComponent (DataObject obj) {
//// this ();
//// Node n = obj.getNodeDelegate ();
////
//// nodeName = new NodeName (this);
//// nodeName.attach (n);
////
//// getAccessibleContext().setAccessibleDescription(n.getDisplayName());
//// }
/** Getter for class that allows obtaining of information about components.
* It allows to find out which component is selected, which nodes are
* currently or has been activated and list of all components.
*
* @return the registry of components
*/
public static final Registry getRegistry () {
return WindowManager.getDefault().getRegistry();
}
/** Get the set of activated nodes in this component.
* @return the activated nodes for this component
*/
public final Node[] getActivatedNodes () {
return getManager ().getActivatedNodes ();
}
/** Set the set of activated nodes in this component.
* @param nodes activated nodes for this component
*/
public final void setActivatedNodes (Node[] nodes) {
getManager ().setActivatedNodes (nodes);
firePropertyChange ("activatedNodes", null, null); // NOI18N
}
/** Get the undo/redo support for this component.
* The default implementation returns a dummy support that cannot
* undo anything.
*
* @return undoable edit for this component
*/
public UndoRedo getUndoRedo () {
return UndoRedo.NONE;
}
/** Show the component on current workspace.
* Note that this method only makes it visible, but does not
* give it focus. Implemented via call to open(null).
* @see #requestFocus
*/
public void open () {
open(null);
}
/** Show the component on given workspace. If given workspace is
* not active, component will be shown only after given workspace
* will become visible.
* Note that this method only makes it visible, but does not
* give it focus.
* @param workspace Workspace on which component should be opened.
* Parameter can be null -> means current workspace.
* @see #requestFocus
*/
public void open (Workspace workspace) {
getManager().open(workspace);
}
/** Finds out if this top component is opened at least on one workspace.
* @return true if given top component is opened on at least
* one workspace, false otherwise */
public final boolean isOpened () {
return getManager().whereOpened().size() > 0;
}
/** Finds out whether this top component is opened or not on specified
* workspace.
* @return true if given top component is opened on given workspace,
* false otherwise */
public final boolean isOpened (Workspace workspace) {
return getManager().whereOpened().contains(workspace);
}
public void addNotify() {
super.addNotify();
if (!(getParent() instanceof TopComponent)) {
//(TDB) Note: The constant below is defined for reference in NbTheme, but
//since NbTheme is in core, cannot create a dependancy.
setBorder (javax.swing.UIManager.getBorder ("nb.TopComponent.border"));
}
}
/** Closes the top component on current workspace.
* First asks canClose() method to see if it is
* possible to close now. If canClose() returns false, component will not
* be closed.
* Semantics of this method depends on top component's closeOperation
* state. If closeOperation is set to CLOSE_LAST (default), top component
* will be closed only on current workspace. If it is set to
* CLOSE_EACH, if will be closed on all workspaces at once.
*
* @return true if top component was succesfully closed, false if
* top component for some reason refused to close.
*/
public final boolean close () {
return close(WindowManager.getDefault().getCurrentWorkspace());
}
/** Closes the top component on given workspace, if closeOperation
* is set to CLOSE_LAST. If it is set to CLOSE_EACH, given parameter
* will be ignored and component will be closed on all workspaces
* at once.
*
* @param workspace Workspace on which component should be closed.
* @return true if top component was succesfully closed, false if
* top component for some reason refused to close.
*/
public final boolean close (Workspace workspace) {
Set whereOpened = getManager().whereOpened();
// don't close multiple times
if ((closeOperation != CLOSE_EACH) && !whereOpened.contains(workspace))
return true;
boolean result;
switch (closeOperation) {
case CLOSE_LAST:
result = canClose(workspace, whereOpened.size() == 1);
break;
case CLOSE_EACH:
result = canClose(null, true);
break;
default:
throw new IllegalStateException ("closeOperation=" + closeOperation); // NOI18N
}
if (result)
getManager().close(workspace);
return result;
}
/** This method is called when top component is about to close.
* Allows subclasses to decide if top component is ready for closing
* or not.<br>
* Default implementation always return true.
*
* @param workspace the workspace on which we are about to close or
* null which means that component will be closed
* on all workspaces where it is opened (CLOSE_EACH mode)
* @param last true if this is last workspace where top component is
* opened, false otherwise. If close operation is set to
* CLOSE_EACH, then this param is always true
* @return true if top component is ready to close, false otherwise.
*/
public boolean canClose (Workspace workspace, boolean last) {
return true;
}
/** Called only when top component was closed on all workspaces before and
* now is opened for the first time on some workspace. The intent is to
* provide subclasses information about TopComponent's life cycle across
* all existing workspaces.
* Subclasses will usually perform initializing tasks here.
* @deprecated Use {@link #componentOpened} instead. */
protected void openNotify () {
}
/** Called only when top component was closed so that now it is closed
* on all workspaces in the system. The intent is to provide subclasses
* information about TopComponent's life cycle across workspaces.
* Subclasses will usually perform cleaning tasks here.
* @deprecated Use {@link #componentClosed} instead.
*/
protected void closeNotify () {
}
/** Gets the system actions which will appear in the popup menu of this component.
* @return array of system actions for this component
* @deprecated Use {@link #getActions()} instead.
*/
public SystemAction[] getSystemActions () {
// lazy inicialization
synchronized(TopComponent.class) {
if (DEFAULT_ACTIONS == null) {
DEFAULT_ACTIONS = new SystemAction[] {
//// SystemAction.get(SaveAction.class),
SystemAction.get(CloneViewAction.class),
null,
SystemAction.get(CloseViewAction.class)
};
}
}
return DEFAULT_ACTIONS;
}
/** Gets the actions which will appear in the popup menu of this component.
* <p>Subclasses are encouraged to override this method to specify
* their own sets of actions.
* <p>Remember to call the super method when overriding and add your actions
* to the superclass' ones (in some order),
* because the default implementation provides support for standard
* component actions like save, close, and clone.
* @return array of actions for this component
* @since 3.32
*/
public javax.swing.Action[] getActions() {
return getSystemActions();
}
/** Set the close mode for the component.
* @param closeOperation one of {@link #CLOSE_EACH} or {@link #CLOSE_LAST}
* @throws IllegalArgumentException if an unrecognized close mode was supplied
* @see #close()
*/
public final void setCloseOperation (final int closeOperation) {
if ((closeOperation != CLOSE_EACH) && (closeOperation != CLOSE_LAST))
throw new IllegalArgumentException(
NbBundle.getBundle(TopComponent.class).getString("EXC_UnknownOperation")
);
if (this.closeOperation == closeOperation) return;
this.closeOperation = closeOperation;
firePropertyChange ("closeOperation", null, null); // NOI18N
}
/** Get the current close mode for this component.
* @return one of {@link #CLOSE_EACH} or {@link #CLOSE_LAST}
*/
public final int getCloseOperation () {
return closeOperation;
}
/** Called only when top component was closed on all workspaces before and
* now is opened for the first time on some workspace. The intent is to
* provide subclasses information about TopComponent's life cycle across
* all existing workspaces.
* Subclasses will usually perform initializing tasks here.
* @since 2.18 */
protected void componentOpened() {
openNotify();
}
/** Called only when top component was closed so that now it is closed
* on all workspaces in the system. The intent is to provide subclasses
* information about TopComponent's life cycle across workspaces.
* Subclasses will usually perform cleaning tasks here.
* @since 2.18 */
protected void componentClosed() {
closeNotify();
}
/** Called when <code>TopComponent</code> is about to be shown.
* Shown here means the component is selected or resides in it own cell
* in container in its <code>Mode</code>. The container is visible and not minimized.
* <p><em>Note:</em> component
* is considered to be shown, even its container window
* is overlapped by another window.</p>
* @since 2.18 */
protected void componentShowing() {
}
/** Called when <code>TopComponent</code> was hidden. <em>Nore</em>:
* <p><em>Note:</em> Beside typical situations when component is hidden,
* it is considered to be hidden even in that case
* the component is in <code>Mode</code> container hierarchy,
* the cointainer is visible, not minimized,
* but the component is neither selected nor in its own cell,
* i.e. it has it's own tab, but is not the selected one.
* @since 2.18 */
protected void componentHidden() {
}
/** Called when this component is activated.
* This happens when the parent window of this component gets focus
* (and this component is the preferred one in it), <em>or</em> when
* this component is selected in its window (and its window was already focussed).
* Remember to call the super method.
* The default implementation does nothing.
*/
protected void componentActivated () {
}
/** Called when this component is deactivated.
* This happens when the parent window of this component loses focus
* (and this component is the preferred one in the parent),
* <em>or</em> when this component loses preference in the parent window
* (and the parent window is focussed).
* Remember to call the super method.
* The default implementation does nothing.
*/
protected void componentDeactivated () {
}
/** Request focus for the window holding this top component.
* Also makes the component preferred in that window.
* The component will <em>not</em> be automatically {@link #open opened} first
* if it is not already.
* <p>Subclasses should override this method to transfer focus to desired
* focusable component. <code>TopComponent</code> itself is not focusable.
* See for example {@link org.openide.text.CloneableEditor#requestFocus}.
*/
public void requestFocus () {
getManager().requestFocus();
super.requestFocus();
}
/** Set this component visible but not selected or focused if possible.
* If focus is in other container (multitab) or other pane (split) in
* the same container it makes this component only visible eg. it selects
* tab with this component.
* If focus is in the same container (multitab) or in the same pane (split)
* it has the same effect as requestFocus().
*/
public void requestVisible () {
getManager().requestVisible();
}
/** Set the name of this top component.
* The default implementation just notifies the window manager.
* @param name the new display name
*/
public void setName (final String name) {
String old = getName();
if ((name != null) && (name.equals(old)))
return;
super.setName(name);
firePropertyChange("name", old, name);
getManager().nameChanged();
}
/** Sets toolTip for this <code>TopComponent</code>, adds notification
* about the change to its <code>WindowManager.TopComponentManager</code>. */
public void setToolTipText(String toolTip) {
if(toolTip != null && toolTip.equals(getToolTipText())) {
return;
}
super.setToolTipText(toolTip);
// XXX #19428. Container updates name and tooltip in the same handler.
getManager().nameChanged();
}
/** Set the icon of this top component.
* The icon will be used for
* the component's representation on the screen, e.g. in a multiwindow's tab.
* The default implementation just notifies the window manager.
* @param icon New components' icon.
*/
public void setIcon (final Image icon) {
getManager().setIcon(icon);
firePropertyChange ("icon", null, null); // NOI18N
}
/** @return The icon of the top component */
public Image getIcon () {
return getManager().getIcon();
}
/** Get the help context for this component.
* Subclasses should generally override this to return specific help.
* @return the help context
*/
public HelpCtx getHelpCtx () {
return new HelpCtx (TopComponent.class);
}
/** Allows top component to specify list of modes into which can be docked
* by end user. Subclasses should override this method if they want to
* alter docking policy of top component. <p>
* So for example, by returning empty list, top component refuses
* to be docked anywhere. <p>
* Default implementation allows docking anywhere by returning
* input list unchanged.
*
* @param modes list of {@link Mode} which represent all modes of current
* workspace, can contain nulls. Items are structured in logical groups
* separated by null entries. <p>
* Input array also contains special constant modes for docking
* into newly created frames. Their names are "SingleNewMode",
* "MultiNewMode", "SplitNewMode", can be used for their
* recognition. Please note that names and existence of special modes
* can change in future releases.
*
* @return list of {@link Mode} which are available for dock, can contain nulls
* @since 2.14
*/
public List availableModes (List modes) {
return modes;
}
/** Overrides superclass method, adds possible additional handling of global keystrokes
* in case this <code>TopComoponent</code> is ancestor of focused component. */
//// protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
//// int condition, boolean pressed) {
//// boolean ret = super.processKeyBinding(ks, e, condition, pressed);
////
//// // XXX #30189 Reason of overriding: to process global shortcut.
//// if(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT == condition
//// && ret == false && !e.isConsumed()
//////// && (Dependency.JAVA_SPEC.compareTo(new SpecificationVersion("1.4")) >= 0)
//// ) { // NOI18N
//// Keymap km = (Keymap)Lookup.getDefault().lookup(Keymap.class);
//// Action action = km.getAction(ks);
////
//// if(action == null) {
//// return false;
//// }
////
//// // If necessary create context aware instance.
//// if(action instanceof ContextAwareAction) {
//// action = ((ContextAwareAction)action).createContextAwareInstance(getLookup());
//// } else if(SwingUtilities.getWindowAncestor(e.getComponent())
//// instanceof java.awt.Dialog) {
//// // #30303 For 'old type' actions check the transmodal flag,
//// // if invoked in dialog. See ShorcutAndMenuKeyEventProcessor in core.
//// Object value = action.getValue("OpenIDE-Transmodal-Action"); // NOI18N
//// if(!Boolean.TRUE.equals(value)) {
//// return false;
//// }
//// }
////
//// // #30600 We have to use ActionManager, which replanes performation
//// // of action to another thread, since our actions rely on that.
////// return SwingUtilities.notifyAction(
////// action, ks, e, this, e.getModifiers());
//// ActionManager am = (ActionManager)Lookup.getDefault().lookup(ActionManager.class);
//// am.invokeAction(
//// action,
//// new ActionEvent(this, ActionEvent.ACTION_PERFORMED, Utilities.keyToString(ks))
//// );
////
//// return true;
//// } else {
//// return ret;
//// }
//// }
/** Getter for manager for this component. This manager allows to
* control where is the component shown can be used to destroy and show the
* component, etc.
*/
final WindowManager.Component getManager () {
return manager;
}
/** Serialize this top component.
* Subclasses wishing to store state must call the super method, then write to the stream.
* @param out the stream to serialize to
*/
public void writeExternal (ObjectOutput out)
throws IOException {
out.writeObject(new Short (serialVersion));
out.writeInt (closeOperation);
out.writeObject (getName());
out.writeObject (getToolTipText());
Node.Handle h = nodeName == null ? null : nodeName.node.getHandle ();
out.writeObject(h);
}
/** Deserialize this top component.
* Subclasses wishing to store state must call the super method, then read from the stream.
* @param in the stream to deserialize from
*/
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException {
throw new IOException("GP: restore not supported"); ////
//// Object firstObject = in.readObject ();
//// if (firstObject instanceof Integer) {
//// // backward compatibility read
//// serialVersion = 0;
////
//// closeOperation = ((Integer)firstObject).intValue();
//// DataObject obj = (DataObject)in.readObject();
////
//// super.setName((String)in.readObject());
//// setToolTipText((String)in.readObject());
////
//// // initialize the connection to a data object
//// if (obj != null) {
//// nodeName = new NodeName (this);
//// nodeName.attach (obj.getNodeDelegate ());
//// }
//// } else {
//// // new serialization
//// serialVersion = ((Short)firstObject).shortValue ();
////
//// closeOperation = in.readInt ();
//// super.setName ((String)in.readObject ());
//// setToolTipText ((String)in.readObject ());
////
//// Node.Handle h = (Node.Handle)in.readObject ();
//// if (h != null) {
//// Node n = h.getNode ();
//// nodeName = new NodeName (this);
//// nodeName.attach (n);
//// }
//// }
//// if (closeOperation != CLOSE_EACH && closeOperation != CLOSE_LAST) {
//// throw new IOException ("invalid closeOperation: " + closeOperation); // NOI18N
//// }
}
/** Delegates instance of replacer class to be serialized instead
* of top component itself. Replacer class calls writeExternal and
* constructor, readExternal and readResolve methods properly, so
8 any top component can behave like any other externalizable object.
* Subclasses can override this method to perform their
* serialization differentrly */
protected Object writeReplace () throws ObjectStreamException {
return new Replacer(this);
}
/** Each top component that wishes to be cloned should implement
* this interface, so CloneAction can check it and call the cloneComponent
* method.
*/
public static interface Cloneable {
/** Creates a clone of this component
* @return cloned component.
*/
public TopComponent cloneComponent ();
}
/* Read accessible context
* @return - accessible context
*/
public AccessibleContext getAccessibleContext () {
if(accessibleContext == null) {
accessibleContext = new AccessibleJComponent() {
public AccessibleRole getAccessibleRole() {
return AccessibleRole.PANEL;
}
public String getAccessibleName() {
if (accessibleName != null) {
return accessibleName;
}
return getName();
}
/* Fix for 19344: Null accessible decription of all TopComponents on JDK1.4 */
public String getToolTipText() {
return TopComponent.this.getToolTipText();
}
};
}
return accessibleContext;
}
/** Gets lookup which represents context of this component. By default
* the lookup delegates to result of <code>getActivatedNodes</code>
* method and result of this component <code>ActionMap</code> delegate.
*
* @return a lookup with designates context of this component
* @see org.openide.util.ContextAwareAction
* @see org.openide.util.Utilities#actionsToPopup(Action[], Lookup)
* @since 3.29
*/
public Lookup getLookup() {
synchronized (defaultLookupLock) {
Object l = defaultLookupRef.get ();
if (l instanceof Lookup) {
return (Lookup)l;
}
Lookup lookup = new ProxyLookup(new Lookup[] {
new DefaultTopComponentLookup (this), // Lookup of activated nodes.
Lookups.singleton(new DelegateActionMap(this)) // Action map lookup.
});
defaultLookupRef = new java.lang.ref.WeakReference (lookup);
return lookup;
}
}
/** This class provides the connection between the node name and
* a name of the component.
*/
public static class NodeName extends NodeAdapter {
/** weak reference to the top component */
private transient Reference top;
/** node we are attached to or null */
private transient Node node;
/** Constructs new name adapter that
* can be attached to any node and will listen on changes
* of its display name and modify the name of the component.
*
* @param top top compoonent to modify its name
*/
public NodeName (TopComponent top) {
this.top = new WeakReference (top);
}
/** Attaches itself to a given node.
*/
final void attach (Node n) {
TopComponent top = (TopComponent)this.top.get ();
if (top != null) {
synchronized (top) {
// ok no change
if (n == node) return;
// change the node we are attached to
if (node != null) {
node.removeNodeListener (this);
}
node = n;
if (n != null) {
n.addNodeListener (this);
top.setActivatedNodes (new Node[] { n });
top.setName (n.getDisplayName ());
}
}
}
}
/** Listens to Node.PROP_DISPLAY_NAME.
*/
public void propertyChange(PropertyChangeEvent ev) {
TopComponent top = (TopComponent)this.top.get ();
if (top == null) {
// stop listening if top component no longer exists
if (ev.getSource () instanceof Node) {
Node n = (Node)ev.getSource ();
n.removeNodeListener (this);
}
return;
}
// ensure we are attached
attach (node);
if (ev.getPropertyName ().equals (Node.PROP_DISPLAY_NAME)) {
top.setName (node.getDisplayName());
}
}
} // end of NodeName
/** Registry of all top components.
* There is one instance that can be obtained via {@link TopComponent#getRegistry}
* and it permits listening to the currently selected element, and to
* the activated nodes assigned to it.
*/
public static interface Registry {
/** Name of property for the set of opened components. */
public static final String PROP_OPENED = "opened"; // NOI18N
/** Name of property for the selected top component. */
public static final String PROP_ACTIVATED = "activated"; // NOI18N
/** Name of property for currently selected nodes. */
public static final String PROP_CURRENT_NODES = "currentNodes"; // NOI18N
/** Name of property for lastly activated nodes nodes. */
public static final String PROP_ACTIVATED_NODES = "activatedNodes"; // NOI18N
/** Get reference to a set of all opened componets in the system.
*
* @return live read-only set of {@link TopComponent}s
*/
public Set getOpened ();
/** Get the currently selected element.
* @return the selected top component, or <CODE>null</CODE> if there is none
*/
public TopComponent getActivated ();
/** Getter for the currently selected nodes.
* @return array of nodes or null if no component activated or it returns
* null from getActivatedNodes ().
*/
public Node[] getCurrentNodes ();
/** Getter for the lastly activated nodes. Comparing
* to previous method it always remembers the selected nodes
* of the last component that had ones.
*
* @return array of nodes (not null)
*/
public Node[] getActivatedNodes();
/** Add a property change listener.
* @param l the listener to add
*/
public void addPropertyChangeListener (PropertyChangeListener l);
/** Remove a property change listener.
* @param l the listener to remove
*/
public void removePropertyChangeListener (PropertyChangeListener l);
}
/** Instance of this class is serialized instead of TopComponent itself.
* Emulates behaviour of serialization of externalizable objects
* to keep TopComponent serialization compatible with previous versions. */
private static final class Replacer implements Serializable {
/** SUID */
static final long serialVersionUID=-8897067133215740572L;
/** Asociation with top component which is to be serialized using
* this replacer */
transient TopComponent tc;
public Replacer (TopComponent tc) {
this.tc = tc;
}
private void writeObject (ObjectOutputStream oos)
throws IOException, ClassNotFoundException {
// write the name of the top component first
oos.writeObject(tc.getClass().getName());
// and now let top component to serialize itself
tc.writeExternal(oos);
}
private void readObject (ObjectInputStream ois)
throws IOException, ClassNotFoundException {
// read the name of top component's class, instantiate it
// and read its attributes from the stream
String name = (String)ois.readObject();
name = org.openide.util.Utilities.translate(name);
try {
Class tcClass = Class.forName(
name,
true,
(ClassLoader)Lookup.getDefault().lookup(ClassLoader.class)
);
// instantiate class event if it has protected or private
// default constructor
java.lang.reflect.Constructor con = tcClass.getDeclaredConstructor(new Class[0]);
con.setAccessible(true);
try {
tc = (TopComponent)con.newInstance(new Object[0]);
} finally {
con.setAccessible(false);
}
tc.readExternal(ois);
// call readResolve() if present and use resolved value
Method resolveMethod = findReadResolveMethod(tcClass);
if (resolveMethod != null) {
// check exceptions clause
Class[] result = resolveMethod.getExceptionTypes();
if ((result.length == 1) &&
ObjectStreamException.class.equals(result[0])) {
// returned value type
if (Object.class.equals(resolveMethod.getReturnType())) {
// make readResolve accessible (it can have any access modifier)
resolveMethod.setAccessible(true);
// invoke resolve method and accept its result
try {
TopComponent unresolvedTc = tc;
tc = (TopComponent)resolveMethod.invoke(tc, new Class[0]);
if (tc == null) {
throw new java.io.InvalidObjectException(
"TopComponent.readResolve() cannot return null." // NOI18N
+ " See http://www.netbeans.org/issues/show_bug.cgi?id=27849 for more info." // NOI18N
+ " TopComponent:" + unresolvedTc); // NOI18N
}
} finally {
resolveMethod.setAccessible(false);
}
}
}
}
} catch (ClassNotFoundException exc) {
//Bugfix #16408: Ignore missing classes from objectbrowser and icebrowser module
if ((exc.getMessage().indexOf("org.netbeans.modules.objectbrowser") != -1) ||
(exc.getMessage().indexOf("org.netbeans.modules.icebrowser") != -1)) {
tc = null;
} else {
// turn all troubles into IOException
IOException newEx = new IOException(exc.getMessage());
ErrorManager.getDefault().annotate(newEx, exc);
throw newEx;
}
} catch (Exception exc) {
Throwable th = exc;
// Extract target exception.
if(th instanceof InvocationTargetException) {
th = ((InvocationTargetException)th).getTargetException();
}
// IOException throw directly.
if(th instanceof IOException) {
throw (IOException)th;
}
// All others wrap into IOException.
IOException newEx = new IOException(th.getMessage());
ErrorManager.getDefault().annotate(newEx, th);
throw newEx;
}
}
/** Resolve to original top component instance */
private Object readResolve () throws ObjectStreamException {
return tc;
}
/** Tries to find readResolve method in given class. Finds
* both public and non-public occurences of the method and
* searches also in superclasses */
private static Method findReadResolveMethod (Class clazz) {
Method result = null;
// first try public occurences
try {
result = clazz.getMethod("readResolve", new Class[0]); // NOI18N
} catch (NoSuchMethodException exc) {
// public readResolve does not exist
}
// now try non-public occurences; search also in superclasses
for (Class i = clazz; i != null; i = i.getSuperclass()) {
try {
result = i.getDeclaredMethod("readResolve", new Class[0]); // NOI18N
// get out of cycle if method found
break;
} catch (NoSuchMethodException exc) {
// readResolve does not exist in current class
}
}
return result;
}
} // end of Replacer inner class
}