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

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

Change most occurrences of Dataset.nodes/ways/relations with getNodes()/../.. or addPrimitive

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