com.anrisoftware.prefdialog.miscswing.components.menu.PopupMenuComponent.java Source code

Java tutorial

Introduction

Here is the source code for com.anrisoftware.prefdialog.miscswing.components.menu.PopupMenuComponent.java

Source

/*
 * Copyright 2013-2016 Erwin Mller <erwin.mueller@deventm.org>
 *
 * This file is part of prefdialog-misc-swing.
 *
 * prefdialog-misc-swing 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 3 of the License, or (at your
 * option) any later version.
 *
 * prefdialog-misc-swing 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 Lesser General Public License
 * along with prefdialog-misc-swing. If not, see <http://www.gnu.org/licenses/>.
 */
package com.anrisoftware.prefdialog.miscswing.components.menu;

import static org.apache.commons.lang3.Validate.notNull;

import java.awt.Component;
import java.awt.Point;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serializable;

import javax.swing.JComponent;
import javax.swing.JPopupMenu;

import org.apache.commons.lang3.builder.ToStringBuilder;

/**
 * Decorates a component to open the pop-up menu if the user clicks on it.
 * 
 * @author Erwin Mueller, erwin.mueller@deventm.org
 * @since 1.0
 */
@SuppressWarnings("serial")
public class PopupMenuComponent implements Serializable {

    /**
     * Filters mouse events that are allowing to trigger the pop-up menu.
     * 
     * @author Erwin Mueller, erwin.mueller@deventm.org
     * @since 1.0
     */
    public static interface MouseFilter extends Serializable {

        /**
         * Test if the mouse event should trigger the pop-up menu.
         * 
         * @param e
         *            the {@link MouseEvent}.
         * 
         * @return {@code true} to trigger the pop-up menu.
         */
        boolean allow(MouseEvent e);

    }

    /**
     * Returns the position for the pop-up menu.
     * 
     * @author Erwin Mueller, erwin.mueller@deventm.org
     * @since 1.0
     */
    public static interface PopupMenuPosition extends Serializable {

        /**
         * Returns the position for the pop-up menu relative to the specified
         * component.
         * 
         * @param c
         *            the {@link Component}.
         * 
         * @param mouseLocation
         *            the {@link Point} location of the mouse.
         * 
         * @return the {@link Point} position of the pop-up menu.
         */
        Point getPosition(Component c, Point mouseLocation);

    }

    /**
     * The default mouse filter that allows all mouse events to trigger the
     * pop-up menu.
     */
    public static final MouseFilter ALLOW_ALL_MOUSE_FILTER = new MouseFilter() {

        @Override
        public boolean allow(MouseEvent e) {
            return true;
        }
    };

    /**
     * The default popup menu position is direct under the component.
     */
    public static final PopupMenuPosition DIRECT_UNDER_POSITION = new PopupMenuPosition() {

        @Override
        public Point getPosition(Component c, Point mouseLocation) {
            return new Point(-1, c.getHeight());
        }

    };

    /**
     * @see #decorate(JComponent, JPopupMenu)
     */
    public static PopupMenuComponent createPopup(JComponent component, JPopupMenu menu) {
        return decorate(component, menu);
    }

    /**
     * Decorates the specified component to open the pop-up menu if the user
     * clicks on it.
     * 
     * @param component
     *            the {@link JComponent}.
     * 
     * @param menu
     *            the {@link JPopupMenu}.
     * 
     * @return the {@link PopupMenuComponent}.
     */
    public static PopupMenuComponent decorate(JComponent component, JPopupMenu menu) {
        return new PopupMenuComponent(component, menu);
    }

    private transient FocusListener menuFocusListener;

    private transient MouseListener componentMouseListener;

    private JComponent component;

    private JPopupMenu menu;

    private boolean alreadyShowingPopup;

    private boolean showPopup;

    private MouseFilter mouseFilter;

    private PopupMenuPosition popupMenuPosition;

    /**
     * @see #decorate(JComponent, JPopupMenu)
     */
    PopupMenuComponent(JComponent component, JPopupMenu menu) {
        this.showPopup = true;
        this.alreadyShowingPopup = false;
        this.component = component;
        this.menu = menu;
        this.mouseFilter = ALLOW_ALL_MOUSE_FILTER;
        this.popupMenuPosition = DIRECT_UNDER_POSITION;
        resolveObject();
        setComponent(component);
        setPopupMenu(menu);
    }

    private Object resolveObject() {
        if (menuFocusListener == null) {
            createMenuFocusListener();
        }
        if (componentMouseListener == null) {
            createComponentMouseListener();
        }
        return this;
    }

    private void createComponentMouseListener() {
        componentMouseListener = new MouseAdapter() {

            @Override
            public void mousePressed(MouseEvent e) {
                if (!mouseFilter.allow(e)) {
                    return;
                }
                if (alreadyShowingPopup) {
                    showPopup = false;
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                if (!mouseFilter.allow(e)) {
                    return;
                }
                if (showPopup) {
                    showPopup(e.getPoint());
                } else {
                    showPopup = true;
                }
            }

        };
    }

    private void createMenuFocusListener() {
        menuFocusListener = new FocusListener() {

            @Override
            public void focusLost(FocusEvent e) {
                alreadyShowingPopup = false;
            }

            @Override
            public void focusGained(FocusEvent e) {
                alreadyShowingPopup = true;
            }
        };
    }

    private void showPopup(Point mouseLocation) {
        Component c = component;
        Point position = popupMenuPosition.getPosition(c, mouseLocation);
        menu.show(c, position.x, position.y);
        menu.requestFocus();
    }

    /**
     * Sets the component for which the pop-menu is shown.
     * 
     * @param component
     *            the {@link JComponent}.
     * 
     * @throws NullPointerException
     *             if the specified component is {@code null}.
     */
    public void setComponent(JComponent component) {
        notNull(component);
        JComponent oldValue = this.component;
        this.component = component;
        if (oldValue != null) {
            oldValue.removeMouseListener(componentMouseListener);
        }
        component.addMouseListener(componentMouseListener);
    }

    /**
     * Returns the component for which the pop-menu is shown.
     * 
     * @return the {@link JComponent}.
     */
    public JComponent getComponent() {
        return component;
    }

    /**
     * Sets the pop-up menu.
     * 
     * @param menu
     *            the {@link JPopupMenu}.
     * 
     * @throws NullPointerException
     *             if the specified menu is {@code null}.
     */
    public void setPopupMenu(JPopupMenu menu) {
        notNull(component);
        JPopupMenu oldValue = this.menu;
        this.menu = menu;
        if (oldValue != null) {
            oldValue.removeFocusListener(menuFocusListener);
        }
        menu.addFocusListener(menuFocusListener);
    }

    /**
     * Returns the pop-up menu.
     * 
     * @return the {@link JPopupMenu}.
     */
    public JPopupMenu getMenu() {
        return menu;
    }

    /**
     * Sets the mouse filter to decide if the pop-up menu should be triggered.
     * 
     * @param filter
     *            the {@link MouseFilter}.
     * 
     * @throws NullPointerException
     *             if the specified filter is {@code null}.
     */
    public void setMouseFilter(MouseFilter filter) {
        notNull(mouseFilter);
        this.mouseFilter = filter;
    }

    /**
     * Sets to calculate the position of the pop-up menu.
     * 
     * @param position
     *            the {@link PopupMenuPosition}.
     * 
     * @throws NullPointerException
     *             if the specified position is {@code null}.
     */
    public void setPopupMenuPosition(PopupMenuPosition position) {
        notNull(position);
        this.popupMenuPosition = position;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).append("component", component).append("menu", menu).toString();
    }
}