/*
* HtmlSheetSupport.java
* Copyright 2011 Connor Petty <cpmeister@users.sourceforge.net>
*
* 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
*
* Created on Mar 10, 2011, 1:35:43 PM
*/
package pcgen.gui2.tabs.models;
import java.awt.Image;
import java.awt.Toolkit;
import java.io.BufferedWriter;
import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import javax.swing.ImageIcon;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.EditorKit;
import javax.swing.text.html.HTMLDocument;
import org.apache.commons.lang3.StringUtils;
import pcgen.base.lang.UnreachableError;
import pcgen.facade.core.CharacterFacade;
import pcgen.io.ExportHandler;
import pcgen.util.Logging;
/**
*
* @author Connor Petty <cpmeister@users.sourceforge.net>
*/
public class HtmlSheetSupport
{
private static final ThreadFactory threadFactory = new ThreadFactory()
{
@Override
public Thread newThread(Runnable r)
{
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("html-sheet-thread");
return thread;
}
};
private ExecutorService executor = Executors.newSingleThreadExecutor(threadFactory);
private WeakReference<CharacterFacade> characterRef;
private final File templateFile;
private final JEditorPane htmlPane;
private ImageCache cache = new ImageCache();
private FutureTask<HTMLDocument> refresher = null;
private boolean installed = false;
private String missingSheetMsg;
public HtmlSheetSupport(JEditorPane htmlPane, String infoSheetFile)
{
if (!StringUtils.isEmpty(infoSheetFile))
{
templateFile = new File(infoSheetFile);
}
else
{
templateFile = null;
}
this.htmlPane = htmlPane;
}
public HtmlSheetSupport(CharacterFacade character, JEditorPane htmlPane, String infoSheetFile)
{
this(htmlPane, infoSheetFile);
setCharacter(character);
}
public void setCharacter(CharacterFacade character)
{
this.characterRef = new WeakReference<>(character);
}
public void install()
{
installed = true;
refresh();
}
public void uninstall()
{
installed = false;
}
public void refresh()
{
if (templateFile == null)
{
htmlPane.setText(missingSheetMsg);
return;
}
if (characterRef == null || characterRef.get() == null)
{
return;
}
if (refresher != null && !refresher.isDone())
{
refresher.cancel(false);
}
refresher = new Refresher();
executor.execute(refresher);
}
public void setMissingSheetMsg(String missingSheetMsg)
{
this.missingSheetMsg = missingSheetMsg;
}
private class Refresher extends FutureTask<HTMLDocument>
{
public Refresher()
{
super(new DocumentBuilder());
}
@Override
protected void done()
{
if (!installed || isCancelled())
{
return;
}
try
{
final HTMLDocument doc = get();
SwingUtilities.invokeAndWait(new Runnable()
{
@Override
public void run()
{
htmlPane.setDocument(doc);
}
});
}
catch (InvocationTargetException ex)
{
throw new UnreachableError();
}
catch (InterruptedException ex)
{
Logging.errorPrint(templateFile.getName(), ex);
}
catch (ExecutionException ex)
{
Logging.errorPrint(templateFile.getName(), ex.getCause());
}
}
}
private class DocumentBuilder implements Callable<HTMLDocument>
{
@Override
public HTMLDocument call() throws Exception
{
StringWriter writer = new StringWriter();
characterRef.get().export(new ExportHandler(templateFile), new BufferedWriter(writer));
StringReader reader = new StringReader(writer.toString());
EditorKit kit = htmlPane.getEditorKit();
HTMLDocument doc = new HTMLDocument();
doc.setBase(templateFile.getParentFile().toURI().toURL());
doc.putProperty("IgnoreCharsetDirective", true);
// XXX - This is a hack specific to Sun's JDK 5.0 and in no
// way should be trusted to work in future java releases
// (though it still might) - Connor Petty
doc.putProperty("imageCache", cache);
kit.read(reader, doc, 0);
return doc;
}
}
// private class DocumentBuilder implements Callable<HTMLDocument>, Runnable
// {
//
// private PipedWriter writer = new PipedWriter();
//
// public HTMLDocument call() throws Exception
// {
// PipedReader reader = new PipedReader(writer);
// createWriterThread().start();
//
// EditorKit kit = htmlPane.getEditorKit();
// HTMLDocument doc = new HTMLDocument();
// doc.setBase(templateFile.getParentFile().toURL());
// // XXX - This is a hack specific to Sun's JDK 5.0 and in no
// // way should be trusted to work in future java releases
// // (though it still might) - Connor Petty
// doc.putProperty("imageCache", cache);
// kit.read(reader, doc, 0);
// reader.close();
// return doc;
// }
//
// private Thread createWriterThread()
// {
// Thread thread = new Thread(this);
// thread.setName("html-sheet-thread-2");
// thread.setDaemon(true);
// thread.setPriority(Thread.NORM_PRIORITY);
// return thread;
// }
//
// public void run()
// {
// BufferedWriter bw = new BufferedWriter(writer, 1);
// try
// {
// character.export(new ExportHandler(templateFile), bw);
// }
// finally
// {
// try
// {
// bw.close();
// }
// catch (IOException ex)
// {
// Logging.errorPrint("Unable to close PipedWriter", ex);
// }
// }
// }
//
// }
/**
* A cache for images loaded onto the info pane.
*/
private static class ImageCache extends Dictionary<URL, Image>
{
private HashMap<URL, Image> cache = new HashMap<>();
@Override
public int size()
{
return cache.size();
}
@Override
public boolean isEmpty()
{
return cache.isEmpty();
}
@Override
public Enumeration<URL> keys()
{
return Collections.enumeration(cache.keySet());
}
@Override
public Enumeration<Image> elements()
{
return Collections.enumeration(cache.values());
}
@Override
public Image get(Object key)
{
if (!(key instanceof URL))
{
return null;
}
URL src = (URL) key;
if (!cache.containsKey(src))
{
Image newImage = Toolkit.getDefaultToolkit().createImage(src);
if (newImage != null)
{
// Force the image to be loaded by using an ImageIcon.
ImageIcon ii = new ImageIcon();
ii.setImage(newImage);
}
cache.put(src, newImage);
}
return cache.get(src);
}
@Override
public Image put(URL key, Image value)
{
return cache.put(key, value);
}
@Override
public Image remove(Object key)
{
return cache.remove(key);
}
}
}