2ad3edc9afa996192126f60cddc931ad508ddacb
[idea/community.git] / java / java-impl / src / com / intellij / ide / util / PackageChooserDialog.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.ide.util;
17
18 import com.intellij.CommonBundle;
19 import com.intellij.icons.AllIcons;
20 import com.intellij.ide.IdeBundle;
21 import com.intellij.openapi.actionSystem.*;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.command.CommandProcessor;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.event.*;
26 import com.intellij.openapi.editor.event.DocumentAdapter;
27 import com.intellij.openapi.fileChooser.ex.FileChooserDialogImpl;
28 import com.intellij.openapi.fileChooser.ex.TextFieldAction;
29 import com.intellij.openapi.fileTypes.StdFileTypes;
30 import com.intellij.openapi.module.Module;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.roots.ContentIterator;
33 import com.intellij.openapi.roots.FileIndex;
34 import com.intellij.openapi.roots.ModuleRootManager;
35 import com.intellij.openapi.roots.ProjectRootManager;
36 import com.intellij.openapi.ui.InputValidator;
37 import com.intellij.openapi.ui.Messages;
38 import com.intellij.openapi.ui.PackageChooser;
39 import com.intellij.openapi.util.Comparing;
40 import com.intellij.openapi.util.text.StringUtil;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.psi.*;
43 import com.intellij.ui.*;
44 import com.intellij.ui.components.labels.LinkLabel;
45 import com.intellij.ui.treeStructure.Tree;
46 import com.intellij.util.Alarm;
47 import com.intellij.util.IncorrectOperationException;
48 import com.intellij.util.PlatformIcons;
49 import com.intellij.util.containers.Convertor;
50 import com.intellij.util.ui.UIUtil;
51 import com.intellij.util.ui.tree.TreeUtil;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
54
55 import javax.swing.*;
56 import javax.swing.event.TreeSelectionEvent;
57 import javax.swing.event.TreeSelectionListener;
58 import javax.swing.tree.DefaultMutableTreeNode;
59 import javax.swing.tree.DefaultTreeCellRenderer;
60 import javax.swing.tree.DefaultTreeModel;
61 import javax.swing.tree.TreePath;
62 import java.awt.*;
63 import java.util.Comparator;
64 import java.util.Enumeration;
65 import java.util.List;
66
67 public class PackageChooserDialog extends PackageChooser {
68   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.PackageChooserDialog");
69
70   private Tree myTree;
71   private DefaultTreeModel myModel;
72   private final Project myProject;
73   private final String myTitle;
74   private Module myModule;
75   private EditorTextField myPathEditor;
76
77   private Alarm myAlarm = new Alarm(getDisposable()); 
78
79   public PackageChooserDialog(String title, @NotNull Module module) {
80     super(module.getProject(), true);
81     setTitle(title);
82     myTitle = title;
83     myProject = module.getProject();
84     myModule = module;
85     init();
86   }
87
88   public PackageChooserDialog(String title, Project project) {
89     super(project, true);
90     setTitle(title);
91     myTitle = title;
92     myProject = project;
93     init();
94   }
95
96   protected JComponent createCenterPanel() {
97     JPanel panel = new JPanel();
98     panel.setLayout(new BorderLayout());
99
100
101     myModel = new DefaultTreeModel(new DefaultMutableTreeNode());
102     createTreeModel();
103     myTree = new Tree(myModel);
104
105     UIUtil.setLineStyleAngled(myTree);
106     myTree.setCellRenderer(
107       new DefaultTreeCellRenderer() {
108         public Component getTreeCellRendererComponent(
109           JTree tree, Object value,
110           boolean sel,
111           boolean expanded,
112           boolean leaf, int row,
113           boolean hasFocus
114         ) {
115           super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
116           setIcon(PlatformIcons.PACKAGE_ICON);
117
118           if (value instanceof DefaultMutableTreeNode) {
119             DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
120             Object object = node.getUserObject();
121             if (object instanceof PsiPackage) {
122               String name = ((PsiPackage)object).getName();
123               if (name != null && name.length() > 0) {
124                 setText(name);
125               }
126               else {
127                 setText(IdeBundle.message("node.default"));
128               }
129             }
130           }
131           return this;
132         }
133       }
134     );
135
136     JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
137     scrollPane.setPreferredSize(new Dimension(500, 300));
138
139     new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() {
140       public String convert(TreePath path) {
141         DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
142         Object object = node.getUserObject();
143         if (object instanceof PsiPackage) return ((PsiPackage)object).getName();
144         else
145           return "";
146       }
147     });
148
149     myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
150       public void valueChanged(TreeSelectionEvent e) {
151         PsiPackage selection = getTreeSelection();
152         if (selection != null) {
153           String name = selection.getQualifiedName();
154           setTitle(myTitle + " - " + ("".equals(name) ? IdeBundle.message("node.default.package") : name));
155         }
156         else {
157           setTitle(myTitle);
158         }
159         updatePathFromTree();
160       }
161     });
162
163     panel.add(scrollPane, BorderLayout.CENTER);
164     DefaultActionGroup group = createActionGroup(myTree);
165
166     final JPanel northPanel = new JPanel(new BorderLayout());
167     panel.add(northPanel, BorderLayout.NORTH);
168     ActionToolbar toolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
169     northPanel.add(toolBar.getComponent(), BorderLayout.WEST);
170     setupPathComponent(northPanel);
171     return panel;
172   }
173
174   private void setupPathComponent(final JPanel northPanel) {
175     northPanel.add(new TextFieldAction() {
176       @Override
177       public void linkSelected(LinkLabel aSource, Object aLinkData) {
178         toggleShowPathComponent(northPanel, this);
179       }
180     }, BorderLayout.EAST);
181     myPathEditor = new EditorTextField(JavaReferenceEditorUtil.createDocument("", myProject, false), myProject, StdFileTypes.JAVA);
182     myPathEditor.addDocumentListener(new DocumentAdapter() {
183       @Override
184       public void documentChanged(DocumentEvent e) {
185         myAlarm.cancelAllRequests();
186         myAlarm.addRequest(new Runnable() {
187           @Override
188           public void run() {
189             updateTreeFromPath();
190           }
191         }, 300);
192       }
193     });
194     myPathEditor.setBorder(BorderFactory.createEmptyBorder(0, 0, 2, 0));
195     northPanel.add(myPathEditor, BorderLayout.SOUTH);
196   }
197
198   private void toggleShowPathComponent(JPanel northPanel, TextFieldAction fieldAction) {
199     boolean toShowTextField = !isPathShowing();
200     PropertiesComponent.getInstance().setValue(FileChooserDialogImpl.FILE_CHOOSER_SHOW_PATH_PROPERTY, Boolean.toString(toShowTextField));
201     myPathEditor.setVisible(toShowTextField);
202     fieldAction.update();
203     northPanel.revalidate();
204     northPanel.repaint();
205     updatePathFromTree();
206   }
207
208   private static boolean isPathShowing() {
209     return PropertiesComponent.getInstance().getBoolean(FileChooserDialogImpl.FILE_CHOOSER_SHOW_PATH_PROPERTY, true);
210   }
211
212   private void updatePathFromTree() {
213     if (!isPathShowing()) return;
214     final PsiPackage selection = getTreeSelection();
215     myPathEditor.setText(selection != null ? selection.getQualifiedName() : "");
216   }
217
218   private void updateTreeFromPath() {
219     selectPackage(myPathEditor.getText().trim());
220   }
221   
222   private DefaultActionGroup createActionGroup(JComponent component) {
223     final DefaultActionGroup group = new DefaultActionGroup();
224     final DefaultActionGroup temp = new DefaultActionGroup();
225     NewPackageAction newPackageAction = new NewPackageAction();
226     newPackageAction.enableInModalConext();
227     newPackageAction.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_NEW_ELEMENT).getShortcutSet(), component);
228     temp.add(newPackageAction);
229     group.add(temp);
230     return group;
231   }
232
233   protected void doOKAction(){
234     super.doOKAction();
235   }
236
237   public String getDimensionServiceKey(){
238     return "#com.intellij.ide.util.PackageChooserDialog";
239   }
240
241   public JComponent getPreferredFocusedComponent(){
242     return myTree;
243   }
244
245   public PsiPackage getSelectedPackage(){
246     return getTreeSelection();
247   }
248
249   public List<PsiPackage> getSelectedPackages() {
250     return TreeUtil.collectSelectedObjectsOfType(myTree, PsiPackage.class);
251   }
252
253   public void selectPackage(final String qualifiedName) {
254     /*ApplicationManager.getApplication().invokeLater(new Runnable() {
255         public void run() {*/
256           DefaultMutableTreeNode node = findNodeForPackage(qualifiedName);
257           if (node != null) {
258             TreePath path = new TreePath(node.getPath());
259             TreeUtil.selectPath(myTree, path);
260           }
261        /* }
262       }, ModalityState.stateForComponent(getRootPane()));*/
263   }
264
265   @Nullable
266   private PsiPackage getTreeSelection() {
267     if (myTree == null) return null;
268     TreePath path = myTree.getSelectionPath();
269     if (path == null) return null;
270     DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
271     return (PsiPackage)node.getUserObject();
272   }
273
274   private void createTreeModel() {
275     final PsiManager psiManager = PsiManager.getInstance(myProject);
276     final FileIndex fileIndex = myModule != null ? ModuleRootManager.getInstance(myModule).getFileIndex() : ProjectRootManager.getInstance(myProject).getFileIndex();
277     fileIndex.iterateContent(
278       new ContentIterator() {
279         public boolean processFile(VirtualFile fileOrDir) {
280           if (fileOrDir.isDirectory() && fileIndex.isInSourceContent(fileOrDir)){
281             final PsiDirectory psiDirectory = psiManager.findDirectory(fileOrDir);
282             LOG.assertTrue(psiDirectory != null);
283             PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(psiDirectory);
284             if (aPackage != null){
285               addPackage(aPackage);
286             }
287           }
288           return true;
289         }
290       }
291     );
292
293     TreeUtil.sort(myModel, new Comparator() {
294       public int compare(Object o1, Object o2) {
295         DefaultMutableTreeNode n1 = (DefaultMutableTreeNode) o1;
296         DefaultMutableTreeNode n2 = (DefaultMutableTreeNode) o2;
297         PsiNamedElement element1 = (PsiNamedElement) n1.getUserObject();
298         PsiNamedElement element2 = (PsiNamedElement) n2.getUserObject();
299         return element1.getName().compareToIgnoreCase(element2.getName());
300       }
301     });
302   }
303
304   @NotNull
305   private DefaultMutableTreeNode addPackage(PsiPackage aPackage) {
306     final String qualifiedPackageName = aPackage.getQualifiedName();
307     final PsiPackage parentPackage = aPackage.getParentPackage();
308     if (parentPackage == null) {
309       final DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)myModel.getRoot();
310       if (qualifiedPackageName.length() == 0) {
311         rootNode.setUserObject(aPackage);
312         return rootNode;
313       }
314       else {
315         DefaultMutableTreeNode packageNode = findPackageNode(rootNode, qualifiedPackageName);
316         if (packageNode != null) return packageNode;
317         packageNode = new DefaultMutableTreeNode(aPackage);
318         rootNode.add(packageNode);
319         return packageNode;
320       }
321     }
322     else {
323       final DefaultMutableTreeNode parentNode = addPackage(parentPackage);
324       DefaultMutableTreeNode packageNode = findPackageNode(parentNode, qualifiedPackageName);
325       if (packageNode != null) {
326         return packageNode;
327       }
328       packageNode = new DefaultMutableTreeNode(aPackage);
329       parentNode.add(packageNode);
330       return packageNode;
331     }
332   }
333
334   @Nullable
335   private static DefaultMutableTreeNode findPackageNode(DefaultMutableTreeNode rootNode, String qualifiedName) {
336     for (int i = 0; i < rootNode.getChildCount(); i++) {
337       final DefaultMutableTreeNode child = (DefaultMutableTreeNode)rootNode.getChildAt(i);
338       final PsiPackage nodePackage = (PsiPackage)child.getUserObject();
339       if (nodePackage != null) {
340         if (Comparing.equal(nodePackage.getQualifiedName(), qualifiedName)) return child;
341       }
342     }
343     return null;
344   }
345
346   private DefaultMutableTreeNode findNodeForPackage(String qualifiedPackageName) {
347     DefaultMutableTreeNode root = (DefaultMutableTreeNode)myModel.getRoot();
348     Enumeration enumeration = root.depthFirstEnumeration();
349     while (enumeration.hasMoreElements()) {
350       Object o = enumeration.nextElement();
351       if (o instanceof DefaultMutableTreeNode) {
352         DefaultMutableTreeNode node = (DefaultMutableTreeNode)o;
353         PsiPackage nodePackage = (PsiPackage)node.getUserObject();
354         if (nodePackage != null) {
355           if (Comparing.equal(nodePackage.getQualifiedName(), qualifiedPackageName)) return node;
356         }
357       }
358     }
359     return null;
360   }
361
362   private void createNewPackage() {
363     final PsiPackage selectedPackage = getTreeSelection();
364     if (selectedPackage == null) return;
365
366     final String newPackageName = Messages.showInputDialog(myProject, IdeBundle.message("prompt.enter.a.new.package.name"), IdeBundle.message("title.new.package"), Messages.getQuestionIcon(), "",
367                                                            new InputValidator() {
368                                                              public boolean checkInput(final String inputString) {
369                                                                return inputString != null && inputString.length() > 0;
370                                                              }
371
372                                                              public boolean canClose(final String inputString) {
373                                                                return checkInput(inputString);
374                                                              }
375                                                            });
376     if (newPackageName == null) return;
377
378     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
379       public void run() {
380         final Runnable action = new Runnable() {
381               public void run() {
382
383                 try {
384                   String newQualifiedName = selectedPackage.getQualifiedName();
385                   if (!Comparing.strEqual(newQualifiedName,"")) newQualifiedName += ".";
386                   newQualifiedName += newPackageName;
387                   final PsiDirectory dir = PackageUtil.findOrCreateDirectoryForPackage(myProject, newQualifiedName, null, false);
388                   if (dir == null) return;
389                   final PsiPackage newPackage = JavaDirectoryService.getInstance().getPackage(dir);
390
391                   DefaultMutableTreeNode node = (DefaultMutableTreeNode)myTree.getSelectionPath().getLastPathComponent();
392                   final DefaultMutableTreeNode newChild = new DefaultMutableTreeNode();
393                   newChild.setUserObject(newPackage);
394                   node.add(newChild);
395
396                   final DefaultTreeModel model = (DefaultTreeModel)myTree.getModel();
397                   model.nodeStructureChanged(node);
398
399                   final TreePath selectionPath = myTree.getSelectionPath();
400                   TreePath path;
401                   if (selectionPath == null) {
402                     path = new TreePath(newChild.getPath());
403                   } else {
404                     path = selectionPath.pathByAddingChild(newChild);
405                   }
406                     myTree.setSelectionPath(path);
407                     myTree.scrollPathToVisible(path);
408                     myTree.expandPath(path);
409
410                 }
411                 catch (IncorrectOperationException e) {
412                   Messages.showMessageDialog(
413                     getContentPane(),
414                     StringUtil.getMessage(e),
415                     CommonBundle.getErrorTitle(),
416                     Messages.getErrorIcon()
417                   );
418                   if (LOG.isDebugEnabled()) {
419                     LOG.debug(e);
420                   }
421                 }
422               }
423             };
424         ApplicationManager.getApplication().runReadAction(action);
425       }
426     },
427     IdeBundle.message("command.create.new.package"),
428     null);
429   }
430
431   private class NewPackageAction extends AnAction {
432     public NewPackageAction() {
433       super(IdeBundle.message("action.new.package"),
434             IdeBundle.message("action.description.create.new.package"), AllIcons.Actions.NewFolder);
435     }
436
437     public void actionPerformed(AnActionEvent e) {
438       createNewPackage();
439     }
440
441     public void update(AnActionEvent event) {
442       Presentation presentation = event.getPresentation();
443       presentation.setEnabled(getTreeSelection() != null);
444     }
445
446     public void enableInModalConext() {
447       setEnabledInModalContext(true);
448     }
449   }
450
451 }
452