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}