| 1 | // License: GPL. See LICENSE file for details. |
|---|
| 2 | |
|---|
| 3 | package org.openstreetmap.josm.gui.layer; |
|---|
| 4 | |
|---|
| 5 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 6 | |
|---|
| 7 | import java.awt.Color; |
|---|
| 8 | import java.awt.Component; |
|---|
| 9 | import java.awt.Graphics2D; |
|---|
| 10 | import java.awt.event.ActionEvent; |
|---|
| 11 | import java.beans.PropertyChangeListener; |
|---|
| 12 | import java.beans.PropertyChangeSupport; |
|---|
| 13 | import java.io.File; |
|---|
| 14 | import java.util.List; |
|---|
| 15 | |
|---|
| 16 | import javax.swing.AbstractAction; |
|---|
| 17 | import javax.swing.Action; |
|---|
| 18 | import javax.swing.Icon; |
|---|
| 19 | import javax.swing.JOptionPane; |
|---|
| 20 | import javax.swing.JSeparator; |
|---|
| 21 | |
|---|
| 22 | import org.openstreetmap.josm.Main; |
|---|
| 23 | import org.openstreetmap.josm.actions.GpxExportAction; |
|---|
| 24 | import org.openstreetmap.josm.actions.SaveAction; |
|---|
| 25 | import org.openstreetmap.josm.actions.SaveAsAction; |
|---|
| 26 | import org.openstreetmap.josm.data.Bounds; |
|---|
| 27 | import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; |
|---|
| 28 | import org.openstreetmap.josm.data.projection.Projection; |
|---|
| 29 | import org.openstreetmap.josm.data.projection.ProjectionChangeListener; |
|---|
| 30 | import org.openstreetmap.josm.gui.MapView; |
|---|
| 31 | import org.openstreetmap.josm.tools.Destroyable; |
|---|
| 32 | import org.openstreetmap.josm.tools.ImageProvider; |
|---|
| 33 | |
|---|
| 34 | /** |
|---|
| 35 | * A layer encapsulates the gui component of one dataset and its representation. |
|---|
| 36 | * |
|---|
| 37 | * Some layers may display data directly imported from OSM server. Other only |
|---|
| 38 | * display background images. Some can be edited, some not. Some are static and |
|---|
| 39 | * other changes dynamically (auto-updated). |
|---|
| 40 | * |
|---|
| 41 | * Layers can be visible or not. Most actions the user can do applies only on |
|---|
| 42 | * selected layers. The available actions depend on the selected layers too. |
|---|
| 43 | * |
|---|
| 44 | * All layers are managed by the MapView. They are displayed in a list to the |
|---|
| 45 | * right of the screen. |
|---|
| 46 | * |
|---|
| 47 | * @author imi |
|---|
| 48 | */ |
|---|
| 49 | abstract public class Layer implements Destroyable, MapViewPaintable, ProjectionChangeListener { |
|---|
| 50 | |
|---|
| 51 | public interface LayerAction { |
|---|
| 52 | boolean supportLayers(List<Layer> layers); |
|---|
| 53 | Component createMenuComponent(); |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | public interface MultiLayerAction { |
|---|
| 57 | Action getMultiLayerAction(List<Layer> layers); |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | |
|---|
| 61 | /** |
|---|
| 62 | * Special class that can be returned by getMenuEntries when JSeparator needs to be created |
|---|
| 63 | * |
|---|
| 64 | */ |
|---|
| 65 | public static class SeparatorLayerAction extends AbstractAction implements LayerAction { |
|---|
| 66 | public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction(); |
|---|
| 67 | @Override |
|---|
| 68 | public void actionPerformed(ActionEvent e) { |
|---|
| 69 | throw new UnsupportedOperationException(); |
|---|
| 70 | } |
|---|
| 71 | @Override |
|---|
| 72 | public Component createMenuComponent() { |
|---|
| 73 | return new JSeparator(); |
|---|
| 74 | } |
|---|
| 75 | @Override |
|---|
| 76 | public boolean supportLayers(List<Layer> layers) { |
|---|
| 77 | return false; |
|---|
| 78 | } |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | static public final String VISIBLE_PROP = Layer.class.getName() + ".visible"; |
|---|
| 82 | static public final String OPACITY_PROP = Layer.class.getName() + ".opacity"; |
|---|
| 83 | static public final String NAME_PROP = Layer.class.getName() + ".name"; |
|---|
| 84 | |
|---|
| 85 | /** keeps track of property change listeners */ |
|---|
| 86 | protected PropertyChangeSupport propertyChangeSupport; |
|---|
| 87 | |
|---|
| 88 | /** |
|---|
| 89 | * The visibility state of the layer. |
|---|
| 90 | * |
|---|
| 91 | */ |
|---|
| 92 | private boolean visible = true; |
|---|
| 93 | |
|---|
| 94 | /** |
|---|
| 95 | * The opacity of the layer. |
|---|
| 96 | * |
|---|
| 97 | */ |
|---|
| 98 | private double opacity = 1; |
|---|
| 99 | |
|---|
| 100 | /** |
|---|
| 101 | * The layer should be handled as a background layer in automatic handling |
|---|
| 102 | * |
|---|
| 103 | */ |
|---|
| 104 | private boolean background = false; |
|---|
| 105 | |
|---|
| 106 | /** |
|---|
| 107 | * The name of this layer. |
|---|
| 108 | * |
|---|
| 109 | */ |
|---|
| 110 | private String name; |
|---|
| 111 | |
|---|
| 112 | /** |
|---|
| 113 | * If a file is associated with this layer, this variable should be set to it. |
|---|
| 114 | */ |
|---|
| 115 | private File associatedFile; |
|---|
| 116 | |
|---|
| 117 | /** |
|---|
| 118 | * Create the layer and fill in the necessary components. |
|---|
| 119 | */ |
|---|
| 120 | public Layer(String name) { |
|---|
| 121 | this.propertyChangeSupport = new PropertyChangeSupport(this); |
|---|
| 122 | setName(name); |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | /** |
|---|
| 126 | * Paint the dataset using the engine set. |
|---|
| 127 | * @param mv The object that can translate GeoPoints to screen coordinates. |
|---|
| 128 | */ |
|---|
| 129 | @Override |
|---|
| 130 | abstract public void paint(Graphics2D g, MapView mv, Bounds box); |
|---|
| 131 | /** |
|---|
| 132 | * Return a representative small image for this layer. The image must not |
|---|
| 133 | * be larger than 64 pixel in any dimension. |
|---|
| 134 | */ |
|---|
| 135 | abstract public Icon getIcon(); |
|---|
| 136 | |
|---|
| 137 | /** |
|---|
| 138 | * Return a Color for this layer. Return null when no color specified. |
|---|
| 139 | * @param ignoreCustom Custom color should return null, as no default color |
|---|
| 140 | * is used. When this is true, then even for custom coloring the base |
|---|
| 141 | * color is returned - mainly for layer internal use. |
|---|
| 142 | */ |
|---|
| 143 | public Color getColor(boolean ignoreCustom) { |
|---|
| 144 | return null; |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | /** |
|---|
| 148 | * @return A small tooltip hint about some statistics for this layer. |
|---|
| 149 | */ |
|---|
| 150 | abstract public String getToolTipText(); |
|---|
| 151 | |
|---|
| 152 | /** |
|---|
| 153 | * Merges the given layer into this layer. Throws if the layer types are |
|---|
| 154 | * incompatible. |
|---|
| 155 | * @param from The layer that get merged into this one. After the merge, |
|---|
| 156 | * the other layer is not usable anymore and passing to one others |
|---|
| 157 | * mergeFrom should be one of the last things to do with a layer. |
|---|
| 158 | */ |
|---|
| 159 | abstract public void mergeFrom(Layer from); |
|---|
| 160 | |
|---|
| 161 | /** |
|---|
| 162 | * @param other The other layer that is tested to be mergable with this. |
|---|
| 163 | * @return Whether the other layer can be merged into this layer. |
|---|
| 164 | */ |
|---|
| 165 | abstract public boolean isMergable(Layer other); |
|---|
| 166 | |
|---|
| 167 | abstract public void visitBoundingBox(BoundingXYVisitor v); |
|---|
| 168 | |
|---|
| 169 | abstract public Object getInfoComponent(); |
|---|
| 170 | |
|---|
| 171 | /** |
|---|
| 172 | * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other |
|---|
| 173 | * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also |
|---|
| 174 | * have correct equals implementation. |
|---|
| 175 | * |
|---|
| 176 | * Use SeparatorLayerAction.INSTANCE instead of new JSeparator |
|---|
| 177 | * |
|---|
| 178 | */ |
|---|
| 179 | abstract public Action[] getMenuEntries(); |
|---|
| 180 | |
|---|
| 181 | /** |
|---|
| 182 | * Called, when the layer is removed from the mapview and is going to be |
|---|
| 183 | * destroyed. |
|---|
| 184 | * |
|---|
| 185 | * This is because the Layer constructor can not add itself safely as listener |
|---|
| 186 | * to the layerlist dialog, because there may be no such dialog yet (loaded |
|---|
| 187 | * via command line parameter). |
|---|
| 188 | */ |
|---|
| 189 | @Override |
|---|
| 190 | public void destroy() {} |
|---|
| 191 | |
|---|
| 192 | public File getAssociatedFile() { return associatedFile; } |
|---|
| 193 | public void setAssociatedFile(File file) { associatedFile = file; } |
|---|
| 194 | |
|---|
| 195 | /** |
|---|
| 196 | * Replies the name of the layer |
|---|
| 197 | * |
|---|
| 198 | * @return the name of the layer |
|---|
| 199 | */ |
|---|
| 200 | public String getName() { |
|---|
| 201 | return name; |
|---|
| 202 | } |
|---|
| 203 | |
|---|
| 204 | /** |
|---|
| 205 | * Sets the name of the layer |
|---|
| 206 | * |
|---|
| 207 | *@param name the name. If null, the name is set to the empty string. |
|---|
| 208 | * |
|---|
| 209 | */ |
|---|
| 210 | public void setName(String name) { |
|---|
| 211 | if (name == null) { |
|---|
| 212 | name = ""; |
|---|
| 213 | } |
|---|
| 214 | String oldValue = this.name; |
|---|
| 215 | this.name = name; |
|---|
| 216 | if (!this.name.equals(oldValue)) { |
|---|
| 217 | propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name); |
|---|
| 218 | } |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | /** |
|---|
| 222 | * Replies true if this layer is a background layer |
|---|
| 223 | * |
|---|
| 224 | * @return true if this layer is a background layer |
|---|
| 225 | */ |
|---|
| 226 | public boolean isBackgroundLayer() { |
|---|
| 227 | return background; |
|---|
| 228 | } |
|---|
| 229 | |
|---|
| 230 | /** |
|---|
| 231 | * Sets whether this layer is a background layer |
|---|
| 232 | * |
|---|
| 233 | * @param background true, if this layer is a background layer |
|---|
| 234 | */ |
|---|
| 235 | public void setBackgroundLayer(boolean background) { |
|---|
| 236 | this.background = background; |
|---|
| 237 | } |
|---|
| 238 | |
|---|
| 239 | /** |
|---|
| 240 | * Sets the visibility of this layer. Emits property change event for |
|---|
| 241 | * property {@see #VISIBLE_PROP}. |
|---|
| 242 | * |
|---|
| 243 | * @param visible true, if the layer is visible; false, otherwise. |
|---|
| 244 | */ |
|---|
| 245 | public void setVisible(boolean visible) { |
|---|
| 246 | boolean oldValue = isVisible(); |
|---|
| 247 | this.visible = visible; |
|---|
| 248 | if (visible && opacity == 0) { |
|---|
| 249 | setOpacity(1); |
|---|
| 250 | } else if (oldValue != isVisible()) { |
|---|
| 251 | fireVisibleChanged(oldValue, isVisible()); |
|---|
| 252 | } |
|---|
| 253 | } |
|---|
| 254 | |
|---|
| 255 | /** |
|---|
| 256 | * Replies true if this layer is visible. False, otherwise. |
|---|
| 257 | * @return true if this layer is visible. False, otherwise. |
|---|
| 258 | */ |
|---|
| 259 | public boolean isVisible() { |
|---|
| 260 | return visible && opacity != 0; |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | public double getOpacity() { |
|---|
| 264 | return opacity; |
|---|
| 265 | } |
|---|
| 266 | |
|---|
| 267 | public void setOpacity(double opacity) { |
|---|
| 268 | if (!(opacity >= 0 && opacity <= 1)) |
|---|
| 269 | throw new IllegalArgumentException("Opacity value must be between 0 and 1"); |
|---|
| 270 | double oldOpacity = getOpacity(); |
|---|
| 271 | boolean oldVisible = isVisible(); |
|---|
| 272 | this.opacity = opacity; |
|---|
| 273 | if (oldOpacity != getOpacity()) { |
|---|
| 274 | fireOpacityChanged(oldOpacity, getOpacity()); |
|---|
| 275 | } |
|---|
| 276 | if (oldVisible != isVisible()) { |
|---|
| 277 | fireVisibleChanged(oldVisible, isVisible()); |
|---|
| 278 | } |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | /** |
|---|
| 282 | * Toggles the visibility state of this layer. |
|---|
| 283 | */ |
|---|
| 284 | public void toggleVisible() { |
|---|
| 285 | setVisible(!isVisible()); |
|---|
| 286 | } |
|---|
| 287 | |
|---|
| 288 | /** |
|---|
| 289 | * Adds a {@see PropertyChangeListener} |
|---|
| 290 | * |
|---|
| 291 | * @param listener the listener |
|---|
| 292 | */ |
|---|
| 293 | public void addPropertyChangeListener(PropertyChangeListener listener) { |
|---|
| 294 | propertyChangeSupport.addPropertyChangeListener(listener); |
|---|
| 295 | } |
|---|
| 296 | |
|---|
| 297 | /** |
|---|
| 298 | * Removes a {@see PropertyChangeListener} |
|---|
| 299 | * |
|---|
| 300 | * @param listener the listener |
|---|
| 301 | */ |
|---|
| 302 | public void removePropertyChangeListener(PropertyChangeListener listener) { |
|---|
| 303 | propertyChangeSupport.removePropertyChangeListener(listener); |
|---|
| 304 | } |
|---|
| 305 | |
|---|
| 306 | /** |
|---|
| 307 | * fires a property change for the property {@see #VISIBLE_PROP} |
|---|
| 308 | * |
|---|
| 309 | * @param oldValue the old value |
|---|
| 310 | * @param newValue the new value |
|---|
| 311 | */ |
|---|
| 312 | protected void fireVisibleChanged(boolean oldValue, boolean newValue) { |
|---|
| 313 | propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue); |
|---|
| 314 | } |
|---|
| 315 | |
|---|
| 316 | /** |
|---|
| 317 | * fires a property change for the property {@see #OPACITY_PROP} |
|---|
| 318 | * |
|---|
| 319 | * @param oldValue the old value |
|---|
| 320 | * @param newValue the new value |
|---|
| 321 | */ |
|---|
| 322 | protected void fireOpacityChanged(double oldValue, double newValue) { |
|---|
| 323 | propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue); |
|---|
| 324 | } |
|---|
| 325 | |
|---|
| 326 | /** |
|---|
| 327 | * Check changed status of layer |
|---|
| 328 | * |
|---|
| 329 | * @return True if layer was changed since last paint |
|---|
| 330 | */ |
|---|
| 331 | public boolean isChanged() { |
|---|
| 332 | return true; |
|---|
| 333 | } |
|---|
| 334 | |
|---|
| 335 | /** |
|---|
| 336 | * allows to check whether a projection is supported or not |
|---|
| 337 | * |
|---|
| 338 | * @return True if projection is supported for this layer |
|---|
| 339 | */ |
|---|
| 340 | public boolean isProjectionSupported(Projection proj) { |
|---|
| 341 | return true; |
|---|
| 342 | } |
|---|
| 343 | |
|---|
| 344 | /** |
|---|
| 345 | * Specify user information about projections |
|---|
| 346 | * |
|---|
| 347 | * @return User readable text telling about supported projections |
|---|
| 348 | */ |
|---|
| 349 | public String nameSupportedProjections() { |
|---|
| 350 | return tr("All projections are supported"); |
|---|
| 351 | } |
|---|
| 352 | |
|---|
| 353 | /** |
|---|
| 354 | * The action to save a layer |
|---|
| 355 | * |
|---|
| 356 | */ |
|---|
| 357 | public static class LayerSaveAction extends AbstractAction { |
|---|
| 358 | private Layer layer; |
|---|
| 359 | public LayerSaveAction(Layer layer) { |
|---|
| 360 | putValue(SMALL_ICON, ImageProvider.get("save")); |
|---|
| 361 | putValue(SHORT_DESCRIPTION, tr("Save the current data.")); |
|---|
| 362 | putValue(NAME, tr("Save")); |
|---|
| 363 | setEnabled(true); |
|---|
| 364 | this.layer = layer; |
|---|
| 365 | } |
|---|
| 366 | |
|---|
| 367 | public void actionPerformed(ActionEvent e) { |
|---|
| 368 | SaveAction.getInstance().doSave(layer); |
|---|
| 369 | } |
|---|
| 370 | } |
|---|
| 371 | |
|---|
| 372 | public static class LayerSaveAsAction extends AbstractAction { |
|---|
| 373 | private Layer layer; |
|---|
| 374 | public LayerSaveAsAction(Layer layer) { |
|---|
| 375 | putValue(SMALL_ICON, ImageProvider.get("save_as")); |
|---|
| 376 | putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file.")); |
|---|
| 377 | putValue(NAME, tr("Save As...")); |
|---|
| 378 | setEnabled(true); |
|---|
| 379 | this.layer = layer; |
|---|
| 380 | } |
|---|
| 381 | |
|---|
| 382 | public void actionPerformed(ActionEvent e) { |
|---|
| 383 | SaveAsAction.getInstance().doSave(layer); |
|---|
| 384 | } |
|---|
| 385 | } |
|---|
| 386 | |
|---|
| 387 | public static class LayerGpxExportAction extends AbstractAction { |
|---|
| 388 | private Layer layer; |
|---|
| 389 | public LayerGpxExportAction(Layer layer) { |
|---|
| 390 | putValue(SMALL_ICON, ImageProvider.get("exportgpx")); |
|---|
| 391 | putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file.")); |
|---|
| 392 | putValue(NAME, tr("Export to GPX...")); |
|---|
| 393 | setEnabled(true); |
|---|
| 394 | this.layer = layer; |
|---|
| 395 | } |
|---|
| 396 | |
|---|
| 397 | public void actionPerformed(ActionEvent e) { |
|---|
| 398 | new GpxExportAction().export(layer); |
|---|
| 399 | } |
|---|
| 400 | } |
|---|
| 401 | |
|---|
| 402 | /* --------------------------------------------------------------------------------- */ |
|---|
| 403 | /* interface ProjectionChangeListener */ |
|---|
| 404 | /* --------------------------------------------------------------------------------- */ |
|---|
| 405 | @Override |
|---|
| 406 | public void projectionChanged(Projection oldValue, Projection newValue) { |
|---|
| 407 | if(!isProjectionSupported(newValue)) { |
|---|
| 408 | JOptionPane.showMessageDialog(Main.parent, |
|---|
| 409 | tr("The layer {0} does not support the new projection {1}.\n{2}\n" |
|---|
| 410 | + "Change the projection again or remove the layer.", |
|---|
| 411 | getName(), newValue.toCode(), nameSupportedProjections()), |
|---|
| 412 | tr("Warning"), |
|---|
| 413 | JOptionPane.WARNING_MESSAGE); |
|---|
| 414 | } |
|---|
| 415 | } |
|---|
| 416 | } |
|---|