IDEA-144857 add some love to new Find in Path popup
[idea/community.git] / platform / lang-impl / src / com / intellij / find / impl / FindPopupPanel.java
1 /*
2  * Copyright 2000-2016 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.find.impl;
17
18 import com.intellij.find.*;
19 import com.intellij.find.actions.ShowUsagesAction;
20 import com.intellij.find.findInProject.FindInProjectManager;
21 import com.intellij.icons.AllIcons;
22 import com.intellij.ide.ui.UISettings;
23 import com.intellij.ide.util.scopeChooser.ScopeChooserCombo;
24 import com.intellij.ide.util.scopeChooser.ScopeDescriptor;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.MnemonicHelper;
27 import com.intellij.openapi.actionSystem.*;
28 import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
29 import com.intellij.openapi.actionSystem.impl.ActionButton;
30 import com.intellij.openapi.actionSystem.impl.ActionButtonWithText;
31 import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.application.ModalityState;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.fileChooser.FileChooser;
36 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
37 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
38 import com.intellij.openapi.keymap.KeymapUtil;
39 import com.intellij.openapi.module.Module;
40 import com.intellij.openapi.module.ModuleManager;
41 import com.intellij.openapi.module.ModuleUtilCore;
42 import com.intellij.openapi.progress.ProgressIndicator;
43 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
44 import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
45 import com.intellij.openapi.progress.util.ReadTask;
46 import com.intellij.openapi.project.DumbAwareAction;
47 import com.intellij.openapi.project.Project;
48 import com.intellij.openapi.ui.*;
49 import com.intellij.openapi.ui.popup.ComponentPopupBuilder;
50 import com.intellij.openapi.ui.popup.JBPopup;
51 import com.intellij.openapi.ui.popup.JBPopupFactory;
52 import com.intellij.openapi.util.*;
53 import com.intellij.openapi.util.text.StringUtil;
54 import com.intellij.openapi.vfs.LocalFileSystem;
55 import com.intellij.openapi.vfs.VirtualFile;
56 import com.intellij.openapi.wm.WindowManager;
57 import com.intellij.openapi.wm.impl.IdeFrameImpl;
58 import com.intellij.psi.PsiBundle;
59 import com.intellij.psi.search.SearchScope;
60 import com.intellij.ui.*;
61 import com.intellij.ui.awt.RelativePoint;
62 import com.intellij.ui.components.JBLabel;
63 import com.intellij.ui.components.JBPanel;
64 import com.intellij.ui.components.JBScrollPane;
65 import com.intellij.ui.components.labels.LinkLabel;
66 import com.intellij.ui.popup.AbstractPopup;
67 import com.intellij.ui.popup.PopupPositionManager;
68 import com.intellij.ui.table.JBTable;
69 import com.intellij.usageView.UsageInfo;
70 import com.intellij.usages.FindUsagesProcessPresentation;
71 import com.intellij.usages.Usage;
72 import com.intellij.usages.UsageInfo2UsageAdapter;
73 import com.intellij.usages.UsageViewPresentation;
74 import com.intellij.usages.impl.UsagePreviewPanel;
75 import com.intellij.util.*;
76 import com.intellij.util.ui.EmptyIcon;
77 import com.intellij.util.ui.JBFont;
78 import com.intellij.util.ui.JBUI;
79 import com.intellij.util.ui.UIUtil;
80 import net.miginfocom.swing.MigLayout;
81 import org.jetbrains.annotations.NotNull;
82 import org.jetbrains.annotations.Nullable;
83
84 import javax.swing.*;
85 import javax.swing.event.DocumentEvent;
86 import javax.swing.event.ListSelectionEvent;
87 import javax.swing.event.ListSelectionListener;
88 import javax.swing.table.DefaultTableModel;
89 import java.awt.*;
90 import java.awt.event.*;
91 import java.util.ArrayList;
92 import java.util.Arrays;
93 import java.util.Collections;
94 import java.util.List;
95 import java.util.concurrent.atomic.AtomicBoolean;
96 import java.util.concurrent.atomic.AtomicInteger;
97 import java.util.regex.Pattern;
98 import java.util.regex.PatternSyntaxException;
99
100 import static com.intellij.find.impl.FindDialog.createCheckbox;
101
102 public class FindPopupPanel extends JBPanel {
103   private static final Logger LOG = Logger.getInstance(FindPopupPanel.class);
104   // unify with CommonShortcuts.CTRL_ENTER
105   private static final KeyStroke OK_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, SystemInfo.isMac
106                                                                                           ? InputEvent.META_DOWN_MASK
107                                                                                           : InputEvent.CTRL_DOWN_MASK);
108
109   private static final KeyStroke MOVE_CARET_DOWN = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
110   private static final KeyStroke MOVE_CARET_UP = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
111   private static final KeyStroke NEW_LINE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
112
113   private static final KeyStroke MOVE_CARET_DOWN_ALTERNATIVE = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK);
114   private static final KeyStroke MOVE_CARET_UP_ALTERNATIVE = KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK);
115   private static final KeyStroke NEW_LINE_ALTERNATIVE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.ALT_DOWN_MASK);
116
117   private JComponent myCodePreviewComponent;
118   private SearchTextArea mySearchTextArea;
119   private SearchTextArea myReplaceTextArea;
120   private ActionListener myOkActionListener;
121
122   enum Scope {
123     PROJECT, MODULE, DIRECTORY, SCOPE
124   }
125
126   @NotNull private final Project myProject;
127   @NotNull private final FindModel myModel;
128   @NotNull private final DataContext myDataContext;
129   @NotNull private final Disposable myDisposable;
130
131   private Alarm mySearchRescheduleOnCancellationsAlarm;
132   private Alarm myUpdateResultsPopupBoundsAlarm;
133   private volatile ProgressIndicatorBase myResultsPreviewSearchProgress;
134
135
136   private JLabel myTitleLabel;
137   private StateRestoringCheckBox myCbCaseSensitive;
138   private StateRestoringCheckBox myCbPreserveCase;
139   private StateRestoringCheckBox myCbWholeWordsOnly;
140   private StateRestoringCheckBox myCbRegularExpressions;
141   private StateRestoringCheckBox myCbFileFilter;
142   private ActionToolbarImpl myScopeSelectionToolbar;
143   private TextFieldWithAutoCompletion<String> myFileMaskField;
144   private ArrayList<String> myFileMasks = new ArrayList<String>();
145   private ActionButton myFilterContextButton;
146   private ActionButton myTabResultsButton;
147   private JButton myOKButton;
148   private JTextArea mySearchComponent;
149   private JTextArea myReplaceComponent;
150   private String mySelectedContextName = FindBundle.message("find.context.anywhere.scope.label");
151   private Scope mySelectedScope = Scope.PROJECT;
152   private JPanel myScopeDetailsPanel;
153   private ComboBox myModuleComboBox;
154   private ComboBox myDirectoryComboBox;
155   private FixedSizeButton mySelectDirectoryButton;
156   private JToggleButton myRecursiveDirectoryButton;
157   private ScopeChooserCombo myScopeCombo;
158
159   private JBTable myResultsPreviewTable;
160   private UsagePreviewPanel myUsagePreviewPanel;
161   private JBPopup myFindBalloon;
162   private AbstractPopup myResultsPopup;
163
164   static void showBalloon(@NotNull Project project, @NotNull FindModel model, @NotNull DataContext dataContext) {
165     FindPopupPanel panel = new FindPopupPanel(project, model, dataContext);
166     panel.doShowBalloon();
167   }
168
169   private void doShowBalloon() {
170     if (myFindBalloon != null && myFindBalloon.isVisible()) {
171       return;
172     }
173     if (myFindBalloon != null && !myFindBalloon.isDisposed()) {
174       myFindBalloon.cancel();
175     }
176     if (myFindBalloon == null || myFindBalloon.isDisposed()) {
177       final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(this, getPreferredFocusedComponent());
178       myFindBalloon = builder
179         .setProject(myProject)
180         .setMayBeParent(true)
181         .setModalContext(false)
182         .setRequestFocus(true)
183         .createPopup();
184       Disposer.register(myFindBalloon, myDisposable);
185       registerCloseAction(myFindBalloon);
186       myFindBalloon.getContent().setBorder(JBUI.Borders.empty());
187       final Window window = WindowManager.getInstance().suggestParentWindow(myProject);
188       Component parent = UIUtil.findUltimateParent(window);
189       final RelativePoint showPoint;
190       if (parent != null) {
191         int height = UISettings.getInstance().SHOW_MAIN_TOOLBAR ? 135 : 115;
192         if (parent instanceof IdeFrameImpl && ((IdeFrameImpl)parent).isInFullScreen()) {
193           height -= 20;
194         }
195         showPoint = new RelativePoint(parent, new Point((parent.getSize().width - getPreferredSize().width) / 2, height));
196       }
197       else {
198         showPoint = JBPopupFactory.getInstance().guessBestPopupLocation(myDataContext);
199       }
200       myFindBalloon.show(showPoint);
201       myFindBalloon.pack(true, true);
202     }
203   }
204
205   private FindPopupPanel(@NotNull Project project, @NotNull FindModel model, @NotNull DataContext dataContext) {
206     myProject = project;
207     myModel = model;
208     myDataContext = dataContext;
209     myDisposable = Disposer.newDisposable();
210     Disposer.register(myDisposable, new Disposable() {
211       @Override
212       public void dispose() {
213         FindPopupPanel.this.finishPreviousPreviewSearch();
214         if (mySearchRescheduleOnCancellationsAlarm != null) Disposer.dispose(mySearchRescheduleOnCancellationsAlarm);
215         if (myUpdateResultsPopupBoundsAlarm != null) Disposer.dispose(myUpdateResultsPopupBoundsAlarm);
216         if (myUsagePreviewPanel != null) Disposer.dispose(myUsagePreviewPanel);
217       }
218     });
219
220     initComponents();
221     initByModel();
222     updateReplaceVisibility();
223
224     ApplicationManager.getApplication().invokeLater(new Runnable() {
225       @Override
226       public void run() {
227         FindPopupPanel.this.scheduleResultsUpdate();
228       }
229     }, ModalityState.any());
230   }
231
232   private void initComponents() {
233     myTitleLabel = new JBLabel(FindBundle.message("find.in.path.dialog.title"), UIUtil.ComponentStyle.REGULAR);
234     myTitleLabel.setFont(myTitleLabel.getFont().deriveFont(Font.BOLD));
235     myCbCaseSensitive = createCheckbox(FindBundle.message("find.popup.case.sensitive"));
236     ItemListener liveResultsPreviewUpdateListener = new ItemListener() {
237       @Override
238       public void itemStateChanged(ItemEvent e) {
239         scheduleResultsUpdate();
240       }
241     };
242     myCbCaseSensitive.addItemListener(liveResultsPreviewUpdateListener);
243     myCbPreserveCase = createCheckbox(FindBundle.message("find.options.replace.preserve.case"));
244     myCbPreserveCase.addItemListener(liveResultsPreviewUpdateListener);
245     myCbPreserveCase.setVisible(myModel.isReplaceState());
246     myCbWholeWordsOnly = createCheckbox(FindBundle.message("find.popup.whole.words"));
247     myCbWholeWordsOnly.addItemListener(liveResultsPreviewUpdateListener);
248     myCbRegularExpressions = createCheckbox(FindBundle.message("find.popup.regex"));
249     myCbRegularExpressions.addItemListener(liveResultsPreviewUpdateListener);
250     myCbFileFilter = createCheckbox("");
251     myCbFileFilter.setMargin(new Insets(0, 0, 0, 0));
252     myCbFileFilter.setBorder(null);
253     myCbFileFilter.addItemListener(liveResultsPreviewUpdateListener);
254     myFileMaskField =
255       new TextFieldWithAutoCompletion<String>(myProject, new TextFieldWithAutoCompletion.StringsCompletionProvider(myFileMasks, null),
256                                               false, null) {
257         @Override
258         public void setEnabled(boolean enabled) {
259           super.setEnabled(enabled);
260           setBackground(enabled ? JBColor.background() : UIUtil.getComboBoxDisabledBackground());
261         }
262       };
263     myFileMaskField.setPreferredWidth(JBUI.scale(100));
264     myFileMaskField.addDocumentListener(new com.intellij.openapi.editor.event.DocumentAdapter() {
265       @Override
266       public void documentChanged(com.intellij.openapi.editor.event.DocumentEvent e) {
267         scheduleResultsUpdate();
268       }
269     });
270     myCbFileFilter.addItemListener(new ItemListener() {
271       @Override
272       public void itemStateChanged(ItemEvent e) {
273         myFileMaskField.setEnabled(myCbFileFilter.isSelected());
274       }
275     });
276     DefaultActionGroup switchContextGroup = new DefaultActionGroup();
277     switchContextGroup.add(new MySwitchContextToggleAction(FindModel.SearchContext.ANY));
278     switchContextGroup.add(new MySwitchContextToggleAction(FindModel.SearchContext.IN_COMMENTS));
279     switchContextGroup.add(new MySwitchContextToggleAction(FindModel.SearchContext.IN_STRING_LITERALS));
280     switchContextGroup.add(new MySwitchContextToggleAction(FindModel.SearchContext.EXCEPT_COMMENTS));
281     switchContextGroup.add(new MySwitchContextToggleAction(FindModel.SearchContext.EXCEPT_STRING_LITERALS));
282     switchContextGroup.add(new MySwitchContextToggleAction(FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS));
283     switchContextGroup.setPopup(true);
284     Presentation filterPresentation = new Presentation();
285     filterPresentation.setIcon(AllIcons.General.Filter);
286     myFilterContextButton =
287       new ActionButton(switchContextGroup, filterPresentation, ActionPlaces.UNKNOWN, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE) {
288         @Override
289         public int getPopState() {
290           int state = super.getPopState();
291           if (state != ActionButtonComponent.NORMAL) return state;
292           return mySelectedContextName.equals(FindDialog.getPresentableName(FindModel.SearchContext.ANY))
293                  ? ActionButtonComponent.NORMAL
294                  : ActionButtonComponent.PUSHED;
295         }
296       };
297
298     DefaultActionGroup tabResultsContextGroup = new DefaultActionGroup();
299     tabResultsContextGroup.add(new ToggleAction(FindBundle.message("find.options.skip.results.tab.with.one.usage.checkbox")) {
300       @Override
301       public boolean isSelected(AnActionEvent e) {
302         return FindSettings.getInstance().isSkipResultsWithOneUsage();
303       }
304
305       @Override
306       public void setSelected(AnActionEvent e, boolean state) {
307         FindSettings.getInstance().setSkipResultsWithOneUsage(state);
308       }
309     });
310     tabResultsContextGroup.add(new ToggleAction(FindBundle.message("find.open.in.new.tab.checkbox")) {
311       @Override
312       public boolean isSelected(AnActionEvent e) {
313         return FindSettings.getInstance().isShowResultsInSeparateView();
314       }
315
316       @Override
317       public void setSelected(AnActionEvent e, boolean state) {
318         FindSettings.getInstance().setShowResultsInSeparateView(state);
319       }
320     });
321     tabResultsContextGroup.setPopup(true);
322     Presentation tabSettingsPresentation = new Presentation();
323     tabSettingsPresentation.setIcon(AllIcons.General.SecondaryGroup);
324     myTabResultsButton =
325       new ActionButton(tabResultsContextGroup, tabSettingsPresentation, ActionPlaces.UNKNOWN, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE);
326     myOKButton = new JButton(FindBundle.message("find.popup.find.button"));
327     myOkActionListener = new ActionListener() {
328       @Override
329       public void actionPerformed(ActionEvent e) {
330         findSettingsChanged();
331         FindInProjectManager.getInstance(myProject).startFindInProject(myModel);
332         Disposer.dispose(myFindBalloon);
333       }
334     };
335     myOKButton.addActionListener(myOkActionListener);
336     registerKeyboardAction(myOkActionListener, OK_KEYSTROKE, WHEN_IN_FOCUSED_WINDOW);
337     mySearchComponent = new JTextArea();
338     mySearchComponent.setColumns(25);
339     mySearchComponent.setRows(1);
340     myReplaceComponent = new JTextArea();
341     myReplaceComponent.setColumns(25);
342     myReplaceComponent.setRows(1);
343     mySearchTextArea = new SearchTextArea(mySearchComponent, true);
344     myReplaceTextArea = new SearchTextArea(myReplaceComponent, false);
345     DocumentAdapter documentAdapter = new DocumentAdapter() {
346       @Override
347       protected void textChanged(DocumentEvent e) {
348         int searchRows1 = mySearchComponent.getRows();
349         int searchRows2 = Math.max(1, Math.min(3, StringUtil.countChars(mySearchComponent.getText(), '\n') + 1));
350         mySearchComponent.setRows(searchRows2);
351         int replaceRows1 = myReplaceComponent.getRows();
352         int replaceRows2 = Math.max(1, Math.min(3, StringUtil.countChars(myReplaceComponent.getText(), '\n') + 1));
353         myReplaceComponent.setRows(replaceRows2);
354
355         if (myFindBalloon == null) return;
356
357         if (searchRows1 != searchRows2 || replaceRows1 != replaceRows2) {
358           Point resultsLocation = myResultsPopup != null && myResultsPopup.isVisible() ? myResultsPopup.getLocationOnScreen() : null;
359           Dimension findSize = myFindBalloon.getSize();
360           myFindBalloon.pack(false, true);
361           if (resultsLocation != null) {
362             int hDiff = myFindBalloon.getSize().height - findSize.height;
363             myResultsPopup.setLocation(new Point(resultsLocation.x, resultsLocation.y + hDiff));
364           }
365         }
366         scheduleResultsUpdate();
367       }
368     };
369     mySearchComponent.getDocument().addDocumentListener(documentAdapter);
370     myReplaceComponent.getDocument().addDocumentListener(documentAdapter);
371
372
373     DefaultActionGroup scopeActionGroup = new DefaultActionGroup();
374     scopeActionGroup.add(new MySelectScopeToggleAction(Scope.PROJECT));
375     scopeActionGroup.add(new MySelectScopeToggleAction(Scope.MODULE));
376     scopeActionGroup.add(new MySelectScopeToggleAction(Scope.DIRECTORY));
377     scopeActionGroup.add(new MySelectScopeToggleAction(Scope.SCOPE));
378     myScopeSelectionToolbar =
379       (ActionToolbarImpl)ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, scopeActionGroup, true);
380     myScopeSelectionToolbar.setForceMinimumSize(true);
381     myScopeSelectionToolbar.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY);
382
383     Module[] modules = ModuleManager.getInstance(myProject).getModules();
384     String[] names = new String[modules.length];
385     for (int i = 0; i < modules.length; i++) {
386       names[i] = modules[i].getName();
387     }
388
389     Arrays.sort(names, String.CASE_INSENSITIVE_ORDER);
390     myModuleComboBox = new ComboBox(names);
391     myModuleComboBox.addActionListener(new ActionListener() {
392       @Override
393       public void actionPerformed(ActionEvent e) {
394         scheduleResultsUpdate();
395       }
396     });
397     myDirectoryComboBox = new ComboBox(200);
398     Component editorComponent = myDirectoryComboBox.getEditor().getEditorComponent();
399     if (editorComponent instanceof JTextField) {
400       JTextField field = (JTextField)editorComponent;
401       field.setColumns(40);
402     }
403     initCombobox(myDirectoryComboBox);
404     myDirectoryComboBox.addActionListener(new ActionListener() {
405       @Override
406       public void actionPerformed(ActionEvent e) {
407         scheduleResultsUpdate();
408       }
409     });
410     mySelectDirectoryButton = new FixedSizeButton(myDirectoryComboBox);
411     TextFieldWithBrowseButton.MyDoClickAction.addTo(mySelectDirectoryButton, myDirectoryComboBox);
412     mySelectDirectoryButton.setMargin(new Insets(0, 0, 0, 0));
413     mySelectDirectoryButton.addActionListener(new ActionListener() {
414       @Override
415       public void actionPerformed(ActionEvent e) {
416         FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
417         FileChooser.chooseFiles(descriptor, myProject, FindPopupPanel.this, null, new Consumer<java.util.List<VirtualFile>>() {
418           @Override
419           public void consume(final java.util.List<VirtualFile> files) {
420             myDirectoryComboBox.setSelectedItem(files.get(0).getPresentableUrl());
421           }
422         });
423       }
424     });
425
426     myRecursiveDirectoryButton = new JToggleButton(AllIcons.General.Recursive, myModel.isWithSubdirectories());
427     myRecursiveDirectoryButton.setIcon(AllIcons.General.Recursive);
428     myRecursiveDirectoryButton.setMargin(new Insets(0, 0, 0, 0));
429     myRecursiveDirectoryButton.addActionListener(new ActionListener() {
430       @Override
431       public void actionPerformed(ActionEvent e) {
432         scheduleResultsUpdate();
433       }
434     });
435     //DefaultActionGroup recursiveActionGroup = new DefaultActionGroup();
436     //ActionToolbar recursiveDirectoryToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, recursiveActionGroup, true);
437     JPanel directoryPanel = new JPanel(new BorderLayout());
438     directoryPanel.add(myDirectoryComboBox, BorderLayout.CENTER);
439     JPanel buttonsPanel = new JPanel(new GridLayout(1, 2));
440     buttonsPanel.add(mySelectDirectoryButton);
441     buttonsPanel.add(myRecursiveDirectoryButton);
442     directoryPanel.add(buttonsPanel, BorderLayout.EAST);
443
444     myScopeCombo = new ScopeChooserCombo();
445     myScopeCombo.init(myProject, true, true, FindSettings.getInstance().getDefaultScopeName(), new Condition<ScopeDescriptor>() {
446       final String projectFilesScopeName = PsiBundle.message("psi.search.scope.project");
447       final String moduleFilesScopeName;
448
449       {
450         String moduleScopeName = PsiBundle.message("search.scope.module", "");
451         final int ind = moduleScopeName.indexOf(' ');
452         moduleFilesScopeName = moduleScopeName.substring(0, ind + 1);
453       }
454
455       @Override
456       public boolean value(ScopeDescriptor descriptor) {
457         final String display = descriptor.getDisplay();
458         return !projectFilesScopeName.equals(display) && !display.startsWith(moduleFilesScopeName);
459       }
460     });
461     myScopeCombo.getComboBox().addActionListener(new ActionListener() {
462       @Override
463       public void actionPerformed(ActionEvent e) {
464         scheduleResultsUpdate();
465       }
466     });
467     Disposer.register(myDisposable, myScopeCombo);
468
469
470     myScopeDetailsPanel = new JPanel(new CardLayout());
471     myScopeDetailsPanel.add(Scope.PROJECT.name(), new JLabel());
472     myScopeDetailsPanel.add(Scope.MODULE.name(), myModuleComboBox);
473     myScopeDetailsPanel.add(Scope.DIRECTORY.name(), directoryPanel);
474     myScopeDetailsPanel.add(Scope.SCOPE.name(), myScopeCombo);
475
476
477     setLayout(new MigLayout("flowx, ins 4, fillx, hidemode 3, gap 0"));
478     add(myTitleLabel, "pushx, gapleft 4");
479     add(Box.createHorizontalStrut(JBUI.scale(50)));
480     add(myCbCaseSensitive);
481     add(myCbPreserveCase);
482     add(myCbWholeWordsOnly);
483     add(myCbRegularExpressions);
484     LinkLabel helpLink = RegExHelpPopup.createRegExLink("<html><body><b>?</b></body></html>", myCbRegularExpressions, LOG);
485     add(helpLink, "gapright 8");
486     add(myCbFileFilter);
487     add(myFileMaskField);
488     add(myFilterContextButton, "wrap");
489     add(mySearchTextArea, "pushx, growx, sx 9, wrap");
490     add(myReplaceTextArea, "pushx, growx, sx 9, wrap");
491     add(myScopeSelectionToolbar.getComponent(), "gaptop 4");
492     add(myScopeDetailsPanel, "sx 8, pushx, growx");
493     MnemonicHelper.init(this);
494
495     myResultsPreviewTable = new JBTable() {
496       @Override
497       public Dimension getPreferredScrollableViewportSize() {
498         return new Dimension(getWidth(), 1 + getRowHeight() * Math.min(9, Math.max(4, getRowCount())));
499       }
500     };
501     myResultsPreviewTable.getEmptyText().setShowAboveCenter(false);
502     myResultsPreviewTable.setShowColumns(false);
503     myResultsPreviewTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
504     myResultsPreviewTable.setShowGrid(false);
505     myResultsPreviewTable.setIntercellSpacing(JBUI.emptySize());
506     new NavigateToSourceListener().installOn(myResultsPreviewTable);
507     applyFont(JBUI.Fonts.label(), myCbCaseSensitive, myCbPreserveCase, myCbWholeWordsOnly, myCbRegularExpressions,
508               myResultsPreviewTable);
509     KeymapUtil.reassignAction(mySearchComponent, MOVE_CARET_DOWN, MOVE_CARET_DOWN_ALTERNATIVE, WHEN_IN_FOCUSED_WINDOW);
510     KeymapUtil.reassignAction(mySearchComponent, MOVE_CARET_UP, MOVE_CARET_UP_ALTERNATIVE, WHEN_IN_FOCUSED_WINDOW);
511     KeymapUtil.reassignAction(mySearchComponent, NEW_LINE, NEW_LINE_ALTERNATIVE, WHEN_IN_FOCUSED_WINDOW);
512     UIUtil.redirectKeystrokes(myDisposable, mySearchComponent, myResultsPreviewTable, MOVE_CARET_UP, MOVE_CARET_DOWN, NEW_LINE);
513
514
515     myUsagePreviewPanel = new UsagePreviewPanel(myProject, new UsageViewPresentation()) {
516       @Override
517       public Dimension getPreferredSize() {
518         Dimension size = super.getPreferredSize();
519         size.height = Math.min(size.height, getLineHeight() * 20);
520         return size;
521       }
522     };
523     Disposer.register(myDisposable, myUsagePreviewPanel);
524     myResultsPreviewTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
525       @Override
526       public void valueChanged(ListSelectionEvent e) {
527         if (e.getValueIsAdjusting()) return;
528         int index = myResultsPreviewTable.getSelectedRow();
529         if (index != -1) {
530           UsageInfo usageInfo = ((UsageInfo2UsageAdapter)myResultsPreviewTable.getModel().getValueAt(index, 0)).getUsageInfo();
531           myUsagePreviewPanel.updateLayout(Collections.singletonList(usageInfo));
532         }
533         else {
534           myUsagePreviewPanel.updateLayout(null);
535         }
536       }
537     });
538     mySearchRescheduleOnCancellationsAlarm = new Alarm();
539     myUpdateResultsPopupBoundsAlarm = new Alarm();
540   }
541
542   private void registerCloseAction(JBPopup popup) {
543     final AnAction escape = ActionManager.getInstance().getAction("EditorEscape");
544     DumbAwareAction closeAction = new DumbAwareAction() {
545       @Override
546       public void actionPerformed(AnActionEvent e) {
547         if (myFindBalloon != null && myFindBalloon.isVisible()) {
548           myFindBalloon.cancel();
549         }
550         if (myResultsPopup != null && myResultsPopup.isVisible()) {
551           myResultsPopup.cancel();
552         }
553       }
554     };
555     closeAction.registerCustomShortcutSet(escape == null ? CommonShortcuts.ESCAPE : escape.getShortcutSet(), popup.getContent(), popup);
556   }
557
558   @Override
559   public void reshape(int x, int y, int w, int h) {
560     super.reshape(x, y, w, h);
561
562     if (myResultsPopup != null && myResultsPopup.isVisible()) {
563       adjustPopup();
564     }
565   }
566
567   @Override
568   public void addNotify() {
569     super.addNotify();
570     showResultsPopupIfNeed();
571     myScopeSelectionToolbar.updateActionsImmediately();
572   }
573
574   private static final int POPUP_MAX_WIDTH = 600;
575
576   private void scheduleUpdateResultsPopupBounds() {
577     if (myUpdateResultsPopupBoundsAlarm == null || myUpdateResultsPopupBoundsAlarm.isDisposed()) return;
578     boolean later = myUpdateResultsPopupBoundsAlarm.getActiveRequestCount() > 0;
579
580     myUpdateResultsPopupBoundsAlarm.cancelAllRequests();
581     myUpdateResultsPopupBoundsAlarm.addRequest(new Runnable() {
582       @Override
583       public void run() {
584         updateResultsPopupBounds();
585       }
586     }, later? 50 : 0);
587   }
588
589   private void updateResultsPopupBounds() {
590     if (myResultsPopup == null || !myResultsPopup.isVisible()) {
591       return;
592     }
593     Dimension size = myResultsPopup.getComponent().getPreferredSize();
594     if (size.width + 2 < getWidth()) {
595       size.width = getWidth();
596     }
597     Dimension sz = new Dimension(size.width, size.height);
598     if (!SystemInfo.isMac) {
599       if ((sz.width > POPUP_MAX_WIDTH || sz.height > POPUP_MAX_WIDTH)) {
600         final JBScrollPane pane = new JBScrollPane();
601         final int extraWidth = pane.getVerticalScrollBar().getWidth() + 1;
602         final int extraHeight = pane.getHorizontalScrollBar().getHeight() + 1;
603         sz = new Dimension(Math.min(POPUP_MAX_WIDTH, Math.max(getWidth(), sz.width + extraWidth)),
604                            Math.min(POPUP_MAX_WIDTH, sz.height + extraHeight));
605         sz.width += 20;
606         sz.height += 2;
607       }
608       else {
609         sz.width += 2;
610         sz.height += 2;
611       }
612     }
613     myResultsPopup.setSize(sz);
614     adjustPopup();
615   }
616
617   private void adjustPopup() {
618     if (!isShowing()) return;
619     final Dimension d = PopupPositionManager.PositionAdjuster.getPopupSize(myResultsPopup);
620     Point myRelativeOnScreen = getLocationOnScreen();
621     Rectangle screen = ScreenUtil.getScreenRectangle(myRelativeOnScreen);
622     Rectangle popupRect = null;
623     Rectangle r = new Rectangle(myRelativeOnScreen.x, myRelativeOnScreen.y + getHeight(), d.width, d.height);
624
625     if (screen.contains(r)) {
626       popupRect = r;
627     }
628
629     if (popupRect != null) {
630       Point location = new Point(r.x, r.y);
631       if (!location.equals(myResultsPopup.getLocationOnScreen())) {
632         myResultsPopup.setLocation(location);
633       }
634     }
635     else {
636       if (r.y + d.height > screen.y + screen.height) {
637         r.height = screen.y + screen.height - r.y - 2;
638       }
639       if (r.width > screen.width) {
640         r.width = screen.width - 50;
641       }
642       if (r.x + r.width > screen.x + screen.width) {
643         r.x = screen.x + screen.width - r.width - 2;
644       }
645       myResultsPopup.setSize(r.getSize());
646       myResultsPopup.setLocation(r.getLocation());
647     }
648   }
649
650   private void initByModel() {
651     myTitleLabel
652       .setText(FindBundle.message(myModel.isReplaceState() ? "find.replace.in.project.dialog.title" : "find.in.path.dialog.title"));
653     myCbCaseSensitive.setSelected(myModel.isCaseSensitive());
654     myCbWholeWordsOnly.setSelected(myModel.isWholeWordsOnly());
655     myCbRegularExpressions.setSelected(myModel.isRegularExpressions());
656
657     mySelectedContextName = FindDialog.getSearchContextName(myModel);
658     if (myModel.isReplaceState()) {
659       myCbPreserveCase.setSelected(myModel.isPreserveCase());
660     }
661     mySelectedScope = getScope(myModel);
662     final String dirName = myModel.getDirectoryName();
663     setDirectories(FindSettings.getInstance().getRecentDirectories(), dirName);
664
665     if (!StringUtil.isEmptyOrSpaces(dirName)) {
666       VirtualFile dir = LocalFileSystem.getInstance().findFileByPath(dirName);
667       if (dir != null) {
668         Module module = ModuleUtilCore.findModuleForFile(dir, myProject);
669         if (module != null) {
670           myModuleComboBox.setSelectedItem(module.getName());
671         }
672       }
673     }
674
675     if (mySelectedScope == Scope.MODULE) {
676       myModuleComboBox.setSelectedItem(myModel.getModuleName());
677     }
678     boolean isThereFileFilter = myModel.getFileFilter() != null && !myModel.getFileFilter().isEmpty();
679     myCbFileFilter.setSelected(isThereFileFilter);
680     List<String> variants = Arrays.asList(ArrayUtil.reverseArray(FindSettings.getInstance().getRecentFileMasks()));
681     myFileMaskField.setVariants(variants);
682     if (!variants.isEmpty()) {
683       myFileMaskField.setText(variants.get(0));
684     }
685     myFileMaskField.setEnabled(isThereFileFilter);
686     updateScopeDetailsPanel();
687     String toSearch = myModel.getStringToFind();
688     if (StringUtil.isEmpty(toSearch)) {
689       String[] history = FindSettings.getInstance().getRecentFindStrings();
690       toSearch = history.length > 0 ? history[history.length - 1] : "";
691     }
692     mySearchComponent.setText(toSearch);
693     String toReplace = myModel.getStringToReplace();
694     if (StringUtil.isEmpty(toReplace)) {
695       String[] history = FindSettings.getInstance().getRecentReplaceStrings();
696       toReplace = history.length > 0 ? history[history.length - 1] : "";
697     }
698     myReplaceComponent.setText(toReplace);
699     updateControls();
700     updateScopeDetailsPanel();
701     updateReplaceVisibility();
702   }
703
704   private void setDirectories(@NotNull List<String> strings, String directoryName) {
705     if (myDirectoryComboBox.getItemCount() > 0) {
706       myDirectoryComboBox.removeAllItems();
707     }
708     if (directoryName != null && !directoryName.isEmpty()) {
709       if (strings.contains(directoryName)) {
710         strings.remove(directoryName);
711       }
712       myDirectoryComboBox.addItem(directoryName);
713     }
714     for (int i = strings.size() - 1; i >= 0; i--) {
715       myDirectoryComboBox.addItem(strings.get(i));
716     }
717     if (myDirectoryComboBox.getItemCount() == 0) {
718       myDirectoryComboBox.addItem("");
719     }
720   }
721
722   private static Scope getScope(FindModel model) {
723     if (model.isCustomScope()) {
724       return Scope.SCOPE;
725     } else
726     if (model.isProjectScope()) {
727       return Scope.PROJECT;
728     } else
729     if (model.getDirectoryName() != null) {
730        return Scope.DIRECTORY;
731     } else
732     if (model.getModuleName() != null) {
733        return Scope.MODULE;
734     }
735     return Scope.PROJECT;
736   }
737
738   private void updateControls() {
739     if (myCbRegularExpressions.isSelected()) {
740       myCbWholeWordsOnly.makeUnselectable(false);
741     }
742     else {
743       myCbWholeWordsOnly.makeSelectable();
744     }
745     if (myModel.isReplaceState()) {
746       if (myCbRegularExpressions.isSelected() || myCbCaseSensitive.isSelected()) {
747         myCbPreserveCase.makeUnselectable(false);
748       }
749       else {
750         myCbPreserveCase.makeSelectable();
751       }
752
753       if (myCbPreserveCase.isSelected()) {
754         myCbRegularExpressions.makeUnselectable(false);
755         myCbCaseSensitive.makeUnselectable(false);
756       }
757       else {
758         myCbRegularExpressions.makeSelectable();
759         myCbCaseSensitive.makeSelectable();
760       }
761     }
762     myRecursiveDirectoryButton.setSelected(myModel.isWithSubdirectories());
763   }
764
765   private void updateReplaceVisibility() {
766     myReplaceTextArea.setVisible(myModel.isReplaceState());
767     myCbPreserveCase.setVisible(myModel.isReplaceState());
768   }
769
770   public JComponent getPreferredFocusedComponent() {
771     return mySearchComponent;
772   }
773
774   private static void applyFont(JBFont font, Component... components) {
775     for (Component component : components) {
776       component.setFont(font);
777     }
778   }
779
780   private void updateScopeDetailsPanel() {
781     ((CardLayout)myScopeDetailsPanel.getLayout()).show(myScopeDetailsPanel, mySelectedScope.name());
782     myScopeDetailsPanel.revalidate();
783     myScopeDetailsPanel.repaint();
784   }
785
786   private void scheduleResultsUpdate() {
787     if (myFindBalloon == null || !myFindBalloon.isVisible()) return;
788     if (mySearchRescheduleOnCancellationsAlarm == null || mySearchRescheduleOnCancellationsAlarm.isDisposed()) return;
789     mySearchRescheduleOnCancellationsAlarm.cancelAllRequests();
790     mySearchRescheduleOnCancellationsAlarm.addRequest(new Runnable() {
791       @Override
792       public void run() {
793         findSettingsChanged();
794       }
795     }, 100);
796   }
797
798   private void finishPreviousPreviewSearch() {
799     if (myResultsPreviewSearchProgress != null && !myResultsPreviewSearchProgress.isCanceled()) {
800       myResultsPreviewSearchProgress.cancel();
801     }
802   }
803
804
805   private void findSettingsChanged() {
806     if (isShowing()) {
807       showResultsPopupIfNeed();
808     }
809     final ModalityState state = ModalityState.current();
810     finishPreviousPreviewSearch();
811     mySearchRescheduleOnCancellationsAlarm.cancelAllRequests();
812     applyTo(myModel, false);
813     FindManager.getInstance(myProject).getFindInProjectModel().copyFrom(myModel);
814     ((FindManagerImpl)FindManager.getInstance(myProject)).changeGlobalSettings(myModel);
815     FindSettings findSettings = FindSettings.getInstance();
816     findSettings.setDefaultScopeName(myScopeCombo.getSelectedScopeName());
817     findSettings.setFileMask(myModel.getFileFilter());
818
819     ValidationInfo result = getValidationInfo(myModel);
820
821     final ProgressIndicatorBase progressIndicatorWhenSearchStarted = new ProgressIndicatorBase();
822     myResultsPreviewSearchProgress = progressIndicatorWhenSearchStarted;
823
824     final DefaultTableModel model = new DefaultTableModel() {
825       @Override
826       public boolean isCellEditable(int row, int column) {
827         return false;
828       }
829     };
830
831     model.addColumn("Usages");
832
833     myCodePreviewComponent.setVisible(false);
834
835     myResultsPreviewTable.setModel(model);
836
837     if (result != null) {
838       myResultsPreviewTable.getEmptyText().setText(UIBundle.message("message.nothingToShow"));
839       return;
840     }
841
842     myResultsPreviewTable.getColumnModel().getColumn(0).setCellRenderer(new FindDialog.UsageTableCellRenderer(myCbFileFilter.isSelected(), false));
843     myResultsPreviewTable.getEmptyText().setText("Searching...");
844
845     final AtomicInteger resultsCount = new AtomicInteger();
846
847     ProgressIndicatorUtils.scheduleWithWriteActionPriority(myResultsPreviewSearchProgress, new ReadTask() {
848       @Override
849       public void computeInReadAction(@NotNull ProgressIndicator indicator) {
850         final UsageViewPresentation presentation =
851           FindInProjectUtil.setupViewPresentation(findSettings.isShowResultsInSeparateView(), /*findModel*/myModel.clone());
852         final boolean showPanelIfOnlyOneUsage = !findSettings.isSkipResultsWithOneUsage();
853
854         final FindUsagesProcessPresentation processPresentation =
855           FindInProjectUtil.setupProcessPresentation(myProject, showPanelIfOnlyOneUsage, presentation);
856         FindInProjectUtil.findUsages(myModel.clone(), myProject, new Processor<UsageInfo>() {
857           @Override
858           public boolean process(final UsageInfo info) {
859             final Usage usage = UsageInfo2UsageAdapter.CONVERTER.fun(info);
860             usage.getPresentation().getIcon(); // cache icon
861             ApplicationManager.getApplication().invokeLater(new Runnable() {
862               @Override
863               public void run() {
864                 model.addRow(new Object[]{usage});
865                 myCodePreviewComponent.setVisible(true);
866                 if (model.getRowCount() == 1 && myResultsPreviewTable.getModel() == model) {
867                   myResultsPreviewTable.setRowSelectionInterval(0, 0);
868                 }
869                 scheduleUpdateResultsPopupBounds();
870               }
871             }, state);
872             return resultsCount.incrementAndGet() < ShowUsagesAction.USAGES_PAGE_SIZE;
873           }
874         }, processPresentation);
875         boolean succeeded = !progressIndicatorWhenSearchStarted.isCanceled();
876         if (succeeded) {
877           ApplicationManager.getApplication().invokeLater(new Runnable() {
878             @Override
879             public void run() {
880               if (progressIndicatorWhenSearchStarted == myResultsPreviewSearchProgress && !myResultsPreviewSearchProgress.isCanceled()) {
881                 int occurrences = resultsCount.get();
882                 if (occurrences == 0) myResultsPreviewTable.getEmptyText().setText(UIBundle.message("message.nothingToShow"));
883                 myCodePreviewComponent.setVisible(occurrences > 0);
884                 StringBuilder info = new StringBuilder();
885                 if (occurrences > 0) {
886                   info.append(Math.min(ShowUsagesAction.USAGES_PAGE_SIZE, occurrences));
887                   if (occurrences >= ShowUsagesAction.USAGES_PAGE_SIZE) {
888                     info.append("+");
889                   }
890                   info.append(UIBundle.message("message.matches", occurrences));
891                 }
892                 mySearchTextArea.setInfoText(info.toString());
893                 scheduleUpdateResultsPopupBounds();
894               }
895             }
896           }, state);
897         }
898       }
899
900       @Override
901       public void onCanceled(@NotNull ProgressIndicator indicator) {
902         if (isShowing() && progressIndicatorWhenSearchStarted == myResultsPreviewSearchProgress) {
903           scheduleResultsUpdate();
904         }
905       }
906     });
907   }
908
909   private void showResultsPopupIfNeed() {
910     if ((myResultsPopup == null || !myResultsPopup.isVisible()) && isShowing()) {
911       UIUtil.invokeLaterIfNeeded(new Runnable() {
912         @Override
913         public void run() {
914           JPanel popupContent = new JPanel(new BorderLayout());
915           popupContent.setName("PopupContent!!!");
916           Splitter splitter = new JBSplitter(true, .33F);
917           splitter.setDividerWidth(1);
918           splitter.getDivider().setBackground(OnePixelDivider.BACKGROUND);
919           JBScrollPane scrollPane = new JBScrollPane(myResultsPreviewTable) {
920             @Override
921             public Dimension getMinimumSize() {
922               Dimension size = super.getMinimumSize();
923               size.height = Math.max(size.height, myResultsPreviewTable.getPreferredScrollableViewportSize().height);
924               return size;
925             }
926           };
927           scrollPane.setBorder(IdeBorderFactory.createEmptyBorder());
928           splitter.setFirstComponent(scrollPane);
929           popupContent.add(splitter, BorderLayout.CENTER);
930           JPanel bottomPanel = new JPanel(new MigLayout("flowx, ins 4, fillx, hidemode 3, gap 0"));
931           bottomPanel.add(myTabResultsButton);
932           bottomPanel.add(Box.createHorizontalGlue(), "growx, pushx");
933           JBLabel label = new JBLabel(KeymapUtil.getShortcutsText(new Shortcut[]{new KeyboardShortcut(OK_KEYSTROKE, null)}));
934           label.setEnabled(false);
935           bottomPanel.add(label, "gapright 10");
936           bottomPanel.add(myOKButton);
937           popupContent.add(bottomPanel, BorderLayout.SOUTH);
938
939           popupContent.registerKeyboardAction(myOkActionListener, OK_KEYSTROKE, WHEN_IN_FOCUSED_WINDOW);
940
941           myCodePreviewComponent = myUsagePreviewPanel.createComponent();
942           myCodePreviewComponent.setBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM));
943           splitter.setSecondComponent(myCodePreviewComponent);
944
945           AtomicBoolean canClose = new AtomicBoolean();
946           final ComponentPopupBuilder builder = JBPopupFactory.getInstance()
947             .createComponentPopupBuilder(popupContent, null);
948           myResultsPopup = (AbstractPopup)builder
949             .setShowShadow(false)
950             .setShowBorder(false)
951             .setCancelCallback(new Computable<Boolean>() {
952               @Override
953               public Boolean compute() {
954                 if (canClose.get()) return Boolean.TRUE;
955                 Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
956                 Window balloonWindow = SwingUtilities.windowForComponent(myFindBalloon.getContent());
957                 if (activeWindow == balloonWindow || (activeWindow != null && activeWindow.getParent() == balloonWindow)) {
958                   return Boolean.FALSE;
959                 }
960                 ApplicationManager.getApplication().invokeLater(new Runnable() {
961                   @Override
962                   public void run() {
963                     if (myFindBalloon != null) {
964                       Disposer.dispose(myFindBalloon);
965                       myFindBalloon = null;
966                     }
967                   }
968                 });
969                 return Boolean.TRUE;
970               }
971             })
972             .setKeyEventHandler(new BooleanFunction<KeyEvent>() {
973               @Override
974               public boolean fun(KeyEvent event) {
975                 if (AbstractPopup.isCloseRequest(event)) {
976                   canClose.set(true);
977                   myResultsPopup.cancel(event);
978                   if (myFindBalloon != null && myFindBalloon.isVisible()) {
979                     myFindBalloon.cancel();
980                   }
981                   return true;
982                 }
983                 return false;
984               }
985             })
986             .createPopup();
987           RelativePoint point = new RelativePoint(FindPopupPanel.this, new Point(0, FindPopupPanel.this.getHeight()));
988           myResultsPopup.pack(true, true);
989           myResultsPopup.show(point);
990           Disposer.register(myDisposable, myResultsPopup);
991           registerCloseAction(myResultsPopup);
992           updateResultsPopupBounds();
993           ScrollingUtil.ensureSelectionExists(myResultsPreviewTable);
994         }
995       });
996     }
997   }
998
999   @Nullable
1000   private String getFileTypeMask() {
1001     String mask = null;
1002     if (myCbFileFilter != null && myCbFileFilter.isSelected()) {
1003       mask = myFileMaskField.getText();
1004     }
1005     return mask;
1006   }
1007
1008   @Nullable("null means OK")
1009   private ValidationInfo getValidationInfo(@NotNull FindModel model) {
1010     if (mySelectedScope == Scope.DIRECTORY) {
1011       VirtualFile directory = FindInProjectUtil.getDirectory(model);
1012       if (directory == null) {
1013         return new ValidationInfo(FindBundle.message("find.directory.not.found.error", getDirectory()), myDirectoryComboBox);
1014       }
1015     }
1016
1017     if (!canSearchThisString()) {
1018       return new ValidationInfo("String to find is empty", mySearchComponent);
1019     }
1020
1021     if (myCbRegularExpressions != null && myCbRegularExpressions.isSelected() && myCbRegularExpressions.isEnabled()) {
1022       String toFind = getStringToFind();
1023       try {
1024         boolean isCaseSensitive = myCbCaseSensitive != null && myCbCaseSensitive.isSelected() && myCbCaseSensitive.isEnabled();
1025         Pattern pattern =
1026           Pattern.compile(toFind, isCaseSensitive ? Pattern.MULTILINE : Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
1027         if (pattern.matcher("").matches() && !toFind.endsWith("$") && !toFind.startsWith("^")) {
1028           return new ValidationInfo(FindBundle.message("find.empty.match.regular.expression.error"), mySearchComponent);
1029         }
1030       }
1031       catch (PatternSyntaxException e) {
1032         return new ValidationInfo(FindBundle.message("find.invalid.regular.expression.error", toFind, e.getDescription()),
1033                                   mySearchComponent);
1034       }
1035     }
1036
1037     final String mask = getFileTypeMask();
1038
1039     if (mask != null) {
1040       if (mask.isEmpty()) {
1041         return new ValidationInfo(FindBundle.message("find.filter.empty.file.mask.error"), myFileMaskField);
1042       }
1043
1044       if (mask.contains(";")) {
1045         return new ValidationInfo("File masks should be comma-separated", myFileMaskField);
1046       }
1047
1048       else {
1049         try {
1050           FindInProjectUtil.createFileMaskRegExp(mask);   // verify that the regexp compiles
1051         }
1052         catch (PatternSyntaxException ex) {
1053           return new ValidationInfo(FindBundle.message("find.filter.invalid.file.mask.error", mask), myFileMaskField);
1054         }
1055       }
1056     }
1057     return null;
1058   }
1059
1060   private boolean canSearchThisString() {
1061     return !StringUtil.isEmpty(getStringToFind()) || !myModel.isReplaceState() && !myModel.isFindAllEnabled() && getFileTypeMask() != null;
1062   }
1063
1064   @NotNull
1065   private String getStringToFind() {
1066     return mySearchComponent.getText();
1067   }
1068
1069   @NotNull
1070   private String getStringToReplace() {
1071     return myReplaceComponent.getText();
1072   }
1073
1074   private String getDirectory() {
1075     return (String)myDirectoryComboBox.getSelectedItem();
1076   }
1077
1078
1079   private void applyTo(@NotNull FindModel model, boolean findAll) {
1080     model.setCaseSensitive(myCbCaseSensitive.isSelected());
1081
1082     if (model.isReplaceState()) {
1083       model.setPreserveCase(myCbPreserveCase.isSelected());
1084     }
1085
1086     model.setWholeWordsOnly(myCbWholeWordsOnly.isSelected());
1087
1088     String selectedSearchContextInUi = mySelectedContextName;
1089     FindModel.SearchContext searchContext = FindDialog.parseSearchContext(selectedSearchContextInUi);
1090
1091     model.setSearchContext(searchContext);
1092
1093     model.setRegularExpressions(myCbRegularExpressions.isSelected());
1094     String stringToFind = getStringToFind();
1095     model.setStringToFind(stringToFind);
1096
1097     if (model.isReplaceState()) {
1098       model.setPromptOnReplace(true);
1099       model.setReplaceAll(false);
1100       String stringToReplace = getStringToReplace();
1101       model.setStringToReplace(StringUtil.convertLineSeparators(stringToReplace));
1102     }
1103
1104
1105     model.setProjectScope(mySelectedScope == Scope.PROJECT);
1106     model.setDirectoryName(null);
1107     model.setModuleName(null);
1108     model.setCustomScopeName(null);
1109     model.setCustomScope(null);
1110     model.setCustomScope(false);
1111
1112     if (mySelectedScope == Scope.DIRECTORY) {
1113       String directory = getDirectory();
1114       model.setDirectoryName(directory == null ? "" : directory);
1115       model.setWithSubdirectories(myRecursiveDirectoryButton.isSelected());
1116     }
1117     else if (mySelectedScope == Scope.MODULE) {
1118       model.setModuleName((String)myModuleComboBox.getSelectedItem());
1119     }
1120     else if (mySelectedScope == Scope.SCOPE) {
1121       SearchScope selectedScope = myScopeCombo.getSelectedScope();
1122       String customScopeName = selectedScope == null ? null : selectedScope.getDisplayName();
1123       model.setCustomScopeName(customScopeName);
1124       model.setCustomScope(selectedScope == null ? null : selectedScope);
1125       model.setCustomScope(true);
1126     }
1127
1128     model.setFindAll(findAll);
1129
1130     String mask = getFileTypeMask();
1131     model.setFileFilter(mask);
1132   }
1133
1134   private static void initCombobox(@NotNull final ComboBox comboBox) {
1135     comboBox.setEditable(true);
1136     comboBox.setMaximumRowCount(8);
1137   }
1138
1139
1140   private class MySwitchContextToggleAction extends ToggleAction {
1141     public MySwitchContextToggleAction(FindModel.SearchContext context) {
1142       super(FindDialog.getPresentableName(context));
1143     }
1144
1145     @Override
1146     public void beforeActionPerformedUpdate(@NotNull AnActionEvent e) {
1147       super.beforeActionPerformedUpdate(e);
1148     }
1149
1150     @Override
1151     public boolean isSelected(AnActionEvent e) {
1152       return Comparing.equal(mySelectedContextName, getTemplatePresentation().getText());
1153     }
1154
1155     @Override
1156     public void setSelected(AnActionEvent e, boolean state) {
1157       if (state) {
1158         mySelectedContextName = getTemplatePresentation().getText();
1159         scheduleResultsUpdate();
1160       }
1161     }
1162   }
1163
1164
1165   private class MySelectScopeToggleAction extends ToggleAction implements CustomComponentAction {
1166     private final Scope myScope;
1167
1168     public MySelectScopeToggleAction(Scope scope) {
1169       super(FindBundle.message("find.popup.scope." + scope.name().toLowerCase()), null, EmptyIcon.ICON_0);
1170       getTemplatePresentation().setHoveredIcon(EmptyIcon.ICON_0);
1171       getTemplatePresentation().setDisabledIcon(EmptyIcon.ICON_0);
1172       myScope = scope;
1173     }
1174
1175     @Override
1176     public JComponent createCustomComponent(Presentation presentation) {
1177       return new ActionButtonWithText(this, presentation, ActionPlaces.EDITOR_TOOLBAR, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE);
1178     }
1179
1180     @Override
1181     public boolean displayTextInToolbar() {
1182       return true;
1183     }
1184
1185     @Override
1186     public boolean isSelected(AnActionEvent e) {
1187       return mySelectedScope == myScope;
1188     }
1189
1190     @Override
1191     public void setSelected(AnActionEvent e, boolean state) {
1192       if (state) {
1193         mySelectedScope = myScope;
1194         myScopeSelectionToolbar.updateActionsImmediately();
1195         updateScopeDetailsPanel();
1196         scheduleResultsUpdate();
1197       }
1198     }
1199   }
1200
1201   private class NavigateToSourceListener extends DoubleClickListener {
1202
1203     @Override
1204     protected boolean onDoubleClick(MouseEvent event) {
1205       Object source = event.getSource();
1206       if (!(source instanceof JBTable)) return false;
1207       navigateToSelectedUsage((JBTable)source);
1208       return true;
1209     }
1210
1211     @Override
1212     public void installOn(@NotNull final Component c) {
1213       super.installOn(c);
1214
1215       if (c instanceof JBTable) {
1216         AnAction anAction = new AnAction() {
1217           @Override
1218           public void actionPerformed(AnActionEvent e) {
1219             navigateToSelectedUsage((JBTable)c);
1220           }
1221         };
1222
1223         String key = "navigate.to.usage";
1224         JComponent component = (JComponent)c;
1225         component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(NEW_LINE, key);
1226         component.getActionMap().put(key, new AbstractAction() {
1227           @Override
1228           public void actionPerformed(ActionEvent e) {
1229             navigateToSelectedUsage((JBTable)c);
1230           }
1231         });
1232         anAction.registerCustomShortcutSet(CommonShortcuts.ALT_ENTER, component);
1233         anAction.registerCustomShortcutSet(CommonShortcuts.getEditSource(), component);
1234       }
1235     }
1236   }
1237
1238   private void navigateToSelectedUsage(JBTable source) {
1239     int[] rows = source.getSelectedRows();
1240     java.util.List<Usage> navigations = null;
1241     for (int row : rows) {
1242       Object valueAt = source.getModel().getValueAt(row, 0);
1243       if (valueAt instanceof Usage) {
1244         if (navigations == null) navigations = new SmartList<Usage>();
1245         Usage at = (Usage)valueAt;
1246         navigations.add(at);
1247       }
1248     }
1249
1250     if (navigations != null) {
1251       applyTo(FindManager.getInstance(myProject).getFindInProjectModel(), false);
1252       myFindBalloon.cancel();
1253
1254       navigations.get(0).navigate(true);
1255       for (int i = 1; i < navigations.size(); ++i) navigations.get(i).highlightInEditor();
1256     }
1257   }
1258 }