/*
* 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.explorer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.io.*;
import java.text.MessageFormat;
import javax.swing.Timer;
import org.openide.nodes.Node;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.io.*;
import org.openide.windows.TopComponent;
import org.openide.windows.Workspace;
import org.openide.windows.WindowManager;
/** Simple top component capable of displaying an Explorer.
* Holds one instance of {@link ExplorerManager} and
* implements {@link ExplorerManager.Provider} to allow child components to share
* the same explorer manager.
* <p>Uses {@link java.awt.BorderLayout} by default.
* Pays attention to the selected nodes and explored context as indicated by the manager.
* Cut/copy/paste actions are sensitive to the activation state of the component.
* <p>It is up to you to add a view and other UI apparatus to the panel.
*
* @author Jaroslav Tulach
*/
public class ExplorerPanel extends TopComponent implements ExplorerManager.Provider {
/** serial version UID */
static final long serialVersionUID = 5522528786650751459L;
/** The message formatter for Explorer title */
private static MessageFormat formatExplorerTitle;
/** the instance of the explorer manager*/
private ExplorerManager manager;
/** listens on the selected nodes in the ExporerManager */
transient private final PropertyChangeListener managerListener = new PropL();
/** action handler for cut/copy/paste/delete */
private static final ExplorerActions actions = new ExplorerActions ();
/** Init delay for second change of the activated nodes. */
private static final int INIT_DELAY = 70;
/** Maximum delay for repeated change of the activated nodes. */
private static final int MAX_DELAY = 350;
private static Boolean scheduleAcivatedNodes;
/** Initialize the explorer panel with the provided manager.
* @param manager the explorer manager to use
*/
public ExplorerPanel (ExplorerManager manager) {
this.manager = manager;
setLayout (new java.awt.BorderLayout ());
initActionMap();
initListening();
}
/** Default constructor. Uses newly created manager.
*/
public ExplorerPanel () {
this(null);
}
/** Initializes actions map. */
private void initActionMap() {
ExplorerActions a = new ExplorerActions (false);
a.attach (getExplorerManager ());
getActionMap ().put (
javax.swing.text.DefaultEditorKit.copyAction, a.copyAction ()
);
getActionMap ().put (
javax.swing.text.DefaultEditorKit.cutAction, a.cutAction ()
);
getActionMap ().put (
javax.swing.text.DefaultEditorKit.pasteAction, a.pasteAction ()
);
getActionMap ().put (
"delete", a.deleteAction () // NOI18N
);
}
/** Initializes listening on ExplorerManager property changes. */
private void initListening() {
// Attaches listener if there is not one already.
ExplorerManager man = getExplorerManager();
man.addPropertyChangeListener(
org.openide.util.WeakListener.propertyChange(
managerListener, man));
}
/* Add a listener to the explorer panel in addition to the normal
* open behaviour.
*/
public void open () {
open(WindowManager.getDefault().getCurrentWorkspace());
}
/* Add a listener to the explorer panel in addition to the normal
* open behaviour.
*/
public void open (Workspace workspace) {
super.open(workspace);
setActivatedNodes (getExplorerManager ().getSelectedNodes ());
updateTitle ();
}
/* Provides the explorer manager to all who are interested.
* @return the manager
*/
public synchronized ExplorerManager getExplorerManager () {
if (manager == null) {
manager = new ExplorerManager ();
}
return manager;
}
/* Deactivates copy/cut/paste actions.
*/
protected void componentDeactivated () {
if (getExplorerManager() == actions.getAttachedManager()) { // #18137
actions.detach ();
}
}
/** Called when the explored context changes.
* The default implementation updates the title of the window.
*/
protected void updateTitle () {
String name = ""; // NOI18N
ExplorerManager em = getExplorerManager ();
if (em != null) {
Node n = em.getExploredContext();
if (n != null) {
String nm = n.getDisplayName();
if (nm != null) {
name = nm;
}
}
}
if (formatExplorerTitle == null) {
formatExplorerTitle = new MessageFormat (NbBundle.getMessage (ExplorerPanel.class, "explorerTitle"));
}
setName(formatExplorerTitle.format (
new Object[] { name }
));
}
/** Get context help for an explorer window.
* Looks at the manager's node selection.
* @return the help context
* @see #getHelpCtx(Node[],HelpCtx)
*/
public HelpCtx getHelpCtx () {
return getHelpCtx (getExplorerManager ().getSelectedNodes (),
new HelpCtx (ExplorerPanel.class));
}
/** Utility method to get context help from a node selection.
* Tries to find context helps for selected nodes.
* If there are some, and they all agree, uses that.
* In all other cases, uses the supplied generic help.
* @param sel a list of nodes to search for help in
* @param def the default help to use if they have none or do not agree
* @return a help context
*/
public static HelpCtx getHelpCtx (Node[] sel, HelpCtx def) {
HelpCtx result = null;
for (int i = 0; i < sel.length; i++) {
HelpCtx attempt = sel[i].getHelpCtx ();
if (attempt != null && ! attempt.equals (HelpCtx.DEFAULT_HELP)) {
if (result == null || result.equals (attempt)) {
result = attempt;
} else {
// More than one found, and they conflict. Get general help on the Explorer instead.
result = null;
break;
}
}
}
if (result != null)
return result;
else
return def;
}
/** Set whether deletions should have to be confirmed on all Explorer panels.
* @param confirmDelete <code>true</code> to confirm, <code>false</code> to delete at once
*/
public static void setConfirmDelete (boolean confirmDelete) {
actions.setConfirmDelete (confirmDelete);
}
/** Are deletions confirmed on all Explorer panels?
* @return <code>true</code> if they must be confirmed
*/
public static boolean isConfirmDelete () {
return actions.isConfirmDelete ();
}
/** Stores the manager */
public void writeExternal (ObjectOutput oo) throws IOException {
super.writeExternal (oo);
oo.writeObject (new NbMarshalledObject (manager));
}
/** Reads the manager.
* Deserialization may throw {@link SafeException} in case
* the manager cannot be loaded correctly but the stream is still uncorrupted.
*/
public void readExternal (ObjectInput oi)
throws IOException, ClassNotFoundException {
super.readExternal (oi);
Object anObj = oi.readObject ();
if (anObj instanceof ExplorerManager) {
manager = (ExplorerManager)anObj;
initActionMap();
initListening();
return;
}
NbMarshalledObject obj = (NbMarshalledObject) anObj;
// --- read all data from main stream, it is OK now ---
try {
manager = (ExplorerManager) obj.get ();
initActionMap();
initListening();
} catch (SafeException se) {
throw se;
} catch (IOException ioe) {
throw new SafeException (ioe);
}
}
// temporary workaround the issue #31244
private boolean delayActivatedNodes () {
if (scheduleAcivatedNodes == null) {
if (System.getProperty ("netbeans.delay.tc") != null) { // NOI18N
scheduleAcivatedNodes = Boolean.getBoolean ("netbeans.delay.tc") ? Boolean.TRUE : Boolean.FALSE; // NOI18N
} else {
scheduleAcivatedNodes = Boolean.FALSE;
}
}
return scheduleAcivatedNodes.booleanValue ();
}
/** Listener on the explorer manager properties.
* Changes selected nodes of this frame.
*/
private final class PropL extends Object implements PropertyChangeListener {
PropL() {}
public void propertyChange(PropertyChangeEvent evt) {
if(evt.getSource() != manager) {
return;
}
if (ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) {
if (delayActivatedNodes ()) {
scheduleActivatedNodes (manager.getSelectedNodes ());
} else {
setActivatedNodes (manager.getSelectedNodes ());
}
return;
}
if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) {
updateTitle ();
return;
}
}
}
private class DelayedSetter implements ActionListener {
DelayedSetter () {}
private Node[] nodes;
private Timer timer;
private boolean firstChange = true;
public void scheduleActivatedNodes (Node[] nodes) {
synchronized (this) {
this.nodes = nodes;
if (timer == null) {
// start timer with INIT_DELAY
timer = new Timer (INIT_DELAY, this);
timer.setCoalesce (true);
timer.setRepeats (false);
}
if (timer.isRunning ()) {
// if timer is running then double init delay
if (timer.getInitialDelay () < MAX_DELAY) timer.setInitialDelay (timer.getInitialDelay () * 2);
firstChange = false;
} else {
// the first change is set immediatelly
setActivatedNodes (nodes);
firstChange = true;
}
// make sure timer is running
timer.restart();
}
}
public void actionPerformed (ActionEvent evt) {
synchronized (this) {
synchronized (this) {
timer.stop ();
}
}
// set activated nodes for 2nd and next changes
if (!firstChange) {
setActivatedNodes (nodes);
}
}
}
private transient DelayedSetter delayedSetter;
// schudule activation the nodes
private final void scheduleActivatedNodes (Node[] nodes) {
synchronized (this) {
if (delayedSetter == null)
delayedSetter = new DelayedSetter ();
}
delayedSetter.scheduleActivatedNodes (nodes);
}
}