org.xhtmlrenderer.swing.BasicPanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of flying-saucer-core Show documentation
Show all versions of flying-saucer-core Show documentation
Flying Saucer is a CSS 2.1 renderer written in Java. This artifact contains the core rendering and layout code as well as Java2D output.
/*
* {{{ header & license
* Copyright (c) 2004, 2005 Joshua Marinacci
*
* This program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* }}}
*/
package org.xhtmlrenderer.swing;
import org.w3c.dom.Document;
import org.xhtmlrenderer.css.style.CalculatedStyle;
import org.xhtmlrenderer.css.style.derived.RectPropertySet;
import org.xhtmlrenderer.extend.NamespaceHandler;
import org.xhtmlrenderer.extend.UserAgentCallback;
import org.xhtmlrenderer.layout.Layer;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.render.Box;
import org.xhtmlrenderer.render.PageBox;
import org.xhtmlrenderer.render.RenderingContext;
import org.xhtmlrenderer.resource.XMLResource;
import org.xhtmlrenderer.simple.NoNamespaceHandler;
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
import org.xhtmlrenderer.util.Configuration;
import org.xhtmlrenderer.util.Uu;
import org.xhtmlrenderer.util.XRLog;
import org.xml.sax.InputSource;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.swing.*;
import java.awt.*;
import java.awt.print.PrinterGraphics;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.logging.Level;
/**
* A Swing {@link javax.swing.JPanel} that encloses the Flying Saucer renderer
* for easy integration into Swing applications.
*
* @author Joshua Marinacci
*/
@ParametersAreNonnullByDefault
public abstract class BasicPanel extends RootPanel implements FormSubmissionListener {
private static final int PAGE_PAINTING_CLEARANCE_WIDTH = 10;
private static final int PAGE_PAINTING_CLEARANCE_HEIGHT = 10;
private boolean explicitlyOpaque;
private final MouseTracker mouseTracker;
private boolean centeredPagedView;
protected FormSubmissionListener formSubmissionListener;
protected BasicPanel() {
this(new NaiveUserAgent());
}
protected BasicPanel(UserAgentCallback uac) {
sharedContext = new SharedContext(uac);
mouseTracker = new MouseTracker(this);
formSubmissionListener = query -> {
System.out.println("Form Submitted!");
System.out.println("Data: " + query);
JOptionPane.showMessageDialog(
null,
"Form submit called; check console to see the query string" +
" that would have been submitted.",
"Form Submission",
JOptionPane.INFORMATION_MESSAGE
);
};
sharedContext.setFormSubmissionListener(formSubmissionListener);
init();
}
@Override
public void paintComponent(Graphics g) {
if (doc == null) {
paintDefaultBackground(g);
return;
}
// if this is the first time painting this document, then calc layout
Layer root = getRootLayer();
if (root == null || isNeedRelayout()) {
Graphics gg = g.create();
try {
doDocumentLayout(gg);
} finally {
gg.dispose();
}
root = getRootLayer();
}
setNeedRelayout(false);
if (root == null) {
XRLog.render(Level.FINE, "skipping the actual painting");
} else {
Graphics gg = g.create();
try {
RenderingContext c = newRenderingContext((Graphics2D) gg);
long start = System.currentTimeMillis();
doRender(c, root);
long end = System.currentTimeMillis();
XRLog.render(Level.FINE, "RENDERING TOOK " + (end - start) + " ms");
} finally {
gg.dispose();
}
}
}
protected void doRender(RenderingContext c, Layer root) {
try {
// paint the normal swing background first
// but only if we aren't printing.
Graphics g = ((Java2DOutputDevice)c.getOutputDevice()).getGraphics();
paintDefaultBackground(g);
JScrollPane scrollPane = getEnclosingScrollPane();
if (scrollPane == null) {
Insets insets = getInsets();
g.translate(insets.left, insets.top);
}
long start = System.currentTimeMillis();
if (!c.isPrint()) {
root.paint(c);
} else {
paintPagedView(c, root);
}
long after = System.currentTimeMillis();
if (Configuration.isTrue("xr.incremental.repaint.print-timing", false)) {
Uu.p("repaint took ms: " + (after - start));
}
} catch (ThreadDeath t) {
throw t;
} catch (Throwable t) {
if (hasDocumentListeners()) {
fireOnRenderException(t);
} else {
if (t instanceof Error) {
throw (Error)t;
}
if (t instanceof RuntimeException) {
throw (RuntimeException)t;
}
// "Shouldn't" happen
XRLog.exception(t.getMessage(), t);
}
}
}
private void paintDefaultBackground(Graphics g) {
if (!(g instanceof PrinterGraphics) && explicitlyOpaque) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
}
private void paintPagedView(RenderingContext c, Layer root) {
if (root.getLastPage() == null) {
return;
}
final int pagePaintingClearanceWidth = isCenteredPagedView() ?
calcCenteredPageLeftOffset(root.getMaxPageWidth(c, 0)) :
PAGE_PAINTING_CLEARANCE_WIDTH;
root.assignPagePaintingPositions(
c, Layer.PAGED_MODE_SCREEN, PAGE_PAINTING_CLEARANCE_HEIGHT);
setPreferredSize(new Dimension(
root.getMaxPageWidth(c, pagePaintingClearanceWidth),
root.getLastPage().getPaintingBottom() + PAGE_PAINTING_CLEARANCE_HEIGHT));
revalidate();
Graphics2D g = ((Java2DOutputDevice)c.getOutputDevice()).getGraphics();
Shape working = g.getClip();
List pages = root.getPages();
c.setPageCount(pages.size());
for (int i = 0; i < pages.size(); i++) {
PageBox page = pages.get(i);
c.setPage(i, page);
g.setClip(working);
Rectangle overall = page.getScreenPaintingBounds(c, pagePaintingClearanceWidth);
overall.x -= 1;
overall.y -= 1;
overall.width += 1;
overall.height += 1;
Rectangle bounds = new Rectangle(overall);
bounds.width += 1;
bounds.height += 1;
if (working.intersects(bounds)) {
page.paintBackground(c, pagePaintingClearanceWidth, Layer.PAGED_MODE_SCREEN);
page.paintMarginAreas(c, pagePaintingClearanceWidth, Layer.PAGED_MODE_SCREEN);
page.paintBorder(c, pagePaintingClearanceWidth, Layer.PAGED_MODE_SCREEN);
Color old = g.getColor();
g.setColor(Color.BLACK);
g.drawRect(overall.x, overall.y, overall.width, overall.height);
g.setColor(old);
Rectangle content = page.getPagedViewClippingBounds(c, pagePaintingClearanceWidth);
g.clip(content);
int left = pagePaintingClearanceWidth +
page.getMarginBorderPadding(c, CalculatedStyle.LEFT);
int top = page.getPaintingTop()
+ page.getMarginBorderPadding(c, CalculatedStyle.TOP)
- page.getTop();
g.translate(left, top);
root.paint(c);
g.translate(-left, -top);
g.setClip(working);
}
}
g.setClip(working);
}
private int calcCenteredPageLeftOffset(int maxPageWidth) {
return (getWidth() - maxPageWidth) / 2;
}
public void paintPage(Graphics2D g, int pageNo) {
Layer root = getRootLayer();
if (root == null) {
throw new RuntimeException("Document needs layout");
}
if (pageNo < 0 || pageNo >= root.getPages().size()) {
throw new IllegalArgumentException("Page " + pageNo + " is not between 0 " +
"and " + root.getPages().size());
}
RenderingContext c = newRenderingContext(g);
PageBox page = root.getPages().get(pageNo);
c.setPageCount(root.getPages().size());
c.setPage(pageNo, page);
page.paintBackground(c, 0, Layer.PAGED_MODE_PRINT);
page.paintMarginAreas(c, 0, Layer.PAGED_MODE_PRINT);
page.paintBorder(c, 0, Layer.PAGED_MODE_PRINT);
Shape working = g.getClip();
Rectangle content = page.getPrintClippingBounds(c);
g.clip(content);
int top = -page.getPaintingTop() +
page.getMarginBorderPadding(c, CalculatedStyle.TOP);
int left = page.getMarginBorderPadding(c, CalculatedStyle.LEFT);
g.translate(left, top);
root.paint(c);
g.translate(-left, -top);
g.setClip(working);
}
public void assignPagePrintPositions(Graphics2D g) {
RenderingContext c = newRenderingContext(g);
getRootLayer().assignPagePaintingPositions(c, Layer.PAGED_MODE_PRINT);
}
public void printTree() {
printTree(getRootBox(), "");
}
private void printTree(Box box, String tab) {
XRLog.layout(Level.FINEST, tab + "Box = " + box);
for (Box bx : box.getChildren()) {
printTree(bx, tab + " ");
}
}
/**
* Sets the layout attribute of the BasicPanel object
* Overrides the method to do nothing, since you shouldn't have a
* LayoutManager on an FS panel.
*
* @param l The new layout value
*/
@Override
public void setLayout(LayoutManager l) {
}
public void setSharedContext(SharedContext ctx) {
sharedContext = ctx;
}
@Override
public void setSize(Dimension d) {
XRLog.layout(Level.FINEST, "set size called");
super.setSize(d);
/* CLEAN: do we need this?
if (doc != null && body_box != null) {
if(body_box.width != d.width)
RenderQueue.getInstance().dispatchLayoutEvent(new ReflowEvent(ReflowEvent.CANVAS_RESIZED, d));
//don't need the below, surely
//else if(body_box.height != d.height)
// RenderQueue.getInstance().dispatchRepaintEvent(new ReflowEvent(ReflowEvent.CANVAS_RESIZED, d));
} */
}
/*
=========== set document utility methods =============== */
public void setDocument(InputStream stream, String url, NamespaceHandler nsh) {
Document dom = XMLResource.load(stream).getDocument();
setDocument(dom, url, nsh);
}
public void setDocumentFromString(String content, @Nullable String url, NamespaceHandler nsh) {
InputSource is = new InputSource(new StringReader(content));
Document dom = XMLResource.load(is).getDocument();
setDocument(dom, url, nsh);
}
public void setDocument(Document doc, String url) {
setDocument(doc, url, new NoNamespaceHandler());
}
public void setDocument(String url) {
setDocument(loadDocument(url), url, new NoNamespaceHandler());
}
public void setDocument(String url, NamespaceHandler nsh) {
setDocument(loadDocument(url), url, nsh);
}
// TODO: should throw more specific exception (PWW 25/07/2006)
protected void setDocument(InputStream stream, String url) {
setDocument(stream, url, new NoNamespaceHandler());
}
/**
* Sets the new current document, where the new document
* is located relative, e.g. using a relative URL.
*
* @param filename The new document to load
*/
protected void setDocumentRelative(String filename) {
String url = getSharedContext().getUac().resolveURI(filename);
if (isAnchorInCurrentDocument(filename)) {
String id = getAnchorId(filename);
Box box = getSharedContext().getBoxById(id);
if (box != null) {
Point pt;
if (box.getStyle().isInline()) {
pt = new Point(box.getAbsX(), box.getAbsY());
} else {
RectPropertySet margin = box.getMargin(getLayoutContext());
pt = new Point(
box.getAbsX() + (int)margin.left(),
box.getAbsY() + (int)margin.top());
}
scrollTo(pt);
return;
}
}
Document dom = loadDocument(url);
setDocument(dom, url);
}
/**
* Reloads the document using the same base URL and namespace handler. Reloading will pick up changes to styles
* within the document.
*
* @param URI A URI for the Document to load, for example, file.toURL().toExternalForm().
*/
public void reloadDocument(String URI) {
reloadDocument(loadDocument(URI));
}
/**
* Reloads the document using the same base URL and namespace handler. Reloading will pick up changes to styles
* within the document.
*
* @param doc The document to reload.
*/
public void reloadDocument(Document doc) {
if (this.doc == null) {
XRLog.render("Reload called on BasicPanel, but there is no document set on the panel yet.");
return;
}
this.doc = doc;
setDocument(this.doc, getSharedContext().getBaseURL(), getSharedContext().getNamespaceHandler());
}
public URL getURL() {
try {
return new URL(getSharedContext().getUac().getBaseURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
public Document getDocument() {
return doc;
}
/**
* Returns the title as reported by the NamespaceHandler assigned to the SharedContext in this panel. For an HTML
* document, this will be the contents of /html/head/title.
*
* @return the document title, or "" if the namespace handler cannot find a title, or if there is no current document
* in the panel.
*/
public String getDocumentTitle() {
return doc == null ? "" : getSharedContext().getNamespaceHandler().getDocumentTitle(doc);
}
protected Document loadDocument(final String uri) {
XMLResource xmlResource = sharedContext.getUac().getXMLResource(uri);
return xmlResource.getDocument();
}
/**
* Returns whether the background of this {@code BasicPanel} will
* be painted when it is rendered.
*
* @return {@code true} if the background of this
* {@code BasicPanel} will be painted, {@code false} if it
* will not.
*/
@Override
public boolean isOpaque() {
checkOpacityMethodClient();
return explicitlyOpaque;
}
/**
* Specifies whether the background of this {@code BasicPanel} will
* be painted when it is rendered.
*
* @param opaque {@code true} if the background of this
* {@code BasicPanel} should be painted, {@code false} if it
* should not.
*/
@Override
public void setOpaque(boolean opaque) {
checkOpacityMethodClient();
explicitlyOpaque = opaque;
}
/**
* Checks that the calling method of the method that calls this method is not in this class
* and throws a RuntimeException if it was. This is used to ensure that parts of this class that
* use the opacity to indicate something other than whether the background is painted do not
* interfere with the user's intentions regarding the background painting.
*
* @throws IllegalStateException if the method that called this method was itself called by a
* method in this same class.
*/
private void checkOpacityMethodClient() {
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
if (stackTrace.length > 2) {
String callingClassName = stackTrace[2].getClassName();
if (BasicPanel.class.getName().equals(callingClassName))
throw new IllegalStateException("BasicPanel should not use its own opacity methods. Use " +
"super.isOpaque()/setOpaque() instead.");
}
}
private boolean isAnchorInCurrentDocument(String str) {
return str.charAt(0) == '#';
}
private String getAnchorId(String url) {
return url.substring(1);
}
/**
* Scroll the panel to make the specified point be on screen. Typically,
* this will scroll the screen down to the y component of the point.
*/
public void scrollTo(Point pt) {
JScrollPane scrollPane = getEnclosingScrollPane();
if (scrollPane != null) {
JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
if(scrollBar != null) {
scrollBar.setValue(pt.y);
}
}
}
public boolean isInteractive() {
return getSharedContext().isInteractive();
}
public void setInteractive(boolean interactive) {
getSharedContext().setInteractive(interactive);
}
public void addMouseTrackingListener(FSMouseListener l) {
mouseTracker.addListener(l);
}
public void removeMouseTrackingListener(FSMouseListener l) {
mouseTracker.removeListener(l);
}
public List getMouseTrackingListeners() {
return mouseTracker.getListeners();
}
protected void resetMouseTracker() {
mouseTracker.reset();
}
public boolean isCenteredPagedView() {
return centeredPagedView;
}
public void setCenteredPagedView(boolean centeredPagedView) {
this.centeredPagedView = centeredPagedView;
}
@Override
public void submit(String url) {
formSubmissionListener.submit(url);
}
public void setFormSubmissionListener(FormSubmissionListener fsl) {
formSubmissionListener =fsl;
sharedContext.setFormSubmissionListener(formSubmissionListener);
}
}