2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 package com
.intellij
.ide
.projectView
.impl
;
22 import com
.intellij
.ide
.DataManager
;
23 import com
.intellij
.ide
.PsiCopyPasteManager
;
24 import com
.intellij
.ide
.SelectInTarget
;
25 import com
.intellij
.ide
.dnd
.aware
.DnDAwareTree
;
26 import com
.intellij
.ide
.favoritesTreeView
.FavoritesTreeViewPanel
;
27 import com
.intellij
.ide
.projectView
.BaseProjectTreeBuilder
;
28 import com
.intellij
.ide
.projectView
.ProjectView
;
29 import com
.intellij
.ide
.projectView
.impl
.nodes
.AbstractModuleNode
;
30 import com
.intellij
.ide
.projectView
.impl
.nodes
.AbstractProjectNode
;
31 import com
.intellij
.ide
.projectView
.impl
.nodes
.ModuleGroupNode
;
32 import com
.intellij
.ide
.util
.treeView
.*;
33 import com
.intellij
.openapi
.Disposable
;
34 import com
.intellij
.openapi
.actionSystem
.DataConstants
;
35 import com
.intellij
.openapi
.actionSystem
.DataContext
;
36 import com
.intellij
.openapi
.actionSystem
.DataProvider
;
37 import com
.intellij
.openapi
.actionSystem
.DefaultActionGroup
;
38 import com
.intellij
.openapi
.application
.ApplicationManager
;
39 import com
.intellij
.openapi
.diagnostic
.Logger
;
40 import com
.intellij
.openapi
.extensions
.ExtensionPointName
;
41 import com
.intellij
.openapi
.module
.Module
;
42 import com
.intellij
.openapi
.project
.Project
;
43 import com
.intellij
.openapi
.roots
.ModuleRootManager
;
44 import com
.intellij
.openapi
.util
.*;
45 import com
.intellij
.openapi
.vfs
.VirtualFile
;
46 import com
.intellij
.openapi
.wm
.ToolWindowId
;
47 import com
.intellij
.openapi
.wm
.ToolWindowManager
;
48 import com
.intellij
.pom
.Navigatable
;
49 import com
.intellij
.psi
.*;
50 import com
.intellij
.util
.ArrayUtil
;
51 import com
.intellij
.util
.ReflectionCache
;
52 import com
.intellij
.util
.containers
.HashMap
;
53 import com
.intellij
.util
.ui
.tree
.TreeUtil
;
54 import org
.jdom
.Element
;
55 import org
.jetbrains
.annotations
.NonNls
;
56 import org
.jetbrains
.annotations
.NotNull
;
57 import org
.jetbrains
.annotations
.Nullable
;
60 import javax
.swing
.tree
.DefaultMutableTreeNode
;
61 import javax
.swing
.tree
.TreeNode
;
62 import javax
.swing
.tree
.TreePath
;
63 import java
.awt
.datatransfer
.DataFlavor
;
64 import java
.awt
.datatransfer
.Transferable
;
65 import java
.awt
.dnd
.*;
66 import java
.util
.ArrayList
;
67 import java
.util
.Collections
;
68 import java
.util
.List
;
72 public abstract class AbstractProjectViewPane
implements DataProvider
, Disposable
{
73 public static ExtensionPointName
<AbstractProjectViewPane
> EP_NAME
= ExtensionPointName
.create("com.intellij.projectViewPane");
75 protected final Project myProject
;
76 private Runnable myTreeChangeListener
;
77 protected DnDAwareTree myTree
;
78 protected AbstractTreeStructure myTreeStructure
;
79 private AbstractTreeBuilder myTreeBuilder
;
80 // subId->Tree state; key may be null
81 private final Map
<String
,TreeState
> myReadTreeState
= new HashMap
<String
, TreeState
>();
82 private String mySubId
;
83 @NonNls private static final String ELEMENT_SUBPANE
= "subPane";
84 @NonNls private static final String ATTRIBUTE_SUBID
= "subId";
86 protected AbstractProjectViewPane(Project project
) {
90 protected final void fireTreeChangeListener() {
91 if (myTreeChangeListener
!= null) myTreeChangeListener
.run();
94 public final void setTreeChangeListener(Runnable listener
) {
95 myTreeChangeListener
= listener
;
98 public final void removeTreeChangeListener() {
99 myTreeChangeListener
= null;
102 public abstract String
getTitle();
103 public abstract Icon
getIcon();
104 @NotNull public abstract String
getId();
105 @Nullable public final String
getSubId(){
109 public final void setSubId(@Nullable String subId
) {
114 public boolean isInitiallyVisible() {
119 * @return all supported sub views IDs.
120 * should return empty array if there is no subViews as in Project/Packages view.
122 @NotNull public String
[] getSubIds(){
123 return ArrayUtil
.EMPTY_STRING_ARRAY
;
126 @NotNull public String
getPresentableSubIdName(@NotNull final String subId
) {
127 throw new IllegalStateException("should not call");
129 public abstract JComponent
createComponent();
130 public JComponent
getComponentToFocus() {
133 public void expand(@Nullable final Object
[] path
, final boolean requestFocus
){
134 if (getTreeBuilder() == null || path
== null) return;
135 getTreeBuilder().buildNodeForPath(path
);
137 DefaultMutableTreeNode node
= getTreeBuilder().getNodeForPath(path
);
141 TreePath treePath
= new TreePath(node
.getPath());
142 myTree
.expandPath(treePath
);
144 myTree
.requestFocus();
146 TreeUtil
.selectPath(myTree
, treePath
);
149 public void dispose() {
150 setTreeBuilder(null);
152 myTreeStructure
= null;
155 public abstract ActionCallback
updateFromRoot(boolean restoreExpandedPaths
);
157 public abstract void select(Object element
, VirtualFile file
, boolean requestFocus
);
158 public void selectModule(final Module module
, final boolean requestFocus
) {
159 doSelectModuleOrGroup(module
, requestFocus
);
162 private void doSelectModuleOrGroup(final Object toSelect
, final boolean requestFocus
) {
163 ToolWindowManager windowManager
=ToolWindowManager
.getInstance(myProject
);
164 final Runnable runnable
= new Runnable() {
166 ProjectView projectView
= ProjectView
.getInstance(myProject
);
168 projectView
.changeView(getId(), getSubId());
170 ((BaseProjectTreeBuilder
)getTreeBuilder()).selectInWidth(toSelect
, requestFocus
, new Condition
<AbstractTreeNode
>(){
171 public boolean value(final AbstractTreeNode node
) {
172 return node
instanceof AbstractModuleNode
|| node
instanceof ModuleGroupNode
|| node
instanceof AbstractProjectNode
;
178 windowManager
.getToolWindow(ToolWindowId
.PROJECT_VIEW
).activate(runnable
);
185 public void selectModuleGroup(ModuleGroup moduleGroup
, boolean requestFocus
) {
186 doSelectModuleOrGroup(moduleGroup
, requestFocus
);
189 public TreePath
[] getSelectionPaths() {
190 return myTree
== null ?
null : myTree
.getSelectionPaths();
193 public void addToolbarActions(DefaultActionGroup actionGroup
) {
197 protected <T
extends NodeDescriptor
> List
<T
> getSelectedNodes(final Class
<T
> nodeClass
){
198 TreePath
[] paths
= getSelectionPaths();
199 if (paths
== null) return Collections
.emptyList();
200 final ArrayList
<T
> result
= new ArrayList
<T
>();
201 for (TreePath path
: paths
) {
202 Object lastPathComponent
= path
.getLastPathComponent();
203 if (lastPathComponent
instanceof DefaultMutableTreeNode
) {
204 DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)lastPathComponent
;
205 Object userObject
= node
.getUserObject();
206 if (userObject
!= null && ReflectionCache
.isAssignable(nodeClass
, userObject
.getClass())) {
207 result
.add((T
)userObject
);
214 public Object
getData(String dataId
) {
215 if (DataConstants
.NAVIGATABLE_ARRAY
.equals(dataId
)) {
216 TreePath
[] paths
= getSelectionPaths();
217 if (paths
== null) return null;
218 final ArrayList
<Navigatable
> navigatables
= new ArrayList
<Navigatable
>();
219 for (TreePath path
: paths
) {
220 Object lastPathComponent
= path
.getLastPathComponent();
221 if (lastPathComponent
instanceof DefaultMutableTreeNode
) {
222 DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)lastPathComponent
;
223 Object userObject
= node
.getUserObject();
224 if (userObject
instanceof Navigatable
) {
225 navigatables
.add((Navigatable
)userObject
);
227 else if (node
instanceof Navigatable
) {
228 navigatables
.add((Navigatable
)node
);
232 if (navigatables
.isEmpty()) {
236 return navigatables
.toArray(new Navigatable
[navigatables
.size()]);
239 if (myTreeStructure
instanceof AbstractTreeStructureBase
) {
240 return ((AbstractTreeStructureBase
) myTreeStructure
).getDataFromProviders(getSelectedNodes(AbstractTreeNode
.class), dataId
);
245 // used for sorting tabs in the tabbed pane
246 public abstract int getWeight();
248 public abstract SelectInTarget
createSelectInTarget();
250 public final TreePath
getSelectedPath() {
251 final TreePath
[] paths
= getSelectionPaths();
252 if (paths
!= null && paths
.length
== 1) return paths
[0];
256 public final NodeDescriptor
getSelectedDescriptor() {
257 final DefaultMutableTreeNode node
= getSelectedNode();
258 if (node
== null) return null;
259 Object userObject
= node
.getUserObject();
260 if (userObject
instanceof NodeDescriptor
) {
261 return (NodeDescriptor
)userObject
;
266 public final DefaultMutableTreeNode
getSelectedNode() {
267 TreePath path
= getSelectedPath();
271 Object lastPathComponent
= path
.getLastPathComponent();
272 if (!(lastPathComponent
instanceof DefaultMutableTreeNode
)) {
275 return (DefaultMutableTreeNode
)lastPathComponent
;
278 public final Object
getSelectedElement() {
279 final Object
[] elements
= getSelectedElements();
280 return elements
.length
== 1 ? elements
[0] : null;
284 public final PsiElement
[] getSelectedPSIElements() {
285 List
<PsiElement
> psiElements
= new ArrayList
<PsiElement
>();
286 for (Object element
: getSelectedElements()) {
287 final PsiElement psiElement
= getPSIElement(element
);
288 if (psiElement
!= null) {
289 psiElements
.add(psiElement
);
292 return psiElements
.toArray(new PsiElement
[psiElements
.size()]);
296 protected PsiElement
getPSIElement(@Nullable final Object element
) {
297 if (element
instanceof PsiElement
) {
298 PsiElement psiElement
= (PsiElement
)element
;
299 if (psiElement
.isValid()) {
307 public final Object
[] getSelectedElements() {
308 TreePath
[] paths
= getSelectionPaths();
309 if (paths
== null) return PsiElement
.EMPTY_ARRAY
;
310 ArrayList
<Object
> list
= new ArrayList
<Object
>(paths
.length
);
311 for (TreePath path
: paths
) {
312 Object lastPathComponent
= path
.getLastPathComponent();
313 if (lastPathComponent
instanceof TreeNode
) {
314 Object element
= getElement((TreeNode
)lastPathComponent
);
315 if (element
!= null) {
320 return ArrayUtil
.toObjectArray(list
);
324 private Object
getElement(@Nullable final TreeNode treeNode
) {
325 if (treeNode
instanceof DefaultMutableTreeNode
) {
326 DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)treeNode
;
327 return exhumeElementFromNode(node
);
332 private TreeNode
[] getSelectedTreeNodes(){
333 TreePath
[] paths
= getSelectionPaths();
334 if (paths
== null) return null;
335 final List
<TreeNode
> result
= new ArrayList
<TreeNode
>();
336 for (TreePath path
: paths
) {
337 Object lastPathComponent
= path
.getLastPathComponent();
338 if (lastPathComponent
instanceof DefaultMutableTreeNode
) {
339 result
.add ( (TreeNode
) lastPathComponent
);
342 return result
.toArray(new TreeNode
[result
.size()]);
346 protected Object
exhumeElementFromNode(final DefaultMutableTreeNode node
) {
347 return extractUserObject(node
);
350 public static Object
extractUserObject(DefaultMutableTreeNode node
) {
351 Object userObject
= node
.getUserObject();
352 Object element
= null;
353 if (userObject
instanceof AbstractTreeNode
) {
354 AbstractTreeNode descriptor
= (AbstractTreeNode
)userObject
;
355 element
= descriptor
.getValue();
357 else if (userObject
instanceof NodeDescriptor
) {
358 NodeDescriptor descriptor
= (NodeDescriptor
)userObject
;
359 element
= descriptor
.getElement();
360 if (element
instanceof AbstractTreeNode
) {
361 element
= ((AbstractTreeNode
)element
).getValue();
364 else if (userObject
!= null) {
365 element
= userObject
;
370 public AbstractTreeBuilder
getTreeBuilder() {
371 return myTreeBuilder
;
374 public void readExternal(Element element
) throws InvalidDataException
{
375 List
<Element
> subPanes
= element
.getChildren(ELEMENT_SUBPANE
);
376 for (Element subPane
: subPanes
) {
377 String subId
= subPane
.getAttributeValue(ATTRIBUTE_SUBID
);
378 TreeState treeState
= new TreeState();
379 treeState
.readExternal(subPane
);
380 myReadTreeState
.put(subId
, treeState
);
384 public void writeExternal(Element element
) throws WriteExternalException
{
386 for (String subId
: myReadTreeState
.keySet()) {
387 TreeState treeState
= myReadTreeState
.get(subId
);
388 Element subPane
= new Element(ELEMENT_SUBPANE
);
390 subPane
.setAttribute(ATTRIBUTE_SUBID
, subId
);
392 treeState
.writeExternal(subPane
);
393 element
.addContent(subPane
);
397 void saveExpandedPaths() {
398 if (myTree
!= null) {
399 TreeState treeState
= TreeState
.createOn(myTree
);
400 myReadTreeState
.put(getSubId(), treeState
);
404 public final void restoreExpandedPaths(){
405 TreeState treeState
= myReadTreeState
.get(getSubId());
406 if (treeState
!= null) {
407 treeState
.applyTo(myTree
);
411 public void installComparator() {
412 final ProjectView projectView
= ProjectView
.getInstance(myProject
);
413 getTreeBuilder().setNodeDescriptorComparator(new GroupByTypeComparator(projectView
, getId()));
416 public JTree
getTree() {
420 public PsiDirectory
[] getSelectedDirectories() {
421 final PsiElement
[] elements
= getSelectedPSIElements();
422 if (elements
.length
== 1) {
423 final PsiElement element
= elements
[0];
424 if (element
instanceof PsiDirectory
) {
425 return new PsiDirectory
[]{(PsiDirectory
)element
};
427 else if (element
instanceof PsiDirectoryContainer
) {
428 return ((PsiDirectoryContainer
)element
).getDirectories();
431 final PsiFile containingFile
= element
.getContainingFile();
432 if (containingFile
!= null) {
433 final PsiDirectory psiDirectory
= containingFile
.getContainingDirectory();
434 return psiDirectory
!= null ?
new PsiDirectory
[]{psiDirectory
} : PsiDirectory
.EMPTY_ARRAY
;
439 final DefaultMutableTreeNode selectedNode
= getSelectedNode();
440 if (selectedNode
!= null) {
441 return getSelectedDirectoriesInAmbiguousCase(selectedNode
);
444 return PsiDirectory
.EMPTY_ARRAY
;
447 protected PsiDirectory
[] getSelectedDirectoriesInAmbiguousCase(@NotNull final DefaultMutableTreeNode node
) {
448 final Object userObject
= node
.getUserObject();
449 if (userObject
instanceof AbstractModuleNode
) {
450 final ModuleRootManager moduleRootManager
= ModuleRootManager
.getInstance(((AbstractModuleNode
)userObject
).getValue());
451 final VirtualFile
[] sourceRoots
= moduleRootManager
.getSourceRoots();
452 List
<PsiDirectory
> dirs
= new ArrayList
<PsiDirectory
>(sourceRoots
.length
);
453 final PsiManager psiManager
= PsiManager
.getInstance(myProject
);
454 for (final VirtualFile sourceRoot
: sourceRoots
) {
455 final PsiDirectory directory
= psiManager
.findDirectory(sourceRoot
);
456 if (directory
!= null) {
460 return dirs
.toArray(new PsiDirectory
[dirs
.size()]);
462 return PsiDirectory
.EMPTY_ARRAY
;
467 public static final DataFlavor
[] FLAVORS
;
468 private static final Logger LOG
= Logger
.getInstance("com.intellij.ide.projectView.ProjectViewImpl");
469 private final MyDragSourceListener myDragSourceListener
= new MyDragSourceListener();
472 DataFlavor
[] flavors
;
474 final Class aClass
= MyTransferable
.class;
475 flavors
= new DataFlavor
[]{new DataFlavor(
476 DataFlavor
.javaJVMLocalObjectMimeType
+ ";class=" + aClass
.getName(),
477 FavoritesTreeViewPanel
.ABSTRACT_TREE_NODE_TRANSFERABLE
,
478 aClass
.getClassLoader()
481 catch (ClassNotFoundException e
) {
482 LOG
.error(e
); // should not happen
483 flavors
= new DataFlavor
[0];
488 protected void enableDnD() {
489 if (!ApplicationManager
.getApplication().isHeadlessEnvironment()) {
490 DragSource
.getDefaultDragSource().createDefaultDragGestureRecognizer(myTree
, DnDConstants
.ACTION_COPY_OR_MOVE
, new MyDragGestureListener());
491 new DropTarget(myTree
, new MoveDropTargetListener(new PsiRetriever() {
493 public PsiElement
getPsiElement(@Nullable final TreeNode node
) {
494 return getPSIElement(getElement(node
));
496 }, myTree
, myProject
, FLAVORS
[0]));
498 myTree
.enableDnd(this);
502 public void setTreeBuilder(final AbstractTreeBuilder treeBuilder
) {
503 if (treeBuilder
!= null) {
504 Disposer
.register(this, treeBuilder
);
505 // needs refactoring for project view first
506 // treeBuilder.setCanYieldUpdate(true);
508 myTreeBuilder
= treeBuilder
;
511 private static class MyTransferable
implements Transferable
{
512 private final Object myTransferable
;
514 public MyTransferable(Object transferable
) {
515 myTransferable
= transferable
;
518 public DataFlavor
[] getTransferDataFlavors() {
519 DataFlavor
[] flavors
= new DataFlavor
[2];
520 flavors
[0] = FLAVORS
[0];
521 flavors
[1] = DataFlavor
.javaFileListFlavor
;
525 public boolean isDataFlavorSupported(DataFlavor flavor
) {
526 DataFlavor
[] flavors
= getTransferDataFlavors();
527 return ArrayUtil
.find(flavors
, flavor
) != -1;
530 public Object
getTransferData(DataFlavor flavor
) {
531 if (flavor
== DataFlavor
.javaFileListFlavor
) {
532 TransferableWrapper wrapper
= (TransferableWrapper
) myTransferable
;
533 return PsiCopyPasteManager
.asFileList(wrapper
.getPsiElements());
535 return myTransferable
;
539 public interface TransferableWrapper
{
540 TreeNode
[] getTreeNodes();
541 @Nullable PsiElement
[] getPsiElements();
544 private class MyDragGestureListener
implements DragGestureListener
{
545 public void dragGestureRecognized(DragGestureEvent dge
) {
546 if ((dge
.getDragAction() & DnDConstants
.ACTION_COPY_OR_MOVE
) == 0) return;
547 DataContext dataContext
= DataManager
.getInstance().getDataContext();
548 ProjectView projectView
= (ProjectView
)dataContext
.getData(ProjectViewImpl
.PROJECT_VIEW_DATA_CONSTANT
);
549 if (projectView
== null) return;
551 final AbstractProjectViewPane currentPane
= projectView
.getCurrentProjectViewPane();
552 final TreeNode
[] nodes
= currentPane
.getSelectedTreeNodes();
554 final Object
[] elements
= currentPane
.getSelectedElements();
555 final PsiElement
[] psiElements
= currentPane
.getSelectedPSIElements();
557 Object transferableWrapper
= new TransferableWrapper() {
558 public TreeNode
[] getTreeNodes() {
561 public PsiElement
[] getPsiElements() {
566 //FavoritesManager.getInstance(myProject).getCurrentTreeViewPanel().setDraggableObject(draggableObject.getClass(), draggableObject.getValue());
567 if ((psiElements
!= null && psiElements
.length
> 0) || canDragElements(elements
)) {
568 dge
.startDrag(DragSource
.DefaultMoveNoDrop
, new MyTransferable(transferableWrapper
), myDragSourceListener
);
571 catch (InvalidDnDOperationException idoe
) {
577 private boolean canDragElements(Object
[] elements
) {
578 for (Object element
: elements
) {
579 if (element
instanceof Module
) {
587 private static class MyDragSourceListener
implements DragSourceListener
{
589 public void dragEnter(DragSourceDragEvent dsde
) {
590 dsde
.getDragSourceContext().setCursor(null);
593 public void dragOver(DragSourceDragEvent dsde
) {}
595 public void dropActionChanged(DragSourceDragEvent dsde
) {
596 dsde
.getDragSourceContext().setCursor(null);
599 public void dragDropEnd(DragSourceDropEvent dsde
) { }
601 public void dragExit(DragSourceEvent dse
) { }