KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > explorer > ExplorerActionsImpl


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.openide.explorer;
21
22 import java.awt.Toolkit JavaDoc;
23 import java.awt.datatransfer.Clipboard JavaDoc;
24 import java.awt.datatransfer.StringSelection JavaDoc;
25 import java.awt.datatransfer.Transferable JavaDoc;
26 import java.awt.datatransfer.UnsupportedFlavorException JavaDoc;
27 import java.awt.event.ActionEvent JavaDoc;
28 import java.awt.event.ActionListener JavaDoc;
29 import java.beans.PropertyChangeEvent JavaDoc;
30 import java.beans.PropertyChangeListener JavaDoc;
31 import java.beans.PropertyVetoException JavaDoc;
32 import java.io.IOException JavaDoc;
33 import java.util.Arrays JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.logging.Level JavaDoc;
36 import java.util.logging.Logger JavaDoc;
37 import javax.swing.AbstractAction JavaDoc;
38 import javax.swing.Action JavaDoc;
39 import javax.swing.Timer JavaDoc;
40 import org.netbeans.modules.openide.explorer.ExternalDragAndDrop;
41 import org.openide.DialogDisplayer;
42 import org.openide.NotifyDescriptor;
43 import org.openide.nodes.Node;
44 import org.openide.util.Exceptions;
45 import org.openide.util.Lookup;
46 import org.openide.util.Mutex;
47 import org.openide.util.NbBundle;
48 import org.openide.util.WeakListeners;
49 import org.openide.util.datatransfer.ClipboardEvent;
50 import org.openide.util.datatransfer.ClipboardListener;
51 import org.openide.util.datatransfer.ExClipboard;
52 import org.openide.util.datatransfer.ExTransferable;
53 import org.openide.util.datatransfer.MultiTransferObject;
54 import org.openide.util.datatransfer.PasteType;
55
56 /**
57  * This class contains the default implementation of reactions to the standard
58  * explorer actions. It can be attached to any {@link ExplorerManager}. Then
59  * this class will listen to changes of selected nodes or the explored context
60  * of that manager, and update the state of cut/copy/paste/delete actions. <P>
61  * An instance of this class can only be attached to one manager at a time. Use
62  * {@link #attach} and {@link #detach} to make the connection.
63  *
64  * @author Jan Jancura, Petr Hamernik, Ian Formanek, Jaroslav Tulach
65  */

66 final class ExplorerActionsImpl {
67     /** copy action performer */
68     private final CopyCutActionPerformer copyActionPerformer = new CopyCutActionPerformer(true);
69
70     /** cut action performer */
71     private final CopyCutActionPerformer cutActionPerformer = new CopyCutActionPerformer(false);
72
73     /** delete action performer */
74     private final DeleteActionPerformer deleteActionPerformerConfirm = new DeleteActionPerformer(true);
75
76     /** delete action performer no confirm */
77     private final DeleteActionPerformer deleteActionPerformerNoConfirm = new DeleteActionPerformer(false);
78
79     /** own paste action */
80     private final OwnPaste pasteActionPerformer = new OwnPaste();
81     private ActionStateUpdater actionStateUpdater;
82
83     /** the manager we are listening on */
84     private ExplorerManager manager;
85
86     /** Creates new instance with a decision whether the action should update
87      * performers (the old behaviour) or only set the state of cut,copy,delete,
88      * and paste actions.
89      */

90     ExplorerActionsImpl() {
91     }
92
93     //
94
// Implementation
95
//
96

97     /** Getter for the copy action.
98      */

99     final Action JavaDoc copyAction() {
100         return copyActionPerformer;
101     }
102
103     /** The cut action */
104     final Action JavaDoc cutAction() {
105         return cutActionPerformer;
106     }
107
108     /** The delete action
109      */

110     final Action JavaDoc deleteAction(boolean confirm) {
111         return confirm ? deleteActionPerformerConfirm : deleteActionPerformerNoConfirm;
112     }
113
114     /** Own paste action
115      */

116     final Action JavaDoc pasteAction() {
117         return pasteActionPerformer;
118     }
119
120     /** Attach to new manager.
121      * @param m the manager to listen on
122      */

123     public synchronized void attach(ExplorerManager m) {
124         if (manager != null) {
125             // first of all detach
126
detach();
127         }
128
129         manager = m;
130
131         // Sets action state updater and registers listening on manager and
132
// exclipboard.
133
actionStateUpdater = new ActionStateUpdater();
134         manager.addPropertyChangeListener(WeakListeners.propertyChange(actionStateUpdater, manager));
135
136         Clipboard JavaDoc c = getClipboard();
137
138         if (c instanceof ExClipboard) {
139             ExClipboard clip = (ExClipboard) c;
140             clip.addClipboardListener(
141                 WeakListeners.create(
142                     ClipboardListener.class, actionStateUpdater, clip
143                 )
144             );
145         }
146
147         updateActions();
148     }
149
150     /** Detach from manager currently being listened on. */
151     public synchronized void detach() {
152         if (manager == null) {
153             return;
154         }
155
156         // Unregisters (weak) listening on manager and exclipboard (see attach).
157
actionStateUpdater = null;
158
159         stopActions();
160
161         manager = null;
162     }
163
164     /** Stops listening on all actions */
165     private void stopActions() {
166         if (copyActionPerformer != null) {
167             copyActionPerformer.setEnabled(false);
168             cutActionPerformer.setEnabled(false);
169             deleteActionPerformerConfirm.setEnabled(false);
170             deleteActionPerformerNoConfirm.setEnabled(false);
171             pasteActionPerformer.setEnabled(false);
172         }
173     }
174
175     /** Updates the state of all actions.
176      * @param path list of selected nodes
177      */

178     private void updateActions() {
179         if (manager == null) {
180             return;
181         }
182
183         Node[] path = manager.getSelectedNodes();
184
185         int i;
186         int k = (path != null) ? path.length : 0;
187
188         if (k > 0) {
189             boolean incest = false;
190
191             if (k > 1) {
192                 // Do a special check for parenthood. Affects delete (for a long time),
193
// copy (#13418), cut (#13426). If one node is a parent of another,
194
// assume that the situation is sketchy and prevent it.
195
// For k==1 it is impossible so do not waste time on it.
196
HashMap JavaDoc<Node, Object JavaDoc> allNodes = new HashMap JavaDoc<Node, Object JavaDoc>(101);
197
198                 for (i = 0; i < k; i++) {
199                     if (!checkParents(path[i], allNodes)) {
200                         incest = true;
201
202                         break;
203                     }
204                 }
205             }
206
207             for (i = 0; i < k; i++) {
208                 if (incest || !path[i].canCopy()) {
209                     copyActionPerformer.setEnabled(false);
210
211                     break;
212                 }
213             }
214
215             if (i == k) {
216                 copyActionPerformer.setEnabled(true);
217             }
218
219             for (i = 0; i < k; i++) {
220                 if (incest || !path[i].canCut()) {
221                     cutActionPerformer.setEnabled(false);
222
223                     break;
224                 }
225             }
226
227             if (i == k) {
228                 cutActionPerformer.setEnabled(true);
229             }
230
231             for (i = 0; i < k; i++) {
232                 if (incest || !path[i].canDestroy()) {
233                     deleteActionPerformerConfirm.setEnabled(false);
234                     deleteActionPerformerNoConfirm.setEnabled(false);
235
236                     break;
237                 }
238             }
239
240             if (i == k) {
241                 deleteActionPerformerConfirm.setEnabled(true);
242                 deleteActionPerformerNoConfirm.setEnabled(true);
243             }
244         } else { // k==0, i.e. no nodes selected
245
copyActionPerformer.setEnabled(false);
246             cutActionPerformer.setEnabled(false);
247             deleteActionPerformerConfirm.setEnabled(false);
248             deleteActionPerformerNoConfirm.setEnabled(false);
249         }
250
251         updatePasteAction(path);
252     }
253
254     /** Adds all parent nodes into the set.
255      * @param set set of all nodes
256      * @param node the node to check
257      * @return false if one of the nodes is parent of another
258      */

259     private boolean checkParents(Node node, HashMap JavaDoc<Node, Object JavaDoc> set) {
260         if (set.get(node) != null) {
261             return false;
262         }
263
264         // this signals that this node is the original one
265
set.put(node, this);
266
267         for (;;) {
268             node = node.getParentNode();
269
270             if (node == null) {
271                 return true;
272             }
273
274             if (set.put(node, node) == this) {
275                 // our parent is a node that is also in the set
276
return false;
277             }
278         }
279     }
280
281     /** Updates paste action.
282     * @param path selected nodes
283     */

284     private void updatePasteAction(Node[] path) {
285         ExplorerManager man = manager;
286
287         if (man == null) {
288             pasteActionPerformer.setPasteTypes(null);
289
290             return;
291         }
292
293         if ((path != null) && (path.length > 1)) {
294             pasteActionPerformer.setPasteTypes(null);
295
296             return;
297         } else {
298             Node node = man.getExploredContext();
299             Node[] selectedNodes = man.getSelectedNodes();
300
301             if ((selectedNodes != null) && (selectedNodes.length == 1)) {
302                 node = selectedNodes[0];
303             }
304
305             if (node != null) {
306                 Transferable JavaDoc trans = getClipboard().getContents(this);
307                 updatePasteTypes(trans, node);
308             }
309         }
310     }
311
312     /** Actually updates paste types. */
313     private void updatePasteTypes(Transferable JavaDoc trans, Node pan) {
314         if (trans != null) {
315             // First, just ask the node if it likes this transferable, whatever it may be.
316
// If it does, then fine.
317
PasteType[] pasteTypes = (pan == null) ? new PasteType[] { } : pan.getPasteTypes(trans);
318
319             if (pasteTypes.length != 0) {
320                 pasteActionPerformer.setPasteTypes(pasteTypes);
321
322                 return;
323             }
324
325             if (trans.isDataFlavorSupported(ExTransferable.multiFlavor)) {
326                 // The node did not accept this multitransfer as is--try to break it into
327
// individual transfers and paste them in sequence instead.
328
try {
329                     MultiTransferObject obj = (MultiTransferObject) trans.getTransferData(ExTransferable.multiFlavor);
330                     int count = obj.getCount();
331                     boolean ok = true;
332                     Transferable JavaDoc[] t = new Transferable JavaDoc[count];
333                     PasteType[] p = new PasteType[count];
334
335                     for (int i = 0; i < count; i++) {
336                         t[i] = obj.getTransferableAt(i);
337                         pasteTypes = (pan == null) ? new PasteType[] { } : pan.getPasteTypes(t[i]);
338
339                         if (pasteTypes.length == 0) {
340                             ok = false;
341
342                             break;
343                         }
344
345                         // [PENDING] this is ugly! ideally should be some way of comparing PasteType's for similarity?
346
p[i] = pasteTypes[0];
347                     }
348
349                     if (ok) {
350                         PasteType[] arrOfPaste = new PasteType[] { new MultiPasteType(t, p) };
351                         pasteActionPerformer.setPasteTypes(arrOfPaste);
352
353                         return;
354                     }
355                 } catch (UnsupportedFlavorException JavaDoc e) {
356                     // [PENDING] notify?!
357
} catch (IOException JavaDoc e) {
358                     // [PENDING] notify?!
359
}
360             }
361         }
362
363         pasteActionPerformer.setPasteTypes(null);
364     }
365
366     /** If our clipboard is not found return the default system clipboard. */
367     private static Clipboard JavaDoc getClipboard() {
368         Clipboard JavaDoc c = Lookup.getDefault().lookup(Clipboard JavaDoc.class);
369
370         if (c == null) {
371             c = Toolkit.getDefaultToolkit().getSystemClipboard();
372         }
373
374         return c;
375     }
376
377     /** Updates actions state via updater (if the updater is present). */
378     private void updateActionsState() {
379         ActionStateUpdater asu;
380
381         synchronized (this) {
382             asu = actionStateUpdater;
383         }
384
385         if (asu != null) {
386             asu.update();
387         }
388     }
389
390     /** Paste type used when in clipbopard is MultiTransferable */
391     private static class MultiPasteType extends PasteType {
392         /** Array of transferables */
393         Transferable JavaDoc[] t;
394
395         /** Array of paste types */
396         PasteType[] p;
397
398         /** Constructs new MultiPasteType for the given content of the clipboard */
399         MultiPasteType(Transferable JavaDoc[] t, PasteType[] p) {
400             this.t = t;
401             this.p = p;
402         }
403
404         /** Performs the paste action.
405         * @return Transferable which should be inserted into the clipboard after
406         * paste action. It can be null, which means that clipboard content
407         * should be cleared.
408         */

409         public Transferable JavaDoc paste() throws IOException JavaDoc {
410             int size = p.length;
411             Transferable JavaDoc[] arr = new Transferable JavaDoc[size];
412
413             for (int i = 0; i < size; i++) {
414                 Transferable JavaDoc newTransferable = p[i].paste();
415
416                 if (newTransferable != null) {
417                     arr[i] = newTransferable;
418                 } else {
419                     // keep the orginal
420
arr[i] = t[i];
421                 }
422             }
423
424             return new ExTransferable.Multi(arr);
425         }
426     }
427
428     /** Own implementation of paste action
429      */

430     private class OwnPaste extends AbstractAction JavaDoc {
431         private PasteType[] pasteTypes;
432
433         OwnPaste() {
434         }
435
436         public boolean isEnabled() {
437             updateActionsState();
438
439             return super.isEnabled();
440         }
441
442         public void setPasteTypes(PasteType[] arr) {
443             synchronized (this) {
444                 this.pasteTypes = arr;
445             }
446
447             setEnabled(arr != null);
448         }
449
450         public void actionPerformed(ActionEvent JavaDoc e) {
451             PasteType[] arr = this.pasteTypes;
452             throw new IllegalStateException JavaDoc(
453                 "Should not be invoked at all. Paste types: " + (arr == null ? null : Arrays.asList(arr)) // NOI18N
454
);
455         }
456
457         public Object JavaDoc getValue(String JavaDoc s) {
458             updateActionsState();
459
460             if ("delegates".equals(s)) { // NOI18N
461

462                 return pasteTypes;
463             }
464
465             return super.getValue(s);
466         }
467     }
468
469     /** Class which performs copy and cut actions */
470     private class CopyCutActionPerformer extends AbstractAction JavaDoc {
471         /** determine if adapter is used for copy or cut action. */
472         private boolean copyCut;
473
474         /** Create new adapter */
475         public CopyCutActionPerformer(boolean b) {
476             copyCut = b;
477         }
478
479         public boolean isEnabled() {
480             updateActionsState();
481
482             return super.isEnabled();
483         }
484
485         public void actionPerformed(ActionEvent JavaDoc ev) {
486             Transferable JavaDoc trans = null;
487             Node[] sel = manager.getSelectedNodes();
488
489             if (sel.length != 1) {
490                 Transferable JavaDoc[] arrayTrans = new Transferable JavaDoc[sel.length];
491
492                 for (int i = 0; i < sel.length; i++)
493                     if ((arrayTrans[i] = getTransferableOwner(sel[i])) == null) {
494                         return;
495                     }
496
497                 trans = ExternalDragAndDrop.maybeAddExternalFileDnd( new ExTransferable.Multi(arrayTrans) );
498             } else {
499                 trans = getTransferableOwner(sel[0]);
500             }
501
502             if (trans != null) {
503                 Clipboard JavaDoc clipboard = getClipboard();
504
505                 clipboard.setContents(trans, new StringSelection JavaDoc("")); // NOI18N
506
}
507         }
508
509         private Transferable JavaDoc getTransferableOwner(Node node) {
510             try {
511                 return copyCut ? node.clipboardCopy() : node.clipboardCut();
512             } catch (IOException JavaDoc e) {
513                 Logger.getLogger(ExplorerActionsImpl.class.getName()).log(Level.WARNING, null, e);
514
515                 return null;
516             }
517         }
518     }
519
520     /** Class which performs delete action */
521     private class DeleteActionPerformer extends AbstractAction JavaDoc implements Runnable JavaDoc {
522         private boolean confirmDelete;
523
524         DeleteActionPerformer(boolean confirmDelete) {
525             this.confirmDelete = confirmDelete;
526         }
527
528         public boolean isEnabled() {
529             updateActionsState();
530
531             return super.isEnabled();
532         }
533
534         public void actionPerformed(ActionEvent JavaDoc ev) {
535             final Node[] sel = manager.getSelectedNodes();
536
537             if ((sel == null) || (sel.length == 0)) {
538                 return;
539             }
540
541             // perform action if confirmed
542
if (!confirmDelete || doConfirm(sel)) {
543                 // clear selected nodes
544
try {
545                     if (manager != null) {
546                         manager.setSelectedNodes(new Node[] { });
547                     }
548                 } catch (PropertyVetoException JavaDoc e) {
549                     // never thrown, setting empty selected nodes cannot be vetoed
550
}
551
552                 doDestroy(sel);
553
554                 // disables the action in AWT thread
555
Mutex.EVENT.readAccess(this);
556             }
557         }
558
559         /** Disables the action.
560          */

561         public void run() {
562             setEnabled(false);
563         }
564
565         private boolean doConfirm(Node[] sel) {
566             String JavaDoc message;
567             String JavaDoc title;
568             boolean customDelete = true;
569
570             for (int i = 0; i < sel.length; i++) {
571                 if (!Boolean.TRUE.equals(sel[i].getValue("customDelete"))) { // NOI18N
572
customDelete = false;
573
574                     break;
575                 }
576             }
577
578             if (customDelete) {
579                 return true;
580             }
581
582             if (sel.length == 1) {
583                 message = NbBundle.getMessage(
584                         ExplorerActionsImpl.class, "MSG_ConfirmDeleteObject", sel[0].getDisplayName()
585                     );
586                 title = NbBundle.getMessage(ExplorerActionsImpl.class, "MSG_ConfirmDeleteObjectTitle");
587             } else {
588                 message = NbBundle.getMessage(
589                         ExplorerActionsImpl.class, "MSG_ConfirmDeleteObjects", Integer.valueOf(sel.length)
590                     );
591                 title = NbBundle.getMessage(ExplorerActionsImpl.class, "MSG_ConfirmDeleteObjectsTitle");
592             }
593
594             NotifyDescriptor desc = new NotifyDescriptor.Confirmation(message, title, NotifyDescriptor.YES_NO_OPTION);
595
596             return NotifyDescriptor.YES_OPTION.equals(DialogDisplayer.getDefault().notify(desc));
597         }
598
599         private void doDestroy(final Node[] sel) {
600             for (int i = 0; i < sel.length; i++) {
601                 try {
602                     sel[i].destroy();
603                 }
604                 catch (IOException JavaDoc e) {
605                     Exceptions.printStackTrace(e);
606                 }
607             }
608         }
609
610     }
611
612     /** Class which register changes in manager, and clipboard, coalesces
613      * them if they are frequent and performs the update of actions state. */

614     private class ActionStateUpdater implements PropertyChangeListener JavaDoc, ClipboardListener, ActionListener JavaDoc, Runnable JavaDoc {
615         private final Timer JavaDoc timer;
616         private boolean planned;
617
618         ActionStateUpdater() {
619             timer = new FixIssue29405Timer(150, this);
620             timer.setCoalesce(true);
621             timer.setRepeats(false);
622         }
623
624         public synchronized void propertyChange(PropertyChangeEvent JavaDoc e) {
625             timer.restart();
626             planned = true;
627         }
628
629         public void clipboardChanged(ClipboardEvent ev) {
630             if (!ev.isConsumed()) {
631                 Mutex.EVENT.readAccess(this);
632             }
633         }
634
635         public void run() {
636             ExplorerManager em = manager;
637
638             if (em != null) {
639                 updatePasteAction(em.getSelectedNodes());
640             }
641         }
642
643         public void actionPerformed(ActionEvent JavaDoc evt) {
644             updateActions();
645
646             synchronized (this) {
647                 timer.stop();
648                 planned = false;
649             }
650         }
651
652         /** Updates actions states now if there is pending event. */
653         public void update() {
654             boolean update;
655
656             synchronized (this) {
657                 update = planned;
658             }
659
660             if (update) {
661                 timer.stop();
662                 updateActions();
663             }
664         }
665     }
666
667     /** Timer which fixes problem with running status (issue #29405). */
668     private static class FixIssue29405Timer extends Timer JavaDoc {
669         private boolean running;
670
671         public FixIssue29405Timer(int delay, ActionListener JavaDoc l) {
672             super(delay, l);
673         }
674
675         public void restart() {
676             super.restart();
677             running = true;
678         }
679
680         public void stop() {
681             running = false;
682             super.stop();
683         }
684
685         public boolean isRunning() {
686             return running;
687         }
688     }
689      // End of FixIssue29405Timer class.
690
}
691
Popular Tags