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