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

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

Encalupse OsmPrimitive.incomplete

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