source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDialog.java@ 9278

Last change on this file since 9278 was 9278, checked in by bastiK, 8 years ago

move ElemStyle classes to new package, rename to (Style)Element

  • Property svn:eol-style set to native
File size: 16.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Dimension;
8import java.awt.GridBagLayout;
9import java.text.Collator;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.List;
14import java.util.Locale;
15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.TreeMap;
18
19import javax.swing.JPanel;
20import javax.swing.JScrollPane;
21import javax.swing.JTabbedPane;
22import javax.swing.SingleSelectionModel;
23import javax.swing.event.ChangeEvent;
24import javax.swing.event.ChangeListener;
25
26import org.openstreetmap.josm.Main;
27import org.openstreetmap.josm.data.conflict.Conflict;
28import org.openstreetmap.josm.data.coor.EastNorth;
29import org.openstreetmap.josm.data.osm.BBox;
30import org.openstreetmap.josm.data.osm.Node;
31import org.openstreetmap.josm.data.osm.OsmPrimitive;
32import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator;
33import org.openstreetmap.josm.data.osm.Relation;
34import org.openstreetmap.josm.data.osm.RelationMember;
35import org.openstreetmap.josm.data.osm.Way;
36import org.openstreetmap.josm.gui.DefaultNameFormatter;
37import org.openstreetmap.josm.gui.ExtendedDialog;
38import org.openstreetmap.josm.gui.NavigatableComponent;
39import org.openstreetmap.josm.gui.layer.OsmDataLayer;
40import org.openstreetmap.josm.gui.mappaint.Cascade;
41import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
42import org.openstreetmap.josm.gui.mappaint.ElemStyles;
43import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
44import org.openstreetmap.josm.gui.mappaint.MultiCascade;
45import org.openstreetmap.josm.gui.mappaint.StyleCache;
46import org.openstreetmap.josm.gui.mappaint.StyleCache.StyleList;
47import org.openstreetmap.josm.gui.mappaint.StyleSource;
48import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
49import org.openstreetmap.josm.gui.mappaint.xml.XmlStyleSource;
50import org.openstreetmap.josm.gui.util.GuiHelper;
51import org.openstreetmap.josm.gui.widgets.JosmTextArea;
52import org.openstreetmap.josm.tools.GBC;
53import org.openstreetmap.josm.tools.Geometry;
54import org.openstreetmap.josm.tools.WindowGeometry;
55import org.openstreetmap.josm.tools.date.DateUtils;
56
57/**
58 * Panel to inspect one or more OsmPrimitives.
59 *
60 * Gives an unfiltered view of the object's internal state.
61 * Might be useful for power users to give more detailed bug reports and
62 * to better understand the JOSM data representation.
63 */
64public class InspectPrimitiveDialog extends ExtendedDialog {
65
66 protected transient List<OsmPrimitive> primitives;
67 protected transient OsmDataLayer layer;
68 private boolean mappaintTabLoaded;
69 private boolean editcountTabLoaded;
70
71 public InspectPrimitiveDialog(Collection<OsmPrimitive> primitives, OsmDataLayer layer) {
72 super(Main.parent, tr("Advanced object info"), new String[] {tr("Close")});
73 this.primitives = new ArrayList<>(primitives);
74 this.layer = layer;
75 setRememberWindowGeometry(getClass().getName() + ".geometry",
76 WindowGeometry.centerInWindow(Main.parent, new Dimension(750, 550)));
77
78 setButtonIcons(new String[]{"ok.png"});
79 final JTabbedPane tabs = new JTabbedPane();
80
81 tabs.addTab(tr("data"), genericMonospacePanel(new JPanel(), buildDataText()));
82
83 final JPanel pMapPaint = new JPanel();
84 tabs.addTab(tr("map style"), pMapPaint);
85 tabs.getModel().addChangeListener(new ChangeListener() {
86
87 @Override
88 public void stateChanged(ChangeEvent e) {
89 if (!mappaintTabLoaded && ((SingleSelectionModel) e.getSource()).getSelectedIndex() == 1) {
90 mappaintTabLoaded = true;
91 genericMonospacePanel(pMapPaint, buildMapPaintText());
92 }
93 }
94 });
95
96 final JPanel pEditCounts = new JPanel();
97 tabs.addTab(tr("edit counts"), pEditCounts);
98 tabs.getModel().addChangeListener(new ChangeListener() {
99
100 @Override
101 public void stateChanged(ChangeEvent e) {
102 if (!editcountTabLoaded && ((SingleSelectionModel) e.getSource()).getSelectedIndex() == 2) {
103 editcountTabLoaded = true;
104 genericMonospacePanel(pEditCounts, buildListOfEditorsText());
105 }
106 }
107 });
108
109 setContent(tabs, false);
110 }
111
112 protected JPanel genericMonospacePanel(JPanel p, String s) {
113 p.setLayout(new GridBagLayout());
114 JosmTextArea jte = new JosmTextArea();
115 jte.setFont(GuiHelper.getMonospacedFont(jte));
116 jte.setEditable(false);
117 jte.append(s);
118 p.add(new JScrollPane(jte), GBC.std().fill());
119 return p;
120 }
121
122 protected String buildDataText() {
123 DataText dt = new DataText();
124 Collections.sort(primitives, new OsmPrimitiveComparator());
125 for (OsmPrimitive o : primitives) {
126 dt.addPrimitive(o);
127 }
128 return dt.toString();
129 }
130
131 class DataText {
132 private static final String INDENT = " ";
133 private static final char NL = '\n';
134
135 private final StringBuilder s = new StringBuilder();
136
137 private DataText add(String title, String... values) {
138 s.append(INDENT).append(title);
139 for (String v : values) {
140 s.append(v);
141 }
142 s.append(NL);
143 return this;
144 }
145
146 private String getNameAndId(String name, long id) {
147 if (name != null) {
148 return name + tr(" ({0})", /* sic to avoid thousand seperators */ Long.toString(id));
149 } else {
150 return Long.toString(id);
151 }
152 }
153
154 public void addPrimitive(OsmPrimitive o) {
155
156 addHeadline(o);
157
158 if (!(o.getDataSet() != null && o.getDataSet().getPrimitiveById(o) != null)) {
159 s.append(NL).append(INDENT).append(tr("not in data set")).append(NL);
160 return;
161 }
162 if (o.isIncomplete()) {
163 s.append(NL).append(INDENT).append(tr("incomplete")).append(NL);
164 return;
165 }
166 s.append(NL);
167
168 addState(o);
169 addCommon(o);
170 addAttributes(o);
171 addSpecial(o);
172 addReferrers(s, o);
173 addConflicts(o);
174 s.append(NL);
175 }
176
177 void addHeadline(OsmPrimitive o) {
178 addType(o);
179 addNameAndId(o);
180 }
181
182 void addType(OsmPrimitive o) {
183 if (o instanceof Node) {
184 s.append(tr("Node: "));
185 } else if (o instanceof Way) {
186 s.append(tr("Way: "));
187 } else if (o instanceof Relation) {
188 s.append(tr("Relation: "));
189 }
190 }
191
192 void addNameAndId(OsmPrimitive o) {
193 String name = o.get("name");
194 if (name == null) {
195 s.append(o.getUniqueId());
196 } else {
197 s.append(getNameAndId(name, o.getUniqueId()));
198 }
199 }
200
201 void addState(OsmPrimitive o) {
202 StringBuilder sb = new StringBuilder(INDENT);
203 /* selected state is left out: not interesting as it is always selected */
204 if (o.isDeleted()) {
205 sb.append(tr("deleted")).append(INDENT);
206 }
207 if (!o.isVisible()) {
208 sb.append(tr("deleted-on-server")).append(INDENT);
209 }
210 if (o.isModified()) {
211 sb.append(tr("modified")).append(INDENT);
212 }
213 if (o.isDisabledAndHidden()) {
214 sb.append(tr("filtered/hidden")).append(INDENT);
215 }
216 if (o.isDisabled()) {
217 sb.append(tr("filtered/disabled")).append(INDENT);
218 }
219 if (o.hasDirectionKeys()) {
220 if (o.reversedDirection()) {
221 sb.append(tr("has direction keys (reversed)")).append(INDENT);
222 } else {
223 sb.append(tr("has direction keys")).append(INDENT);
224 }
225 }
226 String state = sb.toString().trim();
227 if (!state.isEmpty()) {
228 add(tr("State: "), sb.toString().trim());
229 }
230 }
231
232 void addCommon(OsmPrimitive o) {
233 add(tr("Data Set: "), Integer.toHexString(o.getDataSet().hashCode()));
234 add(tr("Edited at: "), o.isTimestampEmpty() ? tr("<new object>")
235 : DateUtils.fromTimestamp(o.getRawTimestamp()));
236 add(tr("Edited by: "), o.getUser() == null ? tr("<new object>")
237 : getNameAndId(o.getUser().getName(), o.getUser().getId()));
238 add(tr("Version: "), Integer.toString(o.getVersion()));
239 add(tr("In changeset: "), Integer.toString(o.getChangesetId()));
240 }
241
242 void addAttributes(OsmPrimitive o) {
243 if (o.hasKeys()) {
244 add(tr("Tags: "));
245 for (String key : o.keySet()) {
246 s.append(INDENT).append(INDENT);
247 s.append(String.format("\"%s\"=\"%s\"%n", key, o.get(key)));
248 }
249 }
250 }
251
252 void addSpecial(OsmPrimitive o) {
253 if (o instanceof Node) {
254 addCoordinates((Node) o);
255 } else if (o instanceof Way) {
256 addBbox(o);
257 add(tr("Centroid: "), Main.getProjection().eastNorth2latlon(
258 Geometry.getCentroid(((Way) o).getNodes())).toStringCSV(", "));
259 addWayNodes((Way) o);
260 } else if (o instanceof Relation) {
261 addBbox(o);
262 addRelationMembers((Relation) o);
263 }
264 }
265
266 void addRelationMembers(Relation r) {
267 add(trn("{0} Member: ", "{0} Members: ", r.getMembersCount(), r.getMembersCount()));
268 for (RelationMember m : r.getMembers()) {
269 s.append(INDENT).append(INDENT);
270 addHeadline(m.getMember());
271 s.append(tr(" as \"{0}\"", m.getRole()));
272 s.append(NL);
273 }
274 }
275
276 void addWayNodes(Way w) {
277 add(tr("{0} Nodes: ", w.getNodesCount()));
278 for (Node n : w.getNodes()) {
279 s.append(INDENT).append(INDENT);
280 addNameAndId(n);
281 s.append(NL);
282 }
283 }
284
285 void addBbox(OsmPrimitive o) {
286 BBox bbox = o.getBBox();
287 if (bbox != null) {
288 add(tr("Bounding box: "), bbox.toStringCSV(", "));
289 EastNorth bottomRigth = Main.getProjection().latlon2eastNorth(bbox.getBottomRight());
290 EastNorth topLeft = Main.getProjection().latlon2eastNorth(bbox.getTopLeft());
291 add(tr("Bounding box (projected): "),
292 Double.toString(topLeft.east()), ", ",
293 Double.toString(bottomRigth.north()), ", ",
294 Double.toString(bottomRigth.east()), ", ",
295 Double.toString(topLeft.north()));
296 add(tr("Center of bounding box: "), bbox.getCenter().toStringCSV(", "));
297 }
298 }
299
300 void addCoordinates(Node n) {
301 if (n.getCoor() != null) {
302 add(tr("Coordinates: "),
303 Double.toString(n.getCoor().lat()), ", ",
304 Double.toString(n.getCoor().lon()));
305 add(tr("Coordinates (projected): "),
306 Double.toString(n.getEastNorth().east()), ", ",
307 Double.toString(n.getEastNorth().north()));
308 }
309 }
310
311 void addReferrers(StringBuilder s, OsmPrimitive o) {
312 List<OsmPrimitive> refs = o.getReferrers();
313 if (!refs.isEmpty()) {
314 add(tr("Part of: "));
315 for (OsmPrimitive p : refs) {
316 s.append(INDENT).append(INDENT);
317 addHeadline(p);
318 s.append(NL);
319 }
320 }
321 }
322
323 void addConflicts(OsmPrimitive o) {
324 Conflict<?> c = layer.getConflicts().getConflictForMy(o);
325 if (c != null) {
326 add(tr("In conflict with: "));
327 addNameAndId(c.getTheir());
328 }
329 }
330
331 @Override
332 public String toString() {
333 return s.toString();
334 }
335 }
336
337 protected String buildMapPaintText() {
338 final Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getAllSelected();
339 ElemStyles elemstyles = MapPaintStyles.getStyles();
340 NavigatableComponent nc = Main.map.mapView;
341 double scale = nc.getDist100Pixel();
342
343 final StringBuilder txtMappaint = new StringBuilder();
344 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock();
345 try {
346 for (OsmPrimitive osm : sel) {
347 txtMappaint.append(tr("Styles Cache for \"{0}\":", osm.getDisplayName(DefaultNameFormatter.getInstance())));
348
349 MultiCascade mc = new MultiCascade();
350
351 for (StyleSource s : elemstyles.getStyleSources()) {
352 if (s.active) {
353 txtMappaint.append(tr("\n\n> applying {0} style \"{1}\"\n", getSort(s), s.getDisplayString()));
354 s.apply(mc, osm, scale, false);
355 txtMappaint.append(tr("\nRange:{0}", mc.range));
356 for (Entry<String, Cascade> e : mc.getLayers()) {
357 txtMappaint.append("\n ").append(e.getKey()).append(": \n").append(e.getValue());
358 }
359 } else {
360 txtMappaint.append(tr("\n\n> skipping \"{0}\" (not active)", s.getDisplayString()));
361 }
362 }
363 txtMappaint.append(tr("\n\nList of generated Styles:\n"));
364 StyleList sl = elemstyles.get(osm, scale, nc);
365 for (StyleElement s : sl) {
366 txtMappaint.append(" * ").append(s).append('\n');
367 }
368 txtMappaint.append("\n\n");
369 }
370 } finally {
371 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock();
372 }
373 if (sel.size() == 2) {
374 List<OsmPrimitive> selList = new ArrayList<>(sel);
375 StyleCache sc1 = selList.get(0).mappaintStyle;
376 StyleCache sc2 = selList.get(1).mappaintStyle;
377 if (sc1 == sc2) {
378 txtMappaint.append(tr("The 2 selected objects have identical style caches."));
379 }
380 if (!sc1.equals(sc2)) {
381 txtMappaint.append(tr("The 2 selected objects have different style caches."));
382 }
383 if (sc1.equals(sc2) && sc1 != sc2) {
384 txtMappaint.append(tr("Warning: The 2 selected objects have equal, but not identical style caches."));
385 }
386 }
387 return txtMappaint.toString();
388 }
389
390 /* Future Ideas:
391 Calculate the most recent edit date from o.getTimestamp().
392 Sort by the count for presentation, so the most active editors are on top.
393 Count only tagged nodes (so empty way nodes don't inflate counts).
394 */
395 protected String buildListOfEditorsText() {
396 final StringBuilder s = new StringBuilder();
397 final Map<String, Integer> editCountByUser = new TreeMap<>(Collator.getInstance(Locale.getDefault()));
398
399 // Count who edited each selected object
400 for (OsmPrimitive o : primitives) {
401 if (o.getUser() != null) {
402 String username = o.getUser().getName();
403 Integer oldCount = editCountByUser.get(username);
404 if (oldCount == null) {
405 editCountByUser.put(username, 1);
406 } else {
407 editCountByUser.put(username, oldCount + 1);
408 }
409 }
410 }
411
412 // Print the count in sorted order
413 s.append(trn("{0} user last edited the selection:", "{0} users last edited the selection:",
414 editCountByUser.size(), editCountByUser.size()));
415 s.append("\n\n");
416 for (Map.Entry<String, Integer> entry : editCountByUser.entrySet()) {
417 final String username = entry.getKey();
418 final Integer editCount = entry.getValue();
419 s.append(String.format("%6d %s%n", editCount, username));
420 }
421 return s.toString();
422 }
423
424 private static String getSort(StyleSource s) {
425 if (s instanceof XmlStyleSource) {
426 return tr("xml");
427 } else if (s instanceof MapCSSStyleSource) {
428 return tr("mapcss");
429 } else {
430 return tr("unknown");
431 }
432 }
433}
Note: See TracBrowser for help on using the repository browser.