/* class JComboBox
 *
 * Copyright (C) 2001-2003  R M Pitman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * Modified Jul 15, 2003 by Tadpole Computer, Inc.
 * Modifications Copyright 2003 by Tadpole Computer, Inc.
 *
 * Modifications are hereby licensed to all parties at no charge under
 * the same terms as the original.
 *
 * Added the JComboBox(ComboBoxModel) constructor.
 * Added the removeAllItems method.
 * Added a stubbed out setEditable method (it would be nice to have editable
 * ComboBoxes, too.
 * Fixed color handling to inherit colors from our parent.
 */

package charvax.swing;

import charva.awt.*;
import charva.awt.event.*;
import charvax.swing.border.LineBorder;
import charvax.swing.event.ListSelectionEvent;
import charvax.swing.event.ListSelectionListener;

import java.util.Enumeration;
import java.util.Vector;

/**
 * The JComboBox component allows the user to select an item from a pop-up
 * list of choices.<p>
 * When the combobox is in a non-popped-up state, it looks like a JButton
 * with a "diamond" character on the right. To make the popup menu appear,
 * the user positions the cursor over the combobox and presses ENTER.<p>
 * <p/>
 * When the user selects an item in the popup menu (by positioning the
 * cursor on it and pressing ENTER), the pop-up menu disappears and
 * only the selected item is shown. At the same time the JComboBox
 * posts an ActionEvent onto the system event queue. The "action command"
 * encapsulated in the ActionEvent is the list item that was selected.<p>
 * Note that this class provides an "Uneditable" JComboBox only.
 */
public class JComboBox
        extends JComponent
        implements ListSelectionListener, // (an implementation side-effect)
        ItemSelectable {

    /**
     * Creates an empty JComboBox.
     */
    public JComboBox() {
        DefaultComboBoxModel model = new DefaultComboBoxModel();
        setModel(model);
    }

    /**
     * Creates a JComboBox with the given model.
     */
    public JComboBox(ComboBoxModel model) {
        setModel(model);
    }

    /**
     * Creates a JComboBox that contains the elements in the specified
     * array.
     *
     * @param items_ the array of items to display in the combobox
     */
    public JComboBox(Object[] items_) {
        DefaultComboBoxModel model = new DefaultComboBoxModel(items_);
        setModel(model);
    }

    /**
     * Creates a JComboBox that contains the elements in the specified
     * Vector.
     *
     * @param items_ the vector of items to display in the combobox
     */
    public JComboBox(Vector items_) {
        DefaultComboBoxModel model = new DefaultComboBoxModel(items_);
        setModel(model);
    }

    /**
     * Sets the data model that the JComboBox uses to obtain the list of
     * items.
     */
    public void setModel(ComboBoxModel model_) {
        _model = model_;

        for (int i = 0; i < _model.getSize(); i++) {
            String str = _model.getElementAt(i).toString();
            if (str.length() > _columns)
                _columns = str.length();
        }

        if (super.isDisplayed())
            super.repaint();
    }

    /**
     * Add the specified item into the list of items.<p>
     * <p/>
     * Note that this method works only if the data model is
     * a MutableComboBoxModel (by default, it is).
     */
    public void addItem(Object item_) {
        ((MutableComboBoxModel) _model).addElement(item_);
        if (item_.toString().length() > _columns)
            _columns = item_.toString().length();
    }

    /**
     * Insert the specified item at the specified index.<p>
     * <p/>
     * Note that this method works only if the data model is
     * a MutableComboBoxModel (by default, it is).
     */
    public void insertItemAt(Object item_, int index_) {
        ((MutableComboBoxModel) _model).insertElementAt(item_, index_);
        if (item_.toString().length() > _columns)
            _columns = item_.toString().length();
    }

    /**
     * Remove the item at the specified index. <p>
     * <p/>
     * Note that this method works only if the data model is
     * a MutableComboBoxModel (by default, it is).
     */
    public void removeItemAt(int index_) {
        ((MutableComboBoxModel) _model).removeElementAt(index_);
    }

    /**
     * Removes the specified item from the combobox's list. If the
     * item was not in the list, the list is not changed.<p>
     * <p/>
     * Note that this method works only if the data model is
     * a MutableComboBoxModel (by default, it is).
     */
    public void removeItem(Object item_) {
        ((MutableComboBoxModel) _model).removeElement(item_);
    }

    /**
     * Removes all items.
     * <p/>
     * Note that this method works only if the data model is
     * a MutableComboBoxModel (by default, it is).
     */
    public void removeAllItems() {
        while (_model.getSize() > 0) {
            removeItemAt(0);
        }
    }

    /**
     * Returns the selected item.
     */
    public Object getSelectedItem() {
        return _model.getSelectedItem();
    }

    /**
     * Sets the selected item in the JComboBox by specifying the
     * object in the list.
     */
    public void setSelectedItem(Object obj_) {
        _model.setSelectedItem(obj_);
    }

    /**
     * Sets the selected item in the JComboBox by specifying
     * the index in the list.
     */
    public void setSelectedIndex(int index_) {
        Object selected = _model.getElementAt(index_);
        _model.setSelectedItem(selected);
    }

    /**
     * Sets the maximum number of rows that the JComboBox displays.
     */
    public void setMaximumRowCount(int rows_) {
        _maxRows = rows_;
    }

    /**
     * Returns width (including the diamond symbol).
     */
    public int getWidth() {
        Insets insets = super.getInsets();
        return _columns + insets.left + insets.right + 2;
    }

    public int getHeight() {
        Insets insets = super.getInsets();
        return 1 + insets.top + insets.bottom;
    }

    public Dimension getSize() {
        return new Dimension(this.getWidth(), this.getHeight());
    }

    public Dimension minimumSize() {
        return this.getSize();
    }

    /**
     * Draw the selected item, surrounded by a box.
     */
    public void draw() {

        // Draw the border if it exists
        super.draw();

        /* Get the absolute origin of this component.
         */
        Point origin = getLocationOnScreen();
        Insets insets = super.getInsets();
        origin.translate(insets.left, insets.right);

        Toolkit term = Toolkit.getDefaultToolkit();

        int colorpair = getCursesColor();
        term.setCursor(origin);
        String selectedItem = (String) _model.getSelectedItem();
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < _columns + 1; i++)
            buf.append(' ');
        if (selectedItem != null) {
            buf.replace(1, selectedItem.length() + 1, selectedItem);
        }

        int attribute =
                super.isEnabled() ? Toolkit.A_REVERSE : Toolkit.A_NORMAL;

        term.addString(buf.toString(), attribute, colorpair);
        term.setCursor(origin.addOffset(_columns + 1, 0));
        term.addChar(Toolkit.ACS_DIAMOND, attribute, colorpair);
    }

    /**
     * Register an ItemListener object for this component.
     */
    public void addItemListener(ItemListener il_) {
        if (_itemListeners == null)
            _itemListeners = new Vector();
        _itemListeners.add(il_);
    }

    public void removeItemListener(ItemListener listener_) {
        if (_itemListeners == null)
            return;
        _itemListeners.remove(listener_);
    }

    /**
     * Invoke all the ItemListener callbacks that may have been registered
     * for this component.
     */
    protected void fireItemStateChanged(ItemEvent ie_) {
        if (_itemListeners != null) {
            for (Enumeration e = _itemListeners.elements();
                 e.hasMoreElements();) {

                ItemListener il = (ItemListener) e.nextElement();
                il.itemStateChanged(ie_);
            }
        }
    }

    /**
     * Overrides method in superclass.
     */
    public void processEvent(AWTEvent evt_) {
        super.processEvent(evt_);

        if (evt_ instanceof ActionEvent) {
            fireActionEvent((ActionEvent) evt_);
            ItemEvent item_event =
                    new ItemEvent(this, this, ItemEvent.SELECTED);
            fireItemStateChanged(item_event);
        }
    }

    /**
     * Register an ActionListener object for this component.
     */
    public void addActionListener(ActionListener al_) {
        if (_actionListeners == null)
            _actionListeners = new Vector();
        _actionListeners.add(al_);
    }

    /**
     * Invoke all the ActionListener callbacks that may have been registered
     * for this component.
     */
    protected void fireActionEvent(ActionEvent ae_) {
        if (_actionListeners != null) {
            for (Enumeration e = _actionListeners.elements();
                 e.hasMoreElements();) {

                ActionListener al = (ActionListener) e.nextElement();
                al.actionPerformed(ae_);
            }
        }
    }

    /**
     * Process KeyEvents that have been generated by this JComboBox component.
     */
    public void processKeyEvent(KeyEvent ke_) {

        int key = ke_.getKeyCode();
        if (key == '\t') {
            getParent().nextFocus();
            return;
        } else if (key == KeyEvent.VK_BACK_TAB) {
            getParent().previousFocus();
            return;
        } else if (key == KeyEvent.VK_ENTER) {
            _activate();
        }
    }

    /**
     * Process a MouseEvent that was generated by clicking the mouse
     * on this JComboBox.
     */
    public void processMouseEvent(MouseEvent e_) {
        // Request focus if this is a MOUSE_PRESSED
        super.processMouseEvent(e_);

        if (e_.getButton() == MouseEvent.BUTTON1 &&
                e_.getModifiers() == MouseEvent.MOUSE_CLICKED &&
                this.isFocusTraversable()) {

            _activate();
        }
    }

    /**
     * Make the combobox editable.
     */
    public void setEditable(boolean editable) {
        // implement me
    }

    /**
     * Implements the ListSelectionListener interface.
     */
    public void valueChanged(ListSelectionEvent e_) {
        _popup.hide();

        /* Put an ActionEvent onto the system event queue.
         */
        EventQueue evtqueue =
                Toolkit.getDefaultToolkit().getSystemEventQueue();

        Object selectedItem = _popup.getSelectedItem();
        if (selectedItem != null)
            _model.setSelectedItem(selectedItem);

        evtqueue.postEvent(new ActionEvent(this, _model.getSelectedItem().toString()));
    }

    public void requestFocus() {
        /* Generate the FOCUS_GAINED event.
         */
        super.requestFocus();

        /* Get the absolute origin of this component
         */
        Point origin = getLocationOnScreen();
        Insets insets = super.getInsets();
        Toolkit.getDefaultToolkit().setCursor(origin.addOffset(insets.left, insets.top));
    }

    /**
     * Returns a String representation of this component.
     */
    public String toString() {
        return "JComboBox location=" + getLocation() +
                " selectedItem=\"" + getSelectedItem() + "\"";
    }

    /**
     * Output a description of this component to stderr.
     */
    public void debug(int level_) {
        for (int i = 0; i < level_; i++)
            System.err.print("    ");
        System.err.println(toString());
    }

    private void _activate() {
        _popup = this.new Popup(this, _model);
        _popup.setMaximumRowCount(_maxRows);

        /* Get the absolute origin of this component.
         */
        Point origin = getLocationOnScreen();
        _popup.setLocation(origin);
        _popup.show();
    }

    //====================================================================
    // INSTANCE VARIABLES

    private ComboBoxModel _model;

    private int _columns = 3;	// initial width

    /**
     * The default value of 0 indicates that there is no limit on the
     * number of rows to display in the popup menu.
     */
    private int _maxRows = 3;

    /**
     * A list of ActionListeners registered for this component.
     */
    protected Vector _actionListeners = null;

    /**
     * A list of ItemListeners registered for this component.
     */
    protected Vector _itemListeners = null;

    /**
     * This is a non-static inner class that implements the popup
     * window for the JComboBox component.
     */
    private class Popup
            extends Frame {
        Popup(JComboBox parent_, ComboBoxModel model_) {

            _list = new JList();

            setBackground(parent_.getBackground());
            setForeground(parent_.getForeground());

            _scrollpane = new JScrollPane(_list);
            _scrollpane.setViewportBorder(new LineBorder(getForeground()));

            // ComboBoxModel is a subclass of ListModel, so this works.
            _list.setModel(model_);
            _list.setColumns(_columns);
            Object selected = model_.getSelectedItem();
            int selectedIndex = 0;
            for (int i = 0; i < model_.getSize(); i++) {
                Object obj = model_.getElementAt(i);

                if (selected == obj)
                    selectedIndex = i;

                String str = obj.toString();
            }
            _list.setSelectedIndex(selectedIndex);

            /* Ensure we use the add() method inherited from Container
             * rather than the add() method in the outer class.
             */
            super.add(_scrollpane);
            _list.addListSelectionListener(parent_);
            _list.ensureIndexIsVisible(selectedIndex);
        }

        Object getSelectedItem() {
            /* If the user presses ENTER on the list entry that is
             * already selected, it becomes deselected in the JList;
             * so that _list.getselectedValue() returns null. The
             * caller must take this into account and ignore the value.
             */
            return _list.getSelectedValue();
        }

        /**
         * Set the maximum number of rows to be displayed
         */
        void setMaximumRowCount(int rows_) {
            _list.setVisibleRowCount(rows_);

            pack();
        }

        private JList _list;
        private JScrollPane _scrollpane;
    }
    // end of nonstatic inner class Popup.

    private JComboBox.Popup _popup;
}
