001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.views.navigator;
011:
012: import java.util.ArrayList;
013: import java.util.Iterator;
014:
015: import org.eclipse.core.resources.IContainer;
016: import org.eclipse.core.resources.IResource;
017: import org.eclipse.core.runtime.IAdaptable;
018: import org.eclipse.core.runtime.IStatus;
019: import org.eclipse.core.runtime.MultiStatus;
020: import org.eclipse.core.runtime.Status;
021: import org.eclipse.jface.dialogs.ErrorDialog;
022: import org.eclipse.jface.dialogs.IDialogConstants;
023: import org.eclipse.jface.dialogs.MessageDialog;
024: import org.eclipse.jface.viewers.ISelection;
025: import org.eclipse.jface.viewers.IStructuredSelection;
026: import org.eclipse.jface.viewers.StructuredViewer;
027: import org.eclipse.osgi.util.NLS;
028: import org.eclipse.swt.dnd.DND;
029: import org.eclipse.swt.dnd.DropTargetEvent;
030: import org.eclipse.swt.dnd.FileTransfer;
031: import org.eclipse.swt.dnd.TransferData;
032: import org.eclipse.swt.widgets.Display;
033: import org.eclipse.swt.widgets.Shell;
034: import org.eclipse.ui.PlatformUI;
035: import org.eclipse.ui.actions.CopyFilesAndFoldersOperation;
036: import org.eclipse.ui.actions.MoveFilesAndFoldersOperation;
037: import org.eclipse.ui.actions.ReadOnlyStateChecker;
038: import org.eclipse.ui.dialogs.IOverwriteQuery;
039: import org.eclipse.ui.internal.views.navigator.ResourceNavigatorMessages;
040: import org.eclipse.ui.part.PluginDropAdapter;
041: import org.eclipse.ui.part.ResourceTransfer;
042:
043: /**
044: * Implements drop behaviour for drag and drop operations
045: * that land on the resource navigator.
046: *
047: * @since 2.0
048: */
049: public class NavigatorDropAdapter extends PluginDropAdapter implements
050: IOverwriteQuery {
051:
052: /**
053: * A flag indicating that overwrites should always occur.
054: */
055: private boolean alwaysOverwrite = false;
056:
057: /**
058: * The last valid operation.
059: */
060: private int lastValidOperation = DND.DROP_NONE;
061:
062: /**
063: * Constructs a new drop adapter.
064: *
065: * @param viewer the navigator's viewer
066: */
067: public NavigatorDropAdapter(StructuredViewer viewer) {
068: super (viewer);
069: }
070:
071: /*
072: * @see org.eclipse.swt.dnd.DropTargetListener#dragEnter(org.eclipse.swt.dnd.DropTargetEvent)
073: */
074: public void dragEnter(DropTargetEvent event) {
075: if (FileTransfer.getInstance().isSupportedType(
076: event.currentDataType)
077: && event.detail == DND.DROP_DEFAULT) {
078: // default to copy when dragging from outside Eclipse. Fixes bug 16308.
079: event.detail = DND.DROP_COPY;
080: }
081: super .dragEnter(event);
082: }
083:
084: /**
085: * Returns an error status with the given info.
086: */
087: private IStatus error(String message) {
088: return error(message, null);
089: }
090:
091: /**
092: * Returns an error status with the given info.
093: */
094: private IStatus error(String message, Throwable exception) {
095: return new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, 0,
096: message, exception);
097: }
098:
099: /**
100: * Returns the actual target of the drop, given the resource
101: * under the mouse. If the mouse target is a file, then the drop actually
102: * occurs in its parent. If the drop location is before or after the
103: * mouse target and feedback is enabled, the target is also the parent.
104: */
105: private IContainer getActualTarget(IResource mouseTarget) {
106: /* if cursor is before or after mouseTarget, set target to parent */
107: if (getFeedbackEnabled()) {
108: if (getCurrentLocation() == LOCATION_BEFORE
109: || getCurrentLocation() == LOCATION_AFTER) {
110: return mouseTarget.getParent();
111: }
112: }
113: /* if cursor is on a file, return the parent */
114: if (mouseTarget.getType() == IResource.FILE) {
115: return mouseTarget.getParent();
116: }
117: /* otherwise the mouseTarget is the real target */
118: return (IContainer) mouseTarget;
119: }
120:
121: /**
122: * Returns the display
123: */
124: private Display getDisplay() {
125: return getViewer().getControl().getDisplay();
126: }
127:
128: /**
129: * Returns the resource selection from the LocalSelectionTransfer.
130: *
131: * @return the resource selection from the LocalSelectionTransfer
132: */
133: private IResource[] getSelectedResources() {
134: ArrayList selectedResources = new ArrayList();
135:
136: ISelection selection = LocalSelectionTransfer.getInstance()
137: .getSelection();
138: if (selection instanceof IStructuredSelection) {
139: IStructuredSelection ssel = (IStructuredSelection) selection;
140: for (Iterator i = ssel.iterator(); i.hasNext();) {
141: Object o = i.next();
142: if (o instanceof IResource) {
143: selectedResources.add(o);
144: } else if (o instanceof IAdaptable) {
145: IAdaptable a = (IAdaptable) o;
146: IResource r = (IResource) a
147: .getAdapter(IResource.class);
148: if (r != null) {
149: selectedResources.add(r);
150: }
151: }
152: }
153: }
154: return (IResource[]) selectedResources
155: .toArray(new IResource[selectedResources.size()]);
156: }
157:
158: /**
159: * Returns the shell
160: */
161: private Shell getShell() {
162: return getViewer().getControl().getShell();
163: }
164:
165: /**
166: * Returns an error status with the given info.
167: */
168: private IStatus info(String message) {
169: return new Status(IStatus.INFO, PlatformUI.PLUGIN_ID, 0,
170: message, null);
171: }
172:
173: /**
174: * Adds the given status to the list of problems. Discards
175: * OK statuses. If the status is a multi-status, only its children
176: * are added.
177: */
178: private void mergeStatus(MultiStatus status, IStatus toMerge) {
179: if (!toMerge.isOK()) {
180: status.merge(toMerge);
181: }
182: }
183:
184: /**
185: * Returns an status indicating success.
186: */
187: private IStatus ok() {
188: return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, 0,
189: ResourceNavigatorMessages.DropAdapter_ok, null);
190: }
191:
192: /**
193: * Opens an error dialog if necessary. Takes care of
194: * complex rules necessary for making the error dialog look nice.
195: */
196: private void openError(IStatus status) {
197: if (status == null) {
198: return;
199: }
200:
201: String genericTitle = ResourceNavigatorMessages.DropAdapter_title;
202: int codes = IStatus.ERROR | IStatus.WARNING;
203:
204: //simple case: one error, not a multistatus
205: if (!status.isMultiStatus()) {
206: ErrorDialog.openError(getShell(), genericTitle, null,
207: status, codes);
208: return;
209: }
210:
211: //one error, single child of multistatus
212: IStatus[] children = status.getChildren();
213: if (children.length == 1) {
214: ErrorDialog.openError(getShell(), status.getMessage(),
215: null, children[0], codes);
216: return;
217: }
218: //several problems
219: ErrorDialog.openError(getShell(), genericTitle, null, status,
220: codes);
221: }
222:
223: /**
224: * Perform the drop.
225: * @see org.eclipse.swt.dnd.DropTargetListener#drop(org.eclipse.swt.dnd.DropTargetEvent)
226: */
227: public boolean performDrop(final Object data) {
228: alwaysOverwrite = false;
229: if (getCurrentTarget() == null || data == null) {
230: return false;
231: }
232: boolean result = false;
233: IStatus status = null;
234: IResource[] resources = null;
235: TransferData currentTransfer = getCurrentTransfer();
236: if (LocalSelectionTransfer.getInstance().isSupportedType(
237: currentTransfer)) {
238: resources = getSelectedResources();
239: } else if (ResourceTransfer.getInstance().isSupportedType(
240: currentTransfer)) {
241: resources = (IResource[]) data;
242: } else if (FileTransfer.getInstance().isSupportedType(
243: currentTransfer)) {
244: status = performFileDrop(data);
245: result = status.isOK();
246: } else {
247: result = NavigatorDropAdapter.super .performDrop(data);
248: }
249: if (resources != null && resources.length > 0) {
250: if (getCurrentOperation() == DND.DROP_COPY) {
251: status = performResourceCopy(getShell(), resources);
252: } else {
253: status = performResourceMove(resources);
254: }
255: }
256: openError(status);
257: return result;
258: }
259:
260: /**
261: * Performs a drop using the FileTransfer transfer type.
262: */
263: private IStatus performFileDrop(Object data) {
264: MultiStatus problems = new MultiStatus(PlatformUI.PLUGIN_ID, 0,
265: ResourceNavigatorMessages.DropAdapter_problemImporting,
266: null);
267: mergeStatus(problems, validateTarget(getCurrentTarget(),
268: getCurrentTransfer()));
269:
270: final IContainer target = getActualTarget((IResource) getCurrentTarget());
271: final String[] names = (String[]) data;
272: // Run the import operation asynchronously.
273: // Otherwise the drag source (e.g., Windows Explorer) will be blocked
274: // while the operation executes. Fixes bug 16478.
275: Display.getCurrent().asyncExec(new Runnable() {
276: public void run() {
277: getShell().forceActive();
278: CopyFilesAndFoldersOperation operation = new CopyFilesAndFoldersOperation(
279: getShell());
280: operation.copyFiles(names, target);
281: }
282: });
283: return problems;
284: }
285:
286: /**
287: * Performs a resource copy
288: */
289: private IStatus performResourceCopy(Shell shell, IResource[] sources) {
290: MultiStatus problems = new MultiStatus(PlatformUI.PLUGIN_ID, 1,
291: ResourceNavigatorMessages.DropAdapter_problemsMoving,
292: null);
293: mergeStatus(problems, validateTarget(getCurrentTarget(),
294: getCurrentTransfer()));
295:
296: IContainer target = getActualTarget((IResource) getCurrentTarget());
297: CopyFilesAndFoldersOperation operation = new CopyFilesAndFoldersOperation(
298: shell);
299: operation.copyResources(sources, target);
300:
301: return problems;
302: }
303:
304: /**
305: * Performs a resource move
306: */
307: private IStatus performResourceMove(IResource[] sources) {
308: MultiStatus problems = new MultiStatus(PlatformUI.PLUGIN_ID, 1,
309: ResourceNavigatorMessages.DropAdapter_problemsMoving,
310: null);
311: mergeStatus(problems, validateTarget(getCurrentTarget(),
312: getCurrentTransfer()));
313:
314: IContainer target = getActualTarget((IResource) getCurrentTarget());
315: ReadOnlyStateChecker checker = new ReadOnlyStateChecker(
316: getShell(),
317: ResourceNavigatorMessages.MoveResourceAction_title,
318: ResourceNavigatorMessages.MoveResourceAction_checkMoveMessage);
319: sources = checker.checkReadOnlyResources(sources);
320: MoveFilesAndFoldersOperation operation = new MoveFilesAndFoldersOperation(
321: getShell());
322: operation.copyResources(sources, target);
323:
324: return problems;
325: }
326:
327: /*
328: * @see org.eclipse.ui.dialogs.IOverwriteQuery#queryOverwrite(java.lang.String)
329: */
330: public String queryOverwrite(String pathString) {
331: if (alwaysOverwrite) {
332: return ALL;
333: }
334:
335: final String returnCode[] = { CANCEL };
336: final String msg = NLS.bind(
337: ResourceNavigatorMessages.DropAdapter_overwriteQuery,
338: pathString);
339: final String[] options = { IDialogConstants.YES_LABEL,
340: IDialogConstants.YES_TO_ALL_LABEL,
341: IDialogConstants.NO_LABEL,
342: IDialogConstants.CANCEL_LABEL };
343: getDisplay().syncExec(new Runnable() {
344: public void run() {
345: MessageDialog dialog = new MessageDialog(getShell(),
346: ResourceNavigatorMessages.DropAdapter_question,
347: null, msg, MessageDialog.QUESTION, options, 0);
348: dialog.open();
349: int returnVal = dialog.getReturnCode();
350: String[] returnCodes = { YES, ALL, NO, CANCEL };
351: returnCode[0] = returnVal < 0 ? CANCEL
352: : returnCodes[returnVal];
353: }
354: });
355: if (returnCode[0] == ALL) {
356: alwaysOverwrite = true;
357: }
358: return returnCode[0];
359: }
360:
361: /**
362: * This method is used to notify the action that some aspect of
363: * the drop operation has changed.
364: */
365: public boolean validateDrop(Object target, int dragOperation,
366: TransferData transferType) {
367:
368: if (dragOperation != DND.DROP_NONE) {
369: lastValidOperation = dragOperation;
370: }
371: if (FileTransfer.getInstance().isSupportedType(transferType)
372: && lastValidOperation != DND.DROP_COPY) {
373: // only allow copying when dragging from outside Eclipse
374: return false;
375: }
376: if (super .validateDrop(target, dragOperation, transferType)) {
377: return true;
378: }
379: return validateTarget(target, transferType).isOK();
380: }
381:
382: /**
383: * Ensures that the drop target meets certain criteria
384: */
385: private IStatus validateTarget(Object target,
386: TransferData transferType) {
387: if (!(target instanceof IResource)) {
388: return info(ResourceNavigatorMessages.DropAdapter_targetMustBeResource);
389: }
390: IResource resource = (IResource) target;
391: if (!resource.isAccessible()) {
392: return error(ResourceNavigatorMessages.DropAdapter_canNotDropIntoClosedProject);
393: }
394: IContainer destination = getActualTarget(resource);
395: if (destination.getType() == IResource.ROOT) {
396: return error(ResourceNavigatorMessages.DropAdapter_resourcesCanNotBeSiblings);
397: }
398: String message = null;
399: // drag within Eclipse?
400: if (LocalSelectionTransfer.getInstance().isSupportedType(
401: transferType)) {
402: IResource[] selectedResources = getSelectedResources();
403:
404: if (selectedResources.length == 0) {
405: message = ResourceNavigatorMessages.DropAdapter_dropOperationErrorOther;
406: } else {
407: CopyFilesAndFoldersOperation operation;
408: if (lastValidOperation == DND.DROP_COPY) {
409: operation = new CopyFilesAndFoldersOperation(
410: getShell());
411: } else {
412: operation = new MoveFilesAndFoldersOperation(
413: getShell());
414: }
415: message = operation.validateDestination(destination,
416: selectedResources);
417: }
418: } // file import?
419: else if (FileTransfer.getInstance().isSupportedType(
420: transferType)) {
421: String[] sourceNames = (String[]) FileTransfer
422: .getInstance().nativeToJava(transferType);
423: if (sourceNames == null) {
424: // source names will be null on Linux. Use empty names to do destination validation.
425: // Fixes bug 29778
426: sourceNames = new String[0];
427: }
428: CopyFilesAndFoldersOperation copyOperation = new CopyFilesAndFoldersOperation(
429: getShell());
430: message = copyOperation.validateImportDestination(
431: destination, sourceNames);
432: }
433: if (message != null) {
434: return error(message);
435: }
436: return ok();
437: }
438: }
|