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 */ 122 public Layer(String name) { 123 this.propertyChangeSupport = new PropertyChangeSupport(this); 124 setName(name); 125 } 126 127 /** 128 * Initialization code, that depends on Main.map.mapView. 129 * 130 * It is always called in the event dispatching thread. 131 * Note that Main.map is null as long as no layer has been added, so do 132 * not execute code in the constructor, that assumes Main.map.mapView is 133 * not null. Instead override this method. 134 */ 135 public void hookUpMapView() { 136 } 137 138 /** 139 * Paint the dataset using the engine set. 140 * @param mv The object that can translate GeoPoints to screen coordinates. 141 */ 142 @Override 143 public abstract void paint(Graphics2D g, MapView mv, Bounds box); 144 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 public abstract 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 public abstract 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 public abstract 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 public abstract boolean isMergable(Layer other); 180 181 public abstract void visitBoundingBox(BoundingXYVisitor v); 182 183 public abstract Object getInfoComponent(); 184 185 /** 186 * Determines if info dialog can be resized (false by default). 187 * @return {@code true} if the info dialog can be resized, {@code false} otherwise 188 * @since 6708 189 */ 190 public boolean isInfoResizable() { 191 return false; 192 } 193 194 /** 195 * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other 196 * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also 197 * have correct equals implementation. 198 * 199 * Use SeparatorLayerAction.INSTANCE instead of new JSeparator 200 * 201 */ 202 public abstract Action[] getMenuEntries(); 203 204 /** 205 * Called, when the layer is removed from the mapview and is going to be 206 * destroyed. 207 * 208 * This is because the Layer constructor can not add itself safely as listener 209 * to the layerlist dialog, because there may be no such dialog yet (loaded 210 * via command line parameter). 211 */ 212 @Override 213 public void destroy() {} 214 215 public File getAssociatedFile() { return associatedFile; } 216 public void setAssociatedFile(File file) { associatedFile = file; } 217 218 /** 219 * Replies the name of the layer 220 * 221 * @return the name of the layer 222 */ 223 public String getName() { 224 return name; 225 } 226 227 /** 228 * Sets the name of the layer 229 * 230 *@param name the name. If null, the name is set to the empty string. 231 * 232 */ 233 public final void setName(String name) { 234 if (name == null) { 235 name = ""; 236 } 237 String oldValue = this.name; 238 this.name = name; 239 if (!this.name.equals(oldValue)) { 240 propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name); 241 } 242 } 243 244 /** 245 * Replies true if this layer is a background layer 246 * 247 * @return true if this layer is a background layer 248 */ 249 public boolean isBackgroundLayer() { 250 return background; 251 } 252 253 /** 254 * Sets whether this layer is a background layer 255 * 256 * @param background true, if this layer is a background layer 257 */ 258 public void setBackgroundLayer(boolean background) { 259 this.background = background; 260 } 261 262 /** 263 * Sets the visibility of this layer. Emits property change event for 264 * property {@link #VISIBLE_PROP}. 265 * 266 * @param visible true, if the layer is visible; false, otherwise. 267 */ 268 public void setVisible(boolean visible) { 269 boolean oldValue = isVisible(); 270 this.visible = visible; 271 if (visible && opacity == 0) { 272 setOpacity(1); 273 } else if (oldValue != isVisible()) { 274 fireVisibleChanged(oldValue, isVisible()); 275 } 276 } 277 278 /** 279 * Replies true if this layer is visible. False, otherwise. 280 * @return true if this layer is visible. False, otherwise. 281 */ 282 public boolean isVisible() { 283 return visible && opacity != 0; 284 } 285 286 public double getOpacity() { 287 return opacity; 288 } 289 290 public void setOpacity(double opacity) { 291 if (!(opacity >= 0 && opacity <= 1)) 292 throw new IllegalArgumentException("Opacity value must be between 0 and 1"); 293 double oldOpacity = getOpacity(); 294 boolean oldVisible = isVisible(); 295 this.opacity = opacity; 296 if (oldOpacity != getOpacity()) { 297 fireOpacityChanged(oldOpacity, getOpacity()); 298 } 299 if (oldVisible != isVisible()) { 300 fireVisibleChanged(oldVisible, isVisible()); 301 } 302 } 303 304 /** 305 * Toggles the visibility state of this layer. 306 */ 307 public void toggleVisible() { 308 setVisible(!isVisible()); 309 } 310 311 /** 312 * Adds a {@link PropertyChangeListener} 313 * 314 * @param listener the listener 315 */ 316 public void addPropertyChangeListener(PropertyChangeListener listener) { 317 propertyChangeSupport.addPropertyChangeListener(listener); 318 } 319 320 /** 321 * Removes a {@link PropertyChangeListener} 322 * 323 * @param listener the listener 324 */ 325 public void removePropertyChangeListener(PropertyChangeListener listener) { 326 propertyChangeSupport.removePropertyChangeListener(listener); 327 } 328 329 /** 330 * fires a property change for the property {@link #VISIBLE_PROP} 331 * 332 * @param oldValue the old value 333 * @param newValue the new value 334 */ 335 protected void fireVisibleChanged(boolean oldValue, boolean newValue) { 336 propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue); 337 } 338 339 /** 340 * fires a property change for the property {@link #OPACITY_PROP} 341 * 342 * @param oldValue the old value 343 * @param newValue the new value 344 */ 345 protected void fireOpacityChanged(double oldValue, double newValue) { 346 propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue); 347 } 348 349 /** 350 * Check changed status of layer 351 * 352 * @return True if layer was changed since last paint 353 */ 354 public boolean isChanged() { 355 return true; 356 } 357 358 /** 359 * allows to check whether a projection is supported or not 360 * 361 * @return True if projection is supported for this layer 362 */ 363 public boolean isProjectionSupported(Projection proj) { 364 return true; 365 } 366 367 /** 368 * Specify user information about projections 369 * 370 * @return User readable text telling about supported projections 371 */ 372 public String nameSupportedProjections() { 373 return tr("All projections are supported"); 374 } 375 376 /** 377 * The action to save a layer 378 * 379 */ 380 public static class LayerSaveAction extends AbstractAction { 381 private Layer layer; 382 public LayerSaveAction(Layer layer) { 383 putValue(SMALL_ICON, ImageProvider.get("save")); 384 putValue(SHORT_DESCRIPTION, tr("Save the current data.")); 385 putValue(NAME, tr("Save")); 386 setEnabled(true); 387 this.layer = layer; 388 } 389 390 @Override 391 public void actionPerformed(ActionEvent e) { 392 SaveAction.getInstance().doSave(layer); 393 } 394 } 395 396 public static class LayerSaveAsAction extends AbstractAction { 397 private Layer layer; 398 public LayerSaveAsAction(Layer layer) { 399 putValue(SMALL_ICON, ImageProvider.get("save_as")); 400 putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file.")); 401 putValue(NAME, tr("Save As...")); 402 setEnabled(true); 403 this.layer = layer; 404 } 405 406 @Override 407 public void actionPerformed(ActionEvent e) { 408 SaveAsAction.getInstance().doSave(layer); 409 } 410 } 411 412 public static class LayerGpxExportAction extends AbstractAction { 413 private Layer layer; 414 public LayerGpxExportAction(Layer layer) { 415 putValue(SMALL_ICON, ImageProvider.get("exportgpx")); 416 putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file.")); 417 putValue(NAME, tr("Export to GPX...")); 418 setEnabled(true); 419 this.layer = layer; 420 } 421 422 @Override 423 public void actionPerformed(ActionEvent e) { 424 new GpxExportAction().export(layer); 425 } 426 } 427 428 /* --------------------------------------------------------------------------------- */ 429 /* interface ProjectionChangeListener */ 430 /* --------------------------------------------------------------------------------- */ 431 @Override 432 public void projectionChanged(Projection oldValue, Projection newValue) { 433 if(!isProjectionSupported(newValue)) { 434 JOptionPane.showMessageDialog(Main.parent, 435 tr("The layer {0} does not support the new projection {1}.\n{2}\n" 436 + "Change the projection again or remove the layer.", 437 getName(), newValue.toCode(), nameSupportedProjections()), 438 tr("Warning"), 439 JOptionPane.WARNING_MESSAGE); 440 } 441 } 442 443 /** 444 * Initializes the layer after a successful load of data from a file 445 * @since 5459 446 */ 447 public void onPostLoadFromFile() { 448 // To be overriden if needed 449 } 450 451 /** 452 * Replies the savable state of this layer (i.e if it can be saved through a "File->Save" dialog). 453 * @return true if this layer can be saved to a file 454 * @since 5459 455 */ 456 public boolean isSavable() { 457 return false; 458 } 459 460 /** 461 * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.) 462 * @return <code>true</code>, if it is safe to save. 463 * @since 5459 464 */ 465 public boolean checkSaveConditions() { 466 return true; 467 } 468 469 /** 470 * Creates a new "Save" dialog for this layer and makes it visible.<br> 471 * When the user has chosen a file, checks the file extension, and confirms overwrite if needed. 472 * @return The output {@code File} 473 * @since 5459 474 * @see SaveActionBase#createAndOpenSaveFileChooser 475 */ 476 public File createAndOpenSaveFileChooser() { 477 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay"); 478 } 479}