001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.awt.Dimension; 008import java.awt.Font; 009import java.awt.GridBagLayout; 010import java.util.ArrayList; 011import java.util.Collection; 012import java.util.Collections; 013import java.util.List; 014import java.util.Map.Entry; 015 016import javax.swing.JPanel; 017import javax.swing.JScrollPane; 018import javax.swing.JTabbedPane; 019import javax.swing.SingleSelectionModel; 020import javax.swing.event.ChangeEvent; 021import javax.swing.event.ChangeListener; 022 023import org.openstreetmap.josm.Main; 024import org.openstreetmap.josm.data.conflict.Conflict; 025import org.openstreetmap.josm.data.coor.EastNorth; 026import org.openstreetmap.josm.data.osm.BBox; 027import org.openstreetmap.josm.data.osm.Node; 028import org.openstreetmap.josm.data.osm.OsmPrimitive; 029import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator; 030import org.openstreetmap.josm.data.osm.Relation; 031import org.openstreetmap.josm.data.osm.RelationMember; 032import org.openstreetmap.josm.data.osm.Way; 033import org.openstreetmap.josm.gui.DefaultNameFormatter; 034import org.openstreetmap.josm.gui.ExtendedDialog; 035import org.openstreetmap.josm.gui.NavigatableComponent; 036import org.openstreetmap.josm.gui.layer.OsmDataLayer; 037import org.openstreetmap.josm.gui.mappaint.Cascade; 038import org.openstreetmap.josm.gui.mappaint.ElemStyle; 039import org.openstreetmap.josm.gui.mappaint.ElemStyles; 040import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 041import org.openstreetmap.josm.gui.mappaint.MultiCascade; 042import org.openstreetmap.josm.gui.mappaint.StyleCache; 043import org.openstreetmap.josm.gui.mappaint.StyleCache.StyleList; 044import org.openstreetmap.josm.gui.mappaint.StyleSource; 045import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 046import org.openstreetmap.josm.gui.mappaint.xml.XmlStyleSource; 047import org.openstreetmap.josm.gui.widgets.JosmTextArea; 048import org.openstreetmap.josm.tools.DateUtils; 049import org.openstreetmap.josm.tools.GBC; 050import org.openstreetmap.josm.tools.WindowGeometry; 051 052/** 053 * Panel to inspect one or more OsmPrimitives. 054 * 055 * Gives an unfiltered view of the object's internal state. 056 * Might be useful for power users to give more detailed bug reports and 057 * to better understand the JOSM data representation. 058 */ 059public class InspectPrimitiveDialog extends ExtendedDialog { 060 061 protected List<OsmPrimitive> primitives; 062 protected OsmDataLayer layer; 063 private JosmTextArea txtMappaint; 064 boolean mappaintTabLoaded; 065 066 public InspectPrimitiveDialog(Collection<OsmPrimitive> primitives, OsmDataLayer layer) { 067 super(Main.parent, tr("Advanced object info"), new String[] {tr("Close")}); 068 this.primitives = new ArrayList<OsmPrimitive>(primitives); 069 this.layer = layer; 070 setRememberWindowGeometry(getClass().getName() + ".geometry", 071 WindowGeometry.centerInWindow(Main.parent, new Dimension(750, 550))); 072 073 setButtonIcons(new String[]{"ok.png"}); 074 final JTabbedPane tabs = new JTabbedPane(); 075 JPanel pData = buildDataPanel(); 076 tabs.addTab(tr("data"), pData); 077 final JPanel pMapPaint = new JPanel(); 078 tabs.addTab(tr("map style"), pMapPaint); 079 tabs.getModel().addChangeListener(new ChangeListener() { 080 081 @Override 082 public void stateChanged(ChangeEvent e) { 083 if (!mappaintTabLoaded && ((SingleSelectionModel) e.getSource()).getSelectedIndex() == 1) { 084 mappaintTabLoaded = true; 085 buildMapPaintPanel(pMapPaint); 086 createMapPaintText(); 087 } 088 } 089 }); 090 setContent(tabs, false); 091 } 092 093 protected JPanel buildDataPanel() { 094 JPanel p = new JPanel(new GridBagLayout()); 095 JosmTextArea txtData = new JosmTextArea(); 096 txtData.setFont(new Font("Monospaced", txtData.getFont().getStyle(), txtData.getFont().getSize())); 097 txtData.setEditable(false); 098 txtData.setText(buildDataText()); 099 txtData.setSelectionStart(0); 100 txtData.setSelectionEnd(0); 101 102 JScrollPane scroll = new JScrollPane(txtData); 103 104 p.add(scroll, GBC.std().fill()); 105 return p; 106 } 107 108 protected String buildDataText() { 109 DataText dt = new DataText(); 110 Collections.sort(primitives, new OsmPrimitiveComparator()); 111 for (OsmPrimitive o : primitives) { 112 dt.addPrimitive(o); 113 } 114 return dt.toString(); 115 } 116 117 class DataText { 118 static final String INDENT = " "; 119 static final String NL = "\n"; 120 121 private StringBuilder s = new StringBuilder(); 122 123 private DataText add(String title, String... values) { 124 s.append(INDENT).append(title); 125 for (String v : values) { 126 s.append(v); 127 } 128 s.append(NL); 129 return this; 130 } 131 132 private String getNameAndId(String name, long id) { 133 if (name != null) { 134 return name + tr(" ({0})", /* sic to avoid thousand seperators */ Long.toString(id)); 135 } else { 136 return Long.toString(id); 137 } 138 } 139 140 public void addPrimitive(OsmPrimitive o) { 141 142 addHeadline(o); 143 144 if (!(o.getDataSet() != null && o.getDataSet().getPrimitiveById(o) != null)) { 145 s.append(NL).append(INDENT).append(tr("not in data set")).append(NL); 146 return; 147 } 148 if (o.isIncomplete()) { 149 s.append(NL).append(INDENT).append(tr("incomplete")).append(NL); 150 return; 151 } 152 s.append(NL); 153 154 addState(o); 155 addCommon(o); 156 addAttributes(o); 157 addSpecial(o); 158 addReferrers(s, o); 159 addConflicts(o); 160 s.append(NL); 161 } 162 163 void addHeadline(OsmPrimitive o) { 164 addType(o); 165 addNameAndId(o); 166 } 167 168 void addType(OsmPrimitive o) { 169 if (o instanceof Node) { 170 s.append(tr("Node: ")); 171 } else if (o instanceof Way) { 172 s.append(tr("Way: ")); 173 } else if (o instanceof Relation) { 174 s.append(tr("Relation: ")); 175 } 176 } 177 178 void addNameAndId(OsmPrimitive o) { 179 String name = o.get("name"); 180 if (name == null) { 181 s.append(o.getUniqueId()); 182 } else { 183 s.append(getNameAndId(name, o.getUniqueId())); 184 } 185 } 186 187 void addState(OsmPrimitive o) { 188 StringBuilder sb = new StringBuilder(INDENT); 189 /* selected state is left out: not interesting as it is always selected */ 190 if (o.isDeleted()) { 191 sb.append(tr("deleted")).append(INDENT); 192 } 193 if (!o.isVisible()) { 194 sb.append(tr("deleted-on-server")).append(INDENT); 195 } 196 if (o.isModified()) { 197 sb.append(tr("modified")).append(INDENT); 198 } 199 if (o.isDisabledAndHidden()) { 200 sb.append(tr("filtered/hidden")).append(INDENT); 201 } 202 if (o.isDisabled()) { 203 sb.append(tr("filtered/disabled")).append(INDENT); 204 } 205 if (o.hasDirectionKeys()) { 206 if (o.reversedDirection()) { 207 sb.append(tr("has direction keys (reversed)")).append(INDENT); 208 } else { 209 sb.append(tr("has direction keys")).append(INDENT); 210 } 211 } 212 String state = sb.toString().trim(); 213 if (!state.isEmpty()) { 214 add(tr("State: "), sb.toString().trim()); 215 } 216 } 217 218 void addCommon(OsmPrimitive o) { 219 add(tr("Data Set: "), Integer.toHexString(o.getDataSet().hashCode())); 220 add(tr("Edited at: "), o.isTimestampEmpty() ? tr("<new object>") 221 : DateUtils.fromDate(o.getTimestamp())); 222 add(tr("Edited by: "), o.getUser() == null ? tr("<new object>") 223 : getNameAndId(o.getUser().getName(), o.getUser().getId())); 224 add(tr("Version: "), Integer.toString(o.getVersion())); 225 add(tr("In changeset: "), Integer.toString(o.getChangesetId())); 226 } 227 228 void addAttributes(OsmPrimitive o) { 229 if (o.hasKeys()) { 230 add(tr("Tags: ")); 231 for (String key : o.keySet()) { 232 s.append(INDENT).append(INDENT); 233 s.append(String.format("\"%s\"=\"%s\"%n", key, o.get(key))); 234 } 235 } 236 } 237 238 void addSpecial(OsmPrimitive o) { 239 if (o instanceof Node) { 240 addCoordinates((Node) o); 241 } else if (o instanceof Way) { 242 addBbox(o); 243 addWayNodes((Way) o); 244 } else if (o instanceof Relation) { 245 addBbox(o); 246 addRelationMembers((Relation) o); 247 } 248 } 249 250 void addRelationMembers(Relation r) { 251 add(trn("{0} Member: ", "{0} Members: ", r.getMembersCount(), r.getMembersCount())); 252 for (RelationMember m : r.getMembers()) { 253 s.append(INDENT).append(INDENT); 254 addHeadline(m.getMember()); 255 s.append(tr(" as \"{0}\"", m.getRole())); 256 s.append(NL); 257 } 258 } 259 260 void addWayNodes(Way w) { 261 add(tr("{0} Nodes: ", w.getNodesCount())); 262 for (Node n : w.getNodes()) { 263 s.append(INDENT).append(INDENT); 264 addNameAndId(n); 265 s.append(NL); 266 } 267 } 268 269 void addBbox(OsmPrimitive o) { 270 BBox bbox = o.getBBox(); 271 if (bbox != null) { 272 add(tr("Bounding box: "), bbox.toStringCSV(", ")); 273 EastNorth bottomRigth = Main.getProjection().latlon2eastNorth(bbox.getBottomRight()); 274 EastNorth topLeft = Main.getProjection().latlon2eastNorth(bbox.getTopLeft()); 275 add(tr("Bounding box (projected): "), 276 Double.toString(topLeft.east()), ", ", 277 Double.toString(bottomRigth.north()), ", ", 278 Double.toString(bottomRigth.east()), ", ", 279 Double.toString(topLeft.north())); 280 } 281 } 282 283 void addCoordinates(Node n) { 284 if (n.getCoor() != null) { 285 add(tr("Coordinates: "), 286 Double.toString(n.getCoor().lat()), ", ", 287 Double.toString(n.getCoor().lon())); 288 add(tr("Coordinates (projected): "), 289 Double.toString(n.getEastNorth().east()), ", ", 290 Double.toString(n.getEastNorth().north())); 291 } 292 } 293 294 void addReferrers(StringBuilder s, OsmPrimitive o) { 295 List<OsmPrimitive> refs = o.getReferrers(); 296 if (!refs.isEmpty()) { 297 add(tr("Part of: ")); 298 for (OsmPrimitive p : refs) { 299 s.append(INDENT).append(INDENT); 300 addHeadline(p); 301 s.append(NL); 302 } 303 } 304 } 305 306 void addConflicts(OsmPrimitive o) { 307 Conflict<?> c = layer.getConflicts().getConflictForMy(o); 308 if (c != null) { 309 add(tr("In conflict with: ")); 310 addNameAndId(c.getTheir()); 311 } 312 } 313 314 @Override 315 public String toString() { 316 return s.toString(); 317 } 318 } 319 320 protected void buildMapPaintPanel(JPanel p) { 321 p.setLayout(new GridBagLayout()); 322 txtMappaint = new JosmTextArea(); 323 txtMappaint.setFont(new Font("Monospaced", txtMappaint.getFont().getStyle(), txtMappaint.getFont().getSize())); 324 txtMappaint.setEditable(false); 325 326 p.add(new JScrollPane(txtMappaint), GBC.std().fill()); 327 } 328 329 protected void createMapPaintText() { 330 final Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getAllSelected(); 331 ElemStyles elemstyles = MapPaintStyles.getStyles(); 332 NavigatableComponent nc = Main.map.mapView; 333 double scale = nc.getDist100Pixel(); 334 335 for (OsmPrimitive osm : sel) { 336 txtMappaint.append(tr("Styles Cache for \"{0}\":", osm.getDisplayName(DefaultNameFormatter.getInstance()))); 337 338 MultiCascade mc = new MultiCascade(); 339 340 for (StyleSource s : elemstyles.getStyleSources()) { 341 if (s.active) { 342 txtMappaint.append(tr("\n\n> applying {0} style \"{1}\"\n", getSort(s), s.getDisplayString())); 343 s.apply(mc, osm, scale, null, false); 344 txtMappaint.append(tr("\nRange:{0}", mc.range)); 345 for (Entry<String, Cascade> e : mc.getLayers()) { 346 txtMappaint.append("\n " + e.getKey() + ": \n" + e.getValue()); 347 } 348 } else { 349 txtMappaint.append(tr("\n\n> skipping \"{0}\" (not active)", s.getDisplayString())); 350 } 351 } 352 txtMappaint.append(tr("\n\nList of generated Styles:\n")); 353 StyleList sl = elemstyles.get(osm, scale, nc); 354 for (ElemStyle s : sl) { 355 txtMappaint.append(" * " + s + "\n"); 356 } 357 txtMappaint.append("\n\n"); 358 } 359 360 if (sel.size() == 2) { 361 List<OsmPrimitive> selList = new ArrayList<OsmPrimitive>(sel); 362 StyleCache sc1 = selList.get(0).mappaintStyle; 363 StyleCache sc2 = selList.get(1).mappaintStyle; 364 if (sc1 == sc2) { 365 txtMappaint.append(tr("The 2 selected objects have identical style caches.")); 366 } 367 if (!sc1.equals(sc2)) { 368 txtMappaint.append(tr("The 2 selected objects have different style caches.")); 369 } 370 if (sc1.equals(sc2) && sc1 != sc2) { 371 txtMappaint.append(tr("Warning: The 2 selected objects have equal, but not identical style caches.")); 372 } 373 } 374 } 375 376 private String getSort(StyleSource s) { 377 if (s instanceof XmlStyleSource) { 378 return tr("xml"); 379 } else if (s instanceof MapCSSStyleSource) { 380 return tr("mapcss"); 381 } else { 382 return tr("unknown"); 383 } 384 } 385}