/* * Kontalk Java client * Copyright (C) 2016 Kontalk Devteam <devteam@kontalk.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kontalk.view; import javax.swing.event.MouseInputAdapter; import javax.swing.text.BadLocationException; import javax.swing.text.Element; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.swing.text.StyledDocument; import java.awt.Color; import java.awt.Cursor; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.net.MalformedURLException; import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.alee.laf.text.WebTextPane; import com.alee.utils.WebUtils; /** * Static methods/field for parsing web links in the text of a WebTextPane. * Cause Android has Linkify and I have to write this myself .( * Some parts taken from: https://community.oracle.com/thread/2089990 * * @author Alexander Bikadorov {@literal <bikaejkb@mail.tu-berlin.de>} */ final class LinkUtils { private static final Logger LOGGER = Logger.getLogger(LinkUtils.class.getName()); static final TextClickListener CLICK_LISTENER = new TextClickListener(); static final TextMotionListener MOTION_LISTENER = new TextMotionListener(); private static final Style DEFAULT_STYLE = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE); /** Undoubtedly the best URL regex ever made. */ private static final Pattern URL_PATTERN = Pattern.compile( "(http[s]?://)?" + // scheme; group 1 "(\\w+[a-zA-Z_0-9-]*\\w+\\.)+" + // sub- and host-level(s); group 2 "[a-z]{2,}(:[0-9]+)?" + // TLD and port; group 3 "(/[^\\s?#/]*)*" + // path; group 4 "(\\?[^\\s?#]*)*" + // query; group 5 "(\\#[^\\s?#]*)*", // fragment; group 6 Pattern.CASE_INSENSITIVE); private static final String URL_ATT_NAME = "URL"; static class Linkifier { private final StyledDocument mDocument; // style for all links in a document private final Style mURLStyle; Linkifier(StyledDocument doc) { mDocument = doc; mURLStyle = mDocument.addStyle(null, DEFAULT_STYLE); StyleConstants.setForeground(mURLStyle, Color.BLUE); // only for identifying URLs mURLStyle.addAttribute(URL_ATT_NAME, new Object()); } void linkify(String text) throws BadLocationException { Matcher m = URL_PATTERN.matcher(text); int lastPos = 0; while (m.find()) { // non-matching insertDefault(mDocument, text.substring(lastPos, m.start())); // matching mDocument.insertString(mDocument.getLength(), m.group(), mURLStyle); lastPos = m.end(); } // last non-matching insertDefault(mDocument, lastPos >= text.length() ? "" : text.substring(lastPos)); } } private static void insertDefault(StyledDocument doc, String text) throws BadLocationException { doc.insertString(doc.getLength(), text, DEFAULT_STYLE); } private static class TextClickListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { WebTextPane textPane = (WebTextPane) e.getComponent(); StyledDocument doc = textPane.getStyledDocument(); Element elem = doc.getCharacterElement(textPane.viewToModel(e.getPoint())); if (!elem.getAttributes().isDefined(URL_ATT_NAME)) // not a link return; int len = elem.getEndOffset() - elem.getStartOffset(); final String url; try { url = doc.getText(elem.getStartOffset(), len); } catch (BadLocationException ex) { LOGGER.log(Level.WARNING, "can't get URL", ex); return; } Runnable run = new Runnable() { @Override public void run() { WebUtils.browseSiteSafely(fixProto(url)); } }; new Thread(run, "Link Browser").start(); } } private static class TextMotionListener extends MouseInputAdapter { @Override public void mouseMoved(MouseEvent e) { WebTextPane textPane = (WebTextPane) e.getComponent(); StyledDocument doc = textPane.getStyledDocument(); Element elem = doc.getCharacterElement(textPane.viewToModel(e.getPoint())); if (elem.getAttributes().isDefined(URL_ATT_NAME)) { textPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else { textPane.setCursor(Cursor.getDefaultCursor()); } } } private static String fixProto(String url) { try { new URL(url); return url; } catch (MalformedURLException ignored) { } url = "http://" + url; try { new URL(url); } catch (MalformedURLException ex) { LOGGER.log(Level.WARNING, "invalid url", ex); } return url; } }