a8e4d57b34c74cbddb51f97d77a9db5096f45a8d
[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     mySearchTextComponent.setSelectionStart(0);
220     mySearchTextComponent.setSelectionEnd(mySearchTextComponent.getText().length());
221     IdeFocusManager.getInstance(project).requestFocus(mySearchTextComponent, true);
222   }
223
224   public void setStatusText(@NotNull String status) {
225     myStatusText = status;
226   }
227
228   @NotNull
229   public String getStatusText() {
230     return myStatusText;
231   }
232
233   public void replace() {
234     if (myReplaceAction != null) {
235       myReplaceAction.run();
236     }
237   }
238
239   public void close() {
240     if (myCloseAction != null) {
241       myCloseAction.run();
242     }
243   }
244
245   public void setRegularBackground() {
246     mySearchTextComponent.setBackground(UIUtil.getTextFieldBackground());
247   }
248
249   public void setNotFoundBackground() {
250     mySearchTextComponent.setBackground(LightColors.RED);
251   }
252
253   @Override
254   public Insets getInsets() {
255     Insets insets = super.getInsets();
256     if (UIUtil.isUnderGTKLookAndFeel() || UIUtil.isUnderNimbusLookAndFeel()) {
257       insets.top += 1;
258       insets.bottom += 2;
259     }
260     return insets;
261   }
262
263   @Nullable
264   @Override
265   public Object getData(@NonNls String dataId) {
266     if (SpeedSearchSupply.SPEED_SEARCH_CURRENT_QUERY.is(dataId)) {
267       return mySearchTextComponent.getText();
268     }
269     return myDataProviderDelegate != null ? myDataProviderDelegate.getData(dataId) : null;
270   }
271
272   public Project getProject() {
273     return myProject;
274   }
275
276   public void addListener(@NotNull Listener listener) {
277     myEventDispatcher.addListener(listener);
278   }
279
280   public boolean isMultiline() {
281     return myMultilineMode;
282   }
283
284   private void setMultilineInternal(boolean multiline) {
285     boolean stateChanged = multiline != myMultilineMode;
286     myMultilineMode = multiline;
287     if (stateChanged) {
288       multilineStateChanged();
289     }
290   }
291
292   @NotNull
293   public JTextComponent getSearchTextComponent() {
294     return mySearchTextComponent;
295   }
296
297   @NotNull
298   public JTextComponent getReplaceTextComponent() {
299     return myReplaceTextComponent;
300   }
301
302
303   private void updateSearchComponent(@NotNull String textToSet) {
304     final int oldCaretPosition = mySearchTextComponent != null ? mySearchTextComponent.getCaretPosition() : 0;
305     boolean wasNull = mySearchTextComponent == null;
306     if (!updateTextComponent(true)) {
307       if (!mySearchTextComponent.getText().equals(textToSet)) {
308         mySearchTextComponent.setText(textToSet);
309       }
310       return;
311     }
312
313     if (!mySearchTextComponent.getText().equals(textToSet)) {
314       mySearchTextComponent.setText(textToSet);
315       if (wasNull) {
316         mySearchTextComponent.selectAll();
317       }
318     }
319     mySearchTextComponent.getDocument().addDocumentListener(new DocumentAdapter() {
320       @Override
321       protected void textChanged(DocumentEvent e) {
322         ApplicationManager.getApplication().invokeLater(() -> searchFieldDocumentChanged());
323       }
324     });
325
326     mySearchTextComponent.registerKeyboardAction(new ActionListener() {
327                                                    @Override
328                                                    public void actionPerformed(final ActionEvent e) {
329                                                      if (StringUtil.isEmpty(mySearchTextComponent.getText())) {
330                                                        close();
331                                                      }
332                                                      else {
333                                                        IdeFocusManager.getInstance(myProject).requestFocus(myTargetComponent, true);
334                                                        addTextToRecent(mySearchTextComponent);
335                                                      }
336                                                    }
337                                                  }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, SystemInfo.isMac ? InputEvent.META_DOWN_MASK : InputEvent.CTRL_DOWN_MASK),
338                                                  JComponent.WHEN_FOCUSED);
339     if (!wasNull) {
340       ApplicationManager.getApplication().invokeLater(
341         () -> mySearchTextComponent.setCaretPosition(Math.min(oldCaretPosition, mySearchTextComponent.getText().length())));
342     }
343
344     new VariantsCompletionAction(mySearchTextComponent); // It registers a shortcut set automatically on construction
345   }
346
347   private void updateReplaceComponent(@NotNull String textToSet) {
348     final int oldCaretPosition = myReplaceTextComponent != null ? myReplaceTextComponent.getCaretPosition() : 0;
349     if (!updateTextComponent(false)) {
350       if (!myReplaceTextComponent.getText().equals(textToSet)) {
351         myReplaceTextComponent.setText(textToSet);
352       }
353       return;
354     }
355     myReplaceTextComponent.setText(textToSet);
356
357     myReplaceTextComponent.getDocument().addDocumentListener(new DocumentAdapter() {
358       @Override
359       protected void textChanged(DocumentEvent e) {
360         ApplicationManager.getApplication().invokeLater(() -> replaceFieldDocumentChanged());
361       }
362     });
363
364     if (!isMultiline()) {
365       installReplaceOnEnterAction(myReplaceTextComponent);
366     }
367
368     //myReplaceTextComponent.setText(myFindModel.getStringToReplace());
369     ApplicationManager.getApplication().invokeLater(() -> myReplaceTextComponent.setCaretPosition(oldCaretPosition));
370
371     new VariantsCompletionAction(myReplaceTextComponent);
372     myReplaceFieldWrapper.revalidate();
373     myReplaceFieldWrapper.repaint();
374   }
375
376   public void update(@NotNull String findText, @NotNull String replaceText, boolean replaceMode, boolean multiline) {
377     setMultilineInternal(multiline);
378     boolean needToResetSearchFocus = mySearchTextComponent != null && mySearchTextComponent.hasFocus();
379     boolean needToResetReplaceFocus = myReplaceTextComponent != null && myReplaceTextComponent.hasFocus();
380     updateSearchComponent(findText);
381     updateReplaceComponent(replaceText);
382     if (replaceMode) {
383       if (myReplaceFieldWrapper.getParent() == null) {
384         myLeftPanel.add(myReplaceFieldWrapper, BorderLayout.CENTER);
385       }
386       if (myReplaceToolbarWrapper.getParent() == null) {
387         myRightPanel.add(myReplaceToolbarWrapper, BorderLayout.CENTER);
388       }
389       if (needToResetReplaceFocus) {
390         myReplaceTextComponent.requestFocusInWindow();
391       }
392     }
393     else {
394       if (myReplaceFieldWrapper.getParent() != null) {
395         myLeftPanel.remove(myReplaceFieldWrapper);
396       }
397       if (myReplaceToolbarWrapper.getParent() != null) {
398         myRightPanel.remove(myReplaceToolbarWrapper);
399       }
400     }
401     if (needToResetSearchFocus) mySearchTextComponent.requestFocusInWindow();
402     updateBindings();
403     updateActions();
404     revalidate();
405     repaint();
406   }
407
408   public void updateActions() {
409     mySearchActionsToolbar1.updateActionsImmediately();
410     mySearchActionsToolbar2.updateActionsImmediately();
411     myReplaceActionsToolbar1.updateActionsImmediately();
412     myReplaceActionsToolbar2.updateActionsImmediately();
413   }
414
415   public void addTextToRecent(@NotNull JTextComponent textField) {
416     final String text = textField.getText();
417     if (text.length() > 0) {
418       FindInProjectSettings findInProjectSettings = FindInProjectSettings.getInstance(myProject);
419       if (textField == mySearchTextComponent) {
420         findInProjectSettings.addStringToFind(text);
421         if (mySearchFieldWrapper.getTargetComponent() instanceof SearchTextField) {
422           ((SearchTextField)mySearchFieldWrapper.getTargetComponent()).addCurrentTextToHistory();
423         }
424       }
425       else {
426         findInProjectSettings.addStringToReplace(text);
427         if (myReplaceFieldWrapper.getTargetComponent() instanceof SearchTextField) {
428           ((SearchTextField)myReplaceFieldWrapper.getTargetComponent()).addCurrentTextToHistory();
429         }
430       }
431     }
432   }
433
434   private boolean updateTextComponent(boolean search) {
435     JTextComponent oldComponent = search ? mySearchTextComponent : myReplaceTextComponent;
436     Color oldBackground = oldComponent != null ? oldComponent.getBackground() : null;
437     final MyTextComponentWrapper wrapper = search ? mySearchFieldWrapper : myReplaceFieldWrapper;
438     if (oldComponent != null) return false;
439
440     final JTextComponent textComponent;
441       SearchTextArea textArea = new SearchTextArea(search);
442       textComponent = textArea.getTextArea();
443       ((JTextArea)textComponent).setRows(isMultiline() ? 2 : 1);
444       KeymapUtil.reassignAction(textComponent,
445                                 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
446                                 NEW_LINE_KEYSTROKE,
447                                 WHEN_FOCUSED);
448
449     textComponent.registerKeyboardAction(e -> {
450       if (isMultiline(textComponent)) {
451         if (textComponent.isEditable() && textComponent.isEnabled()) {
452           textComponent.replaceSelection("\t");
453         }
454         else {
455           UIManager.getLookAndFeel().provideErrorFeedback(textComponent);
456         }
457       }
458       else {
459         textComponent.transferFocus();
460       }
461     }, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), WHEN_FOCUSED);
462
463     textComponent.registerKeyboardAction(e -> {
464       textComponent.transferFocusBackward();
465     }, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK), WHEN_FOCUSED);
466
467     wrapper.setContent(textArea);
468
469     UIUtil.addUndoRedoActions(textComponent);
470     Utils.setSmallerFont(textComponent);
471
472     textComponent.putClientProperty("AuxEditorComponent", Boolean.TRUE);
473     if (oldBackground != null) {
474       textComponent.setBackground(oldBackground);
475     }
476     textComponent.addFocusListener(new FocusListener() {
477       @Override
478       public void focusGained(final FocusEvent e) {
479         textComponent.repaint();
480       }
481
482       @Override
483       public void focusLost(final FocusEvent e) {
484         textComponent.repaint();
485       }
486     });
487     installCloseOnEscapeAction(textComponent);
488     return true;
489   }
490
491   private void searchFieldDocumentChanged() {
492     if (mySearchTextComponent instanceof JTextArea) {
493       adjustRows((JTextArea)mySearchTextComponent);
494     }
495     myEventDispatcher.getMulticaster().searchFieldDocumentChanged();
496   }
497
498   private void replaceFieldDocumentChanged() {
499     if (myReplaceTextComponent instanceof JTextArea) {
500       adjustRows((JTextArea)myReplaceTextComponent);
501     }
502     myEventDispatcher.getMulticaster().replaceFieldDocumentChanged();
503   }
504
505   private void multilineStateChanged() {
506     myEventDispatcher.getMulticaster().multilineStateChanged();
507   }
508
509   private static void adjustRows(@NotNull JTextArea area) {
510     area.setRows(Math.max(2, Math.min(3, StringUtil.countChars(area.getText(), '\n') + 1)));
511   }
512
513   private static boolean isMultiline(@NotNull JTextComponent component) {
514     return component.getText().contains("\n");
515   }
516
517
518   private void installCloseOnEscapeAction(@NotNull JTextComponent c) {
519     ActionListener action = new ActionListener() {
520       @Override
521       public void actionPerformed(ActionEvent e) {
522         close();
523       }
524     };
525     c.registerKeyboardAction(action, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
526     if (KeymapUtil.isEmacsKeymap()) {
527       c.registerKeyboardAction(action, KeyStroke.getKeyStroke(KeyEvent.VK_G, InputEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
528     }
529   }
530
531   private void installReplaceOnEnterAction(@NotNull JTextComponent c) {
532     ActionListener action = new ActionListener() {
533       @Override
534       public void actionPerformed(ActionEvent e) {
535         replace();
536       }
537     };
538     c.registerKeyboardAction(action, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
539   }
540
541   private void updateBindings() {
542     updateBindings(mySearchFieldActions, mySearchFieldWrapper);
543     updateBindings(mySearchActionsToolbar1, mySearchFieldWrapper);
544     updateBindings(mySearchActionsToolbar2, mySearchFieldWrapper);
545
546     updateBindings(myReplaceFieldActions, myReplaceFieldWrapper);
547     updateBindings(myReplaceActionsToolbar1, myReplaceToolbarWrapper);
548     updateBindings(myReplaceActionsToolbar2, myReplaceToolbarWrapper);
549   }
550
551   private void updateBindings(@NotNull DefaultActionGroup group, @NotNull JComponent shortcutHolder) {
552     updateBindings(ContainerUtil.immutableList(group.getChildActionsOrStubs()), shortcutHolder);
553   }
554
555   private void updateBindings(@NotNull ActionToolbarImpl toolbar, @NotNull JComponent shortcutHolder) {
556     updateBindings(toolbar.getActions(), shortcutHolder);
557   }
558
559   private void updateBindings(@NotNull List<? extends AnAction> actions, @NotNull JComponent shortcutHolder) {
560     DataContext context = DataManager.getInstance().getDataContext(this);
561     for (AnAction action : actions) {
562       ShortcutSet shortcut = null;
563       if (action instanceof ContextAwareShortcutProvider) {
564         shortcut = ((ContextAwareShortcutProvider)action).getShortcut(context);
565       }
566       else if (action instanceof ShortcutProvider) {
567         shortcut = ((ShortcutProvider)action).getShortcut();
568       }
569       if (shortcut != null) {
570         action.registerCustomShortcutSet(shortcut, shortcutHolder);
571       }
572     }
573   }
574
575
576   @NotNull
577   private ActionToolbarImpl createSearchToolbar1(@NotNull DefaultActionGroup group) {
578     ActionToolbarImpl toolbar = createToolbar(group);
579     toolbar.setForceMinimumSize(true);
580     toolbar.setReservePlaceAutoPopupIcon(false);
581     toolbar.setSecondaryButtonPopupStateModifier(mySearchToolbar1PopupStateModifier);
582     toolbar.setSecondaryActionsTooltip("More Options(" + ShowMoreOptions.SHORT_CUT + ")");
583     new ShowMoreOptions(toolbar, mySearchFieldWrapper);
584     return toolbar;
585   }
586
587   @NotNull
588   private ActionToolbarImpl createSearchToolbar2(@NotNull DefaultActionGroup group) {
589     return createToolbar(group);
590   }
591
592   @NotNull
593   private ActionToolbarImpl createReplaceToolbar1(@NotNull DefaultActionGroup group) {
594     ActionToolbarImpl toolbar = createToolbar(group);
595     toolbar.setForceMinimumSize(true);
596     toolbar.setReservePlaceAutoPopupIcon(false);
597     return toolbar;
598   }
599
600   @NotNull
601   private ActionToolbarImpl createReplaceToolbar2(@NotNull DefaultActionGroup group) {
602     return createToolbar(group);
603   }
604
605   @NotNull
606   private ActionToolbarImpl createToolbar(@NotNull ActionGroup group) {
607     return tweakToolbar((ActionToolbarImpl)ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, group, true));
608   }
609
610   @NotNull
611   private ActionToolbarImpl tweakToolbar(@NotNull ActionToolbarImpl toolbar) {
612     toolbar.setTargetComponent(this);
613     toolbar.setLayoutPolicy(ActionToolbar.AUTO_LAYOUT_POLICY);
614     toolbar.setBorder(null);
615     Utils.setSmallerFontForChildren(toolbar);
616     return toolbar;
617   }
618
619
620   public interface Listener extends EventListener {
621     void searchFieldDocumentChanged();
622
623     void replaceFieldDocumentChanged();
624
625     void multilineStateChanged();
626   }
627
628   public static class Builder {
629     private final Project myProject;
630     private final JComponent myTargetComponent;
631
632     private DataProvider myDataProvider;
633
634     private Runnable myReplaceAction;
635     private Runnable myCloseAction;
636
637     private DefaultActionGroup mySearchActions = new DefaultActionGroup("search bar 1", false);
638     private DefaultActionGroup myExtraSearchActions = new DefaultActionGroup("search bar 2", false);
639     private DefaultActionGroup mySearchFieldActions = new DefaultActionGroup("search field actions", false);
640     private BooleanGetter mySearchToolbarModifiedFlagGetter = BooleanGetter.FALSE;
641
642     private DefaultActionGroup myReplaceActions = new DefaultActionGroup("replace bar 1", false);
643     private DefaultActionGroup myExtraReplaceActions = new DefaultActionGroup("replace bar 1", false);
644     private DefaultActionGroup myReplaceFieldActions = new DefaultActionGroup("replace field actions", false);
645
646     private Builder(@Nullable Project project, @NotNull JComponent component) {
647       myProject = project;
648       myTargetComponent = component;
649     }
650
651     @NotNull
652     public Builder withDataProvider(@NotNull DataProvider provider) {
653       myDataProvider = provider;
654       return this;
655     }
656
657     @NotNull
658     public Builder withReplaceAction(@NotNull Runnable action) {
659       myReplaceAction = action;
660       return this;
661     }
662
663     @NotNull
664     public Builder withCloseAction(@NotNull Runnable action) {
665       myCloseAction = action;
666       return this;
667     }
668
669     @NotNull
670     public Builder addSearchFieldActions(@NotNull AnAction... actions) {
671       mySearchFieldActions.addAll(actions);
672       return this;
673     }
674
675     @NotNull
676     public Builder addReplaceFieldActions(@NotNull AnAction... actions) {
677       myReplaceFieldActions.addAll(actions);
678       return this;
679     }
680
681     @NotNull
682     public Builder addPrimarySearchActions(@NotNull AnAction... actions) {
683       mySearchActions.addAll(actions);
684       return this;
685     }
686
687     @NotNull
688     public Builder addSecondarySearchActions(@NotNull AnAction... actions) {
689       for (AnAction action : actions) {
690         mySearchActions.addAction(action).setAsSecondary(true);
691       }
692       return this;
693     }
694
695     @NotNull
696     public Builder withSecondarySearchActionsIsModifiedGetter(@NotNull BooleanGetter getter) {
697       mySearchToolbarModifiedFlagGetter = getter;
698       return this;
699     }
700
701     @NotNull
702     public Builder addExtraSearchActions(@NotNull AnAction... actions) {
703       myExtraSearchActions.addAll(actions);
704       return this;
705     }
706
707     @NotNull
708     public Builder addPrimaryReplaceActions(@NotNull AnAction... actions) {
709       myReplaceActions.addAll(actions);
710       return this;
711     }
712
713     @NotNull
714     public Builder addExtraReplaceAction(@NotNull AnAction... actions) {
715       myExtraReplaceActions.addAll(actions);
716       return this;
717     }
718
719     @NotNull
720     public SearchReplaceComponent build() {
721       return new SearchReplaceComponent(myProject,
722                                         myTargetComponent,
723                                         mySearchActions,
724                                         mySearchToolbarModifiedFlagGetter,
725                                         myExtraSearchActions,
726                                         mySearchFieldActions,
727                                         myReplaceActions,
728                                         myExtraReplaceActions,
729                                         myReplaceFieldActions,
730                                         myReplaceAction,
731                                         myCloseAction,
732                                         myDataProvider);
733     }
734   }
735
736   private static class MyTextComponentWrapper extends Wrapper {
737     @Nullable
738     public JTextComponent getTextComponent() {
739       JComponent wrapped = getTargetComponent();
740       return wrapped != null ? unwrapTextComponent(wrapped) : null;
741     }
742
743     @NotNull
744     protected static JTextComponent unwrapTextComponent(@NotNull JComponent wrapped) {
745       if (wrapped instanceof SearchTextField) {
746         return ((SearchTextField)wrapped).getTextEditor();
747       }
748       if (wrapped instanceof SearchTextArea) {
749         return ((SearchTextArea)wrapped).getTextArea();
750       }
751       throw new AssertionError();
752     }
753   }
754 }