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

Last change on this file since 17356 was 16553, checked in by Don-vip, 4 years ago

see #19334 - javadoc fixes + protected constructors for abstract classes

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