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 */
050public abstract 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     * Special class that can be returned by getMenuEntries when JSeparator needs to be created
063     *
064     */
065    public static class SeparatorLayerAction extends AbstractAction implements LayerAction {
066        public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
067        @Override
068        public void actionPerformed(ActionEvent e) {
069            throw new UnsupportedOperationException();
070        }
071        @Override
072        public Component createMenuComponent() {
073            return new JSeparator();
074        }
075        @Override
076        public boolean supportLayers(List<Layer> layers) {
077            return false;
078        }
079    }
080
081    public static final String VISIBLE_PROP = Layer.class.getName() + ".visible";
082    public static final String OPACITY_PROP = Layer.class.getName() + ".opacity";
083    public static final String NAME_PROP = Layer.class.getName() + ".name";
084
085    public static final int ICON_SIZE = 16;
086
087    /** keeps track of property change listeners */
088    protected PropertyChangeSupport propertyChangeSupport;
089
090    /**
091     * The visibility state of the layer.
092     *
093     */
094    private boolean visible = true;
095
096    /**
097     * The opacity of the layer.
098     *
099     */
100    private double opacity = 1;
101
102    /**
103     * The layer should be handled as a background layer in automatic handling
104     *
105     */
106    private boolean background = false;
107
108    /**
109     * The name of this layer.
110     *
111     */
112    private  String name;
113
114    /**
115     * If a file is associated with this layer, this variable should be set to it.
116     */
117    private File associatedFile;
118
119    /**
120     * Create the layer and fill in the necessary components.
121     */
122    public Layer(String name) {
123        this.propertyChangeSupport = new PropertyChangeSupport(this);
124        setName(name);
125    }
126
127    /**
128     * Initialization code, that depends on Main.map.mapView.
129     *
130     * It is always called in the event dispatching thread.
131     * Note that Main.map is null as long as no layer has been added, so do
132     * not execute code in the constructor, that assumes Main.map.mapView is
133     * not null. Instead override this method.
134     */
135    public void hookUpMapView() {
136    }
137
138    /**
139     * Paint the dataset using the engine set.
140     * @param mv The object that can translate GeoPoints to screen coordinates.
141     */
142    @Override
143    public abstract void paint(Graphics2D g, MapView mv, Bounds box);
144
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    public abstract 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    public abstract 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    public abstract 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    public abstract boolean isMergable(Layer other);
180
181    public abstract void visitBoundingBox(BoundingXYVisitor v);
182
183    public abstract Object getInfoComponent();
184
185    /**
186     * Determines if info dialog can be resized (false by default).
187     * @return {@code true} if the info dialog can be resized, {@code false} otherwise
188     * @since 6708
189     */
190    public boolean isInfoResizable() {
191        return false;
192    }
193
194    /**
195     * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
196     * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
197     * have correct equals implementation.
198     *
199     * Use SeparatorLayerAction.INSTANCE instead of new JSeparator
200     *
201     */
202    public abstract Action[] getMenuEntries();
203
204    /**
205     * Called, when the layer is removed from the mapview and is going to be
206     * destroyed.
207     *
208     * This is because the Layer constructor can not add itself safely as listener
209     * to the layerlist dialog, because there may be no such dialog yet (loaded
210     * via command line parameter).
211     */
212    @Override
213    public void destroy() {}
214
215    public File getAssociatedFile() { return associatedFile; }
216    public void setAssociatedFile(File file) { associatedFile = file; }
217
218    /**
219     * Replies the name of the layer
220     *
221     * @return the name of the layer
222     */
223    public String getName() {
224        return name;
225    }
226
227    /**
228     * Sets the name of the layer
229     *
230     *@param name the name. If null, the name is set to the empty string.
231     *
232     */
233    public final void setName(String name) {
234        if (name == null) {
235            name = "";
236        }
237        String oldValue = this.name;
238        this.name = name;
239        if (!this.name.equals(oldValue)) {
240            propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
241        }
242    }
243
244    /**
245     * Replies true if this layer is a background layer
246     *
247     * @return true if this layer is a background layer
248     */
249    public boolean isBackgroundLayer() {
250        return background;
251    }
252
253    /**
254     * Sets whether this layer is a background layer
255     *
256     * @param background true, if this layer is a background layer
257     */
258    public void setBackgroundLayer(boolean background) {
259        this.background = background;
260    }
261
262    /**
263     * Sets the visibility of this layer. Emits property change event for
264     * property {@link #VISIBLE_PROP}.
265     *
266     * @param visible true, if the layer is visible; false, otherwise.
267     */
268    public void setVisible(boolean visible) {
269        boolean oldValue = isVisible();
270        this.visible  = visible;
271        if (visible && opacity == 0) {
272            setOpacity(1);
273        } else if (oldValue != isVisible()) {
274            fireVisibleChanged(oldValue, isVisible());
275        }
276    }
277
278    /**
279     * Replies true if this layer is visible. False, otherwise.
280     * @return  true if this layer is visible. False, otherwise.
281     */
282    public boolean isVisible() {
283        return visible && opacity != 0;
284    }
285
286    public double getOpacity() {
287        return opacity;
288    }
289
290    public void setOpacity(double opacity) {
291        if (!(opacity >= 0 && opacity <= 1))
292            throw new IllegalArgumentException("Opacity value must be between 0 and 1");
293        double oldOpacity = getOpacity();
294        boolean oldVisible = isVisible();
295        this.opacity = opacity;
296        if (oldOpacity != getOpacity()) {
297            fireOpacityChanged(oldOpacity, getOpacity());
298        }
299        if (oldVisible != isVisible()) {
300            fireVisibleChanged(oldVisible, isVisible());
301        }
302    }
303
304    /**
305     * Toggles the visibility state of this layer.
306     */
307    public void toggleVisible() {
308        setVisible(!isVisible());
309    }
310
311    /**
312     * Adds a {@link PropertyChangeListener}
313     *
314     * @param listener the listener
315     */
316    public void addPropertyChangeListener(PropertyChangeListener listener) {
317        propertyChangeSupport.addPropertyChangeListener(listener);
318    }
319
320    /**
321     * Removes a {@link PropertyChangeListener}
322     *
323     * @param listener the listener
324     */
325    public void removePropertyChangeListener(PropertyChangeListener listener) {
326        propertyChangeSupport.removePropertyChangeListener(listener);
327    }
328
329    /**
330     * fires a property change for the property {@link #VISIBLE_PROP}
331     *
332     * @param oldValue the old value
333     * @param newValue the new value
334     */
335    protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
336        propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
337    }
338
339    /**
340     * fires a property change for the property {@link #OPACITY_PROP}
341     *
342     * @param oldValue the old value
343     * @param newValue the new value
344     */
345    protected void fireOpacityChanged(double oldValue, double newValue) {
346        propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
347    }
348
349    /**
350     * Check changed status of layer
351     *
352     * @return True if layer was changed since last paint
353     */
354    public boolean isChanged() {
355        return true;
356    }
357
358    /**
359     * allows to check whether a projection is supported or not
360     *
361     * @return True if projection is supported for this layer
362     */
363    public boolean isProjectionSupported(Projection proj) {
364        return true;
365    }
366
367    /**
368     * Specify user information about projections
369     *
370     * @return User readable text telling about supported projections
371     */
372    public String nameSupportedProjections() {
373        return tr("All projections are supported");
374    }
375
376    /**
377     * The action to save a layer
378     *
379     */
380    public static class LayerSaveAction extends AbstractAction {
381        private Layer layer;
382        public LayerSaveAction(Layer layer) {
383            putValue(SMALL_ICON, ImageProvider.get("save"));
384            putValue(SHORT_DESCRIPTION, tr("Save the current data."));
385            putValue(NAME, tr("Save"));
386            setEnabled(true);
387            this.layer = layer;
388        }
389
390        @Override
391        public void actionPerformed(ActionEvent e) {
392            SaveAction.getInstance().doSave(layer);
393        }
394    }
395
396    public static class LayerSaveAsAction extends AbstractAction {
397        private Layer layer;
398        public LayerSaveAsAction(Layer layer) {
399            putValue(SMALL_ICON, ImageProvider.get("save_as"));
400            putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
401            putValue(NAME, tr("Save As..."));
402            setEnabled(true);
403            this.layer = layer;
404        }
405
406        @Override
407        public void actionPerformed(ActionEvent e) {
408            SaveAsAction.getInstance().doSave(layer);
409        }
410    }
411
412    public static class LayerGpxExportAction extends AbstractAction {
413        private Layer layer;
414        public LayerGpxExportAction(Layer layer) {
415            putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
416            putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
417            putValue(NAME, tr("Export to GPX..."));
418            setEnabled(true);
419            this.layer = layer;
420        }
421
422        @Override
423        public void actionPerformed(ActionEvent e) {
424            new GpxExportAction().export(layer);
425        }
426    }
427
428    /* --------------------------------------------------------------------------------- */
429    /* interface ProjectionChangeListener                                                */
430    /* --------------------------------------------------------------------------------- */
431    @Override
432    public void projectionChanged(Projection oldValue, Projection newValue) {
433        if(!isProjectionSupported(newValue)) {
434            JOptionPane.showMessageDialog(Main.parent,
435                    tr("The layer {0} does not support the new projection {1}.\n{2}\n"
436                            + "Change the projection again or remove the layer.",
437                            getName(), newValue.toCode(), nameSupportedProjections()),
438                            tr("Warning"),
439                            JOptionPane.WARNING_MESSAGE);
440        }
441    }
442
443    /**
444     * Initializes the layer after a successful load of data from a file
445     * @since 5459
446     */
447    public void onPostLoadFromFile() {
448        // To be overriden if needed
449    }
450
451    /**
452     * Replies the savable state of this layer (i.e if it can be saved through a "File-&gt;Save" dialog).
453     * @return true if this layer can be saved to a file
454     * @since 5459
455     */
456    public boolean isSavable() {
457        return false;
458    }
459
460    /**
461     * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
462     * @return <code>true</code>, if it is safe to save.
463     * @since 5459
464     */
465    public boolean checkSaveConditions() {
466        return true;
467    }
468
469    /**
470     * Creates a new "Save" dialog for this layer and makes it visible.<br>
471     * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
472     * @return The output {@code File}
473     * @since 5459
474     * @see SaveActionBase#createAndOpenSaveFileChooser
475     */
476    public File createAndOpenSaveFileChooser() {
477        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
478    }
479}