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
;
47 import javax
.swing
.event
.CaretEvent
;
48 import javax
.swing
.event
.CaretListener
;
49 import javax
.swing
.event
.DocumentEvent
;
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
) {
87 public ScopeEditorPanel(Project project
, final NamedScopesHolder 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
) {
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));
138 myCaretPositionLabel
.setText("");
142 public JPanel
getPanel() {
146 public JPanel
getTreePanel(){
147 JPanel panel
= new JPanel(new BorderLayout());
148 panel
.add(myTreePanel
, BorderLayout
.CENTER
);
149 panel
.add(myLegendPanel
, BorderLayout
.SOUTH
);
153 public JPanel
getTreeToolbar() {
154 return myTreeToolbar
;
157 private void onTextChange() {
159 myUpdateAlarm
.cancelAllRequests();
160 myCurrentScope
= null;
162 myCurrentScope
= PackageSetFactory
.getInstance().compile(myPatternField
.getText());
163 myErrorMessage
= null;
164 myTextChanged
= true;
167 catch (Exception e
) {
168 myErrorMessage
= e
.getMessage();
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);
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
);
226 myCurrentScope
= new IntersectionPackageSet(myCurrentScope
, new ComplementPackageSet(set
));
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
;
240 myCurrentScope
= new UnionPackageSet(myCurrentScope
, set
);
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
);
262 private JComponent
createTreeToolbar() {
263 final DefaultActionGroup group
= new DefaultActionGroup();
264 final Runnable update
= new Runnable() {
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
)) {
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() {
298 ApplicationManager
.getApplication().executeOnPooledThread(new Runnable() {
301 if (updateText
&& myCurrentScope
!= null) {
302 final String text
= myCurrentScope
.getText();
303 SwingUtilities
.invokeLater(new Runnable() {
305 myPatternField
.setText(text
);
310 if (!myProject
.isDisposed()) {
311 updateTreeModel(requestFocus
);
314 catch (ProcessCanceledException e
) {
317 if (runnable
!= null) {
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() {
350 final ProcessCanceledException
[] ex
= new ProcessCanceledException
[1];
351 ApplicationManager
.getApplication().runReadAction(new Runnable() {
354 myTreeExpansionMonitor
.freeze();
355 final TreeModel model
= PatternDialectProvider
.getInstance(DependencyUISettings
.getInstance().SCOPE_TYPE
).createTreeModel(myProject
, myTreeMarker
);
356 if (myErrorMessage
== null) {
358 .setText(IdeBundle
.message("label.scope.contains.files", model
.getMarkedFileCount(), model
.getTotalFileCount()));
359 myMatchingCountLabel
.setForeground(new JLabel().getForeground());
365 SwingUtilities
.invokeLater(new Runnable(){
366 public void run() { //not under progress
367 myPackageTree
.setModel(model
);
368 myTreeExpansionMonitor
.restore();
371 } catch (ProcessCanceledException e
) {
375 myCurrentProgress
= null;
377 setToComponent(myMatchingCountLabel
, requestFocus
);
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();
421 SwingUtilities
.invokeLater(new Runnable(){
423 myPatternField
.requestFocusInWindow();
429 public void restoreCanceledProgress() {
435 public void clearCaches() {
436 FileTreeModelBuilder
.clearCaches(myProject
);
439 public NamedScopesHolder
getHolder() {
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
,
454 if (value
instanceof PackageDependenciesNode
) {
455 PackageDependenciesNode node
= (PackageDependenciesNode
)value
;
457 setIcon(node
.getOpenIcon());
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
) {
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();
497 public void update(final AnActionEvent 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"));
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);
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() {
541 myTextChanged
= false;
544 public boolean isCanceled() {
545 return super.isCanceled() || myTextChanged
|| (myCheckVisible
&& !myPanel
.isShowing());
550 setToComponent(myMatchingCountLabel
, myRequestFocus
);
553 public String
getText() { //just show non-blocking progress
557 public String
getText2() {