ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / platform / lang-impl / src / com / intellij / ide / projectView / impl / AbstractProjectViewPane.java
blobee435d453f58efe190320ad3d85d2fc4520bd234
1 /*
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.
17 /**
18 * @author cdr
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.DataContext;
35 import com.intellij.openapi.actionSystem.DataProvider;
36 import com.intellij.openapi.actionSystem.DefaultActionGroup;
37 import com.intellij.openapi.actionSystem.PlatformDataKeys;
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;
59 import javax.swing.*;
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;
69 import java.util.Map;
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) {
87 myProject = 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(){
106 return mySubId;
109 public final void setSubId(@Nullable String subId) {
110 saveExpandedPaths();
111 mySubId = subId;
114 public boolean isInitiallyVisible() {
115 return true;
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() {
131 return myTree;
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);
138 if (node == null) {
139 return;
141 TreePath treePath = new TreePath(node.getPath());
142 myTree.expandPath(treePath);
143 if (requestFocus) {
144 myTree.requestFocus();
146 TreeUtil.selectPath(myTree, treePath);
149 public void dispose() {
150 setTreeBuilder(null);
151 myTree = 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() {
165 public void run() {
166 ProjectView projectView = ProjectView.getInstance(myProject);
167 if (requestFocus) {
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;
177 if (requestFocus) {
178 windowManager.getToolWindow(ToolWindowId.PROJECT_VIEW).activate(runnable);
180 else {
181 runnable.run();
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) {
196 @NotNull
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);
211 return result;
214 public Object getData(String dataId) {
215 if (PlatformDataKeys.NAVIGATABLE_ARRAY.is(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()) {
233 return null;
235 else {
236 return navigatables.toArray(new Navigatable[navigatables.size()]);
239 if (myTreeStructure instanceof AbstractTreeStructureBase) {
240 return ((AbstractTreeStructureBase) myTreeStructure).getDataFromProviders(getSelectedNodes(AbstractTreeNode.class), dataId);
242 return null;
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];
253 return null;
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;
263 return null;
266 public final DefaultMutableTreeNode getSelectedNode() {
267 TreePath path = getSelectedPath();
268 if (path == null) {
269 return null;
271 Object lastPathComponent = path.getLastPathComponent();
272 if (!(lastPathComponent instanceof DefaultMutableTreeNode)) {
273 return null;
275 return (DefaultMutableTreeNode)lastPathComponent;
278 public final Object getSelectedElement() {
279 final Object[] elements = getSelectedElements();
280 return elements.length == 1 ? elements[0] : null;
283 @NotNull
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()]);
295 @Nullable
296 protected PsiElement getPSIElement(@Nullable final Object element) {
297 if (element instanceof PsiElement) {
298 PsiElement psiElement = (PsiElement)element;
299 if (psiElement.isValid()) {
300 return psiElement;
303 return null;
306 @NotNull
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) {
316 list.add(element);
320 return ArrayUtil.toObjectArray(list);
323 @Nullable
324 private Object getElement(@Nullable final TreeNode treeNode) {
325 if (treeNode instanceof DefaultMutableTreeNode) {
326 DefaultMutableTreeNode node = (DefaultMutableTreeNode)treeNode;
327 return exhumeElementFromNode(node);
329 return null;
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;
367 return element;
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 {
385 saveExpandedPaths();
386 for (String subId : myReadTreeState.keySet()) {
387 TreeState treeState = myReadTreeState.get(subId);
388 Element subPane = new Element(ELEMENT_SUBPANE);
389 if (subId != null) {
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() {
417 return myTree;
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();
430 else {
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;
438 else {
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) {
457 dirs.add(directory);
460 return dirs.toArray(new PsiDirectory[dirs.size()]);
462 return PsiDirectory.EMPTY_ARRAY;
465 // Drag'n'Drop stuff
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();
471 static {
472 DataFlavor[] flavors;
473 try {
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];
485 FLAVORS = flavors;
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() {
492 @Nullable
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;
522 return flavors;
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 = ProjectViewImpl.DATA_KEY.getData(dataContext);
549 if (projectView == null) return;
551 final AbstractProjectViewPane currentPane = projectView.getCurrentProjectViewPane();
552 final TreeNode[] nodes = currentPane.getSelectedTreeNodes();
553 if (nodes != null) {
554 final Object[] elements = currentPane.getSelectedElements();
555 final PsiElement[] psiElements = currentPane.getSelectedPSIElements();
556 try {
557 Object transferableWrapper = new TransferableWrapper() {
558 public TreeNode[] getTreeNodes() {
559 return nodes;
561 public PsiElement[] getPsiElements() {
562 return psiElements;
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) {
572 // ignore
577 private boolean canDragElements(Object[] elements) {
578 for (Object element : elements) {
579 if (element instanceof Module) {
580 return true;
583 return false;
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) { }