blob: 8c20d5a556fdf67d49de6af14b8cf843c8428542 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2008, 2009 IBM Corporation 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:
* IBM Corporation - initial API and implementation
****************************************************************************/
package org.eclipse.gmf.runtime.diagram.ui.printing.internal.util;
import java.util.Iterator;
import org.eclipse.core.runtime.Assert;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.SWTGraphics;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gmf.runtime.common.ui.util.DisplayUtils;
import org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramRootEditPart;
import org.eclipse.gmf.runtime.diagram.ui.internal.pagesetup.PageInfoHelper;
import org.eclipse.gmf.runtime.diagram.ui.internal.pagesetup.PageInfoHelper.PageMargins;
import org.eclipse.gmf.runtime.diagram.ui.internal.properties.WorkspaceViewerProperties;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditor;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer;
import org.eclipse.gmf.runtime.diagram.ui.util.DiagramEditorUtil;
import org.eclipse.gmf.runtime.draw2d.ui.internal.graphics.MapModeGraphics;
import org.eclipse.gmf.runtime.draw2d.ui.internal.graphics.PrinterGraphics;
import org.eclipse.gmf.runtime.draw2d.ui.internal.graphics.ScaledGraphics;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
import org.eclipse.gmf.runtime.draw2d.ui.render.internal.graphics.RenderedScaledGraphics;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
/**
* This class supports printing using the SWT printing constructs.
* Much of the paging code was derived from the previous <code> DiagramPrinter </code>.
*
* @author Wayne Diu, wdiu
*/
public class SWTDiagramPrinter extends DiagramPrinter
implements Runnable {
protected Printer printer;
private GC gc;
private PrinterGraphics printerGraphics;
private Point printerOffset;
private Rectangle logicalClientArea;
/**
* Creates a new instance. The following variables must be initialized
* before calling <code>run()</code>:
* <li><code>printer</code></li>
* <li><code>display_dpi</code></li>
* <li><code>diagrams</code></li>
* @param mm <code>IMapMode</code> to do the coordinate mapping
*/
public SWTDiagramPrinter(PreferencesHint preferencesHint, IMapMode mm) {
super(preferencesHint, mm);
}
/**
* Creates a new instance. The following variables must be initialized
* before calling <code>run()</code>:
* <li><code>printer</code></li>
* <li><code>display_dpi</code></li>
* <li><code>diagrams</code></li>
* @param mapMode <code>IMapMode</code> to do the coordinate mapping
*/
public SWTDiagramPrinter(PreferencesHint preferencesHint) {
this(preferencesHint, MapModeUtil.getMapMode());
}
/**
* Sets the printer.
*
* @param printer
* The printer to set.
*/
public void setPrinter(Printer printer) {
this.printer = printer;
}
/**
* Prints the contents of the diagram editor part.
*/
public void run() {
assert null != printer : "printer must be set"; //$NON-NLS-1$
if (!(printer.startJob("Printing"))) { //$NON-NLS-1$
return;
}
assert diagrams != null;
Iterator<Diagram> it = diagrams.iterator();
Shell shell = new Shell();
try {
while (it.hasNext()) {
Object obj = it.next();
//the diagrams List is only supposed to have Diagram objects
Assert.isTrue(obj instanceof Diagram);
Diagram diagram = (Diagram)obj;
DiagramEditor openedDiagramEditor = DiagramEditorUtil
.findOpenedDiagramEditorForID(ViewUtil
.getIdStr(diagram));
DiagramEditPart dgrmEP = openedDiagramEditor == null ? PrintHelperUtil
.createDiagramEditPart(diagram, preferencesHint, shell)
: openedDiagramEditor.getDiagramEditPart();
boolean loadedPreferences = openedDiagramEditor != null || PrintHelperUtil.initializePreferences(dgrmEP, preferencesHint);
RootEditPart rep = dgrmEP.getRoot();
if (rep instanceof DiagramRootEditPart)
this.mapMode = ((DiagramRootEditPart)rep).getMapMode();
initialize();
IPreferenceStore pref = null;
assert dgrmEP.getViewer() instanceof DiagramGraphicalViewer;
pref = ((DiagramGraphicalViewer)dgrmEP.getViewer()).getWorkspaceViewerPreferenceStore();
if (pref.getBoolean(WorkspaceViewerProperties.PREF_USE_WORKSPACE_SETTINGS)) {
//get workspace settings...
if (dgrmEP.getDiagramPreferencesHint().getPreferenceStore() != null){
pref = (IPreferenceStore)dgrmEP.getDiagramPreferencesHint().getPreferenceStore();
}
}
// Ensure the preference value is properly updated when the
// user overrides the preference store with values from the
// print settings.
// Printing and preview use the preference settings, to
// calculate page size.
PrinterData printerData = printer.getPrinterData();
if (printerData != null) {
boolean useLandscape = (printerData.orientation == PrinterData.LANDSCAPE);
if (pref.getBoolean(WorkspaceViewerProperties.PREF_USE_LANDSCAPE) != useLandscape) {
pref.setValue(
WorkspaceViewerProperties.PREF_USE_LANDSCAPE,
useLandscape);
}
if (pref.getBoolean(WorkspaceViewerProperties.PREF_USE_PORTRAIT) == useLandscape) {
pref.setValue(
WorkspaceViewerProperties.PREF_USE_PORTRAIT,
!useLandscape);
}
}
doPrintDiagram(dgrmEP, loadedPreferences, pref);
dispose();
}
printer.endJob();
} finally {
shell.dispose();
}
}
/**
* Prints to scale or prints to rows x columns pages
*/
protected void doPrintDiagram(DiagramEditPart dgrmEP, boolean loadedPreferences, IPreferenceStore fPreferences) {
this.graphics.pushState();
if (isScaledPercent) {
printToScale(dgrmEP, loadedPreferences, fPreferences);
} else {
printToPages(dgrmEP, loadedPreferences, fPreferences);
}
this.graphics.popState();
}
protected void initialize() {
assert null != printer : "printer must be set"; //$NON-NLS-1$
//check for rtl orientation...
int style = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell().getStyle();
if ((style & SWT.MIRRORED) != 0)
this.gc = new GC(printer, SWT.RIGHT_TO_LEFT);
else
this.gc = new GC(printer);
gc.setXORMode(false);
this.swtGraphics = new SWTGraphics(gc);
this.printerGraphics = createPrinterGraphics(swtGraphics);
this.graphics = createMapModeGraphics(printerGraphics);
this.graphics.scale(computePrinterDisplayScale());
this.logicalClientArea = this.graphics.getClip(new Rectangle(
this.printer.getClientArea()));
}
/**
* Usually, the printable area is less than the page.
* This method returns the offset for each x margin and each y margin.
* x margins are left and right
* y margins are top and bottom
*
* We'll assume the left and right offsets are the same and the
* top and bottom offsets are the same.
*
* @return Point with x and y offsets
*/
protected Point getPrinterOffset() {
if (printerOffset == null) {
int offsetX = this.printer.getBounds().width
- this.printer.getClientArea().width;
int offsetY = this.printer.getBounds().height
- this.printer.getClientArea().height;
// assume half on each side
offsetX = (int) (getMapMode()
.DPtoLP((int) (offsetX / 2.0f * display_dpi.x / printer.getDPI().x)) / userScale);
offsetY = (int) (getMapMode()
.DPtoLP((int) (offsetY / 2.0f * display_dpi.y / printer.getDPI().y)) / userScale);
printerOffset = new Point(offsetX, offsetY);
}
return printerOffset;
}
/**
* Print the diagram figure using specified scale factor.
*
* @param dgrmEP the DiagramEditPart that will be printed
* @param loadedPreferences true if existing prefs could be loaded
* successfully, false if not and defaults are being used. This parameter
* is important to obtain the correct page break bounds.
* @param fPreferences the preferenceStore that could either contain
* existing preferences or defaults
*/
protected void printToScale(DiagramEditPart dgrmEP, boolean loadedPreferences, IPreferenceStore fPreferences) {
assert null != printer : "printer must be set"; //$NON-NLS-1$
Rectangle figureBounds = PrintHelperUtil.getPageBreakBounds(dgrmEP, loadedPreferences);
org.eclipse.draw2d.geometry.Point pageBounds = PageInfoHelper.getPageSize(fPreferences, getMapMode());
//translate to offset initial figure position
translated = new Point((int) (-figureBounds.x * userScale), (int) (-figureBounds.y * userScale));
//calculate the number of page rows and columns
int numRows = 0, numCols = 0;
PageMargins margins = PageInfoHelper.getPageMargins(fPreferences, getMapMode());
adjustMargins(margins, userScale, getPrinterOffset());
GC gc_ = new GC(DisplayUtils.getDisplay(), this.gc.getStyle());
gc_.setAntialias(this.gc.getAntialias());
FontData fontData = JFaceResources.getDefaultFont().getFontData()[0];
Font font = new Font(printer, fontData);
org.eclipse.draw2d.geometry.Point pageCount = getPageCount(dgrmEP, figureBounds, pageBounds, true);
numCols = pageCount.x;
numRows = pageCount.y;
//finalRow and finalColumn will be used if we are printing within a page range...
int row = 1, col = 1, finalRow = 0, finalColumn = 0;
if (this.printRangePageSelection) {
//print only the pages specified in the page range...
row = calculateRowFromPage(this.pageFrom, numCols);
col = calculateColumnFromPage(this.pageFrom, numCols, row);
finalRow = calculateRowFromPage(this.pageTo, numCols);
finalColumn = calculateColumnFromPage(this.pageTo, numCols, finalRow);
}
try {
//print the pages in row, column order
for (; row <= numRows; row++) {
for (; col <= numCols; col++) {
printer.startPage();
drawPage(gc_, dgrmEP, fPreferences, figureBounds, margins, font, row, col);
printer.endPage();
if (row == finalRow && col == finalColumn && this.printRangePageSelection == true)
break;
}
if (row == finalRow && col == finalColumn && this.printRangePageSelection == true)
break;
col = 1;
}
} finally {
//must dispose resources
font.dispose();
gc_.dispose();
}
}
/**
* Draw the header and footer
*
* @param gc_,
* a graphics context that is not null which this method will use
* for figuring ouyt the font's extent
* @param figureBounds,
* Rectangle with the bounds of the figure
* @param rowIndex,
* int
* @param colIndex,
* int
*/
protected void drawHeaderAndFooter(GC gc_, DiagramEditPart dgrmEP, Rectangle figureBounds,
Font font, int rowIndex, int colIndex) {
int width = this.logicalClientArea.width;
int height = this.logicalClientArea.height;
this.graphics.pushState(); //draw text, don't make it too small or big
this.graphics.setFont(font);
this.graphics.scale(1.0f / userScale);
this.graphics.translate(-translated.x, -translated.y);
String headerOrFooter = HeaderAndFooterHelper.makeHeaderOrFooterString(
WorkspaceViewerProperties.HEADER_PREFIX, rowIndex, colIndex,
dgrmEP);
this.graphics.drawText(headerOrFooter,
getMapMode().DPtoLP(HeaderAndFooterHelper.LEFT_MARGIN_DP)
+ (width - getMapMode().DPtoLP(gc_.textExtent(headerOrFooter).x))
/ 2, getMapMode().DPtoLP(HeaderAndFooterHelper.TOP_MARGIN_DP));
headerOrFooter = HeaderAndFooterHelper.makeHeaderOrFooterString(
WorkspaceViewerProperties.FOOTER_PREFIX, rowIndex, colIndex,
dgrmEP);
this.graphics.drawText(headerOrFooter,
getMapMode().DPtoLP(HeaderAndFooterHelper.LEFT_MARGIN_DP)
+ (width - getMapMode().DPtoLP(gc_.textExtent(headerOrFooter).x))
/ 2, height - getMapMode().DPtoLP(HeaderAndFooterHelper.BOTTOM_MARGIN_DP));
this.graphics.popState(); //for drawing the text
}
/**
* This method paints a portion of the diagram. (The area painted
* representing one page.)
*
* @param gc_ a graphics context that is not null which this method will use
* for figuring out the font's extent
* @param dgrmEP the DiagramEditPart that will be printed
* @param fPreferences the preferenceStore that could either contain
* existing preferences or defaults
* @param figureBounds the page break bounds we'll have to offset by
* @param font the Font to print the header or footer with
* @param rowIndex index of row we're printing
* @param colIndex index of column we're priniting
* to check if it is the first time the method is getting called for the current
* print.
*/
protected void drawPage(GC gc_, DiagramEditPart dgrmEP,
IPreferenceStore fPreferences, Rectangle figureBounds,
PageMargins margins, Font font, int rowIndex, int colIndex) {
org.eclipse.draw2d.geometry.Point pageSize = PageInfoHelper
.getPageSize(fPreferences, false, getMapMode());
boolean rtlEnabled = ( this.gc !=null) && ((this.gc.getStyle() & SWT.MIRRORED) != 0);
if (rtlEnabled)
{
// draw everything on an offscreen image first and then draw that image
// onto the printer gc...this takes care of certain drawing bugs.
// This causes issues with printing resolution as it uses a display image
// which is typically 72dpi
// This code should be removed when a resolution to Bugzilla 162459 is found
Image image = new Image(DisplayUtils.getDisplay(), getMapMode().LPtoDP(pageSize.x), getMapMode().LPtoDP(pageSize.y));
GC imgGC = new GC(image, (rtlEnabled) ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT);
imgGC.setXORMode(false);
SWTGraphics sg = new SWTGraphics(imgGC);
//for scaling
ScaledGraphics g1 = new RenderedScaledGraphics(sg);
//for himetrics and svg
MapModeGraphics mmg = createMapModeGraphics(g1);
//if mmg's font is null, gc.setFont will use a default font
imgGC.setFont(mmg.getFont());
internalDrawPage(dgrmEP,figureBounds,fPreferences,margins,mmg,rowIndex, colIndex,true);
this.graphics.pushState();
this.graphics.drawImage(image, 0, 0);
this.graphics.popState();
//draw the header and footer after drawing the image to avoid getting the image getting drawn over them
drawHeaderAndFooter(gc_, dgrmEP, figureBounds, font, rowIndex, colIndex);
disposeImageVars(imgGC, image, sg, g1, mmg);
} else {
internalDrawPage(dgrmEP,figureBounds,fPreferences,margins,this.graphics,rowIndex, colIndex,false);
//draw the header and footer after drawing the image to avoid getting the image getting drawn over them
drawHeaderAndFooter(gc_, dgrmEP, figureBounds, font, rowIndex, colIndex);
}
}
protected void internalDrawPage(DiagramEditPart dgrmEP,
Rectangle figureBounds, IPreferenceStore fPreferences,
PageMargins margins, Graphics g, int rowIndex, int colIndex,
boolean RTL_ENABLED) {
org.eclipse.draw2d.geometry.Point pageSize = PageInfoHelper
.getPageSize(fPreferences, false, getMapMode());
int width = pageSize.x, height = pageSize.y;
g.pushState();
g.translate(translated.x, translated.y);
g.scale(userScale);
int translateX = -(width * (colIndex - 1));
int translateY = -(height * (rowIndex - 1));
int scaledTranslateX = (int) (translateX / userScale);
int scaledTranslateY = (int) (translateY / userScale);
int scaledWidth = (int) (width / userScale);
int scaledHeight = (int) (height / userScale);
if (RTL_ENABLED) {
scaledTranslateX += (margins.left * (colIndex - 1))
+ (margins.right * (colIndex));
scaledTranslateY += ((margins.top * rowIndex) + (margins.bottom * (rowIndex - 1)));
} else {
scaledTranslateX += ((margins.left * colIndex) + (margins.right * (colIndex - 1)));
scaledTranslateY += ((margins.top * rowIndex) + (margins.bottom * (rowIndex - 1)));
}
g.translate(scaledTranslateX, scaledTranslateY);
Rectangle clip = new Rectangle(
(scaledWidth - margins.left - margins.right) * (colIndex - 1)
+ figureBounds.x, (scaledHeight - margins.bottom - margins.top)
* (rowIndex - 1) + figureBounds.y, scaledWidth - margins.right
- margins.left, scaledHeight - margins.top - margins.bottom);
g.clipRect(clip);
dgrmEP.getLayer(LayerConstants.PRINTABLE_LAYERS).paint(g);
g.popState();
}
/**
* Print the diagram figure to fit the number and rows and columns
* specified by the user.
*
* @param dgrmEP the DiagramEditPart that will be printed
* @param loadedPreferences true if existing prefs could be loaded
* successfully, false if not and defaults are being used. This parameter
* is important to obtain the correct page break bounds.
* @param fPreferences the preferenceStore that could either contain
* existing preferences or defaults
*/
protected void printToPages(DiagramEditPart dgrmEP,
boolean loadedPreferences, IPreferenceStore fPreferences) {
assert null != printer : "printer must be set"; //$NON-NLS-1$
Rectangle figureBounds = PrintHelperUtil.getPageBreakBounds(dgrmEP,
loadedPreferences);
PageMargins margins = PageInfoHelper.getPageMargins(fPreferences, getMapMode());
//do not include margins
org.eclipse.draw2d.geometry.Point pageBounds = PageInfoHelper
.getPageSize(fPreferences, getMapMode());
org.eclipse.draw2d.geometry.Point pageCount = getPageCount(dgrmEP, figureBounds, pageBounds, false);
int numCols = pageCount.x;
int numRows = pageCount.y;
float actualWidth = 0;
float actualHeight = 0;
if (this.rows==1 && this.columns==1 && fitToPage){
figureBounds = dgrmEP.getChildrenBounds();
actualWidth = figureBounds.width;
actualHeight = figureBounds.height;
}else {
actualWidth = numCols * pageBounds.x;
actualHeight = numRows * pageBounds.y;
}
int totalHeight = (this.rows * pageBounds.y);
int totalWidth = (this.columns * pageBounds.x);
float vScale = totalHeight / actualHeight;
float hScale = totalWidth / actualWidth;
this.userScale = Math.min(hScale, vScale);
// translate to offset figure position
translated = new Point((int) (-figureBounds.x * userScale),
(int) (-figureBounds.y * userScale));
adjustMargins(margins, userScale, getPrinterOffset());
GC gc_ = new GC(DisplayUtils.getDisplay());
FontData fontData = JFaceResources.getDefaultFont().getFontData()[0];
Font font = new Font(printer, fontData);
int row = 1, col = 1, finalRow = 0, finalColumn = 0;
if (this.printRangePageSelection) {
//print only the pages specified in the page range
//this corresponds to the physical pages, not the print range of pages on one physical page.
row = calculateRowFromPage(this.pageFrom, this.columns);
col = calculateColumnFromPage(this.pageFrom, this.columns, row);
finalRow = calculateRowFromPage(this.pageTo, this.columns);
finalColumn = calculateColumnFromPage(this.pageTo, this.columns, finalRow);
}
try {
// print the pages in row, column order
for (; row <= rows; row++) {
for (; col <= columns; col++) {
printer.startPage();
drawPage(gc_, dgrmEP, fPreferences, figureBounds, margins,
font, row, col);
printer.endPage();
if (row == finalRow && col == finalColumn && this.printRangePageSelection == true)
break;
}
if (row == finalRow && col == finalColumn && this.printRangePageSelection == true)
break;
col = 1;
}
} finally {
// must dispose resources
font.dispose();
gc_.dispose();
}
}
/**
* Return scale factor between printer and display.
*
* @return float
*/
private float computePrinterDisplayScale() {
assert null != printer : "printer must be set"; //$NON-NLS-1$
assert null != display_dpi : "display_dpi must be set"; //$NON-NLS-1$
Point dpi = printer.getDPI();
float scale = dpi.x / (float) display_dpi.x;
return scale;
}
/**
* Disposes of the resources.
*/
protected void dispose() {
if (this.graphics != null) {
try {
this.graphics.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
this.graphics = null;
}
}
if (this.printerGraphics != null) {
try {
this.printerGraphics.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
this.printerGraphics = null;
}
}
if (this.swtGraphics != null) {
try {
this.swtGraphics.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
this.swtGraphics = null;
}
}
if (this.gc != null) {
try {
this.gc.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
this.gc = null;
}
}
//reset the printer offset, just in case the next diagram to be printed
//uses a different map mode.
printerOffset = null;
}
private void disposeImageVars(GC imgGC, Image image, SWTGraphics sg,
ScaledGraphics g1, MapModeGraphics mmg) {
if (mmg != null) {
try {
mmg.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
mmg = null;
}
}
if (g1 != null) {
try {
g1.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
g1 = null;
}
}
if (sg != null) {
try {
sg.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
sg = null;
}
}
if (imgGC != null) {
try {
imgGC.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
imgGC = null;
}
}
if (image != null) {
try {
image.dispose();
}
catch (NullPointerException e) {
//do nothing
}
finally {
image = null;
}
}
}
/**
* Creates the <code>PrinterGraphics</code>.
*
* @param theGraphics
* the <code>Graphics</code> object
* @return the new <code>PrinterGraphics</code>
*/
protected PrinterGraphics createPrinterGraphics(Graphics theGraphics) {
return new PrinterGraphics(theGraphics, printer, true);
}
/**
* Adjust the given PageMargins by the scale and offset
*
* @param margins PageMargins to adjust
* @param scale margins will be scaled by this amount
* @param offset to adjust margins by
*/
protected void adjustMargins(PageMargins margins, float scale, Point offset) {
//scale
margins.left /= scale;
margins.top /= scale;
margins.right /= scale;
margins.bottom /= scale;
//offsets
margins.left -= offset.x;
margins.right += offset.x;
margins.top -= offset.y;
margins.bottom += offset.y;
// this is more readable than doing Math.min for all the above
if (margins.left < 0)
margins.left = 0;
if (margins.right < 0)
margins.right = 0;
if (margins.top < 0)
margins.top = 0;
if (margins.bottom < 0)
margins.bottom = 0;
}
}