Ignore:
Timestamp:
2017-11-11T22:11:36+01:00 (6 years ago)
Author:
Don-vip
Message:

fix #11217, fix #12623 - major rework of notes tooltips:

  • display clickable links
  • allow to copy text from notes, including comments
Location:
trunk/src/org/openstreetmap/josm/gui
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java

    r12846 r13111  
    55import static org.openstreetmap.josm.tools.I18n.trn;
    66
     7import java.awt.Color;
    78import java.awt.Dimension;
    89import java.awt.Graphics2D;
     
    1819
    1920import javax.swing.Action;
     21import javax.swing.BorderFactory;
    2022import javax.swing.Icon;
    2123import javax.swing.ImageIcon;
    22 import javax.swing.JToolTip;
     24import javax.swing.JWindow;
    2325import javax.swing.SwingUtilities;
    24 
     26import javax.swing.UIManager;
     27
     28import org.openstreetmap.josm.Main;
    2529import org.openstreetmap.josm.actions.SaveActionBase;
    2630import org.openstreetmap.josm.data.Bounds;
     
    3236import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    3337import org.openstreetmap.josm.gui.MainApplication;
     38import org.openstreetmap.josm.gui.MainFrame;
    3439import org.openstreetmap.josm.gui.MapView;
    3540import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
     
    4045import org.openstreetmap.josm.gui.io.importexport.NoteExporter;
    4146import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     47import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    4248import org.openstreetmap.josm.io.OsmApi;
    4349import org.openstreetmap.josm.io.XmlWriter;
     
    5561
    5662    private final NoteData noteData;
     63
     64    private Note displayedNote;
     65    private HtmlPanel displayedPanel;
     66    private JWindow displayedWindow;
    5767
    5868    /**
     
    8191        MainApplication.getMap().mapView.removeMouseListener(this);
    8292        noteData.removeNoteDataUpdateListener(this);
     93        hideNoteWindow();
    8394        super.destroy();
    8495    }
     
    137148            g.drawImage(icon.getImage(), p.x - (width / 2), p.y - height, MainApplication.getMap().mapView);
    138149        }
    139         if (noteData.getSelectedNote() != null) {
    140             StringBuilder sb = new StringBuilder("<html>");
    141             sb.append(tr("Note"))
    142               .append(' ').append(noteData.getSelectedNote().getId());
    143             for (NoteComment comment : noteData.getSelectedNote().getComments()) {
    144                 String commentText = comment.getText();
    145                 //closing a note creates an empty comment that we don't want to show
    146                 if (commentText != null && !commentText.trim().isEmpty()) {
    147                     sb.append("<hr/>");
    148                     String userName = XmlWriter.encode(comment.getUser().getName());
    149                     if (userName == null || userName.trim().isEmpty()) {
    150                         userName = "&lt;Anonymous&gt;";
    151                     }
    152                     sb.append(userName);
    153                     sb.append(" on ");
    154                     sb.append(DateUtils.getDateFormat(DateFormat.MEDIUM).format(comment.getCommentTimestamp()));
    155                     sb.append(":<br/>");
    156                     String htmlText = XmlWriter.encode(comment.getText(), true);
    157                     htmlText = htmlText.replace("&#xA;", "<br/>"); //encode method leaves us with entity instead of \n
    158                     htmlText = htmlText.replace("/", "/\u200b"); //zero width space to wrap long URLs (see #10864)
    159                     sb.append(htmlText);
     150        Note selectedNote = noteData.getSelectedNote();
     151        if (selectedNote != null) {
     152            paintSelectedNote(g, mv, iconHeight, iconWidth, selectedNote);
     153        } else {
     154            hideNoteWindow();
     155        }
     156    }
     157
     158    private void hideNoteWindow() {
     159        if (displayedWindow != null) {
     160            displayedWindow.setVisible(false);
     161            displayedWindow.dispose();
     162            displayedWindow = null;
     163            displayedPanel = null;
     164            displayedNote = null;
     165        }
     166    }
     167
     168    private void paintSelectedNote(Graphics2D g, MapView mv, final int iconHeight, final int iconWidth, Note selectedNote) {
     169        Point p = mv.getPoint(selectedNote.getLatLon());
     170
     171        g.setColor(ColorHelper.html2color(Config.getPref().get("color.selected")));
     172        g.drawRect(p.x - (iconWidth / 2), p.y - iconHeight, iconWidth - 1, iconHeight - 1);
     173
     174        if (displayedNote != null && !displayedNote.equals(selectedNote)) {
     175            hideNoteWindow();
     176        }
     177
     178        Point screenloc = mv.getLocationOnScreen();
     179        int tx = screenloc.x + p.x + (iconWidth / 2) + 5;
     180        int ty = screenloc.y + p.y - iconHeight - 1;
     181
     182        String text = getNoteToolTip(selectedNote);
     183
     184        if (displayedWindow == null) {
     185            displayedPanel = new HtmlPanel(text);
     186            displayedPanel.setBackground(UIManager.getColor("ToolTip.background"));
     187            displayedPanel.setForeground(UIManager.getColor("ToolTip.foreground"));
     188            displayedPanel.setFont(UIManager.getFont("ToolTip.font"));
     189            displayedPanel.setBorder(BorderFactory.createLineBorder(Color.black));
     190            displayedPanel.enableClickableHyperlinks();
     191            fixPanelSize(mv, text);
     192            displayedWindow = new JWindow((MainFrame) Main.parent);
     193            displayedWindow.add(displayedPanel);
     194        } else {
     195            displayedPanel.setText(text);
     196            fixPanelSize(mv, text);
     197        }
     198
     199        displayedWindow.pack();
     200        displayedWindow.setLocation(tx, ty);
     201        displayedWindow.setVisible(true);
     202        displayedNote = selectedNote;
     203    }
     204
     205    private void fixPanelSize(MapView mv, String text) {
     206        Dimension d = displayedPanel.getPreferredSize();
     207        if (d.width > mv.getWidth() / 2) {
     208            // To make sure long notes such as https://www.openstreetmap.org/note/278197 are displayed correctly
     209            displayedPanel.setText(text.replaceAll("\\. ([\\p{Lower}\\p{Upper}\\p{Punct}])", "\\.<br>$1"));
     210        }
     211    }
     212
     213    /**
     214     * Returns the HTML-formatted tooltip text for the given note.
     215     * @param note note to display
     216     * @return the HTML-formatted tooltip text for the given note
     217     * @since 13111
     218     */
     219    public static String getNoteToolTip(Note note) {
     220        StringBuilder sb = new StringBuilder("<html>");
     221        sb.append(tr("Note"))
     222          .append(' ').append(note.getId());
     223        for (NoteComment comment : note.getComments()) {
     224            String commentText = comment.getText();
     225            //closing a note creates an empty comment that we don't want to show
     226            if (commentText != null && !commentText.trim().isEmpty()) {
     227                sb.append("<hr/>");
     228                String userName = XmlWriter.encode(comment.getUser().getName());
     229                if (userName == null || userName.trim().isEmpty()) {
     230                    userName = "&lt;Anonymous&gt;";
    160231                }
     232                sb.append(userName)
     233                  .append(" on ")
     234                  .append(DateUtils.getDateFormat(DateFormat.MEDIUM).format(comment.getCommentTimestamp()))
     235                  .append(":<br>");
     236                String htmlText = XmlWriter.encode(comment.getText(), true);
     237                // encode method leaves us with entity instead of \n
     238                htmlText = htmlText.replace("&#xA;", "<br>");
     239                // convert URLs to proper HTML links
     240                htmlText = htmlText.replaceAll("(https?://\\S+)", "<a href=\"$1\">$1</a>");
     241                sb.append(htmlText);
    161242            }
    162             sb.append("</html>");
    163             JToolTip toolTip = new JToolTip();
    164             toolTip.setTipText(sb.toString());
    165             Point p = mv.getPoint(noteData.getSelectedNote().getLatLon());
    166 
    167             g.setColor(ColorHelper.html2color(Config.getPref().get("color.selected")));
    168             g.drawRect(p.x - (iconWidth / 2), p.y - iconHeight,
    169                     iconWidth - 1, iconHeight - 1);
    170 
    171             int tx = p.x + (iconWidth / 2) + 5;
    172             int ty = p.y - iconHeight - 1;
    173             g.translate(tx, ty);
    174 
    175             //Carried over from the OSB plugin. Not entirely sure why it is needed
    176             //but without it, the tooltip doesn't get sized correctly
    177             for (int x = 0; x < 2; x++) {
    178                 Dimension d = toolTip.getUI().getPreferredSize(toolTip);
    179                 d.width = Math.min(d.width, mv.getWidth() / 2);
    180                 if (d.width > 0 && d.height > 0) {
    181                     toolTip.setSize(d);
    182                     try {
    183                         toolTip.paint(g);
    184                     } catch (IllegalArgumentException e) {
    185                         // See #11123 - https://bugs.openjdk.java.net/browse/JDK-6719550
    186                         // Ignore the exception, as Netbeans does: http://hg.netbeans.org/main-silver/rev/c96f4d5fbd20
    187                         Logging.log(Logging.LEVEL_ERROR, e);
    188                     }
    189                 }
    190             }
    191             g.translate(-tx, -ty);
    192         }
     243        }
     244        sb.append("</html>");
     245        String result = sb.toString();
     246        Logging.debug(result);
     247        return result;
    193248    }
    194249
  • trunk/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java

    r12928 r13111  
    3535import javax.swing.SwingUtilities;
    3636import javax.swing.UIManager;
    37 import javax.swing.event.HyperlinkEvent;
    38 import javax.swing.event.HyperlinkListener;
    3937import javax.swing.text.html.HTMLEditorKit;
    4038
     
    5250import org.openstreetmap.josm.tools.ImageProvider;
    5351import org.openstreetmap.josm.tools.InputMapUtils;
    54 import org.openstreetmap.josm.tools.OpenBrowser;
    5552import org.openstreetmap.josm.tools.UserCancelException;
    5653import org.openstreetmap.josm.tools.Utils;
     
    137134                        + "</body></html>"
    138135        );
    139         pnlMessage.getEditorPane().addHyperlinkListener(new ExternalBrowserLauncher());
     136        pnlMessage.enableClickableHyperlinks();
    140137        pnl.add(pnlMessage, gc);
    141138
     
    423420        }
    424421    }
    425 
    426     static class ExternalBrowserLauncher implements HyperlinkListener {
    427         @Override
    428         public void hyperlinkUpdate(HyperlinkEvent e) {
    429             if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
    430                 OpenBrowser.displayUrl(e.getDescription());
    431             }
    432         }
    433     }
    434422}
  • trunk/src/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreference.java

    r12987 r13111  
    3939import javax.swing.JToolBar;
    4040import javax.swing.UIManager;
    41 import javax.swing.event.HyperlinkEvent.EventType;
    4241import javax.swing.event.ListSelectionEvent;
    4342import javax.swing.event.ListSelectionListener;
     
    7473import org.openstreetmap.josm.tools.LanguageInfo;
    7574import org.openstreetmap.josm.tools.Logging;
    76 import org.openstreetmap.josm.tools.OpenBrowser;
    7775
    7876/**
     
    371369            HtmlPanel help = new HtmlPanel(tr("New default entries can be added in the <a href=\"{0}\">Wiki</a>.",
    372370                Main.getJOSMWebsite()+"/wiki/Maps"));
    373             help.getEditorPane().addHyperlinkListener(e -> {
    374                 if (e.getEventType() == EventType.ACTIVATED) {
    375                     OpenBrowser.displayUrl(e.getURL().toString());
    376                 }
    377             });
     371            help.enableClickableHyperlinks();
    378372            add(help, GBC.eol().insets(10, 0, 0, 10).fill(GBC.HORIZONTAL));
    379373
  • trunk/src/org/openstreetmap/josm/gui/preferences/plugin/PluginListPanel.java

    r11366 r13111  
    1515import javax.swing.SwingConstants;
    1616import javax.swing.SwingUtilities;
    17 import javax.swing.event.HyperlinkEvent.EventType;
    1817
    1918import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    2019import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
    2120import org.openstreetmap.josm.plugins.PluginInformation;
    22 import org.openstreetmap.josm.tools.OpenBrowser;
    2321
    2422/**
     
    157155            HtmlPanel description = new HtmlPanel();
    158156            description.setText(pi.getDescriptionAsHtml());
    159             description.getEditorPane().addHyperlinkListener(e -> {
    160                 if (e.getEventType() == EventType.ACTIVATED) {
    161                     OpenBrowser.displayUrl(e.getURL().toString());
    162                 }
    163             });
     157            description.enableClickableHyperlinks();
    164158            lblPlugin.setLabelFor(description);
    165159
  • trunk/src/org/openstreetmap/josm/gui/widgets/HtmlPanel.java

    r11553 r13111  
    55import java.awt.Font;
    66import java.text.MessageFormat;
     7import java.util.Arrays;
    78import java.util.Optional;
    89
     
    1011import javax.swing.JPanel;
    1112import javax.swing.UIManager;
     13import javax.swing.event.HyperlinkEvent;
     14import javax.swing.event.HyperlinkListener;
    1215import javax.swing.text.html.StyleSheet;
     16
     17import org.openstreetmap.josm.tools.OpenBrowser;
    1318
    1419/**
     
    2227 */
    2328public class HtmlPanel extends JPanel {
     29
     30    private static final HyperlinkListener defaultHyperlinkListener = e -> {
     31        if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) {
     32            OpenBrowser.displayUrl(e.getURL().toString());
     33        }
     34    };
     35
    2436    private JosmEditorPane jepMessage;
    2537
     
    88100        jepMessage.setText(Optional.ofNullable(text).orElse(""));
    89101    }
     102
     103    /**
     104     * Opens hyperlinks on click.
     105     * @since 13111
     106     */
     107    public final void enableClickableHyperlinks() {
     108        if (!Arrays.asList(jepMessage.getHyperlinkListeners()).contains(defaultHyperlinkListener)) {
     109            jepMessage.addHyperlinkListener(defaultHyperlinkListener);
     110        }
     111    }
    90112}
Note: See TracChangeset for help on using the changeset viewer.