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; 010import java.util.Collection; 011import java.util.Collections; 012 013import org.openstreetmap.josm.actions.JosmAction; 014import org.openstreetmap.josm.data.osm.OsmPrimitive; 015import org.openstreetmap.josm.gui.MainApplication; 016import org.openstreetmap.josm.gui.MapFrame; 017import org.openstreetmap.josm.gui.layer.Layer; 018import org.openstreetmap.josm.gui.layer.OsmDataLayer; 019import org.openstreetmap.josm.spi.preferences.Config; 020import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent; 021import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener; 022import org.openstreetmap.josm.tools.ImageProvider; 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 putValue("active", Boolean.TRUE); 072 Config.getPref().addPreferenceChangeListener(this); 073 readPreferences(); 074 MainApplication.getMap().mapView.setNewCursor(cursor, this); 075 updateStatusLine(); 076 } 077 078 /** 079 * Makes this map mode inactive. 080 */ 081 public void exitMode() { 082 putValue("active", Boolean.FALSE); 083 Config.getPref().removePreferenceChangeListener(this); 084 MainApplication.getMap().mapView.resetCursor(this); 085 } 086 087 protected void updateStatusLine() { 088 MapFrame map = MainApplication.getMap(); 089 if (map != null && map.statusLine != null) { 090 map.statusLine.setHelpText(getModeHelpText()); 091 map.statusLine.repaint(); 092 } 093 } 094 095 /** 096 * Returns a short translated help message describing how this map mode can be used, to be displayed in status line. 097 * @return a short translated help message describing how this map mode can be used 098 */ 099 public String getModeHelpText() { 100 return ""; 101 } 102 103 protected void readPreferences() {} 104 105 /** 106 * Call selectMapMode(this) on the parent mapFrame. 107 */ 108 @Override 109 public void actionPerformed(ActionEvent e) { 110 if (MainApplication.isDisplayingMapView()) { 111 MainApplication.getMap().selectMapMode(this); 112 } 113 } 114 115 /** 116 * Determines if layer {@code l} is supported by this map mode. 117 * By default, all tools will work with all layers. 118 * Can be overwritten to require a special type of layer 119 * @param l layer 120 * @return {@code true} if the layer is supported by this map mode 121 */ 122 public boolean layerIsSupported(Layer l) { 123 return l != null; 124 } 125 126 /** 127 * Update internal ctrl, alt, shift mask from given input event. 128 * @param e input event 129 */ 130 protected void updateKeyModifiers(InputEvent e) { 131 updateKeyModifiersEx(e.getModifiersEx()); 132 } 133 134 /** 135 * Update internal ctrl, alt, shift mask from given mouse event. 136 * @param e mouse event 137 */ 138 protected void updateKeyModifiers(MouseEvent e) { 139 updateKeyModifiersEx(e.getModifiersEx()); 140 } 141 142 /** 143 * Update internal ctrl, alt, shift mask from given action event. 144 * @param e action event 145 * @since 12526 146 */ 147 protected void updateKeyModifiers(ActionEvent e) { 148 // ActionEvent does not have a getModifiersEx() method like other events :( 149 updateKeyModifiersEx(mapOldModifiers(e.getModifiers())); 150 } 151 152 /** 153 * Update internal ctrl, alt, shift mask from given extended modifiers mask. 154 * @param modifiers event extended modifiers mask 155 * @since 12517 156 */ 157 protected void updateKeyModifiersEx(int modifiers) { 158 ctrl = (modifiers & InputEvent.CTRL_DOWN_MASK) != 0; 159 alt = (modifiers & (InputEvent.ALT_DOWN_MASK | InputEvent.ALT_GRAPH_DOWN_MASK)) != 0; 160 shift = (modifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 161 } 162 163 /** 164 * Map old (pre jdk 1.4) modifiers to extended modifiers (only for Ctrl, Alt, Shift). 165 * @param modifiers old modifiers 166 * @return extended modifiers 167 */ 168 @SuppressWarnings("deprecation") 169 private static int mapOldModifiers(int modifiers) { 170 if ((modifiers & InputEvent.CTRL_MASK) != 0) { 171 modifiers |= InputEvent.CTRL_DOWN_MASK; 172 } 173 if ((modifiers & InputEvent.ALT_MASK) != 0) { 174 modifiers |= InputEvent.ALT_DOWN_MASK; 175 } 176 if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) { 177 modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK; 178 } 179 if ((modifiers & InputEvent.SHIFT_MASK) != 0) { 180 modifiers |= InputEvent.SHIFT_DOWN_MASK; 181 } 182 183 return modifiers; 184 } 185 186 protected void requestFocusInMapView() { 187 if (isEnabled()) { 188 // request focus in order to enable the expected keyboard shortcuts (see #8710) 189 MainApplication.getMap().mapView.requestFocus(); 190 } 191 } 192 193 @Override 194 public void mouseReleased(MouseEvent e) { 195 requestFocusInMapView(); 196 } 197 198 @Override 199 public void mouseExited(MouseEvent e) { 200 // Do nothing 201 } 202 203 @Override 204 public void mousePressed(MouseEvent e) { 205 requestFocusInMapView(); 206 } 207 208 @Override 209 public void mouseClicked(MouseEvent e) { 210 // Do nothing 211 } 212 213 @Override 214 public void mouseEntered(MouseEvent e) { 215 // Do nothing 216 } 217 218 @Override 219 public void mouseMoved(MouseEvent e) { 220 // Do nothing 221 } 222 223 @Override 224 public void mouseDragged(MouseEvent e) { 225 // Do nothing 226 } 227 228 @Override 229 public void preferenceChanged(PreferenceChangeEvent e) { 230 readPreferences(); 231 } 232 233 /** 234 * Gets a collection of primitives that should not be hidden by the filter. 235 * @return The primitives that the filter should not hide. 236 * @deprecated use {@link org.openstreetmap.josm.data.osm.DataSet#allPreservedPrimitives} 237 * @since 11993 238 */ 239 @Deprecated 240 public Collection<? extends OsmPrimitive> getPreservedPrimitives() { 241 return Collections.emptySet(); 242 } 243 244 /** 245 * Determines if the given layer is a data layer that can be modified. 246 * Useful for {@link #layerIsSupported(Layer)} implementations. 247 * @param l layer 248 * @return {@code true} if the given layer is a data layer that can be modified 249 * @since 13434 250 */ 251 protected boolean isEditableDataLayer(Layer l) { 252 return l instanceof OsmDataLayer && !((OsmDataLayer) l).isLocked(); 253 } 254}