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}