001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.openide.explorer;
042:
043: import org.netbeans.core.IDESettings;
044: import org.openide.nodes.Node;
045: import org.openide.util.HelpCtx;
046: import org.openide.util.NbBundle;
047: import org.openide.util.NbPreferences;
048: import org.openide.util.WeakListeners;
049: import org.openide.util.io.*;
050: import org.openide.windows.TopComponent;
051: import org.openide.windows.WindowManager;
052: import org.openide.windows.Workspace;
053:
054: import java.awt.event.ActionEvent;
055: import java.awt.event.ActionListener;
056:
057: import java.beans.PropertyChangeEvent;
058: import java.beans.PropertyChangeListener;
059:
060: import java.io.*;
061:
062: import java.text.MessageFormat;
063:
064: import javax.swing.Timer;
065:
066: /** Simple top component capable of displaying an Explorer.
067: * Holds one instance of {@link ExplorerManager} and
068: * implements {@link ExplorerManager.Provider} to allow child components to share
069: * the same explorer manager.
070: * <p>Uses {@link java.awt.BorderLayout} by default.
071: * Pays attention to the selected nodes and explored context as indicated by the manager.
072: * Cut/copy/paste actions are sensitive to the activation state of the component.
073: * <p>It is up to you to add a view and other UI apparatus to the panel.
074: *
075: * @deprecated Use {@link ExplorerUtils#actionCopy}, etc, see {@link ExplorerUtils} javadoc
076: * for details
077: * @author Jaroslav Tulach
078: */
079: public class ExplorerPanel extends TopComponent implements
080: ExplorerManager.Provider {
081: /** serial version UID */
082: static final long serialVersionUID = 5522528786650751459L;
083:
084: /** The message formatter for Explorer title */
085: private static MessageFormat formatExplorerTitle;
086:
087: /** Init delay for second change of the activated nodes. */
088: private static final int INIT_DELAY = 70;
089:
090: /** Maximum delay for repeated change of the activated nodes. */
091: private static final int MAX_DELAY = 350;
092: private static Boolean scheduleAcivatedNodes;
093:
094: /** mapping from ExplorerManagers to the ExplorerPanels they are associated
095: * with. ExplorerManager -> Reference (ExplorerPanel)
096: */
097: private static java.util.WeakHashMap panels = new java.util.WeakHashMap();
098:
099: /** the instance of the explorer manager*/
100: private ExplorerManager manager;
101:
102: /** listens on the selected nodes in the ExporerManager */
103: transient private final PropertyChangeListener managerListener = new PropL();
104:
105: /** action handler for cut/copy/paste/delete for this panel */
106: private ExplorerActions actions;
107: private transient DelayedSetter delayedSetter;
108:
109: /** Initialize the explorer panel with the provided manager.
110: * @param manager the explorer manager to use
111: */
112: public ExplorerPanel(ExplorerManager manager) {
113: this (manager, null);
114: }
115:
116: /** Default constructor. Uses newly created manager.
117: */
118: public ExplorerPanel() {
119: this (null, null);
120: }
121:
122: /** Allows to create an explorer with disabled delete confirmation.
123: * @param confirm false if delete action should not be confirmed
124: */
125: ExplorerPanel(ExplorerManager manager, boolean confirm) {
126: this (manager, Boolean.valueOf(confirm));
127: }
128:
129: private ExplorerPanel(ExplorerManager manager, Boolean confirm) {
130: if (manager == null) {
131: manager = new ExplorerManager();
132: }
133:
134: this .manager = manager;
135: panels.put(manager, new java.lang.ref.WeakReference(this ));
136:
137: setLayout(new java.awt.BorderLayout());
138: initActionMap(confirm);
139: initListening();
140: }
141:
142: // bugfix #36509, added 3-state (true/false/unset) parameter for confirm delete
143: // unset means read parameter from the global ConfirmDelete option
144:
145: /** Initializes actions map. */
146: private void initActionMap(Boolean confirm) {
147: ExplorerActions a = new ExplorerActions(false);
148:
149: if (confirm != null) {
150: a.setConfirmDelete(confirm.booleanValue());
151: }
152:
153: a.attach(getExplorerManager());
154: }
155:
156: /** Called from a ExplorerActions.attach, to notify that these
157: * ExplorerActions will now be responsible for given explorer
158: * manager.
159: *
160: * @param actions the actions
161: * @param em the manager
162: */
163: static void associateActions(ExplorerActions actions,
164: ExplorerManager em) {
165: java.lang.ref.Reference ref = (java.lang.ref.Reference) panels
166: .get(em);
167: ExplorerPanel p = (ref == null) ? null : (ExplorerPanel) ref
168: .get();
169:
170: if (p != null) {
171: p.getActionMap().put(
172: javax.swing.text.DefaultEditorKit.copyAction,
173: actions.copyAction());
174: p.getActionMap().put(
175: javax.swing.text.DefaultEditorKit.cutAction,
176: actions.cutAction());
177: p.getActionMap().put(
178: javax.swing.text.DefaultEditorKit.pasteAction,
179: actions.pasteAction());
180: p.getActionMap().put("delete", actions.deleteAction() // NOI18N
181: );
182:
183: // and remember the actions
184: p.actions = actions;
185: }
186: }
187:
188: /** Initializes listening on ExplorerManager property changes. */
189: private void initListening() {
190: // Attaches listener if there is not one already.
191: ExplorerManager man = getExplorerManager();
192: man.addPropertyChangeListener(org.openide.util.WeakListeners
193: .propertyChange(managerListener, man));
194: setActivatedNodes(manager.getSelectedNodes());
195: }
196:
197: /* Add a listener to the explorer panel in addition to the normal
198: * open behaviour.
199: */
200: public void open() {
201: open(WindowManager.getDefault().getCurrentWorkspace());
202: }
203:
204: /* Add a listener to the explorer panel in addition to the normal
205: * open behaviour.
206: */
207: public void open(Workspace workspace) {
208: super .open(workspace);
209: setActivatedNodes(getExplorerManager().getSelectedNodes());
210: updateTitle();
211: }
212:
213: /* Provides the explorer manager to all who are interested.
214: * @return the manager
215: */
216: public ExplorerManager getExplorerManager() {
217: return manager;
218: }
219:
220: /** Activates copy/cut/paste actions.
221: */
222: protected void componentActivated() {
223: if (actions != null) {
224: actions.attach(getExplorerManager());
225: }
226: }
227:
228: /* Deactivates copy/cut/paste actions.
229: */
230: protected void componentDeactivated() {
231: if (actions != null) {
232: actions.detach();
233: }
234: }
235:
236: /** Called when the explored context changes.
237: * The default implementation updates the title of the window.
238: */
239: protected void updateTitle() {
240: String name = ""; // NOI18N
241:
242: ExplorerManager em = getExplorerManager();
243:
244: if (em != null) {
245: Node n = em.getExploredContext();
246:
247: if (n != null) {
248: String nm = n.getDisplayName();
249:
250: if (nm != null) {
251: name = nm;
252: }
253: }
254: }
255:
256: if (formatExplorerTitle == null) {
257: formatExplorerTitle = new MessageFormat(NbBundle
258: .getMessage(ExplorerPanel.class, "explorerTitle"));
259: }
260:
261: setName(formatExplorerTitle.format(new Object[] { name }));
262: }
263:
264: /** Get context help for an explorer window.
265: * Looks at the manager's node selection.
266: * @return the help context
267: * @see #getHelpCtx(Node[],HelpCtx)
268: */
269: public HelpCtx getHelpCtx() {
270: return getHelpCtx(getExplorerManager().getSelectedNodes(),
271: new HelpCtx(ExplorerPanel.class));
272: }
273:
274: /** Utility method to get context help from a node selection.
275: * Tries to find context helps for selected nodes.
276: * If there are some, and they all agree, uses that.
277: * In all other cases, uses the supplied generic help.
278: * @param sel a list of nodes to search for help in
279: * @param def the default help to use if they have none or do not agree
280: * @return a help context
281: */
282: public static HelpCtx getHelpCtx(Node[] sel, HelpCtx def) {
283: return ExplorerUtils.getHelpCtx(sel, def);
284: }
285:
286: /** Set whether deletions should have to be confirmed on all Explorer panels.
287: * @param confirmDelete <code>true</code> to confirm, <code>false</code> to delete at once
288: */
289: public static void setConfirmDelete(boolean confirmDelete) {
290: NbPreferences.root().node("/org/netbeans/core").putBoolean(
291: "confirmDelete", confirmDelete);//NOI18N
292: }
293:
294: /** Are deletions confirmed on all Explorer panels?
295: * @return <code>true</code> if they must be confirmed
296: */
297: public static boolean isConfirmDelete() {
298: return NbPreferences.root().node("/org/netbeans/core")
299: .getBoolean("confirmDelete", true);//NOI18N
300: }
301:
302: /** Stores the manager */
303: public void writeExternal(ObjectOutput oo) throws IOException {
304: super .writeExternal(oo);
305: oo.writeObject(new NbMarshalledObject(manager));
306: }
307:
308: /** Reads the manager.
309: * Deserialization may throw {@link SafeException} in case
310: * the manager cannot be loaded correctly but the stream is still uncorrupted.
311: */
312: public void readExternal(ObjectInput oi) throws IOException,
313: ClassNotFoundException {
314: super .readExternal(oi);
315:
316: Object anObj = oi.readObject();
317:
318: if (anObj instanceof ExplorerManager) {
319: manager = (ExplorerManager) anObj;
320: panels.put(manager, new java.lang.ref.WeakReference(this ));
321: initActionMap(null);
322: initListening();
323:
324: return;
325: }
326:
327: NbMarshalledObject obj = (NbMarshalledObject) anObj;
328:
329: // --- read all data from main stream, it is OK now ---
330: try {
331: manager = (ExplorerManager) obj.get();
332: panels.put(manager, new java.lang.ref.WeakReference(this ));
333: initActionMap(null);
334: initListening();
335: } catch (SafeException se) {
336: throw se;
337: } catch (IOException ioe) {
338: throw new SafeException(ioe);
339: }
340: }
341:
342: // temporary workaround the issue #31244
343: private boolean delayActivatedNodes() {
344: if (scheduleAcivatedNodes == null) {
345: if (System.getProperty("netbeans.delay.tc") != null) { // NOI18N
346: scheduleAcivatedNodes = Boolean
347: .getBoolean("netbeans.delay.tc") ? Boolean.TRUE
348: : Boolean.FALSE; // NOI18N
349: } else {
350: scheduleAcivatedNodes = Boolean.FALSE;
351: }
352: }
353:
354: return scheduleAcivatedNodes.booleanValue();
355: }
356:
357: // schudule activation the nodes
358: private final void scheduleActivatedNodes(Node[] nodes) {
359: synchronized (this ) {
360: if (delayedSetter == null) {
361: delayedSetter = new DelayedSetter();
362: }
363: }
364:
365: delayedSetter.scheduleActivatedNodes(nodes);
366: }
367:
368: /** Listener on the explorer manager properties.
369: * Changes selected nodes of this frame.
370: */
371: private final class PropL extends Object implements
372: PropertyChangeListener {
373: PropL() {
374: }
375:
376: public void propertyChange(PropertyChangeEvent evt) {
377: if (evt.getSource() != manager) {
378: return;
379: }
380:
381: if (ExplorerManager.PROP_SELECTED_NODES.equals(evt
382: .getPropertyName())) {
383: if (delayActivatedNodes()) {
384: scheduleActivatedNodes(manager.getSelectedNodes());
385: } else {
386: setActivatedNodes(manager.getSelectedNodes());
387: }
388:
389: return;
390: }
391:
392: if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt
393: .getPropertyName())) {
394: updateTitle();
395:
396: return;
397: }
398: }
399: }
400:
401: private class DelayedSetter implements ActionListener {
402: private Node[] nodes;
403: private Timer timer;
404: private boolean firstChange = true;
405:
406: DelayedSetter() {
407: }
408:
409: public void scheduleActivatedNodes(Node[] nodes) {
410: synchronized (this ) {
411: this .nodes = nodes;
412:
413: if (timer == null) {
414: // start timer with INIT_DELAY
415: timer = new Timer(INIT_DELAY, this );
416: timer.setCoalesce(true);
417: timer.setRepeats(false);
418: }
419:
420: if (timer.isRunning()) {
421: // if timer is running then double init delay
422: if (timer.getInitialDelay() < MAX_DELAY) {
423: timer
424: .setInitialDelay(timer
425: .getInitialDelay() * 2);
426: }
427:
428: firstChange = false;
429: } else {
430: // the first change is set immediatelly
431: setActivatedNodes(nodes);
432: firstChange = true;
433: }
434:
435: // make sure timer is running
436: timer.restart();
437: }
438: }
439:
440: public void actionPerformed(ActionEvent evt) {
441: synchronized (this ) {
442: synchronized (this ) {
443: timer.stop();
444: }
445: }
446:
447: // set activated nodes for 2nd and next changes
448: if (!firstChange) {
449: setActivatedNodes(nodes);
450: }
451: }
452: }
453: }
|