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