thread vialoations fixed
[fedora-idea.git] / platform / lang-impl / src / com / intellij / ide / util / scopeChooser / ScopeEditorPanel.java
blobadeae19c037ecc2b92ec60638d633b91d5b2609f
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 final String text = myCurrentScope.getText();
303 SwingUtilities.invokeLater(new Runnable() {
304 public void run() {
305 myPatternField.setText(text);
309 try {
310 if (!myProject.isDisposed()) {
311 updateTreeModel(requestFocus);
314 catch (ProcessCanceledException e) {
315 return;
317 if (runnable != null) {
318 runnable.run();
320 myIsInUpdate = false;
325 myUpdateAlarm.addRequest(request, 1000);
328 private void rebuild(final boolean updateText) {
329 rebuild(updateText, null, true);
332 private static void initTree(Tree tree) {
333 tree.setCellRenderer(new MyTreeCellRenderer());
334 tree.setRootVisible(false);
335 tree.setShowsRootHandles(true);
336 tree.setLineStyleAngled();
338 TreeToolTipHandler.install(tree);
339 TreeUtil.installActions(tree);
340 SmartExpander.installOn(tree);
341 new TreeSpeedSearch(tree);
344 private void updateTreeModel(final boolean requestFocus) throws ProcessCanceledException {
345 PanelProgressIndicator progress = createProgressIndicator(requestFocus);
346 progress.setBordersVisible(false);
347 myCurrentProgress = progress;
348 Runnable updateModel = new Runnable() {
349 public void run() {
350 final ProcessCanceledException [] ex = new ProcessCanceledException[1];
351 ApplicationManager.getApplication().runReadAction(new Runnable() {
352 public void run() {
353 try {
354 myTreeExpansionMonitor.freeze();
355 final TreeModel model = PatternDialectProvider.getInstance(DependencyUISettings.getInstance().SCOPE_TYPE).createTreeModel(myProject, myTreeMarker);
356 if (myErrorMessage == null) {
357 myMatchingCountLabel
358 .setText(IdeBundle.message("label.scope.contains.files", model.getMarkedFileCount(), model.getTotalFileCount()));
359 myMatchingCountLabel.setForeground(new JLabel().getForeground());
361 else {
362 showErrorMessage();
365 SwingUtilities.invokeLater(new Runnable(){
366 public void run() { //not under progress
367 myPackageTree.setModel(model);
368 myTreeExpansionMonitor.restore();
371 } catch (ProcessCanceledException e) {
372 ex[0] = e;
374 finally {
375 myCurrentProgress = null;
376 //update label
377 setToComponent(myMatchingCountLabel, requestFocus);
381 if (ex[0] != null) {
382 throw ex[0];
386 ProgressManager.getInstance().runProcess(updateModel, progress);
389 protected PanelProgressIndicator createProgressIndicator(final boolean requestFocus) {
390 return new MyPanelProgressIndicator(true, requestFocus);
393 public void cancelCurrentProgress(){
394 if (myCurrentProgress != null && myCurrentProgress.isRunning()){
395 myCurrentProgress.cancel();
399 public void apply() throws ConfigurationException {
400 if (myCurrentScope == null) {
401 throw new ConfigurationException(IdeBundle.message("error.correct.pattern.syntax.errors.first"));
405 public PackageSet getCurrentScope() {
406 return myCurrentScope;
409 public void reset(PackageSet packageSet, Runnable runnable){
410 myCurrentScope = packageSet;
411 myPatternField.setText(myCurrentScope == null ? "" : myCurrentScope.getText());
412 rebuild(false, runnable, false);
415 private void setToComponent(final JComponent cmp, final boolean requestFocus) {
416 myMatchingCountPanel.removeAll();
417 myMatchingCountPanel.add(cmp, BorderLayout.CENTER);
418 myMatchingCountPanel.revalidate();
419 myMatchingCountPanel.repaint();
420 if (requestFocus) {
421 SwingUtilities.invokeLater(new Runnable(){
422 public void run() {
423 myPatternField.requestFocusInWindow();
429 public void restoreCanceledProgress() {
430 if (myIsInUpdate) {
431 rebuild(false);
435 public void clearCaches() {
436 FileTreeModelBuilder.clearCaches(myProject);
439 public NamedScopesHolder getHolder() {
440 return myHolder;
443 private static class MyTreeCellRenderer extends ColoredTreeCellRenderer {
444 private static final Color WHOLE_INCLUDED = new Color(10, 119, 0);
445 private static final Color PARTIAL_INCLUDED = new Color(0, 50, 160);
447 public void customizeCellRenderer(JTree tree,
448 Object value,
449 boolean selected,
450 boolean expanded,
451 boolean leaf,
452 int row,
453 boolean hasFocus) {
454 if (value instanceof PackageDependenciesNode) {
455 PackageDependenciesNode node = (PackageDependenciesNode)value;
456 if (expanded) {
457 setIcon(node.getOpenIcon());
459 else {
460 setIcon(node.getClosedIcon());
463 setForeground(selected && hasFocus ? UIUtil.getTreeSelectionForeground() : UIUtil.getTreeForeground());
464 if (!selected && node.hasMarked() && !DependencyUISettings.getInstance().UI_FILTER_LEGALS) {
465 setForeground(node.hasUnmarked() ? PARTIAL_INCLUDED : WHOLE_INCLUDED);
467 append(node.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES);
468 final String locationString = node.getComment();
469 if (locationString != null) {
470 append(" (" + locationString + ")", SimpleTextAttributes.GRAY_ATTRIBUTES);
476 private final class ChooseScopeTypeAction extends ComboBoxAction{
477 private final Runnable myUpdate;
479 public ChooseScopeTypeAction(final Runnable update) {
480 myUpdate = update;
483 @NotNull
484 protected DefaultActionGroup createPopupActionGroup(final JComponent button) {
485 final DefaultActionGroup group = new DefaultActionGroup();
486 for (final PatternDialectProvider provider : Extensions.getExtensions(PatternDialectProvider.EP_NAME)) {
487 group.add(new AnAction(provider.getDisplayName()) {
488 public void actionPerformed(final AnActionEvent e) {
489 DependencyUISettings.getInstance().SCOPE_TYPE = provider.getShortName();
490 myUpdate.run();
494 return group;
497 public void update(final AnActionEvent e) {
498 super.update(e);
499 final PatternDialectProvider provider = PatternDialectProvider.getInstance(DependencyUISettings.getInstance().SCOPE_TYPE);
500 e.getPresentation().setText(provider.getDisplayName());
501 e.getPresentation().setIcon(provider.getIcon());
505 private final class FilterLegalsAction extends ToggleAction {
506 private final Runnable myUpdate;
508 public FilterLegalsAction(final Runnable update) {
509 super(IdeBundle.message("action.show.included.only"),
510 IdeBundle.message("action.description.show.included.only"), IconLoader.getIcon("/ant/filter.png"));
511 myUpdate = update;
514 public boolean isSelected(AnActionEvent event) {
515 return DependencyUISettings.getInstance().UI_FILTER_LEGALS;
518 public void setSelected(AnActionEvent event, boolean flag) {
519 DependencyUISettings.getInstance().UI_FILTER_LEGALS = flag;
520 UIUtil.setEnabled(myLegendPanel, !flag, true);
521 myUpdate.run();
525 protected class MyPanelProgressIndicator extends PanelProgressIndicator {
526 private final boolean myCheckVisible;
527 private final boolean myRequestFocus;
529 public MyPanelProgressIndicator(final boolean checkVisible, final boolean requestFocus) {
530 super(new Consumer<JComponent>() {
531 public void consume(final JComponent component) {
532 setToComponent(component, requestFocus);
535 myCheckVisible = checkVisible;
536 myRequestFocus = requestFocus;
539 public void start() {
540 super.start();
541 myTextChanged = false;
544 public boolean isCanceled() {
545 return super.isCanceled() || myTextChanged || (myCheckVisible && !myPanel.isShowing());
548 public void stop() {
549 super.stop();
550 setToComponent(myMatchingCountLabel, myRequestFocus);
553 public String getText() { //just show non-blocking progress
554 return null;
557 public String getText2() {
558 return null;