import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.text.html.*; import javax.swing.tree.*; import javax.swing.undo.*; import java.awt.*; import java.util.*; import java.net.URL; import java.net.MalformedURLException; import java.io.IOException; // The following can be commented out for version 1.3 import pre13.AsyncBoxView; /** * A wrapper around JEditorPane to browse html using * an HTMLEditorKit extension that does aysynchronous layout. *

* An example invocation would be: *


 * java HtmlExample url
 * 
*/ public class HtmlExample { public static void main(String[] args) { Properties props = System.getProperties(); props.put("http.proxyHost", "webcache1.eng"); props.put("http.proxyPort", "8080"); if (args.length != 1) { System.err.println("need URL argument"); System.exit(1); } try { JEditorPane html = new JEditorPane(); JEditorPane.registerEditorKitForContentType("text/html", "HtmlExample$AsyncHTMLEditorKit"); URL u = new URL(args[0]); html.setEditable(false); html.addHyperlinkListener(new Hyperactive()); html.setBackground(Color.white); html.setEditorKitForContentType("text/html", new AsyncHTMLEditorKit()); JScrollPane scroller = new JScrollPane(); JViewport vp = scroller.getViewport(); vp.add(html); vp.setBackingStoreEnabled(true); JFrame f = new JFrame("testing"); f.getContentPane().setLayout(new BorderLayout()); f.getContentPane().add("Center", scroller); f.pack(); f.setSize(600, 600); f.setVisible(true); // Not safe to call setPage from a thread // other than the event thread SwingUtilities.invokeLater(new Init(html, u)); } catch (MalformedURLException e) { System.err.println("Bad url"); System.exit(1); } catch (IOException ioe) { System.err.println("IOException: " + ioe.getMessage()); System.exit(1); } } static class Init implements Runnable { Init(JEditorPane pane, URL u) { this.u = u; this.pane = pane; } public void run() { try { pane.setPage(u); } catch (Throwable e) { e.printStackTrace(); System.exit(1); } } URL u; JEditorPane pane; } /** * An HTMLEditorKit that does asychronous layout * to represent the body element and table cell * elements. */ public static class AsyncHTMLEditorKit extends HTMLEditorKit { public AsyncHTMLEditorKit() { super(); } /** * Fetch a factory that is suitable for producing * views of any models that are produced by this * kit. * * @return the factory */ public ViewFactory getViewFactory() { return asyncFactory; } static ViewFactory asyncFactory = new AsyncFactory(); /** * Factory to build views of the html elements. This * simply extends the behavior of the default html factory * to build a view that does asynchronous layout for the * BODY and TD elements. */ public static class AsyncFactory extends HTMLFactory { public View create(Element elem) { Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute); if (o instanceof HTML.Tag) { HTML.Tag kind = (HTML.Tag) o; if ((kind == HTML.Tag.BODY) || (kind == HTML.Tag.TD)) { //System.err.println("creating BlockView for: " + elem); return new BlockView(elem, View.Y_AXIS); } } return super.create(elem); } } /** * A view implementation to display an html block * (as an asynchronous box) with CSS specifications. */ public static class BlockView extends AsyncBoxView { /** * Creates a new view that represents an * html box. This can be used for a number * of elements. * * @param elem the element to create a view for * @param axis either View.X_AXIS or View.Y_AXIS */ public BlockView(Element elem, int axis) { super(elem, axis); StyleSheet sheet = getStyleSheet(); attr = sheet.getViewAttributes(this); painter = sheet.getBoxPainter(attr); width = -1f; height = -1f; } /** * Update any cached values that come from attributes. */ protected void setPropertiesFromAttributes() { attr = getStyleSheet().getViewAttributes(this); if (attr != null) { setTopInset(painter.getInset(TOP, this)); setLeftInset(painter.getInset(LEFT, this)); setBottomInset(painter.getInset(BOTTOM, this)); setRightInset(painter.getInset(RIGHT, this)); // determine css width... default to unspecified width = -1; Object widthValue = attr.getAttribute(CSS.Attribute.WIDTH); if (widthValue != null) { // this is wrong.... but CSS.LengthValue isn't public yet try { width = Float.valueOf(widthValue.toString()).floatValue(); } catch (NumberFormatException nfe) { width = -1; } } // determine css height... default to unspecified height = -1; Object heightValue = attr.getAttribute(CSS.Attribute.HEIGHT); if (heightValue != null) { // this is wrong.... but CSS.LengthValue isn't public yet try { width = Float.valueOf(heightValue.toString()).floatValue(); } catch (NumberFormatException nfe) { height = -1; } } } } /** * Get the StyleSheet to use for this view. By default the * associated document is assumed to be an HTMLDocument, and * the StyleSheet implementation is fetched from it. */ protected StyleSheet getStyleSheet() { HTMLDocument doc = (HTMLDocument) getDocument(); return doc.getStyleSheet(); } // --- View methods ------------------------------------------------ /** * Determines the minimum span for this view along an * axis. If a css length has been specified (i.e. width * for X_AXIS or height for Y_AXIS), that will be used, * otherwise the superclass behavior is used. * * @param axis may be either View.X_AXIS or View.Y_AXIS * @returns the minimum span the view can be rendered into. * @see View#getPreferredSpan */ public float getMinimumSpan(int axis) { if ((axis == X_AXIS) && (width >= 0)) { return width; } else if ((axis == Y_AXIS) && (height >= 0)) { return height; } else { return super.getMinimumSpan(axis); } } /** * Determines the preferred span for this view along an * axis. If a css length has been specified (i.e. width * for X_AXIS or height for Y_AXIS), that will be used, * otherwise the superclass behavior is used. * * @param axis may be either View.X_AXIS or View.Y_AXIS * @returns the minimum span the view can be rendered into. * @see View#getPreferredSpan */ public float getPreferredSpan(int axis) { if ((axis == X_AXIS) && (width >= 0)) { return width; } else if ((axis == Y_AXIS) && (height >= 0)) { return height; } else { return super.getPreferredSpan(axis); } } /** * Establishes the parent view for this view. This is * guaranteed to be called before any other methods if the * parent view is functioning properly. *

* This is implemented * to forward to the superclass as well as call the * setPropertiesFromAttributes * method to set the paragraph properties from the css * attributes. The call is made at this time to ensure * the ability to resolve upward through the parents * view attributes. * * @param parent the new parent, or null if the view is * being removed from a parent it was previously added * to */ public void setParent(View parent) { super.setParent(parent); setPropertiesFromAttributes(); } /** * Renders using the given rendering surface and area on that * surface. This is implemented to delegate to the css box * painter to paint the border and background prior to the * interior. * * @param g the rendering surface to use * @param allocation the allocated region to render into * @see View#paint */ public void paint(Graphics g, Shape allocation) { Rectangle a = (Rectangle) allocation; painter.paint(g, a.x, a.y, a.width, a.height, this); super.paint(g, a); } /** * Fetches the attributes to use when rendering. This is * implemented to multiplex the attributes specified in the * model with a StyleSheet. */ public AttributeSet getAttributes() { return attr; } /** * Gets the alignment. * * @param axis may be either X_AXIS or Y_AXIS * @return the alignment */ public float getAlignment(int axis) { switch (axis) { case View.X_AXIS: return 0; case View.Y_AXIS: float span = getPreferredSpan(View.Y_AXIS); View v = getView(0); float above = v.getPreferredSpan(View.Y_AXIS); float a = (((int)span) != 0) ? (above * v.getAlignment(View.Y_AXIS)) / span: 0; return a; default: throw new IllegalArgumentException("Invalid axis: " + axis); } } public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) { super.changedUpdate(changes, a, f); int pos = changes.getOffset(); if (pos <= getStartOffset() && (pos + changes.getLength()) >= getEndOffset()) { setPropertiesFromAttributes(); } } float width; float height; private AttributeSet attr; private StyleSheet.BoxPainter painter; } } static class Hyperactive implements HyperlinkListener { /** * Notification of a change relative to a * hyperlink. */ public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { JEditorPane html = (JEditorPane) e.getSource(); if (e instanceof HTMLFrameHyperlinkEvent) { HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e; HTMLDocument doc = (HTMLDocument)html.getDocument(); doc.processHTMLFrameHyperlinkEvent(evt); } else { try { html.setPage(e.getURL()); } catch (Throwable t) { t.printStackTrace(); } } } } } }