001// License: GPL. See LICENSE file for details.
002package org.openstreetmap.josm.gui.layer;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Graphics2D;
007import java.util.Enumeration;
008import java.util.List;
009
010import javax.swing.Action;
011import javax.swing.Icon;
012import javax.swing.tree.DefaultMutableTreeNode;
013
014import org.openstreetmap.josm.Main;
015import org.openstreetmap.josm.actions.RenameLayerAction;
016import org.openstreetmap.josm.data.Bounds;
017import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
018import org.openstreetmap.josm.data.validation.OsmValidator;
019import org.openstreetmap.josm.data.validation.PaintVisitor;
020import org.openstreetmap.josm.data.validation.Severity;
021import org.openstreetmap.josm.data.validation.TestError;
022import org.openstreetmap.josm.gui.MapView;
023import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
024import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
025import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
026import org.openstreetmap.josm.tools.ImageProvider;
027import org.openstreetmap.josm.tools.MultiMap;
028
029/**
030 * A layer showing error messages.
031 *
032 * @author frsantos
033 */
034public class ValidatorLayer extends Layer implements LayerChangeListener {
035
036    private int updateCount = -1;
037
038    /**
039     * Constructs a new Validator layer
040     */
041    public ValidatorLayer() {
042        super(tr("Validation errors"));
043        MapView.addLayerChangeListener(this);
044    }
045
046    /**
047     * Return a static icon.
048     */
049    @Override
050    public Icon getIcon() {
051        return ImageProvider.get("layer", "validator_small");
052    }
053
054    /**
055     * Draw all primitives in this layer but do not draw modified ones (they
056     * are drawn by the edit layer).
057     * Draw nodes last to overlap the ways they belong to.
058     */
059    @SuppressWarnings("unchecked")
060    @Override
061    public void paint(final Graphics2D g, final MapView mv, Bounds bounds) {
062        updateCount = Main.map.validatorDialog.tree.getUpdateCount();
063        DefaultMutableTreeNode root = Main.map.validatorDialog.tree.getRoot();
064        if (root == null || root.getChildCount() == 0)
065            return;
066
067        PaintVisitor paintVisitor = new PaintVisitor(g, mv);
068
069        DefaultMutableTreeNode severity = (DefaultMutableTreeNode) root.getLastChild();
070        while (severity != null) {
071            Enumeration<DefaultMutableTreeNode> errorMessages = severity.breadthFirstEnumeration();
072            while (errorMessages.hasMoreElements()) {
073                Object tn = errorMessages.nextElement().getUserObject();
074                if (tn instanceof TestError) {
075                    paintVisitor.visit(((TestError) tn));
076                }
077            }
078
079            // Severities in inverse order
080            severity = severity.getPreviousSibling();
081        }
082
083        paintVisitor.clearPaintedObjects();
084    }
085
086    @Override
087    public String getToolTipText() {
088        MultiMap<Severity, TestError> errorTree = new MultiMap<>();
089        List<TestError> errors = Main.map.validatorDialog.tree.getErrors();
090        for (TestError e : errors) {
091            errorTree.put(e.getSeverity(), e);
092        }
093
094        StringBuilder b = new StringBuilder();
095        for (Severity s : Severity.values()) {
096            if (errorTree.containsKey(s)) {
097                b.append(tr(s.toString())).append(": ").append(errorTree.get(s).size()).append("<br>");
098            }
099        }
100
101        if (b.length() == 0)
102            return "<html>" + tr("No validation errors") + "</html>";
103        else
104            return "<html>" + tr("Validation errors") + ":<br>" + b + "</html>";
105    }
106
107    @Override
108    public void mergeFrom(Layer from) {
109    }
110
111    @Override
112    public boolean isMergable(Layer other) {
113        return false;
114    }
115
116    @Override
117    public boolean isChanged() {
118        return updateCount != Main.map.validatorDialog.tree.getUpdateCount();
119    }
120
121    @Override
122    public void visitBoundingBox(BoundingXYVisitor v) {
123    }
124
125    @Override
126    public Object getInfoComponent() {
127        return getToolTipText();
128    }
129
130    @Override
131    public Action[] getMenuEntries() {
132        return new Action[] {
133                LayerListDialog.getInstance().createShowHideLayerAction(),
134                LayerListDialog.getInstance().createDeleteLayerAction(),
135                SeparatorLayerAction.INSTANCE,
136                new RenameLayerAction(null, this),
137                SeparatorLayerAction.INSTANCE,
138                new LayerListPopup.InfoAction(this) };
139    }
140
141    @Override
142    public void destroy() {
143    }
144
145    @Override
146    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
147    }
148
149    @Override
150    public void layerAdded(Layer newLayer) {
151    }
152
153    /**
154     * If layer is the OSM Data layer, remove all errors
155     */
156    @Override
157    public void layerRemoved(Layer oldLayer) {
158        if (oldLayer instanceof OsmDataLayer && Main.isDisplayingMapView() && !Main.main.hasEditLayer()) {
159            Main.main.removeLayer(this);
160        } else if (oldLayer == this) {
161            MapView.removeLayerChangeListener(this);
162            OsmValidator.errorLayer = null;
163        }
164    }
165}