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

Last change on this file since 247 was 247, checked in by framm, 17 years ago

Display a yellow rectangle around the original download bounding box(es). Fixes (well, almost) #149.
Still todo: merge rectangles into nice polygon; possibly shade outlying area and optionally forbid changes to items in that area.

File size: 12.3 KB
Line 
1package org.openstreetmap.josm.gui.layer;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4import static org.openstreetmap.josm.tools.I18n.trn;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Graphics;
9import java.awt.GridBagLayout;
10import java.awt.Point;
11import java.awt.event.ActionEvent;
12import java.io.File;
13import java.util.Collection;
14import java.util.HashSet;
15import java.util.Iterator;
16import java.util.LinkedList;
17import java.util.Set;
18import java.util.Stack;
19
20import javax.swing.Icon;
21import javax.swing.JLabel;
22import javax.swing.JMenuItem;
23import javax.swing.JOptionPane;
24import javax.swing.JPanel;
25import javax.swing.JSeparator;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.actions.GpxExportAction;
29import org.openstreetmap.josm.actions.RenameLayerAction;
30import org.openstreetmap.josm.actions.SaveAction;
31import org.openstreetmap.josm.actions.SaveAsAction;
32import org.openstreetmap.josm.command.Command;
33import org.openstreetmap.josm.data.coor.EastNorth;
34import org.openstreetmap.josm.data.osm.DataSet;
35import org.openstreetmap.josm.data.osm.DataSource;
36import org.openstreetmap.josm.data.osm.Node;
37import org.openstreetmap.josm.data.osm.OsmPrimitive;
38import org.openstreetmap.josm.data.osm.Segment;
39import org.openstreetmap.josm.data.osm.Way;
40import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
41import org.openstreetmap.josm.data.osm.visitor.MergeVisitor;
42import org.openstreetmap.josm.data.osm.visitor.SimplePaintVisitor;
43import org.openstreetmap.josm.data.osm.visitor.Visitor;
44import org.openstreetmap.josm.gui.MapView;
45import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
46import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
47import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
48import org.openstreetmap.josm.tools.GBC;
49import org.openstreetmap.josm.tools.ImageProvider;
50
51/**
52 * A layer holding data from a specific dataset.
53 * The data can be fully edited.
54 *
55 * @author imi
56 */
57public class OsmDataLayer extends Layer {
58
59 public final static class DataCountVisitor implements Visitor {
60 public final int[] normal = new int[3];
61 public final int[] deleted = new int[3];
62 public final String[] names = {"node", "segment", "way"};
63
64 private void inc(final OsmPrimitive osm, final int i) {
65 normal[i]++;
66 if (osm.deleted)
67 deleted[i]++;
68 }
69
70 public void visit(final Node n) {
71 inc(n, 0);
72 }
73
74 public void visit(final Segment ls) {
75 inc(ls, 1);
76 }
77
78 public void visit(final Way w) {
79 inc(w, 2);
80 }
81 }
82
83 public interface ModifiedChangedListener {
84 void modifiedChanged(boolean value, OsmDataLayer source);
85 }
86 public interface CommandQueueListener {
87 void commandChanged(int queueSize, int redoSize);
88 }
89
90 /**
91 * The data behind this layer.
92 */
93 public final DataSet data;
94
95 /**
96 * Whether the data of this layer was modified during the session.
97 */
98 private boolean modified = false;
99 /**
100 * Whether the data was modified due an upload of the data to the server.
101 */
102 public boolean uploadedModified = false;
103 /**
104 * Whether the data (or pieces of the data) was loaded from disk rather than from
105 * the server directly. This affects the modified state.
106 */
107 private boolean fromDisk = false;
108 /**
109 * All commands that were made on the dataset. Don't write from outside!
110 */
111 public final LinkedList<Command> commands = new LinkedList<Command>();
112 /**
113 * The stack for redoing commands
114 */
115 private final Stack<Command> redoCommands = new Stack<Command>();
116
117 public final LinkedList<ModifiedChangedListener> listenerModified = new LinkedList<ModifiedChangedListener>();
118 public final LinkedList<CommandQueueListener> listenerCommands = new LinkedList<CommandQueueListener>();
119
120 private SimplePaintVisitor mapPainter = new SimplePaintVisitor();
121
122 /**
123 * Construct a OsmDataLayer.
124 */
125 public OsmDataLayer(final DataSet data, final String name, final File associatedFile) {
126 super(name);
127 this.data = data;
128 this.fromDisk = associatedFile != null;
129 this.associatedFile = associatedFile;
130 }
131
132 /**
133 * TODO: @return Return a dynamic drawn icon of the map data. The icon is
134 * updated by a background thread to not disturb the running programm.
135 */
136 @Override public Icon getIcon() {
137 return ImageProvider.get("layer", "osmdata");
138 }
139
140 /**
141 * Draw all primitives in this layer but do not draw modified ones (they
142 * are drawn by the edit layer).
143 * Draw nodes last to overlap the segments they belong to.
144 */
145 @Override public void paint(final Graphics g, final MapView mv) {
146 if (Main.pref.getBoolean("draw.data.downloaded_area", false)) {
147 // FIXME this is inefficient; instead a proper polygon has to be built, and instead
148 // of drawing the outline, the outlying areas should perhaps be shaded.
149 for (DataSource src : data.dataSources) {
150 if (src.sourceBounds != null) {
151 EastNorth en1 = Main.proj.latlon2eastNorth(src.sourceBounds.min);
152 EastNorth en2 = Main.proj.latlon2eastNorth(src.sourceBounds.max);
153 Point p1 = mv.getPoint(en1);
154 Point p2 = mv.getPoint(en2);
155 g.setColor(SimplePaintVisitor.getPreferencesColor("downloaded Area", Color.YELLOW));
156 g.drawRect(Math.min(p1.x,p2.x), Math.min(p1.y, p2.y), Math.abs(p2.x-p1.x), Math.abs(p2.y-p1.y));
157 }
158 }
159 }
160 mapPainter.setGraphics(g);
161 mapPainter.setNavigatableComponent(mv);
162 mapPainter.visitAll(data);
163 Main.map.conflictDialog.paintConflicts(g, mv);
164 }
165
166 @Override public String getToolTipText() {
167 String tool = "";
168 tool += undeletedSize(data.nodes)+" "+trn("node", "nodes", undeletedSize(data.nodes))+", ";
169 tool += undeletedSize(data.segments)+" "+trn("segment", "segments", undeletedSize(data.segments))+", ";
170 tool += undeletedSize(data.ways)+" "+trn("way", "ways", undeletedSize(data.ways));
171 if (associatedFile != null)
172 tool = "<html>"+tool+"<br>"+associatedFile.getPath()+"</html>";
173 return tool;
174 }
175
176 @Override public void mergeFrom(final Layer from) {
177 final MergeVisitor visitor = new MergeVisitor(data,((OsmDataLayer)from).data);
178 for (final OsmPrimitive osm : ((OsmDataLayer)from).data.allPrimitives())
179 osm.visit(visitor);
180 visitor.fixReferences();
181
182 // copy the merged layer's data source info
183 for (DataSource src : ((OsmDataLayer)from).data.dataSources)
184 data.dataSources.add(src);
185
186 if (visitor.conflicts.isEmpty())
187 return;
188 final ConflictDialog dlg = Main.map.conflictDialog;
189 dlg.add(visitor.conflicts);
190 JOptionPane.showMessageDialog(Main.parent,tr("There were conflicts during import."));
191 if (!dlg.isVisible())
192 dlg.action.actionPerformed(new ActionEvent(this, 0, ""));
193 }
194
195 @Override public boolean isMergable(final Layer other) {
196 return other instanceof OsmDataLayer;
197 }
198
199 @Override public void visitBoundingBox(final BoundingXYVisitor v) {
200 for (final Node n : data.nodes)
201 if (!n.deleted)
202 v.visit(n);
203 }
204
205 /**
206 * Execute the command and add it to the intern command queue. Also mark all
207 * primitives in the command as modified.
208 */
209 public void add(final Command c) {
210 c.executeCommand();
211 commands.add(c);
212 redoCommands.clear();
213 setModified(true);
214 fireCommandsChanged();
215 }
216
217 /**
218 * Undoes the last added command.
219 */
220 public void undo() {
221 if (commands.isEmpty())
222 return;
223 final Command c = commands.removeLast();
224 c.undoCommand();
225 redoCommands.push(c);
226 setModified(uploadedModified || !commands.isEmpty());
227 Main.ds.clearSelection();
228 fireCommandsChanged();
229 }
230 /**
231 * Redoes the last undoed command.
232 */
233 public void redo() {
234 if (redoCommands.isEmpty())
235 return;
236 final Command c = redoCommands.pop();
237 c.executeCommand();
238 commands.add(c);
239 setModified(true);
240 fireCommandsChanged();
241 }
242
243 /**
244 * Clean out the data behind the layer. This means clearing the redo/undo lists,
245 * really deleting all deleted objects and reset the modified flags. This is done
246 * after a successfull upload.
247 *
248 * @param processed A list of all objects, that were actually uploaded.
249 * May be <code>null</code>, which means nothing has been uploaded but
250 * saved to disk instead. Note that an empty collection for "processed"
251 * means that an upload has been attempted but failed.
252 */
253 public void cleanData(final Collection<OsmPrimitive> processed, boolean dataAdded) {
254
255 // return immediately if an upload attempt failed
256 if (processed != null && processed.isEmpty() && !dataAdded)
257 return;
258
259 redoCommands.clear();
260 commands.clear();
261
262 // if uploaded, clean the modified flags as well
263 if (processed != null) {
264 final Set<OsmPrimitive> processedSet = new HashSet<OsmPrimitive>(processed);
265 for (final Iterator<Node> it = data.nodes.iterator(); it.hasNext();)
266 cleanIterator(it, processedSet);
267 for (final Iterator<Segment> it = data.segments.iterator(); it.hasNext();)
268 cleanIterator(it, processedSet);
269 for (final Iterator<Way> it = data.ways.iterator(); it.hasNext();)
270 cleanIterator(it, processedSet);
271 }
272
273 // update the modified flag
274 if (fromDisk && processed != null && !dataAdded)
275 return; // do nothing when uploading non-harmful changes.
276
277 // modified if server changed the data (esp. the id).
278 uploadedModified = fromDisk && processed != null && dataAdded;
279 setModified(uploadedModified);
280 fireCommandsChanged();
281 }
282
283 public void fireCommandsChanged() {
284 for (final CommandQueueListener l : listenerCommands)
285 l.commandChanged(commands.size(), redoCommands.size());
286 }
287
288 /**
289 * Clean the modified flag for the given iterator over a collection if it is in the
290 * list of processed entries.
291 *
292 * @param it The iterator to change the modified and remove the items if deleted.
293 * @param processed A list of all objects that have been successfully progressed.
294 * If the object in the iterator is not in the list, nothing will be changed on it.
295 */
296 private void cleanIterator(final Iterator<? extends OsmPrimitive> it, final Collection<OsmPrimitive> processed) {
297 final OsmPrimitive osm = it.next();
298 if (!processed.remove(osm))
299 return;
300 osm.modified = false;
301 if (osm.deleted)
302 it.remove();
303 }
304
305 public boolean isModified() {
306 return modified;
307 }
308
309 public void setModified(final boolean modified) {
310 if (modified == this.modified)
311 return;
312 this.modified = modified;
313 for (final ModifiedChangedListener l : listenerModified)
314 l.modifiedChanged(modified, this);
315 }
316
317 /**
318 * @return The number of not-deleted primitives in the list.
319 */
320 private int undeletedSize(final Collection<? extends OsmPrimitive> list) {
321 int size = 0;
322 for (final OsmPrimitive osm : list)
323 if (!osm.deleted)
324 size++;
325 return size;
326 }
327
328 @Override public Object getInfoComponent() {
329 final DataCountVisitor counter = new DataCountVisitor();
330 for (final OsmPrimitive osm : data.allPrimitives())
331 osm.visit(counter);
332 final JPanel p = new JPanel(new GridBagLayout());
333 p.add(new JLabel(tr("{0} consists of:", name)), GBC.eol());
334 for (int i = 0; i < counter.normal.length; ++i) {
335 String s = counter.normal[i]+" "+trn(counter.names[i],counter.names[i]+"s",counter.normal[i]);
336 if (counter.deleted[i] > 0)
337 s += tr(" ({0} deleted.)",counter.deleted[i]);
338 p.add(new JLabel(s, ImageProvider.get("data", counter.names[i]), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
339 }
340 return p;
341 }
342
343 @Override public Component[] getMenuEntries() {
344 if (Main.applet) {
345 return new Component[]{
346 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
347 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
348 new JSeparator(),
349 new JMenuItem(new RenameLayerAction(associatedFile, this)),
350 new JSeparator(),
351 new JMenuItem(new LayerListPopup.InfoAction(this))};
352 }
353 return new Component[]{
354 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
355 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
356 new JSeparator(),
357 new JMenuItem(new SaveAction()),
358 new JMenuItem(new SaveAsAction()),
359 new JMenuItem(new GpxExportAction(this)),
360 new JSeparator(),
361 new JMenuItem(new RenameLayerAction(associatedFile, this)),
362 new JSeparator(),
363 new JMenuItem(new LayerListPopup.InfoAction(this))};
364 }
365
366
367 public void setMapPainter(SimplePaintVisitor mapPainter) {
368 this.mapPainter = mapPainter;
369 }
370}
Note: See TracBrowser for help on using the repository browser.