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

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

see #13309 - Caching and notifying preferences (patch by michael2402) - gsoc-core

  • Property svn:eol-style set to native
File size: 20.4 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.event.ActionEvent;
9import java.beans.PropertyChangeListener;
10import java.beans.PropertyChangeSupport;
11import java.io.File;
12import java.util.List;
13
14import javax.swing.AbstractAction;
15import javax.swing.Action;
16import javax.swing.Icon;
17import javax.swing.JOptionPane;
18import javax.swing.JSeparator;
19import javax.swing.SwingUtilities;
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.ProjectionBounds;
27import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
28import org.openstreetmap.josm.data.preferences.AbstractProperty;
29import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
30import org.openstreetmap.josm.data.preferences.ColorProperty;
31import org.openstreetmap.josm.data.projection.Projection;
32import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
33import org.openstreetmap.josm.tools.Destroyable;
34import org.openstreetmap.josm.tools.ImageProvider;
35import org.openstreetmap.josm.tools.Utils;
36
37/**
38 * A layer encapsulates the gui component of one dataset and its representation.
39 *
40 * Some layers may display data directly imported from OSM server. Other only
41 * display background images. Some can be edited, some not. Some are static and
42 * other changes dynamically (auto-updated).
43 *
44 * Layers can be visible or not. Most actions the user can do applies only on
45 * selected layers. The available actions depend on the selected layers too.
46 *
47 * All layers are managed by the MapView. They are displayed in a list to the
48 * right of the screen.
49 *
50 * @author imi
51 */
52public abstract class Layer extends AbstractMapViewPaintable implements Destroyable, ProjectionChangeListener {
53
54 /**
55 * Action related to a single layer.
56 */
57 public interface LayerAction {
58
59 /**
60 * Determines if this action supports a given list of layers.
61 * @param layers list of layers
62 * @return {@code true} if this action supports the given list of layers, {@code false} otherwise
63 */
64 boolean supportLayers(List<Layer> layers);
65
66 /**
67 * Creates and return the menu component.
68 * @return the menu component
69 */
70 Component createMenuComponent();
71 }
72
73 /**
74 * Action related to several layers.
75 * @since 10600 (functional interface)
76 */
77 @FunctionalInterface
78 public interface MultiLayerAction {
79
80 /**
81 * Returns the action for a given list of layers.
82 * @param layers list of layers
83 * @return the action for the given list of layers
84 */
85 Action getMultiLayerAction(List<Layer> layers);
86 }
87
88 /**
89 * Special class that can be returned by getMenuEntries when JSeparator needs to be created
90 */
91 public static class SeparatorLayerAction extends AbstractAction implements LayerAction {
92 /** Unique instance */
93 public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
94
95 @Override
96 public void actionPerformed(ActionEvent e) {
97 throw new UnsupportedOperationException();
98 }
99
100 @Override
101 public Component createMenuComponent() {
102 return new JSeparator();
103 }
104
105 @Override
106 public boolean supportLayers(List<Layer> layers) {
107 return false;
108 }
109 }
110
111 public static final String VISIBLE_PROP = Layer.class.getName() + ".visible";
112 public static final String OPACITY_PROP = Layer.class.getName() + ".opacity";
113 public static final String NAME_PROP = Layer.class.getName() + ".name";
114 public static final String FILTER_STATE_PROP = Layer.class.getName() + ".filterstate";
115
116 /**
117 * keeps track of property change listeners
118 */
119 protected PropertyChangeSupport propertyChangeSupport;
120
121 /**
122 * The visibility state of the layer.
123 */
124 private boolean visible = true;
125
126 /**
127 * The opacity of the layer.
128 */
129 private double opacity = 1;
130
131 /**
132 * The layer should be handled as a background layer in automatic handling
133 */
134 private boolean background;
135
136 /**
137 * The name of this layer.
138 */
139 private String name;
140
141 /**
142 * This is set if user renamed this layer.
143 */
144 private boolean renamed;
145
146 /**
147 * If a file is associated with this layer, this variable should be set to it.
148 */
149 private File associatedFile;
150
151 private final ValueChangeListener<Object> invalidateListener = change -> invalidate();
152
153 /**
154 * Create the layer and fill in the necessary components.
155 * @param name Layer name
156 */
157 public Layer(String name) {
158 this.propertyChangeSupport = new PropertyChangeSupport(this);
159 setName(name);
160 }
161
162 /**
163 * Initialization code, that depends on Main.map.mapView.
164 *
165 * It is always called in the event dispatching thread.
166 * Note that Main.map is null as long as no layer has been added, so do
167 * not execute code in the constructor, that assumes Main.map.mapView is
168 * not null.
169 *
170 * If you need to execute code when this layer is added to the map view, use
171 * {@link #attachToMapView(org.openstreetmap.josm.gui.layer.MapViewPaintable.MapViewEvent)}
172 */
173 public void hookUpMapView() {
174 }
175
176 /**
177 * Return a representative small image for this layer. The image must not
178 * be larger than 64 pixel in any dimension.
179 * @return layer icon
180 */
181 public abstract Icon getIcon();
182
183 /**
184 * Return a Color for this layer. Return null when no color specified.
185 * @param ignoreCustom Custom color should return null, as no default color
186 * is used. When this is true, then even for custom coloring the base
187 * color is returned - mainly for layer internal use.
188 * @return layer color
189 * @deprecated Use the new {@link #getColorProperty()}. To be removed end of 2016.
190 */
191 @Deprecated
192 public Color getColor(boolean ignoreCustom) {
193 return null;
194 }
195
196 /**
197 * Gets the color property to use for this layer.
198 * @return The color property.
199 * @since 10824
200 */
201 public AbstractProperty<Color> getColorProperty() {
202 ColorProperty base = getBaseColorProperty();
203 if (base != null) {
204 // cannot cache this - name may change.
205 return base.getChildColor("layer " + getName());
206 } else {
207 return null;
208 }
209 }
210
211 /**
212 * Gets the color property that stores the default color for this layer.
213 * @return The property or <code>null</code> if this layer is not colored.
214 * @since 10824
215 */
216 protected ColorProperty getBaseColorProperty() {
217 return null;
218 }
219
220 private void addColorPropertyListener() {
221 AbstractProperty<Color> colorProperty = getColorProperty();
222 if (colorProperty != null) {
223 colorProperty.addWeakListener(invalidateListener);
224 }
225 }
226
227 private void removeColorPropertyListener() {
228 AbstractProperty<Color> colorProperty = getColorProperty();
229 if (colorProperty != null) {
230 colorProperty.removeListener(invalidateListener);
231 }
232 }
233
234 /**
235 * @return A small tooltip hint about some statistics for this layer.
236 */
237 public abstract String getToolTipText();
238
239 /**
240 * Merges the given layer into this layer. Throws if the layer types are
241 * incompatible.
242 * @param from The layer that get merged into this one. After the merge,
243 * the other layer is not usable anymore and passing to one others
244 * mergeFrom should be one of the last things to do with a layer.
245 */
246 public abstract void mergeFrom(Layer from);
247
248 /**
249 * @param other The other layer that is tested to be mergable with this.
250 * @return Whether the other layer can be merged into this layer.
251 */
252 public abstract boolean isMergable(Layer other);
253
254 public abstract void visitBoundingBox(BoundingXYVisitor v);
255
256 public abstract Object getInfoComponent();
257
258 /**
259 * Determines if info dialog can be resized (false by default).
260 * @return {@code true} if the info dialog can be resized, {@code false} otherwise
261 * @since 6708
262 */
263 public boolean isInfoResizable() {
264 return false;
265 }
266
267 /**
268 * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
269 * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
270 * have correct equals implementation.
271 *
272 * Use {@link SeparatorLayerAction#INSTANCE} instead of new JSeparator
273 * @return menu actions for this layer
274 */
275 public abstract Action[] getMenuEntries();
276
277 /**
278 * Called, when the layer is removed from the mapview and is going to be destroyed.
279 *
280 * This is because the Layer constructor can not add itself safely as listener
281 * to the layerlist dialog, because there may be no such dialog yet (loaded
282 * via command line parameter).
283 */
284 @Override
285 public void destroy() {
286 // Override in subclasses if needed
287 }
288
289 public File getAssociatedFile() {
290 return associatedFile;
291 }
292
293 public void setAssociatedFile(File file) {
294 associatedFile = file;
295 }
296
297 /**
298 * Replies the name of the layer
299 *
300 * @return the name of the layer
301 */
302 public String getName() {
303 return name;
304 }
305
306 /**
307 * Sets the name of the layer
308 *
309 * @param name the name. If null, the name is set to the empty string.
310 */
311 public final void setName(String name) {
312 if (this.name != null) {
313 removeColorPropertyListener();
314 }
315 if (name == null) {
316 name = "";
317 }
318
319 String oldValue = this.name;
320 this.name = name;
321 if (!this.name.equals(oldValue)) {
322 propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
323 }
324
325 // re-add listener
326 addColorPropertyListener();
327 invalidate();
328 }
329
330 /**
331 * Rename layer and set renamed flag to mark it as renamed (has user given name).
332 *
333 * @param name the name. If null, the name is set to the empty string.
334 */
335 public final void rename(String name) {
336 renamed = true;
337 setName(name);
338 }
339
340 /**
341 * Replies true if this layer was renamed by user
342 *
343 * @return true if this layer was renamed by user
344 */
345 public boolean isRenamed() {
346 return renamed;
347 }
348
349 /**
350 * Replies true if this layer is a background layer
351 *
352 * @return true if this layer is a background layer
353 */
354 public boolean isBackgroundLayer() {
355 return background;
356 }
357
358 /**
359 * Sets whether this layer is a background layer
360 *
361 * @param background true, if this layer is a background layer
362 */
363 public void setBackgroundLayer(boolean background) {
364 this.background = background;
365 }
366
367 /**
368 * Sets the visibility of this layer. Emits property change event for
369 * property {@link #VISIBLE_PROP}.
370 *
371 * @param visible true, if the layer is visible; false, otherwise.
372 */
373 public void setVisible(boolean visible) {
374 boolean oldValue = isVisible();
375 this.visible = visible;
376 if (visible && opacity == 0) {
377 setOpacity(1);
378 } else if (oldValue != isVisible()) {
379 fireVisibleChanged(oldValue, isVisible());
380 }
381 }
382
383 /**
384 * Replies true if this layer is visible. False, otherwise.
385 * @return true if this layer is visible. False, otherwise.
386 */
387 public boolean isVisible() {
388 return visible && opacity != 0;
389 }
390
391 /**
392 * Gets the opacity of the layer, in range 0...1
393 * @return The opacity
394 */
395 public double getOpacity() {
396 return opacity;
397 }
398
399 /**
400 * Sets the opacity of the layer, in range 0...1
401 * @param opacity The opacity
402 * @throws IllegalArgumentException if the opacity is out of range
403 */
404 public void setOpacity(double opacity) {
405 if (!(opacity >= 0 && opacity <= 1))
406 throw new IllegalArgumentException("Opacity value must be between 0 and 1");
407 double oldOpacity = getOpacity();
408 boolean oldVisible = isVisible();
409 this.opacity = opacity;
410 if (!Utils.equalsEpsilon(oldOpacity, getOpacity())) {
411 fireOpacityChanged(oldOpacity, getOpacity());
412 }
413 if (oldVisible != isVisible()) {
414 fireVisibleChanged(oldVisible, isVisible());
415 }
416 }
417
418 /**
419 * Sets new state to the layer after applying {@link ImageProcessor}.
420 */
421 public void setFilterStateChanged() {
422 fireFilterStateChanged();
423 }
424
425 /**
426 * Toggles the visibility state of this layer.
427 */
428 public void toggleVisible() {
429 setVisible(!isVisible());
430 }
431
432 /**
433 * Adds a {@link PropertyChangeListener}
434 *
435 * @param listener the listener
436 */
437 public void addPropertyChangeListener(PropertyChangeListener listener) {
438 propertyChangeSupport.addPropertyChangeListener(listener);
439 }
440
441 /**
442 * Removes a {@link PropertyChangeListener}
443 *
444 * @param listener the listener
445 */
446 public void removePropertyChangeListener(PropertyChangeListener listener) {
447 propertyChangeSupport.removePropertyChangeListener(listener);
448 }
449
450 /**
451 * fires a property change for the property {@link #VISIBLE_PROP}
452 *
453 * @param oldValue the old value
454 * @param newValue the new value
455 */
456 protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
457 propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
458 }
459
460 /**
461 * fires a property change for the property {@link #OPACITY_PROP}
462 *
463 * @param oldValue the old value
464 * @param newValue the new value
465 */
466 protected void fireOpacityChanged(double oldValue, double newValue) {
467 propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
468 }
469
470 /**
471 * fires a property change for the property {@link #FILTER_STATE_PROP}.
472 */
473 protected void fireFilterStateChanged() {
474 propertyChangeSupport.firePropertyChange(FILTER_STATE_PROP, null, null);
475 }
476
477 /**
478 * Check changed status of layer
479 *
480 * @return True if layer was changed since last paint
481 * @deprecated This is not supported by multiple map views.
482 * Fire an {@link #invalidate()} to trigger a repaint.
483 * Let this method return false if you only use invalidation events.
484 */
485 @Deprecated
486 public boolean isChanged() {
487 return true;
488 }
489
490 /**
491 * allows to check whether a projection is supported or not
492 * @param proj projection
493 *
494 * @return True if projection is supported for this layer
495 */
496 public boolean isProjectionSupported(Projection proj) {
497 return proj != null;
498 }
499
500 /**
501 * Specify user information about projections
502 *
503 * @return User readable text telling about supported projections
504 */
505 public String nameSupportedProjections() {
506 return tr("All projections are supported");
507 }
508
509 /**
510 * The action to save a layer
511 */
512 public static class LayerSaveAction extends AbstractAction {
513 private final transient Layer layer;
514
515 public LayerSaveAction(Layer layer) {
516 putValue(SMALL_ICON, ImageProvider.get("save"));
517 putValue(SHORT_DESCRIPTION, tr("Save the current data."));
518 putValue(NAME, tr("Save"));
519 setEnabled(true);
520 this.layer = layer;
521 }
522
523 @Override
524 public void actionPerformed(ActionEvent e) {
525 SaveAction.getInstance().doSave(layer);
526 }
527 }
528
529 public static class LayerSaveAsAction extends AbstractAction {
530 private final transient Layer layer;
531
532 public LayerSaveAsAction(Layer layer) {
533 putValue(SMALL_ICON, ImageProvider.get("save_as"));
534 putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
535 putValue(NAME, tr("Save As..."));
536 setEnabled(true);
537 this.layer = layer;
538 }
539
540 @Override
541 public void actionPerformed(ActionEvent e) {
542 SaveAsAction.getInstance().doSave(layer);
543 }
544 }
545
546 public static class LayerGpxExportAction extends AbstractAction {
547 private final transient Layer layer;
548
549 public LayerGpxExportAction(Layer layer) {
550 putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
551 putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
552 putValue(NAME, tr("Export to GPX..."));
553 setEnabled(true);
554 this.layer = layer;
555 }
556
557 @Override
558 public void actionPerformed(ActionEvent e) {
559 new GpxExportAction().export(layer);
560 }
561 }
562
563 /* --------------------------------------------------------------------------------- */
564 /* interface ProjectionChangeListener */
565 /* --------------------------------------------------------------------------------- */
566 @Override
567 public void projectionChanged(Projection oldValue, Projection newValue) {
568 if (!isProjectionSupported(newValue)) {
569 final String message = "<html><body><p>" +
570 tr("The layer {0} does not support the new projection {1}.", getName(), newValue.toCode()) + "</p>" +
571 "<p style='width: 450px;'>" + tr("Supported projections are: {0}", nameSupportedProjections()) + "</p>" +
572 tr("Change the projection again or remove the layer.");
573
574 // run later to not block loading the UI.
575 SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(Main.parent,
576 message,
577 tr("Warning"),
578 JOptionPane.WARNING_MESSAGE));
579 }
580 }
581
582 /**
583 * Initializes the layer after a successful load of data from a file
584 * @since 5459
585 */
586 public void onPostLoadFromFile() {
587 // To be overriden if needed
588 }
589
590 /**
591 * Replies the savable state of this layer (i.e if it can be saved through a "File-&gt;Save" dialog).
592 * @return true if this layer can be saved to a file
593 * @since 5459
594 */
595 public boolean isSavable() {
596 return false;
597 }
598
599 /**
600 * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
601 * @return <code>true</code>, if it is safe to save.
602 * @since 5459
603 */
604 public boolean checkSaveConditions() {
605 return true;
606 }
607
608 /**
609 * Creates a new "Save" dialog for this layer and makes it visible.<br>
610 * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
611 * @return The output {@code File}
612 * @see SaveActionBase#createAndOpenSaveFileChooser
613 * @since 5459
614 */
615 public File createAndOpenSaveFileChooser() {
616 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
617 }
618
619 /**
620 * @return bytes that the tile will use. Needed for resource management
621 * @deprecated Not used any more.
622 */
623 @Deprecated
624 protected long estimateMemoryUsage() {
625 return 0;
626 }
627
628 /**
629 * Gets the strategy that specifies where this layer should be inserted in a layer list.
630 * @return That strategy.
631 * @since 10008
632 */
633 public LayerPositionStrategy getDefaultLayerPosition() {
634 if (isBackgroundLayer()) {
635 return LayerPositionStrategy.BEFORE_FIRST_BACKGROUND_LAYER;
636 } else {
637 return LayerPositionStrategy.AFTER_LAST_VALIDATION_LAYER;
638 }
639 }
640
641 /**
642 * Gets the {@link ProjectionBounds} for this layer to be visible to the user. This can be the exact bounds, the UI handles padding. Return
643 * <code>null</code> if you cannot provide this information. The default implementation uses the bounds from
644 * {@link #visitBoundingBox(BoundingXYVisitor)}.
645 * @return The bounds for this layer.
646 * @since 10371
647 */
648 public ProjectionBounds getViewProjectionBounds() {
649 BoundingXYVisitor v = new BoundingXYVisitor();
650 visitBoundingBox(v);
651 return v.getBounds();
652 }
653}
Note: See TracBrowser for help on using the repository browser.