org.openide.windows.TopComponent Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.windows;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.swing.*;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.Keymap;
import org.openide.awt.ActionID;
import org.openide.awt.Actions;
import org.openide.awt.UndoRedo;
import org.openide.nodes.Node;
import org.openide.nodes.NodeAdapter;
import org.openide.nodes.NodeListener;
import org.openide.util.*;
import org.openide.util.actions.NodeAction;
import org.openide.util.actions.SystemAction;
/**
* Embeddable visual component to be displayed in NetBeans.
* This is the basic unit of display--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 {
/** UI logger to notify about invocation of an action */
private static Logger UILOG = Logger.getLogger("org.netbeans.ui.actions"); // NOI18N
/** generated Serialized Version UID */
static final long serialVersionUID = -3022538025284122942L;
/** top component logger */
static final Logger LOG = Logger.getLogger(TopComponent.class.getName());
/** Behavior in which a top component closed (by the user) in one workspace
* will be removed from every 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
* @deprecated Do not use. It is redundant since workspaces are not supported anymore. */
@Deprecated
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).
* @deprecated Do not use. It is redundant since workspaces are not supported anymore. */
@Deprecated
public static final int CLOSE_LAST = 1;
/** Persistence type of TopComponent instance. TopComponent is persistent. */
public static final int PERSISTENCE_ALWAYS = 0;
/** Persistence type of TopComponent instance. TopComponent is persistent only when
* it is opened in Mode. */
public static final int PERSISTENCE_ONLY_OPENED = 1;
/** Persistence type of TopComponent instance. TopComponent is not persistent. */
public static final int PERSISTENCE_NEVER = 2;
/** a lock for operations in default impl of getLookup */
private static Object defaultLookupLock = new Object();
/** Classes that have been warned about overriding preferredID() */
private static final Set warnedTCPIClasses = new WeakSet();
/** Used to print warning about getPersistenceType */
private static final Set warnedClasses = new WeakSet();
/** reference to Lookup with default implementation for the
* component or the lookup associated with the component itself
*/
private Object defaultLookupRef;
/** Holds support for sync with node display name or null */
private NodeName nodeName;
// Do not use, deprecated.
/** constant for desired close operation */
private int closeOperation = CLOSE_LAST;
/** Icon of this TopComponent
*/
private transient Image icon;
/** Activated nodes of this TopComponent
. */
private transient Node[] activatedNodes;
/** Localized display name of this TopComponent
. */
private transient String displayName;
/** Holds localized display name of this TopComponent
in html syntax,
* or null if not needed */
private String htmlDisplayName;
/** identification of serialization version
* Used in CloneableTopComponent readObject method.
*/
short serialVersion = 1;
private AttentionGetter attentionGetter = null;
/**
* Name of TopComponent's Boolean client property which forces the window system
* to respect TopComponent's preferred size when it is slided-in from left/right/bottom
* sliding bar when set to Boolean.TRUE. Otherwise the slided-in TopComponent
* will fill the entire width/length of the IDE window (the default behavior).
* This switch is intended for tools/palette windows like e.g. color chooser,
* tool picker etc.
* @since 6.26
*/
public static final String PROP_KEEP_PREFERRED_SIZE_WHEN_SLIDED_IN = "netbeans.winsys.tc.keep_preferred_size_when_slided_in"; //NOI18N
/**
* Name of TopComponent's Boolean client property which removes close button
* from TopComponent's header and disables its 'Close Window' action when set to Boolean.TRUE.
* @since 6.26
*/
public static final String PROP_CLOSING_DISABLED = "netbeans.winsys.tc.closing_disabled"; //NOI18N
/**
* Name of TopComponent's Boolean client property which removes 'minimized' button
* from TopComponent's header and disables its 'Minimize Window' action when
* set to Boolean.TRUE. TopComponents which are already slided-out and have this
* property set to Boolean.TRUE will have 'pin' button removed from their header
* and their 'Minimize Window' action will be disabled.
* @since 6.26
*/
public static final String PROP_SLIDING_DISABLED = "netbeans.winsys.tc.sliding_disabled"; //NOI18N
/**
* Name of TopComponent's Boolean client property which disables TopComponent's
* 'Undock Window' action when set to Boolean.TRUE. TopComponents which are already
* floating and have this property set to Boolean.TRUE will have their 'Dock Window' action disabled.
* @since 6.26
*/
public static final String PROP_UNDOCKING_DISABLED = "netbeans.winsys.tc.undocking_disabled"; //NOI18N
/**
* Name of TopComponent's Boolean client property which disables drag and drop
* when set to Boolean.TRUE.
* @since 6.26
*/
public static final String PROP_DRAGGING_DISABLED = "netbeans.winsys.tc.dragging_disabled"; //NOI18N
/**
* Name of TopComponent's Boolean client property which disables TopComponent
* maximization by double-clicking its header when set to Boolean.TRUE. TopComponent's
* 'Maximize Window' action will be also disabled.
* @since 6.26
*/
public static final String PROP_MAXIMIZATION_DISABLED = "netbeans.winsys.tc.maximization_disabled"; //NOI18N
/**
* Name of TopComponent's Boolean client property which disables 'copy' drag and drop action
* of this TopComponent when set to Boolean.TRUE.
* @since 6.48
*/
public static final String PROP_DND_COPY_DISABLED = "netbeans.winsys.tc.draganddrop_copy_disabled"; //NOI18N
private transient String modeName;
/** Create a top component.
*/
public TopComponent() {
this((Lookup) null);
}
/** Creates a top component for a provided lookup that will delegate
* take and synchronize activated nodes and ActionMap from a provided
* lookup. The lookup will also be returned from {@link #getLookup} method,
* if not overriden.
*
* @param lookup the lookup to associate with
* @since 4.19
*/
public TopComponent(Lookup lookup) {
if (lookup != null) {
setLookup(lookup, true);
}
enableEvents(java.awt.AWTEvent.KEY_EVENT_MASK);
// #27731 TopComponent itself shouldn't get the focus.
// XXX What to do in case nothing in TopComponent is focusable?
setFocusable(false);
initActionMap(lookup);
}
private static final String MODE_ID_PREFERENCES_KEY_INFIX = "_modeId_"; //NOI18N
@Override
public void addNotify() {
super.addNotify();
if (isPersistLocation()) {
Mode m = WindowManager.getDefault().findMode(this);
if (m != null) {
modeName = m.getName();
if (modeName == null) {
modeName = getClass().getAnnotation(
RetainLocation.class).value();
}
NbPreferences.forModule(getClass()).put(getModeIdKey(), modeName);
}
}
}
private boolean isPersistLocation() {
boolean result = getPersistenceType() == PERSISTENCE_NEVER &&
getClass().getAnnotation(RetainLocation.class) != null;
assert annotationAndPersistenceTypeAreCompatible();
return result;
}
private boolean annotationAndPersistenceTypeAreCompatible() {
if (getPersistenceType() != PERSISTENCE_NEVER &&
getClass().getAnnotation(RetainLocation.class) != null) {
Logger.getLogger(TopComponent.class.getName()).log(Level.WARNING,
"Useless to annotate a TopComponent with @RetainLocation if " + //NOI18N
"its persistence type is not PERSISTENCE_NEVER: {0}", //NOI18N
new Object[] { getClass().getName() });
}
return true;
}
private String getModeIdKey() {
return getClass().getName() + MODE_ID_PREFERENCES_KEY_INFIX +
WindowManager.getDefault().findTopComponentID(this);
}
// It is necessary so the old actions (clone and close from org.openide.actions package) remain working.
/** Initialized ActionMap
of this TopComponent
.
* @since 4.13 */
private void initActionMap(Lookup lookup) {
ActionMap inner = null;
if (lookup != null) {
inner = lookup.lookup(ActionMap.class);
}
if (inner == null) {
inner = new ActionMap();
}
DelegateActionMap am = new DelegateActionMap(this, inner);
if (this instanceof TopComponent.Cloneable) {
am.put(
"cloneWindow", // NOI18N
new CloneWindowAction(am)
);
}
am.put(
"closeWindow", // NOI18N
new CloseWindowAction(am)
);
setActionMap(am);
}
/** 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 or null
, null
* means such component does not change {@link Registry#getActivatedNodes()} just
* {@link Registry#getCurrentNodes()} when this component gets activated */
public final Node[] getActivatedNodes() {
return activatedNodes;
}
/** Set the set of activated nodes in this component.
* @param activatedNodes activated nodes for this component
*/
public final void setActivatedNodes(Node[] activatedNodes) {
assert multiviewActivatedNodes();
setActivatedNodesImpl(activatedNodes);
}
private boolean multiviewActivatedNodes() {
if ("org.netbeans.core.multiview.MultiViewTopComponent".equals(this.getClass().getName()) || //NOI18N
"org.netbeans.core.multiview.MultiViewCloneableTopComponent".equals(this.getClass().getName())) { //NOI18N
LOG.info("Warning: You should not call setActivatedNodes()" +//NOI18N
" on the multiview topcomponents. Instead please manipulate the lookup of the embedded MultiViewElements." +//NOI18N
" For details, please see http://www.netbeans.org/issues/show_bug.cgi?id=67257");//NOI18N
}
return true;
}
private void setActivatedNodesImpl(Node[] activatedNodes) {
boolean l = LOG.isLoggable(Level.FINER);
if (Arrays.equals(this.activatedNodes, activatedNodes)) {
if (l) {
LOG.finer("No change to activatedNodes for " + this); // NOI18N
}
return;
}
Lookup lookup = getLookup(false);
if (lookup instanceof DefaultTopComponentLookup) {
if (l) {
LOG.finer("Updating lookup " + lookup + " for " + this); // NOI18N
}
((DefaultTopComponentLookup) lookup).updateLookups(activatedNodes);
}
Node[] old = this.activatedNodes;
this.activatedNodes = activatedNodes;
if (l) {
LOG.finer("activatedNodes changed: " + (activatedNodes == null ? "" : Arrays.asList(activatedNodes).toString())); // NOI18N
}
// notify all that are interested...
WindowManager.getDefault().topComponentActivatedNodesChanged(this, this.activatedNodes);
if (l) {
LOG.finer("window manager notified: " + this); // NOI18N
}
firePropertyChange("activatedNodes", old, this.activatedNodes); // NOI18N
if (l) {
LOG.finer("listeners notified: " + this); // NOI18N
}
}
/**
* Rather than overriding this method, consider using {@link Description}.
* Overwrite when you want to change default persistence type. Default
* persistence type is PERSISTENCE_ALWAYS.
* Return value should be constant over a given TC's lifetime.
* @return one of P_X constants
* @since 4.20
*/
public int getPersistenceType() {
Description info = getClass().getAnnotation(Description.class);
if (info != null) {
return info.persistenceType();
}
//First check for 'PersistenceType' client property for compatibility.
if (warnedClasses.add(getClass()) && !TopComponent.class.equals(getClass())) {
Logger.getAnonymousLogger().warning(
"Note - " // NOI18N
+getClass().getName() + " ought to override getPersistenceType()" // NOI18N
+" rather than using the client property or accepting the default."
); // NOI18N
}
String propValue = (String) getClientProperty("PersistenceType"); // NOI18N
if (propValue == null) {
return PERSISTENCE_ALWAYS;
} else if ("Never".equals(propValue)) { // NOI18N
return PERSISTENCE_NEVER;
} else if ("OnlyOpened".equals(propValue)) { // NOI18N
return PERSISTENCE_ONLY_OPENED;
} else {
return PERSISTENCE_ALWAYS;
}
}
/** 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;
}
/** Shows this TopComponent
.
* Note: This method only makes it visible, but does not
* activates it.
* @see #requestActive */
public void open() {
open(null);
}
/** Shows this TopComponent
in current workspace.
* Node: Currently workspaces are not supported. The method has the same effect
* like {@link #open()}.
* @deprecated Use {@link #open()} instead. */
@Deprecated
public void open(Workspace workspace) {
if (isPersistLocation()) {
modeName = NbPreferences.forModule(getClass()).get(getModeIdKey(), null);
if (modeName == null) {
modeName = getClass().getAnnotation(
RetainLocation.class).value();
}
Mode mode = WindowManager.getDefault().findMode(modeName);
if (mode != null) {
mode.dockInto(this);
}
}
WindowManager.getDefault().topComponentOpen(this);
}
/** Opens TopComponent at given position in the mode. TopComponent is inserted at given
* position, positions of already opened TopComponents in the same mode are
* incremented.
*
*
* - Does no operation if this TopComponent is already opened.
* - For position value less then 0, TopComponent is opened at position 0, the very first one.
* - For position value greater then count of opened TopComponents in the mode,
* TopComponent is opened at last position
*
*
* @param position Index of the requested position.
* @since 6.15
*/
public final void openAtTabPosition (int position) {
WindowManager.getDefault().topComponentOpenAtTabPosition(this, position);
}
/** Gives position index of opened TopComponent in the mode.
*
* For closed TopComponents, position value less then zero is returned.
*
* @return Index of position.
* @since 6.15
*/
public final int getTabPosition () {
return WindowManager.getDefault().topComponentGetTabPosition(this);
}
/** Indicates whether this TopComponent
is opened.
* @return true if given top component is opened, false otherwise */
public final boolean isOpened() {
return isOpened(null);
}
/** Indicates whether this TopComponent
is opened in current workspace.
* Node: Currently workspaces are not supported. The method has the same effect
* like {@link #isOpened()}.
* @deprecated Use {@link #isOpened()} instead. */
@Deprecated
public final boolean isOpened(Workspace workspace) {
return WindowManager.getDefault().topComponentIsOpened(this);
}
/** Closes this TopComponent
.
* @return true if top component was succesfully closed, false if
* top component for some reason refused to close. */
public final boolean close() {
return close(null);
}
/** Closes this TopComponent
in current workspace.
* Node: Currently workspaces are not supported. The method has the same effect
* like {@link #close()}.
* @deprecated Use {@link #close()} instead. */
@Deprecated
public final boolean close(Workspace workspace) {
if (!isOpened()) {
return true;
}
WindowManager.getDefault().topComponentClose(this);
return !isOpened();
}
/** This method is called when this TopComponent
is about to close.
* Allows subclasses to decide if TopComponent
is ready to close.
* @since 4.13 */
public boolean canClose() {
if (!isOpened()) {
return false;
}
return canClose(null, true);
}
/** This method is called when top component is about to close.
* Allows subclasses to decide if top component is ready for closing
* or not.
* 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.
* @deprecated Do not use anymore. Use {@link #canClose()} instead.
* Both parameters are redundant since workspaces are not supported anymore. */
@Deprecated
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. */
@Deprecated
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.
*/
@Deprecated
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.
*/
@Deprecated
public SystemAction[] getSystemActions() {
return new SystemAction[0];
}
/** Gets the actions which will appear in the popup menu of this component.
* Subclasses are encouraged to override this method to specify
* their own sets of actions.
*
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() {
Action[] actions = WindowManager.getDefault().topComponentDefaultActions(this);
SystemAction[] sysActions = getSystemActions();
// If there are some sys actions (i.e. the subclass overrided the defautl impl) add them.
if (sysActions.length > 0) {
List acs = new ArrayList(Arrays.asList(actions));
acs.addAll(Arrays.asList(sysActions));
return acs.toArray(new Action[0]);
} else {
return actions;
}
}
/** Creates an action that opens and activates given TopComponent
,
* when invoked. This method can also be used from
* XML Layer
* directly by following XML definition:
*
* <file name="your-pkg-action-id.instance">
* <attr name="instanceCreate" methodvalue="org.openide.windows.TopComponent.openAction"/>
* <attr name="component" methodvalue="your.pkg.YourComponent.factoryMethod"/>
* <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/>
* <attr name="iconBase" stringvalue="your/pkg/YourComponent.png"/>
* <!-- if desired: <attr name="noIconInMenu" boolvalue="false"/> -->
* <!-- if desired: <attr name="preferredID" stringvalue="id.of.your.tc"/> -->
* </file>
*
* The preferredID
attribute is supported since version
* 6.37. If specified the action first seeks for existing preferredID
* component and if found, it opens and activates it.
*
* Rather than doing all this in XML, consider using {@link OpenActionRegistration}.
*
*
* @param component the component to open
* @param displayName the display name of the action
* @param iconBase the location to the actions icon
* @param noIconInMenu true if this icon shall not have an item in menu
* @see OpenActionRegistration
*
* @since 6.24
*/
public static Action openAction(TopComponent component, String displayName, String iconBase, boolean noIconInMenu) {
return Actions.alwaysEnabled(new OpenComponentAction(component), displayName, iconBase, noIconInMenu);
}
static Action openAction(Map map) {
return Actions.alwaysEnabled(
new OpenComponentAction(map),
(String)map.get("displayName"), // NOI18N
(String)map.get("iconBase"), // NOI18N
Boolean.TRUE.equals(map.get("noIconInMenu")) // NOI18N
);
}
/** 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()
* @deprecated Do not use. It is redundant since workspaces are not supported anymore. */
@Deprecated
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}
* @deprecated Do not use. It is redundant since workspaces are not supported anymore. */
@Deprecated
public final int getCloseOperation() {
return closeOperation;
}
/**
* Rather than overriding this method, consider using {@link Description}.
* Subclasses are encouraged to override this method to provide preferred value
* for unique TopComponent ID returned by {@link org.openide.windows.WindowManager#findTopComponentID}.
*
* Returned value should be a String, preferably describing semantics of
* TopComponent subclass, such as "PieChartViewer" or "HtmlEditor" etc.
* Value is then used by window system as prefix value for creating unique
* TopComponent ID.
*
* Returned String value should be preferably unique, but need not be.
* @since 4.13
*/
protected String preferredID() {
Class> clazz = getClass();
Description id = clazz.getAnnotation(Description.class);
if (id != null) {
return id.preferredID();
}
if (getPersistenceType() != PERSISTENCE_NEVER && warnedTCPIClasses.add(clazz)) {
Logger.getAnonymousLogger().warning(
clazz.getName() + " should override preferredID()" //NOI18N
);
}
String name = getName();
// fix for #47021 and #47115
if (name == null) {
int ind = clazz.getName().lastIndexOf('.');
name = (ind == -1) ? clazz.getName() : clazz.getName().substring(ind + 1);
}
return name;
}
/** 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 TopComponent
is about to be shown.
* Shown here means the component is selected or resides in it own cell
* in container in its Mode
. The container is visible and not minimized.
*
Note: component
* is considered to be shown, even its container window
* is overlapped by another window.
* @since 2.18 */
protected void componentShowing() {
}
/** Called when TopComponent
was hidden. Nore:
* Note: Beside typical situations when component is hidden,
* it is considered to be hidden even in that case
* the component is in Mode
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), or 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),
* or 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 not be automatically {@link #open opened} first
* if it is not already.
*
Subclasses should override this method to transfer focus to desired
* focusable component. TopComponent
itself is not focusable.
* See for example CloneableEditor#requestFocus.
*
Note: Use {@link #requestActive} instead to make TopComponent active
* in the window system (not only focused). This method should be considered deprecated
* for calling from outside; but it may be overridden (inside of which you may call super).
*/
@Override
public void requestFocus() {
if (isFocusable()) {
//Issue 44304 - output window is focusable when empty, need some
//way to give it focus
super.requestFocus();
}
}
/** Request focus for the top component inside focused window.
* Also makes the component preferred in that window.
* The component will not be automatically {@link #open opened} first
* if it is not already.
*
Subclasses should override this method to transfer focus to desired
* focusable component. TopComponent
itself is not focusable.
* See for example CloneableEditor#requestFocusInWindow.
*
Note: Use {@link #requestActive} instead to make TopComponent active
* in the window system (not only focused). This method should be considered deprecated
* for calling from outside; but it may be overridden (inside of which you may call super).
*/
@Override
public boolean requestFocusInWindow() {
if (isFocusable()) {
return super.requestFocusInWindow();
} else {
return false;
}
}
/** Activates this TopComponent
if it is opened.
* @since 4.13 */
public void requestActive() {
WindowManager.getDefault().topComponentRequestActive(this);
}
/**
* Attempts to bring the parent Window
or Frame
* of this TopComponent
to front of other windows.
* @since 5.8
*/
public void toFront() {
WindowManager.getDefault().topComponentToFront(this);
}
/** Selects this TopComponent
, if it is opened, but does not activate it
* unless it is in active mode already. */
public void requestVisible() {
WindowManager.getDefault().topComponentRequestVisible(this);
org.netbeans.modules.openide.windows.GlobalActionContextImpl.blickActionMap(getActionMap());
}
/**
* Cause this TopComponent's tab to flash or otherwise draw attention to
* itself. This method is thread-safe.
*
* It will remain flashing until either cancelRequestAttention
* is called, the component becomes selected or its activated state changes,
* unless the brief
argument is true, in which case it will stop
* after a few second.
* @param brief True if the tab should blink a few times and stop
* @since 5.1
*/
public final void requestAttention(final boolean brief) {
//Reentrancy issues - always invoke later
EventQueue.invokeLater(
new Runnable() {
public void run() {
if ((attentionGetter != null) && !brief) {
attentionGetter.kill();
} else if (!brief) {
WindowManager.getDefault().topComponentRequestAttention(TopComponent.this);
} else if (attentionGetter != null) {
attentionGetter.reset();
} else {
attentionGetter = new AttentionGetter();
}
}
}
);
}
/**
* Notify the user that some (possibly lengthy) process is being run in this
* window.
* It is safe to call this method outside EDT.
*
* @param busy True to start 'busy' notification, 'false' to stop it.
*
* @see WindowManager#topComponentMakeBusy(org.openide.windows.TopComponent, boolean)
* @since 6.51
*/
public final void makeBusy( final boolean busy ) {
Mutex.EVENT.readAccess( new Runnable() {
@Override
public void run() {
WindowManager.getDefault().topComponentMakeBusy( TopComponent.this, busy );
}
});
}
/**
* Cause this TopComponent's tab to stop flashing if it was flashing.
* @since 5.1
*/
public final void cancelRequestAttention() {
//Reentrancy issues - always invoke later
EventQueue.invokeLater(
new Runnable() {
public void run() {
if (attentionGetter != null) {
attentionGetter.stop();
} else {
WindowManager.getDefault().topComponentCancelRequestAttention(TopComponent.this);
}
}
}
);
}
/**
* Permanently highlights this TopComponent's tab until user activates it.
* @param highlight True to highlight the tab, false to switch the highlight off.
* @since 6.58
* @see #requestAttention(boolean)
*/
public final void setAttentionHighlight( final boolean highlight ) {
Mutex.EVENT.readAccess( new Runnable() {
@Override
public void run() {
WindowManager.getDefault().topComponentAttentionHighlight( TopComponent.this, highlight );
}
});
}
/** Set the name of this top component.
* The default implementation just notifies the window manager.
* @param name the new display name
*/
@Override
public void setName(final String name) {
String old = getName();
if ((name != null) && (name.equals(old))) {
return;
}
super.setName(name);
firePropertyChange("name", old, name); // NOI18N
// XXX When displayName is null, it is used the name.
WindowManager.getDefault().topComponentDisplayNameChanged(this, name);
}
/** Sets localized display name of this TopComponent
.
* @param displayName localized display name which is set
* @since 4.13 */
public void setDisplayName(String displayName) {
String old = this.displayName;
if (Objects.equals(displayName, old)) {
return;
}
// warning if display name contains html tags
if (BasicHTML.isHTMLString(displayName)) {
Logger.getAnonymousLogger().warning(
"Call of " + getClass().getName() + ".setDisplayName(\"" + displayName + "\")" +
" shouldn't contain any HTML tags. Please use " + getClass().getName() + ".setHtmlDisplayName(String)" +
"for such purpose. For details please see http://www.netbeans.org/issues/show_bug.cgi?id=66777.");
}
this.displayName = displayName;
firePropertyChange("displayName", old, displayName); // NOI18N
WindowManager.getDefault().topComponentDisplayNameChanged(this, displayName);
}
/** Gets localized display name of this TopComponent
.
* @return localized display name or null
if there is none
* @since 4.13 */
public String getDisplayName() {
return displayName;
}
/**
* Short version of TopComponent's name that doesn't include the name of
* activated Node.
* @return Short version of TopComponent's display name. The default implementation
* returns null.
* @since 6.52
*/
public String getShortName() {
return null;
}
/** Sets localized html display name of this TopComponent
.
* Hmtl name usually contains basic html tags for text coloring and style.
* Html name may be null if not needed.
* Must apparently begin with <html>
.
*
* @param htmlDisplayName localized html display name which is set
*
* @since 6.4
*/
public void setHtmlDisplayName(String htmlDisplayName) {
String old = this.htmlDisplayName;
if (Objects.equals(htmlDisplayName, old)) {
return;
}
this.htmlDisplayName = htmlDisplayName;
firePropertyChange("htmlDisplayName", old, htmlDisplayName); // NOI18N
WindowManager.getDefault().topComponentHtmlDisplayNameChanged(this, htmlDisplayName);
}
/** Gets localized display name of this TopComponent
with added
* html tags for text coloring and/or font style. May return null.
* Must apparently begin with <html>
.
*
* @return localized html display name or null
if there is none
*
* @since 6.4
*/
public String getHtmlDisplayName() {
return htmlDisplayName;
}
/** Sets toolTip for this TopComponent
, adds notification
* about the change to its WindowManager.TopComponentManager
. */
@Override
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.
WindowManager.getDefault().topComponentToolTipChanged(this, toolTip);
}
/** 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) {
if (icon == this.icon) {
return;
}
Image old = this.icon;
this.icon = icon;
WindowManager.getDefault().topComponentIconChanged(this, this.icon); // TEMP
firePropertyChange("icon", old, icon); // NOI18N
}
/**
* Rather than overriding this method, consider using {@link Description}.
* @return The icon of the top component
*/
public Image getIcon() {
Description id;
if (icon == null && (id = getClass().getAnnotation(Description.class)) != null) {
icon = ImageUtilities.loadImage(id.iconBase(), true);
}
return icon;
}
/** 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); // XXX #63303
}
/** 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.
* So for example, by returning empty list, top component refuses
* to be docked anywhere.
* 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.
* 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 TopComoponent
is ancestor of focused component. */
@Override
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()) { // NOI18N
Keymap km = Lookup.getDefault().lookup(Keymap.class);
Action action = (km != null) ? km.getAction(ks) : null;
if (action == null) {
return false;
}
// If necessary create context aware instance.
if (action instanceof ContextAwareAction) {
Action delegate = ((ContextAwareAction) action).createContextAwareInstance(getLookup());
assert delegate != null : "ContextAwareAction cannot return null: " + action;
if( delegate.isEnabled() && getActivatedNodes() != null )
action = delegate;
//else
// use the global instance which might be enabled if it can survive focus changes
} 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;
}
}
if (action.isEnabled()) {
LogRecord rec = new LogRecord(Level.FINER, "UI_ACTION_KEY_PRESS"); // NOI18N
rec.setParameters(new Object[] { ks, ks.toString(), action.toString(), action.getClass().getName(), action.getValue(Action.NAME) });
rec.setResourceBundle(NbBundle.getBundle(TopComponent.class));
rec.setLoggerName(UILOG.getName());
UILOG.log(rec);
ActionEvent ev = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, Utilities.keyToString(ks));
action.actionPerformed(ev);
} else {
Utilities.disabledActionBeep();
}
return true;
} else {
return ret;
}
}
/** 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(serialVersion);
out.writeInt(closeOperation);
out.writeObject(getName());
out.writeObject(getToolTipText());
if (getDisplayName() != null) {
out.writeObject(getDisplayName());
}
Node n = (nodeName == null) ? null : nodeName.getNode();
Node.Handle h = (n == null) ? null : n.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 {
Object firstObject = in.readObject();
if (firstObject instanceof Integer) {
// backward compatibility read
serialVersion = 0;
closeOperation = ((Integer) firstObject).intValue();
// BCR: this is backward compatibility read and is likely not needed
// BCR: anymore. So let's just ignore the read of the data object
// BCR: DataObject obj = (DataObject)in.readObject();
in.readObject();
super.setName((String) in.readObject());
setToolTipText((String) in.readObject());
// initialize the connection to a data object
/* BCR: Remove this as we ignore the DataObject
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());
Object obj = in.readObject();
if (obj instanceof String) {
setDisplayName((String) obj);
obj = in.readObject();
}
Node.Handle h = (Node.Handle) obj;
if (h != null) {
Node n = h.getNode();
NodeName.connect(this, 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);
}
/* Read accessible context
* @return - accessible context
*/
@Override
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new JComponent.AccessibleJComponent() {
@Override
public AccessibleRole getAccessibleRole() {
return AccessibleRole.PANEL;
}
@Override
public String getAccessibleName() {
if (accessibleName != null) {
return accessibleName;
}
return getName();
}
/* Fix for 19344: Null accessible decription of all TopComponents on JDK1.4 */
@Override
public String getToolTipText() {
return TopComponent.this.getToolTipText();
}
};
}
return accessibleContext;
}
/**
* Gets a lookup which represents the "selection" of this component.
*
* By default the lookup includes all nodes from {@link #getActivatedNodes},
* all objects from those nodes' own lookups (excepting the nodes themselves),
* and also the component's {@link ActionMap}.
* This is useful for components with explorer views.
*
* The default implementation also has a special behavior when you look up
* {@link Node Node.class}: if {@link #getActivatedNodes} is null (as opposed to
* an empty array), the result will contain an extra item whose
* {@link org.openide.util.Lookup.Item#getId} is {@code none}
* and whose {@link org.openide.util.Lookup.Item#getInstance} is null.
* This can be used by (say) node-sensitive actions to differentiate
* between a component with an explorer view that currently happens to have no
* selected nodes (zero-length array so no {@code Lookup.Item}),
* vs. a component with no explorer view that would never have a node selection
* (null so one dummy {@code Lookup.Item});
* in either case {@link org.openide.util.Lookup.Result#allInstances}
* would return an empty collection.
* In particular, {@link NodeAction} relies on this behavior to avoid disabling
* an action just because focus is transferred from a component with a (potential)
* node selection to a component that does not have node selections.
*
* If you override the method in your subclass, the default activatedNodes<->lookup synchronization
* will not be performed. That can influence functionality that relies on activated Nodes being present
* in the TopComponent's lookup. If you want to preserve the synchronization, use {@link #associateLookup}
* instead.
*
* @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() {
return getLookup(true);
}
/**
* @param init should a lookup be initialized if it is not?
* @return lookup or null
*/
private Lookup getLookup(boolean init) {
synchronized (defaultLookupLock) {
if (defaultLookupRef instanceof Lookup) {
return (Lookup) defaultLookupRef;
}
if (defaultLookupRef instanceof Object[]) {
return (Lookup) ((Object[]) defaultLookupRef)[0];
}
if (defaultLookupRef instanceof java.lang.ref.Reference) {
Object l = ((java.lang.ref.Reference) defaultLookupRef).get();
if (l instanceof Lookup) {
return (Lookup) l;
}
}
if (!init) {
return null;
}
Lookup lookup = new DefaultTopComponentLookup(this); // Lookup of activated nodes and action map
defaultLookupRef = new java.lang.ref.WeakReference(lookup);
return lookup;
}
}
/** Associates the provided lookup with the component. So it will
* be returned from {@link #getLookup} method. When used, make sure
* the provided {@link Lookup} contains objects needed by other subsystems.
* For example, if {@link Actions#callback(java.lang.String, javax.swing.Action, boolean, java.lang.String, java.lang.String, boolean) callback actions}
* are about to search their actions in this {@link TopComponent},
* it is good idea to include {@link #getActionMap() this.getActionMap()} in
* the lookup.
*
* @param lookup the lookup to associate
* @exception IllegalStateException if there already is a lookup registered
* with this component
* @since 4.23
*/
protected final void associateLookup(Lookup lookup) {
setLookup(lookup, true);
}
/** Associates the provided lookup with the component. So it will
* be returned from {@link #getLookup} method.
*
* @param lookup the lookup to associate
* @param sync synchronize return value of {@link #getActivatedNodes} with the
* content of lookup?
* @exception IllegalStateException if there already is a lookup registered
* with this component
*/
final void setLookup(Lookup lookup, boolean sync) {
synchronized (defaultLookupLock) {
if (defaultLookupRef != null) {
throw new IllegalStateException(
"Trying to set lookup " + lookup + " but there already is " + defaultLookupRef +
" for component: " + this
); // NOI18N
}
defaultLookupRef = lookup;
if (sync) {
defaultLookupRef = new Object[] { defaultLookupRef, new SynchronizeNodes(lookup) };
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("setLookup with " + lookup + " and sync: " + sync + " on " + this); // NOI18N
}
}
}
private void attachNodeName(NodeName nodeName) {
this.nodeName = nodeName;
}
/**
* Retrieves sub-components this TopComponent contains. The resulting array
* is a snapshot valid at the time of the call and shouldn't be held for too long.
* This method can be called from EDT only.
*
* @return Array of internal sub-components or an empty array if the TopComponent
* has no internal sub-components.
* @since 6.52
*/
public SubComponent[] getSubComponents() {
return new SubComponent[0];
}
/** 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();
}
/** Provides basic information about the persistence of a {@link TopComponent}.
* Using this annotation is preferred to overriding {@link #preferredID},
* {@link #getPersistenceType}, or {@link #getIcon}, or calling {@link #setIcon}.
* @since 6.37
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface Description {
/** The default value for {@link TopComponent#preferredID()}.
* It can consist only of upper and lower case letters, digits and '-' or '_' characters.
* If the TopCopmponent is a singleton then you can use this id to
* lookup the TopComponent in {@link WindowManager#findTopComponent(java.lang.String)}
*/
public String preferredID();
/** The icon to load for {@link TopComponent#getIcon()}.
*/
public String iconBase() default "";
/** Default value for {@link TopComponent#getPersistenceType()}.
*
* @return one of {@link TopComponent#PERSISTENCE_ALWAYS},
* {@link TopComponent#PERSISTENCE_NEVER},
* {@link TopComponent#PERSISTENCE_ONLY_OPENED}
*
*/
public int persistenceType() default PERSISTENCE_ALWAYS;
}
/** Registers {@link TopComponent} into specified location among
* existing {@link Mode window system modes}. The mode itself needs
* to be created by other means, this just adds reference to the
* component to it.
*
* @since 6.37
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ ElementType.TYPE, ElementType.METHOD })
public static @interface Registration {
/** Name of the mode the component shall be opened in */
String mode();
/** Position of the component within its mode. */
int position() default Integer.MAX_VALUE;
/** Shall the component be opened at start */
boolean openAtStartup();
/**
* Window layout roles or no roles for the default layout
* @see WindowManager#setRole(java.lang.String)
* @since 6.47
*/
String[] roles() default {};
}
/** Creates an action that can open the component.
* The action is generated only
* if {@link ActionID} annotation is used on the same element, otherwise
* a compilation error is raised. Also the {@link Description} shall be
* present.
* @since 6.37
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ ElementType.TYPE, ElementType.METHOD })
public static @interface OpenActionRegistration {
/** Display name of the action, usually can refer to value from a
* Bundle.properties
using #KEY
.
*/
String displayName();
/** The identification of an existing component to seek for.
*/
String preferredID() default "";
}
/** 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. */
public static final String PROP_ACTIVATED_NODES = "activatedNodes"; // NOI18N
/** Name of property for listening to TopComponents opened through open() call,
* either by user or programmatically.
* Fired property change event returns opened TopComponent from its getNewValue()
*/
public static final String PROP_TC_OPENED = "tcOpened"; // NOI18N
/** Name of property for listening to TopComponents closed through close() call,
* either by user or programmatically.
* Fired property change event returns closed TopComponent from its getNewValue().
*/
public static final String PROP_TC_CLOSED = "tcClosed"; // 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 null
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);
}
/**
* Representation of a visual sub-component displayed in a TopComponent,
* for example sub-tabs in a multiview window.
*
* @see #getSubComponents()
* @since 6.52
*/
public static final class SubComponent {
private final String displayName;
private final String description;
private final boolean active;
private final ActionListener activator;
private final Lookup lookup;
private final boolean showing;
/**
* C'tor
* @param displayName Subcomponent's display name.
* @param activator ActionListener to invoke when the sub-component needs
* to be actived.
* @param active True if the given sub-component is currently active,
* e.g. multiview sub-tab is selected.
*/
public SubComponent( String displayName, ActionListener activator, boolean active ) {
this( displayName, null, activator, active );
}
/**
* C'tor
* @param displayName Subcomponent's display name.
* @param description Short description to show in a tooltip.
* @param activator ActionListener to invoke when the sub-component needs
* to be actived.
* @param active True if the given sub-component is currently active,
* e.g. multiview sub-tab is selected.
*/
public SubComponent( String displayName, String description, ActionListener activator, boolean active ) {
this(displayName, description, activator, active, Lookup.EMPTY, false);
}
/**
* C'tor
* @param displayName Subcomponent's display name.
* @param description Short description to show in a tooltip.
* @param activator ActionListener to invoke when the sub-component needs
* to be actived.
* @param active True if the given sub-component is currently active,
* e.g. multiview sub-tab is selected.
* @param lookup the lookup of the given sub-component
* @param showing True if the given sub-component is currently showing,
* e.g. multiview sub-tab is selected or part of a split.
*/
public SubComponent( String displayName, String description, ActionListener activator, boolean active, Lookup lookup, boolean showing ) {
this.displayName = displayName;
this.description = description;
this.active = active;
this.activator = activator;
this.lookup = lookup;
this.showing = showing;
}
/**
* @return the lookup of the given sub-component, could be null
*/
public Lookup getLookup() {
return lookup;
}
/**
* @return True if this sub-component is the active/selected one or part of a split.
*/
public boolean isShowing() {
return showing;
}
/**
* @return True if this sub-component is the active/selected one.
*/
public final boolean isActive() {
return active;
}
public final String getDescription() {
return description;
}
public final String getDisplayName() {
return displayName;
}
/**
* Make this sub-component the active/selected one.
*/
public final void activate() {
activator.actionPerformed( new ActionEvent( this, 0, "activate") ); //NOI18N
}
}
private class AttentionGetter implements ActionListener {
Timer timer = null;
public AttentionGetter() {
reset();
}
public void reset() {
assert EventQueue.isDispatchThread();
if (timer != null) {
timer.stop();
}
start();
timer = new Timer(3500, this);
timer.setRepeats(false);
timer.start();
}
private void start() {
WindowManager.getDefault().topComponentRequestAttention(TopComponent.this);
}
public void kill() {
timer.stop();
attentionGetter = null;
}
private void stop() {
if (timer != null) {
timer.stop();
}
attentionGetter = null;
WindowManager.getDefault().topComponentCancelRequestAttention(TopComponent.this);
attentionGetter = null;
}
public void actionPerformed(ActionEvent ae) {
stop();
}
}
/** This class provides the connection between the node name and
* a name of the component.
*
* @deprecated Please do not use. This support class does nothing much
* useful. If you need to synchronize display name of your TopComponent
* with some Node's display name, we recommend you to do it manually in
* your client's code.
*/
@Deprecated
public static class NodeName extends NodeAdapter {
/** asociation with top component */
private TopComponent top;
/** weak ref to node we are attached to or null */
private Reference node;
/** Listener to node, used for weak listening */
private NodeListener nodeL;
/** Connects a top component and a node. The name of
* component will be updated as the name of the node
* changes.
*
* @param top top compoonent to modify its name
* @param n node to take name from
*
* @since 4.3
*/
public static void connect(TopComponent top, Node n) {
new NodeName(top).attach(n);
}
/** 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 component to modify its name
*
* @deprecated Please do not use, public just by an accident.
*/
@Deprecated
public NodeName(TopComponent top) {
this.top = top;
}
/** Listens to Node.PROP_DISPLAY_NAME.
*
* @deprecated Please do not use, public just by an accident.
*/
@Deprecated
public void propertyChange(PropertyChangeEvent ev) {
if (ev.getPropertyName().equals(Node.PROP_DISPLAY_NAME)) {
final Node n = (Node) node.get();
if (n != null) {
Mutex.EVENT.readAccess( new Runnable() {
@Override
public void run() {
top.setName(n.getDisplayName());
}
});
}
}
}
/** Attaches itself to a given node.
*/
private void attach(Node n) {
synchronized (top) {
node = new WeakReference(n);
nodeL = WeakListeners.create(NodeListener.class, this, n);
n.addNodeListener(nodeL);
top.attachNodeName(this);
top.setActivatedNodes(new Node[] { n });
top.setName(n.getDisplayName());
}
}
private Node getNode() {
return (Node) node.get();
}
} // end of NodeName
/** 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 {
ClassLoader loader = Lookup.getDefault().lookup(ClassLoader.class);
if (loader == null) {
loader = getClass().getClassLoader();
}
Class tcClass = Class.forName(name, true, loader);
// 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);
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 (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.
throw (IOException) new IOException(th.toString()).initCause(th);
}
}
/** 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;
Class[] params = new Class[0];
// first try public occurences
try {
result = clazz.getMethod("readResolve", params); // 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 != TopComponent.class); i = i.getSuperclass()) { // perf: TC and its superclasses do not have readResolve method
try {
result = i.getDeclaredMethod("readResolve", params); // NOI18N
// get out of cycle if method found
break;
} catch (NoSuchMethodException exc2) {
// readResolve does not exist in current class
}
}
}
return result;
}
}
// end of Replacer inner class
/** Synchronization between Lookup and getActivatedNodes
*/
private class SynchronizeNodes implements org.openide.util.LookupListener, Runnable {
private Lookup.Result res;
public SynchronizeNodes(Lookup l) {
res = l.lookup(new Lookup.Template(Node.class));
res.addLookupListener(this);
resultChanged(null);
}
public void resultChanged(org.openide.util.LookupEvent ev) {
boolean l = LOG.isLoggable(Level.FINE);
if (l) {
LOG.fine("lookup changed for " + TopComponent.this + " is visible: " + TopComponent.this.isVisible()); // NOI18N
}
if (TopComponent.this.isVisible() && EventQueue.isDispatchThread()) {
// run immediatelly
run();
} else {
// replan
EventQueue.invokeLater(this);
}
if (l) {
LOG.fine("lookup changed exit " + TopComponent.this); // NOI18N
}
}
public void run() {
boolean l = LOG.isLoggable(Level.FINE);
Collection extends Node> nodes = res.allInstances();
if (l) {
LOG.fine("setting nodes for " + TopComponent.this + " to " + nodes); // NOI18N
}
setActivatedNodesImpl(nodes.toArray(new Node[0]));
if (l) {
LOG.fine("setting nodes done for " + TopComponent.this + " to " + nodes); // NOI18N
}
}
} // end of SynchronizeNodes
private static class CloneWindowAction extends AbstractAction {
DelegateActionMap am;
public CloneWindowAction(DelegateActionMap am) {
this.am = am;
}
public void actionPerformed(ActionEvent evt) {
TopComponent.Cloneable self = (TopComponent.Cloneable) am.getComponent();
if (self != null) {
TopComponent cloned = self.cloneComponent();
int openIndex = -1;
if( self instanceof TopComponent ) {
Mode m = WindowManager.getDefault().findMode((TopComponent)self);
if( null != m ) {
TopComponent[] tcs = m.getTopComponents();
for( int i=0; i= tcs.length )
openIndex = -1;
}
}
if( openIndex >= 0 ) {
cloned.openAtTabPosition(openIndex);
} else {
cloned.open();
}
cloned.requestActive();
}
}
} // end of CloneWindowAction
private static class CloseWindowAction extends AbstractAction {
DelegateActionMap am;
public CloseWindowAction(DelegateActionMap am) {
this.am = am;
}
public void actionPerformed(ActionEvent evt) {
TopComponent self = (TopComponent) am.getComponent();
if (self != null) {
self.close();
}
}
} // end of CloseWindowAction
}