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);
}
}
}
}