/*
 * This file is part of muCommander, http://www.mucommander.com
 * Copyright (C) 2002-2012 Maxence Bernard
 *
 * muCommander 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 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.ui.combobox;

import com.mucommander.ui.dialog.FocusDialog;

import javax.swing.*;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Vector;
import java.util.WeakHashMap;


/**
 * SaneComboBox is a JComboBox which does not have the awful default JComboBox behavior of firing ActionEvents
 * when navigating with the arrow keys between choices of the popup menu.
 * This page describes the problem in details: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4199622
 *
 * <p>Also when using {@link ComboBoxListener}, action events that are normally triggered by JComboBox
 * when the add/insert/remove item methods are called are filtered out, only actual selection changes performed
 * by the user are fired.
 *
 * @author Maxence Bernard
 */
public class SaneComboBox<E> extends JComboBox<E> {

    private WeakHashMap<ComboBoxListener, Object> listeners = new WeakHashMap<>();
    private boolean ignoreActionEvent;


    public SaneComboBox() {
        super();
        init();
    }

    public SaneComboBox(ComboBoxModel<E> comboBoxModel) {
        super(comboBoxModel);
        init();
    }

    public SaneComboBox(E[] items) {
        super(items);
        init();
    }

    public SaneComboBox(Vector<E> items) {
        super(items);
        init();
    }


    private void init() {
        // Prevent up/down keys from firing ActionEvents
        // for Java 1.3
        putClientProperty("JComboBox.lightweightKeyboardNavigation","Lightweight");
// Commented as it causes rendering issues under Mac OS X Leopard (does not render like a native combo box)
//        // for Java 1.4 and up
//        putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);

        // Listen to combo box action events, these are fired each time an item is selected when the popup menu
        // is visible, either by pressing 'Enter' on an item or by clicking on it.
        addActionListener(new AbstractAction() {
            public void actionPerformed(ActionEvent actionEvent) {
                // Filter out action events triggered by the add/insert/remove item methods
                if(!ignoreActionEvent)
                    fireComboBoxSelectionChanged();
            }
        });
    }


    //////////////////////////////////////
    // ComboBoxListener support methods //
    //////////////////////////////////////

    /**
     * Adds the specified ComboBoxListener to the list of registered listeners.
     *
     * <p>Listeners are stored as weak references so {@link #removeComboBoxListener(ComboBoxListener)}
     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.
     *
     * @param listener the ComboBoxListener to add to the list of registered listeners.
     */
    public void addComboBoxListener(ComboBoxListener listener) {
        listeners.put(listener, null);
    }

    /**
     * Removes the specified ComboBoxListener from the list of registered listeners.
     *
     * @param listener the ComboBoxListener to remove from the list of registered listeners.
     */
    public void removeComboBoxListener(ComboBoxListener listener) {
        listeners.remove(listener);
    }

    /**
     * Notifies all registered ComboBoxListener instances that an item has been selected from the
     * combo box popup menu. The item may have been selected either with the 'Enter' key, or by clicking on the item.
     *
     * <p>Unlike JComboBox ActionListener behavior, calls to the add/insert/remove item methods do *not* trigger
     * a selection event.
     */
    protected void fireComboBoxSelectionChanged() {
        // Iterate on all listeners
        for(ComboBoxListener listener : listeners.keySet())
            listener.comboBoxSelectionChanged(this);
    }


    ////////////////////////
    // Overridden methods //
    ////////////////////////


    @Override
    public void addItem(E object) {
        ignoreActionEvent = true;
        super.addItem(object);
        ignoreActionEvent = false;
    }

    @Override
    public void insertItemAt(E object, int i) {
        ignoreActionEvent = true;
        super.insertItemAt(object, i);
        ignoreActionEvent = false;
    }

    @Override
    public void removeItem(Object object) {
        ignoreActionEvent = true;
        super.removeItem(object);
        ignoreActionEvent = false;
    }

    @Override
    public void removeItemAt(int i) {
        ignoreActionEvent = true;
        super.removeItemAt(i);
        ignoreActionEvent = false;
    }

    @Override
    public void removeAllItems() {
        ignoreActionEvent = true;
        super.removeAllItems();
        ignoreActionEvent = false;
    }

    @Override
    public void processKeyEvent(KeyEvent e) {
        boolean popupVisible = isPopupVisible();
        super.processKeyEvent(e);
        // Close parent FocusDialog if ESC pressed
        if (!popupVisible && e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            Container container = getParent();
            while (container != null) {
                if (container instanceof FocusDialog) {
                    ((FocusDialog) container).dispose();
                    break;
                }
                container = container.getParent();
            }
        }
    }

}
