/*
* 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());
}
}
}
}
}
}