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