001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.Component;
005import java.awt.Point;
006import java.awt.event.FocusEvent;
007import java.awt.event.FocusListener;
008import java.awt.event.MouseAdapter;
009import java.awt.event.MouseEvent;
010
011import javax.swing.JList;
012import javax.swing.JPopupMenu;
013import javax.swing.JTable;
014import javax.swing.JTree;
015import javax.swing.SwingUtilities;
016import javax.swing.tree.TreePath;
017
018/**
019 * Utility class that helps to display popup menus on mouse events.
020 * @since 2688
021 */
022public class PopupMenuLauncher extends MouseAdapter {
023    protected JPopupMenu menu;
024    private final boolean checkEnabled;
025
026    /**
027     * Creates a new {@link PopupMenuLauncher} with no defined menu.
028     * It is then needed to override the {@link #launch} method.
029     * @see #launch(MouseEvent)
030     */
031    public PopupMenuLauncher() {
032        this(null);
033    }
034
035    /**
036     * Creates a new {@link PopupMenuLauncher} with the given menu.
037     * @param menu The popup menu to display
038     */
039    public PopupMenuLauncher(JPopupMenu menu) {
040        this(menu, false);
041    }
042
043    /**
044     * Creates a new {@link PopupMenuLauncher} with the given menu.
045     * @param menu The popup menu to display
046     * @param checkEnabled if {@code true}, the popup menu will only be displayed if the component triggering the mouse event is enabled
047     * @since 5886
048     */
049    public PopupMenuLauncher(JPopupMenu menu, boolean checkEnabled) {
050        this.menu = menu;
051        this.checkEnabled = checkEnabled;
052    }
053
054    @Override public void mousePressed(MouseEvent e) { processEvent(e); }
055    @Override public void mouseClicked(MouseEvent e) {}
056    @Override public void mouseReleased(MouseEvent e) { processEvent(e); }
057
058    private void processEvent(MouseEvent e) {
059        if (e.isPopupTrigger() && (!checkEnabled || e.getComponent().isEnabled())) {
060            launch(e);
061        }
062    }
063
064    /**
065     * Launches the popup menu according to the given mouse event.
066     * This method needs to be overriden if the default constructor has been called.
067     * @param evt A mouse event
068     */
069    public void launch(final MouseEvent evt) {
070        if (evt != null) {
071            final Component component = evt.getComponent();
072            if (checkSelection(component, evt.getPoint())) {
073                checkFocusAndShowMenu(component, evt);
074            }
075        }
076    }
077
078    protected boolean checkSelection(Component component, Point p) {
079        if (component instanceof JList) {
080            return checkListSelection((JList<?>) component, p) > -1;
081        } else if (component instanceof JTable) {
082            return checkTableSelection((JTable) component, p) > -1;
083        } else if (component instanceof JTree) {
084            return checkTreeSelection((JTree) component, p) != null;
085        }
086        return true;
087    }
088
089    protected void checkFocusAndShowMenu(final Component component, final MouseEvent evt) {
090        if (component != null && component.isFocusable() && !component.hasFocus() && component.requestFocusInWindow()) {
091            component.addFocusListener(new FocusListener() {
092                @Override public void focusLost(FocusEvent e) {}
093                @Override public void focusGained(FocusEvent e) {
094                    showMenu(evt);
095                    component.removeFocusListener(this);
096                }
097            });
098        } else {
099            showMenu(evt);
100        }
101    }
102
103    protected void showMenu(MouseEvent evt) {
104        if (menu != null && evt != null) {
105            menu.show(evt.getComponent(), evt.getX(), evt.getY());
106        }
107    }
108
109    protected int checkListSelection(JList<?> list, Point p) {
110        int idx = list.locationToIndex(p);
111        if (idx >= 0 && idx < list.getModel().getSize() && list.getSelectedIndices().length < 2 && !list.isSelectedIndex(idx)) {
112            list.setSelectedIndex(idx);
113        }
114        return idx;
115    }
116
117    protected int checkTableSelection(JTable table, Point p) {
118        int row = table.rowAtPoint(p);
119        if (row >= 0 && row < table.getRowCount() && table.getSelectedRowCount() < 2 && table.getSelectedRow() != row) {
120            table.getSelectionModel().setSelectionInterval(row, row);
121        }
122        return row;
123    }
124
125    protected TreePath checkTreeSelection(JTree tree, Point p) {
126        TreePath path = tree.getPathForLocation(p.x, p.y);
127        if (path != null && tree.getSelectionCount() < 2 && !tree.isPathSelected(path)) {
128            tree.setSelectionPath(path);
129        }
130        return path;
131    }
132
133    protected static boolean isDoubleClick(MouseEvent e) {
134        return e != null && SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2;
135    }
136
137    /**
138     * @return the popup menu if defined, {@code null} otherwise.
139     * @since 5884
140     */
141    public final JPopupMenu getMenu() {
142        return menu;
143    }
144}