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    }
111
112    @Override
113    public boolean isMergable(Layer other) {
114        return false;
115    }
116
117    @Override
118    public boolean isChanged() {
119        return updateCount != Main.map.validatorDialog.tree.getUpdateCount();
120    }
121
122    @Override
123    public void visitBoundingBox(BoundingXYVisitor v) {
124    }
125
126    @Override
127    public Object getInfoComponent() {
128        return getToolTipText();
129    }
130
131    @Override
132    public Action[] getMenuEntries() {
133        return new Action[] {
134                LayerListDialog.getInstance().createShowHideLayerAction(),
135                LayerListDialog.getInstance().createDeleteLayerAction(),
136                SeparatorLayerAction.INSTANCE,
137                new RenameLayerAction(null, this),
138                SeparatorLayerAction.INSTANCE,
139                new LayerListPopup.InfoAction(this) };
140    }
141
142    @Override
143    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
144    }
145
146    @Override
147    public void layerAdded(Layer newLayer) {
148    }
149
150    /**
151     * If layer is the OSM Data layer, remove all errors
152     */
153    @Override
154    public void layerRemoved(Layer oldLayer) {
155        if (oldLayer instanceof OsmDataLayer && Main.isDisplayingMapView() && !Main.main.hasEditLayer()) {
156            Main.main.removeLayer(this);
157        } else if (oldLayer == this) {
158            MapView.removeLayerChangeListener(this);
159            OsmValidator.errorLayer = null;
160        }
161    }
162}