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.FocusAdapter;
007import java.awt.event.FocusEvent;
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
055    public void mousePressed(MouseEvent e) {
056        processEvent(e);
057    }
058
059    @Override
060    public void mouseReleased(MouseEvent e) {
061        processEvent(e);
062    }
063
064    private void processEvent(MouseEvent e) {
065        if (e.isPopupTrigger() && (!checkEnabled || e.getComponent().isEnabled())) {
066            launch(e);
067        }
068    }
069
070    /**
071     * Launches the popup menu according to the given mouse event.
072     * This method needs to be overriden if the default constructor has been called.
073     * @param evt A mouse event
074     */
075    public void launch(final MouseEvent evt) {
076        if (evt != null) {
077            final Component component = evt.getComponent();
078            if (checkSelection(component, evt.getPoint())) {
079                checkFocusAndShowMenu(component, evt);
080            }
081        }
082    }
083
084    protected boolean checkSelection(Component component, Point p) {
085        if (component instanceof JList) {
086            return checkListSelection((JList<?>) component, p) > -1;
087        } else if (component instanceof JTable) {
088            return checkTableSelection((JTable) component, p) > -1;
089        } else if (component instanceof JTree) {
090            return checkTreeSelection((JTree) component, p) != null;
091        }
092        return true;
093    }
094
095    protected void checkFocusAndShowMenu(final Component component, final MouseEvent evt) {
096        if (component != null && component.isFocusable() && !component.hasFocus() && component.requestFocusInWindow()) {
097            component.addFocusListener(new FocusAdapter() {
098                @Override
099                public void focusGained(FocusEvent e) {
100                    showMenu(evt);
101                    component.removeFocusListener(this);
102                }
103            });
104        } else {
105            showMenu(evt);
106        }
107    }
108
109    protected void showMenu(MouseEvent evt) {
110        if (menu != null && evt != null) {
111            menu.show(evt.getComponent(), evt.getX(), evt.getY());
112        }
113    }
114
115    protected int checkListSelection(JList<?> list, Point p) {
116        int idx = list.locationToIndex(p);
117        if (idx >= 0 && idx < list.getModel().getSize() && list.getSelectedIndices().length < 2 && !list.isSelectedIndex(idx)) {
118            list.setSelectedIndex(idx);
119        }
120        return idx;
121    }
122
123    protected int checkTableSelection(JTable table, Point p) {
124        int row = table.rowAtPoint(p);
125        if (row >= 0 && row < table.getRowCount() && table.getSelectedRowCount() < 2 && table.getSelectedRow() != row) {
126            table.getSelectionModel().setSelectionInterval(row, row);
127        }
128        return row;
129    }
130
131    protected TreePath checkTreeSelection(JTree tree, Point p) {
132        TreePath path = tree.getPathForLocation(p.x, p.y);
133        if (path != null && tree.getSelectionCount() < 2 && !tree.isPathSelected(path)) {
134            tree.setSelectionPath(path);
135        }
136        return path;
137    }
138
139    protected static boolean isDoubleClick(MouseEvent e) {
140        return e != null && SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2;
141    }
142
143    /**
144     * @return the popup menu if defined, {@code null} otherwise.
145     * @since 5884
146     */
147    public final JPopupMenu getMenu() {
148        return menu;
149    }
150}