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     * @param name Layer name
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    public abstract void paint(Graphics2D g, MapView mv, Bounds box);
145
146    /**
147     * Return a representative small image for this layer. The image must not
148     * be larger than 64 pixel in any dimension.
149     */
150    public abstract Icon getIcon();
151
152    /**
153     * Return a Color for this layer. Return null when no color specified.
154     * @param ignoreCustom Custom color should return null, as no default color
155     *      is used. When this is true, then even for custom coloring the base
156     *      color is returned - mainly for layer internal use.
157     */
158    public Color getColor(boolean ignoreCustom) {
159        return null;
160    }
161
162    /**
163     * @return A small tooltip hint about some statistics for this layer.
164     */
165    public abstract String getToolTipText();
166
167    /**
168     * Merges the given layer into this layer. Throws if the layer types are
169     * incompatible.
170     * @param from The layer that get merged into this one. After the merge,
171     *      the other layer is not usable anymore and passing to one others
172     *      mergeFrom should be one of the last things to do with a layer.
173     */
174    public abstract void mergeFrom(Layer from);
175
176    /**
177     * @param other The other layer that is tested to be mergable with this.
178     * @return Whether the other layer can be merged into this layer.
179     */
180    public abstract boolean isMergable(Layer other);
181
182    public abstract void visitBoundingBox(BoundingXYVisitor v);
183
184    public abstract Object getInfoComponent();
185
186    /**
187     * Determines if info dialog can be resized (false by default).
188     * @return {@code true} if the info dialog can be resized, {@code false} otherwise
189     * @since 6708
190     */
191    public boolean isInfoResizable() {
192        return false;
193    }
194
195    /**
196     * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
197     * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
198     * have correct equals implementation.
199     *
200     * Use SeparatorLayerAction.INSTANCE instead of new JSeparator
201     *
202     */
203    public abstract Action[] getMenuEntries();
204
205    /**
206     * Called, when the layer is removed from the mapview and is going to be
207     * destroyed.
208     *
209     * This is because the Layer constructor can not add itself safely as listener
210     * to the layerlist dialog, because there may be no such dialog yet (loaded
211     * via command line parameter).
212     */
213    @Override
214    public void destroy() {}
215
216    public File getAssociatedFile() { return associatedFile; }
217    public void setAssociatedFile(File file) { associatedFile = file; }
218
219    /**
220     * Replies the name of the layer
221     *
222     * @return the name of the layer
223     */
224    public String getName() {
225        return name;
226    }
227
228    /**
229     * Sets the name of the layer
230     *
231     *@param name the name. If null, the name is set to the empty string.
232     *
233     */
234    public final void setName(String name) {
235        if (name == null) {
236            name = "";
237        }
238        String oldValue = this.name;
239        this.name = name;
240        if (!this.name.equals(oldValue)) {
241            propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
242        }
243    }
244
245    /**
246     * Replies true if this layer is a background layer
247     *
248     * @return true if this layer is a background layer
249     */
250    public boolean isBackgroundLayer() {
251        return background;
252    }
253
254    /**
255     * Sets whether this layer is a background layer
256     *
257     * @param background true, if this layer is a background layer
258     */
259    public void setBackgroundLayer(boolean background) {
260        this.background = background;
261    }
262
263    /**
264     * Sets the visibility of this layer. Emits property change event for
265     * property {@link #VISIBLE_PROP}.
266     *
267     * @param visible true, if the layer is visible; false, otherwise.
268     */
269    public void setVisible(boolean visible) {
270        boolean oldValue = isVisible();
271        this.visible  = visible;
272        if (visible && opacity == 0) {
273            setOpacity(1);
274        } else if (oldValue != isVisible()) {
275            fireVisibleChanged(oldValue, isVisible());
276        }
277    }
278
279    /**
280     * Replies true if this layer is visible. False, otherwise.
281     * @return  true if this layer is visible. False, otherwise.
282     */
283    public boolean isVisible() {
284        return visible && opacity != 0;
285    }
286
287    public double getOpacity() {
288        return opacity;
289    }
290
291    public void setOpacity(double opacity) {
292        if (!(opacity >= 0 && opacity <= 1))
293            throw new IllegalArgumentException("Opacity value must be between 0 and 1");
294        double oldOpacity = getOpacity();
295        boolean oldVisible = isVisible();
296        this.opacity = opacity;
297        if (oldOpacity != getOpacity()) {
298            fireOpacityChanged(oldOpacity, getOpacity());
299        }
300        if (oldVisible != isVisible()) {
301            fireVisibleChanged(oldVisible, isVisible());
302        }
303    }
304
305    /**
306     * Toggles the visibility state of this layer.
307     */
308    public void toggleVisible() {
309        setVisible(!isVisible());
310    }
311
312    /**
313     * Adds a {@link PropertyChangeListener}
314     *
315     * @param listener the listener
316     */
317    public void addPropertyChangeListener(PropertyChangeListener listener) {
318        propertyChangeSupport.addPropertyChangeListener(listener);
319    }
320
321    /**
322     * Removes a {@link PropertyChangeListener}
323     *
324     * @param listener the listener
325     */
326    public void removePropertyChangeListener(PropertyChangeListener listener) {
327        propertyChangeSupport.removePropertyChangeListener(listener);
328    }
329
330    /**
331     * fires a property change for the property {@link #VISIBLE_PROP}
332     *
333     * @param oldValue the old value
334     * @param newValue the new value
335     */
336    protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
337        propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
338    }
339
340    /**
341     * fires a property change for the property {@link #OPACITY_PROP}
342     *
343     * @param oldValue the old value
344     * @param newValue the new value
345     */
346    protected void fireOpacityChanged(double oldValue, double newValue) {
347        propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
348    }
349
350    /**
351     * Check changed status of layer
352     *
353     * @return True if layer was changed since last paint
354     */
355    public boolean isChanged() {
356        return true;
357    }
358
359    /**
360     * allows to check whether a projection is supported or not
361     *
362     * @return True if projection is supported for this layer
363     */
364    public boolean isProjectionSupported(Projection proj) {
365        return true;
366    }
367
368    /**
369     * Specify user information about projections
370     *
371     * @return User readable text telling about supported projections
372     */
373    public String nameSupportedProjections() {
374        return tr("All projections are supported");
375    }
376
377    /**
378     * The action to save a layer
379     *
380     */
381    public static class LayerSaveAction extends AbstractAction {
382        private Layer layer;
383        public LayerSaveAction(Layer layer) {
384            putValue(SMALL_ICON, ImageProvider.get("save"));
385            putValue(SHORT_DESCRIPTION, tr("Save the current data."));
386            putValue(NAME, tr("Save"));
387            setEnabled(true);
388            this.layer = layer;
389        }
390
391        @Override
392        public void actionPerformed(ActionEvent e) {
393            SaveAction.getInstance().doSave(layer);
394        }
395    }
396
397    public static class LayerSaveAsAction extends AbstractAction {
398        private Layer layer;
399        public LayerSaveAsAction(Layer layer) {
400            putValue(SMALL_ICON, ImageProvider.get("save_as"));
401            putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
402            putValue(NAME, tr("Save As..."));
403            setEnabled(true);
404            this.layer = layer;
405        }
406
407        @Override
408        public void actionPerformed(ActionEvent e) {
409            SaveAsAction.getInstance().doSave(layer);
410        }
411    }
412
413    public static class LayerGpxExportAction extends AbstractAction {
414        private Layer layer;
415        public LayerGpxExportAction(Layer layer) {
416            putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
417            putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
418            putValue(NAME, tr("Export to GPX..."));
419            setEnabled(true);
420            this.layer = layer;
421        }
422
423        @Override
424        public void actionPerformed(ActionEvent e) {
425            new GpxExportAction().export(layer);
426        }
427    }
428
429    /* --------------------------------------------------------------------------------- */
430    /* interface ProjectionChangeListener                                                */
431    /* --------------------------------------------------------------------------------- */
432    @Override
433    public void projectionChanged(Projection oldValue, Projection newValue) {
434        if(!isProjectionSupported(newValue)) {
435            JOptionPane.showMessageDialog(Main.parent,
436                    tr("The layer {0} does not support the new projection {1}.\n{2}\n"
437                            + "Change the projection again or remove the layer.",
438                            getName(), newValue.toCode(), nameSupportedProjections()),
439                            tr("Warning"),
440                            JOptionPane.WARNING_MESSAGE);
441        }
442    }
443
444    /**
445     * Initializes the layer after a successful load of data from a file
446     * @since 5459
447     */
448    public void onPostLoadFromFile() {
449        // To be overriden if needed
450    }
451
452    /**
453     * Replies the savable state of this layer (i.e if it can be saved through a "File-&gt;Save" dialog).
454     * @return true if this layer can be saved to a file
455     * @since 5459
456     */
457    public boolean isSavable() {
458        return false;
459    }
460
461    /**
462     * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
463     * @return <code>true</code>, if it is safe to save.
464     * @since 5459
465     */
466    public boolean checkSaveConditions() {
467        return true;
468    }
469
470    /**
471     * Creates a new "Save" dialog for this layer and makes it visible.<br>
472     * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
473     * @return The output {@code File}
474     * @since 5459
475     * @see SaveActionBase#createAndOpenSaveFileChooser
476     */
477    public File createAndOpenSaveFileChooser() {
478        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
479    }
480}