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.