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