select text in replace field (IDEA-152060)
[idea/community.git] / platform / lang-impl / src / com / intellij / find / SearchReplaceComponent.java
1 /*
2  * Copyright 2000-2015 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;
17
18 import com.intellij.find.editorHeaderActions.ContextAwareShortcutProvider;
19 import com.intellij.find.editorHeaderActions.ShowMoreOptions;
20 import com.intellij.find.editorHeaderActions.Utils;
21 import com.intellij.find.editorHeaderActions.VariantsCompletionAction;
22 import com.intellij.icons.AllIcons;
23 import com.intellij.ide.DataManager;
24 import com.intellij.openapi.actionSystem.*;
25 import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.editor.impl.EditorHeaderComponent;
28 import com.intellij.openapi.keymap.KeymapUtil;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.BooleanGetter;
31 import com.intellij.openapi.util.SystemInfo;
32 import com.intellij.openapi.util.text.StringUtil;
33 import com.intellij.openapi.wm.IdeFocusManager;
34 import com.intellij.ui.DocumentAdapter;
35 import com.intellij.ui.LightColors;
36 import com.intellij.ui.OnePixelSplitter;
37 import com.intellij.ui.SearchTextField;
38 import com.intellij.ui.components.panels.NonOpaquePanel;
39 import com.intellij.ui.components.panels.Wrapper;
40 import com.intellij.ui.speedSearch.SpeedSearchSupply;
41 import com.intellij.util.EventDispatcher;
42 import com.intellij.util.containers.ContainerUtil;
43 import com.intellij.util.ui.JBUI;
44 import com.intellij.util.ui.UIUtil;
45 import org.jetbrains.annotations.NonNls;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48
49 import javax.swing.*;
50 import javax.swing.event.DocumentEvent;
51 import javax.swing.text.JTextComponent;
52 import java.awt.*;
53 import java.awt.event.*;
54 import java.util.EventListener;
55 import java.util.List;
56
57 public class SearchReplaceComponent extends EditorHeaderComponent implements DataProvider {
58   static final KeyStroke NEW_LINE_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
59   private final EventDispatcher<Listener> myEventDispatcher = EventDispatcher.create(Listener.class);
60
61   private final MyTextComponentWrapper mySearchFieldWrapper;
62   private JTextComponent mySearchTextComponent;
63
64   private final MyTextComponentWrapper myReplaceFieldWrapper;
65   private JTextComponent myReplaceTextComponent;
66
67   private final JPanel myLeftPanel;
68   private final JPanel myRightPanel;
69
70   private final DefaultActionGroup mySearchFieldActions;
71   private final ActionToolbarImpl mySearchActionsToolbar1;
72   private final ActionToolbarImpl mySearchActionsToolbar2;
73   private final ActionToolbarImpl.PopupStateModifier mySearchToolbar1PopupStateModifier;
74
75   private final DefaultActionGroup myReplaceFieldActions;
76   private final ActionToolbarImpl myReplaceActionsToolbar1;
77   private final ActionToolbarImpl myReplaceActionsToolbar2;
78   private final JPanel myReplaceToolbarWrapper;
79
80   private final Project myProject;
81   private final JComponent myTargetComponent;
82
83   private final Runnable myCloseAction;
84   private final Runnable myReplaceAction;
85
86   private final DataProvider myDataProviderDelegate;
87
88   private boolean myMultilineMode;
89   private String myStatusText = "";
90
91   @NotNull
92   public static Builder buildFor(@Nullable Project project, @NotNull JComponent component) {
93     return new Builder(project, component);
94   }
95
96   private SearchReplaceComponent(@Nullable Project project,
97                                  @NotNull JComponent targetComponent,
98                                  @NotNull DefaultActionGroup searchToolbar1Actions,
99                                  @NotNull final BooleanGetter searchToolbar1ModifiedFlagGetter,
100                                  @NotNull DefaultActionGroup searchToolbar2Actions,
101                                  @NotNull DefaultActionGroup searchFieldActions,
102                                  @NotNull DefaultActionGroup replaceToolbar1Actions,
103                                  @NotNull DefaultActionGroup replaceToolbar2Actions,
104                                  @NotNull DefaultActionGroup replaceFieldActions,
105                                  @Nullable Runnable replaceAction,
106                                  @Nullable Runnable closeAction,
107                                  @Nullable DataProvider dataProvider) {
108     myProject = project;
109     myTargetComponent = targetComponent;
110     mySearchFieldActions = searchFieldActions;
111     myReplaceFieldActions = replaceFieldActions;
112     myReplaceAction = replaceAction;
113     myCloseAction = closeAction;
114
115     mySearchToolbar1PopupStateModifier = new ActionToolbarImpl.PopupStateModifier() {
116       @Override
117       public int getModifiedPopupState() {
118         return ActionButtonComponent.PUSHED;
119       }
120
121       @Override
122       public boolean willModify() {
123         return searchToolbar1ModifiedFlagGetter.get();
124       }
125     };
126
127     mySearchFieldWrapper = new MyTextComponentWrapper() {
128       @Override
129       public void setContent(JComponent wrapped) {
130         super.setContent(wrapped);
131         mySearchTextComponent = unwrapTextComponent(wrapped);
132       }
133     };
134     myReplaceFieldWrapper = new MyTextComponentWrapper() {
135       @Override
136       public void setContent(JComponent wrapped) {
137         super.setContent(wrapped);
138         myReplaceTextComponent = unwrapTextComponent(wrapped);
139       }
140     };
141
142     myLeftPanel = new NonOpaquePanel(new BorderLayout());
143     myLeftPanel.add(mySearchFieldWrapper, BorderLayout.NORTH);
144     myLeftPanel.add(myReplaceFieldWrapper, BorderLayout.CENTER);
145
146     mySearchActionsToolbar1 = createSearchToolbar1(searchToolbar1Actions);
147     Wrapper searchToolbarWrapper1 = new NonOpaquePanel(new BorderLayout());
148     searchToolbarWrapper1.add(mySearchActionsToolbar1, BorderLayout.WEST);
149     mySearchActionsToolbar2 = createSearchToolbar2(searchToolbar2Actions);
150     Wrapper searchToolbarWrapper2 = new Wrapper(mySearchActionsToolbar2);
151     mySearchActionsToolbar2.setBorder(JBUI.Borders.emptyLeft(16));
152     JPanel searchPair = new NonOpaquePanel(new BorderLayout()).setVerticalSizeReferent(mySearchFieldWrapper);
153     searchPair.add(searchToolbarWrapper1, BorderLayout.WEST);
154     searchPair.add(searchToolbarWrapper2, BorderLayout.CENTER);
155
156     myReplaceActionsToolbar1 = createReplaceToolbar1(replaceToolbar1Actions);
157     Wrapper replaceToolbarWrapper1 = new Wrapper(myReplaceActionsToolbar1).setVerticalSizeReferent(myReplaceFieldWrapper);
158     myReplaceActionsToolbar2 = createReplaceToolbar2(replaceToolbar2Actions);
159     Wrapper replaceToolbarWrapper2 = new Wrapper(myReplaceActionsToolbar2).setVerticalSizeReferent(myReplaceFieldWrapper);
160     myReplaceActionsToolbar2.setBorder(JBUI.Borders.emptyLeft(16));
161     myReplaceToolbarWrapper = new NonOpaquePanel(new BorderLayout());
162     myReplaceToolbarWrapper.add(replaceToolbarWrapper1, BorderLayout.WEST);
163     myReplaceToolbarWrapper.add(replaceToolbarWrapper2, BorderLayout.CENTER);
164
165     searchToolbarWrapper1.setHorizontalSizeReferent(replaceToolbarWrapper1);
166
167     JLabel closeLabel = new JLabel(null, AllIcons.Actions.Cross, SwingConstants.RIGHT);
168     closeLabel.setBorder(JBUI.Borders.empty(5));
169     closeLabel.setVerticalAlignment(SwingConstants.TOP);
170     closeLabel.addMouseListener(new MouseAdapter() {
171       @Override
172       public void mousePressed(final MouseEvent e) {
173         close();
174       }
175     });
176     closeLabel.setToolTipText("Close search bar (Escape)");
177     searchPair.add(new Wrapper.North(closeLabel), BorderLayout.EAST);
178
179     myRightPanel = new NonOpaquePanel(new BorderLayout());
180     myRightPanel.add(searchPair, BorderLayout.NORTH);
181     myRightPanel.add(myReplaceToolbarWrapper, BorderLayout.CENTER);
182
183     OnePixelSplitter splitter = new OnePixelSplitter(false, .25F);
184     myRightPanel.setBorder(JBUI.Borders.emptyLeft(6));
185     splitter.setFirstComponent(myLeftPanel);
186     splitter.setSecondComponent(myRightPanel);
187     splitter.setHonorComponentsMinimumSize(true);
188     splitter.setAndLoadSplitterProportionKey("FindSplitterProportion");
189     splitter.setOpaque(false);
190     splitter.getDivider().setOpaque(false);
191     add(splitter, BorderLayout.CENTER);
192
193     update("", "", false, false);
194
195     // it's assigned after all action updates so that actions don't get access to uninitialized components
196     myDataProviderDelegate = dataProvider;
197
198     setFocusCycleRoot(true);
199
200     setFocusTraversalPolicy(new LayoutFocusTraversalPolicy());
201   }
202
203   public void resetUndoRedoActions() {
204     UIUtil.resetUndoRedoActions(mySearchTextComponent);
205     UIUtil.resetUndoRedoActions(myReplaceTextComponent);
206   }
207
208   @Override
209   public void removeNotify() {
210     super.removeNotify();
211
212     addTextToRecent(mySearchTextComponent);
213     if (myReplaceTextComponent != null) {
214       addTextToRecent(myReplaceTextComponent);
215     }
216   }
217
218   public void requestFocusInTheSearchFieldAndSelectContent(Project project) {
219     doSelectAll(mySearchTextComponent);
220     IdeFocusManager.getInstance(project).requestFocus(mySearchTextComponent, true);
221     if (myReplaceTextComponent != null) {
222       doSelectAll(myReplaceTextComponent);
223     }
224   }
225
226   private static void doSelectAll(JTextComponent searchTextComponent) {
227     searchTextComponent.setSelectionStart(0);
228     searchTextComponent.setSelectionEnd(searchTextComponent.getText().length());
229   }
230
231   public void setStatusText(@NotNull String status) {
232     myStatusText = status;
233   }
234
235   @NotNull
236   public String getStatusText() {
237     return myStatusText;
238   }
239
240   public void replace() {
241     if (myReplaceAction != null) {
242       myReplaceAction.run();
243     }
244   }
245
246   public void close() {
247     if (myCloseAction != null) {
248       myCloseAction.run();
249     }
250   }
251
252   public void setRegularBackground() {
253     mySearchTextComponent.setBackground(UIUtil.getTextFieldBackground());
254   }
255
256   public void setNotFoundBackground() {
257     mySearchTextComponent.setBackground(LightColors.RED);
258   }
259
260   @Override
261   public Insets getInsets() {
262     Insets insets = super.getInsets();
263     if (UIUtil.isUnderGTKLookAndFeel() || UIUtil.isUnderNimbusLookAndFeel()) {
264       insets.top += 1;
265       insets.bottom += 2;
266     }
267     return insets;
268   }
269
270   @Nullable
271   @Override
272   public Object getData(@NonNls String dataId) {
273     if (SpeedSearchSupply.SPEED_SEARCH_CURRENT_QUERY.is(dataId)) {
274       return mySearchTextComponent.getText();
275     }
276     return myDataProviderDelegate != null ? myDataProviderDelegate.getData(dataId) : null;
277   }
278
279   public Project getProject() {
280     return myProject;
281   }
282
283   public void addListener(@NotNull Listener listener) {
284     myEventDispatcher.addListener(listener);
285   }
286
287   public boolean isMultiline() {
288     return myMultilineMode;
289   }
290
291   private void setMultilineInternal(boolean multiline) {
292     boolean stateChanged = multiline != myMultilineMode;
293     myMultilineMode = multiline;
294     if (stateChanged) {
295       multilineStateChanged();
296     }
297   }
298
299   @NotNull
300   public JTextComponent getSearchTextComponent() {
301     return mySearchTextComponent;
302   }
303
304   @NotNull
305   public JTextComponent getReplaceTextComponent() {
306     return myReplaceTextComponent;
307   }
308
309
310   private void updateSearchComponent(@NotNull String textToSet) {
311     final int oldCaretPosition = mySearchTextComponent != null ? mySearchTextComponent.getCaretPosition() : 0;
312     boolean wasNull = mySearchTextComponent == null;
313     if (!updateTextComponent(true)) {
314       if (!mySearchTextComponent.getText().equals(textToSet)) {
315         mySearchTextComponent.setText(textToSet);
316       }
317       return;
318     }
319
320     if (!mySearchTextComponent.getText().equals(textToSet)) {
321       mySearchTextComponent.setText(textToSet);
322       if (wasNull) {
323         mySearchTextComponent.selectAll();
324       }
325     }
326     mySearchTextComponent.getDocument().addDocumentListener(new DocumentAdapter() {
327       @Override
328       protected void textChanged(DocumentEvent e) {
329         ApplicationManager.getApplication().invokeLater(() -> searchFieldDocumentChanged());
330       }
331     });
332
333     mySearchTextComponent.registerKeyboardAction(new ActionListener() {
334                                                    @Override
335                                                    public void actionPerformed(final ActionEvent e) {
336                                                      if (StringUtil.isEmpty(mySearchTextComponent.getText())) {
337                                                        close();
338                                                      }
339                                                      else {
340                                                        IdeFocusManager.getInstance(myProject).requestFocus(myTargetComponent, true);
341                                                        addTextToRecent(mySearchTextComponent);
342                                                      }
343                                                    }
344                                                  }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, SystemInfo.isMac ? InputEvent.META_DOWN_MASK : InputEvent.CTRL_DOWN_MASK),
345                                                  JComponent.WHEN_FOCUSED);
346     if (!wasNull) {
347       ApplicationManager.getApplication().invokeLater(
348         () -> mySearchTextComponent.setCaretPosition(Math.min(oldCaretPosition, mySearchTextComponent.getText().length())));
349     }
350
351     new VariantsCompletionAction(mySearchTextComponent); // It registers a shortcut set automatically on construction
352   }
353
354   private void updateReplaceComponent(@NotNull String textToSet) {
355     final int oldCaretPosition = myReplaceTextComponent != null ? myReplaceTextComponent.getCaretPosition() : 0;
356     boolean wasNull = myReplaceTextComponent == null;
357     if (!updateTextComponent(false)) {
358       if (!myReplaceTextComponent.getText().equals(textToSet)) {
359         myReplaceTextComponent.setText(textToSet);
360       }
361       return;
362     }
363     myReplaceTextComponent.setText(textToSet);
364
365     myReplaceTextComponent.getDocument().addDocumentListener(new DocumentAdapter() {
366       @Override
367       protected void textChanged(DocumentEvent e) {
368         ApplicationManager.getApplication().invokeLater(() -> replaceFieldDocumentChanged());
369       }
370     });
371
372     if (!isMultiline()) {
373       installReplaceOnEnterAction(myReplaceTextComponent);
374     }
375
376     //myReplaceTextComponent.setText(myFindModel.getStringToReplace());
377     if (!wasNull) {
378       ApplicationManager.getApplication().invokeLater(
379         () -> myReplaceTextComponent.setCaretPosition(Math.min(oldCaretPosition, myReplaceTextComponent.getText().length())));
380     }
381
382     new VariantsCompletionAction(myReplaceTextComponent);
383     myReplaceFieldWrapper.revalidate();
384     myReplaceFieldWrapper.repaint();
385   }
386
387   public void update(@NotNull String findText, @NotNull String replaceText, boolean replaceMode, boolean multiline) {
388     setMultilineInternal(multiline);
389     boolean needToResetSearchFocus = mySearchTextComponent != null && mySearchTextComponent.hasFocus();
390     boolean needToResetReplaceFocus = myReplaceTextComponent != null && myReplaceTextComponent.hasFocus();
391     updateSearchComponent(findText);
392     updateReplaceComponent(replaceText);
393     if (replaceMode) {
394       if (myReplaceFieldWrapper.getParent() == null) {
395         myLeftPanel.add(myReplaceFieldWrapper, BorderLayout.CENTER);
396       }
397       if (myReplaceToolbarWrapper.getParent() == null) {
398         myRightPanel.add(myReplaceToolbarWrapper, BorderLayout.CENTER);
399       }
400       if (needToResetReplaceFocus) {
401         myReplaceTextComponent.requestFocusInWindow();
402       }
403     }
404     else {
405       if (myReplaceFieldWrapper.getParent() != null) {
406         myLeftPanel.remove(myReplaceFieldWrapper);
407       }
408       if (myReplaceToolbarWrapper.getParent() != null) {
409         myRightPanel.remove(myReplaceToolbarWrapper);
410       }
411     }
412     if (needToResetSearchFocus) mySearchTextComponent.requestFocusInWindow();
413     updateBindings();
414     updateActions();
415     revalidate();
416     repaint();
417   }
418
419   public void updateActions() {
420     mySearchActionsToolbar1.updateActionsImmediately();
421     mySearchActionsToolbar2.updateActionsImmediately();
422     myReplaceActionsToolbar1.updateActionsImmediately();
423     myReplaceActionsToolbar2.updateActionsImmediately();
424   }
425
426   public void addTextToRecent(@NotNull JTextComponent textField) {
427     final String text = textField.getText();
428     if (text.length() > 0) {
429       FindInProjectSettings findInProjectSettings = FindInProjectSettings.getInstance(myProject);
430       if (textField == mySearchTextComponent) {
431         findInProjectSettings.addStringToFind(text);
432         if (mySearchFieldWrapper.getTargetComponent() instanceof SearchTextField) {
433           ((SearchTextField)mySearchFieldWrapper.getTargetComponent()).addCurrentTextToHistory();
434         }
435       }
436       else {
437         findInProjectSettings.addStringToReplace(text);
438         if (myReplaceFieldWrapper.getTargetComponent() instanceof SearchTextField) {
439           ((SearchTextField)myReplaceFieldWrapper.getTargetComponent()).addCurrentTextToHistory();
440         }
441       }
442     }
443   }
444
445   private boolean updateTextComponent(boolean search) {
446     JTextComponent oldComponent = search ? mySearchTextComponent : myReplaceTextComponent;
447     Color oldBackground = oldComponent != null ? oldComponent.getBackground() : null;
448     final MyTextComponentWrapper wrapper = search ? mySearchFieldWrapper : myReplaceFieldWrapper;
449     if (oldComponent != null) return false;
450
451     final JTextComponent textComponent;
452       SearchTextArea textArea = new SearchTextArea(search);
453       textComponent = textArea.getTextArea();
454       ((JTextArea)textComponent).setRows(isMultiline() ? 2 : 1);
455       KeymapUtil.reassignAction(textComponent,
456                                 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
457                                 NEW_LINE_KEYSTROKE,
458                                 WHEN_FOCUSED);
459
460     textComponent.registerKeyboardAction(e -> {
461       if (isMultiline(textComponent)) {
462         if (textComponent.isEditable() && textComponent.isEnabled()) {
463           textComponent.replaceSelection("\t");
464         }
465         else {
466           UIManager.getLookAndFeel().provideErrorFeedback(textComponent);
467         }
468       }
469       else {
470         textComponent.transferFocus();
471       }
472     }, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), WHEN_FOCUSED);
473
474     textComponent.registerKeyboardAction(e -> {
475       textComponent.transferFocusBackward();
476     }, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK), WHEN_FOCUSED);
477
478     wrapper.setContent(textArea);
479
480     UIUtil.addUndoRedoActions(textComponent);
481     Utils.setSmallerFont(textComponent);
482
483     textComponent.putClientProperty("AuxEditorComponent", Boolean.TRUE);
484     if (oldBackground != null) {
485       textComponent.setBackground(oldBackground);
486     }
487     textComponent.addFocusListener(new FocusListener() {
488       @Override
489       public void focusGained(final FocusEvent e) {
490         textComponent.repaint();
491       }
492
493       @Override
494       public void focusLost(final FocusEvent e) {
495         textComponent.repaint();
496       }
497     });
498     installCloseOnEscapeAction(textComponent);
499     return true;
500   }
501
502   private void searchFieldDocumentChanged() {
503     if (mySearchTextComponent instanceof JTextArea) {
504       adjustRows((JTextArea)mySearchTextComponent);
505     }
506     myEventDispatcher.getMulticaster().searchFieldDocumentChanged();
507   }
508
509   private void replaceFieldDocumentChanged() {
510     if (myReplaceTextComponent instanceof JTextArea) {
511       adjustRows((JTextArea)myReplaceTextComponent);
512     }
513     myEventDispatcher.getMulticaster().replaceFieldDocumentChanged();
514   }
515
516   private void multilineStateChanged() {
517     myEventDispatcher.getMulticaster().multilineStateChanged();
518   }
519
520   private static void adjustRows(@NotNull JTextArea area) {
521     area.setRows(Math.max(2, Math.min(3, StringUtil.countChars(area.getText(), '\n') + 1)));
522   }
523
524   private static boolean isMultiline(@NotNull JTextComponent component) {
525     return component.getText().contains("\n");
526   }
527
528
529   private void installCloseOnEscapeAction(@NotNull JTextComponent c) {
530     ActionListener action = new ActionListener() {
531       @Override
532       public void actionPerformed(ActionEvent e) {
533         close();
534       }
535     };
536     c.registerKeyboardAction(action, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
537     if (KeymapUtil.isEmacsKeymap()) {
538       c.registerKeyboardAction(action, KeyStroke.getKeyStroke(KeyEvent.VK_G, InputEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
539     }
540   }
541
542   private void installReplaceOnEnterAction(@NotNull JTextComponent c) {
543     ActionListener action = new ActionListener() {
544       @Override
545       public void actionPerformed(ActionEvent e) {
546         replace();
547       }
548     };
549     c.registerKeyboardAction(action, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
550   }
551
552   private void updateBindings() {
553     updateBindings(mySearchFieldActions, mySearchFieldWrapper);
554     updateBindings(mySearchActionsToolbar1, mySearchFieldWrapper);
555     updateBindings(mySearchActionsToolbar2, mySearchFieldWrapper);
556
557     updateBindings(myReplaceFieldActions, myReplaceFieldWrapper);
558     updateBindings(myReplaceActionsToolbar1, myReplaceToolbarWrapper);
559     updateBindings(myReplaceActionsToolbar2, myReplaceToolbarWrapper);
560   }
561
562   private void updateBindings(@NotNull DefaultActionGroup group, @NotNull JComponent shortcutHolder) {
563     updateBindings(ContainerUtil.immutableList(group.getChildActionsOrStubs()), shortcutHolder);
564   }
565
566   private void updateBindings(@NotNull ActionToolbarImpl toolbar, @NotNull JComponent shortcutHolder) {
567     updateBindings(toolbar.getActions(), shortcutHolder);
568   }
569
570   private void updateBindings(@NotNull List<? extends AnAction> actions, @NotNull JComponent shortcutHolder) {
571     DataContext context = DataManager.getInstance().getDataContext(this);
572     for (AnAction action : actions) {
573       ShortcutSet shortcut = null;
574       if (action instanceof ContextAwareShortcutProvider) {
575         shortcut = ((ContextAwareShortcutProvider)action).getShortcut(context);
576       }
577       else if (action instanceof ShortcutProvider) {
578         shortcut = ((ShortcutProvider)action).getShortcut();
579       }
580       if (shortcut != null) {
581         action.registerCustomShortcutSet(shortcut, shortcutHolder);
582       }
583     }
584   }
585
586
587   @NotNull
588   private ActionToolbarImpl createSearchToolbar1(@NotNull DefaultActionGroup group) {
589     ActionToolbarImpl toolbar = createToolbar(group);
590     toolbar.setForceMinimumSize(true);
591     toolbar.setReservePlaceAutoPopupIcon(false);
592     toolbar.setSecondaryButtonPopupStateModifier(mySearchToolbar1PopupStateModifier);
593     toolbar.setSecondaryActionsTooltip("More Options(" + ShowMoreOptions.SHORT_CUT + ")");
594     new ShowMoreOptions(toolbar, mySearchFieldWrapper);
595     return toolbar;
596   }
597
598   @NotNull
599   private ActionToolbarImpl createSearchToolbar2(@NotNull DefaultActionGroup group) {
600     return createToolbar(group);
601   }
602
603   @NotNull
604   private ActionToolbarImpl createReplaceToolbar1(@NotNull DefaultActionGroup group) {
605     ActionToolbarImpl toolbar = createToolbar(group);
606     toolbar.setForceMinimumSize(true);
607     toolbar.setReservePlaceAutoPopupIcon(false);
608     return toolbar;
609   }
610
611   @NotNull
612   private ActionToolbarImpl createReplaceToolbar2(@NotNull DefaultActionGroup group) {
613     return createToolbar(group);
614   }
615
616   @NotNull
617   private ActionToolbarImpl createToolbar(@NotNull ActionGroup group) {
618     return tweakToolbar((ActionToolbarImpl)ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, group, true));
619   }
620
621   @NotNull
622   private ActionToolbarImpl tweakToolbar(@NotNull ActionToolbarImpl toolbar) {
623     toolbar.setTargetComponent(this);
624     toolbar.setLayoutPolicy(ActionToolbar.AUTO_LAYOUT_POLICY);
625     toolbar.setBorder(null);
626     Utils.setSmallerFontForChildren(toolbar);
627     return toolbar;
628   }
629
630
631   public interface Listener extends EventListener {
632     void searchFieldDocumentChanged();
633
634     void replaceFieldDocumentChanged();
635
636     void multilineStateChanged();
637   }
638
639   public static class Builder {
640     private final Project myProject;
641     private final JComponent myTargetComponent;
642
643     private DataProvider myDataProvider;
644
645     private Runnable myReplaceAction;
646     private Runnable myCloseAction;
647
648     private DefaultActionGroup mySearchActions = new DefaultActionGroup("search bar 1", false);
649     private DefaultActionGroup myExtraSearchActions = new DefaultActionGroup("search bar 2", false);
650     private DefaultActionGroup mySearchFieldActions = new DefaultActionGroup("search field actions", false);
651     private BooleanGetter mySearchToolbarModifiedFlagGetter = BooleanGetter.FALSE;
652
653     private DefaultActionGroup myReplaceActions = new DefaultActionGroup("replace bar 1", false);
654     private DefaultActionGroup myExtraReplaceActions = new DefaultActionGroup("replace bar 1", false);
655     private DefaultActionGroup myReplaceFieldActions = new DefaultActionGroup("replace field actions", false);
656
657     private Builder(@Nullable Project project, @NotNull JComponent component) {
658       myProject = project;
659       myTargetComponent = component;
660     }
661
662     @NotNull
663     public Builder withDataProvider(@NotNull DataProvider provider) {
664       myDataProvider = provider;
665       return this;
666     }
667
668     @NotNull
669     public Builder withReplaceAction(@NotNull Runnable action) {
670       myReplaceAction = action;
671       return this;
672     }
673
674     @NotNull
675     public Builder withCloseAction(@NotNull Runnable action) {
676       myCloseAction = action;
677       return this;
678     }
679
680     @NotNull
681     public Builder addSearchFieldActions(@NotNull AnAction... actions) {
682       mySearchFieldActions.addAll(actions);
683       return this;
684     }
685
686     @NotNull
687     public Builder addReplaceFieldActions(@NotNull AnAction... actions) {
688       myReplaceFieldActions.addAll(actions);
689       return this;
690     }
691
692     @NotNull
693     public Builder addPrimarySearchActions(@NotNull AnAction... actions) {
694       mySearchActions.addAll(actions);
695       return this;
696     }
697
698     @NotNull
699     public Builder addSecondarySearchActions(@NotNull AnAction... actions) {
700       for (AnAction action : actions) {
701         mySearchActions.addAction(action).setAsSecondary(true);
702       }
703       return this;
704     }
705
706     @NotNull
707     public Builder withSecondarySearchActionsIsModifiedGetter(@NotNull BooleanGetter getter) {
708       mySearchToolbarModifiedFlagGetter = getter;
709       return this;
710     }
711
712     @NotNull
713     public Builder addExtraSearchActions(@NotNull AnAction... actions) {
714       myExtraSearchActions.addAll(actions);
715       return this;
716     }
717
718     @NotNull
719     public Builder addPrimaryReplaceActions(@NotNull AnAction... actions) {
720       myReplaceActions.addAll(actions);
721       return this;
722     }
723
724     @NotNull
725     public Builder addExtraReplaceAction(@NotNull AnAction... actions) {
726       myExtraReplaceActions.addAll(actions);
727       return this;
728     }
729
730     @NotNull
731     public SearchReplaceComponent build() {
732       return new SearchReplaceComponent(myProject,
733                                         myTargetComponent,
734                                         mySearchActions,
735                                         mySearchToolbarModifiedFlagGetter,
736                                         myExtraSearchActions,
737                                         mySearchFieldActions,
738                                         myReplaceActions,
739                                         myExtraReplaceActions,
740                                         myReplaceFieldActions,
741                                         myReplaceAction,
742                                         myCloseAction,
743                                         myDataProvider);
744     }
745   }
746
747   private static class MyTextComponentWrapper extends Wrapper {
748     @Nullable
749     public JTextComponent getTextComponent() {
750       JComponent wrapped = getTargetComponent();
751       return wrapped != null ? unwrapTextComponent(wrapped) : null;
752     }
753
754     @NotNull
755     protected static JTextComponent unwrapTextComponent(@NotNull JComponent wrapped) {
756       if (wrapped instanceof SearchTextField) {
757         return ((SearchTextField)wrapped).getTextEditor();
758       }
759       if (wrapped instanceof SearchTextArea) {
760         return ((SearchTextArea)wrapped).getTextArea();
761       }
762       throw new AssertionError();
763     }
764   }
765 }