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    }