19b71dbbf766de25ec1c7922fb6dfe0b3620dabe
[fedora-idea.git] / platform / lang-impl / src / com / intellij / ide / util / scopeChooser / ScopeEditorPanel.java
blob19b71dbbf766de25ec1c7922fb6dfe0b3620dabe
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.
16 package com.intellij.ide.util.scopeChooser;
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.ide.projectView.impl.nodes.ProjectViewDirectoryHelper;
20 import com.intellij.openapi.actionSystem.*;
21 import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.options.ConfigurationException;
25 import com.intellij.openapi.progress.ProcessCanceledException;
26 import com.intellij.openapi.progress.ProgressManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.VerticalFlowLayout;
29 import com.intellij.openapi.util.IconLoader;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.openapi.module.ModuleManager;
32 import com.intellij.openapi.module.Module;
33 import com.intellij.packageDependencies.DependencyUISettings;
34 import com.intellij.packageDependencies.ui.*;
35 import com.intellij.psi.PsiFile;
36 import com.intellij.psi.search.scope.packageSet.*;
37 import com.intellij.ui.*;
38 import com.intellij.ui.treeStructure.Tree;
39 import com.intellij.util.Alarm;
40 import com.intellij.util.Consumer;
41 import com.intellij.util.ui.UIUtil;
42 import com.intellij.util.ui.tree.TreeUtil;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
46 import javax.swing.*;
47 import javax.swing.event.CaretEvent;
48 import javax.swing.event.CaretListener;
49 import javax.swing.event.DocumentEvent;
50 import java.awt.*;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.FocusEvent;
54 import java.awt.event.FocusListener;
55 import java.util.ArrayList;
57 public class ScopeEditorPanel {
59 private JPanel myButtonsPanel;
60 private JTextField myPatternField;
61 private JPanel myTreeToolbar;
62 private final Tree myPackageTree;
63 private JPanel myPanel;
64 private JPanel myTreePanel;
65 private JLabel myMatchingCountLabel;
66 private JPanel myLegendPanel;
68 private final Project myProject;
69 private final TreeExpansionMonitor myTreeExpansionMonitor;
70 private final Marker myTreeMarker;
71 private PackageSet myCurrentScope = null;
72 private boolean myIsInUpdate = false;
73 private String myErrorMessage;
74 private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
76 private JLabel myCaretPositionLabel;
77 private int myCaretPosition = 0;
78 private boolean myTextChanged = false;
79 private JPanel myMatchingCountPanel;
80 private PanelProgressIndicator myCurrentProgress;
81 private final NamedScopesHolder myHolder;
83 public ScopeEditorPanel(Project project) {
84 this(project, null);
87 public ScopeEditorPanel(Project project, final NamedScopesHolder holder) {
88 myProject = project;
89 myHolder = holder;
90 myButtonsPanel.add(createActionsPanel());
92 myPackageTree = new Tree(new RootNode());
93 myTreePanel.setLayout(new BorderLayout());
94 myTreePanel.add(ScrollPaneFactory.createScrollPane(myPackageTree), BorderLayout.CENTER);
96 myTreeToolbar.setLayout(new BorderLayout());
97 myTreeToolbar.add(createTreeToolbar(), BorderLayout.WEST);
99 myTreeExpansionMonitor = PackageTreeExpansionMonitor.install(myPackageTree, myProject);
101 myTreeMarker = new Marker() {
102 public boolean isMarked(PsiFile file) {
103 return myCurrentScope != null && myCurrentScope.contains(file, getHolder());
107 myPatternField.getDocument().addDocumentListener(new DocumentAdapter() {
108 public void textChanged(DocumentEvent event) {
109 onTextChange();
113 myPatternField.addCaretListener(new CaretListener() {
114 public void caretUpdate(CaretEvent e) {
115 myCaretPosition = e.getDot();
116 updateCaretPositionText();
120 myPatternField.addFocusListener(new FocusListener() {
121 public void focusGained(FocusEvent e) {
122 myCaretPositionLabel.setVisible(true);
125 public void focusLost(FocusEvent e) {
126 myCaretPositionLabel.setVisible(false);
130 initTree(myPackageTree);
133 private void updateCaretPositionText() {
134 if (myErrorMessage != null) {
135 myCaretPositionLabel.setText(IdeBundle.message("label.scope.editor.caret.position", myCaretPosition + 1));
137 else {
138 myCaretPositionLabel.setText("");
142 public JPanel getPanel() {
143 return myPanel;
146 public JPanel getTreePanel(){
147 JPanel panel = new JPanel(new BorderLayout());
148 panel.add(myTreePanel, BorderLayout.CENTER);
149 panel.add(myLegendPanel, BorderLayout.SOUTH);
150 return panel;
153 public JPanel getTreeToolbar() {
154 return myTreeToolbar;
157 private void onTextChange() {
158 if (!myIsInUpdate) {
159 myUpdateAlarm.cancelAllRequests();
160 myCurrentScope = null;
161 try {
162 myCurrentScope = PackageSetFactory.getInstance().compile(myPatternField.getText());
163 myErrorMessage = null;
164 myTextChanged = true;
165 rebuild(false);
167 catch (Exception e) {
168 myErrorMessage = e.getMessage();
169 showErrorMessage();
172 else {
173 myErrorMessage = null;
177 private void showErrorMessage() {
178 myMatchingCountLabel.setText(StringUtil.capitalize(myErrorMessage));
179 myMatchingCountLabel.setForeground(Color.red);
180 myMatchingCountLabel.setToolTipText(myErrorMessage);
183 private JComponent createActionsPanel() {
184 JButton include = new JButton(IdeBundle.message("button.include"));
185 JButton includeRec = new JButton(IdeBundle.message("button.include.recursively"));
186 JButton exclude = new JButton(IdeBundle.message("button.exclude"));
187 JButton excludeRec = new JButton(IdeBundle.message("button.exclude.recursively"));
189 JPanel buttonsPanel = new JPanel(new VerticalFlowLayout());
190 buttonsPanel.add(include);
191 buttonsPanel.add(includeRec);
192 buttonsPanel.add(exclude);
193 buttonsPanel.add(excludeRec);
195 include.addActionListener(new ActionListener() {
196 public void actionPerformed(ActionEvent e) {
197 includeSelected(false);
200 includeRec.addActionListener(new ActionListener() {
201 public void actionPerformed(ActionEvent e) {
202 includeSelected(true);
205 exclude.addActionListener(new ActionListener() {
206 public void actionPerformed(ActionEvent e) {
207 excludeSelected(false);
210 excludeRec.addActionListener(new ActionListener() {
211 public void actionPerformed(ActionEvent e) {
212 excludeSelected(true);
216 return buttonsPanel;
219 private void excludeSelected(boolean recurse) {
220 final ArrayList<PackageSet> selected = getSelectedSets(recurse);
221 if (selected == null || selected.isEmpty()) return;
222 for (PackageSet set : selected) {
223 if (myCurrentScope == null) {
224 myCurrentScope = new ComplementPackageSet(set);
225 } else {
226 myCurrentScope = new IntersectionPackageSet(myCurrentScope, new ComplementPackageSet(set));
229 rebuild(true);
232 private void includeSelected(boolean recurse) {
233 final ArrayList<PackageSet> selected = getSelectedSets(recurse);
234 if (selected == null || selected.isEmpty()) return;
235 for (PackageSet set : selected) {
236 if (myCurrentScope == null) {
237 myCurrentScope = set;
239 else {
240 myCurrentScope = new UnionPackageSet(myCurrentScope, set);
243 rebuild(true);
246 @Nullable
247 private ArrayList<PackageSet> getSelectedSets(boolean recursively) {
248 int[] rows = myPackageTree.getSelectionRows();
249 if (rows == null) return null;
250 final ArrayList<PackageSet> result = new ArrayList<PackageSet>();
251 for (int row : rows) {
252 final PackageDependenciesNode node = (PackageDependenciesNode)myPackageTree.getPathForRow(row).getLastPathComponent();
253 final PackageSet set = PatternDialectProvider.getInstance(DependencyUISettings.getInstance().SCOPE_TYPE).createPackageSet(node, recursively);
254 if (set != null) {
255 result.add(set);
258 return result;
262 private JComponent createTreeToolbar() {
263 final DefaultActionGroup group = new DefaultActionGroup();
264 final Runnable update = new Runnable() {
265 public void run() {
266 rebuild(true);
269 if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) {
270 group.add(new FlattenPackagesAction(update));
272 final PatternDialectProvider[] dialectProviders = Extensions.getExtensions(PatternDialectProvider.EP_NAME);
273 for (PatternDialectProvider provider : dialectProviders) {
274 for (AnAction action : provider.createActions(myProject, update)) {
275 group.add(action);
278 group.add(new ShowFilesAction(update));
279 final Module[] modules = ModuleManager.getInstance(myProject).getModules();
280 if (modules.length > 1) {
281 group.add(new ShowModulesAction(update));
282 group.add(new ShowModuleGroupsAction(update));
284 group.add(new FilterLegalsAction(update));
286 if (dialectProviders.length > 1) {
287 group.add(new ChooseScopeTypeAction(update));
290 ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
291 return toolbar.getComponent();
294 private void rebuild(final boolean updateText, final Runnable runnable, final boolean requestFocus){
295 myUpdateAlarm.cancelAllRequests();
296 final Runnable request = new Runnable() {
297 public void run() {
298 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
299 public void run() {
300 myIsInUpdate = true;
301 if (updateText && myCurrentScope != null) {
302 myPatternField.setText(myCurrentScope.getText());
304 try {
305 if (!myProject.isDisposed()) {
306 updateTreeModel(requestFocus);
309 catch (ProcessCanceledException e) {
310 return;
312 if (runnable != null) {
313 runnable.run();
315 myIsInUpdate = false;
320 myUpdateAlarm.addRequest(request, 1000);
323 private void rebuild(final boolean updateText) {
324 rebuild(updateText, null, true);
327 private static void initTree(Tree tree) {
328 tree.setCellRenderer(new MyTreeCellRenderer());
329 tree.setRootVisible(false);
330 tree.setShowsRootHandles(true);
331 tree.setLineStyleAngled();
333 TreeToolTipHandler.install(tree);
334 TreeUtil.installActions(tree);
335 SmartExpander.installOn(tree);
336 new TreeSpeedSearch(tree);
339 private void updateTreeModel(final boolean requestFocus) throws ProcessCanceledException {
340 PanelProgressIndicator progress = createProgressIndicator(requestFocus);
341 progress.setBordersVisible(false);
342 myCurrentProgress = progress;
343 Runnable updateModel = new Runnable() {
344 public void run() {
345 final ProcessCanceledException [] ex = new ProcessCanceledException[1];
346 ApplicationManager.getApplication().runReadAction(new Runnable() {
347 public void run() {
348 try {
349 myTreeExpansionMonitor.freeze();
350 final TreeModel model = PatternDialectProvider.getInstance(DependencyUISettings.getInstance().SCOPE_TYPE).createTreeModel(myProject, myTreeMarker);
351 if (myErrorMessage == null) {
352 myMatchingCountLabel
353 .setText(IdeBundle.message("label.scope.contains.files", model.getMarkedFileCount(), model.getTotalFileCount()));
354 myMatchingCountLabel.setForeground(new JLabel().getForeground());
356 else {
357 showErrorMessage();
360 SwingUtilities.invokeLater(new Runnable(){
361 public void run() { //not under progress
362 myPackageTree.setModel(model);
363 myTreeExpansionMonitor.restore();
366 } catch (ProcessCanceledException e) {
367 ex[0] = e;
369 finally {
370 myCurrentProgress = null;
371 //update label
372 setToComponent(myMatchingCountLabel, requestFocus);
376 if (ex[0] != null) {
377 throw ex[0];
381 ProgressManager.getInstance().runProcess(updateModel, progress);
384 protected PanelProgressIndicator createProgressIndicator(final boolean requestFocus) {
385 return new MyPanelProgressIndicator(true, requestFocus);
388 public void cancelCurrentProgress(){
389 if (myCurrentProgress != null && myCurrentProgress.isRunning()){
390 myCurrentProgress.cancel();
394 public void apply() throws ConfigurationException {
395 if (myCurrentScope == null) {
396 throw new ConfigurationException(IdeBundle.message("error.correct.pattern.syntax.errors.first"));
400 public PackageSet getCurrentScope() {
401 return myCurrentScope;
404 public void reset(PackageSet packageSet, Runnable runnable){
405 myCurrentScope = packageSet;
406 myPatternField.setText(myCurrentScope == null ? "" : myCurrentScope.getText());
407 rebuild(false, runnable, false);
410 private void setToComponent(final JComponent cmp, final boolean requestFocus) {
411 myMatchingCountPanel.removeAll();
412 myMatchingCountPanel.add(cmp, BorderLayout.CENTER);
413 myMatchingCountPanel.revalidate();
414 myMatchingCountPanel.repaint();
415 if (requestFocus) {
416 SwingUtilities.invokeLater(new Runnable(){
417 public void run() {
418 myPatternField.requestFocusInWindow();
424 public void restoreCanceledProgress() {
425 if (myIsInUpdate) {
426 rebuild(false);
430 public void clearCaches() {
431 FileTreeModelBuilder.clearCaches(myProject);
434 public NamedScopesHolder getHolder() {
435 return myHolder;
438 private static class MyTreeCellRenderer extends ColoredTreeCellRenderer {
439 private static final Color WHOLE_INCLUDED = new Color(10, 119, 0);
440 private static final Color PARTIAL_INCLUDED = new Color(0, 50, 160);
442 public void customizeCellRenderer(JTree tree,
443 Object value,
444 boolean selected,
445 boolean expanded,
446 boolean leaf,
447 int row,
448 boolean hasFocus) {
449 if (value instanceof PackageDependenciesNode) {
450 PackageDependenciesNode node = (PackageDependenciesNode)value;
451 if (expanded) {
452 setIcon(node.getOpenIcon());
454 else {
455 setIcon(node.getClosedIcon());
458 setForeground(selected && hasFocus ? UIUtil.getTreeSelectionForeground() : UIUtil.getTreeForeground());
459 if (!selected && node.hasMarked() && !DependencyUISettings.getInstance().UI_FILTER_LEGALS) {
460 setForeground(node.hasUnmarked() ? PARTIAL_INCLUDED : WHOLE_INCLUDED);
462 append(node.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES);
463 final String locationString = node.getComment();
464 if (locationString != null) {
465 append(" (" + locationString + ")", SimpleTextAttributes.GRAY_ATTRIBUTES);
471 private final class ChooseScopeTypeAction extends ComboBoxAction{
472 private final Runnable myUpdate;
474 public ChooseScopeTypeAction(final Runnable update) {
475 myUpdate = update;
478 @NotNull
479 protected DefaultActionGroup createPopupActionGroup(final JComponent button) {
480 final DefaultActionGroup group = new DefaultActionGroup();
481 for (final PatternDialectProvider provider : Extensions.getExtensions(PatternDialectProvider.EP_NAME)) {
482 group.add(new AnAction(provider.getDisplayName()) {
483 public void actionPerformed(final AnActionEvent e) {
484 DependencyUISettings.getInstance().SCOPE_TYPE = provider.getShortName();
485 myUpdate.run();
489 return group;
492 public void update(final AnActionEvent e) {
493 super.update(e);
494 final PatternDialectProvider provider = PatternDialectProvider.getInstance(DependencyUISettings.getInstance().SCOPE_TYPE);
495 e.getPresentation().setText(provider.getDisplayName());
496 e.getPresentation().setIcon(provider.getIcon());
500 private final class FilterLegalsAction extends ToggleAction {
501 private final Runnable myUpdate;
503 public FilterLegalsAction(final Runnable update) {
504 super(IdeBundle.message("action.show.included.only"),
505 IdeBundle.message("action.description.show.included.only"), IconLoader.getIcon("/ant/filter.png"));
506 myUpdate = update;
509 public boolean isSelected(AnActionEvent event) {
510 return DependencyUISettings.getInstance().UI_FILTER_LEGALS;
513 public void setSelected(AnActionEvent event, boolean flag) {
514 DependencyUISettings.getInstance().UI_FILTER_LEGALS = flag;
515 UIUtil.setEnabled(myLegendPanel, !flag, true);
516 myUpdate.run();
520 protected class MyPanelProgressIndicator extends PanelProgressIndicator {
521 private final boolean myCheckVisible;
522 private final boolean myRequestFocus;
524 public MyPanelProgressIndicator(final boolean checkVisible, final boolean requestFocus) {
525 super(new Consumer<JComponent>() {
526 public void consume(final JComponent component) {
527 setToComponent(component, requestFocus);
530 myCheckVisible = checkVisible;
531 myRequestFocus = requestFocus;
534 public void start() {
535 super.start();
536 myTextChanged = false;
539 public boolean isCanceled() {
540 return super.isCanceled() || myTextChanged || (myCheckVisible && !myPanel.isShowing());
543 public void stop() {
544 super.stop();
545 setToComponent(myMatchingCountLabel, myRequestFocus);
548 public String getText() { //just show non-blocking progress
549 return null;
552 public String getText2() {
553 return null;