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}