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