/* GNU Lesser General Public License ExtendedHTMLEditorKit Copyright (C) 2001-2002 Frits Jalvingh & Howard Kistler This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gmgen.gui; import java.awt.datatransfer.Clipboard; import java.awt.event.ActionEvent; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.io.InputStream; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import javax.swing.Action; import javax.swing.JEditorPane; import javax.swing.JTextPane; import javax.swing.text.*; import javax.swing.text.html.HTML; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; /** * This class extends HTMLEditorKit so that it can provide other renderer * classes instead of the defaults. Most important is the part which renders * relative image paths. * * @author <a href="mailto:jal@grimor.com">Frits Jalvingh </a> */ public class ExtendedHTMLEditorKit extends HTMLEditorKit { /** * Get the HTML tag * @param e * @return HTML.Tag */ private static HTML.Tag getHTMLTag(Element e) { //Set List of tags Map<String, HTML.Tag> tags = new HashMap<>(); HTML.Tag[] tagList = HTML.getAllTags(); for (final HTML.Tag aTagList : tagList) { tags.put(aTagList.toString(), aTagList); } //Get Tag if (tags.containsKey(e.getName())) { return tags.get(e.getName()); } return null; } /** * Get the parent of the list item * @param eleSearch * @return the parent of the list item */ public static Element getListItemParent(Element eleSearch) { String listItemTag = HTML.Tag.LI.toString(); Element workingElement = eleSearch; do { if (listItemTag.equals(workingElement.getName())) { return workingElement; } workingElement = workingElement.getParentElement(); } while (!workingElement.getName().equals(HTML.Tag.HTML.toString())); return null; } /** * Get the unique string * @param strings * @param source * @return the unique string */ private static String[] getUniqueString(int strings, String source) { String[] result = new String[strings]; for (int i = 0; i < strings; i++) { boolean hit; String idString; int counter = 0; do { hit = false; idString = "diesisteineidzumsuchen" + counter + "#" + i; if (source.contains(idString)) { counter++; hit = true; if (counter > 10000) { return null; } } } while (hit); result[i] = idString; } return result; } /** * Method for returning a ViewFactory which handles the image rendering. * @return ViewFactory */ @Override public ViewFactory getViewFactory() { return new HTMLFactoryExtended(); } /** * Check the parents tag * @param e * @param tag * @return true if the tag equals the element */ public static boolean checkParentsTag(Element e, HTML.Tag tag) { if (e.getName().equalsIgnoreCase(tag.toString())) { return true; } Element workingElement = e; do { workingElement = workingElement.getParentElement(); if (workingElement.getName().equalsIgnoreCase(tag.toString())) { return true; } } while (!workingElement.getName().equalsIgnoreCase("html")); return false; } /* WACKY GERMAN CODE */ @Override public Document createDefaultDocument() { StyleSheet styles = getStyleSheet(); StyleSheet ss = new StyleSheet(); ss.addStyleSheet(styles); ExtendedHTMLDocument doc = new ExtendedHTMLDocument(ss); doc.setParser(getParser()); doc.setAsynchronousLoadPriority(4); doc.setTokenThreshold(100); return doc; } /** * Delete * @param pane * @throws BadLocationException */ public static void delete(JTextPane pane) throws BadLocationException { ExtendedHTMLDocument htmlDoc = (ExtendedHTMLDocument) pane.getStyledDocument(); int selStart = pane.getSelectionStart(); int selEnd = pane.getSelectionEnd(); String[] posStrings = getUniqueString(2, pane.getText()); if (posStrings == null) { return; } htmlDoc.insertString(selStart, posStrings[0], null); htmlDoc.insertString(selEnd + posStrings[0].length(), posStrings[1], null); int start = pane.getText().indexOf(posStrings[0]); int end = pane.getText().indexOf(posStrings[1]); if ((start == -1) || (end == -1)) { return; } String htmlString = pane.getText().substring(0, start); htmlString += pane.getText().substring(start + posStrings[0].length(), end); htmlString += pane.getText().substring(end + posStrings[1].length(), pane.getText().length()); String source = htmlString; end -= posStrings[0].length(); htmlString = source.substring(0, start); htmlString += getAllTableTags(source.substring(start, end)); htmlString += source.substring(end, source.length()); pane.setText(htmlString); } /** * Insert a list element * @param pane * @param content */ public static void insertListElement(JTextPane pane, String content) { int pos = pane.getCaretPosition(); ExtendedHTMLDocument htmlDoc = (ExtendedHTMLDocument) pane.getStyledDocument(); String source = pane.getText(); boolean hit; String idString; int counter = 0; do { hit = false; idString = "diesisteineidzumsuchenimsource" + counter; if (source.contains(idString)) { counter++; hit = true; if (counter > 10000) { return; } } } while (hit); Element element = getListItemParent(htmlDoc.getCharacterElement(pane.getCaretPosition())); if (element == null) { return; } SimpleAttributeSet sa = new SimpleAttributeSet(element.getAttributes()); sa.addAttribute("id", idString); ((ExtendedHTMLDocument) pane.getStyledDocument()).replaceAttributes(element, sa, HTML.Tag.LI); source = pane.getText(); StringBuilder newHtmlString = new StringBuilder(); int[] positions = getPositions(element, source, true, idString); newHtmlString.append(source.substring(0, positions[3])); newHtmlString.append("<li>"); newHtmlString.append(content); newHtmlString.append("</li>"); newHtmlString.append(source.substring(positions[3] + 1, source.length())); pane.setText(newHtmlString.toString()); pane.setCaretPosition(pos - 1); element = getListItemParent(htmlDoc.getCharacterElement(pane.getCaretPosition())); sa = new SimpleAttributeSet(element.getAttributes()); sa = removeAttributeByKey(sa, "id"); ((ExtendedHTMLDocument) pane.getStyledDocument()).replaceAttributes(element, sa, HTML.Tag.LI); } /** * Remove arttribute * @param sourceAS * @param removeAS * @return the attribute set */ private static SimpleAttributeSet removeAttribute(SimpleAttributeSet sourceAS, SimpleAttributeSet removeAS) { try { String[] sourceKeys = new String[sourceAS.getAttributeCount()]; String[] sourceValues = new String[sourceAS.getAttributeCount()]; Enumeration<?> sourceEn = sourceAS.getAttributeNames(); int i = 0; while (sourceEn.hasMoreElements()) { Object temp; temp = sourceEn.nextElement(); sourceKeys[i] = temp.toString(); sourceValues[i] = ""; sourceValues[i] = sourceAS.getAttribute(temp).toString(); i++; } String[] removeKeys = new String[removeAS.getAttributeCount()]; String[] removeValues = new String[removeAS.getAttributeCount()]; Enumeration<?> removeEn = removeAS.getAttributeNames(); int j = 0; while (removeEn.hasMoreElements()) { removeKeys[j] = removeEn.nextElement().toString(); removeValues[j] = removeAS.getAttribute(removeKeys[j]).toString(); j++; } SimpleAttributeSet result = new SimpleAttributeSet(); for (int countSource = 0; countSource < sourceKeys.length; countSource++) { boolean hit = false; if ("name".equals(sourceKeys[countSource]) || "resolver".equals(sourceKeys[countSource])) { hit = true; } else { for (int countRemove = 0; countRemove < removeKeys.length; countRemove++) { if (!"NULL".equals(removeKeys[countRemove])) { if (sourceKeys[countSource].equals(removeKeys[countRemove])) { if (!"NULL".equals(removeValues[countRemove])) { if (sourceValues[countSource].equals(removeValues[countRemove])) { hit = true; } } else if ("NULL".equals(removeValues[countRemove])) { hit = true; } } } else if ("NULL".equals(removeKeys[countRemove]) && sourceValues[countSource].equals(removeValues[countRemove])) { hit = true; } } } if (!hit) { result.addAttribute(sourceKeys[countSource], sourceValues[countSource]); } } return result; } catch (ClassCastException cce) { return null; } } /** * Remove attribute by key * @param sourceAS * @param removeKey * @return attribute set */ private static SimpleAttributeSet removeAttributeByKey(SimpleAttributeSet sourceAS, String removeKey) { SimpleAttributeSet temp = new SimpleAttributeSet(); temp.addAttribute(removeKey, "NULL"); return removeAttribute(sourceAS, temp); } /** * Remove a tag * @param pane * @param element * @param closingTag */ public static void removeTag(JTextPane pane, Element element, boolean closingTag) { if (element == null) { return; } HTML.Tag tag = getHTMLTag(element); // Versieht den Tag mit einer einmaligen ID String source = pane.getText(); boolean hit; String idString; int counter = 0; do { hit = false; idString = "diesisteineidzumsuchenimsource" + counter; if (source.contains(idString)) { counter++; hit = true; if (counter > 10000) { return; } } } while (hit); SimpleAttributeSet sa = new SimpleAttributeSet(element.getAttributes()); sa.addAttribute("id", idString); ((ExtendedHTMLDocument) pane.getStyledDocument()).replaceAttributes(element, sa, tag); source = pane.getText(); StringBuilder newHtmlString = new StringBuilder(); int[] position = getPositions(element, source, closingTag, idString); if (position == null) { return; } for (final int aPosition : position) { if (aPosition < 0) { return; } } int beginStartTag = position[0]; int endStartTag = position[1]; if (closingTag) { int beginEndTag = position[2]; int endEndTag = position[3]; newHtmlString.append(source.substring(0, beginStartTag)); newHtmlString.append(source.substring(endStartTag, beginEndTag)); newHtmlString.append(source.substring(endEndTag, source.length())); } else { newHtmlString.append(source.substring(0, beginStartTag)); newHtmlString.append(source.substring(endStartTag, source.length())); } pane.setText(newHtmlString.toString()); } /** * Fetch a resource relative to the HTMLEditorKit classfile. If this is called * on 1.2 the loading will occur under the protection of a doPrivileged call * to allow the HTMLEditorKit to function when used in an applet. * * This method does not properly override its parent in JDK1.3 or JDK1.4 as * its parent has no explicit security on it (public, private or protected) * and this method is in a class in a package that is different to its parent. * * @param name the name of the resource, relative to the HTMLEditorKit class * @return a stream representing the resource */ static InputStream getResourceAsStream(final String name) { return ExtendedHTMLEditorKit.class.getResourceAsStream(name); } private static String getAllTableTags(String source) { StringBuilder result = new StringBuilder(); int caret = -1; do { caret++; int[] tableCarets = new int[6]; tableCarets[0] = source.indexOf("<table", caret); tableCarets[1] = source.indexOf("<tr", caret); tableCarets[2] = source.indexOf("<td", caret); tableCarets[3] = source.indexOf("</table", caret); tableCarets[4] = source.indexOf("</tr", caret); tableCarets[5] = source.indexOf("</td", caret); java.util.Arrays.sort(tableCarets); caret = -1; for (final int tableCaret : tableCarets) { if (tableCaret >= 0) { caret = tableCaret; break; } } if (caret != -1) { result.append(source.substring(caret, source.indexOf('>', caret) + 1)); } } while (caret != -1); return result.toString(); } private static int[] getPositions(Element element, String source, boolean closingTag, String idString) { HTML.Tag tag = getHTMLTag(element); int[] position = new int[4]; for (int i = 0; i < position.length; i++) { position[i] = -1; } String searchString = "<" + tag.toString(); int caret; if ((caret = source.indexOf(idString)) != -1) { position[0] = source.lastIndexOf('<', caret); position[1] = source.indexOf('>', caret) + 1; } if (closingTag) { String searchEndTagString = "</" + tag.toString() + ">"; int hitUp; int beginEndTag; int endEndTag; caret = position[1]; boolean end; beginEndTag = source.indexOf(searchEndTagString, caret); endEndTag = beginEndTag + searchEndTagString.length(); int interncaret = position[1]; do { int temphitpoint; boolean flaghitup; hitUp = 0; do { flaghitup = false; temphitpoint = source.indexOf(searchString, interncaret); if ((temphitpoint > 0) && (temphitpoint < beginEndTag)) { hitUp++; flaghitup = true; interncaret = temphitpoint + searchString.length(); } } while (flaghitup); if (hitUp == 0) { end = true; } else { for (int i = 1; i <= hitUp; i++) { caret = endEndTag; beginEndTag = source.indexOf(searchEndTagString, caret); endEndTag = beginEndTag + searchEndTagString.length(); } end = false; } } while (!end); if ((beginEndTag < 0) || (endEndTag < 0)) { return null; } position[2] = beginEndTag; position[3] = endEndTag; } return position; } /* Inner Classes --------------------------------------------- */ /** * Class that replaces the default ViewFactory and supports the proper * rendering of both URL-based and local images. */ private static class HTMLFactoryExtended extends HTMLFactory { /** * Method to handle IMG tags and invoke the image loader. * @param elem * @return View */ @Override public View create(Element elem) { Object obj = elem.getAttributes().getAttribute(StyleConstants.NameAttribute); if (obj instanceof HTML.Tag) { HTML.Tag tagType = (HTML.Tag) obj; if (tagType == HTML.Tag.IMG) { return new RelativeImageView(elem); } } return super.create(elem); } } /** * InsertListAction */ public static class InsertListAction extends InsertHTMLTextAction { private final HTML.Tag baseTag; /** * Action to insert a list * @param label * @param listType */ public InsertListAction(String label, HTML.Tag listType) { super(label, "", listType, HTML.Tag.LI); baseTag = listType; } @Override public void actionPerformed(ActionEvent ae) { try { JEditorPane editor = getEditor(ae); ExtendedHTMLDocument doc = (ExtendedHTMLDocument) editor.getDocument(); String selTextBase = editor.getSelectedText(); Element elem = doc.getParagraphElement(editor.getCaretPosition()); int textLength = -1; if (selTextBase != null) { textLength = selTextBase.length(); } if ((selTextBase == null) || (textLength < 1)) { if (!"newListPoint".equals(ae.getActionCommand())) { if (checkParentsTag(elem, HTML.Tag.OL) || checkParentsTag(elem, HTML.Tag.UL)) { //Can't have a multilevel list return; } } String sListType = ((baseTag == HTML.Tag.OL) ? "ol" : "ul"); StringBuilder sbNew = new StringBuilder(); if (checkParentsTag(elem, baseTag)) { sbNew.append("<li></li>"); insertHTML(editor, doc, editor.getCaretPosition(), sbNew.toString(), 0, 0, HTML.Tag.LI); } else { sbNew.append("<").append(sListType).append("><li></li></").append(sListType).append("><p> </p>"); insertHTML(editor, doc, editor.getCaretPosition(), sbNew.toString(), 0, 0, (sListType.equals("ol") ? HTML.Tag.OL : HTML.Tag.UL)); } } else { String sListType = ((baseTag == HTML.Tag.OL) ? "ol" : "ul"); HTMLDocument htmlDoc = (HTMLDocument) (editor.getDocument()); int iStart = editor.getSelectionStart(); int iEnd = editor.getSelectionEnd(); String selText = htmlDoc.getText(iStart, iEnd - iStart); StringBuilder sbNew = new StringBuilder(); String sToken = ((selText.contains("\r")) ? "\r" : "\n"); StringTokenizer stTokenizer = new StringTokenizer(selText, sToken); sbNew.append("<").append(sListType).append(">"); while (stTokenizer.hasMoreTokens()) { sbNew.append("<li>"); sbNew.append(stTokenizer.nextToken()); sbNew.append("</li>"); } sbNew.append("</").append(sListType).append("><p> </p>"); htmlDoc.remove(iStart, iEnd - iStart); insertHTML(editor, htmlDoc, iStart, sbNew.toString(), 1, 1, null); } //Refresh } catch (BadLocationException ble) { // TODO - Handle Exception } } } @Override public Action[] getActions() { return TextAction.augmentList(super.getActions(), ExtendedHTMLEditorKit.defaultActions); } private static final Action[] defaultActions = {new PasteAction() }; /** * PasteAction */ private static final class PasteAction extends TextAction { /** Create this object with the appropriate identifier. */ private PasteAction() { super(DefaultEditorKit.pasteAction); } /** * The operation to perform when this action is triggered. * * @param e * the action event */ @Override public void actionPerformed(final ActionEvent e) { JTextComponent target = getTextComponent(e); Clipboard clipboard = target.getToolkit().getSystemClipboard(); clipboard.getContents(null); Class<? extends JTextComponent> k = target.getClass(); try { BeanInfo bi = Introspector.getBeanInfo(k); bi.getPropertyDescriptors(); } catch (final IntrospectionException ex) { // TODO Handle this? } target.paste(); } } }