/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on Oct 29, 2006 * @author Fabio */ package org.python.pydev.navigator.ui; import java.io.File; import java.net.URI; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.jface.util.LocalSelectionTransfer; import org.eclipse.jface.viewers.IElementComparer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.TreePath; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.TransferData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Item; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IMemento; import org.eclipse.ui.IURIEditorInput; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkingSet; import org.eclipse.ui.PartInitException; import org.eclipse.ui.internal.AggregateWorkingSet; import org.eclipse.ui.internal.navigator.ContributorTrackingSet; import org.eclipse.ui.internal.navigator.NavigatorContentService; import org.eclipse.ui.internal.navigator.dnd.CommonDropAdapterDescriptor; import org.eclipse.ui.internal.navigator.dnd.CommonDropDescriptorManager; import org.eclipse.ui.internal.navigator.dnd.NavigatorDnDService; import org.eclipse.ui.navigator.CommonDragAdapter; import org.eclipse.ui.navigator.CommonDropAdapter; import org.eclipse.ui.navigator.CommonDropAdapterAssistant; import org.eclipse.ui.navigator.CommonNavigator; import org.eclipse.ui.navigator.CommonViewer; import org.eclipse.ui.navigator.INavigatorContentService; import org.eclipse.ui.navigator.INavigatorDnDService; import org.eclipse.ui.navigator.INavigatorPipelineService; import org.eclipse.ui.navigator.PipelinedShapeModification; import org.eclipse.ui.part.IShowInTarget; import org.eclipse.ui.part.ShowInContext; import org.python.pydev.core.log.Log; import org.python.pydev.navigator.LabelAndImage; import org.python.pydev.navigator.actions.PythonLinkHelper; import org.python.pydev.navigator.elements.IWrappedResource; import org.python.pydev.shared_core.callbacks.CallbackWithListeners; import org.python.pydev.shared_core.callbacks.ICallbackWithListeners; import org.python.pydev.shared_core.string.StringUtils; import org.python.pydev.shared_core.structure.LinkedListWarningOnSlowOperations; import org.python.pydev.shared_core.structure.TreeNode; import org.python.pydev.shared_ui.editor_input.PydevZipFileEditorInput; import org.python.pydev.shared_ui.editor_input.PydevZipFileStorage; import org.python.pydev.shared_ui.utils.IViewWithControls; import org.python.pydev.ui.NotifyViewCreated; /** * This class is the package explorer for pydev. It uses the CNF (Common Navigator Framework) to show * the resources as python elements. */ @SuppressWarnings({ "restriction", "rawtypes", "unchecked" }) public class PydevPackageExplorer extends CommonNavigator implements IShowInTarget, IViewWithControls { public static class PydevNavigatorContentService extends NavigatorContentService { private INavigatorDnDService pyNavigatorDnDService; public PydevNavigatorContentService(String aViewerId, StructuredViewer aViewer) { super(aViewerId, aViewer); } @Override public INavigatorDnDService getDnDService() { if (pyNavigatorDnDService == null) { pyNavigatorDnDService = new PydevNavigatorDnDService(this); } return pyNavigatorDnDService; } } public static class PydevNavigatorDnDService extends NavigatorDnDService { private static final CommonDropAdapterAssistant[] NO_ASSISTANTS = new CommonDropAdapterAssistant[0]; private INavigatorContentService pyContentService; private CommonDropAdapter pyDropAdapter; private final Map pyDropAssistants = new HashMap(); public PydevNavigatorDnDService(INavigatorContentService aContentService) { super(aContentService); pyContentService = aContentService; } @Override public void setDropAdaptor(CommonDropAdapter da) { super.setDropAdaptor(da); pyDropAdapter = da; } // This method performs the ultimate goal of choosing PyDev drop assistants over others, // to ensure that PYTHONPATH updates happen. private CommonDropAdapterAssistant[] pySortAssistants(CommonDropAdapterAssistant[] array) { Arrays.sort(array, new Comparator() { @Override public int compare(Object arg0, Object arg1) { CommonDropAdapterAssistant a = (CommonDropAdapterAssistant) arg0; CommonDropAdapterAssistant b = (CommonDropAdapterAssistant) arg1; // This is to ensure that the PyDev drop assistant will always // be chosen over non-PyDev ones, if a conflict ever occurs. String id = "org.python.pydev.navigator.actions"; //$NON-NLS-1$ if (a.getClass().getName().startsWith(id)) { return -1; } if (b.getClass().getName().startsWith(id)) { return 1; } return a.getClass().getName().compareTo(b.getClass().getName()); } }); return array; } // These methods are here just so pySortAssistants can function properly. They are more // or less the same as the methods they override (or in the case of private methods, mimic). @Override public CommonDropAdapterAssistant[] findCommonDropAdapterAssistants( Object aDropTarget, TransferData aTransferType) { CommonDropAdapterDescriptor[] descriptors = CommonDropDescriptorManager .getInstance().findCommonDropAdapterAssistants(aDropTarget, pyContentService); if (descriptors.length == 0) { return NO_ASSISTANTS; } if (LocalSelectionTransfer.getTransfer().isSupportedType(aTransferType) && LocalSelectionTransfer.getTransfer().getSelection() instanceof IStructuredSelection) { return pyGetAssistantsBySelection(descriptors, (IStructuredSelection) LocalSelectionTransfer .getTransfer().getSelection()); } return pyGetAssistantsByTransferData(descriptors, aTransferType); } @Override public CommonDropAdapterAssistant[] findCommonDropAdapterAssistants( Object aDropTarget, IStructuredSelection theDragSelection) { CommonDropAdapterDescriptor[] descriptors = CommonDropDescriptorManager .getInstance().findCommonDropAdapterAssistants(aDropTarget, pyContentService); if (descriptors.length == 0) { return NO_ASSISTANTS; } return pyGetAssistantsBySelection(descriptors, theDragSelection); } private CommonDropAdapterAssistant[] pyGetAssistantsByTransferData( CommonDropAdapterDescriptor[] descriptors, TransferData aTransferType) { Set assistants = new LinkedHashSet(); for (int i = 0; i < descriptors.length; i++) { CommonDropAdapterAssistant asst = pyGetAssistant(descriptors[i]); if (asst.isSupportedType(aTransferType)) { assistants.add(asst); } } return pySortAssistants((CommonDropAdapterAssistant[]) assistants .toArray(new CommonDropAdapterAssistant[assistants.size()])); } private CommonDropAdapterAssistant[] pyGetAssistantsBySelection( CommonDropAdapterDescriptor[] descriptors, IStructuredSelection aSelection) { Set assistants = new LinkedHashSet(); for (int i = 0; i < descriptors.length; i++) { if (descriptors[i].areDragElementsSupported(aSelection)) { assistants.add(pyGetAssistant(descriptors[i])); } } return pySortAssistants((CommonDropAdapterAssistant[]) assistants .toArray(new CommonDropAdapterAssistant[assistants.size()])); } private CommonDropAdapterAssistant pyGetAssistant( CommonDropAdapterDescriptor descriptor) { CommonDropAdapterAssistant asst = (CommonDropAdapterAssistant) pyDropAssistants .get(descriptor); if (asst != null) { return asst; } synchronized (pyDropAssistants) { asst = (CommonDropAdapterAssistant) pyDropAssistants.get(descriptor); if (asst == null) { pyDropAssistants.put(descriptor, (asst = descriptor .createDropAssistant())); asst.init(pyContentService); asst.setCommonDropAdapter(pyDropAdapter); } } return asst; } } /** * This viewer is the one used instead of the common viewer -- should only be used to fix failures in the base class. */ public static class PydevCommonViewer extends CommonViewer { /** * This is used so that we only restore the memento in the 'right' place */ public boolean availableToRestoreMemento = false; /** * This is the pydev package explorer */ private PydevPackageExplorer pydevPackageExplorer; public PydevPackageExplorer getPydevPackageExplorer() { return pydevPackageExplorer; } private PydevNavigatorContentService pyContentService; public PydevCommonViewer(String id, Composite parent, int style, PydevPackageExplorer pydevPackageExplorer) { super(id, parent, style); this.pydevPackageExplorer = pydevPackageExplorer; //We need to be able to compare actual resources and IWrappedResources //as if they were the same thing. setComparer(new IElementComparer() { @Override public int hashCode(Object element) { if (element instanceof IWrappedResource) { IWrappedResource wrappedResource = (IWrappedResource) element; return wrappedResource.getActualObject().hashCode(); } return element.hashCode(); } @Override public boolean equals(Object a, Object b) { if (a instanceof IWrappedResource) { IWrappedResource wrappedResource = (IWrappedResource) a; a = wrappedResource.getActualObject(); } if (b instanceof IWrappedResource) { IWrappedResource wrappedResource = (IWrappedResource) b; b = wrappedResource.getActualObject(); } if (a == null) { if (b == null) { return true; } else { return false; } } if (b == null) { return false; } return a.equals(b); } }); } @Override protected void init() { pyContentService = new PydevNavigatorContentService("org.python.pydev.navigator.view", this); super.init(); } @Override protected void initDragAndDrop() { int operations = DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK; CommonDragAdapter dragAdapter = createDragAdapter(); addDragSupport(operations, dragAdapter.getSupportedDragTransfers(), dragAdapter); CommonDropAdapter dropAdapter = createDropAdapter(); addDropSupport(operations, dropAdapter.getSupportedDropTransfers(), dropAdapter); // Set the drop adaptor of the PyDev content service instead of the standard one, // which shouldn't be used for drop policies. NavigatorDnDService dnd = (NavigatorDnDService) pyContentService.getDnDService(); dnd.setDropAdaptor(dropAdapter); } // The only thing that needs the new content service is the drop adapter, because it // sets the DnDService. @Override protected CommonDropAdapter createDropAdapter() { return new CommonDropAdapter(pyContentService, this); } @Override public void dispose() { if (pyContentService != null) { pyContentService.dispose(); } super.dispose(); } /** * Returns the tree path for the given item. * * It's overridden because when using mylyn, the paths may be expanded but not shown, so segment is null * -- that's why we return null if a given segment is null (instead of the assert that it contains in the superclass) * @since 3.2 */ @Override protected TreePath getTreePathFromItem(Item item) { LinkedList<Object> segments = new LinkedListWarningOnSlowOperations<Object>(); while (item != null) { Object segment = item.getData(); if (segment == null) { return null; } segments.addFirst(segment); item = getParentItem(item); } return new TreePath(segments.toArray()); } public IWorkingSet[] getSelectedWorkingSets() { Object input = getInput(); if (input instanceof AggregateWorkingSet) { return ((AggregateWorkingSet) input).getComponents(); } return null; } } /** * This is the memento to be used. */ private IMemento memento; public final ICallbackWithListeners onControlCreated = new CallbackWithListeners(); public final ICallbackWithListeners onControlDisposed = new CallbackWithListeners(); private PydevCommonViewer viewer; private final PythonLinkHelper pythonLinkHelper = new PythonLinkHelper(); public PydevPackageExplorer() { super(); NotifyViewCreated.notifyViewCreated(this); } /** * Overridden to keep the memento to be used later (it's private in the superclass). */ @Override public void init(IViewSite aSite, IMemento aMemento) throws PartInitException { super.init(aSite, aMemento); memento = aMemento; } /** * Overridden to create our viewer and not the superclass CommonViewer. * * (Unfortunately, the superclass does a little more than creating it, so, we have to do those operations here * too -- that's why we have to keep the memento object in the init method). */ @Override protected CommonViewer createCommonViewer(Composite aParent) { //super.createCommonViewer(aParent); -- don't even call the super class CommonViewer aViewer = new PydevCommonViewer(getViewSite().getId(), aParent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, this); initListeners(aViewer); //commented: we do that only after the part is completely created (because otherwise the state is reverted later) //aViewer.getNavigatorContentService().restoreState(memento); return aViewer; } /** * Overridden because if the state is not restored as the last thing, it is reverted back to the previous state. */ @Override public void createPartControl(Composite aParent) { super.createPartControl(aParent); PydevCommonViewer viewer = (PydevCommonViewer) getCommonViewer(); this.viewer = viewer; onControlCreated.call(viewer); viewer.availableToRestoreMemento = true; for (int i = 0; i < 3; i++) { try { //I don't know why the 1st time we restore it it doesn't work... so, we have to do it twice //(and the other 1 is because we may have an exception in the 1st step). viewer.getNavigatorContentService().restoreState(memento); } catch (Exception e1) { if (i > 1) { Log.log("Unable to restore the state of the Pydev Package Explorer.", e1); } } } } @Override public void dispose() { if (viewer != null) { onControlDisposed.call(viewer); viewer = null; } super.dispose(); }; /** * Returns the element contained in the EditorInput */ Object getElementOfInput(IEditorInput input) { if (input instanceof IFileEditorInput) { return ((IFileEditorInput) input).getFile(); } if (input instanceof IURIEditorInput) { IURIEditorInput iuriEditorInput = (IURIEditorInput) input; URI uri = iuriEditorInput.getURI(); return new File(uri); } if (input instanceof PydevZipFileEditorInput) { PydevZipFileEditorInput pydevZipFileEditorInput = (PydevZipFileEditorInput) input; try { IStorage storage = pydevZipFileEditorInput.getStorage(); if (storage instanceof PydevZipFileStorage) { PydevZipFileStorage pydevZipFileStorage = (PydevZipFileStorage) storage; return pydevZipFileStorage; } } catch (CoreException e) { Log.log(e); } } return null; } /** * Implements the 'show in...' action */ @Override public boolean show(ShowInContext context) { Object elementOfInput = null; ISelection selection = context.getSelection(); if (selection instanceof IStructuredSelection) { IStructuredSelection structuredSelection = ((IStructuredSelection) selection); if (structuredSelection.size() == 1) { elementOfInput = structuredSelection.getFirstElement(); } } Object input = context.getInput(); if (input instanceof IEditorInput) { elementOfInput = getElementOfInput((IEditorInput) context.getInput()); } return elementOfInput != null && tryToReveal(elementOfInput); } /** * This is the method that actually tries to reveal some item in the tree. * * It will go through the pipeline to see if the actual object to reveal has been replaced in the replace pipeline. */ public boolean tryToReveal(Object element) { element = getPythonModelElement(element); if (element instanceof PydevZipFileStorage) { pythonLinkHelper.setCommonViewer(this.getCommonViewer()); PydevZipFileStorage pydevZipFileStorage = (PydevZipFileStorage) element; IStructuredSelection externalFileSelectionInTree = pythonLinkHelper .findExternalFileSelection(pydevZipFileStorage.zipFile); if (externalFileSelectionInTree != null && !externalFileSelectionInTree.isEmpty()) { Object firstElement = externalFileSelectionInTree.getFirstElement(); if (firstElement instanceof TreeNode) { TreeNode treeNode = (TreeNode) firstElement; //Ok, got to the zip file, let's try to find the path below it... String zipPath = pydevZipFileStorage.zipPath; List<String> split = StringUtils.split(zipPath, '/'); for (String string : split) { List<TreeNode> children = treeNode.getChildren(); for (TreeNode<LabelAndImage> child : children) { if (string.equals(child.getData().label)) { treeNode = child; break; //Goes on to the next substring... } } } if (revealAndVerify(new StructuredSelection(treeNode))) { return true; } } else { Log.log("Expected a TreeNode. Found: " + firstElement); //Just go on to show the zip, not the internal contents... if (revealAndVerify(externalFileSelectionInTree)) { return true; } } } } else if (element instanceof File) { pythonLinkHelper.setCommonViewer(this.getCommonViewer()); IStructuredSelection externalFileSelectionInTree = pythonLinkHelper .findExternalFileSelection((File) element); if (externalFileSelectionInTree != null && !externalFileSelectionInTree.isEmpty()) { if (revealAndVerify(externalFileSelectionInTree)) { return true; } } } //null is checked in the revealAndVerify function if (revealAndVerify(element)) { return true; } //if it is a wrapped resource that we couldn't show, try to reveal as a resource... if (element instanceof IAdaptable && !(element instanceof IResource)) { IAdaptable adaptable = (IAdaptable) element; IResource resource = adaptable.getAdapter(IResource.class); if (resource != null) { if (revealAndVerify(resource)) { return true; } } } return false; } /** * @param element the element that should be gotten as an element from the pydev model * @return a pydev element or the same element passed as a parameter. */ private Object getPythonModelElement(Object element) { if (element instanceof IWrappedResource) { return element; } INavigatorPipelineService pipelineService = this.getNavigatorContentService().getPipelineService(); if (element instanceof IAdaptable) { IAdaptable adaptable = (IAdaptable) element; IFile file = adaptable.getAdapter(IFile.class); if (file != null) { HashSet<Object> files = new ContributorTrackingSet( (NavigatorContentService) this.getNavigatorContentService()); files.add(file); pipelineService.interceptAdd(new PipelinedShapeModification(file.getParent(), files)); if (files.size() > 0) { element = files.iterator().next(); } } } return element; } /** * Tries to reveal some selection * @return if it revealed the selection correctly (and false otherwise) */ private boolean revealAndVerify(Object element) { if (element == null) { return false; } if (element instanceof ISelection) { selectReveal((ISelection) element); } else { selectReveal(new StructuredSelection(element)); } return !getSite().getSelectionProvider().getSelection().isEmpty(); } @Override public ICallbackWithListeners getOnControlCreated() { return onControlCreated; } @Override public ICallbackWithListeners getOnControlDisposed() { return onControlDisposed; } }