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}