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:
042: package org.openide.explorer.view;
043:
044: import java.awt.Toolkit;
045: import java.awt.datatransfer.Clipboard;
046: import java.awt.datatransfer.Transferable;
047: import java.awt.datatransfer.UnsupportedFlavorException;
048: import java.awt.dnd.DnDConstants;
049: import java.awt.event.ActionEvent;
050: import java.awt.event.ActionListener;
051: import java.io.IOException;
052: import java.util.ArrayList;
053: import java.util.Arrays;
054: import java.util.Iterator;
055: import java.util.List;
056: import java.util.TreeSet;
057: import javax.swing.JMenuItem;
058: import javax.swing.JPopupMenu;
059: import javax.swing.SwingUtilities;
060: import javax.swing.tree.TreeNode;
061: import org.netbeans.modules.openide.explorer.ExternalDragAndDrop;
062: import org.openide.DialogDisplayer;
063: import org.openide.NotifyDescriptor;
064: import org.openide.NotifyDescriptor.Message;
065: import org.openide.awt.Mnemonics;
066: import org.openide.nodes.Node;
067: import org.openide.util.Exceptions;
068: import org.openide.util.Lookup;
069: import org.openide.util.NbBundle;
070: import org.openide.util.UserCancelException;
071: import org.openide.util.datatransfer.ExClipboard;
072: import org.openide.util.datatransfer.ExTransferable;
073: import org.openide.util.datatransfer.ExTransferable.Multi;
074: import org.openide.util.datatransfer.MultiTransferObject;
075: import org.openide.util.datatransfer.PasteType;
076:
077: /** Class that provides methods for common tasks needed during
078: * drag and drop when working with explorer views.
079: *
080: * @author Dafe Simonek
081: */
082: final class DragDropUtilities extends Object {
083: static final boolean dragAndDropEnabled = Boolean
084: .parseBoolean(System.getProperty("netbeans.dnd.enabled",
085: "true")); // NOI18N
086: static final int NODE_UP = -1;
087: static final int NODE_CENTRAL = 0;
088: static final int NODE_DOWN = 1;
089: static Runnable postDropRun = null;
090:
091: // helper constants
092: static final int NoDrag = 0;
093: static final int NoDrop = 1;
094:
095: /** No need to instantiate this class */
096: private DragDropUtilities() {
097: }
098:
099: /** Utility method.
100: * @return true if given node supports given action,
101: * false otherwise.
102: */
103: static boolean checkNodeForAction(Node node, int dragAction) {
104: if (node.canCut()
105: && ((dragAction == DnDConstants.ACTION_MOVE) || (dragAction == DnDConstants.ACTION_COPY_OR_MOVE))) {
106: return true;
107: }
108:
109: if (node.canCopy()
110: && ((dragAction == DnDConstants.ACTION_COPY)
111: || (dragAction == DnDConstants.ACTION_COPY_OR_MOVE)
112: || (dragAction == DnDConstants.ACTION_LINK) || (dragAction == DnDConstants.ACTION_REFERENCE))) {
113: return true;
114: }
115:
116: // hmmm, conditions not satisfied..
117: return false;
118: }
119:
120: /** Gets right transferable of given nodes (according to given
121: * drag action) and also converts the transferable.<br>
122: * Can be called only with correct action constant.
123: * @return The transferable.
124: */
125: static Transferable getNodeTransferable(Node[] nodes, int dragAction)
126: throws IOException {
127: Transferable[] tArray = new Transferable[nodes.length];
128:
129: for (int i = 0; i < nodes.length; i++) {
130: if ((dragAction & DnDConstants.ACTION_MOVE) != 0) {
131: tArray[i] = nodes[i].clipboardCut();
132: } else {
133: tArray[i] = nodes[i].drag();
134: }
135: }
136: Transferable result;
137: if (tArray.length == 1) {
138: // only one node, so return regular single transferable
139: result = tArray[0];
140: } else {
141: // enclose the transferables into multi transferable
142: result = ExternalDragAndDrop
143: .maybeAddExternalFileDnd(new Multi(tArray));
144: }
145:
146: Clipboard c = getClipboard();
147: if (c instanceof ExClipboard) {
148: return ((ExClipboard) c).convert(result);
149: } else {
150: return result;
151: }
152: }
153:
154: /** Returns transferable of given node
155: * @return The transferable.
156: */
157: static Transferable getNodeTransferable(Node node, int dragAction)
158: throws IOException {
159: return getNodeTransferable(new Node[] { node }, dragAction);
160: }
161:
162: /** Sets a runnable it will be executed after drop action is performed.
163: * @param run a runnable for execution */
164: static void setPostDropRun(Runnable run) {
165: postDropRun = run;
166: }
167:
168: /* Invokes the stored runnable if it is there and than set to null.
169: */
170: static private void invokePostDropRun() {
171: if (postDropRun != null) {
172: SwingUtilities.invokeLater(postDropRun);
173: postDropRun = null;
174: }
175: }
176:
177: /**
178: * Performs the drop. Performs paste on given paste type.
179: * (part of bugfix #37279, performPaste returns array of new nodes in target folder)
180: * @param type paste type
181: * @param targetFolder target folder for given paste type, can be null
182: * @return array of new added nodes in target folder
183: */
184: static Node[] performPaste(PasteType type, Node targetFolder) {
185: //System.out.println("performing drop...."+type); // NOI18N
186: try {
187: if (targetFolder == null) {
188: // call paste action
189: type.paste();
190:
191: return new Node[] {};
192: }
193:
194: Node[] preNodes = targetFolder.getChildren().getNodes(true);
195:
196: // call paste action
197: type.paste();
198:
199: Node[] postNodes = targetFolder.getChildren()
200: .getNodes(true);
201:
202: // calculate new nodes
203: List<Node> pre = Arrays.asList(preNodes);
204: List<Node> post = Arrays.asList(postNodes);
205: Iterator<Node> it = post.iterator();
206: List<Node> diff = new ArrayList<Node>();
207:
208: while (it.hasNext()) {
209: Node n = it.next();
210:
211: if (!pre.contains(n)) {
212: diff.add(n);
213: }
214: }
215:
216: return diff.toArray(new Node[diff.size()]);
217:
218: /*Clipboard clipboard = T opManager.getDefault().getClipboard();
219: if (trans != null) {
220: ClipboardOwner owner = trans instanceof ClipboardOwner ?
221: (ClipboardOwner)trans
222: :
223: new StringSelection ("");
224: clipboard.setContents(trans, owner);
225: }*/
226: } catch (UserCancelException exc) {
227: // ignore - user just pressed cancel in some dialog....
228: return new Node[] {};
229: } catch (IOException e) {
230: Exceptions.printStackTrace(e);
231:
232: return new Node[] {};
233: }
234: }
235:
236: /** Returns array of paste types for given transferable.
237: * If given transferable contains multiple transferables,
238: * multi paste type which encloses paste types of all contained
239: * transferables is returned.
240: * Returns empty array if given node did not accepted the transferable
241: * (or some sub-transferables in multi transferable)
242: *
243: * @param node given node to ask fro paste types
244: * @param trans transferable to discover
245: */
246: static PasteType[] getPasteTypes(Node node, Transferable trans) {
247: if (!trans.isDataFlavorSupported(ExTransferable.multiFlavor)) {
248: // only single, so return paste types
249: PasteType[] pt = null;
250:
251: try {
252: pt = node.getPasteTypes(trans);
253: } catch (NullPointerException npe) {
254: Exceptions.printStackTrace(npe);
255:
256: // there are not paste types
257: }
258:
259: return pt;
260: } else {
261: // multi transferable, we must do extra work
262: try {
263: MultiTransferObject obj = (MultiTransferObject) trans
264: .getTransferData(ExTransferable.multiFlavor);
265: int count = obj.getCount();
266: Transferable[] t = new Transferable[count];
267: PasteType[] p = new PasteType[count];
268: PasteType[] curTypes = null;
269:
270: // extract default paste types of transferables
271: for (int i = 0; i < count; i++) {
272: t[i] = obj.getTransferableAt(i);
273: curTypes = node.getPasteTypes(t[i]);
274:
275: // return if not accepted
276: if (curTypes.length == 0) {
277: return curTypes;
278: }
279:
280: p[i] = curTypes[0];
281: }
282:
283: // return new multi paste type
284: return new PasteType[] { new MultiPasteType(t, p) };
285: } catch (UnsupportedFlavorException e) {
286: // ignore and return empty array
287: } catch (IOException e) {
288: // ignore and return empty array
289: }
290: }
291:
292: return new PasteType[0];
293: }
294:
295: /** Returns drop type for given transferable and drop action
296: * If given transferable contains multiple transferables,
297: * multi paste type which encloses drop types of all contained
298: * transferables is returned.
299: * Returns null if given node did not accepted the transferable
300: * (or some sub-transferables in multi transferable)
301: *
302: * @param node given node to ask fro paste types
303: * @param trans transferable to discover
304: * @param action drop action
305: * @param dropIndex where the object is dropped
306: */
307: static PasteType getDropType(Node node, Transferable trans,
308: int action, int dropIndex) {
309: PasteType paste = null;
310:
311: try {
312: paste = node.getDropType(trans, action, dropIndex);
313: if (paste != null)
314: return paste;
315: } catch (NullPointerException npe) {
316: Exceptions.printStackTrace(npe);
317: }
318:
319: if (trans.isDataFlavorSupported(ExTransferable.multiFlavor)) {
320: // multi transferable, we must do extra work
321: try {
322: MultiTransferObject obj = (MultiTransferObject) trans
323: .getTransferData(ExTransferable.multiFlavor);
324: int count = obj.getCount();
325: Transferable[] t = new Transferable[count];
326: PasteType[] p = new PasteType[count];
327: PasteType pt = null;
328:
329: // extract default drop type of transferables
330: for (int i = 0; i < count; i++) {
331: t[i] = obj.getTransferableAt(i);
332: pt = node.getDropType(t[i], action, dropIndex);
333:
334: // return null if not accepted
335: if (pt == null) {
336: return pt;
337: }
338:
339: p[i] = pt;
340: }
341:
342: // return new multi drop type
343: return new MultiPasteType(t, p);
344: } catch (UnsupportedFlavorException e) {
345: // ignore and return null
346: } catch (IOException e) {
347: // ignore and return null
348: }
349: }
350:
351: return null;
352: }
353:
354: /** Notifies user that the drop was not succesfull. */
355: static void dropNotSuccesfull() {
356: DialogDisplayer.getDefault().notify(
357: new Message(NbBundle.getBundle(
358: TreeViewDropSupport.class).getString(
359: "MSG_NoPasteTypes"),
360: NotifyDescriptor.WARNING_MESSAGE));
361: }
362:
363: /** If our clipboard is not found return the default system clipboard. */
364: private static Clipboard getClipboard() {
365: Clipboard c = (Clipboard) Lookup.getDefault().lookup(
366: Clipboard.class);
367:
368: if (c == null) {
369: c = Toolkit.getDefaultToolkit().getSystemClipboard();
370: }
371:
372: return c;
373: }
374:
375: /** Utility method created by Enno Sandner. Is it needed?
376: * I don't know (dstrupl).
377: */
378: static Node secureFindNode(Object o) {
379: assert o instanceof TreeNode : "Object " + o
380: + " is instanceof TreeNode";
381: try {
382: return Visualizer.findNode(o);
383: } catch (ClassCastException e) {
384: e.printStackTrace();
385:
386: return null;
387: }
388: }
389:
390: /**
391: * Creates and populates popup as a result of
392: * dropping an item.
393: * @author Enno Sandner
394: */
395: static JPopupMenu createDropFinishPopup(final TreeSet pasteTypes) {
396: JPopupMenu menu = new JPopupMenu();
397:
398: //System.arraycopy(pasteTypes, 0, pasteTypes_, 0, pasteTypes.length);
399: final JMenuItem[] items_ = new JMenuItem[pasteTypes.size()];
400:
401: ActionListener aListener = new ActionListener() {
402: public void actionPerformed(ActionEvent e) {
403: JMenuItem source = (JMenuItem) e.getSource();
404:
405: final Iterator it = pasteTypes.iterator();
406:
407: for (int i = 0; it.hasNext(); i++) {
408: PasteType action = (PasteType) it.next();
409:
410: if (items_[i].equals(source)) {
411: DragDropUtilities.performPaste(action, null);
412: invokePostDropRun();
413:
414: break;
415: }
416: }
417: }
418: };
419:
420: Iterator it = pasteTypes.iterator();
421:
422: for (int i = 0; it.hasNext(); i++) {
423: items_[i] = new JMenuItem();
424: Mnemonics.setLocalizedText(items_[i], ((PasteType) it
425: .next()).getName());
426: items_[i].addActionListener(aListener);
427: menu.add(items_[i]);
428: }
429:
430: menu.addSeparator();
431:
432: JMenuItem abortItem = new JMenuItem(NbBundle.getBundle(
433: DragDropUtilities.class).getString("MSG_ABORT"));
434: menu.add(abortItem);
435:
436: return menu;
437: }
438:
439: /** Paste type used when in clipbopard is MultiTransferable */
440: static final class MultiPasteType extends PasteType {
441: // Attributes
442:
443: /** Array of transferables */
444: Transferable[] t;
445:
446: /** Array of paste types */
447: PasteType[] p;
448:
449: // Operations
450:
451: /** Constructs new MultiPasteType for the given
452: * transferables and paste types.*/
453: MultiPasteType(Transferable[] t, PasteType[] p) {
454: this .t = t;
455: this .p = p;
456: }
457:
458: /** Performs the paste action.
459: * @return Transferable which should be inserted into the
460: * clipboard after paste action. It can be null, which means
461: * that clipboard content should be cleared.
462: */
463: public Transferable paste() throws IOException {
464: int size = p.length;
465: Transferable[] arr = new Transferable[size];
466:
467: // perform paste for all source transferables
468: for (int i = 0; i < size; i++) {
469: //System.out.println("Pasting #" + i); // NOI18N
470: arr[i] = p[i].paste();
471: }
472:
473: return new Multi(arr);
474: }
475: }
476: // end of MultiPasteType
477: }
|