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