source: josm/trunk/src/org/openstreetmap/josm/gui/MapView.java@ 2809

Last change on this file since 2809 was 2809, checked in by jttt, 14 years ago

Use image without alpha for offscreen buffer. Seems to the same thing but it's faster by 70ms on every paint

  • Property svn:eol-style set to native
File size: 24.0 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui;
4
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Color;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.Point;
11import java.awt.Rectangle;
12import java.awt.event.ComponentAdapter;
13import java.awt.event.ComponentEvent;
14import java.awt.event.MouseEvent;
15import java.awt.event.MouseMotionListener;
16import java.awt.geom.Area;
17import java.awt.geom.GeneralPath;
18import java.awt.image.BufferedImage;
19import java.beans.PropertyChangeEvent;
20import java.beans.PropertyChangeListener;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Comparator;
25import java.util.Enumeration;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.concurrent.CopyOnWriteArrayList;
29
30import javax.swing.AbstractButton;
31import javax.swing.JComponent;
32import javax.swing.JOptionPane;
33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.actions.AutoScaleAction;
36import org.openstreetmap.josm.actions.JosmAction;
37import org.openstreetmap.josm.actions.MoveAction;
38import org.openstreetmap.josm.actions.mapmode.MapMode;
39import org.openstreetmap.josm.data.Bounds;
40import org.openstreetmap.josm.data.SelectionChangedListener;
41import org.openstreetmap.josm.data.coor.LatLon;
42import org.openstreetmap.josm.data.osm.DataSet;
43import org.openstreetmap.josm.data.osm.DataSource;
44import org.openstreetmap.josm.data.osm.OsmPrimitive;
45import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
46import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
47import org.openstreetmap.josm.gui.layer.GpxLayer;
48import org.openstreetmap.josm.gui.layer.Layer;
49import org.openstreetmap.josm.gui.layer.MapViewPaintable;
50import org.openstreetmap.josm.gui.layer.OsmDataLayer;
51import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
52import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
53import org.openstreetmap.josm.tools.AudioPlayer;
54
55/**
56 * This is a component used in the MapFrame for browsing the map. It use is to
57 * provide the MapMode's enough capabilities to operate.
58 *
59 * MapView hold meta-data about the data set currently displayed, as scale level,
60 * center point viewed, what scrolling mode or editing mode is selected or with
61 * what projection the map is viewed etc..
62 *
63 * MapView is able to administrate several layers.
64 *
65 * @author imi
66 */
67public class MapView extends NavigatableComponent implements PropertyChangeListener {
68
69 /**
70 * Interface to notify listeners of the change of the active layer.
71 * @author imi
72 */
73 public interface LayerChangeListener {
74 void activeLayerChange(Layer oldLayer, Layer newLayer);
75 void layerAdded(Layer newLayer);
76 void layerRemoved(Layer oldLayer);
77 }
78
79 public interface EditLayerChangeListener {
80 void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer);
81 }
82
83 /**
84 * the layer listeners
85 */
86 private static final CopyOnWriteArrayList<MapView.LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<MapView.LayerChangeListener>();
87 private static final CopyOnWriteArrayList<EditLayerChangeListener> editLayerChangeListeners = new CopyOnWriteArrayList<EditLayerChangeListener>();
88
89 /**
90 * Removes a layer change listener
91 *
92 * @param listener the listener. Ignored if null or already registered.
93 */
94 public static void removeLayerChangeListener(MapView.LayerChangeListener listener) {
95 layerChangeListeners.remove(listener);
96 }
97
98 public static void removeEditLayerChangeListener(EditLayerChangeListener listener) {
99 editLayerChangeListeners.remove(listener);
100 }
101
102 /**
103 * Adds a layer change listener
104 *
105 * @param listener the listener. Ignored if null or already registered.
106 */
107 public static void addLayerChangeListener(MapView.LayerChangeListener listener) {
108 if (listener != null) {
109 layerChangeListeners.addIfAbsent(listener);
110 }
111 }
112
113 public static void addEditLayerChangeListener(EditLayerChangeListener listener) {
114 if (listener != null) {
115 editLayerChangeListeners.addIfAbsent(listener);
116 }
117 }
118
119 protected static void fireActiveLayerChanged(Layer oldLayer, Layer newLayer) {
120 for (LayerChangeListener l : layerChangeListeners) {
121 l.activeLayerChange(oldLayer, newLayer);
122 }
123 }
124
125 protected static void fireLayerAdded(Layer newLayer) {
126 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
127 l.layerAdded(newLayer);
128 }
129 }
130
131 protected static void fireLayerRemoved(Layer layer) {
132 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
133 l.layerRemoved(layer);
134 }
135 }
136
137 protected static void fireEditLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
138 for (EditLayerChangeListener l : editLayerChangeListeners) {
139 l.editLayerChanged(oldLayer, newLayer);
140 }
141 }
142
143 /**
144 * A list of all layers currently loaded.
145 */
146 private ArrayList<Layer> layers = new ArrayList<Layer>();
147 /**
148 * The play head marker: there is only one of these so it isn't in any specific layer
149 */
150 public PlayHeadMarker playHeadMarker = null;
151
152 /**
153 * The layer from the layers list that is currently active.
154 */
155 private Layer activeLayer;
156
157 private OsmDataLayer editLayer;
158
159 /**
160 * The last event performed by mouse.
161 */
162 public MouseEvent lastMEvent;
163
164 private LinkedList<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
165
166 private BufferedImage offscreenBuffer;
167
168 public MapView() {
169 addComponentListener(new ComponentAdapter(){
170 @Override public void componentResized(ComponentEvent e) {
171 removeComponentListener(this);
172
173 MapSlider zoomSlider = new MapSlider(MapView.this);
174 add(zoomSlider);
175 zoomSlider.setBounds(3, 0, 114, 30);
176
177 MapScaler scaler = new MapScaler(MapView.this);
178 add(scaler);
179 scaler.setLocation(10,30);
180
181 if (!zoomToEditLayerBoundingBox()) {
182 new AutoScaleAction("data").actionPerformed(null);
183 }
184
185 new MapMover(MapView.this, Main.contentPane);
186 JosmAction mv;
187 mv = new MoveAction(MoveAction.Direction.UP);
188 if (mv.getShortcut() != null) {
189 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "UP");
190 Main.contentPane.getActionMap().put("UP", mv);
191 }
192 mv = new MoveAction(MoveAction.Direction.DOWN);
193 if (mv.getShortcut() != null) {
194 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "DOWN");
195 Main.contentPane.getActionMap().put("DOWN", mv);
196 }
197 mv = new MoveAction(MoveAction.Direction.LEFT);
198 if (mv.getShortcut() != null) {
199 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "LEFT");
200 Main.contentPane.getActionMap().put("LEFT", mv);
201 }
202 mv = new MoveAction(MoveAction.Direction.RIGHT);
203 if (mv.getShortcut() != null) {
204 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "RIGHT");
205 Main.contentPane.getActionMap().put("RIGHT", mv);
206 }
207 }
208 });
209
210 // listend to selection changes to redraw the map
211 DataSet.selListeners.add(new SelectionChangedListener(){
212 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
213 repaint();
214 }
215 });
216
217 //store the last mouse action
218 this.addMouseMotionListener(new MouseMotionListener() {
219 public void mouseDragged(MouseEvent e) {
220 mouseMoved(e);
221 }
222 public void mouseMoved(MouseEvent e) {
223 lastMEvent = e;
224 }
225 });
226 }
227
228 /**
229 * Adds a GPX layer. A GPX layer is added below the lowest data layer.
230 *
231 * @param layer the GPX layer
232 */
233 protected void addGpxLayer(GpxLayer layer) {
234 if (layers.isEmpty()) {
235 layers.add(layer);
236 return;
237 }
238 for (int i=layers.size()-1; i>= 0; i--) {
239 if (layers.get(i) instanceof OsmDataLayer) {
240 if (i == layers.size()-1) {
241 layers.add(layer);
242 } else {
243 layers.add(i+1, layer);
244 }
245 return;
246 }
247 }
248 layers.add(layer);
249 }
250
251 /**
252 * Add a layer to the current MapView. The layer will be added at topmost
253 * position.
254 */
255 public void addLayer(Layer layer) {
256 if (layer instanceof MarkerLayer && playHeadMarker == null) {
257 playHeadMarker = PlayHeadMarker.create();
258 }
259
260 if (layer instanceof GpxLayer) {
261 addGpxLayer((GpxLayer)layer);
262 } else if (layer.isBackgroundLayer() || layers.isEmpty()) {
263 layers.add(layer);
264 } else {
265 layers.add(0, layer);
266 }
267 fireLayerAdded(layer);
268 if (layer instanceof OsmDataLayer || activeLayer == null) {
269 // autoselect the new layer
270 setActiveLayer(layer);
271 }
272 layer.addPropertyChangeListener(this);
273 AudioPlayer.reset();
274 repaint();
275 }
276
277 @Override
278 protected DataSet getCurrentDataSet() {
279 if (editLayer != null)
280 return editLayer.data;
281 else
282 return null;
283 }
284
285 /**
286 * Replies true if the active layer is drawable.
287 *
288 * @return true if the active layer is drawable, false otherwise
289 */
290 public boolean isActiveLayerDrawable() {
291 return editLayer != null;
292 }
293
294 /**
295 * Replies true if the active layer is visible.
296 *
297 * @return true if the active layer is visible, false otherwise
298 */
299 public boolean isActiveLayerVisible() {
300 return isActiveLayerDrawable() && editLayer.isVisible();
301 }
302
303 /**
304 * Determines the next active data layer according to the following
305 * rules:
306 * <ul>
307 * <li>if there is at least one {@see OsmDataLayer} the first one
308 * becomes active</li>
309 * <li>otherwise, the top most layer of any type becomes active</li>
310 * </ul>
311 *
312 * @return the next active data layer
313 */
314 protected Layer determineNextActiveLayer(Layer ignoredLayer) {
315 // First look for data layer
316 for (Layer layer:layers) {
317 if (layer instanceof OsmDataLayer && layer != ignoredLayer)
318 return layer;
319 }
320
321 // Then any layer
322 for (Layer layer:layers) {
323 if (layer != ignoredLayer)
324 return layer;
325 }
326
327 // and then give up
328 return null;
329
330 }
331
332 /**
333 * Remove the layer from the mapview. If the layer was in the list before,
334 * an LayerChange event is fired.
335 */
336 public void removeLayer(Layer layer) {
337 if (layer == activeLayer) {
338 setActiveLayer(determineNextActiveLayer(activeLayer));
339 }
340 if (layers.remove(layer)) {
341 fireLayerRemoved(layer);
342 }
343 layer.removePropertyChangeListener(this);
344 layer.destroy();
345 AudioPlayer.reset();
346 repaint();
347 }
348
349 private boolean virtualNodesEnabled = false;
350
351 public void setVirtualNodesEnabled(boolean enabled) {
352 if(virtualNodesEnabled != enabled) {
353 virtualNodesEnabled = enabled;
354 repaint();
355 }
356 }
357 public boolean isVirtualNodesEnabled() {
358 return virtualNodesEnabled;
359 }
360
361 /**
362 * Moves the layer to the given new position. No event is fired, but repaints
363 * according to the new Z-Order of the layers.
364 *
365 * @param layer The layer to move
366 * @param pos The new position of the layer
367 */
368 public void moveLayer(Layer layer, int pos) {
369 int curLayerPos = layers.indexOf(layer);
370 if (curLayerPos == -1)
371 throw new IllegalArgumentException(tr("Layer not in list."));
372 if (pos == curLayerPos)
373 return; // already in place.
374 layers.remove(curLayerPos);
375 if (pos >= layers.size()) {
376 layers.add(layer);
377 } else {
378 layers.add(pos, layer);
379 }
380 setEditLayer();
381 AudioPlayer.reset();
382 repaint();
383 }
384
385 public int getLayerPos(Layer layer) {
386 int curLayerPos = layers.indexOf(layer);
387 if (curLayerPos == -1)
388 throw new IllegalArgumentException(tr("Layer not in list."));
389 return curLayerPos;
390 }
391
392 /**
393 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
394 * first, layer with the highest Z-Order last.
395 *
396 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
397 * first, layer with the highest Z-Order last.
398 */
399 protected List<Layer> getVisibleLayersInZOrder() {
400 ArrayList<Layer> ret = new ArrayList<Layer>();
401 for (Layer l: layers) {
402 if (l.isVisible()) {
403 ret.add(l);
404 }
405 }
406 // sort according to position in the list of layers, with one exception:
407 // an active data layer always becomes a higher Z-Order than all other
408 // data layers
409 //
410 Collections.sort(
411 ret,
412 new Comparator<Layer>() {
413 public int compare(Layer l1, Layer l2) {
414 if (l1 instanceof OsmDataLayer && l2 instanceof OsmDataLayer) {
415 if (l1 == getActiveLayer()) return -1;
416 if (l2 == getActiveLayer()) return 1;
417 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2));
418 } else
419 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2));
420 }
421 }
422 );
423 Collections.reverse(ret);
424 return ret;
425 }
426
427 /**
428 * Draw the component.
429 */
430 @Override public void paint(Graphics g) {
431 if (center == null)
432 return; // no data loaded yet.
433
434 // re-create offscreen-buffer if we've been resized, otherwise
435 // just re-use it.
436 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth()
437 || offscreenBuffer.getHeight() != getHeight()) {
438 offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
439 }
440
441 Graphics2D tempG = offscreenBuffer.createGraphics();
442 tempG.setClip(g.getClip());
443 tempG.setColor(PaintColors.BACKGROUND.get());
444 tempG.fillRect(0, 0, getWidth(), getHeight());
445
446 Bounds box = getLatLonBounds(g.getClipBounds());
447
448 for (Layer l: getVisibleLayersInZOrder()) {
449 l.paint(tempG, this, box);
450 }
451 for (MapViewPaintable mvp : temporaryLayers) {
452 mvp.paint(tempG, this, box);
453 }
454
455 // draw world borders
456 tempG.setColor(Color.WHITE);
457 Bounds b = getProjection().getWorldBoundsLatLon();
458 double lat = b.getMin().lat();
459 double lon = b.getMin().lon();
460
461 Point p = getPoint(b.getMin());
462
463 GeneralPath path = new GeneralPath();
464
465 path.moveTo(p.x, p.y);
466 double max = b.getMax().lat();
467 for(; lat <= max; lat += 1.0)
468 {
469 p = getPoint(new LatLon(lat >= max ? max : lat, lon));
470 path.lineTo(p.x, p.y);
471 }
472 lat = max; max = b.getMax().lon();
473 for(; lon <= max; lon += 1.0)
474 {
475 p = getPoint(new LatLon(lat, lon >= max ? max : lon));
476 path.lineTo(p.x, p.y);
477 }
478 lon = max; max = b.getMin().lat();
479 for(; lat >= max; lat -= 1.0)
480 {
481 p = getPoint(new LatLon(lat <= max ? max : lat, lon));
482 path.lineTo(p.x, p.y);
483 }
484 lat = max; max = b.getMin().lon();
485 for(; lon >= max; lon -= 1.0)
486 {
487 p = getPoint(new LatLon(lat, lon <= max ? max : lon));
488 path.lineTo(p.x, p.y);
489 }
490
491 int w = offscreenBuffer.getWidth();
492 int h = offscreenBuffer.getHeight();
493
494 // Work around OpenJDK having problems when drawing out of bounds
495 final Area border = new Area(path);
496 // Make the viewport 1px larger in every direction to prevent an
497 // additional 1px border when zooming in
498 final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2));
499 border.intersect(viewport);
500 tempG.draw(border);
501
502 if (playHeadMarker != null) {
503 playHeadMarker.paint(tempG, this);
504 }
505
506 g.drawImage(offscreenBuffer, 0, 0, null);
507 super.paint(g);
508 }
509
510 /**
511 * Set the new dimension to the view.
512 */
513 public void recalculateCenterScale(BoundingXYVisitor box) {
514 if (box == null) {
515 box = new BoundingXYVisitor();
516 }
517 if (box.getBounds() == null) {
518 box.visit(getProjection().getWorldBoundsLatLon());
519 }
520 if (!box.hasExtend()) {
521 box.enlargeBoundingBox();
522 }
523
524 zoomTo(box.getBounds());
525 }
526
527 /**
528 * @return An unmodifiable collection of all layers
529 */
530 public Collection<Layer> getAllLayers() {
531 return Collections.unmodifiableCollection(layers);
532 }
533
534 /**
535 * @return An unmodifiable ordered list of all layers
536 */
537 public List<Layer> getAllLayersAsList() {
538 return Collections.unmodifiableList(layers);
539 }
540
541 /**
542 * Replies an unmodifiable list of layers of a certain type.
543 *
544 * Example:
545 * <pre>
546 * List<WMSLayer> wmsLayers = getLayersOfType(WMSLayer.class);
547 * </pre>
548 *
549 * @return an unmodifiable list of layers of a certain type.
550 */
551 public <T> List<T> getLayersOfType(Class<T> ofType) {
552 ArrayList<T> ret = new ArrayList<T>();
553 for (Layer layer : getAllLayersAsList()) {
554 if (ofType.isInstance(layer)) {
555 ret.add(ofType.cast(layer));
556 }
557 }
558 return ret;
559 }
560
561 /**
562 * Replies the number of layers managed by this mav view
563 *
564 * @return the number of layers managed by this mav view
565 */
566 public int getNumLayers() {
567 return layers.size();
568 }
569
570 /**
571 * Replies true if there is at least one layer in this map view
572 *
573 * @return true if there is at least one layer in this map view
574 */
575 public boolean hasLayers() {
576 return getNumLayers() > 0;
577 }
578
579 private void setEditLayer() {
580 OsmDataLayer newEditLayer = editLayer;
581 OsmDataLayer oldEditLayer = editLayer;
582
583 // Find new edit layer
584 if (activeLayer != editLayer) {
585 if (activeLayer instanceof OsmDataLayer) {
586 newEditLayer = (OsmDataLayer) activeLayer;
587 } else {
588 for (Layer layer:layers) {
589 if (layer instanceof OsmDataLayer) {
590 newEditLayer = (OsmDataLayer) layer;
591 break;
592 }
593 }
594 }
595 }
596
597 // Set new edit layer
598 if (newEditLayer != editLayer) {
599 if (newEditLayer == null) {
600 getCurrentDataSet().setSelected();
601 }
602
603 editLayer = newEditLayer;
604 fireEditLayerChanged(oldEditLayer, newEditLayer);
605 refreshTitle();
606 }
607
608 }
609
610 /**
611 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
612 * of {@see OsmDataLayer} also sets {@see #editLayer} to <code>layer</code>.
613 *
614 * @param layer the layer to be activate; must be one of the layers in the list of layers
615 * @exception IllegalArgumentException thrown if layer is not in the lis of layers
616 */
617 public void setActiveLayer(Layer layer) {
618 if (layer != null && !layers.contains(layer))
619 throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
620
621 if (layer == activeLayer)
622 return;
623
624 Layer old = activeLayer;
625 activeLayer = layer;
626 setEditLayer();
627 fireActiveLayerChanged(old, layer);
628
629 /* This only makes the buttons look disabled. Disabling the actions as well requires
630 * the user to re-select the tool after i.e. moving a layer. While testing I found
631 * that I switch layers and actions at the same time and it was annoying to mind the
632 * order. This way it works as visual clue for new users */
633 for (Enumeration<AbstractButton> e = Main.map.toolGroup.getElements() ; e.hasMoreElements() ;) {
634 AbstractButton x=e.nextElement();
635 x.setEnabled(((MapMode)x.getAction()).layerIsSupported(layer));
636 }
637 AudioPlayer.reset();
638 repaint();
639 }
640
641 /**
642 * Replies the currently active layer
643 *
644 * @return the currently active layer (may be null)
645 */
646 public Layer getActiveLayer() {
647 return activeLayer;
648 }
649
650 /**
651 * Replies the current edit layer, if any
652 *
653 * @return the current edit layer. May be null.
654 */
655 public OsmDataLayer getEditLayer() {
656 return editLayer;
657 }
658
659 /**
660 * replies true if the list of layers managed by this map view contain layer
661 *
662 * @param layer the layer
663 * @return true if the list of layers managed by this map view contain layer
664 */
665 public boolean hasLayer(Layer layer) {
666 return layers.contains(layer);
667 }
668
669 /**
670 * Tries to zoom to the download boundingbox[es] of the current edit layer
671 * (aka {@link OsmDataLayer}). If the edit layer has multiple download bounding
672 * boxes it zooms to a large virtual bounding box containing all smaller ones.
673 * This implementation can be used for resolving ticket #1461.
674 *
675 * @return <code>true</code> if a zoom operation has been performed
676 */
677 public boolean zoomToEditLayerBoundingBox() {
678 // workaround for #1461 (zoom to download bounding box instead of all data)
679 // In case we already have an existing data layer ...
680 OsmDataLayer layer= getEditLayer();
681 if (layer == null)
682 return false;
683 Collection<DataSource> dataSources = layer.data.dataSources;
684 // ... with bounding box[es] of data loaded from OSM or a file...
685 BoundingXYVisitor bbox = new BoundingXYVisitor();
686 for (DataSource ds : dataSources) {
687 bbox.visit(ds.bounds);
688 if (bbox.hasExtend()) {
689 // ... we zoom to it's bounding box
690 recalculateCenterScale(bbox);
691 return true;
692 }
693 }
694 return false;
695 }
696
697 public boolean addTemporaryLayer(MapViewPaintable mvp) {
698 if (temporaryLayers.contains(mvp)) return false;
699 return temporaryLayers.add(mvp);
700 }
701
702 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
703 return temporaryLayers.remove(mvp);
704 }
705
706 public void propertyChange(PropertyChangeEvent evt) {
707 if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
708 repaint();
709 } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
710 || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
711 OsmDataLayer layer = (OsmDataLayer)evt.getSource();
712 if (layer == getEditLayer()) {
713 refreshTitle();
714 }
715 }
716 }
717
718 protected void refreshTitle() {
719 boolean dirty = editLayer != null && (editLayer.requiresSaveToFile() || editLayer.requiresUploadToServer());
720 if (dirty) {
721 JOptionPane.getFrameForComponent(Main.parent).setTitle("* " + tr("Java OpenStreetMap Editor"));
722 } else {
723 JOptionPane.getFrameForComponent(Main.parent).setTitle(tr("Java OpenStreetMap Editor"));
724 }
725 }
726
727}
Note: See TracBrowser for help on using the repository browser.