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

Last change on this file since 13724 was 13173, checked in by Don-vip, 6 years ago

see #15310 - remove most of deprecated APIs

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