/*
 * Copyright (c) 2005, Christopher Atlan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.moneydance.awt;

import java.awt.BasicStroke;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.Paint;
import java.awt.GradientPaint;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.awt.Rectangle;
import java.awt.Cursor;
import java.awt.event.KeyEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JTextField;
import javax.swing.JPopupMenu;
import javax.swing.border.Border;
import javax.swing.event.MouseInputAdapter;
import javax.swing.UIManager;
import javax.swing.JComponent;
import javax.swing.KeyStroke;

/**
 * <code>QuickSearchField</code> is a componet like the Cocoa NSSearchField.
 * If the componet has no focus and no text form a user input, it show the default text
 * in default color and default font.
 * If it has a user text, a cancle icon is show. By click on the cancle icon the user input
 * is reset to the default settings.
 * You also can set a JPopupMenu that is show if the user clicks the search icon.
 * @author Christopher Atlan
 */
public class QuickSearchField
extends javax.swing.JTextField
implements java.awt.event.MouseListener, java.awt.event.MouseMotionListener, java.awt.event.FocusListener, javax.swing.border.Border
{
  private static final Color OUTER = UIManager.getColor("control");
  private static final Color BOTTOM_LINE_COLOR = new Color(227, 227, 227);
  private static final Color TOP_LINE_1_COLOR = new Color(104, 104, 104);
  private static final Color TOP_LINE_2_COLOR = new Color(178, 178, 178);
  private static final Color TOP_LINE_3_COLOR = new Color(228, 228, 228);
  private static final int edge_width = 20;
  private static final int edge_height = 20;
  private static final int edge_thickens = 2;
  private static final Cursor TEXT_CURSOR = new Cursor(Cursor.TEXT_CURSOR);
  private static final Cursor DEFAULT_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR);
  public static final String PROP_DEFAULT_SEARCH_TEXT = "defaultSearchText";
  public static final String PROP_DEFAULT_SEARCH_FONT = "defaultSearchFont";
  public static final String PROP_DEFAULT_SEARCH_COLOR = "defaultSearchColor";
  public static final String PROP_POPUP_MENU = "popupMenu";
  
  private static final Icon defaultSearchIcon = 
    new ImageIcon(QuickSearchField.class.
                  getResource("/com/moneydance/awt/images/search_magglass.png"));
  private static final Icon defaultCancelIcon = 
    new ImageIcon(QuickSearchField.class.
                  getResource("/com/moneydance/awt/images/search_remove.png"));
  private static final Icon defaultCancelIconPressed = 
    new ImageIcon(QuickSearchField.class.
                  getResource("/com/moneydance/awt/images/search_remove_pressed.png"));

  private Icon searchIcon;
  private Icon cancelIcon;
  private Icon searchIconPressed;
  private Icon cancelIconPressed;
  private Rectangle searchIconBounds = new Rectangle();
  private Rectangle cancelIconBounds = new Rectangle();
  private boolean isSearchIconPressed = false;
  private boolean isCancelIconPressed = false;
  
  private String defaultSearchText;
  private Font defaultSearchFont;
  private Color defaultSearchColor;
  private JPopupMenu popupMenu;
  
  private Arc2D.Double arc1 = 
    new Arc2D.Double(0, 0, edge_width, edge_height, 90, 90, Arc2D.OPEN);
  private Arc2D.Double arc2 = 
    new Arc2D.Double(0, 0, edge_width, edge_height, 90, 90, Arc2D.OPEN);
  private Arc2D.Double arc3 = 
    new Arc2D.Double(0, 0, edge_width, edge_height, 90, 90, Arc2D.OPEN);
  private Arc2D.Double arc4 =
    new Arc2D.Double(0, 0, edge_width, edge_height, 90, 90, Arc2D.OPEN);

  private Area outer1 = new Area();
  private Area outer2 = new Area();
  private Area outer3 = new Area();
  private Area outer4 = new Area();
  
  private Rectangle2D.Double rect1 = 
    new Rectangle2D.Double(0, 0, edge_width/2, edge_height/2);
  private Rectangle2D.Double rect2 = 
    new Rectangle2D.Double(0, 0, edge_width/2, edge_height/2);
  private Rectangle2D.Double rect3 = 
    new Rectangle2D.Double(0, 0, edge_width/2, edge_height/2);
  private Rectangle2D.Double rect4 = 
    new Rectangle2D.Double(0, 0, edge_width/2, edge_height/2);
  
  private BasicStroke borderStroke = new BasicStroke(edge_thickens);

  
  private boolean isDefault = true;
  
  /**
    * Constructs a new QuickSearchField
   */
  public QuickSearchField() {
    this("", defaultSearchIcon, defaultCancelIcon, null);
  }
  
  /** Constructs a new QuickSearchField initialized with the specified 
    * default text.
    */
  public QuickSearchField(final String defaultSearchText) {
    this(defaultSearchText, defaultSearchIcon, defaultCancelIcon, null);
  }
  
  /** Constructs a new QuickSearchField initialized with the specified default 
    * text, search Icon and Cancel Icon
    */
  public QuickSearchField(final String defaultSearchText, final Icon searchIcon,
                          final Icon cancelIcon)
  {
    this(defaultSearchText, searchIcon, cancelIcon, null);
  }
  
  /** Constructs a new QuickSearchField initialized with the specified default 
    * text, search Icon, Cancel Icon
    * and PopupMenu.
    */
  public QuickSearchField(final String defaultSearchText, final Icon searchIcon,
                          final Icon cancelIcon, final JPopupMenu popupMenu)
  {
    super("", 20);
    
    this.searchIcon = searchIcon;
    this.cancelIcon = cancelIcon;
    this.defaultSearchText = defaultSearchText;
    
    this.searchIconPressed = defaultSearchIcon;
    this.cancelIconPressed = defaultCancelIconPressed;
    
    defaultSearchFont = new Font(getFont().getName(),
                                 getFont().getStyle(),
                                 getFont().getSize()-2);
    
    defaultSearchColor = getDisabledTextColor();
    
    addMouseListener(this);
    addMouseMotionListener(this);
    addFocusListener(this);
    
    registerKeyboardAction(new ActionListener() {
      public void actionPerformed(ActionEvent actionEvent) {
        setCancelValue();
      }
    }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
    
    initComponents();
    
  }
  
  /**
    * Gets the default text, this text is show if the component has no focus
   * and the user has not input a text
   */
  public String getDefaultSearchText() {
    return defaultSearchText;
  }
  
  /**
    * Sets the default text, this text is show if the component has no focus
   * and the user has not input a text
   */
  public void setDefaultSearchText(final String value) {
    final String oldValue = defaultSearchText;
    defaultSearchText = value;
    if (isDefault)
      setDefaultValue();
    
    firePropertyChange(PROP_DEFAULT_SEARCH_TEXT, oldValue, value);
  }
  
  /**
    * Gets the default text font, this font is use for the default text
   * and show if the component has no focus and the user has not input a text
   */
  public Font getDefaultSearchFont() {
    return defaultSearchFont;
  }
  
  /**
    * Sets the default text font, this font is use for the default text
   * and show if the component has no focus and the user has not input a text
   */
  public void setDefaultSearchFont(final Font value) {
    final Font oldValue = defaultSearchFont;
    defaultSearchFont = value;
    firePropertyChange(PROP_DEFAULT_SEARCH_FONT, oldValue, value);
  }
  
  /**
    * Gets the default text color, this font is use for the default text
   * and show if the component has no focus and the user has not input a text
   */
  public Color getDefaultSearchColor() {
    return defaultSearchColor;
  }
  
  /**
    * Sets the default text color, this font is use for the default text
   * and show if the component has no focus and the user has not input a text
   */
  public void setDefaultSearchColor(final Color value) {
    final Color oldValue = defaultSearchColor;
    defaultSearchColor = value;
    firePropertyChange(PROP_DEFAULT_SEARCH_COLOR, oldValue, value);
  }
  
  /**
    * Gets the JPopupMenu, this is show if the user clicks on the searchIcon
   */
  public JPopupMenu getPopupMenu() {
    return popupMenu;
  }
  
  /**
    * Sets a JPopupMenu, this is show if the user clicks on the searchIcon
   */
  public void setPopupMenu(final JPopupMenu value) {
    final JPopupMenu oldValue = popupMenu;
    popupMenu = value;
    firePropertyChange(PROP_POPUP_MENU, oldValue, value);
  }
  
  /**
    * Gets the search icon
   */
  public Icon getSearchIcon() {
    return searchIcon;
  }
  
  /**
    * Sets a search icon
   */
  public void setSearchIcon(Icon value) {
    searchIcon = value;
  }
  
  /**
    * Gets the cancel icon
   */
  public Icon getCancelIcon() {
    return cancelIcon;
  }
  
  /**
    * Sets a cancel icon
   */
  public void setCancelIcon(Icon value) {
    cancelIcon = value;
  }
  
  /**
    * Gets the pressed search icon
   */
  public Icon getSearchIconPressed() {
    return searchIconPressed;
  }
  
  /**
    * Sets a pressed search icon
   */
  public void setSearchIconPressed(Icon value) {
    searchIconPressed = value;
  }
  
  /**
    * Gets the pressed cancel icon
   */
  public Icon getCancelIconPressed() {
    return cancelIconPressed;
  }
  
  /**
    * Sets a pressed cancel icon
   */
  public void setCancelIconPressed(Icon value) {
    cancelIconPressed = value;
  }
  
  private void setDefaultValue() {
    setFont(defaultSearchFont);
    setForeground(defaultSearchColor);
    setText(defaultSearchText);
    isDefault = true;
  }
  
  private void setCancelValue() {
    setText("");
    isCancelIconPressed = false;
  }
  
  public java.awt.Dimension getMinimumSize() {
    java.awt.Dimension dim = super.getMinimumSize();
    dim.height = searchIcon.getIconHeight()+4;
    return dim;
  }
  
  public java.awt.Dimension getPreferredSize() {
    java.awt.Dimension dim = super.getPreferredSize();
    dim.height = searchIcon.getIconHeight()+4;
    return dim;
  }
  
  public Insets getBorderInsets(Component c) {
    return new Insets(4, searchIcon.getIconWidth() + (edge_width/4), 4, cancelIcon.getIconWidth() +  4);
  }
  
  public boolean isBorderOpaque() {
    return false;
  }

  
  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
    int edge_left_x = x + (edge_width/2);
    int y1 = y + height - 1;
    int edge_right_x = x + width - (edge_width/2);
    int y2 = y1;
    
    Color outsideBG = OUTER;
    Component parent = c.getParent();
    if(parent!=null) {
      outsideBG = parent.getBackground();
    }
    
    if (!isCancelIconPressed) {
      searchIconBounds.x = (edge_width/4)-2;
      searchIconBounds.y = y + ((height - searchIcon.getIconHeight())/2)+1;
      searchIconBounds.width = searchIcon.getIconWidth();
      searchIconBounds.height = searchIcon.getIconHeight();
      searchIcon.paintIcon(c, g, searchIconBounds.x, searchIconBounds.y);
    } else {
      searchIconBounds.x = (edge_width/4)-2;
      searchIconBounds.y = y + ((height - searchIconPressed.getIconHeight())/2)+1;
      searchIconBounds.width = searchIconPressed.getIconWidth();
      searchIconBounds.height = searchIconPressed.getIconHeight();
      searchIconPressed.paintIcon(c, g, searchIconBounds.x, searchIconBounds.y);
    }

    if (!isDefault) {
      if (!isCancelIconPressed) {
        cancelIconBounds.x = x + width - (edge_width/4) - cancelIcon.getIconWidth() + 2;
        cancelIconBounds.y = y + ((height - cancelIcon.getIconHeight())/2)+1;
        cancelIconBounds.width = cancelIcon.getIconWidth();
        cancelIconBounds.height = cancelIcon.getIconHeight();
        cancelIcon.paintIcon(c, g, cancelIconBounds.x, cancelIconBounds.y);
      } else {
        cancelIconBounds.x = x + width - (edge_width/4) - cancelIconPressed.getIconWidth() + 2;
        cancelIconBounds.y = y + ((height - cancelIconPressed.getIconHeight())/2)+1;
        cancelIconBounds.width = cancelIconPressed.getIconWidth();
        cancelIconBounds.height = cancelIconPressed.getIconHeight();
        cancelIconPressed.paintIcon(c, g, cancelIconBounds.x, cancelIconBounds.y);
      }
    }
    
    Graphics2D g2 = (Graphics2D) g;
    g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    
    
    
    Shape s;
    
    g.setColor(TOP_LINE_1_COLOR);
    s = new Line2D.Double(edge_left_x, y, edge_right_x + 1, y);
    g2.draw(s);
    
    g.setColor(TOP_LINE_2_COLOR);
    s = new Line2D.Double(edge_left_x - 1, y + 1, edge_right_x + 2, y + 1);
    g2.draw(s);
    
    g.setColor(TOP_LINE_3_COLOR);
    s = new Line2D.Double(edge_left_x - 3, y + 2, edge_right_x + 4, y + 2);
    g2.draw(s);
    
    g2.setColor(BOTTOM_LINE_COLOR);
    s = new Line2D.Double(edge_left_x - 2, y2, edge_right_x + 4, y2);
    g2.draw(s);
    
    g2.setStroke(borderStroke);
    
    // set the paint for the left edge to a gradient
    Paint p = new GradientPaint(0, 0, TOP_LINE_1_COLOR,
                                0, height, BOTTOM_LINE_COLOR);
    g2.setPaint(p);
    
    // top left corner
    arc1.setArcType(Arc2D.OPEN);
    arc1.x = 0;
    arc1.y = 0;
    arc1.width = edge_width;
    arc1.height = edge_height;
    arc1.start = 90;
    arc1.extent = 90;
    g2.draw(arc1);
    
    rect1.x = x;
    rect1.y = y;
    rect1.width = edge_width/2;
    rect1.height = edge_height/2;

    arc1.setArcType(Arc2D.PIE);
    outer1 = new Area(rect1);
    outer1.subtract(new Area(arc1));
    
    // if necessary, draw the straight line connecting the two rounded corners on the left
    if(y+(edge_height/2) < y + height - (edge_height/2)) {
      s = new Line2D.Double(x, y+(edge_height/2), x, y+height-(edge_height/2));
      g2.draw(s);
    }
    
    // bottom left corner
    arc2.setArcType(Arc2D.OPEN);
    arc2.x = 0;
    arc2.y = height-edge_height;
    arc2.width = edge_width;
    arc2.height = edge_height;
    arc2.start = 180;
    arc2.extent = 90;
    g2.draw(arc2);
    
    rect2.x = x;
    rect2.y = y+height-(edge_height/2);
    rect2.width = edge_width/2;
    rect2.height = edge_height/2;
    
    arc2.setArcType(Arc2D.PIE);
    outer2 = new Area(rect2);
    outer2.subtract(new Area(arc2));
    
    // gradient painter for the right side
    p = new GradientPaint(width - edge_width, 0, TOP_LINE_1_COLOR,
                          width - edge_width, height, BOTTOM_LINE_COLOR);
    g2.setPaint(p);
    

    // top right corner
    arc3.setArcType(Arc2D.OPEN);
    arc3.x = width - edge_width;
    arc3.y = 0;
    arc3.width = edge_width;
    arc3.height = edge_height;
    arc3.start = 360;
    arc3.extent = 90;
    g2.draw(arc3);
    
    rect3.x = edge_right_x;
    rect3.y = y;
    rect3.width = edge_width/2;
    rect3.height = edge_height/2;
    
    arc3.setArcType(Arc2D.PIE);
    outer3 = new Area(rect3);
    outer3.subtract(new Area(arc3));
    

    // if necessary, draw the straight line connecting the two rounded corners on the left
    if((y+(edge_height/2)) < (y+height-(edge_height/2))) {
      s = new Line2D.Double(x+width, y+(edge_height/2),
                            x+width, y+height-(edge_height/2));
      g2.draw(s);
    }
    
    // bottom right corner
    arc4.setArcType(Arc2D.OPEN);
    arc4.x = width - edge_width;
    arc4.y = height - edge_height;
    arc4.width = edge_width;
    arc4.height = edge_height;
    arc4.start = 270;
    arc4.extent = 90;
    g2.draw(arc4);
    
    rect4.x = edge_right_x;
    rect4.y = y+height-(edge_height/2);
    rect4.width = edge_width/2;
    rect4.height = edge_height/2;
    
    arc4.setArcType(Arc2D.PIE);
    outer4 = new Area(rect4);
    outer4.subtract(new Area(arc4));
    
    
    g2.setPaint(outsideBG);
    g2.fill(outer1);
    g2.fill(outer2);
    g2.fill(outer3);
    g2.fill(outer4);
  }
  
  public void mouseEntered(MouseEvent e) {
  }
  
  public void mouseReleased(MouseEvent e) {
    int x = e.getX();
    int y = e.getY();
    
    isSearchIconPressed = searchIconBounds.contains(x, y);
    isCancelIconPressed = cancelIconBounds.contains(x, y);
    
    repaint();
  }
  
  public void mouseClicked(MouseEvent e) {
    int x = e.getX();
    int y = e.getY();
    
    if (searchIconBounds.contains(x, y)) {
      if (popupMenu != null) {
        popupMenu.show(e.getComponent(), x, y);
      }
      isSearchIconPressed = false;
    }
    
    if (cancelIconBounds.contains(x, y)) {
      setCancelValue();
      repaint();
    }
  }
  
  public void mousePressed(MouseEvent e) {
    int x = e.getX();
    int y = e.getY();
    
    isSearchIconPressed = searchIconBounds.contains(x, y);
    isCancelIconPressed = cancelIconBounds.contains(x, y);
    
    if (isCancelIconPressed) {
      this.setSelectionStart(0);
      this.setSelectionEnd(getText().length());
    }
    
    repaint();
  }
  
  public void mouseExited(MouseEvent e) {
  }
  
  
  public void mouseDragged(MouseEvent e) {
  }
  
  public void mouseMoved(MouseEvent e) {
    int x = e.getX();
    int y = e.getY();
    
    if (searchIconBounds.contains(x, y) || (cancelIconBounds.contains(x, y) && !isDefault)) {
      this.setCursor(DEFAULT_CURSOR);
    } else {
      this.setCursor(TEXT_CURSOR);
    }
  }
  
  public void focusGained(FocusEvent e) {
    if (isDefault) {
      isDefault = false;
      setText("");
      setFont(UIManager.getFont("TextField.font"));
      setForeground(UIManager.getColor("TextField.foreground"));
    }
  }
  public void focusLost(FocusEvent e) {
    if ( getText().length() == 0 ) {
      setDefaultValue();
    }
  }
  
  /** This method is called from within the constructor to
    * initialize the form.
    * WARNING: Do NOT modify this code. The content of this method is
    * always regenerated by the FormEditor.
    */
  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
  private void initComponents() {
    
    setFont(getDefaultSearchFont());
    setForeground(getDefaultSearchColor());
    setText(getDefaultSearchText());
    setBorder(this);
  }
  // </editor-fold>//GEN-END:initComponents
  
  
  // Variables declaration - do not modify//GEN-BEGIN:variables
  // End of variables declaration//GEN-END:variables
  
}
