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