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.GridBagLayout; 009import java.text.Collator; 010import java.util.ArrayList; 011import java.util.Collection; 012import java.util.Collections; 013import java.util.List; 014import java.util.Locale; 015import java.util.Map; 016import java.util.Map.Entry; 017import java.util.TreeMap; 018 019import javax.swing.JPanel; 020import javax.swing.JScrollPane; 021import javax.swing.JTabbedPane; 022import javax.swing.SingleSelectionModel; 023import javax.swing.event.ChangeEvent; 024import javax.swing.event.ChangeListener; 025 026import org.openstreetmap.josm.Main; 027import org.openstreetmap.josm.data.osm.OsmPrimitive; 028import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator; 029import org.openstreetmap.josm.gui.DefaultNameFormatter; 030import org.openstreetmap.josm.gui.ExtendedDialog; 031import org.openstreetmap.josm.gui.NavigatableComponent; 032import org.openstreetmap.josm.gui.layer.OsmDataLayer; 033import org.openstreetmap.josm.gui.mappaint.Cascade; 034import org.openstreetmap.josm.gui.mappaint.ElemStyles; 035import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 036import org.openstreetmap.josm.gui.mappaint.MultiCascade; 037import org.openstreetmap.josm.gui.mappaint.StyleCache; 038import org.openstreetmap.josm.gui.mappaint.StyleElementList; 039import org.openstreetmap.josm.gui.mappaint.StyleSource; 040import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 041import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement; 042import org.openstreetmap.josm.gui.util.GuiHelper; 043import org.openstreetmap.josm.gui.widgets.JosmTextArea; 044import org.openstreetmap.josm.tools.GBC; 045import org.openstreetmap.josm.tools.WindowGeometry; 046 047/** 048 * Panel to inspect one or more OsmPrimitives. 049 * 050 * Gives an unfiltered view of the object's internal state. 051 * Might be useful for power users to give more detailed bug reports and 052 * to better understand the JOSM data representation. 053 */ 054public class InspectPrimitiveDialog extends ExtendedDialog { 055 056 protected transient List<OsmPrimitive> primitives; 057 protected transient OsmDataLayer layer; 058 private boolean mappaintTabLoaded; 059 private boolean editcountTabLoaded; 060 061 /** 062 * Constructs a new {@code InspectPrimitiveDialog}. 063 * @param primitives collection of primitives 064 * @param layer data layer 065 */ 066 public InspectPrimitiveDialog(final Collection<OsmPrimitive> primitives, OsmDataLayer layer) { 067 super(Main.parent, tr("Advanced object info"), new String[] {tr("Close")}); 068 this.primitives = new ArrayList<>(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 076 tabs.addTab(tr("data"), genericMonospacePanel(new JPanel(), buildDataText(layer, this.primitives))); 077 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 genericMonospacePanel(pMapPaint, buildMapPaintText()); 087 } 088 } 089 }); 090 091 final JPanel pEditCounts = new JPanel(); 092 tabs.addTab(tr("edit counts"), pEditCounts); 093 tabs.getModel().addChangeListener(new ChangeListener() { 094 095 @Override 096 public void stateChanged(ChangeEvent e) { 097 if (!editcountTabLoaded && ((SingleSelectionModel) e.getSource()).getSelectedIndex() == 2) { 098 editcountTabLoaded = true; 099 genericMonospacePanel(pEditCounts, buildListOfEditorsText(primitives)); 100 } 101 } 102 }); 103 104 setContent(tabs, false); 105 } 106 107 protected static JPanel genericMonospacePanel(JPanel p, String s) { 108 p.setLayout(new GridBagLayout()); 109 JosmTextArea jte = new JosmTextArea(); 110 jte.setFont(GuiHelper.getMonospacedFont(jte)); 111 jte.setEditable(false); 112 jte.append(s); 113 p.add(new JScrollPane(jte), GBC.std().fill()); 114 return p; 115 } 116 117 protected static String buildDataText(OsmDataLayer layer, List<OsmPrimitive> primitives) { 118 InspectPrimitiveDataText dt = new InspectPrimitiveDataText(layer); 119 Collections.sort(primitives, new OsmPrimitiveComparator()); 120 for (OsmPrimitive o : primitives) { 121 dt.addPrimitive(o); 122 } 123 return dt.toString(); 124 } 125 126 protected static String buildMapPaintText() { 127 final Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getAllSelected(); 128 ElemStyles elemstyles = MapPaintStyles.getStyles(); 129 NavigatableComponent nc = Main.map.mapView; 130 double scale = nc.getDist100Pixel(); 131 132 final StringBuilder txtMappaint = new StringBuilder(); 133 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock(); 134 try { 135 for (OsmPrimitive osm : sel) { 136 txtMappaint.append(tr("Styles Cache for \"{0}\":", osm.getDisplayName(DefaultNameFormatter.getInstance()))); 137 138 MultiCascade mc = new MultiCascade(); 139 140 for (StyleSource s : elemstyles.getStyleSources()) { 141 if (s.active) { 142 txtMappaint.append(tr("\n\n> applying {0} style \"{1}\"\n", getSort(s), s.getDisplayString())); 143 s.apply(mc, osm, scale, false); 144 txtMappaint.append(tr("\nRange:{0}", mc.range)); 145 for (Entry<String, Cascade> e : mc.getLayers()) { 146 txtMappaint.append("\n ").append(e.getKey()).append(": \n").append(e.getValue()); 147 } 148 } else { 149 txtMappaint.append(tr("\n\n> skipping \"{0}\" (not active)", s.getDisplayString())); 150 } 151 } 152 txtMappaint.append(tr("\n\nList of generated Styles:\n")); 153 StyleElementList sl = elemstyles.get(osm, scale, nc); 154 for (StyleElement s : sl) { 155 txtMappaint.append(" * ").append(s).append('\n'); 156 } 157 txtMappaint.append("\n\n"); 158 } 159 } finally { 160 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock(); 161 } 162 if (sel.size() == 2) { 163 List<OsmPrimitive> selList = new ArrayList<>(sel); 164 StyleCache sc1 = selList.get(0).mappaintStyle; 165 StyleCache sc2 = selList.get(1).mappaintStyle; 166 if (sc1 == sc2) { 167 txtMappaint.append(tr("The 2 selected objects have identical style caches.")); 168 } 169 if (!sc1.equals(sc2)) { 170 txtMappaint.append(tr("The 2 selected objects have different style caches.")); 171 } 172 if (sc1.equals(sc2) && sc1 != sc2) { 173 txtMappaint.append(tr("Warning: The 2 selected objects have equal, but not identical style caches.")); 174 } 175 } 176 return txtMappaint.toString(); 177 } 178 179 /* Future Ideas: 180 Calculate the most recent edit date from o.getTimestamp(). 181 Sort by the count for presentation, so the most active editors are on top. 182 Count only tagged nodes (so empty way nodes don't inflate counts). 183 */ 184 protected static String buildListOfEditorsText(Iterable<OsmPrimitive> primitives) { 185 final Map<String, Integer> editCountByUser = new TreeMap<>(Collator.getInstance(Locale.getDefault())); 186 187 // Count who edited each selected object 188 for (OsmPrimitive o : primitives) { 189 if (o.getUser() != null) { 190 String username = o.getUser().getName(); 191 Integer oldCount = editCountByUser.get(username); 192 if (oldCount == null) { 193 editCountByUser.put(username, 1); 194 } else { 195 editCountByUser.put(username, oldCount + 1); 196 } 197 } 198 } 199 200 // Print the count in sorted order 201 final StringBuilder s = new StringBuilder(48) 202 .append(trn("{0} user last edited the selection:", "{0} users last edited the selection:", 203 editCountByUser.size(), editCountByUser.size())) 204 .append("\n\n"); 205 for (Map.Entry<String, Integer> entry : editCountByUser.entrySet()) { 206 final String username = entry.getKey(); 207 final Integer editCount = entry.getValue(); 208 s.append(String.format("%6d %s", editCount, username)).append('\n'); 209 } 210 return s.toString(); 211 } 212 213 private static String getSort(StyleSource s) { 214 if (s instanceof MapCSSStyleSource) { 215 return tr("mapcss"); 216 } else { 217 return tr("unknown"); 218 } 219 } 220}