001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Color;
007import java.awt.Component;
008import java.awt.Graphics2D;
009import java.awt.event.ActionEvent;
010import java.beans.PropertyChangeListener;
011import java.beans.PropertyChangeSupport;
012import java.io.File;
013import java.util.List;
014
015import javax.swing.AbstractAction;
016import javax.swing.Action;
017import javax.swing.Icon;
018import javax.swing.JOptionPane;
019import javax.swing.JSeparator;
020
021import org.openstreetmap.josm.Main;
022import org.openstreetmap.josm.actions.GpxExportAction;
023import org.openstreetmap.josm.actions.SaveAction;
024import org.openstreetmap.josm.actions.SaveActionBase;
025import org.openstreetmap.josm.actions.SaveAsAction;
026import org.openstreetmap.josm.data.Bounds;
027import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
028import org.openstreetmap.josm.data.projection.Projection;
029import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
030import org.openstreetmap.josm.gui.MapView;
031import org.openstreetmap.josm.tools.Destroyable;
032import org.openstreetmap.josm.tools.ImageProvider;
033import org.openstreetmap.josm.tools.Utils;
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 */
050public abstract class Layer implements Destroyable, MapViewPaintable, ProjectionChangeListener {
051
052    /**
053     * Action related to a single layer.
054     */
055    public interface LayerAction {
056
057        /**
058         * Determines if this action supports a given list of layers.
059         * @param layers list of layers
060         * @return {@code true} if this action supports the given list of layers, {@code false} otherwise
061         */
062        boolean supportLayers(List<Layer> layers);
063
064        /**
065         * Creates and return the menu component.
066         * @return the menu component
067         */
068        Component createMenuComponent();
069    }
070
071    /**
072     * Action related to several layers.
073     */
074    public interface MultiLayerAction {
075
076        /**
077         * Returns the action for a given list of layers.
078         * @param layers list of layers
079         * @return the action for the given list of layers
080         */
081        Action getMultiLayerAction(List<Layer> layers);
082    }
083
084    /**
085     * Special class that can be returned by getMenuEntries when JSeparator needs to be created
086     */
087    public static class SeparatorLayerAction extends AbstractAction implements LayerAction {
088        /** Unique instance */
089        public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
090
091        @Override
092        public void actionPerformed(ActionEvent e) {
093            throw new UnsupportedOperationException();
094        }
095
096        @Override
097        public Component createMenuComponent() {
098            return new JSeparator();
099        }
100
101        @Override
102        public boolean supportLayers(List<Layer> layers) {
103            return false;
104        }
105    }
106
107    public static final String VISIBLE_PROP = Layer.class.getName() + ".visible";
108    public static final String OPACITY_PROP = Layer.class.getName() + ".opacity";
109    public static final String NAME_PROP = Layer.class.getName() + ".name";
110    public static final String FILTER_STATE_PROP = Layer.class.getName() + ".filterstate";
111
112    /**
113     * keeps track of property change listeners
114     */
115    protected PropertyChangeSupport propertyChangeSupport;
116
117    /**
118     * The visibility state of the layer.
119     */
120    private boolean visible = true;
121
122    /**
123     * The opacity of the layer.
124     */
125    private double opacity = 1;
126
127    /**
128     * The layer should be handled as a background layer in automatic handling
129     */
130    private boolean background;
131
132    /**
133     * The name of this layer.
134     */
135    private String name;
136
137    /**
138     * This is set if user renamed this layer.
139     */
140    private boolean renamed;
141
142    /**
143     * If a file is associated with this layer, this variable should be set to it.
144     */
145    private File associatedFile;
146
147    /**
148     * Create the layer and fill in the necessary components.
149     * @param name Layer name
150     */
151    public Layer(String name) {
152        this.propertyChangeSupport = new PropertyChangeSupport(this);
153        setName(name);
154    }
155
156    /**
157     * Initialization code, that depends on Main.map.mapView.
158     *
159     * It is always called in the event dispatching thread.
160     * Note that Main.map is null as long as no layer has been added, so do
161     * not execute code in the constructor, that assumes Main.map.mapView is
162     * not null. Instead override this method.
163     *
164     * This implementation provides check, if JOSM will be able to use Layer. Layers
165     * using a lot of memory, which do know in advance, how much memory they use, should
166     * override {@link #estimateMemoryUsage() estimateMemoryUsage} method and give a hint.
167     *
168     * This allows for preemptive warning message for user, instead of failing later on
169     *
170     * Remember to call {@code super.hookUpMapView()} when overriding this method
171     */
172    public void hookUpMapView() {
173        // calculate total memory needed for all layers
174        long memoryBytesRequired = 50 * 1024 * 1024; // assumed minimum JOSM memory footprint
175        if (Main.map != null && Main.map.mapView != null) {
176            for (Layer layer: Main.map.mapView.getAllLayers()) {
177                memoryBytesRequired += layer.estimateMemoryUsage();
178            }
179            if (memoryBytesRequired >  Runtime.getRuntime().maxMemory()) {
180                throw new IllegalArgumentException(
181                        tr("To add another layer you need to allocate at least {0,number,#}MB memory to JOSM using -Xmx{0,number,#}M "
182                        + "option (see http://forum.openstreetmap.org/viewtopic.php?id=25677).\n"
183                        + "Currently you have {1,number,#}MB memory allocated for JOSM",
184                        memoryBytesRequired / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024));
185            }
186        }
187    }
188
189    /**
190     * Paint the dataset using the engine set.
191     * @param mv The object that can translate GeoPoints to screen coordinates.
192     */
193    @Override
194    public abstract void paint(Graphics2D g, MapView mv, Bounds box);
195
196    /**
197     * Return a representative small image for this layer. The image must not
198     * be larger than 64 pixel in any dimension.
199     * @return layer icon
200     */
201    public abstract Icon getIcon();
202
203    /**
204     * Return a Color for this layer. Return null when no color specified.
205     * @param ignoreCustom Custom color should return null, as no default color
206     *      is used. When this is true, then even for custom coloring the base
207     *      color is returned - mainly for layer internal use.
208     * @return layer color
209     */
210    public Color getColor(boolean ignoreCustom) {
211        return null;
212    }
213
214    /**
215     * @return A small tooltip hint about some statistics for this layer.
216     */
217    public abstract String getToolTipText();
218
219    /**
220     * Merges the given layer into this layer. Throws if the layer types are
221     * incompatible.
222     * @param from The layer that get merged into this one. After the merge,
223     *      the other layer is not usable anymore and passing to one others
224     *      mergeFrom should be one of the last things to do with a layer.
225     */
226    public abstract void mergeFrom(Layer from);
227
228    /**
229     * @param other The other layer that is tested to be mergable with this.
230     * @return Whether the other layer can be merged into this layer.
231     */
232    public abstract boolean isMergable(Layer other);
233
234    public abstract void visitBoundingBox(BoundingXYVisitor v);
235
236    public abstract Object getInfoComponent();
237
238    /**
239     * Determines if info dialog can be resized (false by default).
240     * @return {@code true} if the info dialog can be resized, {@code false} otherwise
241     * @since 6708
242     */
243    public boolean isInfoResizable() {
244        return false;
245    }
246
247    /**
248     * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
249     * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
250     * have correct equals implementation.
251     *
252     * Use {@link SeparatorLayerAction#INSTANCE} instead of new JSeparator
253     * @return menu actions for this layer
254     */
255    public abstract Action[] getMenuEntries();
256
257    /**
258     * Called, when the layer is removed from the mapview and is going to be destroyed.
259     *
260     * This is because the Layer constructor can not add itself safely as listener
261     * to the layerlist dialog, because there may be no such dialog yet (loaded
262     * via command line parameter).
263     */
264    @Override
265    public void destroy() {
266        // Override in subclasses if needed
267    }
268
269    public File getAssociatedFile() {
270        return associatedFile;
271    }
272
273    public void setAssociatedFile(File file) {
274        associatedFile = file;
275    }
276
277    /**
278     * Replies the name of the layer
279     *
280     * @return the name of the layer
281     */
282    public String getName() {
283        return name;
284    }
285
286    /**
287     * Sets the name of the layer
288     *
289     * @param name the name. If null, the name is set to the empty string.
290     */
291    public final void setName(String name) {
292        if (name == null) {
293            name = "";
294        }
295        String oldValue = this.name;
296        this.name = name;
297        if (!this.name.equals(oldValue)) {
298            propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
299        }
300    }
301
302    /**
303     * Rename layer and set renamed flag to mark it as renamed (has user given name).
304     *
305     * @param name the name. If null, the name is set to the empty string.
306     */
307    public final void rename(String name) {
308        renamed = true;
309        setName(name);
310    }
311
312    /**
313     * Replies true if this layer was renamed by user
314     *
315     * @return true if this layer was renamed by user
316     */
317    public boolean isRenamed() {
318        return renamed;
319    }
320
321    /**
322     * Replies true if this layer is a background layer
323     *
324     * @return true if this layer is a background layer
325     */
326    public boolean isBackgroundLayer() {
327        return background;
328    }
329
330    /**
331     * Sets whether this layer is a background layer
332     *
333     * @param background true, if this layer is a background layer
334     */
335    public void setBackgroundLayer(boolean background) {
336        this.background = background;
337    }
338
339    /**
340     * Sets the visibility of this layer. Emits property change event for
341     * property {@link #VISIBLE_PROP}.
342     *
343     * @param visible true, if the layer is visible; false, otherwise.
344     */
345    public void setVisible(boolean visible) {
346        boolean oldValue = isVisible();
347        this.visible  = visible;
348        if (visible && opacity == 0) {
349            setOpacity(1);
350        } else if (oldValue != isVisible()) {
351            fireVisibleChanged(oldValue, isVisible());
352        }
353    }
354
355    /**
356     * Replies true if this layer is visible. False, otherwise.
357     * @return  true if this layer is visible. False, otherwise.
358     */
359    public boolean isVisible() {
360        return visible && opacity != 0;
361    }
362
363    public double getOpacity() {
364        return opacity;
365    }
366
367    public void setOpacity(double opacity) {
368        if (!(opacity >= 0 && opacity <= 1))
369            throw new IllegalArgumentException("Opacity value must be between 0 and 1");
370        double oldOpacity = getOpacity();
371        boolean oldVisible = isVisible();
372        this.opacity = opacity;
373        if (!Utils.equalsEpsilon(oldOpacity, getOpacity())) {
374            fireOpacityChanged(oldOpacity, getOpacity());
375        }
376        if (oldVisible != isVisible()) {
377            fireVisibleChanged(oldVisible, isVisible());
378        }
379    }
380
381    /**
382     * Sets new state to the layer after applying {@link ImageProcessor}.
383     */
384    public void setFilterStateChanged() {
385        fireFilterStateChanged();
386    }
387
388    /**
389     * Toggles the visibility state of this layer.
390     */
391    public void toggleVisible() {
392        setVisible(!isVisible());
393    }
394
395    /**
396     * Adds a {@link PropertyChangeListener}
397     *
398     * @param listener the listener
399     */
400    public void addPropertyChangeListener(PropertyChangeListener listener) {
401        propertyChangeSupport.addPropertyChangeListener(listener);
402    }
403
404    /**
405     * Removes a {@link PropertyChangeListener}
406     *
407     * @param listener the listener
408     */
409    public void removePropertyChangeListener(PropertyChangeListener listener) {
410        propertyChangeSupport.removePropertyChangeListener(listener);
411    }
412
413    /**
414     * fires a property change for the property {@link #VISIBLE_PROP}
415     *
416     * @param oldValue the old value
417     * @param newValue the new value
418     */
419    protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
420        propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
421    }
422
423    /**
424     * fires a property change for the property {@link #OPACITY_PROP}
425     *
426     * @param oldValue the old value
427     * @param newValue the new value
428     */
429    protected void fireOpacityChanged(double oldValue, double newValue) {
430        propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
431    }
432
433    /**
434     * fires a property change for the property {@link #FILTER_STATE_PROP}.
435     */
436    protected void fireFilterStateChanged() {
437        propertyChangeSupport.firePropertyChange(FILTER_STATE_PROP, null, null);
438    }
439
440    /**
441     * Check changed status of layer
442     *
443     * @return True if layer was changed since last paint
444     */
445    public boolean isChanged() {
446        return true;
447    }
448
449    /**
450     * allows to check whether a projection is supported or not
451     * @param proj projection
452     *
453     * @return True if projection is supported for this layer
454     */
455    public boolean isProjectionSupported(Projection proj) {
456        return proj != null;
457    }
458
459    /**
460     * Specify user information about projections
461     *
462     * @return User readable text telling about supported projections
463     */
464    public String nameSupportedProjections() {
465        return tr("All projections are supported");
466    }
467
468    /**
469     * The action to save a layer
470     */
471    public static class LayerSaveAction extends AbstractAction {
472        private final transient Layer layer;
473
474        public LayerSaveAction(Layer layer) {
475            putValue(SMALL_ICON, ImageProvider.get("save"));
476            putValue(SHORT_DESCRIPTION, tr("Save the current data."));
477            putValue(NAME, tr("Save"));
478            setEnabled(true);
479            this.layer = layer;
480        }
481
482        @Override
483        public void actionPerformed(ActionEvent e) {
484            SaveAction.getInstance().doSave(layer);
485        }
486    }
487
488    public static class LayerSaveAsAction extends AbstractAction {
489        private final transient Layer layer;
490
491        public LayerSaveAsAction(Layer layer) {
492            putValue(SMALL_ICON, ImageProvider.get("save_as"));
493            putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
494            putValue(NAME, tr("Save As..."));
495            setEnabled(true);
496            this.layer = layer;
497        }
498
499        @Override
500        public void actionPerformed(ActionEvent e) {
501            SaveAsAction.getInstance().doSave(layer);
502        }
503    }
504
505    public static class LayerGpxExportAction extends AbstractAction {
506        private final transient Layer layer;
507
508        public LayerGpxExportAction(Layer layer) {
509            putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
510            putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
511            putValue(NAME, tr("Export to GPX..."));
512            setEnabled(true);
513            this.layer = layer;
514        }
515
516        @Override
517        public void actionPerformed(ActionEvent e) {
518            new GpxExportAction().export(layer);
519        }
520    }
521
522    /* --------------------------------------------------------------------------------- */
523    /* interface ProjectionChangeListener                                                */
524    /* --------------------------------------------------------------------------------- */
525    @Override
526    public void projectionChanged(Projection oldValue, Projection newValue) {
527        if (!isProjectionSupported(newValue)) {
528            String message = "<html><body><p>" +
529                    tr("The layer {0} does not support the new projection {1}.", getName(), newValue.toCode()) + "</p>" +
530                    "<p style='width: 450px;'>" + tr("Supported projections are: {0}", nameSupportedProjections()) + "</p>" +
531                    tr("Change the projection again or remove the layer.");
532
533            JOptionPane.showMessageDialog(Main.parent,
534                    message,
535                    tr("Warning"),
536                    JOptionPane.WARNING_MESSAGE);
537        }
538    }
539
540    /**
541     * Initializes the layer after a successful load of data from a file
542     * @since 5459
543     */
544    public void onPostLoadFromFile() {
545        // To be overriden if needed
546    }
547
548    /**
549     * Replies the savable state of this layer (i.e if it can be saved through a "File-&gt;Save" dialog).
550     * @return true if this layer can be saved to a file
551     * @since 5459
552     */
553    public boolean isSavable() {
554        return false;
555    }
556
557    /**
558     * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
559     * @return <code>true</code>, if it is safe to save.
560     * @since 5459
561     */
562    public boolean checkSaveConditions() {
563        return true;
564    }
565
566    /**
567     * Creates a new "Save" dialog for this layer and makes it visible.<br>
568     * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
569     * @return The output {@code File}
570     * @see SaveActionBase#createAndOpenSaveFileChooser
571     * @since 5459
572     */
573    public File createAndOpenSaveFileChooser() {
574        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
575    }
576
577    /**
578     * @return bytes that the tile will use. Needed for resource management
579     */
580    protected long estimateMemoryUsage() {
581        return 0;
582    }
583}