001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Color; 007import java.awt.Component; 008import java.awt.event.ActionEvent; 009import java.beans.PropertyChangeListener; 010import java.beans.PropertyChangeSupport; 011import java.io.File; 012import java.util.List; 013 014import javax.swing.AbstractAction; 015import javax.swing.Action; 016import javax.swing.Icon; 017import javax.swing.JOptionPane; 018import javax.swing.JSeparator; 019 020import org.openstreetmap.josm.Main; 021import org.openstreetmap.josm.actions.GpxExportAction; 022import org.openstreetmap.josm.actions.SaveAction; 023import org.openstreetmap.josm.actions.SaveActionBase; 024import org.openstreetmap.josm.actions.SaveAsAction; 025import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 026import org.openstreetmap.josm.data.projection.Projection; 027import org.openstreetmap.josm.data.projection.ProjectionChangeListener; 028import org.openstreetmap.josm.tools.Destroyable; 029import org.openstreetmap.josm.tools.ImageProvider; 030import org.openstreetmap.josm.tools.Utils; 031 032/** 033 * A layer encapsulates the gui component of one dataset and its representation. 034 * 035 * Some layers may display data directly imported from OSM server. Other only 036 * display background images. Some can be edited, some not. Some are static and 037 * other changes dynamically (auto-updated). 038 * 039 * Layers can be visible or not. Most actions the user can do applies only on 040 * selected layers. The available actions depend on the selected layers too. 041 * 042 * All layers are managed by the MapView. They are displayed in a list to the 043 * right of the screen. 044 * 045 * @author imi 046 */ 047public abstract class Layer extends AbstractMapViewPaintable implements Destroyable, ProjectionChangeListener { 048 049 /** 050 * Action related to a single layer. 051 */ 052 public interface LayerAction { 053 054 /** 055 * Determines if this action supports a given list of layers. 056 * @param layers list of layers 057 * @return {@code true} if this action supports the given list of layers, {@code false} otherwise 058 */ 059 boolean supportLayers(List<Layer> layers); 060 061 /** 062 * Creates and return the menu component. 063 * @return the menu component 064 */ 065 Component createMenuComponent(); 066 } 067 068 /** 069 * Action related to several layers. 070 */ 071 public interface MultiLayerAction { 072 073 /** 074 * Returns the action for a given list of layers. 075 * @param layers list of layers 076 * @return the action for the given list of layers 077 */ 078 Action getMultiLayerAction(List<Layer> layers); 079 } 080 081 /** 082 * Special class that can be returned by getMenuEntries when JSeparator needs to be created 083 */ 084 public static class SeparatorLayerAction extends AbstractAction implements LayerAction { 085 /** Unique instance */ 086 public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction(); 087 088 @Override 089 public void actionPerformed(ActionEvent e) { 090 throw new UnsupportedOperationException(); 091 } 092 093 @Override 094 public Component createMenuComponent() { 095 return new JSeparator(); 096 } 097 098 @Override 099 public boolean supportLayers(List<Layer> layers) { 100 return false; 101 } 102 } 103 104 public static final String VISIBLE_PROP = Layer.class.getName() + ".visible"; 105 public static final String OPACITY_PROP = Layer.class.getName() + ".opacity"; 106 public static final String NAME_PROP = Layer.class.getName() + ".name"; 107 public static final String FILTER_STATE_PROP = Layer.class.getName() + ".filterstate"; 108 109 /** 110 * keeps track of property change listeners 111 */ 112 protected PropertyChangeSupport propertyChangeSupport; 113 114 /** 115 * The visibility state of the layer. 116 */ 117 private boolean visible = true; 118 119 /** 120 * The opacity of the layer. 121 */ 122 private double opacity = 1; 123 124 /** 125 * The layer should be handled as a background layer in automatic handling 126 */ 127 private boolean background; 128 129 /** 130 * The name of this layer. 131 */ 132 private String name; 133 134 /** 135 * This is set if user renamed this layer. 136 */ 137 private boolean renamed; 138 139 /** 140 * If a file is associated with this layer, this variable should be set to it. 141 */ 142 private File associatedFile; 143 144 /** 145 * Create the layer and fill in the necessary components. 146 * @param name Layer name 147 */ 148 public Layer(String name) { 149 this.propertyChangeSupport = new PropertyChangeSupport(this); 150 setName(name); 151 } 152 153 /** 154 * Initialization code, that depends on Main.map.mapView. 155 * 156 * It is always called in the event dispatching thread. 157 * Note that Main.map is null as long as no layer has been added, so do 158 * not execute code in the constructor, that assumes Main.map.mapView is 159 * not null. Instead override this method. 160 * 161 * This implementation provides check, if JOSM will be able to use Layer. Layers 162 * using a lot of memory, which do know in advance, how much memory they use, should 163 * override {@link #estimateMemoryUsage() estimateMemoryUsage} method and give a hint. 164 * 165 * This allows for preemptive warning message for user, instead of failing later on 166 * 167 * Remember to call {@code super.hookUpMapView()} when overriding this method 168 */ 169 public void hookUpMapView() { 170 // calculate total memory needed for all layers 171 long memoryBytesRequired = 50L * 1024L * 1024L; // assumed minimum JOSM memory footprint 172 if (Main.map != null && Main.map.mapView != null) { 173 for (Layer layer: Main.map.mapView.getAllLayers()) { 174 memoryBytesRequired += layer.estimateMemoryUsage(); 175 } 176 if (memoryBytesRequired > Runtime.getRuntime().maxMemory()) { 177 throw new IllegalArgumentException( 178 tr("To add another layer you need to allocate at least {0,number,#}MB memory to JOSM using -Xmx{0,number,#}M " 179 + "option (see http://forum.openstreetmap.org/viewtopic.php?id=25677).\n" 180 + "Currently you have {1,number,#}MB memory allocated for JOSM", 181 memoryBytesRequired / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024)); 182 } 183 } 184 } 185 186 /** 187 * Return a representative small image for this layer. The image must not 188 * be larger than 64 pixel in any dimension. 189 * @return layer icon 190 */ 191 public abstract Icon getIcon(); 192 193 /** 194 * Return a Color for this layer. Return null when no color specified. 195 * @param ignoreCustom Custom color should return null, as no default color 196 * is used. When this is true, then even for custom coloring the base 197 * color is returned - mainly for layer internal use. 198 * @return layer color 199 */ 200 public Color getColor(boolean ignoreCustom) { 201 return null; 202 } 203 204 /** 205 * @return A small tooltip hint about some statistics for this layer. 206 */ 207 public abstract String getToolTipText(); 208 209 /** 210 * Merges the given layer into this layer. Throws if the layer types are 211 * incompatible. 212 * @param from The layer that get merged into this one. After the merge, 213 * the other layer is not usable anymore and passing to one others 214 * mergeFrom should be one of the last things to do with a layer. 215 */ 216 public abstract void mergeFrom(Layer from); 217 218 /** 219 * @param other The other layer that is tested to be mergable with this. 220 * @return Whether the other layer can be merged into this layer. 221 */ 222 public abstract boolean isMergable(Layer other); 223 224 public abstract void visitBoundingBox(BoundingXYVisitor v); 225 226 public abstract Object getInfoComponent(); 227 228 /** 229 * Determines if info dialog can be resized (false by default). 230 * @return {@code true} if the info dialog can be resized, {@code false} otherwise 231 * @since 6708 232 */ 233 public boolean isInfoResizable() { 234 return false; 235 } 236 237 /** 238 * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other 239 * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also 240 * have correct equals implementation. 241 * 242 * Use {@link SeparatorLayerAction#INSTANCE} instead of new JSeparator 243 * @return menu actions for this layer 244 */ 245 public abstract Action[] getMenuEntries(); 246 247 /** 248 * Called, when the layer is removed from the mapview and is going to be destroyed. 249 * 250 * This is because the Layer constructor can not add itself safely as listener 251 * to the layerlist dialog, because there may be no such dialog yet (loaded 252 * via command line parameter). 253 */ 254 @Override 255 public void destroy() { 256 // Override in subclasses if needed 257 } 258 259 public File getAssociatedFile() { 260 return associatedFile; 261 } 262 263 public void setAssociatedFile(File file) { 264 associatedFile = file; 265 } 266 267 /** 268 * Replies the name of the layer 269 * 270 * @return the name of the layer 271 */ 272 public String getName() { 273 return name; 274 } 275 276 /** 277 * Sets the name of the layer 278 * 279 * @param name the name. If null, the name is set to the empty string. 280 */ 281 public final void setName(String name) { 282 if (name == null) { 283 name = ""; 284 } 285 String oldValue = this.name; 286 this.name = name; 287 if (!this.name.equals(oldValue)) { 288 propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name); 289 } 290 } 291 292 /** 293 * Rename layer and set renamed flag to mark it as renamed (has user given name). 294 * 295 * @param name the name. If null, the name is set to the empty string. 296 */ 297 public final void rename(String name) { 298 renamed = true; 299 setName(name); 300 } 301 302 /** 303 * Replies true if this layer was renamed by user 304 * 305 * @return true if this layer was renamed by user 306 */ 307 public boolean isRenamed() { 308 return renamed; 309 } 310 311 /** 312 * Replies true if this layer is a background layer 313 * 314 * @return true if this layer is a background layer 315 */ 316 public boolean isBackgroundLayer() { 317 return background; 318 } 319 320 /** 321 * Sets whether this layer is a background layer 322 * 323 * @param background true, if this layer is a background layer 324 */ 325 public void setBackgroundLayer(boolean background) { 326 this.background = background; 327 } 328 329 /** 330 * Sets the visibility of this layer. Emits property change event for 331 * property {@link #VISIBLE_PROP}. 332 * 333 * @param visible true, if the layer is visible; false, otherwise. 334 */ 335 public void setVisible(boolean visible) { 336 boolean oldValue = isVisible(); 337 this.visible = visible; 338 if (visible && opacity == 0) { 339 setOpacity(1); 340 } else if (oldValue != isVisible()) { 341 fireVisibleChanged(oldValue, isVisible()); 342 } 343 } 344 345 /** 346 * Replies true if this layer is visible. False, otherwise. 347 * @return true if this layer is visible. False, otherwise. 348 */ 349 public boolean isVisible() { 350 return visible && opacity != 0; 351 } 352 353 /** 354 * Gets the opacity of the layer, in range 0...1 355 * @return The opacity 356 */ 357 public double getOpacity() { 358 return opacity; 359 } 360 361 /** 362 * Sets the opacity of the layer, in range 0...1 363 * @param opacity The opacity 364 * @throws IllegalArgumentException if the opacity is out of range 365 */ 366 public void setOpacity(double opacity) { 367 if (!(opacity >= 0 && opacity <= 1)) 368 throw new IllegalArgumentException("Opacity value must be between 0 and 1"); 369 double oldOpacity = getOpacity(); 370 boolean oldVisible = isVisible(); 371 this.opacity = opacity; 372 if (!Utils.equalsEpsilon(oldOpacity, getOpacity())) { 373 fireOpacityChanged(oldOpacity, getOpacity()); 374 } 375 if (oldVisible != isVisible()) { 376 fireVisibleChanged(oldVisible, isVisible()); 377 } 378 } 379 380 /** 381 * Sets new state to the layer after applying {@link ImageProcessor}. 382 */ 383 public void setFilterStateChanged() { 384 fireFilterStateChanged(); 385 } 386 387 /** 388 * Toggles the visibility state of this layer. 389 */ 390 public void toggleVisible() { 391 setVisible(!isVisible()); 392 } 393 394 /** 395 * Adds a {@link PropertyChangeListener} 396 * 397 * @param listener the listener 398 */ 399 public void addPropertyChangeListener(PropertyChangeListener listener) { 400 propertyChangeSupport.addPropertyChangeListener(listener); 401 } 402 403 /** 404 * Removes a {@link PropertyChangeListener} 405 * 406 * @param listener the listener 407 */ 408 public void removePropertyChangeListener(PropertyChangeListener listener) { 409 propertyChangeSupport.removePropertyChangeListener(listener); 410 } 411 412 /** 413 * fires a property change for the property {@link #VISIBLE_PROP} 414 * 415 * @param oldValue the old value 416 * @param newValue the new value 417 */ 418 protected void fireVisibleChanged(boolean oldValue, boolean newValue) { 419 propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue); 420 } 421 422 /** 423 * fires a property change for the property {@link #OPACITY_PROP} 424 * 425 * @param oldValue the old value 426 * @param newValue the new value 427 */ 428 protected void fireOpacityChanged(double oldValue, double newValue) { 429 propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue); 430 } 431 432 /** 433 * fires a property change for the property {@link #FILTER_STATE_PROP}. 434 */ 435 protected void fireFilterStateChanged() { 436 propertyChangeSupport.firePropertyChange(FILTER_STATE_PROP, null, null); 437 } 438 439 /** 440 * Check changed status of layer 441 * 442 * @return True if layer was changed since last paint 443 */ 444 public boolean isChanged() { 445 return true; 446 } 447 448 /** 449 * allows to check whether a projection is supported or not 450 * @param proj projection 451 * 452 * @return True if projection is supported for this layer 453 */ 454 public boolean isProjectionSupported(Projection proj) { 455 return proj != null; 456 } 457 458 /** 459 * Specify user information about projections 460 * 461 * @return User readable text telling about supported projections 462 */ 463 public String nameSupportedProjections() { 464 return tr("All projections are supported"); 465 } 466 467 /** 468 * The action to save a layer 469 */ 470 public static class LayerSaveAction extends AbstractAction { 471 private final transient Layer layer; 472 473 public LayerSaveAction(Layer layer) { 474 putValue(SMALL_ICON, ImageProvider.get("save")); 475 putValue(SHORT_DESCRIPTION, tr("Save the current data.")); 476 putValue(NAME, tr("Save")); 477 setEnabled(true); 478 this.layer = layer; 479 } 480 481 @Override 482 public void actionPerformed(ActionEvent e) { 483 SaveAction.getInstance().doSave(layer); 484 } 485 } 486 487 public static class LayerSaveAsAction extends AbstractAction { 488 private final transient Layer layer; 489 490 public LayerSaveAsAction(Layer layer) { 491 putValue(SMALL_ICON, ImageProvider.get("save_as")); 492 putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file.")); 493 putValue(NAME, tr("Save As...")); 494 setEnabled(true); 495 this.layer = layer; 496 } 497 498 @Override 499 public void actionPerformed(ActionEvent e) { 500 SaveAsAction.getInstance().doSave(layer); 501 } 502 } 503 504 public static class LayerGpxExportAction extends AbstractAction { 505 private final transient Layer layer; 506 507 public LayerGpxExportAction(Layer layer) { 508 putValue(SMALL_ICON, ImageProvider.get("exportgpx")); 509 putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file.")); 510 putValue(NAME, tr("Export to GPX...")); 511 setEnabled(true); 512 this.layer = layer; 513 } 514 515 @Override 516 public void actionPerformed(ActionEvent e) { 517 new GpxExportAction().export(layer); 518 } 519 } 520 521 /* --------------------------------------------------------------------------------- */ 522 /* interface ProjectionChangeListener */ 523 /* --------------------------------------------------------------------------------- */ 524 @Override 525 public void projectionChanged(Projection oldValue, Projection newValue) { 526 if (!isProjectionSupported(newValue)) { 527 String message = "<html><body><p>" + 528 tr("The layer {0} does not support the new projection {1}.", getName(), newValue.toCode()) + "</p>" + 529 "<p style='width: 450px;'>" + tr("Supported projections are: {0}", nameSupportedProjections()) + "</p>" + 530 tr("Change the projection again or remove the layer."); 531 532 JOptionPane.showMessageDialog(Main.parent, 533 message, 534 tr("Warning"), 535 JOptionPane.WARNING_MESSAGE); 536 } 537 } 538 539 /** 540 * Initializes the layer after a successful load of data from a file 541 * @since 5459 542 */ 543 public void onPostLoadFromFile() { 544 // To be overriden if needed 545 } 546 547 /** 548 * Replies the savable state of this layer (i.e if it can be saved through a "File->Save" dialog). 549 * @return true if this layer can be saved to a file 550 * @since 5459 551 */ 552 public boolean isSavable() { 553 return false; 554 } 555 556 /** 557 * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.) 558 * @return <code>true</code>, if it is safe to save. 559 * @since 5459 560 */ 561 public boolean checkSaveConditions() { 562 return true; 563 } 564 565 /** 566 * Creates a new "Save" dialog for this layer and makes it visible.<br> 567 * When the user has chosen a file, checks the file extension, and confirms overwrite if needed. 568 * @return The output {@code File} 569 * @see SaveActionBase#createAndOpenSaveFileChooser 570 * @since 5459 571 */ 572 public File createAndOpenSaveFileChooser() { 573 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay"); 574 } 575 576 /** 577 * @return bytes that the tile will use. Needed for resource management 578 */ 579 protected long estimateMemoryUsage() { 580 return 0; 581 } 582 583 /** 584 * Gets the strategy that specifies where this layer should be inserted in a layer list. 585 * @return That strategy. 586 * @since 10008 587 */ 588 public LayerPositionStrategy getDefaultLayerPosition() { 589 if (isBackgroundLayer()) { 590 return LayerPositionStrategy.BEFORE_FIRST_BACKGROUND_LAYER; 591 } else { 592 return LayerPositionStrategy.AFTER_LAST_VALIDATION_LAYER; 593 } 594 } 595}