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

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

remove deprecated stuff - gsoc-core

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