source: josm/trunk/src/org/openstreetmap/josm/gui/layer/Layer.java@ 8542

Last change on this file since 8542 was 8542, checked in by wiktorn, 9 years ago

Add warnings about supported projections for WMS layers

  • Property svn:eol-style set to native
File size: 15.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Graphics2D;
9import java.awt.event.ActionEvent;
10import java.beans.PropertyChangeListener;
11import java.beans.PropertyChangeSupport;
12import java.io.File;
13import java.util.List;
14
15import javax.swing.AbstractAction;
16import javax.swing.Action;
17import javax.swing.Icon;
18import javax.swing.JOptionPane;
19import javax.swing.JSeparator;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.actions.GpxExportAction;
23import org.openstreetmap.josm.actions.SaveAction;
24import org.openstreetmap.josm.actions.SaveActionBase;
25import org.openstreetmap.josm.actions.SaveAsAction;
26import org.openstreetmap.josm.data.Bounds;
27import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
28import org.openstreetmap.josm.data.projection.Projection;
29import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
30import org.openstreetmap.josm.gui.MapView;
31import org.openstreetmap.josm.tools.Destroyable;
32import org.openstreetmap.josm.tools.ImageProvider;
33import org.openstreetmap.josm.tools.Utils;
34
35/**
36 * A layer encapsulates the gui component of one dataset and its representation.
37 *
38 * Some layers may display data directly imported from OSM server. Other only
39 * display background images. Some can be edited, some not. Some are static and
40 * other changes dynamically (auto-updated).
41 *
42 * Layers can be visible or not. Most actions the user can do applies only on
43 * selected layers. The available actions depend on the selected layers too.
44 *
45 * All layers are managed by the MapView. They are displayed in a list to the
46 * right of the screen.
47 *
48 * @author imi
49 */
50public abstract class Layer implements Destroyable, MapViewPaintable, ProjectionChangeListener {
51
52 public interface LayerAction {
53 boolean supportLayers(List<Layer> layers);
54
55 Component createMenuComponent();
56 }
57
58 public interface MultiLayerAction {
59 Action getMultiLayerAction(List<Layer> layers);
60 }
61
62 /**
63 * Special class that can be returned by getMenuEntries when JSeparator needs to be created
64 *
65 */
66 public static class SeparatorLayerAction extends AbstractAction implements LayerAction {
67 public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
68
69 @Override
70 public void actionPerformed(ActionEvent e) {
71 throw new UnsupportedOperationException();
72 }
73
74 @Override
75 public Component createMenuComponent() {
76 return new JSeparator();
77 }
78
79 @Override
80 public boolean supportLayers(List<Layer> layers) {
81 return false;
82 }
83 }
84
85 public static final String VISIBLE_PROP = Layer.class.getName() + ".visible";
86 public static final String OPACITY_PROP = Layer.class.getName() + ".opacity";
87 public static final String NAME_PROP = Layer.class.getName() + ".name";
88
89 public static final int ICON_SIZE = 16;
90
91 /** keeps track of property change listeners */
92 protected PropertyChangeSupport propertyChangeSupport;
93
94 /**
95 * The visibility state of the layer.
96 *
97 */
98 private boolean visible = true;
99
100 /**
101 * The opacity of the layer.
102 *
103 */
104 private double opacity = 1;
105
106 /**
107 * The layer should be handled as a background layer in automatic handling
108 *
109 */
110 private boolean background = false;
111
112 /**
113 * The name of this layer.
114 *
115 */
116 private String name;
117
118 /**
119 * If a file is associated with this layer, this variable should be set to it.
120 */
121 private File associatedFile;
122
123 /**
124 * Create the layer and fill in the necessary components.
125 * @param name Layer name
126 */
127 public Layer(String name) {
128 this.propertyChangeSupport = new PropertyChangeSupport(this);
129 setName(name);
130 }
131
132 /**
133 * Initialization code, that depends on Main.map.mapView.
134 *
135 * It is always called in the event dispatching thread.
136 * Note that Main.map is null as long as no layer has been added, so do
137 * not execute code in the constructor, that assumes Main.map.mapView is
138 * not null. Instead override this method.
139 */
140 public void hookUpMapView() {
141 }
142
143 /**
144 * Paint the dataset using the engine set.
145 * @param mv The object that can translate GeoPoints to screen coordinates.
146 */
147 @Override
148 public abstract void paint(Graphics2D g, MapView mv, Bounds box);
149
150 /**
151 * Return a representative small image for this layer. The image must not
152 * be larger than 64 pixel in any dimension.
153 */
154 public abstract Icon getIcon();
155
156 /**
157 * Return a Color for this layer. Return null when no color specified.
158 * @param ignoreCustom Custom color should return null, as no default color
159 * is used. When this is true, then even for custom coloring the base
160 * color is returned - mainly for layer internal use.
161 */
162 public Color getColor(boolean ignoreCustom) {
163 return null;
164 }
165
166 /**
167 * @return A small tooltip hint about some statistics for this layer.
168 */
169 public abstract String getToolTipText();
170
171 /**
172 * Merges the given layer into this layer. Throws if the layer types are
173 * incompatible.
174 * @param from The layer that get merged into this one. After the merge,
175 * the other layer is not usable anymore and passing to one others
176 * mergeFrom should be one of the last things to do with a layer.
177 */
178 public abstract void mergeFrom(Layer from);
179
180 /**
181 * @param other The other layer that is tested to be mergable with this.
182 * @return Whether the other layer can be merged into this layer.
183 */
184 public abstract boolean isMergable(Layer other);
185
186 public abstract void visitBoundingBox(BoundingXYVisitor v);
187
188 public abstract Object getInfoComponent();
189
190 /**
191 * Determines if info dialog can be resized (false by default).
192 * @return {@code true} if the info dialog can be resized, {@code false} otherwise
193 * @since 6708
194 */
195 public boolean isInfoResizable() {
196 return false;
197 }
198
199 /**
200 * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
201 * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
202 * have correct equals implementation.
203 *
204 * Use SeparatorLayerAction.INSTANCE instead of new JSeparator
205 *
206 */
207 public abstract Action[] getMenuEntries();
208
209 /**
210 * Called, when the layer is removed from the mapview and is going to be destroyed.
211 *
212 * This is because the Layer constructor can not add itself safely as listener
213 * to the layerlist dialog, because there may be no such dialog yet (loaded
214 * via command line parameter).
215 */
216 @Override
217 public void destroy() {
218 // Override in subclasses if needed
219 }
220
221 public File getAssociatedFile() {
222 return associatedFile;
223 }
224
225 public void setAssociatedFile(File file) {
226 associatedFile = file;
227 }
228
229 /**
230 * Replies the name of the layer
231 *
232 * @return the name of the layer
233 */
234 public String getName() {
235 return name;
236 }
237
238 /**
239 * Sets the name of the layer
240 *
241 *@param name the name. If null, the name is set to the empty string.
242 *
243 */
244 public final void setName(String name) {
245 if (name == null) {
246 name = "";
247 }
248 String oldValue = this.name;
249 this.name = name;
250 if (!this.name.equals(oldValue)) {
251 propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
252 }
253 }
254
255 /**
256 * Replies true if this layer is a background layer
257 *
258 * @return true if this layer is a background layer
259 */
260 public boolean isBackgroundLayer() {
261 return background;
262 }
263
264 /**
265 * Sets whether this layer is a background layer
266 *
267 * @param background true, if this layer is a background layer
268 */
269 public void setBackgroundLayer(boolean background) {
270 this.background = background;
271 }
272
273 /**
274 * Sets the visibility of this layer. Emits property change event for
275 * property {@link #VISIBLE_PROP}.
276 *
277 * @param visible true, if the layer is visible; false, otherwise.
278 */
279 public void setVisible(boolean visible) {
280 boolean oldValue = isVisible();
281 this.visible = visible;
282 if (visible && opacity == 0) {
283 setOpacity(1);
284 } else if (oldValue != isVisible()) {
285 fireVisibleChanged(oldValue, isVisible());
286 }
287 }
288
289 /**
290 * Replies true if this layer is visible. False, otherwise.
291 * @return true if this layer is visible. False, otherwise.
292 */
293 public boolean isVisible() {
294 return visible && opacity != 0;
295 }
296
297 public double getOpacity() {
298 return opacity;
299 }
300
301 public void setOpacity(double opacity) {
302 if (!(opacity >= 0 && opacity <= 1))
303 throw new IllegalArgumentException("Opacity value must be between 0 and 1");
304 double oldOpacity = getOpacity();
305 boolean oldVisible = isVisible();
306 this.opacity = opacity;
307 if (!Utils.equalsEpsilon(oldOpacity, getOpacity())) {
308 fireOpacityChanged(oldOpacity, getOpacity());
309 }
310 if (oldVisible != isVisible()) {
311 fireVisibleChanged(oldVisible, isVisible());
312 }
313 }
314
315 /**
316 * Toggles the visibility state of this layer.
317 */
318 public void toggleVisible() {
319 setVisible(!isVisible());
320 }
321
322 /**
323 * Adds a {@link PropertyChangeListener}
324 *
325 * @param listener the listener
326 */
327 public void addPropertyChangeListener(PropertyChangeListener listener) {
328 propertyChangeSupport.addPropertyChangeListener(listener);
329 }
330
331 /**
332 * Removes a {@link PropertyChangeListener}
333 *
334 * @param listener the listener
335 */
336 public void removePropertyChangeListener(PropertyChangeListener listener) {
337 propertyChangeSupport.removePropertyChangeListener(listener);
338 }
339
340 /**
341 * fires a property change for the property {@link #VISIBLE_PROP}
342 *
343 * @param oldValue the old value
344 * @param newValue the new value
345 */
346 protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
347 propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
348 }
349
350 /**
351 * fires a property change for the property {@link #OPACITY_PROP}
352 *
353 * @param oldValue the old value
354 * @param newValue the new value
355 */
356 protected void fireOpacityChanged(double oldValue, double newValue) {
357 propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
358 }
359
360 /**
361 * Check changed status of layer
362 *
363 * @return True if layer was changed since last paint
364 */
365 public boolean isChanged() {
366 return true;
367 }
368
369 /**
370 * allows to check whether a projection is supported or not
371 *
372 * @return True if projection is supported for this layer
373 */
374 public boolean isProjectionSupported(Projection proj) {
375 return true;
376 }
377
378 /**
379 * Specify user information about projections
380 *
381 * @return User readable text telling about supported projections
382 */
383 public String nameSupportedProjections() {
384 return tr("All projections are supported");
385 }
386
387 /**
388 * The action to save a layer
389 *
390 */
391 public static class LayerSaveAction extends AbstractAction {
392 private final transient Layer layer;
393
394 public LayerSaveAction(Layer layer) {
395 putValue(SMALL_ICON, ImageProvider.get("save"));
396 putValue(SHORT_DESCRIPTION, tr("Save the current data."));
397 putValue(NAME, tr("Save"));
398 setEnabled(true);
399 this.layer = layer;
400 }
401
402 @Override
403 public void actionPerformed(ActionEvent e) {
404 SaveAction.getInstance().doSave(layer);
405 }
406 }
407
408 public static class LayerSaveAsAction extends AbstractAction {
409 private final transient Layer layer;
410
411 public LayerSaveAsAction(Layer layer) {
412 putValue(SMALL_ICON, ImageProvider.get("save_as"));
413 putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
414 putValue(NAME, tr("Save As..."));
415 setEnabled(true);
416 this.layer = layer;
417 }
418
419 @Override
420 public void actionPerformed(ActionEvent e) {
421 SaveAsAction.getInstance().doSave(layer);
422 }
423 }
424
425 public static class LayerGpxExportAction extends AbstractAction {
426 private final transient Layer layer;
427
428 public LayerGpxExportAction(Layer layer) {
429 putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
430 putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
431 putValue(NAME, tr("Export to GPX..."));
432 setEnabled(true);
433 this.layer = layer;
434 }
435
436 @Override
437 public void actionPerformed(ActionEvent e) {
438 new GpxExportAction().export(layer);
439 }
440 }
441
442 /* --------------------------------------------------------------------------------- */
443 /* interface ProjectionChangeListener */
444 /* --------------------------------------------------------------------------------- */
445 @Override
446 public void projectionChanged(Projection oldValue, Projection newValue) {
447 if (!isProjectionSupported(newValue)) {
448 JOptionPane.showMessageDialog(Main.parent,
449 tr("The layer {0} does not support the new projection {1}.\n"
450 + "Supported projections are: {2}\n"
451 + "Change the projection again or remove the layer.",
452 getName(), newValue.toCode(), nameSupportedProjections()),
453 tr("Warning"),
454 JOptionPane.WARNING_MESSAGE);
455 }
456 }
457
458 /**
459 * Initializes the layer after a successful load of data from a file
460 * @since 5459
461 */
462 public void onPostLoadFromFile() {
463 // To be overriden if needed
464 }
465
466 /**
467 * Replies the savable state of this layer (i.e if it can be saved through a "File-&gt;Save" dialog).
468 * @return true if this layer can be saved to a file
469 * @since 5459
470 */
471 public boolean isSavable() {
472 return false;
473 }
474
475 /**
476 * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
477 * @return <code>true</code>, if it is safe to save.
478 * @since 5459
479 */
480 public boolean checkSaveConditions() {
481 return true;
482 }
483
484 /**
485 * Creates a new "Save" dialog for this layer and makes it visible.<br>
486 * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
487 * @return The output {@code File}
488 * @see SaveActionBase#createAndOpenSaveFileChooser
489 * @since 5459
490 */
491 public File createAndOpenSaveFileChooser() {
492 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
493 }
494}
Note: See TracBrowser for help on using the repository browser.