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

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

checkstyle

  • Property svn:eol-style set to native
File size: 17.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 public static final String FILTER_STATE_PROP = Layer.class.getName() + ".filterstate";
89
90 public static final int ICON_SIZE = 16;
91
92 /** keeps track of property change listeners */
93 protected PropertyChangeSupport propertyChangeSupport;
94
95 /**
96 * The visibility state of the layer.
97 *
98 */
99 private boolean visible = true;
100
101 /**
102 * The opacity of the layer.
103 *
104 */
105 private double opacity = 1;
106
107 /**
108 * The layer should be handled as a background layer in automatic handling
109 *
110 */
111 private boolean background;
112
113 /**
114 * The name of this layer.
115 *
116 */
117 private String name;
118
119 /**
120 * If a file is associated with this layer, this variable should be set to it.
121 */
122 private File associatedFile;
123
124 /**
125 * Create the layer and fill in the necessary components.
126 * @param name Layer name
127 */
128 public Layer(String name) {
129 this.propertyChangeSupport = new PropertyChangeSupport(this);
130 setName(name);
131 }
132
133 /**
134 * Initialization code, that depends on Main.map.mapView.
135 *
136 * It is always called in the event dispatching thread.
137 * Note that Main.map is null as long as no layer has been added, so do
138 * not execute code in the constructor, that assumes Main.map.mapView is
139 * not null. Instead override this method.
140 *
141 * This implementation provides check, if JOSM will be able to use Layer. Layers
142 * using a lot of memory, which do know in advance, how much memory they use, should
143 * override {@link #estimateMemoryUsage() estimateMemoryUsage} method and give a hint.
144 *
145 * This allows for preemptive warning message for user, instead of failing later on
146 *
147 * Remember to call {@code super.hookUpMapView()} when overriding this method
148 */
149 public void hookUpMapView() {
150 // calculate total memory needed for all layers
151 long memoryBytesRequired = 50 * 1024 * 1024; // assumed minimum JOSM memory footprint
152 if (Main.map != null && Main.map.mapView != null) {
153 for (Layer layer: Main.map.mapView.getAllLayers()) {
154 memoryBytesRequired += layer.estimateMemoryUsage();
155 }
156 if (memoryBytesRequired > Runtime.getRuntime().maxMemory()) {
157 throw new IllegalArgumentException(
158 tr("To add another layer you need to allocate at least {0,number,#}MB memory to JOSM using -Xmx{0,number,#}M "
159 + "option (see http://forum.openstreetmap.org/viewtopic.php?id=25677).\n"
160 + "Currently you have {1,number,#}MB memory allocated for JOSM",
161 memoryBytesRequired / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024));
162 }
163 }
164 }
165
166 /**
167 * Paint the dataset using the engine set.
168 * @param mv The object that can translate GeoPoints to screen coordinates.
169 */
170 @Override
171 public abstract void paint(Graphics2D g, MapView mv, Bounds box);
172
173 /**
174 * Return a representative small image for this layer. The image must not
175 * be larger than 64 pixel in any dimension.
176 */
177 public abstract Icon getIcon();
178
179 /**
180 * Return a Color for this layer. Return null when no color specified.
181 * @param ignoreCustom Custom color should return null, as no default color
182 * is used. When this is true, then even for custom coloring the base
183 * color is returned - mainly for layer internal use.
184 */
185 public Color getColor(boolean ignoreCustom) {
186 return null;
187 }
188
189 /**
190 * @return A small tooltip hint about some statistics for this layer.
191 */
192 public abstract String getToolTipText();
193
194 /**
195 * Merges the given layer into this layer. Throws if the layer types are
196 * incompatible.
197 * @param from The layer that get merged into this one. After the merge,
198 * the other layer is not usable anymore and passing to one others
199 * mergeFrom should be one of the last things to do with a layer.
200 */
201 public abstract void mergeFrom(Layer from);
202
203 /**
204 * @param other The other layer that is tested to be mergable with this.
205 * @return Whether the other layer can be merged into this layer.
206 */
207 public abstract boolean isMergable(Layer other);
208
209 public abstract void visitBoundingBox(BoundingXYVisitor v);
210
211 public abstract Object getInfoComponent();
212
213 /**
214 * Determines if info dialog can be resized (false by default).
215 * @return {@code true} if the info dialog can be resized, {@code false} otherwise
216 * @since 6708
217 */
218 public boolean isInfoResizable() {
219 return false;
220 }
221
222 /**
223 * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
224 * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
225 * have correct equals implementation.
226 *
227 * Use SeparatorLayerAction.INSTANCE instead of new JSeparator
228 *
229 */
230 public abstract Action[] getMenuEntries();
231
232 /**
233 * Called, when the layer is removed from the mapview and is going to be destroyed.
234 *
235 * This is because the Layer constructor can not add itself safely as listener
236 * to the layerlist dialog, because there may be no such dialog yet (loaded
237 * via command line parameter).
238 */
239 @Override
240 public void destroy() {
241 // Override in subclasses if needed
242 }
243
244 public File getAssociatedFile() {
245 return associatedFile;
246 }
247
248 public void setAssociatedFile(File file) {
249 associatedFile = file;
250 }
251
252 /**
253 * Replies the name of the layer
254 *
255 * @return the name of the layer
256 */
257 public String getName() {
258 return name;
259 }
260
261 /**
262 * Sets the name of the layer
263 *
264 *@param name the name. If null, the name is set to the empty string.
265 *
266 */
267 public final void setName(String name) {
268 if (name == null) {
269 name = "";
270 }
271 String oldValue = this.name;
272 this.name = name;
273 if (!this.name.equals(oldValue)) {
274 propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
275 }
276 }
277
278 /**
279 * Replies true if this layer is a background layer
280 *
281 * @return true if this layer is a background layer
282 */
283 public boolean isBackgroundLayer() {
284 return background;
285 }
286
287 /**
288 * Sets whether this layer is a background layer
289 *
290 * @param background true, if this layer is a background layer
291 */
292 public void setBackgroundLayer(boolean background) {
293 this.background = background;
294 }
295
296 /**
297 * Sets the visibility of this layer. Emits property change event for
298 * property {@link #VISIBLE_PROP}.
299 *
300 * @param visible true, if the layer is visible; false, otherwise.
301 */
302 public void setVisible(boolean visible) {
303 boolean oldValue = isVisible();
304 this.visible = visible;
305 if (visible && opacity == 0) {
306 setOpacity(1);
307 } else if (oldValue != isVisible()) {
308 fireVisibleChanged(oldValue, isVisible());
309 }
310 }
311
312 /**
313 * Replies true if this layer is visible. False, otherwise.
314 * @return true if this layer is visible. False, otherwise.
315 */
316 public boolean isVisible() {
317 return visible && opacity != 0;
318 }
319
320 public double getOpacity() {
321 return opacity;
322 }
323
324 public void setOpacity(double opacity) {
325 if (!(opacity >= 0 && opacity <= 1))
326 throw new IllegalArgumentException("Opacity value must be between 0 and 1");
327 double oldOpacity = getOpacity();
328 boolean oldVisible = isVisible();
329 this.opacity = opacity;
330 if (!Utils.equalsEpsilon(oldOpacity, getOpacity())) {
331 fireOpacityChanged(oldOpacity, getOpacity());
332 }
333 if (oldVisible != isVisible()) {
334 fireVisibleChanged(oldVisible, isVisible());
335 }
336 }
337
338 /**
339 * Sets new state to the layer after applying {@link ImageProcessor}.
340 */
341 public void setFilterStateChanged() {
342 fireFilterStateChanged();
343 }
344
345 /**
346 * Toggles the visibility state of this layer.
347 */
348 public void toggleVisible() {
349 setVisible(!isVisible());
350 }
351
352 /**
353 * Adds a {@link PropertyChangeListener}
354 *
355 * @param listener the listener
356 */
357 public void addPropertyChangeListener(PropertyChangeListener listener) {
358 propertyChangeSupport.addPropertyChangeListener(listener);
359 }
360
361 /**
362 * Removes a {@link PropertyChangeListener}
363 *
364 * @param listener the listener
365 */
366 public void removePropertyChangeListener(PropertyChangeListener listener) {
367 propertyChangeSupport.removePropertyChangeListener(listener);
368 }
369
370 /**
371 * fires a property change for the property {@link #VISIBLE_PROP}
372 *
373 * @param oldValue the old value
374 * @param newValue the new value
375 */
376 protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
377 propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
378 }
379
380 /**
381 * fires a property change for the property {@link #OPACITY_PROP}
382 *
383 * @param oldValue the old value
384 * @param newValue the new value
385 */
386 protected void fireOpacityChanged(double oldValue, double newValue) {
387 propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
388 }
389
390 /**
391 * fires a property change for the property {@link #FILTER_STATE_PROP}.
392 */
393 protected void fireFilterStateChanged() {
394 propertyChangeSupport.firePropertyChange(FILTER_STATE_PROP, null, null);
395 }
396
397 /**
398 * Check changed status of layer
399 *
400 * @return True if layer was changed since last paint
401 */
402 public boolean isChanged() {
403 return true;
404 }
405
406 /**
407 * allows to check whether a projection is supported or not
408 *
409 * @return True if projection is supported for this layer
410 */
411 public boolean isProjectionSupported(Projection proj) {
412 return proj != null;
413 }
414
415 /**
416 * Specify user information about projections
417 *
418 * @return User readable text telling about supported projections
419 */
420 public String nameSupportedProjections() {
421 return tr("All projections are supported");
422 }
423
424 /**
425 * The action to save a layer
426 *
427 */
428 public static class LayerSaveAction extends AbstractAction {
429 private final transient Layer layer;
430
431 public LayerSaveAction(Layer layer) {
432 putValue(SMALL_ICON, ImageProvider.get("save"));
433 putValue(SHORT_DESCRIPTION, tr("Save the current data."));
434 putValue(NAME, tr("Save"));
435 setEnabled(true);
436 this.layer = layer;
437 }
438
439 @Override
440 public void actionPerformed(ActionEvent e) {
441 SaveAction.getInstance().doSave(layer);
442 }
443 }
444
445 public static class LayerSaveAsAction extends AbstractAction {
446 private final transient Layer layer;
447
448 public LayerSaveAsAction(Layer layer) {
449 putValue(SMALL_ICON, ImageProvider.get("save_as"));
450 putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
451 putValue(NAME, tr("Save As..."));
452 setEnabled(true);
453 this.layer = layer;
454 }
455
456 @Override
457 public void actionPerformed(ActionEvent e) {
458 SaveAsAction.getInstance().doSave(layer);
459 }
460 }
461
462 public static class LayerGpxExportAction extends AbstractAction {
463 private final transient Layer layer;
464
465 public LayerGpxExportAction(Layer layer) {
466 putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
467 putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
468 putValue(NAME, tr("Export to GPX..."));
469 setEnabled(true);
470 this.layer = layer;
471 }
472
473 @Override
474 public void actionPerformed(ActionEvent e) {
475 new GpxExportAction().export(layer);
476 }
477 }
478
479 /* --------------------------------------------------------------------------------- */
480 /* interface ProjectionChangeListener */
481 /* --------------------------------------------------------------------------------- */
482 @Override
483 public void projectionChanged(Projection oldValue, Projection newValue) {
484 if (!isProjectionSupported(newValue)) {
485 JOptionPane.showMessageDialog(Main.parent,
486 tr("The layer {0} does not support the new projection {1}.\n"
487 + "Supported projections are: {2}\n"
488 + "Change the projection again or remove the layer.",
489 getName(), newValue.toCode(), nameSupportedProjections()),
490 tr("Warning"),
491 JOptionPane.WARNING_MESSAGE);
492 }
493 }
494
495 /**
496 * Initializes the layer after a successful load of data from a file
497 * @since 5459
498 */
499 public void onPostLoadFromFile() {
500 // To be overriden if needed
501 }
502
503 /**
504 * Replies the savable state of this layer (i.e if it can be saved through a "File-&gt;Save" dialog).
505 * @return true if this layer can be saved to a file
506 * @since 5459
507 */
508 public boolean isSavable() {
509 return false;
510 }
511
512 /**
513 * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
514 * @return <code>true</code>, if it is safe to save.
515 * @since 5459
516 */
517 public boolean checkSaveConditions() {
518 return true;
519 }
520
521 /**
522 * Creates a new "Save" dialog for this layer and makes it visible.<br>
523 * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
524 * @return The output {@code File}
525 * @see SaveActionBase#createAndOpenSaveFileChooser
526 * @since 5459
527 */
528 public File createAndOpenSaveFileChooser() {
529 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
530 }
531
532 /**
533 * @return bytes that the tile will use. Needed for resource management
534 */
535 protected long estimateMemoryUsage() {
536 return 0;
537 }
538}
Note: See TracBrowser for help on using the repository browser.