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