/* BasicComboBoxUI.java --
   Copyright (C) 2004, 2005  Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.swing.plaf.basic;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.accessibility.Accessible;
import javax.swing.CellRendererPane;
import javax.swing.ComboBoxEditor;
import javax.swing.ComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.ComponentUI;

/**
 * UI Delegate for JComboBox
 *
 * @author Olga Rodimina
 * @author Robert Schuster
 */
public class BasicComboBoxUI extends ComboBoxUI
{
  /**
   * This arrow button that is displayed in the rigth side of JComboBox. This
   * button is used to hide and show combo box's list of items
   */
  protected JButton arrowButton;

  /**
   * The combo box for which this UI delegate is for
   */
  protected JComboBox comboBox;

  /**
   * Component that is responsible for displaying/editting  selected item of
   * the combo box. By default JTextField is used as an editor for the
   * JComboBox
   */
  protected Component editor;

  /**
   * Listener listening to focus events occuring in the JComboBox
   */
  protected FocusListener focusListener;

  /**
   * tells whether JComboBox currently has focus
   */
  protected boolean hasFocus;

  /**
   * Listener listening to item events fired by the JComboBox
   */
  protected ItemListener itemListener;

  /**
   * KeyListener listening to key events that occur while JComboBox has focus
   */
  protected KeyListener keyListener;

  /**
   * MouseListener listening to mouse events occuring in the combo box
   */
  private MouseListener mouseListener;

  /**
   * List used when rendering selected item of the combo box. The selection
   * and foreground colors for combo box renderer  are configured from this
   * list
   */
  protected JList listBox;

  /**
   * ListDataListener listening to JComboBox model
   */
  protected ListDataListener listDataListener;

  /**
   * Popup list containing combo box's menu items
   */
  protected ComboPopup popup;
  protected KeyListener popupKeyListener;
  protected MouseListener popupMouseListener;
  protected MouseMotionListener popupMouseMotionListener;

  /**
   * Listener listening to changes in the bound properties of JComboBox
   */
  protected PropertyChangeListener propertyChangeListener;

  /**
   * Colors that are used to render selected item in the combo box.
   */
  private Color shadow;
  private Color darkShadow;
  private Color highlight;
  private Color lightHighlight;

  /* Size of the largest item in the comboBox */
  private Dimension largestItemSize;

  // It seems that JComboBox doesn't have a border set explicitely. So we just
  // paint the border everytime combo box is displayed. 

  /* border insets for this JComboBox*/
  private static final Insets borderInsets = new Insets(2, 2, 2, 2);

  // Width of the arrow button  
  private static int arrowButtonWidth = 15;

  // FIXME: This fields aren't used anywhere at this moment.
  protected Dimension cachedMinimumSize;
  protected CellRendererPane currentValuePane;
  protected boolean isMinimumSizeDirty;

  /**
   * Creates a new BasicComboBoxUI object.
   */
  public BasicComboBoxUI()
  {
  }

  /**
   * Factory method to create a BasicComboBoxUI for the given {@link
   * JComponent}, which should be a {@link JComboBox}.
   *
   * @param c The {@link JComponent} a UI is being created for.
   *
   * @return A BasicComboBoxUI for the {@link JComponent}.
   */
  public static ComponentUI createUI(JComponent c)
  {
    return new BasicComboBoxUI();
  }

  /**
   * This method installs the UI for the given JComponent.
   *
   * @param c The JComponent to install a UI for.
   */
  public void installUI(JComponent c)
  {
    super.installUI(c);

    if (c instanceof JComboBox)
      {
	comboBox = (JComboBox) c;
	comboBox.setOpaque(true);
	comboBox.setLayout(createLayoutManager());
	installDefaults();
	installComponents();
	installListeners();
	installKeyboardActions();
      }
  }

  /**
   * This method uninstalls the UI.
   *
   * @param c The JComponent that is having this UI removed.
   */
  public void uninstallUI(JComponent c)
  {
    uninstallKeyboardActions();
    uninstallListeners();
    uninstallComponents();
    uninstallDefaults();
    comboBox = null;
  }

  /**
   * This method installs the defaults that are defined in  the Basic look and
   * feel for this {@link JComboBox}.
   */
  protected void installDefaults()
  {
    UIDefaults defaults = UIManager.getLookAndFeelDefaults();

    comboBox.setBackground(defaults.getColor("ComboBox.background"));
    comboBox.setFont(defaults.getFont("ComboBox.font"));
    comboBox.setForeground(defaults.getColor("ComboBox.foreground"));

    // Set default color that should be used to to render selected item
    // of the combo box.
    shadow = defaults.getColor("Button.shadow");
    darkShadow = defaults.getColor("Button.darkShadow");
    lightHighlight = defaults.getColor("Button.light");
    highlight = defaults.getColor("Button.highlight");
  }

  /**
   * This method creates and installs the listeners for this UI.
   */
  protected void installListeners()
  {
    // install combo box's listeners
    propertyChangeListener = createPropertyChangeListener();
    comboBox.addPropertyChangeListener(propertyChangeListener);

    focusListener = createFocusListener();
    comboBox.addFocusListener(focusListener);

    itemListener = createItemListener();
    comboBox.addItemListener(itemListener);

    keyListener = createKeyListener();
    comboBox.addKeyListener(keyListener);

    mouseListener = createMouseListener();
    comboBox.addMouseListener(mouseListener);

    // install listeners that listen to combo box model
    listDataListener = createListDataListener();
    comboBox.getModel().addListDataListener(listDataListener);

    configureArrowButton();
  }

  /**
   * This method uninstalls the defaults and sets any objects created during
   * install to null
   */
  protected void uninstallDefaults()
  {
    UIDefaults defaults = UIManager.getLookAndFeelDefaults();

    comboBox.setBackground(null);
    comboBox.setFont(null);
    comboBox.setForeground(null);

    shadow = null;
    darkShadow = null;
    lightHighlight = null;
    highlight = null;
  }

  /**
   * Detaches all the listeners we attached in {@link #installListeners}.
   */
  protected void uninstallListeners()
  {
    comboBox.removePropertyChangeListener(propertyChangeListener);
    propertyChangeListener = null;

    comboBox.removeFocusListener(focusListener);
    focusListener = null;

    comboBox.removeItemListener(itemListener);
    itemListener = null;

    comboBox.removeKeyListener(keyListener);
    keyListener = null;

    comboBox.removeMouseListener(mouseListener);
    mouseListener = null;

    comboBox.getModel().removeListDataListener(listDataListener);
    listDataListener = null;

    unconfigureArrowButton();
  }

  /**
   * This method creates popup that will contain list of combo box's items
   *
   * @return popup containing list of combo box's items
   */
  protected ComboPopup createPopup()
  {
    return new BasicComboPopup(comboBox);
  }

  /**
   * Creates KeyListener to listen to key events.
   *
   * @return KeyListener that listens to key events.
   */
  protected KeyListener createKeyListener()
  {
    return new KeyHandler();
  }

  /**
   * This method create MouseListener that will listen to mouse event occuring
   * in combo box.
   *
   * @return the MouseListener
   */
  private MouseListener createMouseListener()
  {
    return new MouseHandler();
  }

  /**
   * This method create FocusListener that will listen to changes in this
   * JComboBox's focus.
   *
   * @return theFocusListener
   */
  protected FocusListener createFocusListener()
  {
    return new FocusHandler();
  }

  /**
   * This method create ListDataListener to listen to ComboBox's  data model
   *
   * @return ListDataListener
   */
  protected ListDataListener createListDataListener()
  {
    return new ListDataHandler();
  }

  /**
   * This method creates ItemListener that will listen to to the changes in
   * the JComboBox's selection.
   *
   * @return the ItemListener
   */
  protected ItemListener createItemListener()
  {
    return new ItemHandler();
  }

  /**
   * This method creates PropertyChangeListener to listen to  the changes in
   * the JComboBox's bound properties.
   *
   * @return the PropertyChangeListener
   */
  protected PropertyChangeListener createPropertyChangeListener()
  {
    return new PropertyChangeHandler();
  }

  /**
   * This method returns layout manager for the combo box.
   *
   * @return layout manager for the combo box
   */
  protected LayoutManager createLayoutManager()
  {
    return new ComboBoxLayoutManager();
  }

  /**
   * This method creates component that will be responsible for rendering the
   * selected component in the combo box.
   *
   * @return render for the combo box
   */
  protected ListCellRenderer createRenderer()
  {
    return new BasicComboBoxRenderer();
  }

  /**
   * Creates component that will be responsible for displaying/editting
   * selected item in the combo box. This editor is used only when combo box
   * is editable.
   *
   * @return component that will be responsible for  displaying/editting
   *         selected item in the combo box.
   */
  protected ComboBoxEditor createEditor()
  {
    return new BasicComboBoxEditor();
  }

  /**
   * This method installs components for this JComboBox. ArrowButton, main
   * part of combo box (upper part) and  popup list of items are created and
   * configured here.
   */
  protected void installComponents()
  {
    // create and install arrow button
    arrowButton = createArrowButton();

    comboBox.add(arrowButton);

    // Set list that will be used by BasicComboBoxRender 
    // in order to determine the right colors when rendering
    listBox = new JList();

    Color background = arrowButton.getBackground();
    listBox.setBackground(background);
    listBox.setSelectionBackground(background.darker());

    Color foreground = arrowButton.getForeground();
    listBox.setForeground(foreground);
    listBox.setSelectionForeground(foreground);

    // set editor and renderer for the combo box. Editor is used
    // only if combo box becomes editable, otherwise renderer is used
    // to paint the selected item; combobox is not editable by default. 
    comboBox.setRenderer(createRenderer());

    comboBox.setEditor(createEditor());
    editor = comboBox.getEditor().getEditorComponent();

    // create drop down list of items
    popup = createPopup();

    comboBox.revalidate();
  }

  /**
   * This method uninstalls components from this JComboBox
   */
  protected void uninstallComponents()
  {
    // uninstall arrow button
    unconfigureArrowButton();
    comboBox.remove(arrowButton);
    arrowButton = null;

    listBox = null;
    popup = null;

    comboBox.setRenderer(null);

    comboBox.setEditor(null);
    editor = null;
  }

  /**
   * This method adds editor to the combo box
   */
  public void addEditor()
  {
    comboBox.add(editor);
  }

  /**
   * This method removes editor from the combo box
   */
  public void removeEditor()
  {
    comboBox.remove(editor);
  }

  /**
   * This method configures editor for this combo box.
   */
  protected void configureEditor()
  {
    // FIXME: Need to implement. Set font and add listeners.
  }

  /**
   * This method removes all the listeners for the editor.
   */
  protected void unconfigureEditor()
  {
    // FIXME: Need to implement    
  }

  /**
   * This method adds listeners to the arrow button part of the combo box.
   */
  public void configureArrowButton()
  {
    arrowButton.addMouseListener(mouseListener);
  }

  /**
   * This method removes listeners from the arrow button part of the combo
   * box.
   */
  public void unconfigureArrowButton()
  {
    arrowButton.removeMouseListener(mouseListener);
  }

  /**
   * This method create arrow button for this JComboBox. Arrow button is
   * responsible for displaying / hiding drop down list of items  when it is
   * clicked.
   *
   * @return JButton arrow button for this JComboBox.
   */
  protected JButton createArrowButton()
  {
    return new BasicArrowButton(BasicArrowButton.SOUTH);
  }

  /**
   * This method checks if popup part of the combo box is visible on the
   * screen
   *
   * @param c The JComboBox to check
   *
   * @return true if popup part of the JComboBox is visible and false
   *         otherwise.
   */
  public boolean isPopupVisible(JComboBox c)
  {
    return popup.isVisible();
  }

  /**
   * Displays/Hides JComboBox's list of items on the screen.
   *
   * @param c The combo box, for which list of items should be
   *        displayed/hidden
   * @param v true if show popup part of the jcomboBox and false to hide.
   */
  public void setPopupVisible(JComboBox c, boolean v)
  {
    if (v)
      popup.show();
    else
      popup.hide();
  }

  /**
   * JComboBox is focus traversable if it is editable and not otherwise.
   *
   * @param c combo box for which to check whether it is focus traversable
   *
   * @return true if focus tranversable and false otherwise
   */
  public boolean isFocusTraversable(JComboBox c)
  {
    if (comboBox.isEditable())
      return true;

    return false;
  }

  /**
   * Paints given menu item using specified graphics context
   *
   * @param g The graphics context used to paint this combo box
   * @param c comboBox which needs to be painted.
   */
  public void paint(Graphics g, JComponent c)
  {
    if (c instanceof JComboBox)
      {
	JComboBox cb = (JComboBox) c;

	paintBorder(g, comboBox.getBounds(), hasFocus);

	Rectangle rect = rectangleForCurrentValue();
	paintCurrentValueBackground(g, rect, hasFocus);
	paintCurrentValue(g, rect, hasFocus);
      }
  }

  private void paintBorder(Graphics g, Rectangle bounds, boolean hasFocus)
  {
    int x = 0;
    int y = 0;
    int width = bounds.width;
    int height = bounds.height;

    Color oldColor = g.getColor();

    if (! arrowButton.getModel().isPressed())
      BasicGraphicsUtils.drawEtchedRect(g, x, y, width, height, Color.gray,
                                        Color.white, Color.gray, Color.white);
    else
      {
	g.setColor(darkShadow);
	g.drawRect(x, y, width, height);
	g.setColor(shadow);
	g.drawRect(x + 1, y + 1, width - 3, height - 3);
      }
    g.setColor(oldColor);
  }

  /**
   * Returns preferred size for the given menu item.
   *
   * @param c comboBox for which to get preferred size
   *
   * @return $Dimension$ preferred size for the given combo box
   */
  public Dimension getPreferredSize(JComponent c)
  {
    // return null to indicate that combo box's layout will determin its
    // preferred size
    return null;
  }

  /**
   * This method returns the minimum size for this {@link JComboBox} for this
   * look and feel.
   *
   * @param c The {@link JComponent} to find the minimum size for.
   *
   * @return The dimensions of the minimum size.
   */
  public Dimension getMinimumSize(JComponent c)
  {
    return null;
  }

  /**
   * This method returns the maximum size for this {@link JComboBox} for this
   * look and feel.
   *
   * @param c The {@link JComponent} to find the maximum size for
   *
   * @return The dimensions of the minimum size.
   */
  public Dimension getMaximumSize(JComponent c)
  {
    return null;
  }

  public int getAccessibleChildrenCount(JComponent c)
  {
    // FIXME: Need to implement
    return 0;
  }

  public Accessible getAccessibleChild(JComponent c, int i)
  {
    // FIXME: Need to implement
    return null;
  }

  /**
   * Returns true if the specified key is a navigation key and false otherwise
   *
   * @param keyCode a key for which to check whether it is navigation key or
   *        not.
   *
   * @return true if the specified key is a navigation key and false otherwis
   */
  protected boolean isNavigationKey(int keyCode)
  {
    return false;
  }

  /**
   * This method selects next possible item relative to the current selection
   * to be next selected item in the combo box.
   */
  protected void selectNextPossibleValue()
  {
    int index = comboBox.getSelectedIndex();
    if (index != comboBox.getItemCount() - 1)
      comboBox.setSelectedIndex(index + 1);
  }

  /**
   * This method selects previous item relative to current selection to be
   * next selected item.
   */
  protected void selectPreviousPossibleValue()
  {
    int index = comboBox.getSelectedIndex();
    if (index != 0)
      comboBox.setSelectedIndex(index - 1);
  }

  /**
   * This method displays combo box popup if the popup is not currently shown
   * on the screen and hides it if it is  currently shown
   */
  protected void toggleOpenClose()
  {
    setPopupVisible(comboBox, ! isPopupVisible(comboBox));
  }

  /**
   * This method returns bounds in which comboBox's selected Item will be
   * displayed
   *
   * @return rectangle bounds in which comboBox's selected Item will be
   *         displayed
   */
  protected Rectangle rectangleForCurrentValue()
  {
    Rectangle cbBounds = comboBox.getBounds();

    // Subtract width or the arrow button and border insets	    
    Rectangle rectForCurrentValue = new Rectangle(cbBounds.x
                                                  + borderInsets.left,
                                                  cbBounds.y
                                                  + borderInsets.top,
                                                  cbBounds.width
                                                  - arrowButtonWidth
                                                  - borderInsets.left
                                                  - borderInsets.right,
                                                  cbBounds.height
                                                  - borderInsets.top
                                                  - borderInsets.bottom);

    return rectForCurrentValue;
  }

  /**
   * This method returns insets of the current border.
   *
   * @return Insets representing space between combo box and its border
   */
  protected Insets getInsets()
  {
    return new Insets(0, 0, 0, 0);
  }

  /**
   * This method paints currently selected value in the main part of the combo
   * box (part without popup).
   *
   * @param g graphics context
   * @param bounds Rectangle representing the size of the area in which
   *        selected item should be drawn
   * @param hasFocus true if combo box has focus and false otherwise
   */
  public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus)
  {
    if (! comboBox.isEditable())
      {
	Object currentValue = comboBox.getSelectedItem();
	boolean isPressed = arrowButton.getModel().isPressed();

	/* Gets the component to be drawn for the current value.
	 * If there is currently no selected item we will take an empty
	 * String as replacement.
	 */
	Component comp = comboBox.getRenderer()
	                             .getListCellRendererComponent(listBox,
	                                                           (currentValue != null ? currentValue : ""),
	                                                           -1,
	                                                           isPressed,
	                                                           hasFocus);
	if (! comboBox.isEnabled())
	      comp.setEnabled(false);

	g.translate(borderInsets.left, borderInsets.top);
	    comp.setBounds(0, 0, bounds.width, bounds.height);
	    comp.paint(g);
	    g.translate(-borderInsets.left, -borderInsets.top);
	    
	comboBox.revalidate();
      }
    else
      comboBox.getEditor().setItem(comboBox.getSelectedItem());
  }

  /**
   * This method paints background of part of the combo box, where currently
   * selected value is displayed. If the combo box has focus this method
   * should also paint focus rectangle around the combo box.
   *
   * @param g graphics context
   * @param bounds Rectangle representing the size of the largest item  in the
   *        comboBox
   * @param hasFocus true if combo box has fox and false otherwise
   */
  public void paintCurrentValueBackground(Graphics g, Rectangle bounds,
                                          boolean hasFocus)
  {
    // background is painted by renderer, so it seems that nothing
    // should be done here.
  }

  /**
   * Returns default size for the combo box that doesn't contain any elements
   * in it
   *
   * @return Default size of the combo box with no elements in it.
   */
  protected Dimension getDefaultSize()
  {
    return new Dimension(6, 17);
  }

  /**
   * Returns size of the largest item in the combo box. This size will be the
   * size of the combo box, not including the arrowButton.
   *
   * @return dimensions of the largest item in the combo box.
   */
  protected Dimension getLargestItemSize()
  {
    ComboBoxModel model = comboBox.getModel();
    int numItems = model.getSize();

    // if combo box doesn't have any items then simply
    // return its default size
    if (numItems == 0)
      {
	largestItemSize = getDefaultSize();
	return largestItemSize;
      }

    Dimension size = new Dimension(0, 0);

    // ComboBox's display size should be equal to the 
    // size of the largest item in the combo box. 
    ListCellRenderer renderer = comboBox.getRenderer();

    for (int i = 0; i < numItems; i++)
      {
	Object item = model.getElementAt(i);
	String s = item.toString();
	Component comp = renderer.getListCellRendererComponent(listBox, item,
	                                                       -1, false, false);

	if (comp.getPreferredSize().getWidth() > size.getWidth())
	  size = comp.getPreferredSize();
      }

    largestItemSize = size;
    return largestItemSize;
  }

  /**
   * This method installs the keyboard actions for the JComboBox as specified
   * by the look and feel.
   */
  protected void installKeyboardActions()
  {
    // FIXME: Need to implement.
  }

  /**
   * This method uninstalls the keyboard actions for the JComboBox there were
   * installed by in {@link #installListeners}.
   */
  protected void uninstallKeyboardActions()
  {
    // FIXME: Need to implement.
  }

  /**
   * This class is Layout Manager for this combo box.
   */
  public class ComboBoxLayoutManager extends Object implements LayoutManager
  {
    /**
     * Creates a new ComboBoxLayoutManager object.
     */
    public ComboBoxLayoutManager()
    {
    }

    public void addLayoutComponent(String name, Component comp)
    {
      // Do nothing
    }

    public void removeLayoutComponent(Component comp)
    {
      // Do nothing
    }

    /**
     * Returns preferred layout size of the JComboBox.
     *
     * @param parent Container for which preferred size should be calculated
     *
     * @return preferred size for the given container
     */
    public Dimension preferredLayoutSize(Container parent)
    {
      Dimension d = new Dimension(0, 0);

      if (largestItemSize == null)
	largestItemSize = getLargestItemSize();

      // add size for the area that will display selected item
      d.width += largestItemSize.getWidth();
      d.height += largestItemSize.getHeight();

      // add size of the arrow button
      d.width += arrowButtonWidth;

      // add width and height of the border
      d.width += borderInsets.left + borderInsets.right;
      d.height += borderInsets.left + borderInsets.right;

      // Add combo box's insets 	
      Insets insets = parent.getInsets();
      d.width += insets.left + insets.right;
      d.width += insets.left + insets.right;

      return d;
    }

    public Dimension minimumLayoutSize(Container parent)
    {
      return preferredLayoutSize(parent);
    }

    /**
     * This method layouts out the components in the container.  It puts arrow
     * button right end part of the comboBox. If the comboBox is editable
     * then editor is placed to the left of arrow  button, starting from the
     * beginning.
     *
     * @param parent Container that should be layed out.
     */
    public void layoutContainer(Container parent)
    {
      // Position editor component to the left of arrow button if combo box is 
      // editable
      int editorWidth = comboBox.getBounds().width - arrowButtonWidth - 2;

      if (comboBox.isEditable())
	editor.setBounds(borderInsets.left, borderInsets.top, editorWidth,
	                 comboBox.getBounds().height - borderInsets.left
	                 - borderInsets.top);

      arrowButton.setBounds(editorWidth, 2, arrowButtonWidth,
                            comboBox.getBounds().height - 4);
      comboBox.revalidate();
    }
  }

  /**
   * This class handles focus changes occuring in the combo box. This class is
   * responsible for repainting combo box whenever focus is gained or lost
   * and also for hiding popup list of items whenever combo box loses its
   * focus.
   */
  public class FocusHandler extends Object implements FocusListener
  {
    /**
     * Creates a new FocusHandler object.
     */
    public FocusHandler()
    {
    }

    /**
     * This mehtod is invoked when combo box gains focus. It repaints main
     * part of combo box  accordingally.
     *
     * @param e the FocusEvent
     */
    public void focusGained(FocusEvent e)
    {
      hasFocus = true;
      comboBox.repaint();
    }

    /**
     * This method is invoked when combo box loses focus It repaint main part
     * of combo box accordingally and  hides popup list of items.
     *
     * @param e the FocusEvent
     */
    public void focusLost(FocusEvent e)
    {
      hasFocus = false;
      comboBox.repaint();
      popup.hide();
    }
  }

  /**
   * This class handles ItemEvent fired by the JComboBox when its selected
   * item changes.
   */
  public class ItemHandler extends Object implements ItemListener
  {
    /**
     * Creates a new ItemHandler object.
     */
    public ItemHandler()
    {
    }

    /**
     * This method is invoked when selected item becomes deselected or when
     * new item becomes selected.
     *
     * @param e the ItemEvent representing item's state change.
     */
    public void itemStateChanged(ItemEvent e)
    {
      comboBox.repaint();
    }
  }

  /**
   * KeyHandler handles key events occuring while JComboBox has focus.
   */
  public class KeyHandler extends KeyAdapter
  {
    public KeyHandler()
    {
    }

    /*
     * This method is invoked whenever key is pressed while JComboBox is in
     * focus.
     */
    public void keyPressed(KeyEvent e)
    {
      // FIXME: This method calls JComboBox.selectWithKeyChar if the key that was 
      // pressed is not a navigation key. 
    }
  }

  /**
   * This class handles to the changes occuring in the JComboBox's data model
   */
  public class ListDataHandler extends Object implements ListDataListener
  {
    /**
     * Creates a new ListDataHandler object.
     */
    public ListDataHandler()
    {
    }

    /**
     * This method is invoked content's of JComboBox's data model  are changed
     *
     * @param e ListDataEvent describing the change.
     */
    public void contentsChanged(ListDataEvent e)
    {
      // if the item is selected or deselected
    }

    /**
     * This method is invoked when items were added to the JComboBox's data
     * model.
     *
     * @param e ListDataEvent describing the change.
     */
    public void intervalAdded(ListDataEvent e)
    {
      // must determine if the size of the combo box should change
      int start = e.getIndex0();
      int end = e.getIndex1();

      ComboBoxModel model = comboBox.getModel();
      ListCellRenderer renderer = comboBox.getRenderer();

      if (largestItemSize == null)
	largestItemSize = new Dimension(0, 0);

      for (int i = start - 1; i < end; i++)
        {
	  Object item = model.getElementAt(i);
	  Component comp = renderer.getListCellRendererComponent(new JList(),
	                                                         item, -1,
	                                                         false, false);
	  if (comp.getPreferredSize().getWidth() > largestItemSize.getWidth())
	    largestItemSize = comp.getPreferredSize();
        }
    }

    /**
     * This method is invoked when items were removed from the JComboBox's
     * data model.
     *
     * @param e ListDataEvent describing the change.
     */
    public void intervalRemoved(ListDataEvent e)
    {
      // recalculate display size of the JComboBox.
      largestItemSize = getLargestItemSize();
      comboBox.repaint();
    }
  }

  /**
   * This class handles PropertyChangeEvents fired by JComboBox.
   */
  public class PropertyChangeHandler extends Object
    implements PropertyChangeListener
  {
    public PropertyChangeHandler()
    {
    }

    /**
     * This method is invoked whenever bound property of JComboBox changes.
     */
    public void propertyChange(PropertyChangeEvent e)
    {
      if (e.getPropertyName().equals("enabled"))
        {
	  arrowButton.setEnabled(comboBox.isEnabled());

	  if (comboBox.isEditable())
	    comboBox.getEditor().getEditorComponent().setEnabled(comboBox
	                                                         .isEnabled());
        }
      else if (e.getPropertyName().equals("editable"))
        {
	  if (comboBox.isEditable())
	    {
	      configureEditor();
	      addEditor();
	    }
	  else
	    {
	      unconfigureEditor();
	      removeEditor();
	    }

	  comboBox.revalidate();
	  comboBox.repaint();
        }
      else if (e.getPropertyName().equals("dataModel"))
        {
	  // remove ListDataListener from old model and add it to new model
	  ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue();
	  if (oldModel != null)
	    oldModel.removeListDataListener(listDataListener);

	  if ((ComboBoxModel) e.getNewValue() != null)
	    comboBox.getModel().addListDataListener(listDataListener);
        }

      // FIXME: Need to handle changes in other bound properties.	
    }
  }

  /**
   * MouseHandler listens to mouse events occuring in the combo box. This
   * class is responsible for repainting this JComboBox whenever the mouse is
   * being pressed or released over it.
   */
  private class MouseHandler extends MouseAdapter
  {
    /**
     * This method is invoked when mouse is pressed over the combo box. It
     * repaints the combo box accordinglly
     *
     * @param e the MouseEvent
     */
    public void mousePressed(MouseEvent e)
    {
      if (comboBox.isEnabled())
        {
	  if (e.getSource() instanceof JComboBox)
	    {
	      arrowButton.getModel().setPressed(true);
	      arrowButton.getModel().setArmed(true);
	    }

	  comboBox.repaint();

	  if (e.getSource() instanceof BasicArrowButton)
	    toggleOpenClose();
        }
    }

    /**
     * This method is invoked when mouse is released over the combo box. It
     * repaints the combo box accordinglly
     *
     * @param e the MouseEvent
     */
    public void mouseReleased(MouseEvent e)
    {
      if (comboBox.isEnabled())
        {
	  if (e.getSource() instanceof JComboBox)
	    {
	      arrowButton.getModel().setPressed(false);
	      arrowButton.getModel().setArmed(false);
	    }

	  comboBox.repaint();
        }
    }
  }
}
