| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License") + you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openmeetings.test; |
| |
| /** |
| * David Bismut, david.bismut@gmail.com |
| * Intern, SETLabs, Infosys Technologies Ltd. May 2004 - Jul 2004 |
| * Ecole des Mines de Nantes, France |
| */ |
| import java.awt.event.KeyAdapter; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.MouseEvent; |
| import java.awt.event.MouseMotionAdapter; |
| import java.io.IOException; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.accessibility.AccessibleHypertext; |
| import javax.swing.JEditorPane; |
| import javax.swing.text.AttributeSet; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Element; |
| import javax.swing.text.html.HTML; |
| import javax.swing.text.html.HTMLDocument; |
| import javax.swing.text.html.HTMLEditorKit; |
| import javax.swing.text.html.StyleSheet; |
| import javax.swing.text.html.parser.ParserDelegator; |
| |
| /** |
| * A "HTML" JEditorPane embedded with a special HTMLDocument that detects urls |
| * and displays them as hyperlinks. When CTRL is pressed, the urls are |
| * clickable. |
| * |
| * @author David Bismut |
| * |
| */ |
| public class EditorPaneLinkDetector extends JEditorPane { |
| private static final long serialVersionUID = 2811878994346374017L; |
| |
| /** |
| * Creates a <code>EditorPaneLinkDetector</code>. |
| */ |
| public EditorPaneLinkDetector() { |
| |
| HTMLEditorKit htmlkit = new HTMLEditorKit(); |
| |
| StyleSheet styles = htmlkit.getStyleSheet(); |
| StyleSheet ss = new StyleSheet(); |
| |
| ss.addStyleSheet(styles); |
| |
| ss.addRule("body {font-family:arial;font-size:12pt}"); |
| ss.addRule("p {font-family:arial;margin:2}"); |
| |
| HTMLDocument doc = new HTMLDocLinkDetector(ss); |
| |
| setEditorKit(htmlkit); |
| |
| setDocument(doc); |
| |
| addMouseMotionListener(new MouseMotionAdapter() { |
| public void mouseMoved(MouseEvent e) { |
| |
| AccessibleJTextComponent context = (AccessibleJTextComponent) getAccessibleContext() |
| .getAccessibleEditableText(); |
| |
| AccessibleHypertext accText = (AccessibleHypertext) context |
| .getAccessibleText(); |
| |
| int index = accText.getIndexAtPoint(e.getPoint()); |
| |
| int linkIndex = accText.getLinkIndex(index); |
| if (linkIndex == -1) { |
| setToolTipText(null); |
| return; |
| } |
| |
| String linkDesc = accText.getLink(linkIndex) |
| .getAccessibleActionDescription(0); |
| |
| String toolTipText = "<html><body style='margin: 3'>" |
| + linkDesc |
| + "<br><b>CTRL + click to follow link</b></body></html>"; |
| setToolTipText(toolTipText); |
| } |
| }); |
| |
| addKeyListener(new KeyAdapter() { |
| public void keyPressed(KeyEvent e) { |
| |
| if (e.getKeyCode() == KeyEvent.VK_CONTROL) { |
| if (isEditable()) |
| setEditable(false); |
| } else { |
| if (!isEditable()) |
| setEditable(true); |
| } |
| |
| } |
| |
| public void keyReleased(KeyEvent e) { |
| |
| if (e.getKeyCode() == KeyEvent.VK_CONTROL) { |
| setEditable(true); |
| } |
| |
| } |
| }); |
| } |
| |
| protected class HTMLDocLinkDetector extends HTMLDocument { |
| |
| /** |
| * |
| */ |
| private static final long serialVersionUID = 1226244167782160437L; |
| |
| public HTMLDocLinkDetector(StyleSheet ss) { |
| super(ss); |
| |
| setAsynchronousLoadPriority(4); |
| setTokenThreshold(100); |
| setParser(new ParserDelegator()); |
| } |
| |
| /** |
| * Returns true if the Element contains a HTML.Tag.A attribute, false |
| * otherwise. |
| * |
| * @param e |
| * the Element to be checkd |
| * @return |
| */ |
| protected boolean isLink(Element e) { |
| |
| return (e.getAttributes().getAttribute(HTML.Tag.A) != null); |
| |
| } |
| |
| /** |
| * This method corrects or creates a url contained in an Element as an |
| * hyperlink. |
| * |
| * @param e |
| * the Element to be computed |
| * @throws BadLocationException |
| */ |
| protected void computeLinks(Element e) throws BadLocationException { |
| |
| int caretPos = getCaretPosition(); |
| try { |
| if (isLink(e)) |
| correctLink(e); |
| else |
| createLink(e); |
| } catch (IOException ex) { |
| ex.printStackTrace(); |
| } |
| setCaretPosition(Math.min(caretPos, getLength())); |
| } |
| |
| /** |
| * The method corrects the url inside an Element, that is supposed to be |
| * an element containing a link only. This function is typically called |
| * when the url is beeing edited. What the function does is to remove |
| * the html tags, so the url is actually edited in plain text and not as |
| * an hyperlink. |
| * |
| * @param e |
| * the Element that contains the url |
| * @throws BadLocationException |
| * @throws IOException |
| */ |
| protected void correctLink(Element e) throws BadLocationException, |
| IOException { |
| |
| int length = e.getEndOffset() - e.getStartOffset(); |
| |
| boolean endOfDoc = e.getEndOffset() == getLength() + 1; |
| |
| // to avoid catching the final '\n' of the document. |
| if (endOfDoc) |
| length--; |
| |
| String text = getText(e.getStartOffset(), length); |
| |
| setOuterHTML(e, text); |
| |
| // insert final spaces ignored by the html |
| Matcher spaceMatcher = Pattern.compile("(\\s+)$").matcher(text); |
| |
| if (spaceMatcher.find()) { |
| String endingSpaces = spaceMatcher.group(1); |
| insertString(Math.min(getLength(), e.getEndOffset()), |
| endingSpaces, null); |
| } |
| } |
| |
| /** |
| * The method check if the element contains a url in plain text, and if |
| * so, it creates the html tag HTML.Tag.A to have the url displayed as |
| * an hyperlink. |
| * |
| * @param e |
| * element that contains the url |
| * @throws BadLocationException |
| * @throws IOException |
| */ |
| protected void createLink(Element e) throws BadLocationException, |
| IOException { |
| |
| int caretPos = getCaretPosition(); |
| |
| int startOffset = e.getStartOffset(); |
| int length = e.getEndOffset() - e.getStartOffset(); |
| |
| boolean endOfDoc = e.getEndOffset() == getLength() + 1; |
| // to avoid catching the final '\n' of the document. |
| if (endOfDoc) |
| length--; |
| |
| String text = getText(startOffset, length); |
| |
| Matcher matcher = Pattern.compile( |
| "(?i)(\\b(http://|https://|www.|ftp://|file:/|mailto:)\\S+)(\\s+)") |
| .matcher(text); |
| |
| if (matcher.find()) { |
| String url = matcher.group(1); |
| //String prefix = matcher.group(2); |
| String endingSpaces = matcher.group(3); |
| |
| // to ignore characters after the caret |
| int validPos = startOffset + matcher.start(3) + 1; |
| if (validPos > caretPos) |
| return; |
| |
| Matcher dotEndMatcher = Pattern.compile("([\\W&&[^/]]+)$") |
| .matcher(url); |
| |
| //Ending non alpha characters like [.,?%] shouldn't be included |
| // in the url. |
| String endingDots = ""; |
| if (dotEndMatcher.find()) { |
| endingDots = dotEndMatcher.group(1); |
| url = dotEndMatcher.replaceFirst(""); |
| } |
| |
| text = matcher.replaceFirst("<a href='" + url + "'>" + url |
| + "</a>" + endingDots + endingSpaces); |
| |
| setOuterHTML(e, text); |
| |
| // insert initial spaces ignored by the html |
| Matcher spaceMatcher = Pattern.compile("^(\\s+)").matcher(text); |
| |
| if (spaceMatcher.find()) { |
| String initialSpaces = spaceMatcher.group(1); |
| insertString(startOffset, initialSpaces, null); |
| } |
| |
| // insert final spaces ignored by the html |
| spaceMatcher = Pattern.compile("(\\s+)$").matcher(text); |
| |
| if (spaceMatcher.find()) { |
| String extraSpaces = spaceMatcher.group(1); |
| int endoffset = e.getEndOffset(); |
| if (extraSpaces.charAt(extraSpaces.length() - 1) == '\n') { |
| extraSpaces = extraSpaces.substring(0, extraSpaces |
| .length() - 1); |
| endoffset--; |
| } |
| insertString(Math.min(getLength(), endoffset), extraSpaces, |
| null); |
| } |
| } |
| } |
| |
| public void remove(int offs, int len) throws BadLocationException { |
| |
| super.remove(offs, len); |
| Element e = getCharacterElement(offs - len); |
| computeLinks(e); |
| } |
| |
| public void insertString(int offs, String str, AttributeSet a) |
| throws BadLocationException { |
| |
| super.insertString(offs, str, a); |
| Element e = getCharacterElement(offs); |
| computeLinks(e); |
| } |
| } |
| } |