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

Last change on this file was 19112, checked in by stoecker, 3 months ago

javadoc fixes

  • Property svn:eol-style set to native
File size: 21.3 KB
RevLine 
[8378]1// License: GPL. For details, see LICENSE file.
[17]2package org.openstreetmap.josm.gui.layer;
3
[1808]4import static org.openstreetmap.josm.tools.I18n.tr;
5
[4230]6import java.awt.Color;
[103]7import java.awt.Component;
[1808]8import java.awt.event.ActionEvent;
[1890]9import java.beans.PropertyChangeListener;
10import java.beans.PropertyChangeSupport;
[138]11import java.io.File;
[3408]12import java.util.List;
[11553]13import java.util.Optional;
[17]14
[1808]15import javax.swing.AbstractAction;
[3408]16import javax.swing.Action;
[17]17import javax.swing.Icon;
[4183]18import javax.swing.JOptionPane;
[3408]19import javax.swing.JSeparator;
[10458]20import javax.swing.SwingUtilities;
[17]21
[1808]22import org.openstreetmap.josm.actions.GpxExportAction;
23import org.openstreetmap.josm.actions.SaveAction;
[5459]24import org.openstreetmap.josm.actions.SaveActionBase;
[1808]25import org.openstreetmap.josm.actions.SaveAsAction;
[10371]26import org.openstreetmap.josm.data.ProjectionBounds;
[71]27import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
[4126]28import org.openstreetmap.josm.data.projection.Projection;
29import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
[14153]30import org.openstreetmap.josm.gui.MainApplication;
[208]31import org.openstreetmap.josm.tools.Destroyable;
[12782]32import org.openstreetmap.josm.tools.ImageProcessor;
[1808]33import org.openstreetmap.josm.tools.ImageProvider;
[8384]34import org.openstreetmap.josm.tools.Utils;
[17]35
36/**
[1508]37 * A layer encapsulates the gui component of one dataset and its representation.
[18895]38 * <p>
[1508]39 * Some layers may display data directly imported from OSM server. Other only
[1169]40 * display background images. Some can be edited, some not. Some are static and
[17]41 * other changes dynamically (auto-updated).
[18895]42 * <p>
[17]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.
[18895]45 * <p>
[1169]46 * All layers are managed by the MapView. They are displayed in a list to the
[17]47 * right of the screen.
[1169]48 *
[17]49 * @author imi
50 */
[10031]51public abstract class Layer extends AbstractMapViewPaintable implements Destroyable, ProjectionChangeListener {
[3408]52
[9949]53 /**
54 * Action related to a single layer.
55 */
[3408]56 public interface LayerAction {
[9949]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 */
[3408]63 boolean supportLayers(List<Layer> layers);
[8510]64
[9949]65 /**
66 * Creates and return the menu component.
67 * @return the menu component
68 */
[3408]69 Component createMenuComponent();
70 }
71
[9949]72 /**
73 * Action related to several layers.
[10600]74 * @since 10600 (functional interface)
[9949]75 */
[10600]76 @FunctionalInterface
[4230]77 public interface MultiLayerAction {
[9949]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 */
[4230]84 Action getMultiLayerAction(List<Layer> layers);
85 }
86
[3408]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 {
[9949]91 /** Unique instance */
[3408]92 public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
[8510]93
[3408]94 @Override
95 public void actionPerformed(ActionEvent e) {
96 throw new UnsupportedOperationException();
97 }
[8510]98
[3408]99 @Override
100 public Component createMenuComponent() {
101 return new JSeparator();
102 }
[8510]103
[3408]104 @Override
105 public boolean supportLayers(List<Layer> layers) {
106 return false;
107 }
108 }
109
[12114]110 /**
111 * The visibility property for this layer. May be <code>true</code> (visible) or <code>false</code> (hidden).
112 */
[6889]113 public static final String VISIBLE_PROP = Layer.class.getName() + ".visible";
[12114]114 /**
115 * The opacity of this layer. A number between 0 and 1
116 */
[6889]117 public static final String OPACITY_PROP = Layer.class.getName() + ".opacity";
[12114]118 /**
119 * The name property of the layer.
120 * You can listen to name changes by listening to changes to this property.
121 */
[6889]122 public static final String NAME_PROP = Layer.class.getName() + ".name";
[12114]123 /**
124 * Property that defines the filter state.
125 * This is currently not used.
126 */
[8625]127 public static final String FILTER_STATE_PROP = Layer.class.getName() + ".filterstate";
[17]128
[9510]129 /**
130 * keeps track of property change listeners
131 */
[2025]132 protected PropertyChangeSupport propertyChangeSupport;
[1890]133
[1169]134 /**
135 * The visibility state of the layer.
136 */
[2017]137 private boolean visible = true;
[989]138
[1169]139 /**
[3705]140 * The opacity of the layer.
141 */
142 private double opacity = 1;
143
144 /**
[1169]145 * The layer should be handled as a background layer in automatic handling
146 */
[8840]147 private boolean background;
[989]148
[1169]149 /**
150 * The name of this layer.
151 */
[9510]152 private String name;
[2017]153
[1169]154 /**
[9510]155 * This is set if user renamed this layer.
156 */
[9978]157 private boolean renamed;
[9510]158
159 /**
[1169]160 * If a file is associated with this layer, this variable should be set to it.
161 */
[1646]162 private File associatedFile;
[23]163
[12025]164 private boolean isDestroyed;
[10824]165
[1169]166 /**
167 * Create the layer and fill in the necessary components.
[7358]168 * @param name Layer name
[1169]169 */
[16553]170 protected Layer(String name) {
[1890]171 this.propertyChangeSupport = new PropertyChangeSupport(this);
172 setName(name);
[1169]173 }
[17]174
[1169]175 /**
[5391]176 * Initialization code, that depends on Main.map.mapView.
[18895]177 * <p>
[5391]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
[10588]181 * not null.
[18895]182 * <p>
[10588]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)}
[5391]185 */
186 public void hookUpMapView() {
[10458]187 }
188
189 /**
[1169]190 * Return a representative small image for this layer. The image must not
191 * be larger than 64 pixel in any dimension.
[8958]192 * @return layer icon
[1169]193 */
[6889]194 public abstract Icon getIcon();
[21]195
[1169]196 /**
[16553]197 * Determines whether the layer has / can handle colors.
[15496]198 * @return whether the layer has / can handle colors.
199 * @since 15496
[10824]200 */
[15496]201 public boolean hasColor() {
202 return false;
[10824]203 }
204
205 /**
[15496]206 * Return the current color of the layer
207 * @return null when not present or not supported
208 * @since 15496
[10824]209 */
[15496]210 public Color getColor() {
[10824]211 return null;
212 }
213
[15496]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) {
[10824]220 }
221
222 /**
[16553]223 * Returns a small tooltip hint about some statistics for this layer.
[1169]224 * @return A small tooltip hint about some statistics for this layer.
225 */
[6889]226 public abstract String getToolTipText();
[23]227
[1169]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 */
[6889]235 public abstract void mergeFrom(Layer from);
[655]236
[1169]237 /**
[16553]238 * Determines if the other layer can be merged into this layer.
[1169]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 */
[6889]242 public abstract boolean isMergable(Layer other);
[78]243
[12114]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 */
[6889]249 public abstract void visitBoundingBox(BoundingXYVisitor v);
[1169]250
[12114]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 */
[6889]257 public abstract Object getInfoComponent();
[1169]258
[3408]259 /**
[6708]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 /**
[3408]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.
[18895]272 * <p>
[8958]273 * Use {@link SeparatorLayerAction#INSTANCE} instead of new JSeparator
274 * @return menu actions for this layer
[3408]275 */
[6889]276 public abstract Action[] getMenuEntries();
[1169]277
278 /**
[8413]279 * Called, when the layer is removed from the mapview and is going to be destroyed.
[18895]280 * <p>
281 * This is because the Layer constructor cannot add itself safely as a listener
[1169]282 * to the layerlist dialog, because there may be no such dialog yet (loaded
283 * via command line parameter).
284 */
[3705]285 @Override
[12025]286 public synchronized void destroy() {
287 if (isDestroyed) {
288 throw new IllegalStateException("The layer has already been destroyed: " + this);
289 }
290 isDestroyed = true;
[8413]291 // Override in subclasses if needed
292 }
[1646]293
[12114]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 */
[8413]299 public File getAssociatedFile() {
300 return associatedFile;
301 }
[1772]302
[12114]303 /**
304 * Sets the associated file for this layer.
[18895]305 * <p>
[12114]306 * The associated file might be the one that the user opened.
307 * @param file The file, may be <code>null</code>
308 */
[8413]309 public void setAssociatedFile(File file) {
310 associatedFile = file;
311 }
312
[1772]313 /**
[17626]314 * Replies a label for this layer useful for UI elements. Defaults to the layer name
315 * @return a label for this layer
316 * @since 17626
317 */
318 public String getLabel() {
319 return getName();
320 }
321
322 /**
[1772]323 * Replies the name of the layer
[2512]324 *
[1772]325 * @return the name of the layer
326 */
327 public String getName() {
328 return name;
329 }
[1808]330
[1890]331 /**
332 * Sets the name of the layer
333 *
[9510]334 * @param name the name. If null, the name is set to the empty string.
[1890]335 */
[12718]336 public void setName(String name) {
[1890]337 String oldValue = this.name;
[11553]338 this.name = Optional.ofNullable(name).orElse("");
[1893]339 if (!this.name.equals(oldValue)) {
[1890]340 propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
341 }
[10824]342 invalidate();
[1890]343 }
[1808]344
[2192]345 /**
[9510]346 * Rename layer and set renamed flag to mark it as renamed (has user given name).
347 *
348 * @param name the name. If null, the name is set to the empty string.
349 */
350 public final void rename(String name) {
351 renamed = true;
352 setName(name);
353 }
354
355 /**
[18895]356 * Replies true if user renamed this layer
[9510]357 *
[18895]358 * @return true if user renamed this layer
[9510]359 */
360 public boolean isRenamed() {
361 return renamed;
362 }
363
364 /**
[2192]365 * Replies true if this layer is a background layer
[2512]366 *
[2192]367 * @return true if this layer is a background layer
368 */
369 public boolean isBackgroundLayer() {
370 return background;
[1808]371 }
372
[2192]373 /**
374 * Sets whether this layer is a background layer
[2512]375 *
[2192]376 * @param background true, if this layer is a background layer
377 */
378 public void setBackgroundLayer(boolean background) {
379 this.background = background;
[1808]380 }
381
[1890]382 /**
383 * Sets the visibility of this layer. Emits property change event for
[5266]384 * property {@link #VISIBLE_PROP}.
[2512]385 *
[1890]386 * @param visible true, if the layer is visible; false, otherwise.
387 */
388 public void setVisible(boolean visible) {
[3705]389 boolean oldValue = isVisible();
[10378]390 this.visible = visible;
[8393]391 if (visible && opacity == 0) {
[3705]392 setOpacity(1);
393 } else if (oldValue != isVisible()) {
394 fireVisibleChanged(oldValue, isVisible());
[1890]395 }
396 }
397
398 /**
399 * Replies true if this layer is visible. False, otherwise.
400 * @return true if this layer is visible. False, otherwise.
401 */
402 public boolean isVisible() {
[8393]403 return visible && opacity != 0;
[1890]404 }
405
[10011]406 /**
407 * Gets the opacity of the layer, in range 0...1
408 * @return The opacity
409 */
[3705]410 public double getOpacity() {
411 return opacity;
412 }
413
[10011]414 /**
415 * Sets the opacity of the layer, in range 0...1
416 * @param opacity The opacity
417 * @throws IllegalArgumentException if the opacity is out of range
418 */
[3705]419 public void setOpacity(double opacity) {
420 if (!(opacity >= 0 && opacity <= 1))
421 throw new IllegalArgumentException("Opacity value must be between 0 and 1");
422 double oldOpacity = getOpacity();
423 boolean oldVisible = isVisible();
424 this.opacity = opacity;
[8384]425 if (!Utils.equalsEpsilon(oldOpacity, getOpacity())) {
[3705]426 fireOpacityChanged(oldOpacity, getOpacity());
427 }
428 if (oldVisible != isVisible()) {
429 fireVisibleChanged(oldVisible, isVisible());
430 }
431 }
432
[1890]433 /**
[8625]434 * Sets new state to the layer after applying {@link ImageProcessor}.
435 */
436 public void setFilterStateChanged() {
437 fireFilterStateChanged();
438 }
439
440 /**
[1890]441 * Toggles the visibility state of this layer.
442 */
443 public void toggleVisible() {
444 setVisible(!isVisible());
445 }
446
447 /**
[5266]448 * Adds a {@link PropertyChangeListener}
[2512]449 *
[1890]450 * @param listener the listener
451 */
452 public void addPropertyChangeListener(PropertyChangeListener listener) {
453 propertyChangeSupport.addPropertyChangeListener(listener);
454 }
455
456 /**
[5266]457 * Removes a {@link PropertyChangeListener}
[2512]458 *
[1890]459 * @param listener the listener
460 */
461 public void removePropertyChangeListener(PropertyChangeListener listener) {
462 propertyChangeSupport.removePropertyChangeListener(listener);
463 }
464
465 /**
[5266]466 * fires a property change for the property {@link #VISIBLE_PROP}
[2512]467 *
[1890]468 * @param oldValue the old value
469 * @param newValue the new value
470 */
471 protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
472 propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
473 }
[2192]474
475 /**
[5266]476 * fires a property change for the property {@link #OPACITY_PROP}
[3116]477 *
[3705]478 * @param oldValue the old value
479 * @param newValue the new value
480 */
481 protected void fireOpacityChanged(double oldValue, double newValue) {
482 propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
483 }
484
485 /**
[18895]486 * Fires a property change for the property {@link #FILTER_STATE_PROP}.
[8625]487 */
488 protected void fireFilterStateChanged() {
489 propertyChangeSupport.firePropertyChange(FILTER_STATE_PROP, null, null);
490 }
491
492 /**
[18895]493 * Allows to check whether a projection is supported or not.
[9231]494 * @param proj projection
[4183]495 * @return True if projection is supported for this layer
496 */
497 public boolean isProjectionSupported(Projection proj) {
[8855]498 return proj != null;
[4183]499 }
500
501 /**
502 * Specify user information about projections
503 *
504 * @return User readable text telling about supported projections
505 */
506 public String nameSupportedProjections() {
507 return tr("All projections are supported");
508 }
509
510 /**
[2192]511 * The action to save a layer
512 */
513 public static class LayerSaveAction extends AbstractAction {
[8308]514 private final transient Layer layer;
[8510]515
[12114]516 /**
517 * Create a new action that saves the layer
518 * @param layer The layer to save.
519 */
[2192]520 public LayerSaveAction(Layer layer) {
[13130]521 new ImageProvider("save").getResource().attachImageIcon(this, true);
[2192]522 putValue(SHORT_DESCRIPTION, tr("Save the current data."));
523 putValue(NAME, tr("Save"));
524 setEnabled(true);
525 this.layer = layer;
526 }
527
[6084]528 @Override
[2192]529 public void actionPerformed(ActionEvent e) {
[15496]530 SaveAction.getInstance().doSave(layer, true);
[2192]531 }
532 }
533
[12114]534 /**
535 * Action to save the layer in a new file
536 */
[2192]537 public static class LayerSaveAsAction extends AbstractAction {
[8308]538 private final transient Layer layer;
[8510]539
[12114]540 /**
541 * Create a new save as action
542 * @param layer The layer that should be saved.
543 */
[2192]544 public LayerSaveAsAction(Layer layer) {
[13130]545 new ImageProvider("save_as").getResource().attachImageIcon(this, true);
[2192]546 putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
547 putValue(NAME, tr("Save As..."));
548 setEnabled(true);
549 this.layer = layer;
550 }
551
[6084]552 @Override
[2192]553 public void actionPerformed(ActionEvent e) {
[5048]554 SaveAsAction.getInstance().doSave(layer);
[2192]555 }
556 }
557
[12114]558 /**
559 * Action that exports the layer as gpx file
560 */
[2192]561 public static class LayerGpxExportAction extends AbstractAction {
[8308]562 private final transient Layer layer;
[8510]563
[12114]564 /**
565 * Create a new gpx export action for the given layer.
566 * @param layer The layer
567 */
[2192]568 public LayerGpxExportAction(Layer layer) {
[13130]569 new ImageProvider("exportgpx").getResource().attachImageIcon(this, true);
[2192]570 putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
571 putValue(NAME, tr("Export to GPX..."));
572 setEnabled(true);
573 this.layer = layer;
574 }
575
[6084]576 @Override
[2192]577 public void actionPerformed(ActionEvent e) {
578 new GpxExportAction().export(layer);
579 }
580 }
[4126]581
582 /* --------------------------------------------------------------------------------- */
583 /* interface ProjectionChangeListener */
584 /* --------------------------------------------------------------------------------- */
585 @Override
586 public void projectionChanged(Projection oldValue, Projection newValue) {
[8510]587 if (!isProjectionSupported(newValue)) {
[10458]588 final String message = "<html><body><p>" +
[11848]589 tr("The layer {0} does not support the new projection {1}.",
590 Utils.escapeReservedCharactersHTML(getName()), newValue.toCode()) + "</p>" +
[9844]591 "<p style='width: 450px;'>" + tr("Supported projections are: {0}", nameSupportedProjections()) + "</p>" +
592 tr("Change the projection again or remove the layer.");
593
[10458]594 // run later to not block loading the UI.
[14153]595 SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
[10611]596 message,
597 tr("Warning"),
598 JOptionPane.WARNING_MESSAGE));
[4183]599 }
[4126]600 }
[5459]601
602 /**
603 * Initializes the layer after a successful load of data from a file
604 * @since 5459
605 */
606 public void onPostLoadFromFile() {
[15662]607 // To be overridden if needed
[5459]608 }
[6070]609
[5459]610 /**
[19112]611 * Replies the savable state of this layer (i.e., if it can be saved through a "File → Save" dialog).
[5459]612 * @return true if this layer can be saved to a file
613 * @since 5459
614 */
615 public boolean isSavable() {
616 return false;
617 }
[6070]618
[5459]619 /**
620 * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
621 * @return <code>true</code>, if it is safe to save.
622 * @since 5459
623 */
624 public boolean checkSaveConditions() {
625 return true;
626 }
[6070]627
[5459]628 /**
[18895]629 * Creates a new "Save" dialog for this layer and makes it visible.
630 * <p>
631 * When the user has chosen a file, checks the file extension, and confirms overwriting if needed.
[5459]632 * @return The output {@code File}
[8419]633 * @see SaveActionBase#createAndOpenSaveFileChooser
[5459]634 * @since 5459
635 */
636 public File createAndOpenSaveFileChooser() {
637 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
638 }
[8860]639
640 /**
[10008]641 * Gets the strategy that specifies where this layer should be inserted in a layer list.
642 * @return That strategy.
643 * @since 10008
644 */
645 public LayerPositionStrategy getDefaultLayerPosition() {
646 if (isBackgroundLayer()) {
647 return LayerPositionStrategy.BEFORE_FIRST_BACKGROUND_LAYER;
648 } else {
649 return LayerPositionStrategy.AFTER_LAST_VALIDATION_LAYER;
650 }
651 }
[10371]652
653 /**
654 * Gets the {@link ProjectionBounds} for this layer to be visible to the user. This can be the exact bounds, the UI handles padding. Return
655 * <code>null</code> if you cannot provide this information. The default implementation uses the bounds from
656 * {@link #visitBoundingBox(BoundingXYVisitor)}.
657 * @return The bounds for this layer.
658 * @since 10371
659 */
660 public ProjectionBounds getViewProjectionBounds() {
661 BoundingXYVisitor v = new BoundingXYVisitor();
662 visitBoundingBox(v);
663 return v.getBounds();
664 }
[10959]665
[15371]666 /**
667 * Get the source for the layer
668 * @return The string for the changeset source tag or {@code null}
669 * @since 15371
670 */
671 public String getChangesetSourceTag() {
672 return null;
673 }
674
[10959]675 @Override
676 public String toString() {
[18895]677 return getClass().getSimpleName() + " [name=" + name + ", associatedFile=" + associatedFile + ", visible=" + visible + ']';
[10959]678 }
[17]679}
Note: See TracBrowser for help on using the repository browser.