package jas2.swingstudio;
import jas2.plot.*;
import jas2.bean.*;
import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import jas2.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
import java.lang.reflect.*;
import java.beans.*;
public class HTMLPage extends JPanel implements HasCommandProcessor, HasPopupItems, Closable
{
HTMLPage(URL homePage)
{
super(new BorderLayout());
// Here's the editor pane configuration. It's important to make
// the "setEditable(false)" call, otherwise our hyperlinks won't
// work. (If the text is editable, then clicking on a hyperlink
// simply means that you want to change the text...not follow the
// link.)
jep.setEditable(false);
jep.setMargin(new Insets(0,0,0,0));
// Here's where we force the pane to use our new editor kit
jep.setEditorKitForContentType("text/html", editorKit = new JASHTMLEditorKit(this));
urlHistory = new URLHistory(homePage);
try
{
jep.setPage(homePage);
}
catch(Exception x)
{
app.error("Could not open home page",x);
}
JScrollPane jsp = new JScrollPane(jep);
// Speed up window scrolling -- see http://java.sun.com/products/jfc/tsc/articles/performance/index.html
jsp.getViewport().putClientProperty("EnableWindowBlit", Boolean.TRUE);
add(jsp,BorderLayout.CENTER);
// and last but not least, hook up our event handlers
jep.addHyperlinkListener(new SimpleLinkListener());
jep.addMouseListener(new jas2.plot.PopupMenuBuilder());
jep.addCaretListener(htmlCommand);
}
/**
* Called by the window manager when this page is being nuked
*/
public boolean pleaseClose()
{
// Tell the editor kit we are beign destroyed.
editorKit.destroy();
return true;
}
public void pageSelected(boolean state)
{
}
public CommandProcessor getCommandProcessor()
{
return htmlCommand;
}
private JASHTMLEditorKit editorKit;
private JEditorPane jep = new JEditorPane();
private HTMLCommand htmlCommand = new HTMLCommand();
private URLHistory urlHistory;
private JavaAnalysisStudio app = JavaAnalysisStudio.getApp();
private class HTMLCommand extends JASCommandProcessor implements CaretListener
{
public void onPrint() throws Exception
{
PrintHelper ph = PrintHelper.instance();
ph.printTarget(jep);
}
public void onRefresh() throws IOException
{
urlHistory.reload();
}
public void onBack() throws IOException
{
urlHistory.goBack();
setChanged();
}
public void onForward() throws IOException
{
urlHistory.goForward();
setChanged();
}
public void enableBack(JASState state)
{
state.setEnabled(urlHistory.enableBack());
}
public void enableForward(JASState state)
{
state.setEnabled(urlHistory.enableForward());
}
public void onCopy()
{
jep.copy();
}
public void enableCopy(JASState state)
{
state.setEnabled(jep.getSelectedText() != null);
}
public void setChanged()
{
super.setChanged();
}
public void caretUpdate(CaretEvent e)
{
setChanged(); // Selection changed, so enabledCopy
}
}
private class SimpleLinkListener implements HyperlinkListener
{
public void hyperlinkUpdate(HyperlinkEvent he)
{
/*
* TODO: Framesets
* We dont seem to get called when the mouse passes over links in a
* frameset, nor do links within frames get included properly in the
* history (Back will take you back to before the frameset was entered)
*/
HyperlinkEvent.EventType type = he.getEventType();
// Ok. Decide which event we got...
if (type == HyperlinkEvent.EventType.ENTERED)
{
URL url = he.getURL();
if (url != null)
{
jep.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
app.setMessage(url.toString());
}
}
else if (type == HyperlinkEvent.EventType.EXITED)
{
jep.setCursor(Cursor.getDefaultCursor());
app.setMessage(" ");
}
else
{
if (he instanceof HTMLFrameHyperlinkEvent)
{
HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) he;
HTMLDocument doc = (HTMLDocument) jep.getDocument();
doc.processHTMLFrameHyperlinkEvent(evt);
}
else
{
showURL(he.getURL());
}
}
}
}
void showURL(URL url)
{
SwingUtilities.invokeLater(new PageLoader(url));
}
public void modifyPopupMenu(JPopupMenu menu, Component source)
{
if (menu.getComponentCount() > 0) menu.addSeparator();
//TODO: Should be built from menus.xml
menu.add(new JASMenuItem("Copy",'C'));
menu.addSeparator();
menu.add(new JASMenuItem("Home", 'H'));
menu.add(new JASMenuItem("Back", 'B'));
menu.add(new JASMenuItem("Forward", 'F'));
menu.add(new JASMenuItem("Refresh", 'R'));
menu.add(new JASMenuItem("Web Page...",'W'));
//menu.addSeparator();
//menu.add(new JASMenuItem("Print...",'P')); // Awaits better HTML printing support
}
private class URLHistory extends Vector
/*
* This class serves two similar but distinct purposes. They are
* similar in that they both store past URLs.
* One function is to provide for back/forward operations. The
* "back" command returns the user to the page that was open previous
* to the current page. The "forward" operation is not so straightforward.
* It returns the user to the page that was most recently read after the
* current page. For example, if the user navigates from site A to site
* B to site C, "back" will return the user to site B and to site A. Now
* if the user navigates to site D, then "forward" from site A is site D, not
* site B or site C.
* The other function is to provide a history. That's pretty
* straightforward too. It is just a list of sites in reverse order from when
* they were visited. Use popupMenu(URLHistory.elements()) to get the
* fully operational history menu.
*/
{
URLHistory(URL root)
{
m_current = new ListItem(root);
recordInHistory(root);
}
private void clear(ListItem i)
// The idea here is to help finalization by clearing unnecessary pointers.
{
if (i == null) return;
clear(i.next);
i.next = i.prev = null;
i.url = null;
}
void recordVisit(URL url)
{
if (m_current.next == null || !m_current.next.url.equals(url))
// if this page is different from what was previously stored in next
{
clear(m_current.next);
// deletes all pointers in that object and in all "next" objects
( m_current.next = new ListItem(url) ).prev = m_current;
// Requires explanation?
// a) The new item is m_current.next
// b) .prev is m_current
}
m_current = m_current.next;
// c) m_current is the new item
recordInHistory(url);
}
private void recordInHistory(URL url)
{
if (contains(url)) removeElement(url); // if it is there it will be moved to the beginning
insertElementAt(url, 0); // put at beginning
}
boolean enableForward()
{
return m_current.next != null;
}
void goForward() throws IOException
{
jep.setPage((m_current = m_current.next).url);
}
boolean enableBack()
{
return m_current.prev != null;
}
void goBack() throws IOException
{
jep.setPage((m_current = m_current.prev).url);
}
void reload() throws IOException
{
// This should force a reload
jep.getDocument().putProperty(Document.StreamDescriptionProperty,null);
jep.setPage(m_current.url);
}
boolean enableHistory()
{
return size() > 0;
}
private ListItem m_current;
private class ListItem
{
ListItem(URL url)
{
this.url = url;
}
URL url;
ListItem next, prev;
}
}
private class HistoryItem extends JMenuItem
{
HistoryItem(URL url)
{
super(url.toString());
this.url = url;
}
protected void fireActionPerformed(ActionEvent e)
{
showURL(url);
super.fireActionPerformed(e);
}
private URL url;
}
/**
* temporary class that loads synchronously (although
* later than the request so that a cursor change
* can be done).
*/
class PageLoader implements Runnable
{
PageLoader(URL u)
{
url = u;
cursor = jep.getCursor();
jep.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
public void run()
{
try
{
jep.setPage(url);
urlHistory.recordVisit(url);
htmlCommand.setChanged();
}
catch (IOException x)
{
app.error("Could not load page "+url,x);
}
finally
{
jep.setCursor(cursor);
}
}
private URL url;
private Cursor cursor;
}
}
class JASHTMLEditorKit extends HTMLEditorKit
{
JASHTMLEditorKit(HTMLPage page)
{
super();
this.page = page;
}
void destroy()
{
if (context != null) context.destroy();
}
public Document createDefaultDocument()
{
Document doc = super.createDefaultDocument();
if (context != null) context.destroy();
context = new JASPageContext((HTMLDocument) doc,page);
return doc;
}
public ViewFactory getViewFactory()
{
return new JASHTMLFactory(context);
}
private HTMLPage page;
private JASPageContext context;
}
class JASPageContext extends Hashtable implements PageContext
{
JASPageContext(HTMLDocument doc, HTMLPage page)
{
this.doc = doc;
this.page = page;
}
public Object getBean(String name)
{
// it seems the HTMLPage lowercases all the bean id's??
return get(name.toLowerCase());
}
public Enumeration getBeans()
{
return elements();
}
public void showStatus(String message)
{
JavaAnalysisStudio.getApp().setMessage(message);
}
public void showDocument(URL url)
{
page.showURL(url);
}
public URL getDocumentBase()
{
return doc.getBase();
}
void addBean(Object name, Object bean) throws Exception
{
if (name == null) name = bean; // name may be null if no ObjectID specified
put(name,bean);
if (bean instanceof PageContextHook) ((PageContextHook) bean).init(this);
}
void destroy()
{
Enumeration e = elements();
while (e.hasMoreElements())
{
Object bean = e.nextElement();
if (bean instanceof PageContextHook)
((PageContextHook) bean).destroy();
}
clear();
}
private HTMLPage page;
private HTMLDocument doc;
}
class JASHTMLFactory extends HTMLEditorKit.HTMLFactory
{
JASHTMLFactory(JASPageContext context)
{
this.context = context;
}
public View create(Element elem)
{
Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
if (o == HTML.Tag.OBJECT)
{
return new JASObjectView(elem,context);
}
return super.create(elem);
}
private JASPageContext context;
}
/**
* This class adds some extra functionality over the standard ObjectView
* specified in javax.swing.text.html.ObjectView. A lot of the code had
* to be copied here, so we should check to see if the original ObjectView
* has been updated sometime.
*
* The changes are:
* 1) allow bean properties that are not simple strings
* 2) keep a list of objects within a document, so that we can inform them
* of their demise when the document is no longer active
*/
class JASObjectView extends ObjectView
{
JASObjectView(Element elem,JASPageContext context)
{
super(elem);
this.context = context;
}
/**
* Create the component. The classid is used
* as a specification of the classname, which
* we try to load.
*/
protected Component createComponent()
{
AttributeSet attr = getElement().getAttributes();
String classname = (String) attr.getAttribute(HTML.Attribute.CLASSID);
String name = (String) attr.getAttribute(HTML.Attribute.ID);
try
{
Class c = getClass(classname);
Object o = c.newInstance();
if (o instanceof Component)
{
Component comp = (Component) o;
setParameters(comp, attr);
context.addBean(name,o);
return comp;
}
else throw new RuntimeException("Class "+classname+" is not a Component");
}
catch (Throwable e)
{
return getUnloadableRepresentation(e);
}
}
/**
* Fetch a component that can be used to represent the
* object if it can't be created.
*/
Component getUnloadableRepresentation(final Throwable t)
{
JButton bomb = new JButton("Error...",JASIcon.create(this,"images/Bomb.gif"))
{
protected void fireActionPerformed(ActionEvent e)
{
Application.getApplication().error("Error loading object",t);
}
};
bomb.setToolTipText("Click on Bomb to see error");
return bomb;
}
/**
* Get a Class object to use for loading the
* classid. If possible, the Classloader
* used to load the associated Document is used.
* This would typically be the same as the ClassLoader
* used to load the EditorKit. If the documents
* ClassLoader is null,
* Class.forName is used.
*/
private Class getClass(String className) throws ClassNotFoundException
{
Class docClass = getDocument().getClass();
ClassLoader loader = docClass.getClassLoader();
try
{
if (loader != null)
{
return loader.loadClass(className);
}
else
{
return Class.forName(className);
}
}
catch (ClassNotFoundException x)
{
JASJob jj = JavaAnalysisStudio.getApp().getJob();
if (jj == null) throw x;
return jj.loadClientClass(className);
}
}
/**
* Initialize this component according the KEY/VALUEs passed in
* via the <param> elements in the corresponding
* <object> element.
*/
private void setParameters(Component comp, AttributeSet attr)
{
Class k = comp.getClass();
BeanInfo bi;
try
{
bi = Introspector.getBeanInfo(k);
}
catch (IntrospectionException ex)
{
System.err.println("introspector failed, ex: "+ex);
return; // quit for now
}
PropertyDescriptor props[] = bi.getPropertyDescriptors();
for (int i=0; i < props.length; i++)
{
//System.err.println("checking on props[i]: "+props[i].getName());
Object v = attr.getAttribute(props[i].getName());
if (v instanceof String)
{
// found a property parameter
String value = (String) v;
Method writer = props[i].getWriteMethod();
if (writer == null)
{
// read-only property. ignore
return; // for now
}
try
{
Object[] args = {convert(value,props[i].getPropertyType())};
writer.invoke(comp, args);
}
catch (Throwable ex)
{
System.err.println("Invocation failed: "+ex);
}
}
}
}
private Object convert(String value, Class type)
{
if (type == String.class) return value;
if (type == Color.class)
{
try
{
return ColorConverter.stringToHTMLColor(value);
}
catch (ColorConverter.ColorConversionException x)
{
throw new RuntimeException(x.getMessage());
}
}
if (type.isPrimitive())
{
if (type == Double.TYPE ) type = Double.class;
else if (type == Boolean.TYPE ) type = Boolean.class;
else if (type == Integer.TYPE ) type = Integer.class;
else if (type == Float.TYPE ) type = Float.class;
else if (type == Byte.TYPE ) type = Byte.class;
else if (type == Character.TYPE) type = Character.class;
else if (type == Long.TYPE ) type = Long.class;
else if (type == Short.TYPE ) type = Short.class;
else if (type == Void.TYPE ) type = Void.class;
}
Class[] argsClass = { value.getClass() };
Object[] args = { value };
try
{
Method m = type.getMethod("valueOf",argsClass);
Object result = type.newInstance();
m.invoke(result,args);
return result;
}
catch (Throwable x) {}
try
{
Constructor c = type.getConstructor(argsClass);
Object result = c.newInstance(args);
return result;
}
catch (Throwable x) {}
throw new RuntimeException("Could not convert "+value+" to "+type);
}
private JASPageContext context;
}
Ads help maintain this website.