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

Last change on this file since 297 was 297, checked in by imi, 17 years ago
  • fixed bug that selection listener were informed twice on selection changes
File size: 12.7 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_small");
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 boolean inactive = Main.map.mapView.getActiveLayer() != this && Main.pref.getBoolean("draw.data.inactive_color", true);
147 if (Main.pref.getBoolean("draw.data.downloaded_area", false)) {
148 // FIXME this is inefficient; instead a proper polygon has to be built, and instead
149 // of drawing the outline, the outlying areas should perhaps be shaded.
150 for (DataSource src : data.dataSources) {
151 if (src.bounds != null) {
152 EastNorth en1 = Main.proj.latlon2eastNorth(src.bounds.min);
153 EastNorth en2 = Main.proj.latlon2eastNorth(src.bounds.max);
154 Point p1 = mv.getPoint(en1);
155 Point p2 = mv.getPoint(en2);
156 Color color = inactive ? SimplePaintVisitor.getPreferencesColor("inactive", Color.DARK_GRAY) :
157 SimplePaintVisitor.getPreferencesColor("downloaded Area", Color.YELLOW);
158 g.setColor(color);
159 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));
160 }
161 }
162 }
163 mapPainter.setGraphics(g);
164 mapPainter.setNavigatableComponent(mv);
165 mapPainter.inactive = inactive;
166 mapPainter.visitAll(data);
167 Main.map.conflictDialog.paintConflicts(g, mv);
168 }
169
170 @Override public String getToolTipText() {
171 String tool = "";
172 tool += undeletedSize(data.nodes)+" "+trn("node", "nodes", undeletedSize(data.nodes))+", ";
173 tool += undeletedSize(data.segments)+" "+trn("segment", "segments", undeletedSize(data.segments))+", ";
174 tool += undeletedSize(data.ways)+" "+trn("way", "ways", undeletedSize(data.ways));
175 if (associatedFile != null)
176 tool = "<html>"+tool+"<br>"+associatedFile.getPath()+"</html>";
177 return tool;
178 }
179
180 @Override public void mergeFrom(final Layer from) {
181 final MergeVisitor visitor = new MergeVisitor(data,((OsmDataLayer)from).data);
182 for (final OsmPrimitive osm : ((OsmDataLayer)from).data.allPrimitives())
183 osm.visit(visitor);
184 visitor.fixReferences();
185
186 // copy the merged layer's data source info
187 for (DataSource src : ((OsmDataLayer)from).data.dataSources)
188 data.dataSources.add(src);
189
190 if (visitor.conflicts.isEmpty())
191 return;
192 final ConflictDialog dlg = Main.map.conflictDialog;
193 dlg.add(visitor.conflicts);
194 JOptionPane.showMessageDialog(Main.parent,tr("There were conflicts during import."));
195 if (!dlg.isVisible())
196 dlg.action.actionPerformed(new ActionEvent(this, 0, ""));
197 }
198
199 @Override public boolean isMergable(final Layer other) {
200 return other instanceof OsmDataLayer;
201 }
202
203 @Override public void visitBoundingBox(final BoundingXYVisitor v) {
204 for (final Node n : data.nodes)
205 if (!n.deleted)
206 v.visit(n);
207 }
208
209 /**
210 * Execute the command and add it to the intern command queue. Also mark all
211 * primitives in the command as modified.
212 */
213 public void add(final Command c) {
214 c.executeCommand();
215 commands.add(c);
216 redoCommands.clear();
217 setModified(true);
218 fireCommandsChanged();
219 }
220
221 /**
222 * Undoes the last added command.
223 * TODO: This has to be moved to a central place in order to support multiple layers.
224 */
225 public void undo() {
226 if (commands.isEmpty())
227 return;
228 final Command c = commands.removeLast();
229 c.undoCommand();
230 redoCommands.push(c);
231 setModified(uploadedModified || !commands.isEmpty());
232 Main.ds.setSelected();
233 fireCommandsChanged();
234 }
235 /**
236 * Redoes the last undoed command.
237 * TODO: This has to be moved to a central place in order to support multiple layers.
238 */
239 public void redo() {
240 if (redoCommands.isEmpty())
241 return;
242 final Command c = redoCommands.pop();
243 c.executeCommand();
244 commands.add(c);
245 setModified(true);
246 fireCommandsChanged();
247 }
248
249 /**
250 * Clean out the data behind the layer. This means clearing the redo/undo lists,
251 * really deleting all deleted objects and reset the modified flags. This is done
252 * after a successfull upload.
253 *
254 * @param processed A list of all objects, that were actually uploaded.
255 * May be <code>null</code>, which means nothing has been uploaded but
256 * saved to disk instead. Note that an empty collection for "processed"
257 * means that an upload has been attempted but failed.
258 */
259 public void cleanData(final Collection<OsmPrimitive> processed, boolean dataAdded) {
260
261 // return immediately if an upload attempt failed
262 if (processed != null && processed.isEmpty() && !dataAdded)
263 return;
264
265 redoCommands.clear();
266 commands.clear();
267
268 // if uploaded, clean the modified flags as well
269 if (processed != null) {
270 final Set<OsmPrimitive> processedSet = new HashSet<OsmPrimitive>(processed);
271 for (final Iterator<Node> it = data.nodes.iterator(); it.hasNext();)
272 cleanIterator(it, processedSet);
273 for (final Iterator<Segment> it = data.segments.iterator(); it.hasNext();)
274 cleanIterator(it, processedSet);
275 for (final Iterator<Way> it = data.ways.iterator(); it.hasNext();)
276 cleanIterator(it, processedSet);
277 }
278
279 // update the modified flag
280 if (fromDisk && processed != null && !dataAdded)
281 return; // do nothing when uploading non-harmful changes.
282
283 // modified if server changed the data (esp. the id).
284 uploadedModified = fromDisk && processed != null && dataAdded;
285 setModified(uploadedModified);
286 fireCommandsChanged();
287 }
288
289 public void fireCommandsChanged() {
290 for (final CommandQueueListener l : listenerCommands)
291 l.commandChanged(commands.size(), redoCommands.size());
292 }
293
294 /**
295 * Clean the modified flag for the given iterator over a collection if it is in the
296 * list of processed entries.
297 *
298 * @param it The iterator to change the modified and remove the items if deleted.
299 * @param processed A list of all objects that have been successfully progressed.
300 * If the object in the iterator is not in the list, nothing will be changed on it.
301 */
302 private void cleanIterator(final Iterator<? extends OsmPrimitive> it, final Collection<OsmPrimitive> processed) {
303 final OsmPrimitive osm = it.next();
304 if (!processed.remove(osm))
305 return;
306 osm.modified = false;
307 if (osm.deleted)
308 it.remove();
309 }
310
311 public boolean isModified() {
312 return modified;
313 }
314
315 public void setModified(final boolean modified) {
316 if (modified == this.modified)
317 return;
318 this.modified = modified;
319 for (final ModifiedChangedListener l : listenerModified)
320 l.modifiedChanged(modified, this);
321 }
322
323 /**
324 * @return The number of not-deleted primitives in the list.
325 */
326 private int undeletedSize(final Collection<? extends OsmPrimitive> list) {
327 int size = 0;
328 for (final OsmPrimitive osm : list)
329 if (!osm.deleted)
330 size++;
331 return size;
332 }
333
334 @Override public Object getInfoComponent() {
335 final DataCountVisitor counter = new DataCountVisitor();
336 for (final OsmPrimitive osm : data.allPrimitives())
337 osm.visit(counter);
338 final JPanel p = new JPanel(new GridBagLayout());
339 p.add(new JLabel(tr("{0} consists of:", name)), GBC.eol());
340 for (int i = 0; i < counter.normal.length; ++i) {
341 String s = counter.normal[i]+" "+trn(counter.names[i],counter.names[i]+"s",counter.normal[i]);
342 if (counter.deleted[i] > 0)
343 s += tr(" ({0} deleted.)",counter.deleted[i]);
344 p.add(new JLabel(s, ImageProvider.get("data", counter.names[i]), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
345 }
346 return p;
347 }
348
349 @Override public Component[] getMenuEntries() {
350 if (Main.applet) {
351 return new Component[]{
352 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
353 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
354 new JSeparator(),
355 new JMenuItem(new RenameLayerAction(associatedFile, this)),
356 new JSeparator(),
357 new JMenuItem(new LayerListPopup.InfoAction(this))};
358 }
359 return new Component[]{
360 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
361 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
362 new JSeparator(),
363 new JMenuItem(new SaveAction(this)),
364 new JMenuItem(new SaveAsAction(this)),
365 new JMenuItem(new GpxExportAction(this)),
366 new JSeparator(),
367 new JMenuItem(new RenameLayerAction(associatedFile, this)),
368 new JSeparator(),
369 new JMenuItem(new LayerListPopup.InfoAction(this))};
370 }
371
372
373 public void setMapPainter(SimplePaintVisitor mapPainter) {
374 this.mapPainter = mapPainter;
375 }
376}
Note: See TracBrowser for help on using the repository browser.