source: josm/trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java@ 2500

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

Added Dataset consistency test that can be invoked on osm layer or in bug report handler

  • Property svn:eol-style set to native
File size: 27.7 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui.layer;
4
5import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
6import static org.openstreetmap.josm.tools.I18n.marktr;
7import static org.openstreetmap.josm.tools.I18n.tr;
8import static org.openstreetmap.josm.tools.I18n.trn;
9
10import java.awt.AlphaComposite;
11import java.awt.Color;
12import java.awt.Component;
13import java.awt.Composite;
14import java.awt.Graphics2D;
15import java.awt.GridBagLayout;
16import java.awt.Point;
17import java.awt.Rectangle;
18import java.awt.TexturePaint;
19import java.awt.event.ActionEvent;
20import java.awt.geom.Area;
21import java.awt.image.BufferedImage;
22import java.io.File;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.HashSet;
26import java.util.Iterator;
27import java.util.LinkedList;
28import java.util.Set;
29
30import javax.swing.AbstractAction;
31import javax.swing.Icon;
32import javax.swing.JLabel;
33import javax.swing.JMenuItem;
34import javax.swing.JOptionPane;
35import javax.swing.JPanel;
36import javax.swing.JScrollPane;
37import javax.swing.JSeparator;
38import javax.swing.JTextArea;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.actions.RenameLayerAction;
42import org.openstreetmap.josm.command.PurgePrimitivesCommand;
43import org.openstreetmap.josm.data.Bounds;
44import org.openstreetmap.josm.data.conflict.Conflict;
45import org.openstreetmap.josm.data.conflict.ConflictCollection;
46import org.openstreetmap.josm.data.coor.EastNorth;
47import org.openstreetmap.josm.data.coor.LatLon;
48import org.openstreetmap.josm.data.gpx.GpxData;
49import org.openstreetmap.josm.data.gpx.GpxTrack;
50import org.openstreetmap.josm.data.gpx.WayPoint;
51import org.openstreetmap.josm.data.osm.BackreferencedDataSet;
52import org.openstreetmap.josm.data.osm.DataSet;
53import org.openstreetmap.josm.data.osm.DataSetMerger;
54import org.openstreetmap.josm.data.osm.DataSource;
55import org.openstreetmap.josm.data.osm.DatasetConsistencyTest;
56import org.openstreetmap.josm.data.osm.Node;
57import org.openstreetmap.josm.data.osm.OsmPrimitive;
58import org.openstreetmap.josm.data.osm.Relation;
59import org.openstreetmap.josm.data.osm.Way;
60import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
61import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
62import org.openstreetmap.josm.data.osm.visitor.MapPaintVisitor;
63import org.openstreetmap.josm.data.osm.visitor.SimplePaintVisitor;
64import org.openstreetmap.josm.gui.HelpAwareOptionPane;
65import org.openstreetmap.josm.gui.MapView;
66import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
67import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
68import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
69import org.openstreetmap.josm.tools.DateUtils;
70import org.openstreetmap.josm.tools.GBC;
71import org.openstreetmap.josm.tools.ImageProvider;
72
73/**
74 * A layer holding data from a specific dataset.
75 * The data can be fully edited.
76 *
77 * @author imi
78 */
79public class OsmDataLayer extends Layer {
80 static public final String REQUIRES_SAVE_TO_DISK_PROP = OsmDataLayer.class.getName() + ".requiresSaveToDisk";
81 static public final String REQUIRES_UPLOAD_TO_SERVER_PROP = OsmDataLayer.class.getName() + ".requiresUploadToServer";
82
83 private boolean requiresSaveToFile = false;
84 private boolean requiresUploadToServer = false;
85
86 protected void setRequiresSaveToFile(boolean newValue) {
87 boolean oldValue = requiresSaveToFile;
88 requiresSaveToFile = newValue;
89 if (oldValue != newValue) {
90 propertyChangeSupport.firePropertyChange(REQUIRES_SAVE_TO_DISK_PROP, oldValue, newValue);
91 }
92 }
93
94 protected void setRequiresUploadToServer(boolean newValue) {
95 boolean oldValue = requiresUploadToServer;
96 requiresUploadToServer = newValue;
97 if (oldValue != newValue) {
98 propertyChangeSupport.firePropertyChange(REQUIRES_UPLOAD_TO_SERVER_PROP, oldValue, newValue);
99 }
100 }
101
102 /** the global counter for created data layers */
103 static private int dataLayerCounter = 0;
104
105 /**
106 * Replies a new unique name for a data layer
107 *
108 * @return a new unique name for a data layer
109 */
110 static public String createNewName() {
111 dataLayerCounter++;
112 return tr("Data Layer {0}", dataLayerCounter);
113 }
114
115 public final static class DataCountVisitor extends AbstractVisitor {
116 public int nodes;
117 public int ways;
118 public int relations;
119 public int deletedNodes;
120 public int deletedWays;
121 public int deletedRelations;
122
123 public void visit(final Node n) {
124 nodes++;
125 if (n.isDeleted()) {
126 deletedNodes++;
127 }
128 }
129
130 public void visit(final Way w) {
131 ways++;
132 if (w.isDeleted()) {
133 deletedWays++;
134 }
135 }
136
137 public void visit(final Relation r) {
138 relations++;
139 if (r.isDeleted()) {
140 deletedRelations++;
141 }
142 }
143 }
144
145 public interface CommandQueueListener {
146 void commandChanged(int queueSize, int redoSize);
147 }
148
149 /**
150 * The data behind this layer.
151 */
152 public final DataSet data;
153
154 /**
155 * the collection of conflicts detected in this layer
156 */
157 private ConflictCollection conflicts;
158
159 public final LinkedList<DataChangeListener> listenerDataChanged = new LinkedList<DataChangeListener>();
160
161 /**
162 * a paint texture for non-downloaded area
163 */
164 private static TexturePaint hatched;
165
166 static {
167 createHatchTexture();
168 }
169
170 /**
171 * Initialize the hatch pattern used to paint the non-downloaded area
172 */
173 public static void createHatchTexture() {
174 BufferedImage bi = new BufferedImage(15, 15, BufferedImage.TYPE_INT_ARGB);
175 Graphics2D big = bi.createGraphics();
176 big.setColor(Main.pref.getColor(marktr("background"), Color.BLACK));
177 Composite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
178 big.setComposite(comp);
179 big.fillRect(0,0,15,15);
180 big.setColor(Main.pref.getColor(marktr("outside downloaded area"), Color.YELLOW));
181 big.drawLine(0,15,15,0);
182 Rectangle r = new Rectangle(0, 0, 15,15);
183 hatched = new TexturePaint(bi, r);
184 }
185
186 /**
187 * Construct a OsmDataLayer.
188 */
189 public OsmDataLayer(final DataSet data, final String name, final File associatedFile) {
190 super(name);
191 this.data = data;
192 this.setAssociatedFile(associatedFile);
193 conflicts = new ConflictCollection();
194 }
195
196 /**
197 * TODO: @return Return a dynamic drawn icon of the map data. The icon is
198 * updated by a background thread to not disturb the running programm.
199 */
200 @Override public Icon getIcon() {
201 return ImageProvider.get("layer", "osmdata_small");
202 }
203
204 /**
205 * Draw all primitives in this layer but do not draw modified ones (they
206 * are drawn by the edit layer).
207 * Draw nodes last to overlap the ways they belong to.
208 */
209 @Override public void paint(final Graphics2D g, final MapView mv, Bounds box) {
210 boolean active = mv.getActiveLayer() == this;
211 boolean inactive = !active && Main.pref.getBoolean("draw.data.inactive_color", true);
212 boolean virtual = !inactive && mv.isVirtualNodesEnabled();
213
214 // draw the hatched area for non-downloaded region. only draw if we're the active
215 // and bounds are defined; don't draw for inactive layers or loaded GPX files etc
216 if (active && Main.pref.getBoolean("draw.data.downloaded_area", true) && !data.dataSources.isEmpty()) {
217 // initialize area with current viewport
218 Rectangle b = mv.getBounds();
219 // on some platforms viewport bounds seem to be offset from the left,
220 // over-grow it just to be sure
221 b.grow(100, 100);
222 Area a = new Area(b);
223
224 // now succesively subtract downloaded areas
225 for (DataSource src : data.dataSources) {
226 if (src.bounds != null && !src.bounds.getMin().equals(src.bounds.getMax())) {
227 EastNorth en1 = mv.getProjection().latlon2eastNorth(src.bounds.getMin());
228 EastNorth en2 = mv.getProjection().latlon2eastNorth(src.bounds.getMax());
229 Point p1 = mv.getPoint(en1);
230 Point p2 = mv.getPoint(en2);
231 Rectangle r = new Rectangle(Math.min(p1.x, p2.x),Math.min(p1.y, p2.y),Math.abs(p2.x-p1.x),Math.abs(p2.y-p1.y));
232 a.subtract(new Area(r));
233 }
234 }
235
236 // paint remainder
237 g.setPaint(hatched);
238 g.fill(a);
239 }
240
241 SimplePaintVisitor painter;
242 if (Main.pref.getBoolean("draw.wireframe")) {
243 painter = new SimplePaintVisitor();
244 } else {
245 painter = new MapPaintVisitor();
246 }
247 painter.setGraphics(g);
248 painter.setNavigatableComponent(mv);
249 painter.inactive = inactive;
250 painter.visitAll(data, virtual, box);
251 Main.map.conflictDialog.paintConflicts(g, mv);
252 }
253
254 @Override public String getToolTipText() {
255 int nodes = undeletedSize(data.getNodes());
256 int ways = undeletedSize(data.getWays());
257
258 String tool = trn("{0} node", "{0} nodes", nodes, nodes)+", ";
259 tool += trn("{0} way", "{0} ways", ways, ways);
260
261 if (data.getVersion() != null) {
262 tool += ", " + tr("version {0}", data.getVersion());
263 }
264 File f = getAssociatedFile();
265 if (f != null) {
266 tool = "<html>"+tool+"<br>"+f.getPath()+"</html>";
267 }
268 return tool;
269 }
270
271 @Override public void mergeFrom(final Layer from) {
272 mergeFrom(((OsmDataLayer)from).data);
273 }
274
275 /**
276 * merges the primitives in dataset <code>from</code> into the dataset of
277 * this layer
278 *
279 * @param from the source data set
280 */
281 public void mergeFrom(final DataSet from) {
282 final DataSetMerger visitor = new DataSetMerger(data,from);
283 visitor.merge();
284
285 Area a = data.getDataSourceArea();
286
287 // copy the merged layer's data source info;
288 // only add source rectangles if they are not contained in the
289 // layer already.
290 for (DataSource src : from.dataSources) {
291 if (a == null || !a.contains(src.bounds.asRect())) {
292 data.dataSources.add(src);
293 }
294 }
295
296 // copy the merged layer's API version, downgrade if required
297 if (data.getVersion() == null) {
298 data.setVersion(from.getVersion());
299 } else if ("0.5".equals(data.getVersion()) ^ "0.5".equals(from.getVersion())) {
300 System.err.println(tr("Warning: mixing 0.6 and 0.5 data results in version 0.5"));
301 data.setVersion("0.5");
302 }
303
304 int numNewConflicts = 0;
305 for (Conflict<?> c : visitor.getConflicts()) {
306 if (!conflicts.hasConflict(c)) {
307 numNewConflicts++;
308 conflicts.add(c);
309 }
310 }
311 PurgePrimitivesCommand cmd = buildPurgeCommand();
312 if (cmd != null) {
313 Main.main.undoRedo.add(cmd);
314 }
315 fireDataChange();
316 // repaint to make sure new data is displayed properly.
317 Main.map.mapView.repaint();
318 warnNumNewConflicts(
319 numNewConflicts,
320 cmd == null ? 0 : cmd.getPurgedPrimitives().size()
321 );
322 }
323
324 /**
325 * Warns the user about the number of detected conflicts
326 *
327 * @param numNewConflicts the number of detected conflicts
328 * @param numPurgedPrimitives the number of automatically purged objects
329 */
330 protected void warnNumNewConflicts(int numNewConflicts, int numPurgedPrimitives) {
331 if (numNewConflicts == 0 && numPurgedPrimitives == 0) return;
332
333 String msg1 = trn(
334 "There was {0} conflict detected.",
335 "There were {0} conflicts detected.",
336 numNewConflicts,
337 numNewConflicts
338 );
339 String msg2 = trn(
340 "{0} conflict has been <strong>resolved automatically</strong> by purging {0} object<br>from the local dataset because it is deleted on the server.",
341 "{0} conflicts have been <strong>resolved automatically</strong> by purging {0} objects<br> from the local dataset because they are deleted on the server.",
342 numPurgedPrimitives,
343 numPurgedPrimitives
344 );
345 int numRemainingConflicts = numNewConflicts - numPurgedPrimitives;
346 String msg3 = "";
347 if (numRemainingConflicts >0) {
348 msg3 = trn(
349 "{0} conflict remains to be resolved.<br><br>Please open the Conflict List Dialog and manually resolve it.",
350 "{0} conflicts remain to be resolved.<br><br>Please open the Conflict List Dialog and manually resolve them.",
351 numRemainingConflicts,
352 numRemainingConflicts
353 );
354 }
355
356 StringBuffer sb = new StringBuffer();
357 sb.append("<html>").append(msg1);
358 if (numPurgedPrimitives > 0) {
359 sb.append("<br>").append(msg2);
360 }
361 if (numRemainingConflicts > 0) {
362 sb.append("<br>").append(msg3);
363 }
364 sb.append("</html>");
365 if (numNewConflicts > 0) {
366 ButtonSpec[] options = new ButtonSpec[] {
367 new ButtonSpec(
368 tr("OK"),
369 ImageProvider.get("ok"),
370 tr("Click to close this dialog and continue editing"),
371 null /* no specific help */
372 )
373 };
374 HelpAwareOptionPane.showOptionDialog(
375 Main.parent,
376 sb.toString(),
377 tr("Conflicts detected"),
378 JOptionPane.WARNING_MESSAGE,
379 null, /* no icon */
380 options,
381 options[0],
382 ht("/Concepts/Conflict#WarningAboutDetectedConflicts")
383 );
384 }
385 }
386
387 /**
388 * Builds the purge command for primitives which can be purged automatically
389 * from the local dataset because they've been deleted on the
390 * server.
391 *
392 * @return the purge command. <code>null</code> if no primitives have to
393 * be purged
394 */
395 protected PurgePrimitivesCommand buildPurgeCommand() {
396 BackreferencedDataSet ds = new BackreferencedDataSet();
397 ArrayList<OsmPrimitive> toPurge = new ArrayList<OsmPrimitive>();
398 conflictLoop: for (Conflict<?> c: conflicts) {
399 if (c.getMy().isDeleted() && !c.getTheir().isVisible()) {
400 // Local and server version of the primitive are deleted. We
401 // can purge it from the local dataset.
402 //
403 toPurge.add(c.getMy());
404 } else if (!c.getMy().isModified() && ! c.getTheir().isVisible()) {
405 // We purge deleted *ways* and *relations* automatically if they are
406 // deleted on the server and if they aren't modified in the local
407 // dataset.
408 //
409 if (c.getMy() instanceof Way || c.getMy() instanceof Relation) {
410 toPurge.add(c.getMy());
411 continue conflictLoop;
412 }
413 // We only purge nodes if they aren't part of a modified way.
414 // Otherwise the number of nodes of a modified way could drop
415 // below 2 and we would lose the modified data when the way
416 // gets purged.
417 //
418 for (OsmPrimitive parent: ds.getParents(c.getMy())) {
419 if (parent.isModified() && parent instanceof Way) {
420 continue conflictLoop;
421 }
422 }
423 toPurge.add(c.getMy());
424 }
425 }
426 if (toPurge.isEmpty()) return null;
427 PurgePrimitivesCommand cmd = new PurgePrimitivesCommand(this, toPurge);
428 cmd.setBackreferenceDataSet(ds);
429 return cmd;
430 }
431
432 @Override public boolean isMergable(final Layer other) {
433 return other instanceof OsmDataLayer;
434 }
435
436 @Override public void visitBoundingBox(final BoundingXYVisitor v) {
437 for (final Node n: data.getNodes()) {
438 if (n.isUsable()) {
439 v.visit(n);
440 }
441 }
442 }
443
444 /**
445 * Clean out the data behind the layer. This means clearing the redo/undo lists,
446 * really deleting all deleted objects and reset the modified flags. This should
447 * be done after an upload, even after a partial upload.
448 *
449 * @param processed A list of all objects that were actually uploaded.
450 * May be <code>null</code>, which means nothing has been uploaded
451 */
452 public void cleanupAfterUpload(final Collection<OsmPrimitive> processed) {
453 // return immediately if an upload attempt failed
454 if (processed == null || processed.isEmpty())
455 return;
456
457 Main.main.undoRedo.clean(this);
458
459 // if uploaded, clean the modified flags as well
460 final Set<OsmPrimitive> processedSet = new HashSet<OsmPrimitive>(processed);
461 data.clenupDeletedPrimitives();
462 for (final Iterator<Node> it = data.getNodes().iterator(); it.hasNext();) {
463 cleanIterator(it, processedSet);
464 }
465 for (final Iterator<Way> it = data.getWays().iterator(); it.hasNext();) {
466 cleanIterator(it, processedSet);
467 }
468 for (final Iterator<Relation> it = data.getRelations().iterator(); it.hasNext();) {
469 cleanIterator(it, processedSet);
470 }
471 }
472
473 /**
474 * Clean the modified flag for the given iterator over a collection if it is in the
475 * list of processed entries.
476 *
477 * @param it The iterator to change the modified and remove the items if deleted.
478 * @param processed A list of all objects that have been successfully progressed.
479 * If the object in the iterator is not in the list, nothing will be changed on it.
480 */
481 private void cleanIterator(final Iterator<? extends OsmPrimitive> it, final Collection<OsmPrimitive> processed) {
482 final OsmPrimitive osm = it.next();
483 if (!processed.remove(osm))
484 return;
485 osm.setModified(false);
486 }
487
488 /**
489 * @return The number of not-deleted and visible primitives in the list.
490 */
491 private int undeletedSize(final Collection<? extends OsmPrimitive> list) {
492 int size = 0;
493 for (final OsmPrimitive osm : list)
494 if (!osm.isDeleted() && osm.isVisible()) {
495 size++;
496 }
497 return size;
498 }
499
500 @Override public Object getInfoComponent() {
501 final DataCountVisitor counter = new DataCountVisitor();
502 for (final OsmPrimitive osm : data.allPrimitives()) {
503 osm.visit(counter);
504 }
505 final JPanel p = new JPanel(new GridBagLayout());
506
507 String nodeText = trn("{0} node", "{0} nodes", counter.nodes, counter.nodes);
508 if (counter.deletedNodes > 0) {
509 nodeText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedNodes, counter.deletedNodes)+")";
510 }
511
512 String wayText = trn("{0} way", "{0} ways", counter.ways, counter.ways);
513 if (counter.deletedWays > 0) {
514 wayText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedWays, counter.deletedWays)+")";
515 }
516
517 String relationText = trn("{0} relation", "{0} relations", counter.relations, counter.relations);
518 if (counter.deletedRelations > 0) {
519 relationText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedRelations, counter.deletedRelations)+")";
520 }
521
522 p.add(new JLabel(tr("{0} consists of:", getName())), GBC.eol());
523 p.add(new JLabel(nodeText, ImageProvider.get("data", "node"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
524 p.add(new JLabel(wayText, ImageProvider.get("data", "way"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
525 p.add(new JLabel(relationText, ImageProvider.get("data", "relation"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
526 p.add(new JLabel(tr("API version: {0}", (data.getVersion() != null) ? data.getVersion() : tr("unset"))));
527
528 return p;
529 }
530
531 @Override public Component[] getMenuEntries() {
532 if (Main.applet)
533 return new Component[]{
534 new JMenuItem(LayerListDialog.getInstance().createActivateLayerAction(this)),
535 new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
536 new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
537 new JSeparator(),
538 new JMenuItem(LayerListDialog.getInstance().createMergeLayerAction(this)),
539 new JSeparator(),
540 new JMenuItem(new RenameLayerAction(getAssociatedFile(), this)),
541 new JMenuItem(new ConsistencyTestAction()),
542 new JSeparator(),
543 new JMenuItem(new LayerListPopup.InfoAction(this))};
544 return new Component[]{
545 new JMenuItem(LayerListDialog.getInstance().createActivateLayerAction(this)),
546 new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
547 new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
548 new JSeparator(),
549 new JMenuItem(LayerListDialog.getInstance().createMergeLayerAction(this)),
550 new JMenuItem(new LayerSaveAction(this)),
551 new JMenuItem(new LayerSaveAsAction(this)),
552 new JMenuItem(new LayerGpxExportAction(this)),
553 new JMenuItem(new ConvertToGpxLayerAction()),
554 new JSeparator(),
555 new JMenuItem(new RenameLayerAction(getAssociatedFile(), this)),
556 new JMenuItem(new ConsistencyTestAction()),
557 new JSeparator(),
558 new JMenuItem(new LayerListPopup.InfoAction(this))};
559 }
560
561 public void fireDataChange() {
562 setRequiresSaveToFile(true);
563 setRequiresUploadToServer(true);
564 for (DataChangeListener dcl : listenerDataChanged) {
565 dcl.dataChanged(this);
566 }
567 }
568
569 public static GpxData toGpxData(DataSet data, File file) {
570 GpxData gpxData = new GpxData();
571 gpxData.storageFile = file;
572 HashSet<Node> doneNodes = new HashSet<Node>();
573 for (Way w : data.getWays()) {
574 if (!w.isUsable()) {
575 continue;
576 }
577 GpxTrack trk = new GpxTrack();
578 gpxData.tracks.add(trk);
579
580 if (w.get("name") != null) {
581 trk.attr.put("name", w.get("name"));
582 }
583
584 ArrayList<WayPoint> trkseg = null;
585 for (Node n : w.getNodes()) {
586 if (!n.isUsable()) {
587 trkseg = null;
588 continue;
589 }
590 if (trkseg == null) {
591 trkseg = new ArrayList<WayPoint>();
592 trk.trackSegs.add(trkseg);
593 }
594 if (!n.isTagged()) {
595 doneNodes.add(n);
596 }
597 WayPoint wpt = new WayPoint(n.getCoor());
598 if (!n.isTimestampEmpty()) {
599 wpt.attr.put("time", DateUtils.fromDate(n.getTimestamp()));
600 wpt.setTime();
601 }
602 trkseg.add(wpt);
603 }
604 }
605
606 // what is this loop meant to do? it creates waypoints but never
607 // records them?
608 for (Node n : data.getNodes()) {
609 if (n.incomplete || n.isDeleted() || doneNodes.contains(n)) {
610 continue;
611 }
612 WayPoint wpt = new WayPoint(n.getCoor());
613 if (!n.isTimestampEmpty()) {
614 wpt.attr.put("time", DateUtils.fromDate(n.getTimestamp()));
615 wpt.setTime();
616 }
617 String name = n.get("name");
618 if (name != null) {
619 wpt.attr.put("name", name);
620 }
621 }
622 return gpxData;
623 }
624
625 public GpxData toGpxData() {
626 return toGpxData(data, getAssociatedFile());
627 }
628
629 public class ConvertToGpxLayerAction extends AbstractAction {
630 public ConvertToGpxLayerAction() {
631 super(tr("Convert to GPX layer"), ImageProvider.get("converttogpx"));
632 }
633 public void actionPerformed(ActionEvent e) {
634 Main.main.addLayer(new GpxLayer(toGpxData(), tr("Converted from: {0}", getName())));
635 Main.main.removeLayer(OsmDataLayer.this);
636 }
637 }
638
639 public boolean containsPoint(LatLon coor) {
640 // we'll assume that if this has no data sources
641 // that it also has no borders
642 if (this.data.dataSources.isEmpty())
643 return true;
644
645 boolean layer_bounds_point = false;
646 for (DataSource src : this.data.dataSources) {
647 if (src.bounds.contains(coor)) {
648 layer_bounds_point = true;
649 break;
650 }
651 }
652 return layer_bounds_point;
653 }
654
655 /**
656 * replies the set of conflicts currently managed in this layer
657 *
658 * @return the set of conflicts currently managed in this layer
659 */
660 public ConflictCollection getConflicts() {
661 return conflicts;
662 }
663
664 /**
665 * Replies true if the data managed by this layer needs to be uploaded to
666 * the server because it contains at least one modified primitive.
667 *
668 * @return true if the data managed by this layer needs to be uploaded to
669 * the server because it contains at least one modified primitive; false,
670 * otherwise
671 */
672 public boolean requiresUploadToServer() {
673 return requiresUploadToServer;
674 }
675
676 /**
677 * Replies true if the data managed by this layer needs to be saved to
678 * a file. Only replies true if a file is assigned to this layer and
679 * if the data managed by this layer has been modified since the last
680 * save operation to the file.
681 *
682 * @return true if the data managed by this layer needs to be saved to
683 * a file
684 */
685 public boolean requiresSaveToFile() {
686 return getAssociatedFile() != null && requiresSaveToFile;
687 }
688
689 /**
690 * Initializes the layer after a successful load of OSM data from a file
691 *
692 */
693 public void onPostLoadFromFile() {
694 setRequiresSaveToFile(false);
695 setRequiresUploadToServer(data.isModified());
696 }
697
698 public void onPostDownloadFromServer() {
699 setRequiresSaveToFile(true);
700 setRequiresUploadToServer(data.isModified());
701 }
702
703 /**
704 * Initializes the layer after a successful save of OSM data to a file
705 *
706 */
707 public void onPostSaveToFile() {
708 setRequiresSaveToFile(false);
709 setRequiresUploadToServer(data.isModified());
710 }
711
712 /**
713 * Initializes the layer after a successful upload to the server
714 *
715 */
716 public void onPostUploadToServer() {
717 setRequiresUploadToServer(data.isModified());
718 // keep requiresSaveToDisk unchanged
719 }
720
721 private class ConsistencyTestAction extends AbstractAction {
722
723 public ConsistencyTestAction() {
724 super(tr("Dataset consistency test"));
725 }
726
727 public void actionPerformed(ActionEvent e) {
728 String result = DatasetConsistencyTest.runTests(data);
729 if (result.length() == 0) {
730 JOptionPane.showMessageDialog(Main.parent, tr("No problems found"));
731 } else {
732 JPanel p = new JPanel(new GridBagLayout());
733 p.add(new JLabel(tr("Following problems found:")), GBC.eol());
734 JTextArea info = new JTextArea(result, 20, 60);
735 info.setCaretPosition(0);
736 info.setEditable(false);
737 p.add(new JScrollPane(info), GBC.eop());
738
739 JOptionPane.showMessageDialog(Main.parent, p, tr("Warning"), JOptionPane.WARNING_MESSAGE);
740 }
741 }
742
743 }
744}
Note: See TracBrowser for help on using the repository browser.