2 * Copyright 2000-2013 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ui;
18 import com.intellij.icons.AllIcons;
19 import com.intellij.openapi.actionSystem.ActionManager;
20 import com.intellij.openapi.actionSystem.AnAction;
21 import com.intellij.openapi.actionSystem.CommonShortcuts;
22 import com.intellij.openapi.actionSystem.IdeActions;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.ui.JBMenuItem;
25 import com.intellij.openapi.ui.JBPopupMenu;
26 import com.intellij.openapi.ui.popup.JBPopup;
27 import com.intellij.openapi.ui.popup.JBPopupFactory;
28 import com.intellij.openapi.util.SystemInfo;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.ui.components.JBList;
31 import com.intellij.util.ui.UIUtil;
34 import javax.swing.border.Border;
35 import javax.swing.border.CompoundBorder;
36 import javax.swing.event.DocumentListener;
38 import java.awt.event.*;
39 import java.util.ArrayList;
40 import java.util.List;
45 public class SearchTextField extends JPanel {
47 private int myHistorySize = 5;
48 private final MyModel myModel;
49 private final TextFieldWithProcessing myTextField;
51 private JBPopup myPopup;
52 private JLabel myClearFieldLabel;
53 private JLabel myToggleHistoryLabel;
54 private JPopupMenu myNativeSearchPopup;
55 private JMenuItem myNoItems;
57 public SearchTextField() {
61 public SearchTextField(boolean historyEnabled) {
62 super(new BorderLayout());
64 myModel = new MyModel();
66 myTextField = new TextFieldWithProcessing() {
68 public void processKeyEvent(final KeyEvent e) {
69 if (preprocessEventForTextField(e)) return;
70 super.processKeyEvent(e);
74 public void setBackground(final Color bg) {
75 super.setBackground(bg);
76 if (!hasIconsOutsideOfTextField()) {
77 if (myClearFieldLabel != null) {
78 myClearFieldLabel.setBackground(bg);
81 if (myToggleHistoryLabel != null) {
82 myToggleHistoryLabel.setBackground(bg);
86 myTextField.setColumns(15);
87 myTextField.addFocusListener(new FocusAdapter() {
89 public void focusLost(FocusEvent e) {
95 public void focusGained(FocusEvent e) {
100 add(myTextField, BorderLayout.CENTER);
101 myTextField.addKeyListener(new KeyAdapter() {
103 public void keyPressed(KeyEvent e) {
104 if (e.getKeyCode() == KeyEvent.VK_DOWN) {
105 if (isSearchControlUISupported() && myNativeSearchPopup != null) {
106 myNativeSearchPopup.show(myTextField, 5, myTextField.getHeight());
107 } else if (myPopup == null || !myPopup.isVisible()) {
114 if (isSearchControlUISupported() || UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF()) {
115 myTextField.putClientProperty("JTextField.variant", "search");
117 if (isSearchControlUISupported()) {
118 if (historyEnabled) {
119 myNativeSearchPopup = new JBPopupMenu();
120 myNoItems = new JBMenuItem("No recent searches");
121 myNoItems.setEnabled(false);
124 myTextField.putClientProperty("JTextField.Search.FindPopup", myNativeSearchPopup);
128 myToggleHistoryLabel = new JLabel(AllIcons.Actions.Search);
129 myToggleHistoryLabel.setOpaque(true);
130 myToggleHistoryLabel.addMouseListener(new MouseAdapter() {
131 public void mousePressed(MouseEvent e) {
135 if (historyEnabled) {
136 add(myToggleHistoryLabel, BorderLayout.WEST);
139 myClearFieldLabel = new JLabel(UIUtil.isUnderDarcula() ? AllIcons.Actions.Clean : AllIcons.Actions.CleanLight);
140 myClearFieldLabel.setOpaque(true);
141 add(myClearFieldLabel, BorderLayout.EAST);
142 myClearFieldLabel.addMouseListener(new MouseAdapter() {
143 public void mousePressed(MouseEvent e) {
144 myTextField.setText("");
149 if (!hasIconsOutsideOfTextField()) {
150 final Border originalBorder;
151 if (SystemInfo.isMac) {
152 originalBorder = BorderFactory.createLoweredBevelBorder();
155 originalBorder = myTextField.getBorder();
158 myToggleHistoryLabel.setBackground(myTextField.getBackground());
159 myClearFieldLabel.setBackground(myTextField.getBackground());
161 setBorder(new CompoundBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0), originalBorder));
163 myTextField.setOpaque(true);
164 myTextField.setBorder(IdeBorderFactory.createEmptyBorder(0, 5, 0, 5));
167 setBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0));
171 if (ApplicationManager.getApplication() != null) { //tests
172 final ActionManager actionManager = ActionManager.getInstance();
173 if (actionManager != null) {
174 final AnAction clearTextAction = actionManager.getAction(IdeActions.ACTION_CLEAR_TEXT);
175 if (clearTextAction.getShortcutSet().getShortcuts().length == 0) {
176 clearTextAction.registerCustomShortcutSet(CommonShortcuts.ESCAPE, this);
182 protected void onFieldCleared() {
185 protected void onFocusLost() {
188 protected void onFocusGained() {
191 private void updateMenu() {
192 if (myNativeSearchPopup != null) {
193 myNativeSearchPopup.removeAll();
194 final int itemsCount = myModel.getSize();
195 if (itemsCount == 0) {
196 myNativeSearchPopup.add(myNoItems);
199 for (int i = 0; i < itemsCount; i++) {
200 final String item = myModel.getElementAt(i);
207 protected boolean isSearchControlUISupported() {
208 return (SystemInfo.isMacOSLeopard && UIUtil.isUnderAquaLookAndFeel()) || UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF();
211 protected boolean hasIconsOutsideOfTextField() {
212 return UIUtil.isUnderGTKLookAndFeel() || UIUtil.isUnderNimbusLookAndFeel();
215 public void addDocumentListener(DocumentListener listener) {
216 getTextEditor().getDocument().addDocumentListener(listener);
219 public void removeDocumentListener(DocumentListener listener) {
220 getTextEditor().getDocument().removeDocumentListener(listener);
223 public void addKeyboardListener(final KeyListener listener) {
224 getTextEditor().addKeyListener(listener);
227 public void setEnabled(boolean enabled) {
228 super.setEnabled(enabled);
229 if (myToggleHistoryLabel != null) {
230 final Color bg = enabled ? UIUtil.getTextFieldBackground() : UIUtil.getPanelBackground();
231 myToggleHistoryLabel.setBackground(bg);
232 myClearFieldLabel.setBackground(bg);
236 public void setHistorySize(int historySize) {
237 if (historySize <= 0) throw new IllegalArgumentException("history size must be a positive number");
238 myHistorySize = historySize;
241 public void setHistory(List<String> aHistory) {
242 myModel.setItems(aHistory);
245 public List<String> getHistory() {
246 final int itemsCount = myModel.getSize();
247 final List<String> history = new ArrayList<String>(itemsCount);
248 for (int i = 0; i < itemsCount; i++) {
249 history.add(myModel.getElementAt(i));
254 public void setText(String aText) {
255 getTextEditor().setText(aText);
258 public String getText() {
259 return getTextEditor().getText();
262 public void removeNotify() {
263 super.removeNotify();
267 public void addCurrentTextToHistory() {
268 if ((myNativeSearchPopup != null && myNativeSearchPopup.isVisible()) || (myPopup != null && myPopup.isVisible())) {
271 final String item = getText();
272 myModel.addElement(item);
275 private void addMenuItem(final String item) {
276 if (myNativeSearchPopup != null) {
277 myNativeSearchPopup.remove(myNoItems);
278 final JMenuItem menuItem = new JBMenuItem(item);
279 myNativeSearchPopup.add(menuItem);
280 menuItem.addActionListener(new ActionListener() {
281 public void actionPerformed(final ActionEvent e) {
282 myTextField.setText(item);
283 addCurrentTextToHistory();
289 public void selectText() {
290 getTextEditor().selectAll();
293 public JTextField getTextEditor() {
297 public boolean requestFocusInWindow() {
298 return myTextField.requestFocusInWindow();
301 public void requestFocus() {
302 getTextEditor().requestFocus();
305 public class MyModel extends AbstractListModel {
306 private List<String> myFullList = new ArrayList<String>();
308 private String mySelectedItem;
310 public String getElementAt(int index) {
311 return myFullList.get(index);
314 public int getSize() {
315 return Math.min(myHistorySize, myFullList.size());
318 public void addElement(String item) {
319 final String newItem = item.trim();
320 if (newItem.isEmpty()) {
324 final int length = myFullList.size();
326 for (int i = 0; i < length; i++) {
327 if (StringUtil.equalsIgnoreCase(myFullList.get(i), newItem)) {
333 // item is already at the top of the list
336 else if (index > 0) {
337 // move item to top of the list
338 myFullList.remove(index);
340 else if (myFullList.size() >= myHistorySize && myFullList.size() > 0) {
342 myFullList.remove(myFullList.size() - 1);
344 insertElementAt(newItem, 0);
347 public void insertElementAt(String item, int index) {
348 myFullList.add(index, item);
349 fireContentsChanged();
352 public String getSelectedItem() {
353 return mySelectedItem;
356 public void setSelectedItem(String anItem) {
357 mySelectedItem = anItem;
360 public void fireContentsChanged() {
361 fireContentsChanged(this, -1, -1);
365 public void setItems(List<String> aList) {
366 myFullList = new ArrayList<String>(aList);
367 fireContentsChanged();
371 private void hidePopup() {
372 if (myPopup != null) {
379 public Dimension getPreferredSize() {
380 Dimension size = super.getPreferredSize();
381 Border border = super.getBorder();
382 if (border != null && UIUtil.isUnderAquaLookAndFeel()) {
383 Insets insets = border.getBorderInsets(this);
384 size.height += insets.top + insets.bottom;
385 size.width += insets.left + insets.right;
390 protected Runnable createItemChosenCallback(final JList list) {
391 return new Runnable() {
393 final String value = (String)list.getSelectedValue();
394 getTextEditor().setText(value != null ? value : "");
395 addCurrentTextToHistory();
396 if (myPopup != null) {
404 protected void showPopup() {
405 if (myPopup == null || !myPopup.isVisible()) {
406 final JList list = new JBList(myModel);
407 final Runnable chooseRunnable = createItemChosenCallback(list);
408 myPopup = JBPopupFactory.getInstance().createListPopupBuilder(list)
410 .setRequestFocus(true)
411 .setItemChoosenCallback(chooseRunnable).createPopup();
413 myPopup.showUnderneathOf(getPopupLocationComponent());
418 protected Component getPopupLocationComponent() {
419 return hasIconsOutsideOfTextField() ? myToggleHistoryLabel : this;
422 private void togglePopup() {
423 if (myPopup == null) {
431 public void setSelectedItem(final String s) {
432 getTextEditor().setText(s);
435 public int getSelectedIndex() {
436 return myModel.myFullList.indexOf(getText());
439 protected static class TextFieldWithProcessing extends JTextField {
440 public void processKeyEvent(KeyEvent e) {
441 super.processKeyEvent(e);
445 public final void keyEventToTextField(KeyEvent e) {
446 myTextField.processKeyEvent(e);
449 protected boolean preprocessEventForTextField(KeyEvent e) {
453 public void setSearchIcon(final Icon icon) {
454 if (! isSearchControlUISupported()) {
455 myToggleHistoryLabel.setIcon(icon);