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.List; 010 011import javax.swing.Action; 012import javax.swing.Icon; 013import javax.swing.tree.DefaultMutableTreeNode; 014 015import org.openstreetmap.josm.actions.RenameLayerAction; 016import org.openstreetmap.josm.actions.SaveActionBase; 017import org.openstreetmap.josm.data.Bounds; 018import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 019import org.openstreetmap.josm.data.validation.OsmValidator; 020import org.openstreetmap.josm.data.validation.Severity; 021import org.openstreetmap.josm.data.validation.TestError; 022import org.openstreetmap.josm.gui.MainApplication; 023import org.openstreetmap.josm.gui.MapView; 024import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 025import org.openstreetmap.josm.gui.dialogs.LayerListPopup; 026import org.openstreetmap.josm.gui.dialogs.validator.ValidatorTreePanel; 027import org.openstreetmap.josm.gui.io.importexport.ValidatorErrorExporter; 028import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; 029import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; 030import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 031import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 032import org.openstreetmap.josm.gui.layer.validation.PaintVisitor; 033import org.openstreetmap.josm.tools.ImageProvider; 034import org.openstreetmap.josm.tools.MultiMap; 035 036/** 037 * A layer showing error messages. 038 * 039 * @author frsantos 040 * 041 * @since 3669 (creation) 042 * @since 10386 (new LayerChangeListener interface) 043 */ 044public class ValidatorLayer extends Layer implements LayerChangeListener { 045 private final Runnable invalidator = this::invalidate; 046 047 /** 048 * Constructs a new Validator layer 049 */ 050 public ValidatorLayer() { 051 super(tr("Validation errors")); 052 MainApplication.getLayerManager().addLayerChangeListener(this); 053 MainApplication.getMap().validatorDialog.tree.addInvalidationListener(invalidator); 054 } 055 056 /** 057 * Return a static icon. 058 */ 059 @Override 060 public Icon getIcon() { 061 return ImageProvider.get("layer", "validator_small"); 062 } 063 064 /** 065 * Draw all primitives in this layer but do not draw modified ones (they 066 * are drawn by the edit layer). 067 * Draw nodes last to overlap the ways they belong to. 068 */ 069 @Override 070 public void paint(final Graphics2D g, final MapView mv, Bounds bounds) { 071 DefaultMutableTreeNode root = MainApplication.getMap().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 ValidatorTreePanel.visitTestErrors(severity, paintVisitor::visit); 080 081 // Severities in inverse order 082 severity = severity.getPreviousSibling(); 083 } 084 085 paintVisitor.clearPaintedObjects(); 086 } 087 088 @Override 089 public String getToolTipText() { 090 MultiMap<Severity, TestError> errorTree = new MultiMap<>(); 091 List<TestError> errors = MainApplication.getMap().validatorDialog.tree.getErrors(); 092 for (TestError e : errors) { 093 errorTree.put(e.getSeverity(), e); 094 } 095 096 StringBuilder b = new StringBuilder(); 097 for (Severity s : Severity.values()) { 098 if (errorTree.containsKey(s)) { 099 b.append(tr(s.toString())).append(": ").append(errorTree.get(s).size()).append("<br>"); 100 } 101 } 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}