/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: PreviewPanel.java 1616590 2014-08-07 20:27:59Z gadams $ */

package org.apache.fop.render.awt.viewer;

import java.awt.Adjustable;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Rectangle2D;

import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;

import org.apache.xmlgraphics.util.UnitConv;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.area.PageViewport;
import org.apache.fop.render.awt.AWTRenderer;


Holds a scrollpane with the rendered page(s) and handles actions performed to alter the display of the page.

Use PreviewPanel when you want to embed a preview in your own application with your own controls. Use PreviewDialog when you want to use the standard Fop controls.

In order to embed a PreviewPanel in your own app, create your own renderer, and your own agent. In order to support reloads, you may also implement your own Renderable extension or the default InputHandler. Setting the Renderable to null works fine though. Then call setPreviewDialogDisplayed(false) to hide the default dialog. Finally create a preview panel with the agent, renderable and renderer and add it to your gui:

FopFactory fopFactory = FopFactory.newInstance();
FOUserAgent agent = fopFactory.newFOUserAgent();
AWTRenderer renderer = new AWTRenderer(agent);
agent.setRendererOverride(renderer);
previewPanel = new PreviewPanel(agent, null, renderer);
previewPanel = new PreviewPanel(ua);
myGui.add(previewPanel);
In order to set options and display a page do:
renderer.clearViewportList();
// build report xml here
reload(); // optional if setting changed
If you wan't to change settings, don't call reload. A good example is to set the page to fill the screen and set the scrolling mode:
double scale = previewPanel.getScaleToFitWindow();
previewPanel.setScaleFactor(scale);
previewPanel.setDisplayMode(PreviewPanel.CONTINUOUS);
/** * <p>Holds a scrollpane with the rendered page(s) and handles actions performed * to alter the display of the page. * </p> * <p>Use PreviewPanel when you want to embed a preview in your own application * with your own controls. Use PreviewDialog when you want to use the standard * Fop controls. * </p> * <p>In order to embed a PreviewPanel in your own app, create your own renderer, * and your own agent. In order to support reloads, you may also implement your * own Renderable extension or the default InputHandler. Setting the Renderable * to null works fine though. * Then call setPreviewDialogDisplayed(false) to hide the * default dialog. Finally create a preview panel with the agent, renderable and * renderer and add it to your gui: * </p> * <pre> * FopFactory fopFactory = FopFactory.newInstance(); * FOUserAgent agent = fopFactory.newFOUserAgent(); * AWTRenderer renderer = new AWTRenderer(agent); * agent.setRendererOverride(renderer); * previewPanel = new PreviewPanel(agent, null, renderer); * previewPanel = new PreviewPanel(ua); * myGui.add(previewPanel); * </pre> * * In order to set options and display a page do: * <pre> * renderer.clearViewportList(); * // build report xml here * reload(); // optional if setting changed * </pre> * * If you wan't to change settings, don't call reload. A good example is * to set the page to fill the screen and set the scrolling mode: * <pre> * double scale = previewPanel.getScaleToFitWindow(); * previewPanel.setScaleFactor(scale); * previewPanel.setDisplayMode(PreviewPanel.CONTINUOUS); * </pre> */
public class PreviewPanel extends JPanel {
Constant for setting single page display.
/** Constant for setting single page display. */
public static final int SINGLE = 1;
Constant for setting continuous page display.
/** Constant for setting continuous page display. */
public static final int CONTINUOUS = 2;
Constant for displaying even/odd pages side by side in continuous form.
/** Constant for displaying even/odd pages side by side in continuous form. */
public static final int CONT_FACING = 3;
The number of pixels left empty at the top bottom and sides of the page.
/** The number of pixels left empty at the top bottom and sides of the page. */
private static final int BORDER_SPACING = 10;
The main display area
/** The main display area */
private JScrollPane previewArea;
The AWT renderer - often shared with PreviewDialog
/** The AWT renderer - often shared with PreviewDialog */
private AWTRenderer renderer;
The FOUserAgent associated with this panel - often shared with PreviewDialog
/** The FOUserAgent associated with this panel - often shared with PreviewDialog */
protected FOUserAgent foUserAgent;
Renderable instance that can be used to reload and re-render a document after modifications.
/** * Renderable instance that can be used to reload and re-render a document after * modifications. */
protected Renderable renderable;
The number of the page which is currently selected
/** The number of the page which is currently selected */
private int currentPage;
The index of the first page displayed on screen.
/** The index of the first page displayed on screen. */
private int firstPage;
The number of pages concurrently displayed on screen.
/** The number of pages concurrently displayed on screen. */
private int pageRange = 1;
The display mode. One of SINGLE, CONTINUOUS or CONT_FACING.
/** The display mode. One of SINGLE, CONTINUOUS or CONT_FACING. */
private int displayMode = SINGLE;
The component(s) that hold the rendered page(s)
/** The component(s) that hold the rendered page(s) */
private ImageProxyPanel[] pagePanels;
Panel showing the page panels in a grid. Usually the dimensions of the grid are 1x1, nx1 or nx2.
/** * Panel showing the page panels in a grid. Usually the dimensions * of the grid are 1x1, nx1 or nx2. */
private JPanel gridPanel;
Asynchronous reloader thread, used when reload() method is called.
/** Asynchronous reloader thread, used when reload() method is called. */
private Reloader reloader;
Allows any mouse drag on the page area to scroll the display window.
/** * Allows any mouse drag on the page area to scroll the display window. */
private ViewportScroller scroller;
Creates a new PreviewPanel instance.
Params:
  • foUserAgent – the user agent
  • renderable – the Renderable instance that is used to reload/re-render a document after modifications.
  • renderer – the AWT Renderer instance to paint with
/** * Creates a new PreviewPanel instance. * @param foUserAgent the user agent * @param renderable the Renderable instance that is used to reload/re-render a document * after modifications. * @param renderer the AWT Renderer instance to paint with */
public PreviewPanel(FOUserAgent foUserAgent, Renderable renderable, AWTRenderer renderer) { super(new GridLayout(1, 1)); this.renderable = renderable; this.renderer = renderer; this.foUserAgent = foUserAgent; //Override target resolution for the computer screen this.foUserAgent.setTargetResolution(Toolkit.getDefaultToolkit().getScreenResolution()); gridPanel = new JPanel(); gridPanel.setLayout(new GridLayout(0, 1)); // rows, cols previewArea = new JScrollPane(gridPanel); previewArea.getViewport().setBackground(Color.gray); previewArea.getVerticalScrollBar().addAdjustmentListener(new PageNumberListener()); // FIXME should add scroll wheel support here at some point. scroller = new ViewportScroller(previewArea.getViewport()); previewArea.addMouseListener(scroller); previewArea.addMouseMotionListener(scroller); previewArea.setMinimumSize(new Dimension(50, 50)); add(previewArea); }
Returns:the currently visible page
/** * @return the currently visible page */
public int getPage() { return currentPage; }
Selects the given page, displays it on screen and notifies listeners about the change in selection.
Params:
  • number – the page number
/** * Selects the given page, displays it on screen and notifies * listeners about the change in selection. * @param number the page number */
public void setPage(int number) { int oldPage = currentPage; if (displayMode == CONTINUOUS || displayMode == CONT_FACING) { currentPage = number; gridPanel.scrollRectToVisible(pagePanels[currentPage].getBounds()); } else { // single page mode currentPage = number; firstPage = currentPage; } showPage(); firePageChange(oldPage, currentPage); }
Sets the display mode.
Params:
  • mode – One of SINGLE, CONTINUOUS or CONT_FACING.
/** * Sets the display mode. * @param mode One of SINGLE, CONTINUOUS or CONT_FACING. */
public void setDisplayMode(int mode) { if (mode != displayMode) { displayMode = mode; gridPanel.setLayout(new GridLayout(0, displayMode == CONT_FACING ? 2 : 1)); reload(); } }
Returns the display mode.
Returns:mode One of SINGLE, CONTINUOUS or CONT_FACING.
/** * Returns the display mode. * @return mode One of SINGLE, CONTINUOUS or CONT_FACING. */
public int getDisplayMode() { return displayMode; }
Reloads and reformats document.
/** * Reloads and reformats document. */
public synchronized void reload() { if (reloader == null || !reloader.isAlive()) { reloader = new Reloader(); reloader.start(); } }
Allows a (yet) simple visual debug of the document.
/** * Allows a (yet) simple visual debug of the document. */
void debug() { renderer.debug = !renderer.debug; reload(); }
Add a listener to receive notification of page change events. Events will be fired whenever the currentPage value is changed. The values recorded are 0-based.
Params:
  • l – the page change listener to add
/** * Add a listener to receive notification of page change events. Events will * be fired whenever the currentPage value is changed. The values recorded * are 0-based. * @param l the page change listener to add */
public void addPageChangeListener(PageChangeListener l) { listenerList.add(PageChangeListener.class, l); }
Removes a page change listener.
Params:
  • l – the page change listener to remove
/** * Removes a page change listener. * @param l the page change listener to remove */
public void removePageChangeListener(PageChangeListener l) { listenerList.remove(PageChangeListener.class, l); }
Notify all registered listeners of a page change event.
Params:
  • oldPage – the old page
  • newPage – the new page
/** * Notify all registered listeners of a page change event. * @param oldPage the old page * @param newPage the new page */
protected void firePageChange(int oldPage, int newPage) { Object[] listeners = listenerList.getListenerList(); PageChangeEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == PageChangeListener.class) { if (e == null) { e = new PageChangeEvent(this, newPage, oldPage); } ((PageChangeListener)listeners[i + 1]).pageChanged(e); } } }
Allows any mouse drag on the page area to scroll the display window.
/** * Allows any mouse drag on the page area to scroll the display window. */
private class ViewportScroller implements MouseListener, MouseMotionListener {
The viewport to be scrolled
/** The viewport to be scrolled */
private final JViewport viewport;
Starting position of a mouse drag - X co-ordinate
/** Starting position of a mouse drag - X co-ordinate */
private int startPosX;
Starting position of a mouse drag - Y co-ordinate
/** Starting position of a mouse drag - Y co-ordinate */
private int startPosY; ViewportScroller(JViewport vp) { viewport = vp; } // ***** MouseMotionListener ***** public synchronized void mouseDragged(MouseEvent e) { if (viewport == null) { return; } int x = e.getX(); int y = e.getY(); int xmove = x - startPosX; int ymove = y - startPosY; int viewWidth = viewport.getExtentSize().width; int viewHeight = viewport.getExtentSize().height; int imageWidth = viewport.getViewSize().width; int imageHeight = viewport.getViewSize().height; Point viewPoint = viewport.getViewPosition(); int viewX = Math.max(0, Math.min(imageWidth - viewWidth, viewPoint.x - xmove)); int viewY = Math.max(0, Math.min(imageHeight - viewHeight, viewPoint.y - ymove)); viewport.setViewPosition(new Point(viewX, viewY)); startPosX = x; startPosY = y; } public void mouseMoved(MouseEvent e) { } // ***** MouseListener ***** public synchronized void mousePressed(MouseEvent e) { startPosX = e.getX(); startPosY = e.getY(); } public void mouseExited(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseClicked(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } }
This class is used to reload document in a thread safe way.
/** * This class is used to reload document in a thread safe way. */
private class Reloader extends Thread { public void run() { if (!renderer.isRenderingDone()) { // do not allow the reloading while FOP is still rendering JOptionPane.showMessageDialog(previewArea, "Cannot perform the requested operation until " + "all page are rendered. Please wait", "Please wait ", 1 /* INFORMATION_MESSAGE */); return; } pagePanels = null; int savedCurrentPage = currentPage; currentPage = 0; gridPanel.removeAll(); switch(displayMode) { case CONT_FACING: // This page intentionally left blank // Makes 0th/1st page on rhs gridPanel.add(new JLabel("")); // @SuppressFBWarnings("SF_SWITCH_FALLTHROUGH") case CONTINUOUS: currentPage = 0; firstPage = 0; pageRange = renderer.getNumberOfPages(); break; case SINGLE: default: currentPage = 0; firstPage = 0; pageRange = 1; break; } pagePanels = new ImageProxyPanel[pageRange]; for (int pg = 0; pg < pageRange; pg++) { pagePanels[pg] = new ImageProxyPanel(renderer, pg + firstPage); pagePanels[pg].setBorder(new EmptyBorder( BORDER_SPACING, BORDER_SPACING, BORDER_SPACING, BORDER_SPACING)); gridPanel.add(pagePanels[pg]); } try { if (renderable != null) { renderer.clearViewportList(); renderable.renderTo(foUserAgent, MimeConstants.MIME_FOP_AWT_PREVIEW); } } catch (FOPException e) { e.printStackTrace(); // FIXME Should show exception in gui - was reportException(e); } setPage(savedCurrentPage); } } private class PageNumberListener implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent e) { if (displayMode == PreviewPanel.CONTINUOUS || displayMode == PreviewPanel.CONT_FACING) { Adjustable a = e.getAdjustable(); int value = +e.getValue(); int min = a.getMinimum(); int max = a.getMaximum(); int page = ((renderer.getNumberOfPages() * value) / (max - min)); if (page != currentPage) { int oldPage = currentPage; currentPage = page; firePageChange(oldPage, currentPage); } } } }
Scales page image
Params:
  • scale – [0;1]
/** * Scales page image * @param scale [0;1] */
public void setScaleFactor(double scale) { renderer.setScaleFactor(scale); reload(); }
Returns the scale factor required in order to fit either the current page within the current window or to fit two adjacent pages within the display if the displaymode is continuous.
Throws:
  • FOPException – in case of an error while fetching the PageViewport
Returns:the requested scale factor
/** * Returns the scale factor required in order to fit either the current * page within the current window or to fit two adjacent pages within * the display if the displaymode is continuous. * @return the requested scale factor * @throws FOPException in case of an error while fetching the PageViewport */
public double getScaleToFitWindow() throws FOPException { Dimension extents = previewArea.getViewport().getExtentSize(); return getScaleToFit(extents.getWidth() - 2 * BORDER_SPACING, extents.getHeight() - 2 * BORDER_SPACING); }
As getScaleToFitWindow, but ignoring the Y axis.
Throws:
  • FOPException – in case of an error while fetching the PageViewport
Returns:the requested scale factor
/** * As getScaleToFitWindow, but ignoring the Y axis. * @return the requested scale factor * @throws FOPException in case of an error while fetching the PageViewport */
public double getScaleToFitWidth() throws FOPException { Dimension extents = previewArea.getViewport().getExtentSize(); return getScaleToFit(extents.getWidth() - 2 * BORDER_SPACING, Double.MAX_VALUE); }
Returns the scale factor required in order to fit either the current page or two adjacent pages within a window of the given height and width, depending on the display mode. In order to ignore either dimension, just specify it as Double.MAX_VALUE.
Params:
  • viewWidth – width of the view
  • viewHeight – height of the view
Throws:
  • FOPException – in case of an error while fetching the PageViewport
Returns:the requested scale factor
/** * Returns the scale factor required in order to fit either the current page or * two adjacent pages within a window of the given height and width, depending * on the display mode. In order to ignore either dimension, * just specify it as Double.MAX_VALUE. * @param viewWidth width of the view * @param viewHeight height of the view * @return the requested scale factor * @throws FOPException in case of an error while fetching the PageViewport */
public double getScaleToFit(double viewWidth, double viewHeight) throws FOPException { PageViewport pageViewport = renderer.getPageViewport(currentPage); Rectangle2D pageSize = pageViewport.getViewArea(); float screenResolution = Toolkit.getDefaultToolkit().getScreenResolution(); float screenFactor = screenResolution / UnitConv.IN2PT; double widthScale = viewWidth / (pageSize.getWidth() / 1000f) / screenFactor; double heightScale = viewHeight / (pageSize.getHeight() / 1000f) / screenFactor; return Math.min(displayMode == CONT_FACING ? widthScale / 2 : widthScale, heightScale); }
Starts rendering process and shows the current page.
/** Starts rendering process and shows the current page. */
public synchronized void showPage() { ShowPageImage viewer = new ShowPageImage(); if (SwingUtilities.isEventDispatchThread()) { viewer.run(); } else { SwingUtilities.invokeLater(viewer); } }
This class is used to render the page image in a thread safe way.
/** This class is used to render the page image in a thread safe way. */
private class ShowPageImage implements Runnable {
The run method that does the actual rendering of the viewed page
/** * The run method that does the actual rendering of the viewed page */
public void run() { for (int pg = firstPage; pg < firstPage + pageRange; pg++) { pagePanels[pg - firstPage].setPage(pg); } revalidate(); } } }