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