001// License: GPL. See LICENSE file for details.
002
003package org.openstreetmap.josm.gui.layer;
004
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.Color;
008import java.awt.Component;
009import java.awt.Graphics2D;
010import java.awt.event.ActionEvent;
011import java.beans.PropertyChangeListener;
012import java.beans.PropertyChangeSupport;
013import java.io.File;
014import java.util.List;
015
016import javax.swing.AbstractAction;
017import javax.swing.Action;
018import javax.swing.Icon;
019import javax.swing.JOptionPane;
020import javax.swing.JSeparator;
021
022import org.openstreetmap.josm.Main;
023import org.openstreetmap.josm.actions.GpxExportAction;
024import org.openstreetmap.josm.actions.SaveAction;
025import org.openstreetmap.josm.actions.SaveActionBase;
026import org.openstreetmap.josm.actions.SaveAsAction;
027import org.openstreetmap.josm.data.Bounds;
028import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
029import org.openstreetmap.josm.data.projection.Projection;
030import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
031import org.openstreetmap.josm.gui.MapView;
032import org.openstreetmap.josm.tools.Destroyable;
033import org.openstreetmap.josm.tools.ImageProvider;
034
035/**
036 * A layer encapsulates the gui component of one dataset and its representation.
037 *
038 * Some layers may display data directly imported from OSM server. Other only
039 * display background images. Some can be edited, some not. Some are static and
040 * other changes dynamically (auto-updated).
041 *
042 * Layers can be visible or not. Most actions the user can do applies only on
043 * selected layers. The available actions depend on the selected layers too.
044 *
045 * All layers are managed by the MapView. They are displayed in a list to the
046 * right of the screen.
047 *
048 * @author imi
049 */
050abstract public class Layer implements Destroyable, MapViewPaintable, ProjectionChangeListener {
051
052    public interface LayerAction {
053        boolean supportLayers(List<Layer> layers);
054        Component createMenuComponent();
055    }
056
057    public interface MultiLayerAction {
058        Action getMultiLayerAction(List<Layer> layers);
059    }
060
061
062    /**
063     * Special class that can be returned by getMenuEntries when JSeparator needs to be created
064     *
065     */
066    public static class SeparatorLayerAction extends AbstractAction implements LayerAction {
067        public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
068        @Override
069        public void actionPerformed(ActionEvent e) {
070            throw new UnsupportedOperationException();
071        }
072        @Override
073        public Component createMenuComponent() {
074            return new JSeparator();
075        }
076        @Override
077        public boolean supportLayers(List<Layer> layers) {
078            return false;
079        }
080    }
081
082    static public final String VISIBLE_PROP = Layer.class.getName() + ".visible";
083    static public final String OPACITY_PROP = Layer.class.getName() + ".opacity";
084    static public final String NAME_PROP = Layer.class.getName() + ".name";
085
086    static public final int ICON_SIZE = 16;
087
088    /** keeps track of property change listeners */
089    protected PropertyChangeSupport propertyChangeSupport;
090
091    /**
092     * The visibility state of the layer.
093     *
094     */
095    private boolean visible = true;
096
097    /**
098     * The opacity of the layer.
099     *
100     */
101    private double opacity = 1;
102
103    /**
104     * The layer should be handled as a background layer in automatic handling
105     *
106     */
107    private boolean background = false;
108
109    /**
110     * The name of this layer.
111     *
112     */
113    private  String name;
114
115    /**
116     * If a file is associated with this layer, this variable should be set to it.
117     */
118    private File associatedFile;
119
120    /**
121     * Create the layer and fill in the necessary components.
122     */
123    public Layer(String name) {
124        this.propertyChangeSupport = new PropertyChangeSupport(this);
125        setName(name);
126    }
127
128    /**
129     * Initialization code, that depends on Main.map.mapView.
130     *
131     * It is always called in the event dispatching thread.
132     * Note that Main.map is null as long as no layer has been added, so do
133     * not execute code in the constructor, that assumes Main.map.mapView is
134     * not null. Instead override this method.
135     */
136    public void hookUpMapView() {
137    }
138
139    /**
140     * Paint the dataset using the engine set.
141     * @param mv The object that can translate GeoPoints to screen coordinates.
142     */
143    @Override
144    abstract public void paint(Graphics2D g, MapView mv, Bounds box);
145    /**
146     * Return a representative small image for this layer. The image must not
147     * be larger than 64 pixel in any dimension.
148     */
149    abstract public Icon getIcon();
150
151    /**
152     * Return a Color for this layer. Return null when no color specified.
153     * @param ignoreCustom Custom color should return null, as no default color
154     *      is used. When this is true, then even for custom coloring the base
155     *      color is returned - mainly for layer internal use.
156     */
157    public Color getColor(boolean ignoreCustom) {
158        return null;
159    }
160
161    /**
162     * @return A small tooltip hint about some statistics for this layer.
163     */
164    abstract public String getToolTipText();
165
166    /**
167     * Merges the given layer into this layer. Throws if the layer types are
168     * incompatible.
169     * @param from The layer that get merged into this one. After the merge,
170     *      the other layer is not usable anymore and passing to one others
171     *      mergeFrom should be one of the last things to do with a layer.
172     */
173    abstract public void mergeFrom(Layer from);
174
175    /**
176     * @param other The other layer that is tested to be mergable with this.
177     * @return Whether the other layer can be merged into this layer.
178     */
179    abstract public boolean isMergable(Layer other);
180
181    abstract public void visitBoundingBox(BoundingXYVisitor v);
182
183    abstract public Object getInfoComponent();
184
185    /**
186     * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
187     * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
188     * have correct equals implementation.
189     *
190     * Use SeparatorLayerAction.INSTANCE instead of new JSeparator
191     *
192     */
193    abstract public Action[] getMenuEntries();
194
195    /**
196     * Called, when the layer is removed from the mapview and is going to be
197     * destroyed.
198     *
199     * This is because the Layer constructor can not add itself safely as listener
200     * to the layerlist dialog, because there may be no such dialog yet (loaded
201     * via command line parameter).
202     */
203    @Override
204    public void destroy() {}
205
206    public File getAssociatedFile() { return associatedFile; }
207    public void setAssociatedFile(File file) { associatedFile = file; }
208
209    /**
210     * Replies the name of the layer
211     *
212     * @return the name of the layer
213     */
214    public String getName() {
215        return name;
216    }
217
218    /**
219     * Sets the name of the layer
220     *
221     *@param name the name. If null, the name is set to the empty string.
222     *
223     */
224    public void setName(String name) {
225        if (name == null) {
226            name = "";
227        }
228        String oldValue = this.name;
229        this.name = name;
230        if (!this.name.equals(oldValue)) {
231            propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
232        }
233    }
234
235    /**
236     * Replies true if this layer is a background layer
237     *
238     * @return true if this layer is a background layer
239     */
240    public boolean isBackgroundLayer() {
241        return background;
242    }
243
244    /**
245     * Sets whether this layer is a background layer
246     *
247     * @param background true, if this layer is a background layer
248     */
249    public void setBackgroundLayer(boolean background) {
250        this.background = background;
251    }
252
253    /**
254     * Sets the visibility of this layer. Emits property change event for
255     * property {@link #VISIBLE_PROP}.
256     *
257     * @param visible true, if the layer is visible; false, otherwise.
258     */
259    public void setVisible(boolean visible) {
260        boolean oldValue = isVisible();
261        this.visible  = visible;
262        if (visible && opacity == 0) {
263            setOpacity(1);
264        } else if (oldValue != isVisible()) {
265            fireVisibleChanged(oldValue, isVisible());
266        }
267    }
268
269    /**
270     * Replies true if this layer is visible. False, otherwise.
271     * @return  true if this layer is visible. False, otherwise.
272     */
273    public boolean isVisible() {
274        return visible && opacity != 0;
275    }
276
277    public double getOpacity() {
278        return opacity;
279    }
280
281    public void setOpacity(double opacity) {
282        if (!(opacity >= 0 && opacity <= 1))
283            throw new IllegalArgumentException("Opacity value must be between 0 and 1");
284        double oldOpacity = getOpacity();
285        boolean oldVisible = isVisible();
286        this.opacity = opacity;
287        if (oldOpacity != getOpacity()) {
288            fireOpacityChanged(oldOpacity, getOpacity());
289        }
290        if (oldVisible != isVisible()) {
291            fireVisibleChanged(oldVisible, isVisible());
292        }
293    }
294
295    /**
296     * Toggles the visibility state of this layer.
297     */
298    public void toggleVisible() {
299        setVisible(!isVisible());
300    }
301
302    /**
303     * Adds a {@link PropertyChangeListener}
304     *
305     * @param listener the listener
306     */
307    public void addPropertyChangeListener(PropertyChangeListener listener) {
308        propertyChangeSupport.addPropertyChangeListener(listener);
309    }
310
311    /**
312     * Removes a {@link PropertyChangeListener}
313     *
314     * @param listener the listener
315     */
316    public void removePropertyChangeListener(PropertyChangeListener listener) {
317        propertyChangeSupport.removePropertyChangeListener(listener);
318    }
319
320    /**
321     * fires a property change for the property {@link #VISIBLE_PROP}
322     *
323     * @param oldValue the old value
324     * @param newValue the new value
325     */
326    protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
327        propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
328    }
329
330    /**
331     * fires a property change for the property {@link #OPACITY_PROP}
332     *
333     * @param oldValue the old value
334     * @param newValue the new value
335     */
336    protected void fireOpacityChanged(double oldValue, double newValue) {
337        propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
338    }
339
340    /**
341     * Check changed status of layer
342     *
343     * @return True if layer was changed since last paint
344     */
345    public boolean isChanged() {
346        return true;
347    }
348
349    /**
350     * allows to check whether a projection is supported or not
351     *
352     * @return True if projection is supported for this layer
353     */
354    public boolean isProjectionSupported(Projection proj) {
355        return true;
356    }
357
358    /**
359     * Specify user information about projections
360     *
361     * @return User readable text telling about supported projections
362     */
363    public String nameSupportedProjections() {
364        return tr("All projections are supported");
365    }
366
367    /**
368     * The action to save a layer
369     *
370     */
371    public static class LayerSaveAction extends AbstractAction {
372        private Layer layer;
373        public LayerSaveAction(Layer layer) {
374            putValue(SMALL_ICON, ImageProvider.get("save"));
375            putValue(SHORT_DESCRIPTION, tr("Save the current data."));
376            putValue(NAME, tr("Save"));
377            setEnabled(true);
378            this.layer = layer;
379        }
380
381        @Override
382        public void actionPerformed(ActionEvent e) {
383            SaveAction.getInstance().doSave(layer);
384        }
385    }
386
387    public static class LayerSaveAsAction extends AbstractAction {
388        private Layer layer;
389        public LayerSaveAsAction(Layer layer) {
390            putValue(SMALL_ICON, ImageProvider.get("save_as"));
391            putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
392            putValue(NAME, tr("Save As..."));
393            setEnabled(true);
394            this.layer = layer;
395        }
396
397        @Override
398        public void actionPerformed(ActionEvent e) {
399            SaveAsAction.getInstance().doSave(layer);
400        }
401    }
402
403    public static class LayerGpxExportAction extends AbstractAction {
404        private Layer layer;
405        public LayerGpxExportAction(Layer layer) {
406            putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
407            putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
408            putValue(NAME, tr("Export to GPX..."));
409            setEnabled(true);
410            this.layer = layer;
411        }
412
413        @Override
414        public void actionPerformed(ActionEvent e) {
415            new GpxExportAction().export(layer);
416        }
417    }
418
419    /* --------------------------------------------------------------------------------- */
420    /* interface ProjectionChangeListener                                                */
421    /* --------------------------------------------------------------------------------- */
422    @Override
423    public void projectionChanged(Projection oldValue, Projection newValue) {
424        if(!isProjectionSupported(newValue)) {
425            JOptionPane.showMessageDialog(Main.parent,
426                    tr("The layer {0} does not support the new projection {1}.\n{2}\n"
427                            + "Change the projection again or remove the layer.",
428                            getName(), newValue.toCode(), nameSupportedProjections()),
429                            tr("Warning"),
430                            JOptionPane.WARNING_MESSAGE);
431        }
432    }
433
434    /**
435     * Initializes the layer after a successful load of data from a file
436     * @since 5459
437     */
438    public void onPostLoadFromFile() {
439        // To be overriden if needed
440    }
441
442    /**
443     * Replies the savable state of this layer (i.e if it can be saved through a "File->Save" dialog).
444     * @return true if this layer can be saved to a file
445     * @since 5459
446     */
447    public boolean isSavable() {
448        return false;
449    }
450
451    /**
452     * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
453     * @return <code>true</code>, if it is safe to save.
454     * @since 5459
455     */
456    public boolean checkSaveConditions() {
457        return true;
458    }
459
460    /**
461     * Creates a new "Save" dialog for this layer and makes it visible.<br/>
462     * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
463     * @return The output {@code File}
464     * @since 5459
465     * @see SaveActionBase#createAndOpenSaveFileChooser
466     */
467    public File createAndOpenSaveFileChooser() {
468        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
469    }
470}