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