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 044 private int updateCount = -1; 045 046 /** 047 * Constructs a new Validator layer 048 */ 049 public ValidatorLayer() { 050 super(tr("Validation errors")); 051 Main.getLayerManager().addLayerChangeListener(this); 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 updateCount = Main.map.validatorDialog.tree.getUpdateCount(); 071 DefaultMutableTreeNode root = Main.map.validatorDialog.tree.getRoot(); 072 if (root == null || root.getChildCount() == 0) 073 return; 074 075 PaintVisitor paintVisitor = new PaintVisitor(g, mv); 076 077 DefaultMutableTreeNode severity = (DefaultMutableTreeNode) root.getLastChild(); 078 while (severity != null) { 079 Enumeration<TreeNode> errorMessages = severity.breadthFirstEnumeration(); 080 while (errorMessages.hasMoreElements()) { 081 Object tn = ((DefaultMutableTreeNode) errorMessages.nextElement()).getUserObject(); 082 if (tn instanceof TestError) { 083 paintVisitor.visit((TestError) tn); 084 } 085 } 086 087 // Severities in inverse order 088 severity = severity.getPreviousSibling(); 089 } 090 091 paintVisitor.clearPaintedObjects(); 092 } 093 094 @Override 095 public String getToolTipText() { 096 MultiMap<Severity, TestError> errorTree = new MultiMap<>(); 097 List<TestError> errors = Main.map.validatorDialog.tree.getErrors(); 098 for (TestError e : errors) { 099 errorTree.put(e.getSeverity(), e); 100 } 101 102 StringBuilder b = new StringBuilder(); 103 for (Severity s : Severity.values()) { 104 if (errorTree.containsKey(s)) { 105 b.append(tr(s.toString())).append(": ").append(errorTree.get(s).size()).append("<br>"); 106 } 107 } 108 109 if (b.length() == 0) 110 return "<html>" + tr("No validation errors") + "</html>"; 111 else 112 return "<html>" + tr("Validation errors") + ":<br>" + b + "</html>"; 113 } 114 115 @Override 116 public void mergeFrom(Layer from) { 117 // Do nothing 118 } 119 120 @Override 121 public boolean isMergable(Layer other) { 122 return false; 123 } 124 125 @Override 126 public boolean isChanged() { 127 return updateCount != Main.map.validatorDialog.tree.getUpdateCount(); 128 } 129 130 @Override 131 public void visitBoundingBox(BoundingXYVisitor v) { 132 // Do nothing 133 } 134 135 @Override 136 public Object getInfoComponent() { 137 return getToolTipText(); 138 } 139 140 @Override 141 public Action[] getMenuEntries() { 142 return new Action[] { 143 LayerListDialog.getInstance().createShowHideLayerAction(), 144 LayerListDialog.getInstance().createDeleteLayerAction(), 145 SeparatorLayerAction.INSTANCE, 146 new RenameLayerAction(null, this), 147 SeparatorLayerAction.INSTANCE, 148 new LayerListPopup.InfoAction(this) }; 149 } 150 151 @Override 152 public void layerOrderChanged(LayerOrderChangeEvent e) { 153 // Do nothing 154 } 155 156 @Override 157 public void layerAdded(LayerAddEvent e) { 158 // Do nothing 159 } 160 161 /** 162 * If layer is the OSM Data layer, remove all errors 163 */ 164 @Override 165 public void layerRemoving(LayerRemoveEvent e) { 166 // Removed layer is still in that list. 167 if (e.getRemovedLayer() instanceof OsmDataLayer && e.getSource().getLayersOfType(OsmDataLayer.class).size() <= 1) { 168 e.scheduleRemoval(Collections.singleton(this)); 169 } else if (e.getRemovedLayer() == this) { 170 Main.getLayerManager().removeLayerChangeListener(this); 171 OsmValidator.errorLayer = null; 172 } 173 } 174 175 @Override 176 public LayerPositionStrategy getDefaultLayerPosition() { 177 return LayerPositionStrategy.IN_FRONT; 178 } 179}