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

Last change on this file since 8384 was 8384, checked in by Don-vip, 9 years ago

squid:S1244 - Floating point numbers should not be tested for equality

  • Property svn:eol-style set to native
File size: 15.0 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 Component createMenuComponent();
55 }
56
57 public interface MultiLayerAction {
58 Action getMultiLayerAction(List<Layer> layers);
59 }
60
61 /**
62 * Special class that can be returned by getMenuEntries when JSeparator needs to be created
63 *
64 */
65 public static class SeparatorLayerAction extends AbstractAction implements LayerAction {
66 public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
67 @Override
68 public void actionPerformed(ActionEvent e) {
69 throw new UnsupportedOperationException();
70 }
71 @Override
72 public Component createMenuComponent() {
73 return new JSeparator();
74 }
75 @Override
76 public boolean supportLayers(List<Layer> layers) {
77 return false;
78 }
79 }
80
81 public static final String VISIBLE_PROP = Layer.class.getName() + ".visible";
82 public static final String OPACITY_PROP = Layer.class.getName() + ".opacity";
83 public static final String NAME_PROP = Layer.class.getName() + ".name";
84
85 public static final int ICON_SIZE = 16;
86
87 /** keeps track of property change listeners */
88 protected PropertyChangeSupport propertyChangeSupport;
89
90 /**
91 * The visibility state of the layer.
92 *
93 */
94 private boolean visible = true;
95
96 /**
97 * The opacity of the layer.
98 *
99 */
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 && Double.doubleToRawLongBits(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 && Double.doubleToRawLongBits(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 (!Utils.equalsEpsilon(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 final transient 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 final transient 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 final transient 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}
Note: See TracBrowser for help on using the repository browser.