/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org> Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.ui.components; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.net.URL; import java.util.Hashtable; import java.util.Map; import java.util.ResourceBundle; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Element; import javax.swing.text.StyleConstants; import javax.swing.text.html.HTML; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLWriter; //Copied from org.netbeans.lib.profiler.ui.components //Autors Ian Formanek & Jiri Sedlacek public class JHTMLEditorPane extends JEditorPane implements HyperlinkListener, MouseListener { //~ Inner Classes ------------------------------------------------------------------------------------------------------------ /** Private Writer that extracts correctly formatted string from HTMLDocument */ private class ExtendedHTMLWriter extends HTMLWriter { //~ Constructors --------------------------------------------------------------------------------------------------------- public ExtendedHTMLWriter(Writer w, HTMLDocument doc, int pos, int len) { super(w, doc, pos, len); setLineLength(Integer.MAX_VALUE); } //~ Methods -------------------------------------------------------------------------------------------------------------- protected boolean isSupportedBreakFlowTag(AttributeSet attr) { Object o = attr.getAttribute(StyleConstants.NameAttribute); if (o instanceof HTML.Tag) { HTML.Tag tag = (HTML.Tag) o; if ((tag == HTML.Tag.HTML) || (tag == HTML.Tag.HEAD) || (tag == HTML.Tag.BODY) || (tag == HTML.Tag.HR)) { return false; } return (tag).breaksFlow(); } return false; } protected void emptyTag(Element elem) throws BadLocationException, IOException { if (isSupportedBreakFlowTag(elem.getAttributes())) { writeLineSeparator(); } if (matchNameAttribute(elem.getAttributes(), HTML.Tag.CONTENT)) { text(elem); } } protected void endTag(Element elem) throws IOException { if (isSupportedBreakFlowTag(elem.getAttributes())) { writeLineSeparator(); } } protected void startTag(Element elem) throws IOException, BadLocationException { } } // --- Private classes for copy/paste support -------------------------------- // // NOTE: only vertical formatting is correctly copy/pasted, // horizontal formatting (ul, li) is ignored. /** Private TransferHandler that copies correctly formatted string from HTMLDocument to system clipboard */ private class HTMLTextAreaTransferHandler extends TransferHandler { //~ Methods -------------------------------------------------------------------------------------------------------------- public void exportToClipboard(JComponent comp, Clipboard clip, int action) { try { int selStart = getSelectionStart(); int selLength = getSelectionEnd() - selStart; StringWriter plainTextWriter = new StringWriter(); try { new ExtendedHTMLWriter(plainTextWriter, (HTMLDocument) getDocument(), selStart, selLength).write(); } catch (Exception e) { } String plainText = NcrToUnicode.decode(plainTextWriter.toString()); clip.setContents(new StringSelection(plainText), null); if (action == TransferHandler.MOVE) { getDocument().remove(selStart, selLength); } } catch (BadLocationException ble) { } } } /** Class for decoding strings from NCR to Unicode */ private static class NcrToUnicode { //~ Static fields/initializers ------------------------------------------------------------------------------------------- private static Map<String, String> entities; //~ Methods -------------------------------------------------------------------------------------------------------------- public static String decode(String str) { StringBuffer ostr = new StringBuffer(); int i1 = 0; int i2 = 0; while (i2 < str.length()) { i1 = str.indexOf("&", i2); //NOI18N if (i1 == -1) { ostr.append(str.substring(i2, str.length())); break; } ostr.append(str.substring(i2, i1)); i2 = str.indexOf(";", i1); //NOI18N if (i2 == -1) { ostr.append(str.substring(i1, str.length())); break; } String tok = str.substring(i1 + 1, i2); if (tok.charAt(0) == '#') { //NOI18N if (tok.equals("#160")) { //NOI18N ostr.append(getEntities().get("nbsp")); //NOI18N // Fixes Issue 92818, " " is resolved as " " before decoding, so redirecting back to " " } else { tok = tok.substring(1); try { int radix = 10; if (tok.trim().charAt(0) == 'x') { //NOI18N radix = 16; tok = tok.substring(1, tok.length()); } ostr.append((char) Integer.parseInt(tok, radix)); } catch (NumberFormatException exp) { ostr.append('?'); //NOI18N } } } else { tok = getEntities().get(tok); if (tok != null) { ostr.append(tok); } else { ostr.append('?'); //NOI18N } } i2++; } return ostr.toString(); } private static synchronized Map<String, String> getEntities() { if (entities == null) { entities = new Hashtable(); //Quotation mark entities.put("quot", "\""); //NOI18N //Ampersand entities.put("amp", "\u0026"); //NOI18N //Less than entities.put("lt", "\u003C"); //NOI18N //Greater than entities.put("gt", "\u003E"); //NOI18N //Nonbreaking space entities.put("nbsp", "\u0020"); //NOI18N // Fixes Issue 92818, "\u00A0" (  equivalent) is resolved as incorrect character, thus mapping to standard space //Inverted exclamation point entities.put("iexcl", "\u00A1"); //NOI18N //Cent sign entities.put("cent", "\u00A2"); //NOI18N //Pound sign entities.put("pound", "\u00A3"); //NOI18N //General currency sign entities.put("curren", "\u00A4"); //NOI18N //Yen sign entities.put("yen", "\u00A5"); //NOI18N //Broken vertical bar entities.put("brvbar", "\u00A6"); //NOI18N //Section sign entities.put("sect", "\u00A7"); //NOI18N //Umlaut entities.put("uml", "\u00A8"); //NOI18N //Copyright entities.put("copy", "\u00A9"); //NOI18N //Feminine ordinal entities.put("ordf", "\u00AA"); //NOI18N //Left angle quote entities.put("laquo", "\u00AB"); //NOI18N //Not sign entities.put("not", "\u00AC"); //NOI18N //Soft hyphen entities.put("shy", "\u00AD"); //NOI18N //Registered trademark entities.put("reg", "\u00AE"); //NOI18N //Macron accent entities.put("macr", "\u00AF"); //NOI18N //Degree sign entities.put("deg", "\u00B0"); //NOI18N //Plus or minus entities.put("plusmn", "\u00B1"); //NOI18N //Superscript 2 entities.put("sup2", "\u00B2"); //NOI18N //Superscript 3 entities.put("sup3", "\u00B3"); //NOI18N //Acute accent entities.put("acute", "\u00B4"); //NOI18N //Micro sign (Greek mu) entities.put("micro", "\u00B5"); //NOI18N //Paragraph sign entities.put("para", "\u00B6"); //NOI18N //Middle dot entities.put("middot", "\u00B7"); //NOI18N //Cedilla entities.put("cedil", "\u00B8"); //NOI18N //Superscript 1 entities.put("sup1", "\u00B9"); //NOI18N //Masculine ordinal entities.put("ordm", "\u00BA"); //NOI18N //Right angle quote entities.put("raquo", "\u00BB"); //NOI18N //Fraction one-fourth entities.put("frac14", "\u00BC"); //NOI18N //Fraction one-half entities.put("frac12", "\u00BD"); //NOI18N //Fraction three-fourths entities.put("frac34", "\u00BE"); //NOI18N //Inverted question mark entities.put("iquest", "\u00BF"); //NOI18N //Capital A, grave accent entities.put("Agrave", "\u00C0"); //NOI18N //Capital A, acute accent entities.put("Aacute", "\u00C1"); //NOI18N //Capital A, circumflex accent entities.put("Acirc", "\u00C2"); //NOI18N //Capital A, tilde entities.put("Atilde", "\u00C3"); //NOI18N //Capital A, umlaut entities.put("Auml", "\u00C4"); //NOI18N //Capital A, ring entities.put("Aring", "\u00C5"); //NOI18N //Capital AE ligature entities.put("AElig", "\u00C6"); //NOI18N //Capital C, cedilla entities.put("Ccedil", "\u00C7"); //NOI18N //Capital E, grave accent entities.put("Egrave", "\u00C8"); //NOI18N //Capital E, acute accent entities.put("Eacute", "\u00C9"); //NOI18N //Capital E, circumflex accent entities.put("Ecirc", "\u00CA"); //NOI18N //Capital E, umlaut entities.put("Euml", "\u00CB"); //NOI18N //Capital I, grave accent entities.put("Igrave", "\u00CC"); //NOI18N //Capital I, acute accent entities.put("Iacute", "\u00CD"); //NOI18N //Capital I, circumflex accent entities.put("Icirc", "\u00CE"); //NOI18N //Capital I, umlaut entities.put("Iuml", "\u00CF"); //NOI18N //Capital eth, Icelandic entities.put("ETH", "\u00D0"); //NOI18N //Capital N, tilde entities.put("Ntilde", "\u00D1"); //NOI18N //Capital O, grave accent entities.put("Ograve", "\u00D2"); //NOI18N //Capital O, acute accent entities.put("Oacute", "\u00D3"); //NOI18N //Capital O, circumflex accent entities.put("Ocirc", "\u00D4"); //NOI18N //Capital O, tilde entities.put("Otilde", "\u00D5"); //NOI18N //Capital O, umlaut entities.put("Ouml", "\u00D6"); //NOI18N //Multiply sign entities.put("times", "\u00D7"); //NOI18N //Capital O, slash entities.put("Oslash", "\u00D8"); //NOI18N //Capital U, grave accent entities.put("Ugrave", "\u00D9"); //NOI18N //Capital U, acute accent entities.put("Uacute", "\u00DA"); //NOI18N //Capital U, circumflex accent entities.put("Ucirc", "\u00DB"); //NOI18N //Capital U, umlaut entities.put("Uuml", "\u00DC"); //NOI18N //Capital Y, acute accent entities.put("Yacute", "\u00DD"); //NOI18N //Capital thorn, Icelandic entities.put("THORN", "\u00DE"); //NOI18N //Small sz ligature, German entities.put("szlig", "\u00DF"); //NOI18N //Small a, grave accent entities.put("agrave", "\u00E0"); //NOI18N //Small a, acute accent entities.put("aacute", "\u00E1"); //NOI18N //Small a, circumflex accent entities.put("acirc", "\u00E2"); //NOI18N //Small a, tilde entities.put("atilde", "\u00E3"); //NOI18N //Small a, umlaut entities.put("auml", "\u00E4"); //NOI18N //Small a, ring entities.put("aring", "\u00E5"); //NOI18N //Small ae ligature entities.put("aelig", "\u00E6"); //NOI18N //Small c, cedilla entities.put("ccedil", "\u00E7"); //NOI18N //Small e, grave accent entities.put("egrave", "\u00E8"); //NOI18N //Small e, acute accent entities.put("eacute", "\u00E9"); //NOI18N //Small e, circumflex accent entities.put("ecirc", "\u00EA"); //NOI18N //Small e, umlaut entities.put("euml", "\u00EB"); //NOI18N //Small i, grave accent entities.put("igrave", "\u00EC"); //NOI18N //Small i, acute accent entities.put("iacute", "\u00ED"); //NOI18N //Small i, circumflex accent entities.put("icirc", "\u00EE"); //NOI18N //Small i, umlaut entities.put("iuml", "\u00EF"); //NOI18N //Small eth, Icelandic entities.put("eth", "\u00F0"); //NOI18N //Small n, tilde entities.put("ntilde", "\u00F1"); //NOI18N //Small o, grave accent entities.put("ograve", "\u00F2"); //NOI18N //Small o, acute accent entities.put("oacute", "\u00F3"); //NOI18N //Small o, circumflex accent entities.put("ocirc", "\u00F4"); //NOI18N //Small o, tilde entities.put("otilde", "\u00F5"); //NOI18N //Small o, umlaut entities.put("ouml", "\u00F6"); //NOI18N //Division sign entities.put("divide", "\u00F7"); //NOI18N //Small o, slash entities.put("oslash", "\u00F8"); //NOI18N //Small u, grave accent entities.put("ugrave", "\u00F9"); //NOI18N //Small u, acute accent entities.put("uacute", "\u00FA"); //NOI18N //Small u, circumflex accent entities.put("ucirc", "\u00FB"); //NOI18N //Small u, umlaut entities.put("uuml", "\u00FC"); //NOI18N //Small y, acute accent entities.put("yacute", "\u00FD"); //NOI18N //Small thorn, Icelandic entities.put("thorn", "\u00FE"); //NOI18N //Small y, umlaut entities.put("yuml", "\u00FF"); //NOI18N } return entities; } } //~ Static fields/initializers ----------------------------------------------------------------------------------------------- // ----- // I18N String constants private static final ResourceBundle messages = ResourceBundle.getBundle("org.gephi.ui.components.Bundle"); // NOI18N private static final String CUT_STRING = messages.getString("HTMLTextArea_CutString"); // NOI18N private static final String COPY_STRING = messages.getString("HTMLTextArea_CopyString"); // NOI18N private static final String PASTE_STRING = messages.getString("HTMLTextArea_PasteString"); // NOI18N private static final String DELETE_STRING = messages.getString("HTMLTextArea_DeleteString"); // NOI18N private static final String SELECT_ALL_STRING = messages.getString("HTMLTextArea_SelectAllString"); // NOI18N // ----- //~ Instance fields ---------------------------------------------------------------------------------------------------------- private ActionListener popupListener; private JMenuItem itemCopy; private JMenuItem itemCut; private JMenuItem itemDelete; private JMenuItem itemPaste; private JMenuItem itemSelectAll; // --- Popup menu support ---------------------------------------------------- private JPopupMenu popupMenu; private String originalText; private boolean showPopup = true; //~ Constructors ------------------------------------------------------------------------------------------------------------- public JHTMLEditorPane() { super(); setEditorKit(new HTMLEditorKit()); setEditable(false); setOpaque(true); setAutoscrolls(true); addHyperlinkListener(this); setTransferHandler(new HTMLTextAreaTransferHandler()); setFont(UIManager.getFont("Label.font")); //NOI18N addMouseListener(this); } public JHTMLEditorPane(String text) { this(); setText(text); } //~ Methods ------------------------------------------------------------------------------------------------------------------ public void setForeground(Color color) { super.setForeground(color); setText(originalText); } public void setShowPopup(boolean showPopup) { this.showPopup = showPopup; } public boolean getShowPopup() { return showPopup; } public void setText(String value) { if (value == null) { return; } originalText = value; Font font = getFont(); Color textColor = getForeground(); value = value.replaceAll("\\n\\r|\\r\\n|\\n|\\r", "<br>"); //NOI18N value = value.replaceAll("<code>", "<code style=\"font-size: " + font.getSize() + "pt;\">"); //NOI18N String colorText = "rgb(" + textColor.getRed() + "," + textColor.getGreen() + "," + textColor.getBlue() + ")"; //NOI18N super.setText("<html><body text=\"" + colorText + "\" style=\"font-size: " + font.getSize() + "pt; font-family: " + font.getName() + ";\">" + value + "</body></html>"); //NOI18N } public void deleteSelection() { try { getDocument().remove(getSelectionStart(), getSelectionEnd() - getSelectionStart()); } catch (Exception ex) { } ; } public void hyperlinkUpdate(HyperlinkEvent e) { if (!isEnabled()) { return; } if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { showURL(e.getURL()); } else if (e.getEventType() == HyperlinkEvent.EventType.ENTERED) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else if (e.getEventType() == HyperlinkEvent.EventType.EXITED) { setCursor(Cursor.getDefaultCursor()); } } public void mouseClicked(MouseEvent e) { if (e.getModifiers() == InputEvent.BUTTON3_MASK) { if (isEnabled() && isFocusable() && showPopup) { JPopupMenu popup = getPopupMenu(); if (popup != null) { updatePopupMenu(); if (!hasFocus()) { requestFocus(); // required for Select All functionality } popup.show(this, e.getX(), e.getY()); } } } } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void paste() { try { replaceSelection(Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this).getTransferData(DataFlavor.stringFlavor).toString()); } catch (Exception ex) { } } protected JPopupMenu getPopupMenu() { if (popupMenu == null) { popupMenu = createPopupMenu(); } return popupMenu; } protected JPopupMenu createPopupMenu() { JPopupMenu popup = new JPopupMenu(); popupListener = createPopupListener(); itemCut = new JMenuItem(CUT_STRING); itemCopy = new JMenuItem(COPY_STRING); itemPaste = new JMenuItem(PASTE_STRING); itemDelete = new JMenuItem(DELETE_STRING); itemSelectAll = new JMenuItem(SELECT_ALL_STRING); itemCut.addActionListener(popupListener); itemCopy.addActionListener(popupListener); itemPaste.addActionListener(popupListener); itemDelete.addActionListener(popupListener); itemSelectAll.addActionListener(popupListener); popup.add(itemCut); popup.add(itemCopy); popup.add(itemPaste); popup.add(itemDelete); popup.addSeparator(); popup.add(itemSelectAll); return popup; } protected void showURL(URL url) { // override to react to URL clicks } protected void updatePopupMenu() { // Cut itemCut.setEnabled(isEditable() && (getSelectedText() != null)); // Copy itemCopy.setEnabled(getSelectedText() != null); // Paste try { Transferable clipboardContent = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this); itemPaste.setEnabled(isEditable() && (clipboardContent != null) && clipboardContent.isDataFlavorSupported(DataFlavor.stringFlavor)); } catch (Exception e) { itemPaste.setEnabled(false); } // Delete if (isEditable()) { itemDelete.setVisible(true); itemDelete.setEnabled(getSelectedText() != null); } else { itemDelete.setVisible(false); } // Select All // always visible and enabled... } private ActionListener createPopupListener() { return new ActionListener() { public void actionPerformed(ActionEvent e) { if (e.getSource() == itemCut) { cut(); } else if (e.getSource() == itemCopy) { copy(); } else if (e.getSource() == itemPaste) { paste(); } else if (e.getSource() == itemDelete) { deleteSelection(); } else if (e.getSource() == itemSelectAll) { selectAll(); } } }; } }