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}