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

Last change on this file since 9510 was 9510, checked in by bastiK, 8 years ago

applied #12391 - saving renamed layer replaces name (patch by kolesar + cosmetic changes)

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