| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.ui; |
| |
| import com.intellij.icons.AllIcons; |
| import com.intellij.openapi.actionSystem.ActionManager; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.CommonShortcuts; |
| import com.intellij.openapi.actionSystem.IdeActions; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.ui.JBMenuItem; |
| import com.intellij.openapi.ui.JBPopupMenu; |
| import com.intellij.openapi.ui.popup.JBPopup; |
| import com.intellij.openapi.ui.popup.JBPopupFactory; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.ui.components.JBList; |
| import com.intellij.util.ui.UIUtil; |
| |
| import javax.swing.*; |
| import javax.swing.border.Border; |
| import javax.swing.border.CompoundBorder; |
| import javax.swing.event.DocumentListener; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * @author max |
| */ |
| public class SearchTextField extends JPanel { |
| |
| private int myHistorySize = 5; |
| private final MyModel myModel; |
| private final TextFieldWithProcessing myTextField; |
| |
| private JBPopup myPopup; |
| private JLabel myClearFieldLabel; |
| private JLabel myToggleHistoryLabel; |
| private JPopupMenu myNativeSearchPopup; |
| private JMenuItem myNoItems; |
| |
| public SearchTextField() { |
| this(true); |
| } |
| |
| public SearchTextField(boolean historyEnabled) { |
| super(new BorderLayout()); |
| |
| myModel = new MyModel(); |
| |
| myTextField = new TextFieldWithProcessing() { |
| @Override |
| public void processKeyEvent(final KeyEvent e) { |
| if (preprocessEventForTextField(e)) return; |
| super.processKeyEvent(e); |
| } |
| |
| @Override |
| public void setBackground(final Color bg) { |
| super.setBackground(bg); |
| if (!hasIconsOutsideOfTextField()) { |
| if (myClearFieldLabel != null) { |
| myClearFieldLabel.setBackground(bg); |
| } |
| } |
| if (myToggleHistoryLabel != null) { |
| myToggleHistoryLabel.setBackground(bg); |
| } |
| } |
| }; |
| myTextField.setColumns(15); |
| myTextField.addFocusListener(new FocusAdapter() { |
| @Override |
| public void focusLost(FocusEvent e) { |
| onFocusLost(); |
| super.focusLost(e); |
| } |
| |
| @Override |
| public void focusGained(FocusEvent e) { |
| onFocusGained(); |
| super.focusGained(e); |
| } |
| }); |
| add(myTextField, BorderLayout.CENTER); |
| myTextField.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.getKeyCode() == KeyEvent.VK_DOWN) { |
| if (isSearchControlUISupported() && myNativeSearchPopup != null) { |
| myNativeSearchPopup.show(myTextField, 5, myTextField.getHeight()); |
| } else if (myPopup == null || !myPopup.isVisible()) { |
| showPopup(); |
| } |
| } |
| } |
| }); |
| |
| if (isSearchControlUISupported() || UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF()) { |
| myTextField.putClientProperty("JTextField.variant", "search"); |
| } |
| if (isSearchControlUISupported()) { |
| if (historyEnabled) { |
| myNativeSearchPopup = new JBPopupMenu(); |
| myNoItems = new JBMenuItem("No recent searches"); |
| myNoItems.setEnabled(false); |
| |
| updateMenu(); |
| myTextField.putClientProperty("JTextField.Search.FindPopup", myNativeSearchPopup); |
| } |
| } |
| else { |
| myToggleHistoryLabel = new JLabel(AllIcons.Actions.Search); |
| myToggleHistoryLabel.setOpaque(true); |
| myToggleHistoryLabel.addMouseListener(new MouseAdapter() { |
| public void mousePressed(MouseEvent e) { |
| togglePopup(); |
| } |
| }); |
| if (historyEnabled) { |
| add(myToggleHistoryLabel, BorderLayout.WEST); |
| } |
| |
| myClearFieldLabel = new JLabel(UIUtil.isUnderDarcula() ? AllIcons.Actions.Clean : AllIcons.Actions.CleanLight); |
| myClearFieldLabel.setOpaque(true); |
| add(myClearFieldLabel, BorderLayout.EAST); |
| myClearFieldLabel.addMouseListener(new MouseAdapter() { |
| public void mousePressed(MouseEvent e) { |
| myTextField.setText(""); |
| onFieldCleared(); |
| } |
| }); |
| |
| if (!hasIconsOutsideOfTextField()) { |
| final Border originalBorder; |
| if (SystemInfo.isMac) { |
| originalBorder = BorderFactory.createLoweredBevelBorder(); |
| } |
| else { |
| originalBorder = myTextField.getBorder(); |
| } |
| |
| myToggleHistoryLabel.setBackground(myTextField.getBackground()); |
| myClearFieldLabel.setBackground(myTextField.getBackground()); |
| |
| setBorder(new CompoundBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0), originalBorder)); |
| |
| myTextField.setOpaque(true); |
| myTextField.setBorder(IdeBorderFactory.createEmptyBorder(0, 5, 0, 5)); |
| } |
| else { |
| setBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0)); |
| } |
| } |
| |
| if (ApplicationManager.getApplication() != null) { //tests |
| final ActionManager actionManager = ActionManager.getInstance(); |
| if (actionManager != null) { |
| final AnAction clearTextAction = actionManager.getAction(IdeActions.ACTION_CLEAR_TEXT); |
| if (clearTextAction.getShortcutSet().getShortcuts().length == 0) { |
| clearTextAction.registerCustomShortcutSet(CommonShortcuts.ESCAPE, this); |
| } |
| } |
| } |
| } |
| |
| protected void onFieldCleared() { |
| } |
| |
| protected void onFocusLost() { |
| } |
| |
| protected void onFocusGained() { |
| } |
| |
| private void updateMenu() { |
| if (myNativeSearchPopup != null) { |
| myNativeSearchPopup.removeAll(); |
| final int itemsCount = myModel.getSize(); |
| if (itemsCount == 0) { |
| myNativeSearchPopup.add(myNoItems); |
| } |
| else { |
| for (int i = 0; i < itemsCount; i++) { |
| final String item = myModel.getElementAt(i); |
| addMenuItem(item); |
| } |
| } |
| } |
| } |
| |
| protected boolean isSearchControlUISupported() { |
| return (SystemInfo.isMacOSLeopard && UIUtil.isUnderAquaLookAndFeel()) || UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF(); |
| } |
| |
| protected boolean hasIconsOutsideOfTextField() { |
| return UIUtil.isUnderGTKLookAndFeel() || UIUtil.isUnderNimbusLookAndFeel(); |
| } |
| |
| public void addDocumentListener(DocumentListener listener) { |
| getTextEditor().getDocument().addDocumentListener(listener); |
| } |
| |
| public void removeDocumentListener(DocumentListener listener) { |
| getTextEditor().getDocument().removeDocumentListener(listener); |
| } |
| |
| public void addKeyboardListener(final KeyListener listener) { |
| getTextEditor().addKeyListener(listener); |
| } |
| |
| public void setEnabled(boolean enabled) { |
| super.setEnabled(enabled); |
| if (myToggleHistoryLabel != null) { |
| final Color bg = enabled ? UIUtil.getTextFieldBackground() : UIUtil.getPanelBackground(); |
| myToggleHistoryLabel.setBackground(bg); |
| myClearFieldLabel.setBackground(bg); |
| } |
| } |
| |
| public void setHistorySize(int historySize) { |
| if (historySize <= 0) throw new IllegalArgumentException("history size must be a positive number"); |
| myHistorySize = historySize; |
| } |
| |
| public void setHistory(List<String> aHistory) { |
| myModel.setItems(aHistory); |
| } |
| |
| public List<String> getHistory() { |
| final int itemsCount = myModel.getSize(); |
| final List<String> history = new ArrayList<String>(itemsCount); |
| for (int i = 0; i < itemsCount; i++) { |
| history.add(myModel.getElementAt(i)); |
| } |
| return history; |
| } |
| |
| public void setText(String aText) { |
| getTextEditor().setText(aText); |
| } |
| |
| public String getText() { |
| return getTextEditor().getText(); |
| } |
| |
| public void removeNotify() { |
| super.removeNotify(); |
| hidePopup(); |
| } |
| |
| public void addCurrentTextToHistory() { |
| if ((myNativeSearchPopup != null && myNativeSearchPopup.isVisible()) || (myPopup != null && myPopup.isVisible())) { |
| return; |
| } |
| final String item = getText(); |
| myModel.addElement(item); |
| } |
| |
| private void addMenuItem(final String item) { |
| if (myNativeSearchPopup != null) { |
| myNativeSearchPopup.remove(myNoItems); |
| final JMenuItem menuItem = new JBMenuItem(item); |
| myNativeSearchPopup.add(menuItem); |
| menuItem.addActionListener(new ActionListener() { |
| public void actionPerformed(final ActionEvent e) { |
| myTextField.setText(item); |
| addCurrentTextToHistory(); |
| } |
| }); |
| } |
| } |
| |
| public void selectText() { |
| getTextEditor().selectAll(); |
| } |
| |
| public JTextField getTextEditor() { |
| return myTextField; |
| } |
| |
| public boolean requestFocusInWindow() { |
| return myTextField.requestFocusInWindow(); |
| } |
| |
| public void requestFocus() { |
| getTextEditor().requestFocus(); |
| } |
| |
| public class MyModel extends AbstractListModel { |
| private List<String> myFullList = new ArrayList<String>(); |
| |
| private String mySelectedItem; |
| |
| public String getElementAt(int index) { |
| return myFullList.get(index); |
| } |
| |
| public int getSize() { |
| return Math.min(myHistorySize, myFullList.size()); |
| } |
| |
| public void addElement(String item) { |
| final String newItem = item.trim(); |
| if (newItem.isEmpty()) { |
| return; |
| } |
| |
| final int length = myFullList.size(); |
| int index = -1; |
| for (int i = 0; i < length; i++) { |
| if (StringUtil.equalsIgnoreCase(myFullList.get(i), newItem)) { |
| index = i; |
| break; |
| } |
| } |
| if (index == 0) { |
| // item is already at the top of the list |
| return; |
| } |
| else if (index > 0) { |
| // move item to top of the list |
| myFullList.remove(index); |
| } |
| else if (myFullList.size() >= myHistorySize && myFullList.size() > 0) { |
| // trim list |
| myFullList.remove(myFullList.size() - 1); |
| } |
| insertElementAt(newItem, 0); |
| } |
| |
| public void insertElementAt(String item, int index) { |
| myFullList.add(index, item); |
| fireContentsChanged(); |
| } |
| |
| public String getSelectedItem() { |
| return mySelectedItem; |
| } |
| |
| public void setSelectedItem(String anItem) { |
| mySelectedItem = anItem; |
| } |
| |
| public void fireContentsChanged() { |
| fireContentsChanged(this, -1, -1); |
| updateMenu(); |
| } |
| |
| public void setItems(List<String> aList) { |
| myFullList = new ArrayList<String>(aList); |
| fireContentsChanged(); |
| } |
| } |
| |
| private void hidePopup() { |
| if (myPopup != null) { |
| myPopup.cancel(); |
| myPopup = null; |
| } |
| } |
| |
| @Override |
| public Dimension getPreferredSize() { |
| Dimension size = super.getPreferredSize(); |
| Border border = super.getBorder(); |
| if (border != null && UIUtil.isUnderAquaLookAndFeel()) { |
| Insets insets = border.getBorderInsets(this); |
| size.height += insets.top + insets.bottom; |
| size.width += insets.left + insets.right; |
| } |
| return size; |
| } |
| |
| protected Runnable createItemChosenCallback(final JList list) { |
| return new Runnable() { |
| public void run() { |
| final String value = (String)list.getSelectedValue(); |
| getTextEditor().setText(value != null ? value : ""); |
| addCurrentTextToHistory(); |
| if (myPopup != null) { |
| myPopup.cancel(); |
| myPopup = null; |
| } |
| } |
| }; |
| } |
| |
| protected void showPopup() { |
| if (myPopup == null || !myPopup.isVisible()) { |
| final JList list = new JBList(myModel); |
| final Runnable chooseRunnable = createItemChosenCallback(list); |
| myPopup = JBPopupFactory.getInstance().createListPopupBuilder(list) |
| .setMovable(false) |
| .setRequestFocus(true) |
| .setItemChoosenCallback(chooseRunnable).createPopup(); |
| if (isShowing()) { |
| myPopup.showUnderneathOf(getPopupLocationComponent()); |
| } |
| } |
| } |
| |
| protected Component getPopupLocationComponent() { |
| return hasIconsOutsideOfTextField() ? myToggleHistoryLabel : this; |
| } |
| |
| private void togglePopup() { |
| if (myPopup == null) { |
| showPopup(); |
| } |
| else { |
| hidePopup(); |
| } |
| } |
| |
| public void setSelectedItem(final String s) { |
| getTextEditor().setText(s); |
| } |
| |
| public int getSelectedIndex() { |
| return myModel.myFullList.indexOf(getText()); |
| } |
| |
| protected static class TextFieldWithProcessing extends JTextField { |
| public void processKeyEvent(KeyEvent e) { |
| super.processKeyEvent(e); |
| } |
| } |
| |
| public final void keyEventToTextField(KeyEvent e) { |
| myTextField.processKeyEvent(e); |
| } |
| |
| protected boolean preprocessEventForTextField(KeyEvent e) { |
| return false; |
| } |
| |
| public void setSearchIcon(final Icon icon) { |
| if (! isSearchControlUISupported()) { |
| myToggleHistoryLabel.setIcon(icon); |
| } |
| } |
| } |