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