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 myPatternField
.setText(myCurrentScope
.getText());
305 if (!myProject
.isDisposed()) {
306 updateTreeModel(requestFocus
);
309 catch (ProcessCanceledException e
) {
312 if (runnable
!= null) {
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() {
345 final ProcessCanceledException
[] ex
= new ProcessCanceledException
[1];
346 ApplicationManager
.getApplication().runReadAction(new Runnable() {
349 myTreeExpansionMonitor
.freeze();
350 final TreeModel model
= PatternDialectProvider
.getInstance(DependencyUISettings
.getInstance().SCOPE_TYPE
).createTreeModel(myProject
, myTreeMarker
);
351 if (myErrorMessage
== null) {
353 .setText(IdeBundle
.message("label.scope.contains.files", model
.getMarkedFileCount(), model
.getTotalFileCount()));
354 myMatchingCountLabel
.setForeground(new JLabel().getForeground());
360 SwingUtilities
.invokeLater(new Runnable(){
361 public void run() { //not under progress
362 myPackageTree
.setModel(model
);
363 myTreeExpansionMonitor
.restore();
366 } catch (ProcessCanceledException e
) {
370 myCurrentProgress
= null;
372 setToComponent(myMatchingCountLabel
, requestFocus
);
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();
416 SwingUtilities
.invokeLater(new Runnable(){
418 myPatternField
.requestFocusInWindow();
424 public void restoreCanceledProgress() {
430 public void clearCaches() {
431 FileTreeModelBuilder
.clearCaches(myProject
);
434 public NamedScopesHolder
getHolder() {
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
,
449 if (value
instanceof PackageDependenciesNode
) {
450 PackageDependenciesNode node
= (PackageDependenciesNode
)value
;
452 setIcon(node
.getOpenIcon());
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
) {
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();
492 public void update(final AnActionEvent 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"));
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);
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() {
536 myTextChanged
= false;
539 public boolean isCanceled() {
540 return super.isCanceled() || myTextChanged
|| (myCheckVisible
&& !myPanel
.isShowing());
545 setToComponent(myMatchingCountLabel
, myRequestFocus
);
548 public String
getText() { //just show non-blocking progress
552 public String
getText2() {