more general replace() implementation
[fedora-idea.git] / platform / lang-impl / src / com / intellij / moduleDependencies / ModulesDependenciesPanel.java
blob8a622b3883fb32ab8db4363eb22832239fa0a236
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 package com.intellij.moduleDependencies;
19 import com.intellij.CommonBundle;
20 import com.intellij.ProjectTopics;
21 import com.intellij.analysis.AnalysisScopeBundle;
22 import com.intellij.cyclicDependencies.CyclicGraphUtil;
23 import com.intellij.ide.CommonActionsManager;
24 import com.intellij.ide.TreeExpander;
25 import com.intellij.ide.actions.ContextHelpAction;
26 import com.intellij.ide.util.PropertiesComponent;
27 import com.intellij.openapi.Disposable;
28 import com.intellij.openapi.actionSystem.*;
29 import com.intellij.openapi.module.Module;
30 import com.intellij.openapi.module.ModuleManager;
31 import com.intellij.openapi.progress.ProgressIndicator;
32 import com.intellij.openapi.progress.ProgressManager;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.roots.ModuleRootEvent;
35 import com.intellij.openapi.roots.ModuleRootListener;
36 import com.intellij.openapi.ui.Splitter;
37 import com.intellij.openapi.util.IconLoader;
38 import com.intellij.ui.*;
39 import com.intellij.ui.content.Content;
40 import com.intellij.ui.treeStructure.Tree;
41 import com.intellij.util.containers.HashMap;
42 import com.intellij.util.graph.DFSTBuilder;
43 import com.intellij.util.graph.Graph;
44 import com.intellij.util.ui.UIUtil;
45 import com.intellij.util.ui.tree.TreeUtil;
46 import org.jetbrains.annotations.NonNls;
48 import javax.swing.*;
49 import javax.swing.event.TreeExpansionEvent;
50 import javax.swing.event.TreeExpansionListener;
51 import javax.swing.event.TreeSelectionEvent;
52 import javax.swing.event.TreeSelectionListener;
53 import javax.swing.tree.DefaultMutableTreeNode;
54 import javax.swing.tree.DefaultTreeModel;
55 import javax.swing.tree.TreePath;
56 import javax.swing.tree.TreeSelectionModel;
57 import java.awt.*;
58 import java.util.*;
59 import java.util.List;
61 /**
62 * User: anna
63 * Date: Feb 10, 2005
65 public class ModulesDependenciesPanel extends JPanel implements ModuleRootListener, Disposable {
66 @NonNls private static final String DIRECTION = "FORWARD_ANALIZER";
67 private Content myContent;
68 private final Project myProject;
69 private Tree myLeftTree;
70 private DefaultTreeModel myLeftTreeModel;
72 private final Tree myRightTree;
73 private final DefaultTreeModel myRightTreeModel;
75 private Graph<Module> myModulesGraph;
76 private final Module[] myModules;
78 private final Splitter mySplitter;
79 @NonNls private static final String ourHelpID = "module.dependencies.tool.window";
81 public ModulesDependenciesPanel(final Project project, final Module[] modules) {
82 super(new BorderLayout());
83 myProject = project;
84 myModules = modules;
86 //noinspection HardCodedStringLiteral
87 myRightTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode("Root"));
88 myRightTree = new Tree(myRightTreeModel);
89 initTree(myRightTree, true);
91 initLeftTree();
93 mySplitter = new Splitter();
94 mySplitter.setFirstComponent(new MyTreePanel(myLeftTree, myProject));
95 mySplitter.setSecondComponent(new MyTreePanel(myRightTree, myProject));
97 setSplitterProportion();
98 add(mySplitter, BorderLayout.CENTER);
99 add(createNorthPanel(), BorderLayout.NORTH);
101 project.getMessageBus().connect(this).subscribe(ProjectTopics.PROJECT_ROOTS, this);
104 private void setSplitterProportion() {
105 if (mySplitter == null){
106 return;
108 myModulesGraph = buildGraph();
109 DFSTBuilder<Module> builder = new DFSTBuilder<Module>(myModulesGraph);
110 builder.buildDFST();
111 if (builder.isAcyclic()){
112 mySplitter.setProportion(1.f);
113 } else {
114 mySplitter.setProportion(0.5f);
118 public void dispose() {
121 public ModulesDependenciesPanel(final Project project) {
122 this(project, ModuleManager.getInstance(project).getModules());
125 private JComponent createNorthPanel(){
126 DefaultActionGroup group = new DefaultActionGroup();
128 group.add(new AnAction(CommonBundle.message("action.close"), AnalysisScopeBundle.message("action.close.modules.dependencies.description"), IconLoader.getIcon("/actions/cancel.png")){
129 public void actionPerformed(AnActionEvent e) {
130 DependenciesAnalyzeManager.getInstance(myProject).closeContent(myContent);
134 appendDependenciesAction(group);
136 group.add(new ToggleAction(AnalysisScopeBundle.message("action.module.dependencies.direction"), "", isForwardDirection() ? IconLoader.getIcon("/actions/sortAsc.png") : IconLoader.getIcon("/actions/sortDesc.png")){
137 public boolean isSelected(AnActionEvent e) {
138 return isForwardDirection();
141 public void setSelected(AnActionEvent e, boolean state) {
142 PropertiesComponent.getInstance(myProject).setValue(DIRECTION, String.valueOf(state));
143 initLeftTreeModel();
146 public void update(final AnActionEvent e) {
147 e.getPresentation().setIcon(isForwardDirection() ? IconLoader.getIcon("/actions/sortAsc.png") : IconLoader.getIcon("/actions/sortDesc.png"));
151 group.add(new ContextHelpAction(ourHelpID));
153 ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
154 return toolbar.getComponent();
157 private boolean isForwardDirection() {
158 final String value = PropertiesComponent.getInstance(myProject).getValue(DIRECTION);
159 return value == null || Boolean.parseBoolean(value);
162 private static void appendDependenciesAction(final DefaultActionGroup group) {
163 final AnAction analyzeDepsAction = ActionManager.getInstance().getAction(IdeActions.ACTION_ANALYZE_DEPENDENCIES);
164 group.add(new AnAction(analyzeDepsAction.getTemplatePresentation().getText(),
165 analyzeDepsAction.getTemplatePresentation().getDescription(),
166 IconLoader.getIcon("/general/toolWindowInspection.png")){
168 public void actionPerformed(AnActionEvent e) {
169 analyzeDepsAction.actionPerformed(e);
173 public void update(AnActionEvent e) {
174 analyzeDepsAction.update(e);
179 private void buildRightTree(Module module){
180 final DefaultMutableTreeNode root = (DefaultMutableTreeNode)myRightTreeModel.getRoot();
181 root.removeAllChildren();
182 final Set<List<Module>> cycles = CyclicGraphUtil.getNodeCycles(myModulesGraph, module);
183 int index = 1;
184 for (List<Module> modules : cycles) {
185 final DefaultMutableTreeNode cycle = new DefaultMutableTreeNode(
186 AnalysisScopeBundle.message("module.dependencies.cycle.node.text", Integer.toString(index++).toUpperCase()));
187 root.add(cycle);
188 cycle.add(new DefaultMutableTreeNode(new MyUserObject(false, module)));
189 for (Module moduleInCycle : modules) {
190 cycle.add(new DefaultMutableTreeNode(new MyUserObject(false, moduleInCycle)));
193 ((DefaultTreeModel)myRightTree.getModel()).reload();
194 TreeUtil.expandAll(myRightTree);
197 private void initLeftTreeModel(){
198 final DefaultMutableTreeNode root = (DefaultMutableTreeNode)myLeftTreeModel.getRoot();
199 root.removeAllChildren();
200 myModulesGraph = buildGraph();
201 setSplitterProportion();
202 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
203 public void run() {
204 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
205 final Map<Module, Boolean> inCycle = new HashMap<Module, Boolean>();
206 for (Module module : myModules) {
207 if (progressIndicator != null) {
208 if (progressIndicator.isCanceled()) return;
209 progressIndicator.setText(AnalysisScopeBundle.message("update.module.tree.progress.text", module.getName()));
211 if (!module.isDisposed()) {
212 Boolean isInCycle = inCycle.get(module);
213 if (isInCycle == null) {
214 isInCycle = !CyclicGraphUtil.getNodeCycles(myModulesGraph, module).isEmpty();
215 inCycle.put(module, isInCycle);
217 final DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode(new MyUserObject(isInCycle.booleanValue(), module));
218 root.add(moduleNode);
219 final Iterator<Module> out = myModulesGraph.getOut(module);
220 while (out.hasNext()) {
221 moduleNode.add(new DefaultMutableTreeNode(new MyUserObject(false, out.next())));
226 }, AnalysisScopeBundle.message("update.module.tree.progress.title"), true, myProject);
227 sortSubTree(root);
228 myLeftTreeModel.reload();
231 private static void sortSubTree(final DefaultMutableTreeNode root) {
232 TreeUtil.sort(root, new Comparator() {
233 public int compare(final Object o1, final Object o2) {
234 DefaultMutableTreeNode node1 = (DefaultMutableTreeNode)o1;
235 DefaultMutableTreeNode node2 = (DefaultMutableTreeNode)o2;
236 if (!(node1.getUserObject() instanceof MyUserObject)){
237 return 1;
239 else if (!(node2.getUserObject() instanceof MyUserObject)){
240 return -1;
242 return (node1.getUserObject().toString().compareTo(node2.getUserObject().toString()));
247 private void selectCycleUpward(final DefaultMutableTreeNode selection){
248 ArrayList<DefaultMutableTreeNode> selectionNodes = new ArrayList<DefaultMutableTreeNode>();
249 selectionNodes.add(selection);
250 DefaultMutableTreeNode current = (DefaultMutableTreeNode)selection.getParent();
251 boolean flag = false;
252 while (current != null && current.getUserObject() != null){
253 if (current.getUserObject().equals(selection.getUserObject())){
254 flag = true;
255 selectionNodes.add(current);
256 break;
258 selectionNodes.add(current);
259 current = (DefaultMutableTreeNode)current.getParent();
261 if (flag){
262 for (DefaultMutableTreeNode node : selectionNodes) {
263 ((MyUserObject)node.getUserObject()).setInCycle(true);
266 if (current != null) current = (DefaultMutableTreeNode)current.getParent();
267 while (current != null) {
268 final Object userObject = current.getUserObject();
269 if (userObject instanceof MyUserObject) {
270 ((MyUserObject)userObject).setInCycle(false);
272 current = (DefaultMutableTreeNode)current.getParent();
274 myLeftTree.repaint();
277 private void initLeftTree(){
278 //noinspection HardCodedStringLiteral
279 final DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
280 myLeftTreeModel = new DefaultTreeModel(root);
281 initLeftTreeModel();
282 myLeftTree = new Tree(myLeftTreeModel);
283 initTree(myLeftTree, false);
285 myLeftTree.addTreeExpansionListener(new TreeExpansionListener() {
286 public void treeCollapsed(TreeExpansionEvent event) {
289 public void treeExpanded(TreeExpansionEvent event) {
290 final DefaultMutableTreeNode expandedNode = (DefaultMutableTreeNode)event.getPath().getLastPathComponent();
291 for(int i = 0; i < expandedNode.getChildCount(); i++){
292 DefaultMutableTreeNode child = (DefaultMutableTreeNode)expandedNode.getChildAt(i);
293 if (child.getChildCount() == 0){
294 Module module = ((MyUserObject)child.getUserObject()).getModule();
295 final Iterator<Module> out = myModulesGraph.getOut(module);
296 while (out.hasNext()) {
297 final Module nextModule = out.next();
298 child.add(new DefaultMutableTreeNode(new MyUserObject(false, nextModule)));
300 sortSubTree(child);
306 myLeftTree.addTreeSelectionListener(new TreeSelectionListener() {
307 public void valueChanged(TreeSelectionEvent e) {
308 final TreePath selectionPath = myLeftTree.getSelectionPath();
309 if (selectionPath != null) {
310 final DefaultMutableTreeNode selection = (DefaultMutableTreeNode)selectionPath.getLastPathComponent();
311 if (selection != null){
312 TreeUtil.traverseDepth(selection, new TreeUtil.Traverse() {
313 public boolean accept(Object node) {
314 DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
315 if (treeNode.getUserObject() instanceof MyUserObject){
316 ((MyUserObject)treeNode.getUserObject()).setInCycle(false);
318 return true;
321 selectCycleUpward(selection);
322 buildRightTree(((MyUserObject)selection.getUserObject()).getModule());
327 TreeUtil.selectFirstNode(myLeftTree);
330 private static ActionGroup createTreePopupActions(final boolean isRightTree, final Tree tree) {
331 DefaultActionGroup group = new DefaultActionGroup();
332 final TreeExpander treeExpander = new TreeExpander() {
333 public void expandAll() {
334 TreeUtil.expandAll(tree);
337 public boolean canExpand() {
338 return isRightTree;
341 public void collapseAll() {
342 TreeUtil.collapseAll(tree, 3);
345 public boolean canCollapse() {
346 return true;
350 final CommonActionsManager actionManager = CommonActionsManager.getInstance();
351 if (isRightTree){
352 group.add(actionManager.createExpandAllAction(treeExpander, tree));
354 group.add(actionManager.createCollapseAllAction(treeExpander, tree));
355 group.add(ActionManager.getInstance().getAction(IdeActions.MODULE_SETTINGS));
356 appendDependenciesAction(group);
357 return group;
360 private static void initTree(Tree tree, boolean isRightTree) {
361 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
362 tree.setCellRenderer(new MyTreeCellRenderer());
363 tree.setRootVisible(false);
364 tree.setShowsRootHandles(true);
365 UIUtil.setLineStyleAngled(tree);
367 TreeToolTipHandler.install(tree);
368 TreeUtil.installActions(tree);
369 new TreeSpeedSearch(tree);
370 PopupHandler.installUnknownPopupHandler(tree, createTreePopupActions(isRightTree, tree), ActionManager.getInstance());
374 private Graph<Module> buildGraph() {
375 final Graph<Module> graph = ModuleManager.getInstance(myProject).moduleGraph();
376 if (isForwardDirection()) {
377 return graph;
379 else {
380 return new Graph<Module>() {
381 public Collection<Module> getNodes() {
382 return graph.getNodes();
385 public Iterator<Module> getIn(final Module n) {
386 return graph.getOut(n);
389 public Iterator<Module> getOut(final Module n) {
390 return graph.getIn(n);
397 public void setContent(final Content content) {
398 myContent = content;
401 public void beforeRootsChange(ModuleRootEvent event) {
404 public void rootsChanged(ModuleRootEvent event) {
405 initLeftTreeModel();
406 TreeUtil.selectFirstNode(myLeftTree);
409 private static class MyUserObject{
410 private boolean myInCycle;
411 private final Module myModule;
413 public MyUserObject(final boolean inCycle, final Module module) {
414 myInCycle = inCycle;
415 myModule = module;
418 public boolean isInCycle() {
419 return myInCycle;
422 public void setInCycle(final boolean inCycle) {
423 myInCycle = inCycle;
426 public Module getModule() {
427 return myModule;
430 public boolean equals(Object object) {
431 return object instanceof MyUserObject && myModule.equals(((MyUserObject)object).getModule());
434 public int hashCode() {
435 return myModule.hashCode();
438 public String toString() {
439 return myModule.getName();
443 private static class MyTreePanel extends JPanel implements DataProvider{
444 private final Tree myTree;
445 private final Project myProject;
446 public MyTreePanel(final Tree tree, Project project) {
447 super(new BorderLayout());
448 myTree = tree;
449 myProject = project;
450 add(ScrollPaneFactory.createScrollPane(myTree), BorderLayout.CENTER);
453 public Object getData(String dataId) {
454 if (PlatformDataKeys.PROJECT.is(dataId)){
455 return myProject;
457 if (LangDataKeys.MODULE_CONTEXT.is(dataId)){
458 final TreePath selectionPath = myTree.getLeadSelectionPath();
459 if (selectionPath != null && selectionPath.getLastPathComponent() instanceof DefaultMutableTreeNode){
460 DefaultMutableTreeNode node = (DefaultMutableTreeNode)selectionPath.getLastPathComponent();
461 if (node.getUserObject() instanceof MyUserObject){
462 return ((MyUserObject)node.getUserObject()).getModule();
466 if (PlatformDataKeys.HELP_ID.is(dataId)) {
467 return ourHelpID;
469 return null;
472 private static class MyTreeCellRenderer extends ColoredTreeCellRenderer {
473 public void customizeCellRenderer(
474 JTree tree,
475 Object value,
476 boolean selected,
477 boolean expanded,
478 boolean leaf,
479 int row,
480 boolean hasFocus
482 final Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
483 if (!(userObject instanceof MyUserObject)){
484 if (userObject != null){
485 append(userObject.toString(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
487 return;
489 MyUserObject node = (MyUserObject)userObject;
490 Module module = node.getModule();
491 setIcon(module.getModuleType().getNodeIcon(expanded));
492 if (node.isInCycle()){
493 append(module.getName(), SimpleTextAttributes.ERROR_ATTRIBUTES);
494 } else {
495 append(module.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES);