package com.jbidwatcher.ui.util; /** * A text field for search/filter interfaces. The extra functionality includes * <p/> * a placeholder string (when the user hasn't yet typed anything), and a button * <p/> * to clear the currently-entered text. * * @author Elliott Hughes */ import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; // // TODO: add a menu of recent searches. // TODO: make recent searches persistent. public class SearchField extends JTextField { private static final Border CANCEL_BORDER = new CancelBorder(); private boolean sendsNotificationForEachKeystroke = false; private boolean showingPlaceholderText = false; private boolean armed = false; public SearchField(String placeholderText, int length) { super(length); addFocusListener(new PlaceholderText(placeholderText)); initBorder(); initKeyListener(); } public SearchField(String placeholderText) { this(placeholderText, 15); } public SearchField() { this("Search"); } private void initBorder() { setBorder(new CompoundBorder(getBorder(), CANCEL_BORDER)); MouseInputListener mouseInputListener = new CancelListener(); addMouseListener(mouseInputListener); addMouseMotionListener(mouseInputListener); } private void initKeyListener() { addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { cancel(); } else if (sendsNotificationForEachKeystroke) { maybeNotify(); } } }); } private void cancel() { setText(""); postActionEvent(); } private void maybeNotify() { if (showingPlaceholderText) { return; } postActionEvent(); } public String getText() { if(showingPlaceholderText) return ""; return super.getText(); } public void setSendsNotificationForEachKeystroke(boolean eachKeystroke) { this.sendsNotificationForEachKeystroke = eachKeystroke; } /** * Draws the cancel button as a gray circle with a white cross inside. */ static class CancelBorder extends EmptyBorder { private static final Color GRAY = new Color(0.7f, 0.7f, 0.7f); CancelBorder() { super(0, 0, 0, 15); } public void paintBorder(Component c, Graphics oldGraphics, int x, int y, int width, int height) { SearchField field = (SearchField) c; if (field.showingPlaceholderText || field.getText().length() == 0) { return; } Graphics2D g = (Graphics2D) oldGraphics; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); final int circleL = 14; final int circleX = x + width - circleL; final int circleY = y + (height - 1 - circleL) / 2; g.setColor(field.armed ? Color.GRAY : GRAY); g.fillOval(circleX, circleY, circleL, circleL); final int lineL = circleL - 8; final int lineX = circleX + 4; final int lineY = circleY + 4; g.setColor(Color.WHITE); g.drawLine(lineX, lineY, lineX + lineL, lineY + lineL); g.drawLine(lineX, lineY + lineL, lineX + lineL, lineY); } } /** * Handles a click on the cancel button by clearing the text and notifying * <p/> * any ActionListeners. */ class CancelListener extends MouseInputAdapter { private boolean isOverButton(MouseEvent e) { // If the button is down, we might be outside the component // without having had mouseExited invoked. if (!contains(e.getPoint())) { return false; } // In lieu of proper hit-testing for the circle, check that // the mouse is somewhere in the border. Rectangle innerArea = SwingUtilities.calculateInnerArea(SearchField.this, null); return (!innerArea.contains(e.getPoint())); } public void mouseDragged(MouseEvent e) { arm(e); } public void mouseEntered(MouseEvent e) { arm(e); } public void mouseExited(MouseEvent e) { disarm(); } public void mousePressed(MouseEvent e) { arm(e); } public void mouseReleased(MouseEvent e) { if (armed) { cancel(); } disarm(); } private void arm(MouseEvent e) { armed = (isOverButton(e) && SwingUtilities.isLeftMouseButton(e)); repaint(); } private void disarm() { armed = false; repaint(); } } /** * Replaces the entered text with a gray placeholder string when the * <p/> * search field doesn't have the focus. The entered text returns when * <p/> * we get the focus back. */ class PlaceholderText implements FocusListener { private String placeholderText; private String previousText = ""; private Color previousColor; PlaceholderText(String placeholderText) { this.placeholderText = placeholderText; focusLost(null); } public void focusGained(FocusEvent e) { setForeground(previousColor); setText(previousText); showingPlaceholderText = false; } public void focusLost(FocusEvent e) { previousText = getText(); previousColor = getForeground(); if (previousText.length() == 0) { showingPlaceholderText = true; setForeground(Color.GRAY); setText(placeholderText); } } } }