IDEA-144857 add some love to new Find in Path popup
[idea/community.git] / platform / lang-impl / src / com / intellij / find / impl / FindDialog.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
17 package com.intellij.find.impl;
18
19 import com.intellij.CommonBundle;
20 import com.intellij.find.FindBundle;
21 import com.intellij.find.FindManager;
22 import com.intellij.find.FindModel;
23 import com.intellij.find.FindSettings;
24 import com.intellij.find.actions.ShowUsagesAction;
25 import com.intellij.ide.util.scopeChooser.ScopeChooserCombo;
26 import com.intellij.ide.util.scopeChooser.ScopeDescriptor;
27 import com.intellij.lang.Language;
28 import com.intellij.openapi.Disposable;
29 import com.intellij.openapi.actionSystem.*;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.application.ModalityState;
32 import com.intellij.openapi.application.TransactionGuard;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.editor.Document;
35 import com.intellij.openapi.editor.EditorSettings;
36 import com.intellij.openapi.editor.event.DocumentAdapter;
37 import com.intellij.openapi.editor.event.DocumentEvent;
38 import com.intellij.openapi.fileChooser.FileChooser;
39 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
40 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
41 import com.intellij.openapi.fileTypes.FileType;
42 import com.intellij.openapi.fileTypes.LanguageFileType;
43 import com.intellij.openapi.fileTypes.PlainTextFileType;
44 import com.intellij.openapi.help.HelpManager;
45 import com.intellij.openapi.module.Module;
46 import com.intellij.openapi.module.ModuleManager;
47 import com.intellij.openapi.module.ModuleUtilCore;
48 import com.intellij.openapi.progress.ProcessCanceledException;
49 import com.intellij.openapi.progress.ProgressIndicator;
50 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
51 import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
52 import com.intellij.openapi.progress.util.ReadTask;
53 import com.intellij.openapi.project.DumbAwareAction;
54 import com.intellij.openapi.project.Project;
55 import com.intellij.openapi.ui.*;
56 import com.intellij.openapi.util.Condition;
57 import com.intellij.openapi.util.Disposer;
58 import com.intellij.openapi.util.registry.Registry;
59 import com.intellij.openapi.util.text.StringUtil;
60 import com.intellij.openapi.vfs.LocalFileSystem;
61 import com.intellij.openapi.vfs.VirtualFile;
62 import com.intellij.projectImport.ProjectAttachProcessor;
63 import com.intellij.psi.PsiBundle;
64 import com.intellij.psi.PsiDocumentManager;
65 import com.intellij.psi.PsiFile;
66 import com.intellij.psi.PsiFileFactory;
67 import com.intellij.psi.search.SearchScope;
68 import com.intellij.ui.*;
69 import com.intellij.ui.components.JBScrollPane;
70 import com.intellij.ui.table.JBTable;
71 import com.intellij.usageView.UsageInfo;
72 import com.intellij.usages.*;
73 import com.intellij.usages.impl.UsagePreviewPanel;
74 import com.intellij.util.*;
75 import com.intellij.util.containers.Convertor;
76 import com.intellij.util.ui.JBUI;
77 import com.intellij.util.ui.UIUtil;
78 import org.jetbrains.annotations.NotNull;
79 import org.jetbrains.annotations.Nullable;
80 import org.jetbrains.annotations.PropertyKey;
81
82 import javax.swing.*;
83 import javax.swing.event.ListSelectionEvent;
84 import javax.swing.event.ListSelectionListener;
85 import javax.swing.table.DefaultTableModel;
86 import javax.swing.table.TableCellRenderer;
87 import javax.swing.text.JTextComponent;
88 import java.awt.*;
89 import java.awt.event.*;
90 import java.util.*;
91 import java.util.List;
92 import java.util.concurrent.atomic.AtomicInteger;
93 import java.util.regex.Pattern;
94 import java.util.regex.PatternSyntaxException;
95
96 public class FindDialog extends DialogWrapper {
97   private static final Logger LOG = Logger.getInstance("#com.intellij.find.impl.FindDialog");
98
99   private ComboBox myInputComboBox;
100   private ComboBox myReplaceComboBox;
101   private StateRestoringCheckBox myCbCaseSensitive;
102   private StateRestoringCheckBox myCbPreserveCase;
103   private StateRestoringCheckBox myCbWholeWordsOnly;
104   private ComboBox mySearchContext;
105   private StateRestoringCheckBox myCbRegularExpressions;
106   private JRadioButton myRbGlobal;
107   private JRadioButton myRbSelectedText;
108   private JRadioButton myRbForward;
109   private JRadioButton myRbBackward;
110   private JRadioButton myRbFromCursor;
111   private JRadioButton myRbEntireScope;
112   private JRadioButton myRbProject;
113   private JRadioButton myRbDirectory;
114   private JRadioButton myRbModule;
115   private ComboBox myModuleComboBox;
116   private ComboBox myDirectoryComboBox;
117   private StateRestoringCheckBox myCbWithSubdirectories;
118   private JCheckBox myCbToOpenInNewTab;
119   private FindModel myModel;
120   private FindModel myPreviousModel;
121   private Consumer<FindModel> myOkHandler;
122   private FixedSizeButton mySelectDirectoryButton;
123   private StateRestoringCheckBox myUseFileFilter;
124   private ComboBox myFileFilter;
125   private JCheckBox myCbToSkipResultsWhenOneUsage;
126   private final Project myProject;
127   private final Map<EditorTextField, DocumentAdapter> myComboBoxListeners = new HashMap<EditorTextField, DocumentAdapter>();
128
129   private Action myFindAllAction;
130   private JRadioButton myRbCustomScope;
131   private ScopeChooserCombo myScopeCombo;
132   protected JLabel myReplacePrompt;
133   private HideableTitledPanel myScopePanel;
134   private static boolean myPreviousResultsExpandedState;
135   private static boolean myPreviewResultsTabWasSelected;
136   private static final int RESULTS_PREVIEW_TAB_INDEX = 1;
137
138   private Splitter myPreviewSplitter;
139   private JBTable myResultsPreviewTable;
140   private UsagePreviewPanel myUsagePreviewPanel;
141   private TabbedPane myContent;
142   private Alarm mySearchRescheduleOnCancellationsAlarm;
143   private static final String PREVIEW_TITLE = UIBundle.message("tool.window.name.preview");
144   private volatile ProgressIndicatorBase myResultsPreviewSearchProgress;
145
146   public FindDialog(@NotNull Project project, @NotNull FindModel model, @NotNull Consumer<FindModel> myOkHandler){
147     super(project, true);
148     myProject = project;
149     myModel = model;
150
151     this.myOkHandler = myOkHandler;
152
153     updateTitle();
154     setOKButtonText(FindBundle.message("find.button"));
155     init();
156     initByModel();
157     updateReplaceVisibility();
158     validateFindButton();
159
160     if (haveResultsPreview()) {
161       ApplicationManager.getApplication().invokeLater(new Runnable() {
162         @Override
163         public void run() {
164           scheduleResultsUpdate();
165         }
166       }, ModalityState.any());
167     }
168   }
169
170   private void updateTitle() {
171     if (myModel.isReplaceState()){
172       if (myModel.isMultipleFiles()){
173         setTitle(FindBundle.message("find.replace.in.project.dialog.title"));
174       }
175       else{
176         setTitle(FindBundle.message("find.replace.text.dialog.title"));
177       }
178     }
179     else{
180       setButtonsMargin(null);
181       if (myModel.isMultipleFiles()){
182         setTitle(FindBundle.message("find.in.path.dialog.title"));
183       }
184       else{
185         setTitle(FindBundle.message("find.text.dialog.title"));
186       }
187     }
188   }
189
190   @Override
191   public void doCancelAction() { // doCancel disposes fields and then calls dispose
192     rememberResultsPreviewWasOpen();
193     super.doCancelAction();
194   }
195
196   private void rememberResultsPreviewWasOpen() {
197     if (myResultsPreviewTable != null) {
198       int selectedIndex = myContent.getSelectedIndex();
199       if (selectedIndex != -1) myPreviewResultsTabWasSelected = selectedIndex == RESULTS_PREVIEW_TAB_INDEX;
200     }
201   }
202
203   @Override
204   protected void dispose() {
205     finishPreviousPreviewSearch();
206     if (mySearchRescheduleOnCancellationsAlarm != null) Disposer.dispose(mySearchRescheduleOnCancellationsAlarm);
207     if (myUsagePreviewPanel != null) Disposer.dispose(myUsagePreviewPanel);
208     for(Map.Entry<EditorTextField, DocumentAdapter> e: myComboBoxListeners.entrySet()) {
209       e.getKey().removeDocumentListener(e.getValue());
210     }
211     myComboBoxListeners.clear();
212     if (myScopePanel != null) myPreviousResultsExpandedState = myScopePanel.isExpanded();
213     super.dispose();
214   }
215
216   @Override
217   public JComponent getPreferredFocusedComponent() {
218     return myInputComboBox;
219   }
220
221   @Override
222   protected String getDimensionServiceKey() {
223     return myModel.isReplaceState() ? "replaceTextDialog" : "findTextDialog";
224   }
225
226   @NotNull
227   @Override
228   protected Action[] createActions() {
229     if (!myModel.isMultipleFiles() && !myModel.isReplaceState() && myModel.isFindAllEnabled()) {
230       return new Action[] { getFindAllAction(), getOKAction(), getCancelAction(), getHelpAction() };
231     }
232     return new Action[] { getOKAction(), getCancelAction(), getHelpAction() };
233   }
234
235   @NotNull
236   private Action getFindAllAction() {
237     return myFindAllAction = new AbstractAction(FindBundle.message("find.all.button")) {
238       @Override
239       public void actionPerformed(ActionEvent e) {
240         doOKAction(true);
241       }
242     };
243   }
244
245   @Override
246   public JComponent createNorthPanel() {
247     JPanel panel = new JPanel(new GridBagLayout());
248
249     JLabel prompt = new JLabel(FindBundle.message("find.text.to.find.label"));
250     panel.add(prompt, new GridBagConstraints(0,0,1,1,0,1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,0,UIUtil.DEFAULT_VGAP,UIUtil.DEFAULT_HGAP), 0,0));
251
252     myInputComboBox = new ComboBox(300);
253     revealWhitespaces(myInputComboBox);
254     initCombobox(myInputComboBox);
255
256     myReplaceComboBox = new ComboBox(300);
257     revealWhitespaces(myReplaceComboBox);
258
259     initCombobox(myReplaceComboBox);
260     final Component editorComponent = myReplaceComboBox.getEditor().getEditorComponent();
261     editorComponent.addFocusListener(
262       new FocusAdapter() {
263         @Override
264         public void focusGained(FocusEvent e) {
265           myReplaceComboBox.getEditor().selectAll();
266           editorComponent.removeFocusListener(this);
267         }
268       }
269     );
270
271
272     panel.add(myInputComboBox, new GridBagConstraints(1,0,1,1,1,1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0,0,UIUtil.DEFAULT_VGAP,0), 0,0));
273     prompt.setLabelFor(myInputComboBox.getEditor().getEditorComponent());
274
275     myReplacePrompt = new JLabel(FindBundle.message("find.replace.with.label"));
276     panel.add(myReplacePrompt, new GridBagConstraints(0,1,1,1,0,1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,0,UIUtil.DEFAULT_VGAP,UIUtil.DEFAULT_HGAP), 0,0));
277
278     panel.add(myReplaceComboBox, new GridBagConstraints(1, 1, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
279                                                         new Insets(0, 0, UIUtil.DEFAULT_VGAP, 0), 0, 0));
280     myReplacePrompt.setLabelFor(myReplaceComboBox.getEditor().getEditorComponent());
281     return panel;
282   }
283
284   private void updateReplaceVisibility() {
285     myReplacePrompt.setVisible(myModel.isReplaceState());
286     myReplaceComboBox.setVisible(myModel.isReplaceState());
287     if (myCbToSkipResultsWhenOneUsage != null) {
288       myCbToSkipResultsWhenOneUsage.setVisible(!myModel.isReplaceState());
289     }
290     myCbPreserveCase.setVisible(myModel.isReplaceState());
291   }
292
293   private void revealWhitespaces(@NotNull ComboBox comboBox) {
294     ComboBoxEditor comboBoxEditor = new RevealingSpaceComboboxEditor(myProject, comboBox);
295     comboBox.setEditor(comboBoxEditor);
296     comboBox.setRenderer(new EditorComboBoxRenderer(comboBoxEditor));
297   }
298
299   private void initCombobox(@NotNull final ComboBox comboBox) {
300     comboBox.setEditable(true);
301     comboBox.setMaximumRowCount(8);
302
303     comboBox.addActionListener(new ActionListener() {
304       @Override
305       public void actionPerformed(ActionEvent e) {
306         validateFindButton();
307       }
308     });
309
310     final Component editorComponent = comboBox.getEditor().getEditorComponent();
311
312     if (editorComponent instanceof EditorTextField) {
313       final EditorTextField etf = (EditorTextField) editorComponent;
314
315       DocumentAdapter listener = new DocumentAdapter() {
316         @Override
317         public void documentChanged(final DocumentEvent e) {
318           handleComboBoxValueChanged(comboBox);
319         }
320       };
321       etf.addDocumentListener(listener);
322       myComboBoxListeners.put(etf, listener);
323     }
324     else {
325       if (editorComponent instanceof JTextComponent) {
326         final javax.swing.text.Document document = ((JTextComponent)editorComponent).getDocument();
327         final com.intellij.ui.DocumentAdapter documentAdapter = new com.intellij.ui.DocumentAdapter() {
328           @Override
329           protected void textChanged(javax.swing.event.DocumentEvent e) {
330             handleAnyComboBoxValueChanged(comboBox);
331           }
332         };
333         document.addDocumentListener(documentAdapter);
334         Disposer.register(myDisposable, new Disposable() {
335           @Override
336           public void dispose() {
337             document.removeDocumentListener(documentAdapter);
338           }
339         });
340       } else {
341         assert false;
342       }
343     }
344
345     if (!myModel.isReplaceState()) {
346       makeResultsPreviewActionOverride(
347         comboBox,
348         KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
349         "choosePrevious",
350         new Runnable() {
351           @Override
352           public void run() {
353             int row = myResultsPreviewTable.getSelectedRow();
354             if (row > 0) myResultsPreviewTable.setRowSelectionInterval(row - 1, row - 1);
355             TableUtil.scrollSelectionToVisible(myResultsPreviewTable);
356           }
357         }
358       );
359
360       makeResultsPreviewActionOverride(
361         comboBox,
362         KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
363         "chooseNext",
364         new Runnable() {
365           @Override
366           public void run() {
367             int row = myResultsPreviewTable.getSelectedRow();
368             if (row >= -1 && row + 1 < myResultsPreviewTable.getRowCount()) {
369               myResultsPreviewTable.setRowSelectionInterval(row + 1, row + 1);
370               TableUtil.scrollSelectionToVisible(myResultsPreviewTable);
371             }
372           }
373         }
374       );
375
376       new AnAction() {
377         @Override
378         public void actionPerformed(AnActionEvent e) {
379           if (isResultsPreviewTabActive()) {
380             navigateToSelectedUsage(myResultsPreviewTable);
381           }
382         }
383       }.registerCustomShortcutSet(CommonShortcuts.getEditSource(), comboBox, myDisposable);
384     }
385   }
386
387   private boolean isResultsPreviewTabActive() {
388     return myResultsPreviewTable != null &&
389         myContent.getSelectedIndex() == RESULTS_PREVIEW_TAB_INDEX;
390   }
391
392   private void makeResultsPreviewActionOverride(final JComboBox component, KeyStroke keyStroke, String newActionKey, final Runnable newAction) {
393     InputMap inputMap = component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
394     Object action = inputMap.get(keyStroke);
395     inputMap.put(keyStroke, newActionKey);
396     final Action previousAction = action != null ? component.getActionMap().get(action) : null;
397     component.getActionMap().put(newActionKey, new AbstractAction() {
398       @Override
399       public void actionPerformed(ActionEvent e) {
400         if(isResultsPreviewTabActive()) {
401           newAction.run();
402           return;
403         }
404
405         if (previousAction != null) {
406           previousAction.actionPerformed(e);
407         }
408       }
409     });
410   }
411
412   private void handleComboBoxValueChanged(@NotNull ComboBox comboBox) {
413     Object item = comboBox.getEditor().getItem();
414     if (item != null && !item.equals(comboBox.getSelectedItem())){
415       comboBox.setSelectedItem(item);
416     }
417
418     handleAnyComboBoxValueChanged(comboBox);
419   }
420
421   private void handleAnyComboBoxValueChanged(@NotNull ComboBox comboBox) {
422     if (comboBox != myReplaceComboBox) scheduleResultsUpdate();
423     validateFindButton();
424   }
425
426   private void findSettingsChanged() {
427     if (haveResultsPreview()) {
428       final ModalityState state = ModalityState.current();
429       if (state == ModalityState.NON_MODAL) return; // skip initial changes
430
431       finishPreviousPreviewSearch();
432       mySearchRescheduleOnCancellationsAlarm.cancelAllRequests();
433       final FindModel findModel = myModel.clone();
434       applyTo(findModel, false);
435
436       ValidationInfo result = getValidationInfo(findModel);
437
438       final ProgressIndicatorBase progressIndicatorWhenSearchStarted = new ProgressIndicatorBase();
439       myResultsPreviewSearchProgress = progressIndicatorWhenSearchStarted;
440
441       final DefaultTableModel model = new DefaultTableModel() {
442         @Override
443         public boolean isCellEditable(int row, int column) {
444           return false;
445         }
446       };
447
448       // Use previously shown usage files as hint for faster search and better usage preview performance if pattern length increased
449       final LinkedHashSet<VirtualFile> filesToScanInitially = new LinkedHashSet<VirtualFile>();
450
451       if (myPreviousModel != null && myPreviousModel.getStringToFind().length() < findModel.getStringToFind().length()) {
452         final DefaultTableModel previousModel = (DefaultTableModel)myResultsPreviewTable.getModel();
453         for (int i = 0, len = previousModel.getRowCount(); i < len; ++i) {
454           final UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)previousModel.getValueAt(i, 0);
455           final VirtualFile file = usage.getFile();
456           if (file != null) filesToScanInitially.add(file);
457         }
458       }
459
460       myPreviousModel = findModel;
461
462       model.addColumn("Usages");
463
464       myResultsPreviewTable.setModel(model);
465
466       if (result != null) {
467         myResultsPreviewTable.getEmptyText().setText(UIBundle.message("message.nothingToShow"));
468         myContent.setTitleAt(RESULTS_PREVIEW_TAB_INDEX, PREVIEW_TITLE);
469         return;
470       }
471
472       myResultsPreviewTable.getColumnModel().getColumn(0).setCellRenderer(new UsageTableCellRenderer(false, true));
473
474       myResultsPreviewTable.getEmptyText().setText("Searching...");
475       myContent.setTitleAt(RESULTS_PREVIEW_TAB_INDEX, PREVIEW_TITLE);
476
477       final Component component = myInputComboBox.getEditor().getEditorComponent();
478
479       // avoid commit of search text document upon encountering / highlighting of first usage that will restart the search
480       // (UsagePreviewPanel.highlight)
481       if (component instanceof EditorTextField) {
482         final Document document = ((EditorTextField)component).getDocument();
483         if (document != null) {
484           TransactionGuard.submitTransaction(() -> PsiDocumentManager.getInstance(myProject).commitDocument(document));
485         }
486       }
487
488       final AtomicInteger resultsCount = new AtomicInteger();
489
490       ProgressIndicatorUtils.scheduleWithWriteActionPriority(myResultsPreviewSearchProgress, new ReadTask() {
491
492         @Nullable
493         @Override
494         public Continuation performInReadAction(@NotNull ProgressIndicator indicator) throws ProcessCanceledException {
495           final UsageViewPresentation presentation =
496             FindInProjectUtil.setupViewPresentation(FindSettings.getInstance().isShowResultsInSeparateView(), findModel);
497           final boolean showPanelIfOnlyOneUsage = !FindSettings.getInstance().isSkipResultsWithOneUsage();
498
499           final FindUsagesProcessPresentation processPresentation =
500             FindInProjectUtil.setupProcessPresentation(myProject, showPanelIfOnlyOneUsage, presentation);
501
502           FindInProjectUtil.findUsages(findModel, myProject, new Processor<UsageInfo>() {
503             @Override
504             public boolean process(final UsageInfo info) {
505               final Usage usage = UsageInfo2UsageAdapter.CONVERTER.fun(info);
506               usage.getPresentation().getIcon(); // cache icon
507               ApplicationManager.getApplication().invokeLater(new Runnable() {
508                 @Override
509                 public void run() {
510                   model.addRow(new Object[]{usage});
511                   if (model.getRowCount() == 1 && myResultsPreviewTable.getModel() == model && isResultsPreviewTabActive()) {
512                     myResultsPreviewTable.setRowSelectionInterval(0, 0);
513                   }
514                 }
515               }, state);
516               return resultsCount.incrementAndGet() < ShowUsagesAction.USAGES_PAGE_SIZE;
517             }
518           }, processPresentation, filesToScanInitially);
519           boolean succeeded = !progressIndicatorWhenSearchStarted.isCanceled();
520           if (succeeded) {
521             return new Continuation(new Runnable() {
522               @Override
523               public void run() {
524                 if (progressIndicatorWhenSearchStarted == myResultsPreviewSearchProgress && !myResultsPreviewSearchProgress.isCanceled()) {
525                   int occurrences = resultsCount.get();
526                   if (occurrences == 0) myResultsPreviewTable.getEmptyText().setText(UIBundle.message("message.nothingToShow"));
527                   myContent.setTitleAt(RESULTS_PREVIEW_TAB_INDEX, PREVIEW_TITLE + " (" + (occurrences != ShowUsagesAction.USAGES_PAGE_SIZE ? Integer.valueOf(occurrences): occurrences + "+") +")");
528                 }
529               }
530             }, state);
531           }
532           return null;
533         }
534
535         @Override
536         public void onCanceled(@NotNull ProgressIndicator indicator) {
537           if (isShowing() && progressIndicatorWhenSearchStarted == myResultsPreviewSearchProgress) {
538             scheduleResultsUpdate();
539           }
540         }
541       });
542     }
543   }
544
545   private void scheduleResultsUpdate() {
546     final Alarm alarm = mySearchRescheduleOnCancellationsAlarm;
547     if (alarm == null || alarm.isDisposed()) return;
548     alarm.cancelAllRequests();
549     alarm.addRequest(new Runnable() {
550       @Override
551       public void run() {
552         findSettingsChanged();
553       }
554     }, 100);
555   }
556
557   private void finishPreviousPreviewSearch() {
558     if (myResultsPreviewSearchProgress != null && !myResultsPreviewSearchProgress.isCanceled()) {
559       myResultsPreviewSearchProgress.cancel();
560     }
561   }
562
563   @NotNull
564   public FindModel getModel() {
565     return myModel;
566   }
567
568   public void setOkHandler(@NotNull Consumer<FindModel> okHandler) {
569     myOkHandler = okHandler;
570   }
571
572   public void setModel(@NotNull FindModel model) {
573     myModel = model;
574     updateReplaceVisibility();
575     updateTitle();
576   }
577
578   private void validateFindButton() {
579     boolean okStatus = canSearchThisString() ||
580                        myRbDirectory != null && myRbDirectory.isSelected() && StringUtil.isEmpty(getDirectory());
581     setOKStatus(okStatus);
582   }
583
584   private boolean canSearchThisString() {
585     return !StringUtil.isEmpty(getStringToFind()) || !myModel.isReplaceState() && !myModel.isFindAllEnabled() && getFileTypeMask() != null;
586   }
587
588   private void setOKStatus(boolean value) {
589     setOKActionEnabled(value);
590     if (myFindAllAction != null) {
591       myFindAllAction.setEnabled(value);
592     }
593   }
594
595   @Override
596   public JComponent createCenterPanel() {
597     JPanel optionsPanel = new JPanel();
598     optionsPanel.setLayout(new GridBagLayout());
599
600     GridBagConstraints gbConstraints = new GridBagConstraints();
601     gbConstraints.weightx = 1;
602     gbConstraints.weighty = 1;
603     gbConstraints.fill = GridBagConstraints.BOTH;
604     gbConstraints.gridwidth = GridBagConstraints.REMAINDER;
605
606     JPanel topOptionsPanel = new JPanel();
607     topOptionsPanel.setLayout(new GridLayout(1, 2, UIUtil.DEFAULT_HGAP, 0));
608     topOptionsPanel.add(createFindOptionsPanel());
609     optionsPanel.add(topOptionsPanel, gbConstraints);
610     
611     JPanel resultsOptionPanel = null;
612     
613     if (myModel.isMultipleFiles()) {
614       optionsPanel.add(createGlobalScopePanel(), gbConstraints);
615       gbConstraints.weightx = 1;
616       gbConstraints.weighty = 1;
617       gbConstraints.fill = GridBagConstraints.HORIZONTAL;
618
619       gbConstraints.gridwidth = GridBagConstraints.REMAINDER;
620       optionsPanel.add(createFilterPanel(),gbConstraints);
621
622       myCbToSkipResultsWhenOneUsage = createCheckbox(FindSettings.getInstance().isSkipResultsWithOneUsage(), FindBundle.message("find.options.skip.results.tab.with.one.occurrence.checkbox"));
623       resultsOptionPanel = createResultsOptionPanel(optionsPanel, gbConstraints);
624       resultsOptionPanel.add(myCbToSkipResultsWhenOneUsage);
625
626       myCbToSkipResultsWhenOneUsage.setVisible(!myModel.isReplaceState());
627
628       if (haveResultsPreview()) {
629         final JBTable table = new JBTable() {
630           @Override
631           public Dimension getPreferredSize() {
632             return new Dimension(myInputComboBox.getWidth(), super.getPreferredSize().height);
633           }
634         };
635         table.setShowColumns(false);
636         table.setShowGrid(false);
637         table.setIntercellSpacing(JBUI.emptySize());
638         new NavigateToSourceListener().installOn(table);
639
640         Splitter previewSplitter = new Splitter(true, 0.5f, 0.1f, 0.9f);
641         myUsagePreviewPanel = new UsagePreviewPanel(myProject, new UsageViewPresentation(), true) {
642           @Override
643           protected void customizeEditorSettings(EditorSettings settings) {
644             super.customizeEditorSettings(settings);
645             settings.setUseSoftWraps(true);
646           }
647         };
648         myResultsPreviewTable = table;
649         new TableSpeedSearch(table, new Convertor<Object, String>() {
650           @Override
651           public String convert(Object o) {
652             return ((UsageInfo2UsageAdapter)o).getFile().getName();
653           }
654         });
655         myResultsPreviewTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
656           @Override
657           public void valueChanged(ListSelectionEvent e) {
658             if (e.getValueIsAdjusting()) return;
659             int index = myResultsPreviewTable.getSelectionModel().getLeadSelectionIndex();
660             if (index != -1) {
661               UsageInfo usageInfo = ((UsageInfo2UsageAdapter)myResultsPreviewTable.getModel().getValueAt(index, 0)).getUsageInfo();
662               myUsagePreviewPanel.updateLayout(Collections.singletonList(usageInfo));
663             }
664             else {
665               myUsagePreviewPanel.updateLayout(null);
666             }
667           }
668         });
669         mySearchRescheduleOnCancellationsAlarm = new Alarm();
670         previewSplitter.setFirstComponent(new JBScrollPane(myResultsPreviewTable));
671         previewSplitter.setSecondComponent(myUsagePreviewPanel.createComponent());
672         myPreviewSplitter = previewSplitter;
673       }
674     }
675     else {
676       JPanel leftOptionsPanel = new JPanel();
677       leftOptionsPanel.setLayout(new GridLayout(3, 1, 0, 4));
678
679       leftOptionsPanel.add(createDirectionPanel());
680       leftOptionsPanel.add(createOriginPanel());
681       leftOptionsPanel.add(createScopePanel());
682       topOptionsPanel.add(leftOptionsPanel);
683     }
684
685     if (myModel.isOpenInNewTabVisible()){
686       myCbToOpenInNewTab = new JCheckBox(FindBundle.message("find.open.in.new.tab.checkbox"));
687       myCbToOpenInNewTab.setFocusable(false);
688       myCbToOpenInNewTab.setSelected(myModel.isOpenInNewTab());
689       myCbToOpenInNewTab.setEnabled(myModel.isOpenInNewTabEnabled());
690
691       if (resultsOptionPanel == null) resultsOptionPanel = createResultsOptionPanel(optionsPanel, gbConstraints);
692       resultsOptionPanel.add(myCbToOpenInNewTab);
693     }
694
695     if (myPreviewSplitter != null) {
696       TabbedPane pane = new JBTabsPaneImpl(myProject, SwingConstants.TOP, myDisposable);
697       pane.insertTab("Options", null, optionsPanel, null, 0);
698       pane.insertTab(PREVIEW_TITLE, null, myPreviewSplitter, null, RESULTS_PREVIEW_TAB_INDEX);
699       myContent = pane;
700       final AnAction anAction = new DumbAwareAction() {
701         @Override
702         public void actionPerformed(AnActionEvent e) {
703           int selectedIndex = myContent.getSelectedIndex();
704           myContent.setSelectedIndex(1 - selectedIndex);
705         }
706       };
707
708       final ShortcutSet shortcutSet = ActionManager.getInstance().getAction(IdeActions.ACTION_SWITCHER).getShortcutSet();
709
710       anAction.registerCustomShortcutSet(shortcutSet, getRootPane(), myDisposable);
711
712       if (myPreviewResultsTabWasSelected) myContent.setSelectedIndex(RESULTS_PREVIEW_TAB_INDEX);
713
714       return pane.getComponent();
715     }
716
717     return optionsPanel;
718   }
719
720   private boolean haveResultsPreview() {
721     return Registry.is("ide.find.show.preview") && myModel.isMultipleFiles();
722   }
723
724   private JPanel createResultsOptionPanel(JPanel optionsPanel, GridBagConstraints gbConstraints) {
725     JPanel resultsOptionPanel = new JPanel();
726     resultsOptionPanel.setLayout(new BoxLayout(resultsOptionPanel, BoxLayout.Y_AXIS));
727
728     myScopePanel = new HideableTitledPanel(FindBundle.message("results.options.group"), resultsOptionPanel,
729                                            myPreviousResultsExpandedState);
730     optionsPanel.add(myScopePanel, gbConstraints);
731     return resultsOptionPanel;
732   }
733
734   @NotNull
735   private JComponent createFilterPanel() {
736     JPanel filterPanel = new JPanel();
737     filterPanel.setLayout(new BorderLayout());
738     filterPanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.filter.file.name.group"),
739                                                               true));
740
741     myFileFilter = new ComboBox(100);
742     initCombobox(myFileFilter);
743     filterPanel.add(myUseFileFilter = createCheckbox(FindBundle.message("find.filter.file.mask.checkbox")),BorderLayout.WEST);
744     filterPanel.add(myFileFilter,BorderLayout.CENTER);
745     initFileFilter(myFileFilter, myUseFileFilter);
746     myUseFileFilter.addActionListener(new ActionListener() {
747       @Override
748       public void actionPerformed(ActionEvent e) {
749         scheduleResultsUpdate();
750         validateFindButton();
751       }
752     });
753     return filterPanel;
754   }
755
756   public static void initFileFilter(@NotNull final JComboBox fileFilter, @NotNull final JCheckBox useFileFilter) {
757     fileFilter.setEditable(true);
758     String[] fileMasks = FindSettings.getInstance().getRecentFileMasks();
759     for(int i=fileMasks.length-1; i >= 0; i--) {
760       fileFilter.addItem(fileMasks[i]);
761     }
762     fileFilter.setEnabled(false);
763
764     useFileFilter.addActionListener(
765       new ActionListener() {
766         @Override
767         public void actionPerformed(ActionEvent e) {
768           if (useFileFilter.isSelected()) {
769             fileFilter.setEnabled(true);
770             fileFilter.getEditor().selectAll();
771             fileFilter.getEditor().getEditorComponent().requestFocusInWindow();
772           }
773           else {
774             fileFilter.setEnabled(false);
775           }
776         }
777       }
778     );
779   }
780
781   @Override
782   public void doOKAction() {
783     doOKAction(false);
784   }
785
786   private void doOKAction(boolean findAll) {
787     FindModel validateModel = myModel.clone();
788     applyTo(validateModel, findAll);
789
790     ValidationInfo validationInfo = getValidationInfo(validateModel);
791
792     if (validationInfo == null) {
793       myModel.copyFrom(validateModel);
794       updateFindSettings();
795
796       rememberResultsPreviewWasOpen();
797       super.doOKAction();
798       myOkHandler.consume(myModel);
799     }
800     else {
801       String message = validationInfo.message;
802       Messages.showMessageDialog(
803         myProject,
804         message,
805         CommonBundle.getErrorTitle(),
806         Messages.getErrorIcon()
807       );
808     }
809   }
810
811   private void updateFindSettings() {
812     FindSettings findSettings = FindSettings.getInstance();
813     findSettings.setCaseSensitive(myModel.isCaseSensitive());
814     if (myModel.isReplaceState()) {
815       findSettings.setPreserveCase(myModel.isPreserveCase());
816     }
817
818     findSettings.setWholeWordsOnly(myModel.isWholeWordsOnly());
819     boolean saveContextBetweenRestarts = false;
820     findSettings.setInStringLiteralsOnly(saveContextBetweenRestarts && myModel.isInStringLiteralsOnly());
821     findSettings.setInCommentsOnly(saveContextBetweenRestarts && myModel.isInCommentsOnly());
822     findSettings.setExceptComments(saveContextBetweenRestarts && myModel.isExceptComments());
823     findSettings.setExceptStringLiterals(saveContextBetweenRestarts && myModel.isExceptStringLiterals());
824     findSettings.setExceptCommentsAndLiterals(saveContextBetweenRestarts && myModel.isExceptCommentsAndStringLiterals());
825
826     findSettings.setRegularExpressions(myModel.isRegularExpressions());
827     if (!myModel.isMultipleFiles()){
828       findSettings.setForward(myModel.isForward());
829       findSettings.setFromCursor(myModel.isFromCursor());
830
831       findSettings.setGlobal(myModel.isGlobal());
832     } else{
833       String directoryName = myModel.getDirectoryName();
834       if (directoryName != null && !directoryName.isEmpty()) {
835         findSettings.setWithSubdirectories(myModel.isWithSubdirectories());
836       }
837       else if (myRbModule.isSelected()) {
838       }
839       else if (myRbCustomScope.isSelected()) {
840         SearchScope selectedScope = myScopeCombo.getSelectedScope();
841         String customScopeName = selectedScope == null ? null : selectedScope.getDisplayName();
842         findSettings.setCustomScope(customScopeName);
843       }
844     }
845
846     if (myCbToSkipResultsWhenOneUsage != null){
847       findSettings.setSkipResultsWithOneUsage(
848         isSkipResultsWhenOneUsage()
849       );
850     }
851
852     findSettings.setFileMask(myModel.getFileFilter());
853   }
854
855   @Nullable("null means OK")
856   private ValidationInfo getValidationInfo(@NotNull FindModel model) {
857     if (myRbDirectory != null && myRbDirectory.isEnabled() && myRbDirectory.isSelected()) {
858       VirtualFile directory = FindInProjectUtil.getDirectory(model);
859       if (directory == null) {
860         return new ValidationInfo(FindBundle.message("find.directory.not.found.error", getDirectory()), myDirectoryComboBox);
861       }
862     }
863
864     if (!canSearchThisString()) {
865       return new ValidationInfo("String to find is empty", myInputComboBox);
866     }
867
868     if (myCbRegularExpressions != null && myCbRegularExpressions.isSelected() && myCbRegularExpressions.isEnabled()) {
869       String toFind = getStringToFind();
870       try {
871         boolean isCaseSensitive = myCbCaseSensitive != null && myCbCaseSensitive.isSelected() && myCbCaseSensitive.isEnabled();
872         Pattern pattern =
873           Pattern.compile(toFind, isCaseSensitive ? Pattern.MULTILINE : Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
874         if (pattern.matcher("").matches() && !toFind.endsWith("$") && !toFind.startsWith("^")) {
875           return new ValidationInfo(FindBundle.message("find.empty.match.regular.expression.error"), myInputComboBox);
876         }
877       }
878       catch (PatternSyntaxException e) {
879         return new ValidationInfo(FindBundle.message("find.invalid.regular.expression.error", toFind, e.getDescription()), myInputComboBox);
880       }
881     }
882
883     final String mask = getFileTypeMask();
884
885     if (mask != null) {
886       if (mask.isEmpty()) {
887         return new ValidationInfo(FindBundle.message("find.filter.empty.file.mask.error"), myFileFilter);
888       }
889
890       if (mask.contains(";")) {
891         return new ValidationInfo("File masks should be comma-separated", myFileFilter);
892       }
893
894       else {
895         try {
896           FindInProjectUtil.createFileMaskCondition(mask);   // verify that the regexp compiles
897         }
898         catch (PatternSyntaxException ex) {
899           return new ValidationInfo(FindBundle.message("find.filter.invalid.file.mask.error", mask), myFileFilter);
900         }
901       }
902     }
903     return null;
904   }
905
906   @Override
907   protected ValidationInfo doValidate() {
908     FindModel validateModel = myModel.clone();
909     applyTo(validateModel, false);
910
911     ValidationInfo result = getValidationInfo(validateModel);
912
913     setOKStatus(result == null);
914
915     return result;
916   }
917
918   @Override
919   public void doHelpAction() {
920     String id = myModel.isReplaceState()
921                 ? myModel.isMultipleFiles() ? HelpID.REPLACE_IN_PATH : HelpID.REPLACE_OPTIONS
922                 : myModel.isMultipleFiles() ? HelpID.FIND_IN_PATH : HelpID.FIND_OPTIONS;
923     HelpManager.getInstance().invokeHelp(id);
924   }
925
926   private boolean isSkipResultsWhenOneUsage() {
927     return myCbToSkipResultsWhenOneUsage!=null && myCbToSkipResultsWhenOneUsage.isSelected();
928   }
929
930   @NotNull
931   private JPanel createFindOptionsPanel() {
932     JPanel findOptionsPanel = new JPanel();
933     findOptionsPanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.options.group"), true));
934     findOptionsPanel.setLayout(new BoxLayout(findOptionsPanel, BoxLayout.Y_AXIS));
935
936     myCbCaseSensitive = createCheckbox(FindBundle.message("find.options.case.sensitive"));
937     findOptionsPanel.add(myCbCaseSensitive);
938     ItemListener liveResultsPreviewUpdateListener = new ItemListener() {
939       @Override
940       public void itemStateChanged(ItemEvent e) {
941         scheduleResultsUpdate();
942       }
943     };
944     myCbCaseSensitive.addItemListener(liveResultsPreviewUpdateListener);
945
946     myCbPreserveCase = createCheckbox(FindBundle.message("find.options.replace.preserve.case"));
947     myCbPreserveCase.addItemListener(liveResultsPreviewUpdateListener);
948     findOptionsPanel.add(myCbPreserveCase);
949     myCbPreserveCase.setVisible(myModel.isReplaceState());
950     myCbWholeWordsOnly = createCheckbox(FindBundle.message("find.options.whole.words.only"));
951     myCbWholeWordsOnly.addItemListener(liveResultsPreviewUpdateListener);
952
953     findOptionsPanel.add(myCbWholeWordsOnly);
954
955     myCbRegularExpressions = createCheckbox(FindBundle.message("find.options.regular.expressions"));
956     myCbRegularExpressions.addItemListener(liveResultsPreviewUpdateListener);
957
958     final JPanel regExPanel = new JPanel();
959     regExPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
960     regExPanel.setLayout(new BoxLayout(regExPanel, BoxLayout.X_AXIS));
961     regExPanel.add(myCbRegularExpressions);
962
963     regExPanel.add(RegExHelpPopup.createRegExLink("[Help]", regExPanel, LOG));
964
965     findOptionsPanel.add(regExPanel);
966
967     mySearchContext = new ComboBox(new Object[] { getPresentableName(FindModel.SearchContext.ANY),
968       getPresentableName(FindModel.SearchContext.IN_COMMENTS),
969       getPresentableName(FindModel.SearchContext.IN_STRING_LITERALS),
970       getPresentableName(FindModel.SearchContext.EXCEPT_COMMENTS),
971       getPresentableName(FindModel.SearchContext.EXCEPT_STRING_LITERALS),
972       getPresentableName(FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS)});
973     mySearchContext.addActionListener(new ActionListener() {
974       @Override
975       public void actionPerformed(ActionEvent e) {
976         scheduleResultsUpdate();
977       }
978     });
979     final JPanel searchContextPanel = new JPanel(new BorderLayout());
980     searchContextPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
981
982     JLabel searchContextLabel = new JLabel(FindBundle.message("find.context.combo.label"));
983     searchContextLabel.setLabelFor(mySearchContext);
984     JPanel panel = new JPanel();
985     panel.setAlignmentX(Component.LEFT_ALIGNMENT);
986     panel.add(searchContextLabel);
987     searchContextPanel.add(panel, BorderLayout.WEST);
988
989     panel = new JPanel(new BorderLayout());
990     panel.add(mySearchContext, BorderLayout.NORTH);
991     searchContextPanel.add(panel, BorderLayout.CENTER);
992
993     findOptionsPanel.add(searchContextPanel);
994
995     ActionListener actionListener = new ActionListener() {
996       @Override
997       public void actionPerformed(ActionEvent e) {
998         updateControls();
999       }
1000     };
1001     myCbRegularExpressions.addActionListener(actionListener);
1002     myCbRegularExpressions.addItemListener(new ItemListener() {
1003       @Override
1004       public void itemStateChanged(final ItemEvent e) {
1005         setupRegExpSetting();
1006       }
1007     });
1008
1009     myCbCaseSensitive.addActionListener(actionListener);
1010     myCbPreserveCase.addActionListener(actionListener);
1011
1012     return findOptionsPanel;
1013   }
1014
1015   public static String getPresentableName(@NotNull FindModel.SearchContext searchContext) {
1016     @PropertyKey(resourceBundle = "messages.FindBundle") String messageKey = null;
1017     if (searchContext == FindModel.SearchContext.ANY) {
1018       messageKey = "find.context.anywhere.scope.label";
1019     } else if (searchContext == FindModel.SearchContext.EXCEPT_COMMENTS) {
1020       messageKey = "find.context.except.comments.scope.label";
1021     } else if (searchContext == FindModel.SearchContext.EXCEPT_STRING_LITERALS) {
1022       messageKey = "find.context.except.literals.scope.label";
1023     } else if (searchContext == FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS) {
1024       messageKey = "find.context.except.comments.and.literals.scope.label";
1025     } else if (searchContext == FindModel.SearchContext.IN_COMMENTS) {
1026       messageKey = "find.context.in.comments.scope.label";
1027     } else if (searchContext == FindModel.SearchContext.IN_STRING_LITERALS) {
1028       messageKey = "find.context.in.literals.scope.label";
1029     }
1030     return messageKey != null ? FindBundle.message(messageKey) : searchContext.toString();
1031   }
1032
1033   @NotNull
1034   public static FindModel.SearchContext parseSearchContext(String presentableName) {
1035     FindModel.SearchContext searchContext = FindModel.SearchContext.ANY;
1036     if (FindBundle.message("find.context.in.literals.scope.label").equals(presentableName)) {
1037       searchContext = FindModel.SearchContext.IN_STRING_LITERALS;
1038     }
1039     else if (FindBundle.message("find.context.in.comments.scope.label").equals(presentableName)) {
1040       searchContext = FindModel.SearchContext.IN_COMMENTS;
1041     }
1042     else if (FindBundle.message("find.context.except.comments.scope.label").equals(presentableName)) {
1043       searchContext = FindModel.SearchContext.EXCEPT_COMMENTS;
1044     }
1045     else if (FindBundle.message("find.context.except.literals.scope.label").equals(presentableName)) {
1046       searchContext = FindModel.SearchContext.EXCEPT_STRING_LITERALS;
1047     } else if (FindBundle.message("find.context.except.comments.and.literals.scope.label").equals(presentableName)) {
1048       searchContext = FindModel.SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS;
1049     }
1050     return searchContext;
1051   }
1052
1053   @NotNull
1054   public static String getSearchContextName(FindModel model) {
1055     String searchContext = FindBundle.message("find.context.anywhere.scope.label");
1056     if (model.isInCommentsOnly()) searchContext = FindBundle.message("find.context.in.comments.scope.label");
1057     else if (model.isInStringLiteralsOnly()) searchContext = FindBundle.message("find.context.in.literals.scope.label");
1058     else if (model.isExceptStringLiterals()) searchContext = FindBundle.message("find.context.except.literals.scope.label");
1059     else if (model.isExceptComments()) searchContext = FindBundle.message("find.context.except.comments.scope.label");
1060     else if (model.isExceptCommentsAndStringLiterals()) searchContext = FindBundle.message("find.context.except.comments.and.literals.scope.label");
1061     return searchContext;
1062   }
1063
1064   private void setupRegExpSetting() {
1065     updateFileTypeForEditorComponent(myInputComboBox);
1066     if (myReplaceComboBox != null) updateFileTypeForEditorComponent(myReplaceComboBox);
1067   }
1068
1069   private void updateFileTypeForEditorComponent(@NotNull ComboBox inputComboBox) {
1070     final Component editorComponent = inputComboBox.getEditor().getEditorComponent();
1071
1072     if (editorComponent instanceof EditorTextField) {
1073       boolean isRegexp = myCbRegularExpressions.isSelectedWhenSelectable();
1074       FileType fileType = PlainTextFileType.INSTANCE;
1075       if (isRegexp) {
1076         Language regexpLanguage = Language.findLanguageByID("RegExp");
1077         if (regexpLanguage != null) {
1078           LanguageFileType regexpFileType = regexpLanguage.getAssociatedFileType();
1079           if (regexpFileType != null) {
1080             fileType = regexpFileType;
1081           }
1082         }
1083       }
1084       String fileName = isRegexp ? "a.regexp" : "a.txt";
1085       final PsiFile file = PsiFileFactory.getInstance(myProject).createFileFromText(fileName, fileType, ((EditorTextField)editorComponent).getText(), -1, true);
1086
1087       ((EditorTextField)editorComponent).setNewDocumentAndFileType(fileType, PsiDocumentManager.getInstance(myProject).getDocument(file));
1088     }
1089   }
1090
1091   private void updateControls() {
1092     if (myCbRegularExpressions.isSelected()) {
1093       myCbWholeWordsOnly.makeUnselectable(false);
1094     }
1095     else {
1096       myCbWholeWordsOnly.makeSelectable();
1097     }
1098     if (myModel.isReplaceState()) {
1099       if (myCbRegularExpressions.isSelected() || myCbCaseSensitive.isSelected()) {
1100         myCbPreserveCase.makeUnselectable(false);
1101       }
1102       else {
1103         myCbPreserveCase.makeSelectable();
1104       }
1105
1106       if (myCbPreserveCase.isSelected()) {
1107         myCbRegularExpressions.makeUnselectable(false);
1108         myCbCaseSensitive.makeUnselectable(false);
1109       }
1110       else {
1111         myCbRegularExpressions.makeSelectable();
1112         myCbCaseSensitive.makeSelectable();
1113       }
1114     }
1115
1116     if (!myModel.isMultipleFiles()) {
1117       myRbFromCursor.setEnabled(myRbGlobal.isSelected());
1118       myRbEntireScope.setEnabled(myRbGlobal.isSelected());
1119     }
1120   }
1121
1122   @NotNull
1123   private JPanel createDirectionPanel() {
1124     JPanel directionPanel = new JPanel();
1125     directionPanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.direction.group"), true));
1126     directionPanel.setLayout(new BoxLayout(directionPanel, BoxLayout.Y_AXIS));
1127
1128     myRbForward = new JRadioButton(FindBundle.message("find.direction.forward.radio"), true);
1129     directionPanel.add(myRbForward);
1130     myRbBackward = new JRadioButton(FindBundle.message("find.direction.backward.radio"));
1131     directionPanel.add(myRbBackward);
1132     ButtonGroup bgDirection = new ButtonGroup();
1133     bgDirection.add(myRbForward);
1134     bgDirection.add(myRbBackward);
1135
1136     return directionPanel;
1137   }
1138
1139   @NotNull
1140   private JComponent createGlobalScopePanel() {
1141     JPanel scopePanel = new JPanel();
1142     scopePanel.setLayout(new GridBagLayout());
1143     scopePanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.scope.group"), true));
1144     GridBagConstraints gbConstraints = new GridBagConstraints();
1145     gbConstraints.fill = GridBagConstraints.HORIZONTAL;
1146     gbConstraints.anchor = GridBagConstraints.WEST;
1147
1148     gbConstraints.gridx = 0;
1149     gbConstraints.gridy = 0;
1150     gbConstraints.gridwidth = 3;
1151     gbConstraints.weightx = 1;
1152     final boolean canAttach = ProjectAttachProcessor.canAttachToProject();
1153     myRbProject = new JRadioButton(canAttach
1154                                    ? FindBundle.message("find.scope.all.projects.radio")
1155                                    : FindBundle.message("find.scope.whole.project.radio"), true);
1156     scopePanel.add(myRbProject, gbConstraints);
1157     ItemListener resultsPreviewUpdateListener = new ItemListener() {
1158       @Override
1159       public void itemStateChanged(ItemEvent e) {
1160         scheduleResultsUpdate();
1161       }
1162     };
1163     myRbProject.addItemListener(resultsPreviewUpdateListener);
1164
1165     gbConstraints.gridx = 0;
1166     gbConstraints.gridy++;
1167     gbConstraints.weightx = 0;
1168     gbConstraints.gridwidth = 1;
1169     myRbModule = new JRadioButton(canAttach
1170                                   ? FindBundle.message("find.scope.project.radio")
1171                                   : FindBundle.message("find.scope.module.radio"), false);
1172     scopePanel.add(myRbModule, gbConstraints);
1173     myRbModule.addItemListener(resultsPreviewUpdateListener);
1174
1175     gbConstraints.gridx = 1;
1176     gbConstraints.gridwidth = 2;
1177     gbConstraints.weightx = 1;
1178     Module[] modules = ModuleManager.getInstance(myProject).getModules();
1179     String[] names = new String[modules.length];
1180     for (int i = 0; i < modules.length; i++) {
1181       names[i] = modules[i].getName();
1182     }
1183
1184     Arrays.sort(names,String.CASE_INSENSITIVE_ORDER);
1185     myModuleComboBox = new ComboBox(names);
1186     myModuleComboBox.addActionListener(new ActionListener() {
1187       @Override
1188       public void actionPerformed(ActionEvent e) {
1189         scheduleResultsUpdate();
1190       }
1191     });
1192     scopePanel.add(myModuleComboBox, gbConstraints);
1193
1194     if (modules.length == 1) {
1195       myModuleComboBox.setVisible(false);
1196       myRbModule.setVisible(false);
1197     }
1198
1199     gbConstraints.gridx = 0;
1200     gbConstraints.gridy++;
1201     gbConstraints.weightx = 0;
1202     gbConstraints.gridwidth = 1;
1203     myRbDirectory = new JRadioButton(FindBundle.message("find.scope.directory.radio"), false);
1204     scopePanel.add(myRbDirectory, gbConstraints);
1205     myRbDirectory.addItemListener(resultsPreviewUpdateListener);
1206
1207     gbConstraints.gridx = 1;
1208     gbConstraints.weightx = 1;
1209     myDirectoryComboBox = new ComboBox(200);
1210     Component editorComponent = myDirectoryComboBox.getEditor().getEditorComponent();
1211     if (editorComponent instanceof JTextField) {
1212       JTextField field = (JTextField)editorComponent;
1213       field.setColumns(40);
1214     }
1215     initCombobox(myDirectoryComboBox);
1216     myDirectoryComboBox.setSwingPopup(false);
1217     myDirectoryComboBox.addActionListener(new ActionListener() {
1218       @Override
1219       public void actionPerformed(ActionEvent e) {
1220         scheduleResultsUpdate();
1221       }
1222     });
1223     scopePanel.add(myDirectoryComboBox, gbConstraints);
1224
1225     gbConstraints.weightx = 0;
1226     gbConstraints.gridx = 2;
1227     mySelectDirectoryButton = new FixedSizeButton(myDirectoryComboBox);
1228     TextFieldWithBrowseButton.MyDoClickAction.addTo(mySelectDirectoryButton, myDirectoryComboBox);
1229     mySelectDirectoryButton.setMargin(new Insets(0, 0, 0, 0));
1230     scopePanel.add(mySelectDirectoryButton, gbConstraints);
1231
1232     gbConstraints.gridx = 0;
1233     gbConstraints.gridy++;
1234     gbConstraints.weightx = 1;
1235     gbConstraints.gridwidth = 3;
1236     gbConstraints.insets = new Insets(0, 16, 0, 0);
1237     myCbWithSubdirectories = createCheckbox(true, FindBundle.message("find.scope.directory.recursive.checkbox"));
1238     myCbWithSubdirectories.setSelected(true);
1239     myCbWithSubdirectories.addItemListener(resultsPreviewUpdateListener);
1240     scopePanel.add(myCbWithSubdirectories, gbConstraints);
1241
1242
1243     gbConstraints.gridx = 0;
1244     gbConstraints.gridy++;
1245     gbConstraints.weightx = 0;
1246     gbConstraints.gridwidth = 1;
1247     gbConstraints.insets = new Insets(0, 0, 0, 0);
1248     myRbCustomScope = new JRadioButton(FindBundle.message("find.scope.custom.radio"), false);
1249     scopePanel.add(myRbCustomScope, gbConstraints);
1250
1251     gbConstraints.gridx++;
1252     gbConstraints.weightx = 1;
1253     gbConstraints.gridwidth = 2;
1254     myScopeCombo = new ScopeChooserCombo();
1255     myScopeCombo.init(myProject, true, true, FindSettings.getInstance().getDefaultScopeName(), new Condition<ScopeDescriptor>() {
1256       //final String projectFilesScopeName = PsiBundle.message("psi.search.scope.project");
1257       private final String moduleFilesScopeName;
1258       {
1259         String moduleScopeName = PsiBundle.message("search.scope.module", "");
1260         final int ind = moduleScopeName.indexOf(' ');
1261         moduleFilesScopeName = moduleScopeName.substring(0, ind + 1);
1262       }
1263       @Override
1264       public boolean value(ScopeDescriptor descriptor) {
1265         final String display = descriptor.getDisplay();
1266         return /*!projectFilesScopeName.equals(display) &&*/ !display.startsWith(moduleFilesScopeName);
1267       }
1268     });
1269     myScopeCombo.getComboBox().addActionListener(new ActionListener() {
1270       @Override
1271       public void actionPerformed(ActionEvent e) {
1272         scheduleResultsUpdate();
1273       }
1274     });
1275     myRbCustomScope.addItemListener(resultsPreviewUpdateListener);
1276
1277     Disposer.register(myDisposable, myScopeCombo);
1278     scopePanel.add(myScopeCombo, gbConstraints);
1279
1280
1281     ButtonGroup bgScope = new ButtonGroup();
1282     bgScope.add(myRbProject);
1283     bgScope.add(myRbModule);
1284     bgScope.add(myRbDirectory);
1285     bgScope.add(myRbCustomScope);
1286
1287     myRbProject.addActionListener(new ActionListener() {
1288       @Override
1289       public void actionPerformed(ActionEvent e) {
1290         validateScopeControls();
1291         validateFindButton();
1292       }
1293     });
1294     myRbCustomScope.addActionListener(new ActionListener() {
1295       @Override
1296       public void actionPerformed(ActionEvent e) {
1297         validateScopeControls();
1298         validateFindButton();
1299         myScopeCombo.getComboBox().requestFocusInWindow();
1300       }
1301     });
1302
1303     myRbDirectory.addActionListener(new ActionListener() {
1304       @Override
1305       public void actionPerformed(ActionEvent e) {
1306         validateScopeControls();
1307         validateFindButton();
1308         myDirectoryComboBox.getEditor().getEditorComponent().requestFocusInWindow();
1309       }
1310     });
1311
1312     myRbModule.addActionListener(new ActionListener() {
1313       @Override
1314       public void actionPerformed(ActionEvent e) {
1315         validateScopeControls();
1316         validateFindButton();
1317         myModuleComboBox.requestFocusInWindow();
1318       }
1319     });
1320
1321     mySelectDirectoryButton.addActionListener(new ActionListener() {
1322       @Override
1323       public void actionPerformed(ActionEvent e) {
1324         FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
1325         FileChooser.chooseFiles(descriptor, myProject, null, new Consumer<List<VirtualFile>>() {
1326           @Override
1327           public void consume(final List<VirtualFile> files) {
1328             myDirectoryComboBox.setSelectedItem(files.get(0).getPresentableUrl());
1329           }
1330         });
1331       }
1332     });
1333
1334     return scopePanel;
1335   }
1336
1337   @NotNull
1338   static StateRestoringCheckBox createCheckbox(@NotNull String message) {
1339     final StateRestoringCheckBox cb = new StateRestoringCheckBox(message);
1340     cb.setFocusable(false);
1341     return cb;
1342   }
1343
1344   @NotNull
1345   static StateRestoringCheckBox createCheckbox(boolean selected, @NotNull String message) {
1346     final StateRestoringCheckBox cb = new StateRestoringCheckBox(message, selected);
1347     cb.setFocusable(false);
1348     return cb;
1349   }
1350
1351   private void validateScopeControls() {
1352     if (myRbDirectory.isSelected()) {
1353       myCbWithSubdirectories.makeSelectable();
1354     }
1355     else {
1356       myCbWithSubdirectories.makeUnselectable(myCbWithSubdirectories.isSelected());
1357     }
1358     myDirectoryComboBox.setEnabled(myRbDirectory.isSelected());
1359     mySelectDirectoryButton.setEnabled(myRbDirectory.isSelected());
1360
1361     myModuleComboBox.setEnabled(myRbModule.isSelected());
1362     myScopeCombo.setEnabled(myRbCustomScope.isSelected());
1363   }
1364
1365   @NotNull
1366   private JPanel createScopePanel() {
1367     JPanel scopePanel = new JPanel();
1368     scopePanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.scope.group"), true));
1369     scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
1370
1371     myRbGlobal = new JRadioButton(FindBundle.message("find.scope.global.radio"), true);
1372     scopePanel.add(myRbGlobal);
1373     myRbSelectedText = new JRadioButton(FindBundle.message("find.scope.selected.text.radio"));
1374     scopePanel.add(myRbSelectedText);
1375     ButtonGroup bgScope = new ButtonGroup();
1376     bgScope.add(myRbGlobal);
1377     bgScope.add(myRbSelectedText);
1378
1379     ActionListener actionListener = new ActionListener() {
1380       @Override
1381       public void actionPerformed(ActionEvent e) {
1382         updateControls();
1383       }
1384     };
1385     myRbGlobal.addActionListener(actionListener);
1386     myRbSelectedText.addActionListener(actionListener);
1387
1388     return scopePanel;
1389   }
1390
1391   @NotNull
1392   private JPanel createOriginPanel() {
1393     JPanel originPanel = new JPanel();
1394     originPanel.setBorder(IdeBorderFactory.createTitledBorder(FindBundle.message("find.origin.group"), true));
1395     originPanel.setLayout(new BoxLayout(originPanel, BoxLayout.Y_AXIS));
1396
1397     myRbFromCursor = new JRadioButton(FindBundle.message("find.origin.from.cursor.radio"), true);
1398     originPanel.add(myRbFromCursor);
1399     myRbEntireScope = new JRadioButton(FindBundle.message("find.origin.entire.scope.radio"));
1400     originPanel.add(myRbEntireScope);
1401     ButtonGroup bgOrigin = new ButtonGroup();
1402     bgOrigin.add(myRbFromCursor);
1403     bgOrigin.add(myRbEntireScope);
1404
1405     return originPanel;
1406   }
1407
1408   @NotNull
1409   private String getStringToFind() {
1410     String string = (String)myInputComboBox.getEditor().getItem();
1411     return string == null ? "" : string;
1412   }
1413
1414   @NotNull
1415   private String getStringToReplace() {
1416     String item = (String)myReplaceComboBox.getEditor().getItem();
1417     return item == null ? "" : item;
1418   }
1419
1420   private String getDirectory() {
1421     return (String)myDirectoryComboBox.getEditor().getItem();
1422   }
1423
1424   private static void setStringsToComboBox(@NotNull String[] strings, @NotNull ComboBox combo, String selected) {
1425     if (combo.getItemCount() > 0){
1426       combo.removeAllItems();
1427     }
1428     if (selected != null && selected.indexOf('\n') < 0) {
1429       strings = ArrayUtil.remove(strings, selected);
1430       // this ensures that last searched string will be selected if selected == ""
1431       if (!selected.isEmpty()) strings = ArrayUtil.append(strings, selected);
1432     }
1433     for(int i = strings.length - 1; i >= 0; i--){
1434       combo.addItem(strings[i]);
1435     }
1436   }
1437
1438   private void setDirectories(@NotNull List<String> strings, String directoryName) {
1439     if (myDirectoryComboBox.getItemCount() > 0){
1440       myReplaceComboBox.removeAllItems();
1441     }
1442     if (directoryName != null && !directoryName.isEmpty()){
1443       if (strings.contains(directoryName)){
1444         strings.remove(directoryName);
1445       }
1446       myDirectoryComboBox.addItem(directoryName);
1447     }
1448     for(int i = strings.size() - 1; i >= 0; i--){
1449       myDirectoryComboBox.addItem(strings.get(i));
1450     }
1451     if (myDirectoryComboBox.getItemCount() == 0){
1452       myDirectoryComboBox.addItem("");
1453     }
1454   }
1455
1456   private void applyTo(@NotNull FindModel model, boolean findAll) {
1457     model.setCaseSensitive(myCbCaseSensitive.isSelected());
1458
1459     if (model.isReplaceState()) {
1460       model.setPreserveCase(myCbPreserveCase.isSelected());
1461     }
1462
1463     model.setWholeWordsOnly(myCbWholeWordsOnly.isSelected());
1464
1465     String selectedSearchContextInUi = (String)mySearchContext.getSelectedItem();
1466     FindModel.SearchContext searchContext = parseSearchContext(selectedSearchContextInUi);
1467
1468     model.setSearchContext(searchContext);
1469
1470     model.setRegularExpressions(myCbRegularExpressions.isSelected());
1471     String stringToFind = getStringToFind();
1472     model.setStringToFind(stringToFind);
1473
1474     if (model.isReplaceState()){
1475       model.setPromptOnReplace(true);
1476       model.setReplaceAll(false);
1477       String stringToReplace = getStringToReplace();
1478       model.setStringToReplace(StringUtil.convertLineSeparators(stringToReplace));
1479     }
1480
1481     if (!model.isMultipleFiles()){
1482       model.setForward(myRbForward.isSelected());
1483       model.setFromCursor(myRbFromCursor.isSelected());
1484       model.setGlobal(myRbGlobal.isSelected());
1485     }
1486     else{
1487       if (myCbToOpenInNewTab != null){
1488         model.setOpenInNewTab(myCbToOpenInNewTab.isSelected());
1489       }
1490
1491       model.setProjectScope(myRbProject.isSelected());
1492       model.setDirectoryName(null);
1493       model.setModuleName(null);
1494       model.setCustomScopeName(null);
1495       model.setCustomScope(null);
1496       model.setCustomScope(false);
1497
1498       if (myRbDirectory.isSelected()) {
1499         String directory = getDirectory();
1500         model.setDirectoryName(directory == null ? "" : directory);
1501         model.setWithSubdirectories(myCbWithSubdirectories.isSelected());
1502       }
1503       else if (myRbModule.isSelected()) {
1504         model.setModuleName((String)myModuleComboBox.getSelectedItem());
1505       }
1506       else if (myRbCustomScope.isSelected()) {
1507         SearchScope selectedScope = myScopeCombo.getSelectedScope();
1508         String customScopeName = selectedScope == null ? null : selectedScope.getDisplayName();
1509         model.setCustomScopeName(customScopeName);
1510         model.setCustomScope(selectedScope == null ? null : selectedScope);
1511         model.setCustomScope(true);
1512       }
1513     }
1514
1515     model.setFindAll(findAll);
1516
1517     String mask = getFileTypeMask();
1518     model.setFileFilter(mask);
1519   }
1520
1521   @Nullable
1522   private String getFileTypeMask() {
1523     String mask = null;
1524     if (myUseFileFilter !=null && myUseFileFilter.isSelected()) {
1525       mask = (String)myFileFilter.getEditor().getItem();
1526     }
1527     return mask;
1528   }
1529
1530
1531   private void initByModel() {
1532     myCbCaseSensitive.setSelected(myModel.isCaseSensitive());
1533     myCbWholeWordsOnly.setSelected(myModel.isWholeWordsOnly());
1534     String searchContext = getSearchContextName(myModel);
1535     mySearchContext.setSelectedItem(searchContext);
1536
1537     myCbRegularExpressions.setSelected(myModel.isRegularExpressions());
1538
1539     if (myModel.isMultipleFiles()) {
1540       final String dirName = myModel.getDirectoryName();
1541       setDirectories(FindSettings.getInstance().getRecentDirectories(), dirName);
1542
1543       if (!StringUtil.isEmptyOrSpaces(dirName)) {
1544         VirtualFile dir = LocalFileSystem.getInstance().findFileByPath(dirName);
1545         if (dir != null) {
1546           Module module = ModuleUtilCore.findModuleForFile(dir, myProject);
1547           if (module != null) {
1548             myModuleComboBox.setSelectedItem(module.getName());
1549           }
1550         }
1551       }
1552       if (myModel.isCustomScope()) {
1553         myRbCustomScope.setSelected(true);
1554
1555         myScopeCombo.setEnabled(true);
1556
1557         myCbWithSubdirectories.setEnabled(false);
1558         myDirectoryComboBox.setEnabled(false);
1559         mySelectDirectoryButton.setEnabled(false);
1560         myModuleComboBox.setEnabled(false);
1561       }
1562       else if (myModel.isProjectScope()) {
1563         myRbProject.setSelected(true);
1564
1565         myCbWithSubdirectories.setEnabled(false);
1566         myDirectoryComboBox.setEnabled(false);
1567         mySelectDirectoryButton.setEnabled(false);
1568         myModuleComboBox.setEnabled(false);
1569         myScopeCombo.setEnabled(false);
1570       }
1571       else if (dirName != null) {
1572         myRbDirectory.setSelected(true);
1573         myCbWithSubdirectories.setEnabled(true);
1574         myDirectoryComboBox.setEnabled(true);
1575         mySelectDirectoryButton.setEnabled(true);
1576         myModuleComboBox.setEnabled(false);
1577         myScopeCombo.setEnabled(false);
1578       }
1579       else if (myModel.getModuleName() != null) {
1580         myRbModule.setSelected(true);
1581
1582         myCbWithSubdirectories.setEnabled(false);
1583         myDirectoryComboBox.setEnabled(false);
1584         mySelectDirectoryButton.setEnabled(false);
1585         myModuleComboBox.setEnabled(true);
1586         myModuleComboBox.setSelectedItem(myModel.getModuleName());
1587         myScopeCombo.setEnabled(false);
1588
1589         // force showing even if we have only one module
1590         myRbModule.setVisible(true);
1591         myModuleComboBox.setVisible(true);
1592       }
1593       else {
1594         assert false : myModel;
1595       }
1596
1597       myCbWithSubdirectories.setSelected(myModel.isWithSubdirectories());
1598
1599       if (myModel.getFileFilter()!=null && !myModel.getFileFilter().isEmpty()) {
1600         myFileFilter.setSelectedItem(myModel.getFileFilter());
1601         myFileFilter.setEnabled(true);
1602         myUseFileFilter.setSelected(true);
1603       }
1604     }
1605     else {
1606       if (myModel.isForward()){
1607         myRbForward.setSelected(true);
1608       }
1609       else{
1610         myRbBackward.setSelected(true);
1611       }
1612
1613       if (myModel.isFromCursor()){
1614         myRbFromCursor.setSelected(true);
1615       }
1616       else{
1617         myRbEntireScope.setSelected(true);
1618       }
1619
1620       if (myModel.isGlobal()){
1621         myRbGlobal.setSelected(true);
1622       }
1623       else{
1624         myRbSelectedText.setSelected(true);
1625       }
1626     }
1627
1628     setStringsToComboBox(FindSettings.getInstance().getRecentFindStrings(), myInputComboBox, myModel.getStringToFind());
1629     if (myModel.isReplaceState()){
1630       myCbPreserveCase.setSelected(myModel.isPreserveCase());
1631       setStringsToComboBox(FindSettings.getInstance().getRecentReplaceStrings(), myReplaceComboBox, myModel.getStringToReplace());
1632     }
1633     updateControls();
1634   }
1635
1636   private void navigateToSelectedUsage(JBTable source) {
1637     int[] rows = source.getSelectedRows();
1638     List<Usage> navigations = null;
1639     for(int row:rows) {
1640       Object valueAt = source.getModel().getValueAt(row, 0);
1641       if (valueAt instanceof Usage) {
1642         if (navigations == null) navigations = new SmartList<Usage>();
1643         Usage at = (Usage)valueAt;
1644         navigations.add(at);
1645       }
1646     }
1647
1648     if (navigations != null) {
1649       applyTo(FindManager.getInstance(myProject).getFindInProjectModel(), false);
1650       doCancelAction();
1651       navigations.get(0).navigate(true);
1652       for(int i = 1; i < navigations.size(); ++i) navigations.get(i).highlightInEditor();
1653     }
1654   }
1655
1656   static class UsageTableCellRenderer extends JPanel implements TableCellRenderer {
1657     private final ColoredTableCellRenderer myUsageRenderer = new ColoredTableCellRenderer() {
1658       @Override
1659       protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
1660         if (value instanceof UsageInfo2UsageAdapter) {
1661           TextChunk[] text = ((UsageInfo2UsageAdapter)value).getPresentation().getText();
1662
1663           // skip line number / file info
1664           for (int i = 1; i < text.length; ++i) {
1665             TextChunk textChunk = text[i];
1666             SimpleTextAttributes attributes = getAttributes(textChunk);
1667             myUsageRenderer.append(textChunk.getText(), attributes);
1668           }
1669         }
1670         setBorder(null);
1671       }
1672
1673       @NotNull
1674       private SimpleTextAttributes getAttributes(@NotNull TextChunk textChunk) {
1675         SimpleTextAttributes at = textChunk.getSimpleAttributesIgnoreBackground();
1676         if (myUseBold) return at;
1677         boolean highlighted = textChunk.getType() != null || at.getFontStyle() == Font.BOLD;
1678         return highlighted
1679                ? new SimpleTextAttributes(null, at.getFgColor(), at.getWaveColor(),
1680                                           (at.getStyle() & ~SimpleTextAttributes.STYLE_BOLD) |
1681                                           SimpleTextAttributes.STYLE_SEARCH_MATCH)
1682                : at;
1683       }
1684     };
1685     private final ColoredTableCellRenderer myFileAndLineNumber = new ColoredTableCellRenderer() {
1686       @Override
1687       protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
1688         if (value instanceof UsageInfo2UsageAdapter) {
1689           UsageInfo2UsageAdapter usageAdapter = (UsageInfo2UsageAdapter)value;
1690           TextChunk[] text = usageAdapter.getPresentation().getText();
1691           // line number / file info
1692           VirtualFile file = usageAdapter.getFile();
1693           String uniqueVirtualFilePath = myOmitFileExtension ? file.getNameWithoutExtension() : file.getName();
1694           append(uniqueVirtualFilePath + " " + text[0].getText(), SimpleTextAttributes.GRAYED_ATTRIBUTES);
1695         }
1696         setBorder(null);
1697       }
1698     };
1699     private static final int MARGIN = 2;
1700     private final boolean myOmitFileExtension;
1701     private final boolean myUseBold;
1702
1703     UsageTableCellRenderer(boolean omitFileExtension, boolean useBold) {
1704       myOmitFileExtension = omitFileExtension;
1705       myUseBold = useBold;
1706       setLayout(new BorderLayout());
1707       add(myUsageRenderer, BorderLayout.WEST);
1708       add(myFileAndLineNumber, BorderLayout.EAST);
1709       setBorder(IdeBorderFactory.createEmptyBorder(MARGIN, MARGIN, MARGIN, 0));
1710     }
1711
1712     @Override
1713     public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1714       myUsageRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1715       myFileAndLineNumber.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
1716       setBackground(myUsageRenderer.getBackground());
1717       if (!isSelected && value instanceof UsageInfo2UsageAdapter) {
1718         UsageInfo2UsageAdapter usageAdapter = (UsageInfo2UsageAdapter)value;
1719         Color color = FileColorManager.getInstance(usageAdapter.getUsageInfo().getProject()).getFileColor(usageAdapter.getFile());
1720         setBackground(color);
1721         myUsageRenderer.setBackground(color);
1722         myFileAndLineNumber.setBackground(color);
1723       }
1724       return this;
1725     }
1726   }
1727
1728   private class NavigateToSourceListener extends DoubleClickListener {
1729
1730     @Override
1731     protected boolean onDoubleClick(MouseEvent event) {
1732       Object source = event.getSource();
1733       if (!(source instanceof JBTable)) return false;
1734       navigateToSelectedUsage((JBTable)source);
1735       return true;
1736     }
1737
1738     @Override
1739     public void installOn(@NotNull final Component c) {
1740       super.installOn(c);
1741
1742       if (c instanceof JBTable) {
1743         AnAction anAction = new AnAction() {
1744           @Override
1745           public void actionPerformed(AnActionEvent e) {
1746             navigateToSelectedUsage((JBTable)c);
1747           }
1748         };
1749
1750         String key = "navigate.to.usage";
1751         JComponent component = (JComponent)c;
1752         component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
1753                                                                                        key);
1754         component.getActionMap().put(key, new AbstractAction() {
1755           @Override
1756           public void actionPerformed(ActionEvent e) {
1757             navigateToSelectedUsage((JBTable)c);
1758           }
1759         });
1760         //anAction.registerCustomShortcutSet(CommonShortcuts.ENTER, component);
1761         anAction.registerCustomShortcutSet(CommonShortcuts.getEditSource(), component, myDisposable);
1762       }
1763     }
1764   }
1765 }
1766