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