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