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
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 FocusListener() {
098                @Override
099                public void focusLost(FocusEvent e) {}
100
101                @Override
102                public void focusGained(FocusEvent e) {
103                    showMenu(evt);
104                    component.removeFocusListener(this);
105                }
106            });
107        } else {
108            showMenu(evt);
109        }
110    }
111
112    protected void showMenu(MouseEvent evt) {
113        if (menu != null && evt != null) {
114            menu.show(evt.getComponent(), evt.getX(), evt.getY());
115        }
116    }
117
118    protected int checkListSelection(JList<?> list, Point p) {
119        int idx = list.locationToIndex(p);
120        if (idx >= 0 && idx < list.getModel().getSize() && list.getSelectedIndices().length < 2 && !list.isSelectedIndex(idx)) {
121            list.setSelectedIndex(idx);
122        }
123        return idx;
124    }
125
126    protected int checkTableSelection(JTable table, Point p) {
127        int row = table.rowAtPoint(p);
128        if (row >= 0 && row < table.getRowCount() && table.getSelectedRowCount() < 2 && table.getSelectedRow() != row) {
129            table.getSelectionModel().setSelectionInterval(row, row);
130        }
131        return row;
132    }
133
134    protected TreePath checkTreeSelection(JTree tree, Point p) {
135        TreePath path = tree.getPathForLocation(p.x, p.y);
136        if (path != null && tree.getSelectionCount() < 2 && !tree.isPathSelected(path)) {
137            tree.setSelectionPath(path);
138        }
139        return path;
140    }
141
142    protected static boolean isDoubleClick(MouseEvent e) {
143        return e != null && SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2;
144    }
145
146    /**
147     * @return the popup menu if defined, {@code null} otherwise.
148     * @since 5884
149     */
150    public final JPopupMenu getMenu() {
151        return menu;
152    }
153}