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

Last change on this file since 276 was 276, checked in by imi, 17 years ago
  • fixed comments only (added some TODO in order to provide #20)
File size: 12.4 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 * TODO: This has to be moved to a central place in order to support multiple layers.
220 */
221 public void undo() {
222 if (commands.isEmpty())
223 return;
224 final Command c = commands.removeLast();
225 c.undoCommand();
226 redoCommands.push(c);
227 setModified(uploadedModified || !commands.isEmpty());
228 Main.ds.clearSelection();
229 fireCommandsChanged();
230 }
231 /**
232 * Redoes the last undoed command.
233 * TODO: This has to be moved to a central place in order to support multiple layers.
234 */
235 public void redo() {
236 if (redoCommands.isEmpty())
237 return;
238 final Command c = redoCommands.pop();
239 c.executeCommand();
240 commands.add(c);
241 setModified(true);
242 fireCommandsChanged();
243 }
244
245 /**
246 * Clean out the data behind the layer. This means clearing the redo/undo lists,
247 * really deleting all deleted objects and reset the modified flags. This is done
248 * after a successfull upload.
249 *
250 * @param processed A list of all objects, that were actually uploaded.
251 * May be <code>null</code>, which means nothing has been uploaded but
252 * saved to disk instead. Note that an empty collection for "processed"
253 * means that an upload has been attempted but failed.
254 */
255 public void cleanData(final Collection<OsmPrimitive> processed, boolean dataAdded) {
256
257 // return immediately if an upload attempt failed
258 if (processed != null && processed.isEmpty() && !dataAdded)
259 return;
260
261 redoCommands.clear();
262 commands.clear();
263
264 // if uploaded, clean the modified flags as well
265 if (processed != null) {
266 final Set<OsmPrimitive> processedSet = new HashSet<OsmPrimitive>(processed);
267 for (final Iterator<Node> it = data.nodes.iterator(); it.hasNext();)
268 cleanIterator(it, processedSet);
269 for (final Iterator<Segment> it = data.segments.iterator(); it.hasNext();)
270 cleanIterator(it, processedSet);
271 for (final Iterator<Way> it = data.ways.iterator(); it.hasNext();)
272 cleanIterator(it, processedSet);
273 }
274
275 // update the modified flag
276 if (fromDisk && processed != null && !dataAdded)
277 return; // do nothing when uploading non-harmful changes.
278
279 // modified if server changed the data (esp. the id).
280 uploadedModified = fromDisk && processed != null && dataAdded;
281 setModified(uploadedModified);
282 fireCommandsChanged();
283 }
284
285 public void fireCommandsChanged() {
286 for (final CommandQueueListener l : listenerCommands)
287 l.commandChanged(commands.size(), redoCommands.size());
288 }
289
290 /**
291 * Clean the modified flag for the given iterator over a collection if it is in the
292 * list of processed entries.
293 *
294 * @param it The iterator to change the modified and remove the items if deleted.
295 * @param processed A list of all objects that have been successfully progressed.
296 * If the object in the iterator is not in the list, nothing will be changed on it.
297 */
298 private void cleanIterator(final Iterator<? extends OsmPrimitive> it, final Collection<OsmPrimitive> processed) {
299 final OsmPrimitive osm = it.next();
300 if (!processed.remove(osm))
301 return;
302 osm.modified = false;
303 if (osm.deleted)
304 it.remove();
305 }
306
307 public boolean isModified() {
308 return modified;
309 }
310
311 public void setModified(final boolean modified) {
312 if (modified == this.modified)
313 return;
314 this.modified = modified;
315 for (final ModifiedChangedListener l : listenerModified)
316 l.modifiedChanged(modified, this);
317 }
318
319 /**
320 * @return The number of not-deleted primitives in the list.
321 */
322 private int undeletedSize(final Collection<? extends OsmPrimitive> list) {
323 int size = 0;
324 for (final OsmPrimitive osm : list)
325 if (!osm.deleted)
326 size++;
327 return size;
328 }
329
330 @Override public Object getInfoComponent() {
331 final DataCountVisitor counter = new DataCountVisitor();
332 for (final OsmPrimitive osm : data.allPrimitives())
333 osm.visit(counter);
334 final JPanel p = new JPanel(new GridBagLayout());
335 p.add(new JLabel(tr("{0} consists of:", name)), GBC.eol());
336 for (int i = 0; i < counter.normal.length; ++i) {
337 String s = counter.normal[i]+" "+trn(counter.names[i],counter.names[i]+"s",counter.normal[i]);
338 if (counter.deleted[i] > 0)
339 s += tr(" ({0} deleted.)",counter.deleted[i]);
340 p.add(new JLabel(s, ImageProvider.get("data", counter.names[i]), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
341 }
342 return p;
343 }
344
345 @Override public Component[] getMenuEntries() {
346 if (Main.applet) {
347 return new Component[]{
348 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
349 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
350 new JSeparator(),
351 new JMenuItem(new RenameLayerAction(associatedFile, this)),
352 new JSeparator(),
353 new JMenuItem(new LayerListPopup.InfoAction(this))};
354 }
355 return new Component[]{
356 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
357 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
358 new JSeparator(),
359 new JMenuItem(new SaveAction()),
360 new JMenuItem(new SaveAsAction()),
361 new JMenuItem(new GpxExportAction(this)),
362 new JSeparator(),
363 new JMenuItem(new RenameLayerAction(associatedFile, this)),
364 new JSeparator(),
365 new JMenuItem(new LayerListPopup.InfoAction(this))};
366 }
367
368
369 public void setMapPainter(SimplePaintVisitor mapPainter) {
370 this.mapPainter = mapPainter;
371 }
372}
Note: See TracBrowser for help on using the repository browser.