/*
* This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com>
* Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/)
*/
/*
* @(#)PrintJob2D.java 1.13 03/01/23
*
* Copyright 2003 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
* Copyright 1998, 1999, 2000 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the proprietary information of Sun Microsystems, Inc.
* Use is subject to license terms.
*/
package sun.print;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.JobAttributes;
import java.awt.PrintJob;
import java.awt.PageAttributes;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Properties;
import javax.print.PrintService;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.ResolutionSyntax;
import javax.print.attribute.Size2DSyntax;
import javax.print.attribute.standard.Chromaticity;
import javax.print.attribute.standard.Copies;
import javax.print.attribute.standard.Destination;
import javax.print.attribute.standard.JobName;
import javax.print.attribute.standard.MediaSize;
import javax.print.attribute.standard.PrintQuality;
import javax.print.attribute.standard.PrinterResolution;
import javax.print.attribute.standard.SheetCollate;
import javax.print.attribute.standard.Sides;
import sun.awt.print.PrintDialog;
import sun.awt.print.AwtPrintControl;
/**
* A class which initiates and executes a print job using
* the underlying PrinterJob graphics conversions.
*
* @see Toolkit#getPrintJob
*
*/
public class PrintJob2D extends PrintJob implements Printable, Runnable {
private Frame frame;
private String docTitle = "";
private JobAttributes jobAttributes;
private PageAttributes pageAttributes;
private PrintRequestAttributeSet attributes;
/*
* Displays the native or cross-platform dialog and allows the
* user to update job & page attributes
*/
private AwtPrintControl printControl;
/**
* The PrinterJob being uses to implement the PrintJob.
*/
private PrinterJob printerJob;
/**
* The size of the page being used for the PrintJob.
*/
private PageFormat pageFormat;
/**
* The PrinterJob and the application run on different
* threads and communicate through a pair of message
* queues. This queue is the list of Graphics that
* the PrinterJob has requested rendering for, but
* for which the application has not yet called getGraphics().
* In practice the length of this message queue is always
* 0 or 1.
*/
private MessageQ graphicsToBeDrawn = new MessageQ("tobedrawn");
/**
* Used to communicate between the application's thread
* and the PrinterJob's thread this message queue holds
* the list of Graphics into which the application has
* finished drawing, but that have not yet been returned
* to the PrinterJob thread. Again, in practice, the
* length of this message queue is always 0 or 1.
*/
private final MessageQ graphicsDrawn = new MessageQ("drawn");
/**
* The last Graphics returned to the application via
* getGraphics. This is the Graphics into which the
* application is currently drawing.
*/
private Graphics2D currentGraphics;
/**
* The zero based index of the page currently being rendered
* by the application.
*/
private int pageIndex = -1;
/**
* The thread on which PrinterJob is running.
* This is different than the applications thread.
*/
private Thread printerJobThread;
public PrintJob2D(Frame frame, String doctitle,
final Properties props) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPrintJobAccess();
}
if (frame == null) {
throw new NullPointerException("Frame must not be null");
}
this.docTitle = (doctitle == null) ? "" : docTitle;
printControl = new AwtPrintControl(frame, doctitle, props);
}
public PrintJob2D(Frame frame, String doctitle,
JobAttributes jobAttributes,
PageAttributes pageAttributes) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPrintJobAccess();
}
if (frame == null &&
(jobAttributes == null ||
jobAttributes.getDialog() == JobAttributes.DialogType.NATIVE)) {
throw new NullPointerException("Frame must not be null");
}
this.docTitle = (doctitle == null) ? "" : docTitle;
this.jobAttributes = jobAttributes;
this.pageAttributes = pageAttributes;
printControl = new AwtPrintControl(frame, doctitle,
jobAttributes, pageAttributes);
}
public boolean printDialog() {
boolean proceedWithPrint = printControl.displayDialog();
if (proceedWithPrint) {
jobAttributes = printControl.getJobAttributes();
pageAttributes = printControl.getPageAttributes();
printerJob = PrinterJob.getPrinterJob();
if (printerJob == null) {
return false;
}
copyAttributes();
if (pageFormat == null) {
pageFormat = printerJob.defaultPage();
}
printerJob.setPrintable(this, pageFormat);
}
return proceedWithPrint;
}
private PrintService findNamedPrintService(String printerName) {
PrintService service = printerJob.getPrintService();
if (service != null && printerName.equals(service.getName())) {
return service;
} else {
PrintService []services = PrinterJob.lookupPrintServices();
for (int i=0; i<services.length; i++) {
if (printerName.equals(services[i].getName())) {
return services[i];
}
}
}
return null;
}
/* From JobAttributes we will copy job name and duplex printing
* and destination.
* The majority of the rest of the attributes are reflected
* attributes.
*
* From PageAttributes we copy color, media size, orientation,
* origin type, resolution and print quality.
* We use the media, orientation in creating the page format, and
* the origin type to set its imageable area.
*
* REMIND: Interpretation of resolution, additional media sizes.
*/
private void copyAttributes() {
attributes = new HashPrintRequestAttributeSet();
printerJob.setJobName(docTitle);
attributes.add(new JobName(docTitle, null));
JobAttributes.DestinationType dest = jobAttributes.getDestination();
if (dest == JobAttributes.DestinationType.PRINTER) {
String printerName = jobAttributes.getPrinter();
if (printerName != null && printerName != "") {
PrintService service = findNamedPrintService(printerName);
if (service != null) {
try {
printerJob.setPrintService(service);
} catch (PrinterException e) {
}
}
}
} else {
String fileName = jobAttributes.getFileName();
if (fileName == null) {
fileName = "out.prn";
}
URI uri = (new File(fileName)).toURI();
attributes.add(new Destination(uri));
}
JobAttributes.SidesType sType = jobAttributes.getSides();
if (sType == JobAttributes.SidesType.TWO_SIDED_LONG_EDGE) {
attributes.add(Sides.TWO_SIDED_LONG_EDGE);
} else if (sType == JobAttributes.SidesType.TWO_SIDED_SHORT_EDGE) {
attributes.add(Sides.TWO_SIDED_SHORT_EDGE);
} else if (sType == JobAttributes.SidesType.ONE_SIDED) {
attributes.add(Sides.ONE_SIDED);
}
JobAttributes.MultipleDocumentHandlingType hType =
jobAttributes.getMultipleDocumentHandling();
if (hType ==
JobAttributes.MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES) {
attributes.add(SheetCollate.COLLATED);
} else {
attributes.add(SheetCollate.UNCOLLATED);
}
attributes.add(new Copies(jobAttributes.getCopies()));
if (pageAttributes.getColor() == PageAttributes.ColorType.COLOR) {
attributes.add(Chromaticity.COLOR);
} else {
attributes.add(Chromaticity.MONOCHROME);
}
pageFormat = printerJob.defaultPage();
if (pageAttributes.getOrientationRequested() ==
PageAttributes.OrientationRequestedType.LANDSCAPE) {
pageFormat.setOrientation(PageFormat.LANDSCAPE);
}
int []mSize = AwtPrintControl.getSize(pageAttributes.getMedia());
Paper paper = new Paper();
paper.setSize(mSize[0], mSize[1]);
if (pageAttributes.getOrigin()==PageAttributes.OriginType.PRINTABLE) {
// AWT uses 1/4" borders by default
paper.setImageableArea(18.0, 18.0,
paper.getWidth()-36.0,
paper.getHeight()-36.0);
} else {
paper.setImageableArea(0.0,0.0,paper.getWidth(),paper.getHeight());
}
pageFormat.setPaper(paper);
PageAttributes.PrintQualityType qType =
pageAttributes.getPrintQuality();
if (qType == PageAttributes.PrintQualityType.DRAFT) {
attributes.add(PrintQuality.DRAFT);
} else if (qType == PageAttributes.PrintQualityType.NORMAL) {
attributes.add(PrintQuality.NORMAL);
} else if (qType == PageAttributes.PrintQualityType.HIGH) {
attributes.add(PrintQuality.HIGH);
}
}
/**
* Gets a Graphics object that will draw to the next page.
* The page is sent to the printer when the graphics
* object is disposed. This graphics object will also implement
* the PrintGraphics interface.
* @see PrintGraphics
*/
public Graphics getGraphics() {
Graphics printGraphics = null;
synchronized (this) {
++pageIndex;
if (pageIndex == 0) {
/* We start a thread on which the PrinterJob will run.
* The PrinterJob will ask for pages on that thread
* and will use a message queue to fulfill the application's
* requests for a Graphics on the application's
* thread.
*/
startPrinterJobThread();
}
notify();
}
/* If the application has already been handed back
* a graphics then we need to put that graphics into
* the drawn queue so that the PrinterJob thread can
* return to the print system.
*/
if (currentGraphics != null) {
graphicsDrawn.append(currentGraphics);
currentGraphics = null;
}
/* We'll block here until a new graphics becomes
* available.
*/
currentGraphics = graphicsToBeDrawn.pop();
if (currentGraphics instanceof PeekGraphics) {
( (PeekGraphics) currentGraphics).setAWTDrawingOnly();
graphicsDrawn.append(currentGraphics);
currentGraphics = graphicsToBeDrawn.pop();
}
if (currentGraphics != null) {
/* In the PrintJob API, the origin is at the upper-
* left of the imageable area when using the new "printable"
* origin attribute, otherwise its the physical origin (for
* backwards compatibility. We emulate this by createing
* a PageFormat which matches and then performing the
* translate to the origin. This is a no-op if physical
* origin is specified.
*/
currentGraphics.translate(pageFormat.getImageableX(),
pageFormat.getImageableY());
/* Scale to accomodate AWT's notion of printer resolution */
double awtScale = 72.0/getPageResolutionInternal();
currentGraphics.scale(awtScale, awtScale);
/* The caller wants a Graphics instance but we do
* not want them to make 2D calls. We can't hand
* back a Graphics2D. The returned Graphics also
* needs to implement PrintGraphics, so we wrap
* the Graphics2D instance. The PrintJob API has
* the application dispose of the Graphics so
* we create a copy of the one returned by PrinterJob.
*/
printGraphics = new ProxyPrintGraphics(currentGraphics.create(),
this);
}
return printGraphics;
}
/**
* Returns the dimensions of the page in pixels.
* The resolution of the page is chosen so that it
* is similar to the screen resolution.
* Except (since 1.3) when the application specifies a resolution.
* In that case it it scaled accordingly.
*/
public Dimension getPageDimension() {
double wid, hgt, scale;
if (pageAttributes != null &&
pageAttributes.getOrigin()==PageAttributes.OriginType.PRINTABLE) {
wid = pageFormat.getImageableWidth();
hgt = pageFormat.getImageableHeight();
} else {
wid = pageFormat.getWidth();
hgt = pageFormat.getHeight();
}
scale = getPageResolutionInternal() / 72.0;
return new Dimension((int)(wid * scale), (int)(hgt * scale));
}
private double getPageResolutionInternal() {
if (pageAttributes != null) {
int []res = pageAttributes.getPrinterResolution();
if (res[2] == 3) {
return res[0];
} else /* if (res[2] == 4) */ {
return (res[0] * 2.54);
}
} else {
return 72.0;
}
}
/**
* Returns the resolution of the page in pixels per inch.
* Note that this doesn't have to correspond to the physical
* resolution of the printer.
*/
public int getPageResolution() {
return (int)getPageResolutionInternal();
}
/**
* Returns true if the last page will be printed first.
*/
public boolean lastPageFirst() {
return false;
}
/**
* Ends the print job and does any necessary cleanup.
*/
public synchronized void end() {
if (currentGraphics == null)
return;
graphicsDrawn.append(currentGraphics);
/* Close the message queues so that nobody is stuck
* waiting for one.
*/
graphicsToBeDrawn.close();
graphicsDrawn.closeWhenEmpty();
if (printerJobThread != null && printerJobThread.isAlive()) {
try {
printerJobThread.join();
}
catch (InterruptedException e) {
}
}
currentGraphics = null;
}
/**
* Ends this print job once it is no longer referenced.
* @see #end
*/
public void finalize() {
end();
}
/**
* Prints the page at the specified index into the specified
* {@link Graphics} context in the specified
* format. A <code>PrinterJob</code> calls the
* <code>Printable</code> interface to request that a page be
* rendered into the context specified by
* <code>graphics</code>. The format of the page to be drawn is
* specified by <code>pageFormat</code>. The zero based index
* of the requested page is specified by <code>pageIndex</code>.
* If the requested page does not exist then this method returns
* NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned.
* The <code>Graphics</code> class or subclass implements the
* {@link PrinterGraphics} interface to provide additional
* information. If the <code>Printable</code> object
* aborts the print job then it throws a {@link PrinterException}.
* @param graphics the context into which the page is drawn
* @param pageFormat the size and orientation of the page being drawn
* @param pageIndex the zero based index of the page to be drawn
* @return PAGE_EXISTS if the page is rendered successfully
* or NO_SUCH_PAGE if <code>pageIndex</code> specifies a
* non-existent page.
* @exception java.awt.print.PrinterException
* thrown when the print job is terminated.
*/
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
throws PrinterException {
int result;
/* This method will be called by the PrinterJob on a thread other
* that the application's thread. We hold on to the graphics
* until we can rendevous with the application's thread and
* hand over the graphics. The application then does all the
* drawing. When the application is done drawing we rendevous
* again with the PrinterJob thread and release the Graphics
* so that it knows we are done.
*/
/* Add the graphics to the message queue of graphics to
* be rendered. This is really a one slot queue. The
* application's thread will come along and remove the
* graphics from the queue when the app asks for a graphics.
*/
graphicsToBeDrawn.append( (Graphics2D) graphics);
/* We now wait for the app's thread to finish drawing on
* the Graphics. This thread will sleep until the application
* release the graphics by placing it in the graphics drawn
* message queue. If the application signals that it is
* finished drawing the entire document then we'll get null
* returned when we try and pop a finished graphic.
*/
if (graphicsDrawn.pop() != null) {
result = PAGE_EXISTS;
} else {
result = NO_SUCH_PAGE;
}
return result;
}
private void startPrinterJobThread() {
printerJobThread = new Thread(this, "printerJobThread");
printerJobThread.start();
}
public void run() {
try {
printerJob.print(attributes);
} catch (PrinterException e) {
//REMIND: need to store this away and not rethrow it.
}
/* Close the message queues so that nobody is stuck
* waiting for one.
*/
graphicsToBeDrawn.closeWhenEmpty();
graphicsDrawn.close();
}
private class MessageQ {
private String qid="noname";
private ArrayList queue = new ArrayList();
MessageQ(String id) {
qid = id;
}
synchronized void closeWhenEmpty() {
while (queue != null && queue.size() > 0) {
try {
wait(1000);
} catch (InterruptedException e) {
// do nothing.
}
}
queue = null;
notifyAll();
}
synchronized void close() {
queue = null;
notifyAll();
}
synchronized boolean append(Graphics2D g) {
boolean queued = false;
if (queue != null) {
queue.add(g);
queued = true;
notify();
}
return queued;
}
synchronized Graphics2D pop() {
Graphics2D g = null;
while (g == null && queue != null) {
if (queue.size() > 0) {
g = (Graphics2D) queue.remove(0);
notify();
} else {
try {
wait(2000);
} catch (InterruptedException e) {
// do nothing.
}
}
}
return g;
}
}
}