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

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

Fixed MergeVisitor so that it will never merge between objects that are
part of the group of objects being added - only between pre-existing
objects and those being added.

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