001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions.mapmode; 003 004import java.awt.Cursor; 005import java.awt.event.ActionEvent; 006import java.awt.event.InputEvent; 007import java.awt.event.MouseEvent; 008import java.awt.event.MouseListener; 009import java.awt.event.MouseMotionListener; 010 011import javax.swing.Action; 012 013import org.openstreetmap.josm.actions.JosmAction; 014import org.openstreetmap.josm.gui.MainApplication; 015import org.openstreetmap.josm.gui.MapFrame; 016import org.openstreetmap.josm.gui.layer.Layer; 017import org.openstreetmap.josm.gui.layer.OsmDataLayer; 018import org.openstreetmap.josm.spi.preferences.Config; 019import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent; 020import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener; 021import org.openstreetmap.josm.tools.ImageProvider; 022import org.openstreetmap.josm.tools.Logging; 023import org.openstreetmap.josm.tools.Shortcut; 024 025/** 026 * A class implementing MapMode is able to be selected as an mode for map editing. 027 * As example scrolling the map is a MapMode, connecting Nodes to new Ways is another. 028 * 029 * MapModes should register/deregister all necessary listeners on the map's view control. 030 */ 031public abstract class MapMode extends JosmAction implements MouseListener, MouseMotionListener, PreferenceChangedListener { 032 protected final Cursor cursor; 033 protected boolean ctrl; 034 protected boolean alt; 035 protected boolean shift; 036 037 /** 038 * Constructor for mapmodes without a menu 039 * @param name the action's text 040 * @param iconName icon filename in {@code mapmode} directory 041 * @param tooltip a longer description of the action that will be displayed in the tooltip. 042 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. 043 * @param cursor cursor displayed when map mode is active 044 * @since 11713 045 */ 046 public MapMode(String name, String iconName, String tooltip, Shortcut shortcut, Cursor cursor) { 047 super(name, "mapmode/"+iconName, tooltip, shortcut, false); 048 this.cursor = cursor; 049 putValue("active", Boolean.FALSE); 050 } 051 052 /** 053 * Constructor for mapmodes with a menu (no shortcut will be registered) 054 * @param name the action's text 055 * @param iconName icon filename in {@code mapmode} directory 056 * @param tooltip a longer description of the action that will be displayed in the tooltip. 057 * @param cursor cursor displayed when map mode is active 058 * @since 11713 059 */ 060 public MapMode(String name, String iconName, String tooltip, Cursor cursor) { 061 putValue(NAME, name); 062 new ImageProvider("mapmode", iconName).getResource().attachImageIcon(this); 063 putValue(SHORT_DESCRIPTION, tooltip); 064 this.cursor = cursor; 065 } 066 067 /** 068 * Makes this map mode active. 069 */ 070 public void enterMode() { 071 Logging.debug("Entering map mode: {0}", getValue(Action.NAME)); 072 putValue("active", Boolean.TRUE); 073 Config.getPref().addPreferenceChangeListener(this); 074 readPreferences(); 075 MainApplication.getMap().mapView.setNewCursor(cursor, this); 076 updateStatusLine(); 077 } 078 079 /** 080 * Makes this map mode inactive. 081 */ 082 public void exitMode() { 083 Logging.debug("Exiting map mode: {0}", getValue(Action.NAME)); 084 putValue("active", Boolean.FALSE); 085 Config.getPref().removePreferenceChangeListener(this); 086 MainApplication.getMap().mapView.resetCursor(this); 087 } 088 089 protected void updateStatusLine() { 090 MapFrame map = MainApplication.getMap(); 091 if (map != null && map.statusLine != null) { 092 map.statusLine.setHelpText(getModeHelpText()); 093 map.statusLine.repaint(); 094 } 095 } 096 097 /** 098 * Returns a short translated help message describing how this map mode can be used, to be displayed in status line. 099 * @return a short translated help message describing how this map mode can be used 100 */ 101 public String getModeHelpText() { 102 return ""; 103 } 104 105 protected void readPreferences() {} 106 107 /** 108 * Call selectMapMode(this) on the parent mapFrame. 109 */ 110 @Override 111 public void actionPerformed(ActionEvent e) { 112 if (MainApplication.isDisplayingMapView()) { 113 MainApplication.getMap().selectMapMode(this); 114 } 115 } 116 117 /** 118 * Determines if layer {@code l} is supported by this map mode. 119 * By default, all tools will work with all layers. 120 * Can be overwritten to require a special type of layer 121 * @param l layer 122 * @return {@code true} if the layer is supported by this map mode 123 */ 124 public boolean layerIsSupported(Layer l) { 125 return l != null; 126 } 127 128 /** 129 * Update internal ctrl, alt, shift mask from given input event. 130 * @param e input event 131 */ 132 protected void updateKeyModifiers(InputEvent e) { 133 updateKeyModifiersEx(e.getModifiersEx()); 134 } 135 136 /** 137 * Update internal ctrl, alt, shift mask from given mouse event. 138 * @param e mouse event 139 */ 140 protected void updateKeyModifiers(MouseEvent e) { 141 updateKeyModifiersEx(e.getModifiersEx()); 142 } 143 144 /** 145 * Update internal ctrl, alt, shift mask from given action event. 146 * @param e action event 147 * @since 12526 148 */ 149 protected void updateKeyModifiers(ActionEvent e) { 150 // ActionEvent does not have a getModifiersEx() method like other events :( 151 updateKeyModifiersEx(mapOldModifiers(e.getModifiers())); 152 } 153 154 /** 155 * Update internal ctrl, alt, shift mask from given extended modifiers mask. 156 * @param modifiers event extended modifiers mask 157 * @since 12517 158 */ 159 protected void updateKeyModifiersEx(int modifiers) { 160 ctrl = (modifiers & InputEvent.CTRL_DOWN_MASK) != 0; 161 alt = (modifiers & (InputEvent.ALT_DOWN_MASK | InputEvent.ALT_GRAPH_DOWN_MASK)) != 0; 162 shift = (modifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 163 } 164 165 /** 166 * Map old (pre jdk 1.4) modifiers to extended modifiers (only for Ctrl, Alt, Shift). 167 * @param modifiers old modifiers 168 * @return extended modifiers 169 */ 170 @SuppressWarnings("deprecation") 171 private static int mapOldModifiers(int modifiers) { 172 if ((modifiers & InputEvent.CTRL_MASK) != 0) { 173 modifiers |= InputEvent.CTRL_DOWN_MASK; 174 } 175 if ((modifiers & InputEvent.ALT_MASK) != 0) { 176 modifiers |= InputEvent.ALT_DOWN_MASK; 177 } 178 if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) { 179 modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK; 180 } 181 if ((modifiers & InputEvent.SHIFT_MASK) != 0) { 182 modifiers |= InputEvent.SHIFT_DOWN_MASK; 183 } 184 185 return modifiers; 186 } 187 188 protected void requestFocusInMapView() { 189 if (isEnabled()) { 190 // request focus in order to enable the expected keyboard shortcuts (see #8710) 191 MainApplication.getMap().mapView.requestFocus(); 192 } 193 } 194 195 @Override 196 public void mouseReleased(MouseEvent e) { 197 requestFocusInMapView(); 198 } 199 200 @Override 201 public void mouseExited(MouseEvent e) { 202 // Do nothing 203 } 204 205 @Override 206 public void mousePressed(MouseEvent e) { 207 requestFocusInMapView(); 208 } 209 210 @Override 211 public void mouseClicked(MouseEvent e) { 212 // Do nothing 213 } 214 215 @Override 216 public void mouseEntered(MouseEvent e) { 217 // Do nothing 218 } 219 220 @Override 221 public void mouseMoved(MouseEvent e) { 222 // Do nothing 223 } 224 225 @Override 226 public void mouseDragged(MouseEvent e) { 227 // Do nothing 228 } 229 230 @Override 231 public void preferenceChanged(PreferenceChangeEvent e) { 232 readPreferences(); 233 } 234 235 /** 236 * Determines if the given layer is a data layer that can be modified. 237 * Useful for {@link #layerIsSupported(Layer)} implementations. 238 * @param l layer 239 * @return {@code true} if the given layer is a data layer that can be modified 240 * @since 13434 241 */ 242 protected boolean isEditableDataLayer(Layer l) { 243 return l instanceof OsmDataLayer && !((OsmDataLayer) l).isLocked(); 244 } 245}