/*
* Copyright (c) 2006 Matthew Hall and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Matthew Hall - initial API and implementation
*/
package org.eclipse.nebula.paperclips.widgets;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.nebula.paperclips.core.PageEnumeration;
import org.eclipse.nebula.paperclips.core.PaperClips;
import org.eclipse.nebula.paperclips.core.PrintJob;
import org.eclipse.nebula.paperclips.core.PrintPiece;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Transform;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
/**
* A WYSIWYG (what you see is what you get) print preview panel. This control
* displays a preview of what a PrintJob will look like on paper, depending on
* the selected printer.
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>(none)</dd>
* <dt><b>Events:</b></dt>
* <dd>(none)</dd>
* </dl>
*
* @author Matthew Hall
*/
public class PrintPreview extends Canvas {
private static final int ALL_PAGES = -1;
private PrintJob printJob = null;
private PrinterData printerData = PaperClips.getDefaultPrinterData();
private int pageIndex = 0;
private boolean fitHorizontal = true;
private boolean fitVertical = true;
private float scale = 1.0f;
private int horizontalPageCount = 1;
private int verticalPageCount = 1;
private boolean lazy = false;
// The bounds of the paper on the printer device.
private Point paperSize = null;
private Printer printer = null;
private GC gc = null;
private PageEnumeration pageEnumeration = null;
private List pages = null;
private Point pageDisplaySize = null;
private Point[] pageDisplayLocations = null;
// Margins and page spacing include paper boilerplate.
private Rectangle margins = new Rectangle(10, 10, 10, 10);
private Point pageSpacing = new Point(10, 10);
/**
* Constructs a PrintPreview control.
*
* @param parent
* the parent control.
* @param style
* the control style.
*/
public PrintPreview(Composite parent, int style) {
super(parent, style | SWT.DOUBLE_BUFFERED);
addListener(SWT.Paint, new Listener() {
public void handleEvent(Event event) {
paint(event);
}
});
addListener(SWT.Resize, new Listener() {
public void handleEvent(Event event) {
invalidatePageDisplayBounds();
redraw();
}
});
addListener(SWT.Dispose, new Listener() {
public void handleEvent(Event event) {
disposeResources();
}
});
}
/**
* Returns the print job.
*
* @return the print job.
*/
public PrintJob getPrintJob() {
checkWidget();
return printJob;
}
/**
* Sets the print job to preview.
*
* @param printJob
* the print job to preview.
*/
public void setPrintJob(PrintJob printJob) {
checkWidget();
this.printJob = printJob;
this.pageIndex = 0;
disposePages();
redraw();
}
/**
* Returns the PrinterData for the printer to preview on.
*
* @return the PrinterData for the printer to preview on.
*/
public PrinterData getPrinterData() {
checkWidget();
return printerData;
}
/**
* Sets the PrinterData for the printer to preview on.
*
* @param printerData
* the PrinterData for the printer to preview on.
*/
public void setPrinterData(PrinterData printerData) {
checkWidget();
this.printerData = printerData;
this.pageIndex = 0;
disposePrinter(); // disposes pages too
redraw();
}
/**
* Returns the index of the first visible page.
*
* @return the index of the first visible page.
*/
public int getPageIndex() {
checkWidget();
return pageIndex;
}
/**
* Sets the index of the first visible page to the argument.
*
* @param pageIndex
* the page index.
*/
public void setPageIndex(int pageIndex) {
checkWidget();
this.pageIndex = pageIndex;
redraw();
}
/**
* Returns the known number of pages in the print job. If
* {@link #setLazyPageLayout(boolean)} is set to true, this method returns
* the number of pages laid out so far. This method returns 0 when
* {@link #getPrintJob()} is null or {@link #getPrinterData()} is null.
*
* @return the known number of pages in the print job.
*/
public int getPageCount() {
checkWidget();
fetchPages(lazy ? horizontalPageCount * verticalPageCount : ALL_PAGES);
return pages == null ? 0 : pages.size();
}
/**
* Returns whether all pages have been laid out.
*
* @return whether all pages have been laid out.
*/
public boolean isPageLayoutComplete() {
checkWidget();
fetchPages(horizontalPageCount * verticalPageCount);
return pageEnumeration == null || !pageEnumeration.hasNext();
}
/**
* Returns whether the page scales to fit the document horizontally.
*
* @return whether the page scales to fit the document horizontally.
*/
public boolean isFitHorizontal() {
checkWidget();
return fitHorizontal;
}
/**
* Sets whether the page scales to fit the document horizontally.
*
* @param fitHorizontal
* whether the page scales to fit the document horizontally.
*/
public void setFitHorizontal(boolean fitHorizontal) {
checkWidget();
if (this.fitHorizontal != fitHorizontal) {
this.fitHorizontal = fitHorizontal;
invalidatePageDisplayBounds();
redraw();
}
}
/**
* Returns whether the page scales to fit the document vertically.
*
* @return whether the page scales to fit the document vertically.
*/
public boolean isFitVertical() {
checkWidget();
return fitVertical;
}
/**
* Sets whether the page scales to fit the document vertically.
*
* @param fitVertical
* whether the page scales to fit the document vertically.
*/
public void setFitVertical(boolean fitVertical) {
checkWidget();
if (this.fitVertical != fitVertical) {
this.fitVertical = fitVertical;
invalidatePageDisplayBounds();
redraw();
}
}
/**
* Returns the view scale. The document displays at this scale when
* !(isFitHorizontal() || isFitVertical()).
*
* @return the view scale.
*/
public float getScale() {
checkWidget();
return scale;
}
/**
* Sets the view scale.
*
* @param scale
* the view scale. A scale of 1.0 causes the document to appear
* at full size on the computer screen.
*/
public void setScale(float scale) {
checkWidget();
this.scale = checkScale(scale);
if (!(fitVertical || fitHorizontal)) {
invalidatePageDisplayBounds();
redraw();
}
}
private static float checkScale(float scale) {
if (!(scale > 0))
PaperClips.error(SWT.ERROR_INVALID_ARGUMENT, "Scale must be > 0"); //$NON-NLS-1$
return scale;
}
/**
* Returns how many pages will be displayed in the horizontal direction.
* <p>
* <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
* FUTURE.</b>
*
* @return how many pages will be displayed in the horizontal direction.
*/
public int getHorizontalPageCount() {
checkWidget();
return horizontalPageCount;
}
/**
* Sets how many pages will be displayed in the horizontal direction.
* <p>
* <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
* FUTURE.</b>
*
* @param horizontalPages
* how many pages will be displayed in the horizontal direction.
*/
public void setHorizontalPageCount(int horizontalPages) {
checkWidget();
if (horizontalPages < 1)
horizontalPages = 1;
this.horizontalPageCount = horizontalPages;
invalidatePageDisplayBounds();
redraw();
}
/**
* Returns how many pages will be displayed in the vertical direction.
* <p>
* <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
* FUTURE.</b>
*
* @return how many pages will be displayed in the vertical direction.
*/
public int getVerticalPageCount() {
checkWidget();
return verticalPageCount;
}
/**
* Sets how many pages will be displayed in the vertical direction.
* <p>
* <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
* FUTURE.</b>
*
* @param verticalPages
* how many pages will be displayed in the vertical direction.
*/
public void setVerticalPageCount(int verticalPages) {
checkWidget();
if (verticalPages < 1)
verticalPages = 1;
this.verticalPageCount = verticalPages;
invalidatePageDisplayBounds();
redraw();
}
/**
* Returns whether the preview lays out pages lazily. Note that total page
* counts in page numbers will not display correctly when this is enabled.
*
* @return whether the preview lays out pages lazily.
*/
public boolean isLazyPageLayout() {
checkWidget();
return lazy;
}
/**
* Sets whether the preview lays out pages lazily. Note that total page
* counts in page numbers will not display correctly when this is enabled.
*
* @param lazy
* whether the preview lays out pages lazily.
*/
public void setLazyPageLayout(boolean lazy) {
checkWidget();
this.lazy = lazy;
}
/**
* Begins lazy loading in the background, invoking the callback runnable
* periodically as pages are laid out.
* <p>
* <b>NOTE:</b> This API is experimental and subject to change.
*
* @param callback
* runnable that will be invoked periodically as pages are laid
* out.
*/
// TODO finalize experimental API
public void startBackgroundLayout(final Runnable callback) {
if (isPageLayoutComplete())
return;
final int DELAY = 10;
getDisplay().timerExec(DELAY, new Runnable() {
public void run() {
if (isDisposed())
return;
if (!isPageLayoutComplete() && pages != null) {
fetchPages(pages.size() + 1);
if (!isPageLayoutComplete()) {
getDisplay().timerExec(DELAY, this);
}
}
callback.run();
}
});
}
private void invalidatePageDisplayBounds() {
pageDisplaySize = null;
pageDisplayLocations = null;
}
private void paint(Event event) {
drawBackground(event);
if (printJob == null || printerData == null)
return;
getPrinter();
getPaperSize();
fetchPages(pageIndex + verticalPageCount * horizontalPageCount);
getPageDisplaySize();
getPageDisplayLocations();
if (printer == null || paperSize == null || pages == null
|| pageDisplaySize == null || pageDisplayLocations == null
|| pageIndex < 0 || pageIndex >= pages.size())
return;
int count = Math.min(verticalPageCount * horizontalPageCount, pages
.size()
- pageIndex);
for (int i = 0; i < count; i++) {
paintPage(event, (PrintPiece) pages.get(pageIndex + i),
pageDisplayLocations[i]);
}
}
private void paintPage(Event event, PrintPiece page, Point location) {
// Check whether any "paper" is in the dirty region
Rectangle rectangle = new Rectangle(location.x, location.y,
pageDisplaySize.x, pageDisplaySize.y);
Rectangle dirtyBounds = new Rectangle(event.x, event.y, event.width,
event.height);
Rectangle dirtyPaperBounds = dirtyBounds.intersection(rectangle);
if (dirtyPaperBounds.width == 0 || dirtyPaperBounds.height == 0)
return;
Image printerImage = null;
GC printerGC = null;
Transform printerTransform = null;
Image displayImage = null;
try {
printerImage = new Image(printer, dirtyPaperBounds.width,
dirtyPaperBounds.height);
printerGC = new GC(printerImage);
configureAntialiasing(printerGC);
printerTransform = new Transform(printer);
printerGC.getTransform(printerTransform);
printerTransform.translate(rectangle.x - dirtyPaperBounds.x,
rectangle.y - dirtyPaperBounds.y);
printerTransform.scale((float) rectangle.width
/ (float) paperSize.x, (float) rectangle.height
/ (float) paperSize.y);
printerGC.setTransform(printerTransform);
page.paint(printerGC, 0, 0);
displayImage = new Image(event.display, printerImage.getImageData());
event.gc.drawImage(displayImage, dirtyPaperBounds.x,
dirtyPaperBounds.y);
} finally {
disposeResources(printerImage, printerGC, printerTransform,
displayImage, page);
}
}
private void disposeResources(Image printerImage, GC printerGC,
Transform printerTransform, Image displayImage, PrintPiece page) {
if (printerImage != null)
printerImage.dispose();
if (displayImage != null)
displayImage.dispose();
if (printerGC != null)
printerGC.dispose();
if (printerTransform != null)
printerTransform.dispose();
page.dispose();
}
private void configureAntialiasing(GC printerGC) {
printerGC.setAdvanced(true);
printerGC.setAntialias(SWT.ON);
printerGC.setTextAntialias(SWT.ON);
printerGC.setInterpolation(SWT.HIGH);
}
private Printer getPrinter() {
if (printer == null && printerData != null) {
printer = new Printer(printerData);
PaperClips.startDummyJob(printer, ""); //$NON-NLS-1$
disposePages(); // just in case
pageDisplaySize = null;
pageDisplayLocations = null;
}
return printer;
}
private GC getGC() {
if (gc == null && printer != null) {
gc = new GC(printer);
gc.setAdvanced(true);
}
return gc;
}
private boolean orientationRequiresRotate() {
int orientation = printJob.getOrientation();
Rectangle bounds = PaperClips.getPaperBounds(printer);
return (orientation == PaperClips.ORIENTATION_PORTRAIT && bounds.width > bounds.height)
|| (orientation == PaperClips.ORIENTATION_LANDSCAPE && bounds.height > bounds.width);
}
private Point getPaperSize() {
Printer printer = getPrinter();
if (paperSize == null && printer != null && printJob != null) {
Rectangle paperBounds = PaperClips.getPaperBounds(printer);
this.paperSize = orientationRequiresRotate() ? new Point(
paperBounds.height, paperBounds.width) : new Point(
paperBounds.width, paperBounds.height);
}
return paperSize;
}
private void fetchPages(int endIndex) {
if (getPrintJob() == null || getPrinter() == null)
return;
if (pageEnumeration == null) {
if (getGC() == null)
return;
pageEnumeration = PaperClips.getPageEnumeration(printJob, printer,
gc);
}
if (pages == null)
pages = new ArrayList();
boolean doRotate = orientationRequiresRotate();
boolean allPages = endIndex == ALL_PAGES || !lazy;
while (pageEnumeration.hasNext()
&& (allPages || pages.size() < endIndex)) {
PrintPiece page = pageEnumeration.nextPage();
if (page != null) {
if (doRotate)
page = new RotateClockwisePrintPiece(printer, page);
pages.add(page);
}
}
if (!pageEnumeration.hasNext())
disposeGC();
}
private void drawBackground(Event event) {
Color oldBackground = event.gc.getBackground();
Color bg = event.display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
try {
event.gc.setBackground(bg);
event.gc.fillRectangle(event.x, event.y, event.width, event.height);
event.gc.setBackground(oldBackground);
} finally {
bg.dispose();
}
}
/**
* Calculates the absolute scale that the print preview is displaying at. If
* either of the fitHorizontal or fitVertical properties are true, this is
* the scale allows the page to fit within this control's current bounds.
* Otherwise the value of the scale property is returned.
*
* @return the absolute scale that the print preview is displaying at.
*/
public float getAbsoluteScale() {
checkWidget();
return getAbsoluteScale(getSize());
}
/**
* Returns a Rectangle whose x, y, width, and height fields respectively
* indicate the margin at the left, top, right, and bottom edges of the
* control.
* <p>
* <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
* FUTURE.</b>
*
* @return a Rectangle whose x, y, width, and height fields respectively
* indicate the margin at the left, top, right, and bottom edges of
* the control.
*/
public Rectangle getMargins() {
checkWidget();
return new Rectangle(margins.x, margins.y, margins.width,
margins.height);
}
/**
* Sets the margins at each edge of the control to the argument.
* <p>
* <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
* FUTURE.</b>
*
* @param margins
* a Rectangle whose x, y, width, and height fields respectively
* indicate the margin at the left, top, right, and bottom edges
* of the control.
*/
public void setMargins(Rectangle margins) {
checkWidget();
if (margins == null)
SWT.error(SWT.ERROR_NULL_ARGUMENT);
this.margins = new Rectangle(margins.x, margins.y, margins.width,
margins.height);
invalidatePageDisplayBounds();
redraw();
}
/**
* Returns a Point whose x and y fields respectively indicate the horizontal
* and vertical spacing between pages on the control.
* <p>
* <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
* FUTURE.</b>
*
* @return a Point whose x and y fields respectively indicate the horizontal
* and vertical spacing between pages on the control.
*/
public Point getPageSpacing() {
return new Point(pageSpacing.x, pageSpacing.y);
}
/**
* Sets the horizontal and vertical spacing between pages to the argument.
* <p>
* <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
* FUTURE.</b>
*
* @param pageSpacing
* a Point whose x and y fields respectively indicate the
* horizontal and vertical spacing between pages on the control.
*/
public void setPageSpacing(Point pageSpacing) {
checkWidget();
if (pageSpacing == null)
SWT.error(SWT.ERROR_NULL_ARGUMENT);
this.pageSpacing = new Point(pageSpacing.x, pageSpacing.y);
invalidatePageDisplayBounds();
redraw();
}
private Point getBoilerplateSize() {
return new Point(margins.x + margins.width + (horizontalPageCount - 1)
* pageSpacing.x, margins.y + margins.height
+ (verticalPageCount - 1) * pageSpacing.y);
}
private float getAbsoluteScale(Point controlSize) {
float result = scale;
if (getPrinter() != null && (fitHorizontal || fitVertical)) {
Rectangle trim = computeTrim(0, 0, 0, 0);
controlSize.x -= trim.width;
controlSize.y -= trim.height;
Point boilerplate = getBoilerplateSize();
controlSize.x -= boilerplate.x;
controlSize.x /= horizontalPageCount;
controlSize.y -= boilerplate.y;
controlSize.y /= verticalPageCount;
Point displayDPI = getDisplay().getDPI();
Point printerDPI = getPrinter().getDPI();
Point paperSize = getPaperSize();
if (fitHorizontal) {
float screenWidth = (float) controlSize.x
/ (float) displayDPI.x; // inches
float paperWidth = (float) paperSize.x / (float) printerDPI.x; // inches
float scaleX = screenWidth / paperWidth;
if (fitVertical) {
float screenHeight = (float) controlSize.y
/ (float) displayDPI.y; // inches
float paperHeight = (float) paperSize.y
/ (float) printerDPI.y; // inches
float scaleY = screenHeight / paperHeight;
result = Math.min(scaleX, scaleY);
} else {
result = scaleX;
}
} else {
float screenHeight = (float) controlSize.y
/ (float) displayDPI.y; // inches
float paperHeight = (float) paperSize.y / (float) printerDPI.y; // inches
float scaleY = screenHeight / paperHeight;
result = scaleY;
}
}
return result;
}
private Point getPageDisplaySize() {
if (pageDisplaySize == null) {
Point size = getSize();
Point displayDPI = getDisplay().getDPI();
Point printerDPI = printer.getDPI();
float absoluteScale = getAbsoluteScale(size);
float scaleX = absoluteScale * displayDPI.x / printerDPI.x;
float scaleY = absoluteScale * displayDPI.y / printerDPI.y;
pageDisplaySize = new Point((int) (scaleX * paperSize.x),
(int) (scaleY * paperSize.y));
}
return pageDisplaySize;
}
private Point[] getPageDisplayLocations() {
if (pageDisplayLocations == null) {
// Center pages horizontally
Rectangle clientArea = getClientArea();
int x0 = clientArea.x + margins.x;
clientArea.width -= getBoilerplateSize().x;
clientArea.width -= (pageDisplaySize.x * horizontalPageCount);
if (clientArea.width > 0)
x0 += clientArea.width / 2;
pageDisplayLocations = new Point[horizontalPageCount
* verticalPageCount];
int y = clientArea.y + margins.y;
for (int r = 0; r < verticalPageCount; r++) {
int x = x0;
for (int c = 0; c < horizontalPageCount; c++) {
pageDisplayLocations[r * horizontalPageCount + c] = new Point(
x, y);
x += pageDisplaySize.x + pageSpacing.x;
}
y += pageDisplaySize.y + pageSpacing.y;
}
}
return pageDisplayLocations;
}
private void disposePages() {
if (pages != null) {
pageEnumeration = null;
for (int i = 0; i < pages.size(); i++)
((PrintPiece) pages.get(i)).dispose();
pages = null;
paperSize = null;
invalidatePageDisplayBounds();
}
}
private void disposePrinter() {
disposePages();
if (printer != null) {
disposeGC();
PaperClips.endDummyJob(printer);
printer.dispose();
printer = null;
}
}
private void disposeGC() {
if (gc != null) {
gc.dispose();
gc = null;
}
}
private void disposeResources() {
disposePages();
disposePrinter();
}
public Point computeSize(int wHint, int hHint, boolean changed) {
checkWidget();
Point size = new Point(wHint, hHint);
fetchPages(horizontalPageCount * verticalPageCount);
if (getPrinter() == null || pages == null) {
Point boilerplate = getBoilerplateSize();
if (wHint == SWT.DEFAULT)
size.x = boilerplate.x;
if (hHint == SWT.DEFAULT)
size.y = boilerplate.y;
return addTrim(size);
}
double scale;
if (wHint != SWT.DEFAULT) {
if (hHint != SWT.DEFAULT) {
return addTrim(size);
}
size.y = Integer.MAX_VALUE;
scale = getAbsoluteScale(size);
} else if (hHint != SWT.DEFAULT) {
size.x = Integer.MAX_VALUE;
scale = getAbsoluteScale(size);
} else {
scale = this.scale;
}
return computeSize(scale);
}
/**
* Returns the control size needed to display a full page at the given
* scale.
*
* @param scale
* the absolute scale. A scale of 1, for example, yields a
* "life size" preview.
* @return the control size needed to display a full page at the given
* scale.
*/
public Point computeSize(double scale) {
checkWidget();
Point size = getBoilerplateSize();
fetchPages(horizontalPageCount * verticalPageCount);
if (getPrinter() != null && pages != null) {
Point displayDPI = getDisplay().getDPI();
Point printerDPI = getPrinter().getDPI();
Point paperSize = getPaperSize();
size.x += horizontalPageCount
* (int) (scale * paperSize.x * displayDPI.x / printerDPI.x);
size.y += verticalPageCount
* (int) (scale * paperSize.y * displayDPI.y / printerDPI.y);
}
return addTrim(size);
}
private Point addTrim(Point size) {
Rectangle trim = computeTrim(0, 0, 0, 0);
return new Point(size.x + trim.width, size.y + trim.height);
}
}