/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.ui.htmlframe; import java.awt.AWTEventMulticaster; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.io.StringReader; import java.util.Enumeration; import java.util.Stack; import java.util.regex.Matcher; import javax.swing.JEditorPane; import javax.swing.ToolTipManager; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.MutableAttributeSet; 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 org.apache.log4j.Logger; import com.t3.client.AppPreferences; import com.t3.client.TabletopTool; import com.t3.client.ui.commandpanel.MessagePanel; import com.t3.macro.MacroException; import com.t3.macro.api.views.MacroButtonView; @SuppressWarnings("serial") public class HTMLPane extends JEditorPane { private static final Logger log = Logger.getLogger(HTMLPane.class); private ActionListener actionListeners; private final HTMLPaneEditorKit editorKit; public HTMLPane() { editorKit = new HTMLPaneEditorKit(this); setEditorKit(editorKit); setContentType("text/html"); setEditable(false); addHyperlinkListener(new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { if (log.isDebugEnabled()) { log.debug("Responding to hyperlink event: " + e.getEventType().toString() + " " + e.toString()); } if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { if (e.getURL() != null) { TabletopTool.showDocument(e.getURL().toString()); } else { Matcher m = MessagePanel.URL_PATTERN.matcher(e.getDescription()); if (m.matches()) { if (m.group(1).equalsIgnoreCase("macro")) { try { MacroButtonView.executeLink(e.getDescription()); } catch (MacroException e1) { //FIXME here should be macro error handling throw new RuntimeException(e1); } } } } } } }); ToolTipManager.sharedInstance().registerComponent(this); } public void addActionListener(ActionListener listener) { actionListeners = AWTEventMulticaster.add(actionListeners, listener); } public void removeActionListener(ActionListener listener) { actionListeners = AWTEventMulticaster.remove(actionListeners, listener); } /** * Handle a submit. * * @param method * The method of the submit. * @param action * The action for the submit. * @param data * The data from the form. */ void doSubmit(String method, String action, String data) { if (actionListeners != null) { if (log.isDebugEnabled()) { log.debug("submit event: method='" + method + "' action='" + action + "' data='" + data + "'"); } actionListeners.actionPerformed(new FormActionEvent(method, action, data)); } } /** * Handle a change in title. * * @param title * The title to change to. */ private void doChangeTitle(String title) { if (actionListeners != null) { if (log.isDebugEnabled()) { log.debug("changeTitle event: " + title); } actionListeners.actionPerformed(new ChangeTitleActionEvent(title)); } } /** * Handle a request to register a macro callback. * * @param type * The type of event. * @param link * The link to the macro. */ private void doRegisterMacro(String type, String link) { if (actionListeners != null) { if (log.isDebugEnabled()) { log.debug("registerMacro event: type='" + type + "' link='" + link + "'"); } actionListeners.actionPerformed(new RegisterMacroActionEvent(type, link)); } } /** * Handle any meta tag information in the html. * * @param name * the name of the meta tag. * @param content * the content of the meta tag. */ private void handleMetaTag(String name, String content) { if (actionListeners != null) { if (log.isDebugEnabled()) { log.debug("metaTag found: name='" + name + "' content='" + content + "'"); } actionListeners.actionPerformed(new MetaTagActionEvent(name, content)); } } @Override public void setText(String text) { // Set up the default style sheet HTMLDocument document = (HTMLDocument) getDocument(); StyleSheet style = document.getStyleSheet(); HTMLEditorKit.Parser parse = editorKit.getParser(); try { super.setText(""); Enumeration<?> snames = style.getStyleNames(); while (snames.hasMoreElements()) { style.removeStyle(snames.nextElement().toString()); } style.addRule("body { font-family: sans-serif; font-size: " + AppPreferences.getFontSize() + "pt; background: #ECE9D8}"); style.addRule("div {margin-bottom: 5px}"); style.addRule("span.roll {background:#efefef}"); parse.parse(new StringReader(text), new ParserCallBack(), true); } catch (IOException e) { // Do nothing, we should not get an io exception on string } if (log.isDebugEnabled()) { log.debug("setting text in HTMLPane: " + text); } // We use ASCII control characters to mark off the rolls so that there's no limitation on what (printable) characters the output can include text = text.replaceAll("\036([^\036\037]*)\037([^\036]*)\036", "<span class='roll' title='« $1 »'>$2</span>"); text = text.replaceAll("\036\01u\02([^\036]*)\036", "« $1 »"); text = text.replaceAll("\036([^\036]*)\036", "«<span class='roll' style='color:blue'> $1 </span>»"); // Auto inline expansion text = text.replaceAll("(^|\\s)(https?://[\\w.%-/~?&+#=]+)", "$1<a href='$2'>$2</a>"); super.setText(text); } /** * Class that listens for form events. * */ public class FormActionEvent extends ActionEvent { private final String method; private final String action; private final String data; private FormActionEvent(String method, String action, String data) { super(HTMLPane.this, 0, "submit"); this.method = method; this.action = action; if (method.equals("json")) { this.data = data; } else { this.data = data.replace("%0A", "%20"); // String properties can not handle \n in strings. // XXX Shouldn't we warn the MTscript programmer somehow? } } public String getMethod() { return method; } public String getAction() { return action; } public String getData() { return data; } } /** * Action event for changing title of the container. */ public class ChangeTitleActionEvent extends ActionEvent { private final String newTitle; public ChangeTitleActionEvent(String title) { super(HTMLPane.this, 0, "changeTitle"); newTitle = title; } /** * Gets the new title. * * @return */ public String getNewTitle() { return newTitle; } } public class MetaTagActionEvent extends ActionEvent { private final String name; private final String content; public MetaTagActionEvent(String name, String content) { super(HTMLPane.this, 0, "metaTag"); this.name = name; this.content = content; } /** * Gets the name of the meta tag. * * @return the name of the meta tag. */ public String getName() { return name; } /** * Gets the content for the meta tag. * * @return the content of the meta tag. */ public String getContent() { return content; } } /** * Action event for registering a macro */ public class RegisterMacroActionEvent extends ActionEvent { private final String type; private final String macro; RegisterMacroActionEvent(String type, String macro) { super(HTMLPane.this, 0, "registerMacro"); this.type = type; this.macro = macro; } /** * Gets the type of macro to register. * * @return the type of macro. */ public String getType() { return type; } /** * Gets the link to the macro. * * @return the link to the macro. */ public String getMacro() { return macro; } } /** * Class that deals with html parser callbacks. */ class ParserCallBack extends HTMLEditorKit.ParserCallback { private final Stack<HTML.Tag> tagStack = new Stack<HTML.Tag>(); @Override public void handleStartTag(HTML.Tag tag, MutableAttributeSet attributes, int position) { tagStack.push(tag); if (tag == HTML.Tag.LINK) { handleLinkTag(attributes); } else if (tag == HTML.Tag.META) { handleMetaTag(attributes); } } @Override public void handleEndTag(HTML.Tag tag, int position) { tagStack.pop(); } @Override public void handleText(char[] text, int position) { if (tagStack.peek() == HTML.Tag.TITLE) { doChangeTitle(String.valueOf(text)); } } @Override public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes, int pos) { if (tag == HTML.Tag.LINK) { handleLinkTag(attributes); } else if (tag == HTML.Tag.META) { handleMetaTag(attributes); } } @Override public void handleError(String errorMsg, int pos) { if (log.isTraceEnabled()) { log.trace("handleError called in client.ui.htmlframe.HTMLPane.ParserCallBack: " + errorMsg); } } /** * Handles meta tags. * * @param attributes * the attributes for the tag. */ void handleMetaTag(MutableAttributeSet attributes) { Object name = attributes.getAttribute(HTML.Attribute.NAME); Object content = attributes.getAttribute(HTML.Attribute.CONTENT); if (name != null && content != null) { HTMLPane.this.handleMetaTag(name.toString(), content.toString()); } } /** * Handles all the actions for a HTML Link tag. * * @param attributes * The attributes for the tag. */ void handleLinkTag(MutableAttributeSet attributes) { Object rel = attributes.getAttribute(HTML.Attribute.REL); Object type = attributes.getAttribute(HTML.Attribute.TYPE); Object href = attributes.getAttribute(HTML.Attribute.HREF); if (rel != null && type != null && href != null) { if (rel.toString().equalsIgnoreCase("stylesheet")) { /*String[] vals = href.toString().split("@"); if (vals.length != 2) { return; } try { String cssText = TabletopTool.getParser().getTokenLibMacro(vals[0], vals[1]); HTMLDocument document = (HTMLDocument) getDocument(); StyleSheet style = document.getStyleSheet(); style.loadRules(new StringReader(cssText), null); } catch (IOException e) { // Do nothing }*/ //this needs to be fixed. Find out what this meant throw new UnsupportedOperationException("Not implemented yet"); } else if (type.toString().equalsIgnoreCase("macro")) { if (rel.toString().equalsIgnoreCase("onChangeImpersonated")) { doRegisterMacro("onChangeImpersonated", href.toString()); } else if (rel.toString().equalsIgnoreCase("onChangeSelection")) { doRegisterMacro("onChangeSelection", href.toString()); } else if (rel.toString().equalsIgnoreCase("onChangeToken")) { doRegisterMacro("onChangeToken", href.toString()); } } } } } }