package edu.oregonstate.cartography.gui;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serializable;
import javax.swing.JComboBox;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

/**
 * A toggle button that shows a popup menu while being selected.
 * http://explodingpixels.wordpress.com/2008/11/10/prevent-popup-menu-dismissal/
 */
public class MenuToggleButton extends JToggleButton implements Serializable{

    private JPopupMenu popupMenu = null;
    private boolean shouldHandlePopupWillBecomeInvisible = true;

    public MenuToggleButton() {

        // install a special client property on the button to prevent it from
        // closing of the popup when the down arrow is pressed.
        JComboBox box = new JComboBox();
        Object preventHide = box.getClientProperty("doNotCancelPopup");
        putClientProperty("doNotCancelPopup", preventHide);
    }

    private MouseListener createButtonMouseListener() {
        return new MouseAdapter() {

            @Override
            public void mousePressed(MouseEvent e) {
                // if the popup menu is currently showing, then hide it.
                // else if the popup menu is not showing, then show it.
                if (popupMenu.isShowing()) {
                    hidePopupMenu();
                } else if (isEnabled()) {
                    showPopupMenu();
                }
            }
        };
    }

    private PopupMenuListener createPopupMenuListener() {
        return new PopupMenuListener() {

            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                // no implementation.
                }

            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                // handle this event if so indicated. the only time we don't handle
                // this event is when the button itself is pressed, the press action
                // toggles the button selected state for us. this case handles when
                // the button has been toggled, but the user clicks outside the
                // button in order to dismiss the menu.
                if (shouldHandlePopupWillBecomeInvisible) {
                    setSelected(false);
                }
            }

            public void popupMenuCanceled(PopupMenuEvent e) {
                // the popup menu has been canceled externally (either by
                // pressing escape or clicking off of the popup menu). update
                // the button's state to reflect the menu dismissal.
                setSelected(false);
            }
        };
    }

    private void hidePopupMenu() {
        shouldHandlePopupWillBecomeInvisible = false;
        popupMenu.setVisible(false);
        shouldHandlePopupWillBecomeInvisible = true;
    }

    private void showPopupMenu() {
        // show the menu below the button, and slightly to the right.
        popupMenu.show(this, 5, getHeight());
    }

    public JPopupMenu getPopupMenu() {
        return popupMenu;
    }

    public void setPopupMenu(JPopupMenu popupMenu) {
        
        // bad things happen if the menu is set more than once!
        if (this.popupMenu != null) {
            throw new IllegalStateException("menu already set");
        }
        this.popupMenu = popupMenu;

        // add a popup menu listener to update the button's selection state
        // when the menu is being dismissed.
        this.popupMenu.addPopupMenuListener(createPopupMenuListener());

        // install a mouse listener on the button to hide and show the popup
        // menu as appropriate.
        addMouseListener(createButtonMouseListener());
    }

}
