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

Last change on this file since 2253 was 2253, checked in by Gubaer, 15 years ago

Improved dialogs in the context of conflict resolution.
Improved support for online help in the context of conflict resolution, see also info about conflicts

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