/*
* $Id$
*
* Copyright (c) 2000-2003 by Rodney Kinney
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.build.widget;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.ComponentView;
import javax.swing.text.Element;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import VASSAL.build.BadDataReport;
import VASSAL.build.Buildable;
import VASSAL.build.GameModule;
import VASSAL.build.Widget;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.i18n.Resources;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.ReadErrorDialog;
import VASSAL.tools.ScrollPane;
import VASSAL.tools.imageop.Op;
import VASSAL.tools.imageop.OpIcon;
import VASSAL.tools.imageop.SourceOp;
import VASSAL.tools.io.IOUtils;
/**
* An HtmlChart is used for displaying html information for the module. The
* charts are loaded as html files stored in the DataArchive. As a subclass of
* Widget, a Chart may be added to any Widget, but it may not contain children
* of its own
*/
public class HtmlChart extends Widget implements MouseListener {
public static final String NAME = "chartName";
public static final String FILE = "fileName";
private String fileName;
private JScrollPane scroller;
private JEditorPane htmlWin;
public HtmlChart() {
}
private boolean isURL() {
return htmlWin.getDocument().getProperty("stream") != null;
}
private void setText(String text) {
htmlWin.setText(text);
// ensure hyperlink engine knows we are no longer at the last URL
htmlWin.getDocument().putProperty("stream", null);
htmlWin.revalidate();
}
private void setFile(String fname) {
setText(getFile(fname));
}
private String getFile(final String fname) {
if (fname == null) return null;
String s = null;
InputStream in = null;
try {
in = new BufferedInputStream(
GameModule.getGameModule().getDataArchive().getInputStream(fname));
s = IOUtils.toString(in);
in.close();
}
catch (IOException e) {
ErrorDialog.dataError(new BadDataReport(this, Resources.getString("Error.not_found", "Chart"),fname,e));
}
finally {
IOUtils.closeQuietly(in);
}
return s;
}
// Warning: Creating a JEditorPane with a "jar" url or using setPage()
// with a jar URL will leave a resource open in the MOD file, making it
// impossible to save or rename it. This might be acceptable for people
// playing a module, but is unacceptable for editors; they can only save
// their work to a new file. Therefore, we read the entire file instead
// of simply using:
// GameModule.getGameModule().getDataArchive().getURL( fileName );
public Component getComponent() {
if (htmlWin == null) {
htmlWin = new JEditorPane();
htmlWin.setEditable(false);
htmlWin.setContentType("text/html");
XTMLEditorKit myHTMLEditorKit = new XTMLEditorKit();
htmlWin.setEditorKit(myHTMLEditorKit);
htmlWin.addHyperlinkListener(new HtmlChartHyperlinkListener());
htmlWin.addMouseListener(this);
setFile(fileName);
scroller = new ScrollPane(htmlWin);
scroller.getViewport().setPreferredSize(htmlWin.getPreferredSize());
scroller.getViewport().setAlignmentY(0.0F);
}
return scroller;
}
public String getFileName() {
return fileName;
}
public void addTo(Buildable parent) {
}
public static String getConfigureTypeName() {
return "HTML Chart";
}
public void removeFrom(Buildable parent) {
}
public HelpFile getHelpFile() {
return HelpFile.getReferenceManualPage("ChartWindow.htm", "HtmlChart");
}
public void setAttribute(String key, Object val) {
if (NAME.equals(key)) {
setConfigureName((String) val);
}
else if (FILE.equals(key)) {
if (val instanceof File) {
val = ((File) val).getName();
}
fileName = (String) val;
if (htmlWin != null) {
setFile(fileName);
}
}
}
public Class<?>[] getAllowableConfigureComponents() {
return new Class<?>[0];
}
/**
* The Attributes of a Chart are:
* <dl>
* <dt><code>NAME</code></dt><dd>for the name of the chart</dd>
* <dt><code>FILE</code></dt><dd>for the name of the HTML file
* in the {@link VASSAL.tools.DataArchive}</dd>
* </dl>
*/
public String[] getAttributeNames() {
return new String[]{NAME, FILE};
}
public String[] getAttributeDescriptions() {
return new String[]{"Name: ", "HTML File: "};
}
public Class<?>[] getAttributeTypes() {
return new Class<?>[]{String.class, File.class};
}
public String getAttributeValueString(String name) {
if (NAME.equals(name)) {
return getConfigureName();
}
else if (FILE.equals(name)) {
return fileName;
}
return null;
}
public void mousePressed(MouseEvent event) {
if (event.isMetaDown()) {
final JPopupMenu popup = new JPopupMenu();
final JMenuItem item = new JMenuItem("Return to default page");
item.addActionListener(new ActionListener() {
// Return to default page
public void actionPerformed(ActionEvent e) {
setFile(fileName);
}
});
popup.add(item);
if (event.getComponent().isShowing()) {
popup.show(event.getComponent(), event.getX(), event.getY());
}
}
}
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public class HtmlChartHyperlinkListener implements HyperlinkListener {
public void hyperlinkUpdate(HyperlinkEvent event) {
if (event.getEventType() != HyperlinkEvent.EventType.ACTIVATED) {
return;
}
final String desc = event.getDescription();
if ((!isURL() && desc.indexOf('/') < 0) || event.getURL() == null) {
final int hash = desc.lastIndexOf("#");
if (hash < 0) {
// no anchor
setFile(desc);
}
else if (hash > 0) {
// browse to the part before the anchor
setFile(desc.substring(0, hash));
}
if (hash != -1) {
// we have an anchor
htmlWin.scrollToReference(desc.substring(hash+1));
}
}
else {
try {
htmlWin.setPage(event.getURL());
}
catch (IOException ex) {
ReadErrorDialog.error(ex, event.getURL().toString());
}
htmlWin.revalidate();
}
}
}
/**
* Extended HTML Editor kit to extend the <src> tag to display images
* from the module DataArchive where no pathname included in the image name.
* The image is placed on a label and returned as a ComponentView. An
* ImageView cannot be used as the standard Java HTML Renderer can only
* display Images from an external URL.
*/
public static class XTMLEditorKit extends HTMLEditorKit {
private static final long serialVersionUID = 1L;
public ViewFactory getViewFactory() {
return new XTMLFactory();
}
public static class XTMLFactory extends HTMLFactory implements ViewFactory {
public XTMLFactory() {
super();
}
public View create(javax.swing.text.Element element) {
final HTML.Tag kind = (HTML.Tag) (element.getAttributes().getAttribute(javax.swing.text.StyleConstants.NameAttribute));
if (kind instanceof HTML.Tag && element.getName().equals("img")) {
final String imageName = (String) element.getAttributes().getAttribute(HTML.Attribute.SRC);
if (imageName.indexOf("/") < 0) {
return new ImageComponentView(element);
}
}
return super.create(element);
}
public static class ImageComponentView extends ComponentView {
protected String imageName;
protected SourceOp srcOp;
/**
* Very basic Attribute handling only. Expand as needed.
*/
public ImageComponentView(Element e) {
super(e);
imageName = (String) e.getAttributes()
.getAttribute(HTML.Attribute.SRC);
srcOp = imageName == null || imageName.trim().length() == 0
? null : Op.load(imageName);
}
protected Component createComponent() {
final JLabel label = new JLabel();
label.setIcon(new OpIcon(srcOp));
return label;
}
}
}
}
}