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