source: josm/trunk/src/org/openstreetmap/josm/gui/layer/Layer.java@ 10809

Last change on this file since 10809 was 10809, checked in by Don-vip, 8 years ago

fix #13175 - Use invalidation event instead of isChanged() (patch by michael2402) - gsoc-core

  • Property svn:eol-style set to native
File size: 18.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.event.ActionEvent;
9import java.beans.PropertyChangeListener;
10import java.beans.PropertyChangeSupport;
11import java.io.File;
12import java.util.List;
13
14import javax.swing.AbstractAction;
15import javax.swing.Action;
16import javax.swing.Icon;
17import javax.swing.JOptionPane;
18import javax.swing.JSeparator;
19import javax.swing.SwingUtilities;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.actions.GpxExportAction;
23import org.openstreetmap.josm.actions.SaveAction;
24import org.openstreetmap.josm.actions.SaveActionBase;
25import org.openstreetmap.josm.actions.SaveAsAction;
26import org.openstreetmap.josm.data.ProjectionBounds;
27import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
28import org.openstreetmap.josm.data.projection.Projection;
29import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
30import org.openstreetmap.josm.tools.Destroyable;
31import org.openstreetmap.josm.tools.ImageProvider;
32import org.openstreetmap.josm.tools.Utils;
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 */
49public abstract class Layer extends AbstractMapViewPaintable implements Destroyable, ProjectionChangeListener {
50
51 /**
52 * Action related to a single layer.
53 */
54 public interface LayerAction {
55
56 /**
57 * Determines if this action supports a given list of layers.
58 * @param layers list of layers
59 * @return {@code true} if this action supports the given list of layers, {@code false} otherwise
60 */
61 boolean supportLayers(List<Layer> layers);
62
63 /**
64 * Creates and return the menu component.
65 * @return the menu component
66 */
67 Component createMenuComponent();
68 }
69
70 /**
71 * Action related to several layers.
72 * @since 10600 (functional interface)
73 */
74 @FunctionalInterface
75 public interface MultiLayerAction {
76
77 /**
78 * Returns the action for a given list of layers.
79 * @param layers list of layers
80 * @return the action for the given list of layers
81 */
82 Action getMultiLayerAction(List<Layer> layers);
83 }
84
85 /**
86 * Special class that can be returned by getMenuEntries when JSeparator needs to be created
87 */
88 public static class SeparatorLayerAction extends AbstractAction implements LayerAction {
89 /** Unique instance */
90 public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
91
92 @Override
93 public void actionPerformed(ActionEvent e) {
94 throw new UnsupportedOperationException();
95 }
96
97 @Override
98 public Component createMenuComponent() {
99 return new JSeparator();
100 }
101
102 @Override
103 public boolean supportLayers(List<Layer> layers) {
104 return false;
105 }
106 }
107
108 public static final String VISIBLE_PROP = Layer.class.getName() + ".visible";
109 public static final String OPACITY_PROP = Layer.class.getName() + ".opacity";
110 public static final String NAME_PROP = Layer.class.getName() + ".name";
111 public static final String FILTER_STATE_PROP = Layer.class.getName() + ".filterstate";
112
113 /**
114 * keeps track of property change listeners
115 */
116 protected PropertyChangeSupport propertyChangeSupport;
117
118 /**
119 * The visibility state of the layer.
120 */
121 private boolean visible = true;
122
123 /**
124 * The opacity of the layer.
125 */
126 private double opacity = 1;
127
128 /**
129 * The layer should be handled as a background layer in automatic handling
130 */
131 private boolean background;
132
133 /**
134 * The name of this layer.
135 */
136 private String name;
137
138 /**
139 * This is set if user renamed this layer.
140 */
141 private boolean renamed;
142
143 /**
144 * If a file is associated with this layer, this variable should be set to it.
145 */
146 private File associatedFile;
147
148 /**
149 * Create the layer and fill in the necessary components.
150 * @param name Layer name
151 */
152 public Layer(String name) {
153 this.propertyChangeSupport = new PropertyChangeSupport(this);
154 setName(name);
155 }
156
157 /**
158 * Initialization code, that depends on Main.map.mapView.
159 *
160 * It is always called in the event dispatching thread.
161 * Note that Main.map is null as long as no layer has been added, so do
162 * not execute code in the constructor, that assumes Main.map.mapView is
163 * not null.
164 *
165 * If you need to execute code when this layer is added to the map view, use
166 * {@link #attachToMapView(org.openstreetmap.josm.gui.layer.MapViewPaintable.MapViewEvent)}
167 */
168 public void hookUpMapView() {
169 }
170
171 /**
172 * Return a representative small image for this layer. The image must not
173 * be larger than 64 pixel in any dimension.
174 * @return layer icon
175 */
176 public abstract Icon getIcon();
177
178 /**
179 * Return a Color for this layer. Return null when no color specified.
180 * @param ignoreCustom Custom color should return null, as no default color
181 * is used. When this is true, then even for custom coloring the base
182 * color is returned - mainly for layer internal use.
183 * @return layer color
184 */
185 public Color getColor(boolean ignoreCustom) {
186 return null;
187 }
188
189 /**
190 * @return A small tooltip hint about some statistics for this layer.
191 */
192 public abstract String getToolTipText();
193
194 /**
195 * Merges the given layer into this layer. Throws if the layer types are
196 * incompatible.
197 * @param from The layer that get merged into this one. After the merge,
198 * the other layer is not usable anymore and passing to one others
199 * mergeFrom should be one of the last things to do with a layer.
200 */
201 public abstract void mergeFrom(Layer from);
202
203 /**
204 * @param other The other layer that is tested to be mergable with this.
205 * @return Whether the other layer can be merged into this layer.
206 */
207 public abstract boolean isMergable(Layer other);
208
209 public abstract void visitBoundingBox(BoundingXYVisitor v);
210
211 public abstract Object getInfoComponent();
212
213 /**
214 * Determines if info dialog can be resized (false by default).
215 * @return {@code true} if the info dialog can be resized, {@code false} otherwise
216 * @since 6708
217 */
218 public boolean isInfoResizable() {
219 return false;
220 }
221
222 /**
223 * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
224 * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
225 * have correct equals implementation.
226 *
227 * Use {@link SeparatorLayerAction#INSTANCE} instead of new JSeparator
228 * @return menu actions for this layer
229 */
230 public abstract Action[] getMenuEntries();
231
232 /**
233 * Called, when the layer is removed from the mapview and is going to be destroyed.
234 *
235 * This is because the Layer constructor can not add itself safely as listener
236 * to the layerlist dialog, because there may be no such dialog yet (loaded
237 * via command line parameter).
238 */
239 @Override
240 public void destroy() {
241 // Override in subclasses if needed
242 }
243
244 public File getAssociatedFile() {
245 return associatedFile;
246 }
247
248 public void setAssociatedFile(File file) {
249 associatedFile = file;
250 }
251
252 /**
253 * Replies the name of the layer
254 *
255 * @return the name of the layer
256 */
257 public String getName() {
258 return name;
259 }
260
261 /**
262 * Sets the name of the layer
263 *
264 * @param name the name. If null, the name is set to the empty string.
265 */
266 public final void setName(String name) {
267 if (name == null) {
268 name = "";
269 }
270 String oldValue = this.name;
271 this.name = name;
272 if (!this.name.equals(oldValue)) {
273 propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
274 }
275 }
276
277 /**
278 * Rename layer and set renamed flag to mark it as renamed (has user given name).
279 *
280 * @param name the name. If null, the name is set to the empty string.
281 */
282 public final void rename(String name) {
283 renamed = true;
284 setName(name);
285 }
286
287 /**
288 * Replies true if this layer was renamed by user
289 *
290 * @return true if this layer was renamed by user
291 */
292 public boolean isRenamed() {
293 return renamed;
294 }
295
296 /**
297 * Replies true if this layer is a background layer
298 *
299 * @return true if this layer is a background layer
300 */
301 public boolean isBackgroundLayer() {
302 return background;
303 }
304
305 /**
306 * Sets whether this layer is a background layer
307 *
308 * @param background true, if this layer is a background layer
309 */
310 public void setBackgroundLayer(boolean background) {
311 this.background = background;
312 }
313
314 /**
315 * Sets the visibility of this layer. Emits property change event for
316 * property {@link #VISIBLE_PROP}.
317 *
318 * @param visible true, if the layer is visible; false, otherwise.
319 */
320 public void setVisible(boolean visible) {
321 boolean oldValue = isVisible();
322 this.visible = visible;
323 if (visible && opacity == 0) {
324 setOpacity(1);
325 } else if (oldValue != isVisible()) {
326 fireVisibleChanged(oldValue, isVisible());
327 }
328 }
329
330 /**
331 * Replies true if this layer is visible. False, otherwise.
332 * @return true if this layer is visible. False, otherwise.
333 */
334 public boolean isVisible() {
335 return visible && opacity != 0;
336 }
337
338 /**
339 * Gets the opacity of the layer, in range 0...1
340 * @return The opacity
341 */
342 public double getOpacity() {
343 return opacity;
344 }
345
346 /**
347 * Sets the opacity of the layer, in range 0...1
348 * @param opacity The opacity
349 * @throws IllegalArgumentException if the opacity is out of range
350 */
351 public void setOpacity(double opacity) {
352 if (!(opacity >= 0 && opacity <= 1))
353 throw new IllegalArgumentException("Opacity value must be between 0 and 1");
354 double oldOpacity = getOpacity();
355 boolean oldVisible = isVisible();
356 this.opacity = opacity;
357 if (!Utils.equalsEpsilon(oldOpacity, getOpacity())) {
358 fireOpacityChanged(oldOpacity, getOpacity());
359 }
360 if (oldVisible != isVisible()) {
361 fireVisibleChanged(oldVisible, isVisible());
362 }
363 }
364
365 /**
366 * Sets new state to the layer after applying {@link ImageProcessor}.
367 */
368 public void setFilterStateChanged() {
369 fireFilterStateChanged();
370 }
371
372 /**
373 * Toggles the visibility state of this layer.
374 */
375 public void toggleVisible() {
376 setVisible(!isVisible());
377 }
378
379 /**
380 * Adds a {@link PropertyChangeListener}
381 *
382 * @param listener the listener
383 */
384 public void addPropertyChangeListener(PropertyChangeListener listener) {
385 propertyChangeSupport.addPropertyChangeListener(listener);
386 }
387
388 /**
389 * Removes a {@link PropertyChangeListener}
390 *
391 * @param listener the listener
392 */
393 public void removePropertyChangeListener(PropertyChangeListener listener) {
394 propertyChangeSupport.removePropertyChangeListener(listener);
395 }
396
397 /**
398 * fires a property change for the property {@link #VISIBLE_PROP}
399 *
400 * @param oldValue the old value
401 * @param newValue the new value
402 */
403 protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
404 propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
405 }
406
407 /**
408 * fires a property change for the property {@link #OPACITY_PROP}
409 *
410 * @param oldValue the old value
411 * @param newValue the new value
412 */
413 protected void fireOpacityChanged(double oldValue, double newValue) {
414 propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
415 }
416
417 /**
418 * fires a property change for the property {@link #FILTER_STATE_PROP}.
419 */
420 protected void fireFilterStateChanged() {
421 propertyChangeSupport.firePropertyChange(FILTER_STATE_PROP, null, null);
422 }
423
424 /**
425 * Check changed status of layer
426 *
427 * @return True if layer was changed since last paint
428 * @deprecated This is not supported by multiple map views.
429 * Fire an {@link #invalidate()} to trigger a repaint.
430 * Let this method return false if you only use invalidation events.
431 */
432 @Deprecated
433 public boolean isChanged() {
434 return true;
435 }
436
437 /**
438 * allows to check whether a projection is supported or not
439 * @param proj projection
440 *
441 * @return True if projection is supported for this layer
442 */
443 public boolean isProjectionSupported(Projection proj) {
444 return proj != null;
445 }
446
447 /**
448 * Specify user information about projections
449 *
450 * @return User readable text telling about supported projections
451 */
452 public String nameSupportedProjections() {
453 return tr("All projections are supported");
454 }
455
456 /**
457 * The action to save a layer
458 */
459 public static class LayerSaveAction extends AbstractAction {
460 private final transient Layer layer;
461
462 public LayerSaveAction(Layer layer) {
463 putValue(SMALL_ICON, ImageProvider.get("save"));
464 putValue(SHORT_DESCRIPTION, tr("Save the current data."));
465 putValue(NAME, tr("Save"));
466 setEnabled(true);
467 this.layer = layer;
468 }
469
470 @Override
471 public void actionPerformed(ActionEvent e) {
472 SaveAction.getInstance().doSave(layer);
473 }
474 }
475
476 public static class LayerSaveAsAction extends AbstractAction {
477 private final transient Layer layer;
478
479 public LayerSaveAsAction(Layer layer) {
480 putValue(SMALL_ICON, ImageProvider.get("save_as"));
481 putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
482 putValue(NAME, tr("Save As..."));
483 setEnabled(true);
484 this.layer = layer;
485 }
486
487 @Override
488 public void actionPerformed(ActionEvent e) {
489 SaveAsAction.getInstance().doSave(layer);
490 }
491 }
492
493 public static class LayerGpxExportAction extends AbstractAction {
494 private final transient Layer layer;
495
496 public LayerGpxExportAction(Layer layer) {
497 putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
498 putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
499 putValue(NAME, tr("Export to GPX..."));
500 setEnabled(true);
501 this.layer = layer;
502 }
503
504 @Override
505 public void actionPerformed(ActionEvent e) {
506 new GpxExportAction().export(layer);
507 }
508 }
509
510 /* --------------------------------------------------------------------------------- */
511 /* interface ProjectionChangeListener */
512 /* --------------------------------------------------------------------------------- */
513 @Override
514 public void projectionChanged(Projection oldValue, Projection newValue) {
515 if (!isProjectionSupported(newValue)) {
516 final String message = "<html><body><p>" +
517 tr("The layer {0} does not support the new projection {1}.", getName(), newValue.toCode()) + "</p>" +
518 "<p style='width: 450px;'>" + tr("Supported projections are: {0}", nameSupportedProjections()) + "</p>" +
519 tr("Change the projection again or remove the layer.");
520
521 // run later to not block loading the UI.
522 SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(Main.parent,
523 message,
524 tr("Warning"),
525 JOptionPane.WARNING_MESSAGE));
526 }
527 }
528
529 /**
530 * Initializes the layer after a successful load of data from a file
531 * @since 5459
532 */
533 public void onPostLoadFromFile() {
534 // To be overriden if needed
535 }
536
537 /**
538 * Replies the savable state of this layer (i.e if it can be saved through a "File-&gt;Save" dialog).
539 * @return true if this layer can be saved to a file
540 * @since 5459
541 */
542 public boolean isSavable() {
543 return false;
544 }
545
546 /**
547 * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
548 * @return <code>true</code>, if it is safe to save.
549 * @since 5459
550 */
551 public boolean checkSaveConditions() {
552 return true;
553 }
554
555 /**
556 * Creates a new "Save" dialog for this layer and makes it visible.<br>
557 * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
558 * @return The output {@code File}
559 * @see SaveActionBase#createAndOpenSaveFileChooser
560 * @since 5459
561 */
562 public File createAndOpenSaveFileChooser() {
563 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
564 }
565
566 /**
567 * @return bytes that the tile will use. Needed for resource management
568 * @deprecated Not used any more.
569 */
570 @Deprecated
571 protected long estimateMemoryUsage() {
572 return 0;
573 }
574
575 /**
576 * Gets the strategy that specifies where this layer should be inserted in a layer list.
577 * @return That strategy.
578 * @since 10008
579 */
580 public LayerPositionStrategy getDefaultLayerPosition() {
581 if (isBackgroundLayer()) {
582 return LayerPositionStrategy.BEFORE_FIRST_BACKGROUND_LAYER;
583 } else {
584 return LayerPositionStrategy.AFTER_LAST_VALIDATION_LAYER;
585 }
586 }
587
588 /**
589 * Gets the {@link ProjectionBounds} for this layer to be visible to the user. This can be the exact bounds, the UI handles padding. Return
590 * <code>null</code> if you cannot provide this information. The default implementation uses the bounds from
591 * {@link #visitBoundingBox(BoundingXYVisitor)}.
592 * @return The bounds for this layer.
593 * @since 10371
594 */
595 public ProjectionBounds getViewProjectionBounds() {
596 BoundingXYVisitor v = new BoundingXYVisitor();
597 visitBoundingBox(v);
598 return v.getBounds();
599 }
600}
Note: See TracBrowser for help on using the repository browser.