Ticket #11153: 11153.patch

File 11153.patch, 12.4 KB (added by taylor.smock, 3 years ago)

Add hyperlinks to *.tag|*.key in the ValidatorTreePanel

  • src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java

    Subject: [PATCH] #11153: improve readability of validator warnings
    ---
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java b/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java
    a b  
    1111import java.util.HashMap;
    1212import java.util.HashSet;
    1313import java.util.List;
     14import java.util.Locale;
    1415import java.util.Map;
    1516import java.util.Objects;
    1617import java.util.Optional;
     
    4445import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
    4546import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
    4647import org.openstreetmap.josm.io.IllegalDataException;
     48import org.openstreetmap.josm.spi.preferences.Config;
    4749import org.openstreetmap.josm.tools.CheckParameterUtil;
    4850import org.openstreetmap.josm.tools.Logging;
    4951import org.openstreetmap.josm.tools.Utils;
     
    277279                    Integer.parseInt(m.group(1)), m.group(2), p);
    278280            try {
    279281                // Perform replacement with null-safe + regex-safe handling
    280                 m.appendReplacement(sb, String.valueOf(argument).replace("^(", "").replace(")$", ""));
     282                final String replacement = String.valueOf(argument).replace("^(", "").replace(")$", "");
     283                final String type = m.group(2);
     284                final String url;
     285                if ("key".equals(type) || "tag".equals(type)) {
     286                    url = Config.getUrls().getOSMWiki() + "/wiki/"
     287                            + m.group(2).substring(0, 1).toUpperCase(Locale.ROOT) + m.group(2).substring(1)
     288                            + ":" + replacement;
     289                } else {
     290                    url = null;
     291                }
     292                m.appendReplacement(sb, (url != null ? "<a href=\"" + url + "\">" : "")
     293                        + replacement
     294                        + (url != null ? "</a>" : ""));
    281295            } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
    282296                Logging.log(Logging.LEVEL_ERROR, tr("Unable to replace argument {0} in {1}: {2}", argument, sb, e.getMessage()), e);
    283297            }
  • src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java b/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java
    a b  
    267267                    }
    268268                    final String msg = addSize(searchMsg, errorsWithDescription);
    269269
    270                     final DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
     270                    final DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode("<html>" + msg + "</html>");
    271271                    DefaultMutableTreeNode currNode = groupNode != null ? groupNode : severityNode;
    272272                    currNode.add(messageNode);
    273273                    if (oldExpandedRows.contains(searchMsg)) {
  • src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreeRenderer.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreeRenderer.java b/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreeRenderer.java
    a b  
    33
    44import java.awt.Component;
    55
     6import javax.swing.JLabel;
    67import javax.swing.JTree;
    78import javax.swing.tree.DefaultMutableTreeNode;
    8 import javax.swing.tree.DefaultTreeCellRenderer;
    99
    1010import org.openstreetmap.josm.data.validation.Severity;
    1111import org.openstreetmap.josm.data.validation.TestError;
    1212import org.openstreetmap.josm.data.validation.util.MultipleNameVisitor;
     13import org.openstreetmap.josm.gui.widgets.HtmlTreeCellRenderer;
    1314import org.openstreetmap.josm.tools.ImageProvider;
    1415
    1516/**
    1617 * Tree renderer for displaying errors
    1718 * @author frsantos
    1819 */
    19 public class ValidatorTreeRenderer extends DefaultTreeCellRenderer {
     20public class ValidatorTreeRenderer extends HtmlTreeCellRenderer {
     21
     22    private static final long serialVersionUID = 4750085115702320153L;
    2023
    2124    @Override
    2225    public Component getTreeCellRendererComponent(JTree tree, Object value,
    2326            boolean selected, boolean expanded, boolean leaf, int row,
    2427            boolean hasFocus) {
    25         super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
     28        final Component component = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
    2629
    27         DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
    28         Object nodeInfo = node.getUserObject();
     30        final DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
     31        final Object nodeInfo = node.getUserObject();
    2932
    30         if (nodeInfo instanceof Severity) {
    31             Severity s = (Severity) nodeInfo;
    32             setIcon(ImageProvider.get("data", s.getIcon()));
    33         } else if (nodeInfo instanceof TestError) {
    34             TestError error = (TestError) nodeInfo;
    35             MultipleNameVisitor v = error.getNameVisitor();
    36             setText(v.getText());
    37             setIcon(v.getIcon());
     33        if (component instanceof JLabel) {
     34            final JLabel label = (JLabel) component;
     35            if (nodeInfo instanceof Severity) {
     36                final Severity s = (Severity) nodeInfo;
     37                label.setIcon(ImageProvider.get("data", s.getIcon()));
     38            } else if (nodeInfo instanceof TestError) {
     39                final TestError error = (TestError) nodeInfo;
     40                MultipleNameVisitor v = error.getNameVisitor();
     41                label.setText(v.getText());
     42                label.setIcon(v.getIcon());
     43            }
    3844        }
    39         return this;
     45        return component;
    4046    }
    4147}
  • new file src/org/openstreetmap/josm/gui/widgets/HtmlTreeCellRenderer.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/widgets/HtmlTreeCellRenderer.java b/src/org/openstreetmap/josm/gui/widgets/HtmlTreeCellRenderer.java
    new file mode 100644
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.widgets;
     3
     4import java.awt.Component;
     5import java.awt.Point;
     6import java.awt.Rectangle;
     7import java.awt.event.MouseEvent;
     8import java.util.Arrays;
     9
     10import javax.annotation.Nullable;
     11import javax.swing.JTree;
     12import javax.swing.event.HyperlinkEvent;
     13import javax.swing.event.MouseInputAdapter;
     14import javax.swing.tree.DefaultMutableTreeNode;
     15import javax.swing.tree.DefaultTreeCellRenderer;
     16import javax.swing.tree.TreePath;
     17
     18import org.openstreetmap.josm.tools.OpenBrowser;
     19
     20/**
     21 * A {@link javax.swing.tree.TreeCellRenderer} for cells that may contain hyperlinks.
     22 * @since xxx
     23 */
     24public class HtmlTreeCellRenderer extends DefaultTreeCellRenderer {
     25    /**
     26     * This only exists since the JTree cannot pass events to subcomponents.
     27     */
     28    private static class JTreeMouseInputListener extends MouseInputAdapter {
     29        @Override
     30        public void mouseClicked(MouseEvent mouseEvent) {
     31            if (mouseEvent.getComponent() instanceof JTree) {
     32                final Point p = mouseEvent.getPoint();
     33                final JTree tree = (JTree) mouseEvent.getComponent();
     34                final Component component = getComponentAt(tree, p); // p is translated here
     35                if (component != null) {
     36                    component.dispatchEvent(new MouseEvent(component, // Use the component we found to fire the event
     37                            mouseEvent.getID(), mouseEvent.getWhen(), mouseEvent.getModifiers(),
     38                            p.x, p.y, mouseEvent.getClickCount(), mouseEvent.isPopupTrigger(),
     39                            mouseEvent.getButton()));
     40                }
     41            }
     42        }
     43
     44        /**
     45         * Get a component at
     46         * @param tree The tree to get the component from
     47         * @param p The point to get the component at (will be modified)
     48         * @return The component
     49         */
     50        @Nullable
     51        private static Component getComponentAt(JTree tree, Point p) {
     52            final TreePath path = tree.getPathForLocation(p.x, p.y);
     53            if (path != null) {
     54                final int row = tree.getRowForPath(path);
     55                final Object last = path.getLastPathComponent();
     56                // We need to get the component as "shown" so that we can get the link clicked.
     57                final Component component = tree.getCellRenderer().getTreeCellRendererComponent(tree, last,
     58                        tree.isRowSelected(row), tree.isExpanded(row),
     59                        tree.getModel().isLeaf(last), row, true);
     60                final Rectangle bounds = tree.getPathBounds(path);
     61                if (bounds != null) {
     62                    // If we don't translate the point, we are attempting to get the link in the wrong frame of reference
     63                    // The reference x/y are 0. This is set in BasicTextUI#getVisibleEditorRect, so we must translate the point here.
     64                    p.translate(-bounds.x, -bounds.y);
     65                    // Just make certain that we are consistent for other objects.
     66                    bounds.x = 0;
     67                    bounds.y = 0;
     68                    // Set the bounds from the JTree component (needed for proper layout)
     69                    component.setBounds(bounds);
     70                    return component;
     71                }
     72            }
     73            return null;
     74        }
     75    }
     76
     77    private static final long serialVersionUID = -4842755204541968238L;
     78    /**
     79     * A reusable panel to avoid new objects where possible.
     80     * It isn't worth it to make a new object for each row, since it doesn't receive mouse events (tested on Java 8).
     81     */
     82    private final JosmEditorPane panel = new JosmEditorPane();
     83    /** JTree does not send mouse events to subcomponents */
     84    private final JTreeMouseInputListener mouseTreeListener = new JTreeMouseInputListener();
     85
     86    /**
     87     * Create a new renderer
     88     */
     89    public HtmlTreeCellRenderer() {
     90        panel.addHyperlinkListener(e -> {
     91            if (e.getURL() != null && HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) {
     92                OpenBrowser.displayUrl(e.getURL().toString());
     93            }
     94        });
     95        JosmEditorPane.makeJLabelLike(panel, false);
     96    }
     97
     98    @Override
     99    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
     100                                                  boolean leaf, int row, boolean hasFocus) {
     101        if (value instanceof DefaultMutableTreeNode) {
     102            if (Arrays.stream(tree.getMouseListeners()).noneMatch(this.mouseTreeListener::equals)) {
     103                tree.addMouseListener(this.mouseTreeListener);
     104                tree.addMouseMotionListener(this.mouseTreeListener);
     105            }
     106            final DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
     107            final Object object = node.getUserObject();
     108            if (object instanceof String && ((String) object).startsWith("<html>") && ((String) object).endsWith("</html>")) {
     109                this.panel.setText((String) object);
     110                return this.panel;
     111            }
     112        }
     113        return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
     114    }
     115}