blob: 7266c2d3d38bb24b9920f519479d931a56ffe6ce [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 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.swt.printing;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.motif.*;
/**
* Instances of this class are used to print to a printer.
* Applications create a GC on a printer using <code>new GC(printer)</code>
* and then draw on the printer GC using the usual graphics calls.
* <p>
* A <code>Printer</code> object may be constructed by providing
* a <code>PrinterData</code> object which identifies the printer.
* A <code>PrintDialog</code> presents a print dialog to the user
* and returns an initialized instance of <code>PrinterData</code>.
* Alternatively, calling <code>new Printer()</code> will construct a
* printer object for the user's default printer.
* </p><p>
* Application code must explicitly invoke the <code>Printer.dispose()</code>
* method to release the operating system resources managed by each instance
* when those instances are no longer required.
* </p>
*
* @see PrinterData
* @see PrintDialog
* @see <a href="http://www.eclipse.org/swt/snippets/#printing">Printing snippets</a>
* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
*/
public final class Printer extends Device {
PrinterData data;
int printContext, xScreen, xDrawable, xtContext;
Font defaultFont;
boolean isGCCreated;
static String APP_NAME = "SWT_Printer";
static DeviceData checkNull (PrinterData data) {
if (data == null) data = new PrinterData();
if (data.application_name == null) {
data.application_name = APP_NAME;
}
if (data.application_class == null) {
data.application_class = APP_NAME;
}
if (data.name == null || data.driver == null) {
PrinterData defaultData = getDefaultPrinterData();
if (defaultData == null) SWT.error(SWT.ERROR_NO_HANDLES);
data.name = defaultData.name;
data.driver = defaultData.driver;
}
return data;
}
/**
* Returns a <code>PrinterData</code> object representing
* the default printer or <code>null</code> if there is no
* default printer.
*
* @return the default printer data or null
*
* @since 2.1
*/
public static PrinterData getDefaultPrinterData() {
PrinterData[] list = getEnvPrinterList();
PrinterData defaultPrinter = getEnvDefaultPrinter(list);
if (defaultPrinter != null) return defaultPrinter;
if (list.length != 0) return list[0];
return null;
}
/**
* Returns the default printer <code>PrinterData</code> specified
* by XPRINTER.
*
* @return the default printer data or <code>null</code>
*/
static PrinterData getEnvDefaultPrinter(PrinterData[] serverList) {
String[] printerNames = new String[]{"XPRINTER", "PDPRINTER", "LPDEST", "PRINTER"};
for (int i = 0; i < printerNames.length; i++) {
int ptr = OS.getenv(Converter.wcsToMbcs(null, printerNames[i], true));
if (ptr != 0) {
int length = OS.strlen(ptr);
byte[] buffer = new byte[length];
OS.memmove(buffer, ptr, length);
String defaultPrinter = new String(Converter.mbcsToWcs(null, buffer));
int index = defaultPrinter.indexOf("@");
if (index != -1) {
String name = defaultPrinter.substring(0, index);
String driver = defaultPrinter.substring(index + 1);
return new PrinterData(driver, name);
} else {
for (int j = 0; j < serverList.length; j++) {
PrinterData printerData = serverList[j];
if (defaultPrinter.equals(printerData.name)) {
return new PrinterData(printerData.driver, defaultPrinter);
}
}
}
}
}
return null;
}
/**
* Returns an array of <code>PrinterData</code> objects
* representing all available printers. If there are no
* printers, the array will be empty.
*
* @return an array of PrinterData objects representing the available printers
*/
public static PrinterData[] getPrinterList() {
PrinterData[] list = getEnvPrinterList();
/* Ensure that default printer data occurs in the printer list */
PrinterData data = getEnvDefaultPrinter(list);
if (data == null) return list;
for (int i = 0; i < list.length; i++) {
PrinterData printerData = list[i];
if (printerData.name.equals(data.name) && printerData.driver.equals(data.driver)) {
return list;
}
}
PrinterData[] newList = new PrinterData[list.length + 1];
System.arraycopy(list, 0, newList, 1, list.length);
newList[0] = data;
return newList;
}
/**
* Returns a array of <code> PrinterData</code> objects
* representing all available printers for all Xprint
* servers specified in XPSERVERLIST.
*
* Note: The default printer define by XPRINTER may not be
* included in the list.
*
* @return the list of printer for all servers
*/
static PrinterData[] getEnvPrinterList() {
String[] serverList = getXPServerList();
PrinterData[] printerList = new PrinterData[0];
for (int i = 0; i < serverList.length; i++) {
PrinterData[] printers = getEnvPrinterList(serverList[i]);
if (printers.length != 0) {
PrinterData[] newPrinterList = new PrinterData [printerList.length + printers.length];
System.arraycopy(printerList, 0, newPrinterList, 0, printerList.length);
System.arraycopy(printers, 0, newPrinterList, printerList.length, printers.length);
printerList = newPrinterList;
}
}
return printerList;
}
/**
* Returns a array of <code> PrinterData</code> objects
* representing all available printers for a specific
* XPrint server
*
* @param server the XPrint server name
* @return the list of printers for a given XPrint server
*/
static PrinterData[] getEnvPrinterList(String server) {
byte[] buffer = Converter.wcsToMbcs(null, server, true);
int pdpy = OS.XOpenDisplay (buffer);
if (pdpy == 0) return new PrinterData[0];
/* Get the list of printers */
int[] count = new int[1];
int plist = OS.XpGetPrinterList(pdpy, null, count);
int printerCount = count[0];
if (plist == 0 || printerCount == 0) {
OS.XCloseDisplay(pdpy);
if (plist != 0) OS.XpFreePrinterList(plist);
return new PrinterData[0];
}
/* Copy the printer names into PrinterData objects */
int[] stringPointers = new int[printerCount * 2];
OS.memmove(stringPointers, plist, printerCount * 2 * 4);
PrinterData printerList[] = new PrinterData[printerCount];
for (int i = 0; i < printerCount; i++) {
String name = "";
int address = stringPointers[i * 2];
if (address != 0) {
int length = OS.strlen(address);
buffer = new byte [length];
OS.memmove(buffer, address, length);
name = new String(Converter.mbcsToWcs(null, buffer));
}
printerList[i] = new PrinterData(server, name);
}
OS.XCloseDisplay(pdpy);
OS.XpFreePrinterList(plist);
return printerList;
}
/**
* Returns the value of XPSERVERLIST.
*
* @return the value of the XPSERVERLIST variable from the environment
*/
static String[] getXPServerList() {
byte[] name = Converter.wcsToMbcs(null, "XPSERVERLIST", true);
int ptr = OS.getenv(name);
String[] serversList = new String[0];
if (ptr != 0) {
int length = OS.strlen(ptr);
byte[] buffer1 = new byte[length];
OS.memmove(buffer1, ptr, length);
char[] buffer2 = Converter.mbcsToWcs(null, buffer1);
int i = 0;
while (i < buffer2.length) {
if (buffer2[i] != ' ') {
int start = i;
while (++i < buffer2.length && buffer2[i] != ' ') {/*empty*/}
String server = new String(buffer2, start, i - start);
String[] newServerList = new String[serversList.length + 1];
System.arraycopy(serversList, 0, newServerList, 0, serversList.length);
newServerList[serversList.length] = server;
serversList = newServerList;
}
i++;
}
}
return serversList;
}
/**
* Constructs a new printer representing the default printer.
* <p>
* Note: You must dispose the printer when it is no longer required.
* </p>
*
* @exception SWTError <ul>
* <li>ERROR_NO_HANDLES - if there are no valid printers
* </ul>
*
* @see Device#dispose
*/
public Printer() {
this(null);
}
/**
* Constructs a new printer given a <code>PrinterData</code>
* object representing the desired printer. If the argument
* is null, then the default printer will be used.
* <p>
* Note: You must dispose the printer when it is no longer required.
* </p>
*
* @param data the printer data for the specified printer, or null to use the default printer
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT - if the specified printer data does not represent a valid printer
* </ul>
* @exception SWTError <ul>
* <li>ERROR_NO_HANDLES - if there are no valid printers
* </ul>
*
* @see Device#dispose
*/
public Printer(PrinterData data) {
super(checkNull(data));
}
/**
* Creates the printer handle.
* This method is called internally by the instance creation
* mechanism of the <code>Device</code> class.
* @param deviceData the device data
*/
protected void create(DeviceData deviceData) {
data = (PrinterData)deviceData;
/* Use the character encoding for the default locale */
byte [] displayName = null, appName = null, appClass = null;
displayName = Converter.wcsToMbcs(null, data.driver, true);
appName = Converter.wcsToMbcs(null, data.application_name, true);
appClass = Converter.wcsToMbcs(null, data.application_class, true);
/* Open the display for the X print server */
xtContext = OS.XtCreateApplicationContext();
if (xtContext == 0) SWT.error(SWT.ERROR_NO_HANDLES);
xDisplay = OS.XtOpenDisplay(xtContext, displayName, appName, appClass, 0, 0, new int[]{0}, 0);
if (xDisplay == 0) {
OS.XtDestroyApplicationContext(xtContext);
SWT.error(SWT.ERROR_NO_HANDLES);
}
setDPI = true;
}
protected void init() {
super.init();
/* Use the character encoding for the default locale */
byte[] buffer = Converter.wcsToMbcs(null, data.name, true);
/*
* Bug in Xp. If the printer name is not valid, Xp will
* cause a segmentation fault. The fix is to check if the
* printer name is valid before calling XpCreateContext().
*/
int[] count = new int[1];
int plist = OS.XpGetPrinterList(xDisplay, buffer, count);
if (plist != 0) OS.XpFreePrinterList(plist);
if (count[0] == 0) SWT.error(SWT.ERROR_NO_HANDLES);
/* Create the printContext for the printer */
printContext = OS.XpCreateContext(xDisplay, buffer);
/* Set the printContext into the display */
OS.XpSetContext(xDisplay, printContext);
/* Get the printer's screen */
xScreen = OS.XpGetScreenOfContext(xDisplay, printContext);
if (xScreen == 0) SWT.error(SWT.ERROR_NO_HANDLES);
/* Initialize the xDrawable */
XRectangle rect = new XRectangle();
short[] width = new short[1];
short[] height = new short[1];
OS.XpGetPageDimensions(xDisplay, printContext, width, height, rect);
xDrawable = OS.XCreateWindow(xDisplay, OS.XRootWindowOfScreen(xScreen),
0, 0, rect.width, rect.height, 0,
OS.CopyFromParent, OS.CopyFromParent, OS.CopyFromParent, 0, null);
if (xDrawable == 0) SWT.error(SWT.ERROR_NO_HANDLES);
/* Initialize the default font */
/* Use the character encoding for the default locale */
Point dpi = getDPI();
buffer = Converter.wcsToMbcs(null, "-*-courier-medium-r-*-*-*-120-"+dpi.x+"-"+dpi.y+"-*-*-iso8859-1", true);
int fontListEntry = OS.XmFontListEntryLoad(xDisplay, buffer, OS.XmFONT_IS_FONTSET, OS.XmFONTLIST_DEFAULT_TAG);
if (fontListEntry == 0) SWT.error(SWT.ERROR_NO_HANDLES);
int defaultFontList = OS.XmFontListAppendEntry(0, fontListEntry);
OS.XmFontListEntryFree(new int[]{fontListEntry});
defaultFont = Font.motif_new(this, defaultFontList);
}
/**
* Destroys the printer handle.
* This method is called internally by the dispose
* mechanism of the <code>Device</code> class.
*/
protected void destroy() {
if (xtContext != 0) OS.XtDestroyApplicationContext (xtContext);
}
/**
* Invokes platform specific functionality to allocate a new GC handle.
* <p>
* <b>IMPORTANT:</b> This method is <em>not</em> part of the public
* API for <code>Printer</code>. It is marked public only so that it
* can be shared within the packages provided by SWT. It is not
* available on all platforms, and should never be called from
* application code.
* </p>
*
* @param data the platform specific GC data
* @return the platform specific GC handle
*/
public int internal_new_GC(GCData data) {
if (data != null) {
if (isGCCreated) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
if ((data.style & mask) == 0) {
data.style |= SWT.LEFT_TO_RIGHT;
}
data.device = this;
data.display = xDisplay;
data.drawable = xDrawable;
data.font = defaultFont;
data.colormap = OS.XDefaultColormapOfScreen(xScreen);
int defaultGC = OS.XDefaultGCOfScreen(xScreen);
if (defaultGC != 0) {
XGCValues values = new XGCValues();
OS.XGetGCValues(xDisplay, defaultGC, OS.GCBackground | OS.GCForeground, values);
XColor foreground = new XColor ();
foreground.pixel = values.foreground;
data.foreground = foreground;
XColor background = new XColor ();
background.pixel = values.background;
data.background = background;
}
isGCCreated = true;
}
int xGC = OS.XCreateGC(xDisplay, xDrawable, 0, null);
if (xGC == 0) SWT.error(SWT.ERROR_NO_HANDLES);
return xGC;
}
/**
* Invokes platform specific functionality to dispose a GC handle.
* <p>
* <b>IMPORTANT:</b> This method is <em>not</em> part of the public
* API for <code>Printer</code>. It is marked public only so that it
* can be shared within the packages provided by SWT. It is not
* available on all platforms, and should never be called from
* application code.
* </p>
*
* @param hDC the platform specific GC handle
* @param data the platform specific GC data
*/
public void internal_dispose_GC(int xGC, GCData data) {
OS.XFreeGC(xDisplay, xGC);
if (data != null) isGCCreated = false;
}
/**
* Starts a print job and returns true if the job started successfully
* and false otherwise.
* <p>
* This must be the first method called to initiate a print job,
* followed by any number of startPage/endPage calls, followed by
* endJob. Calling startPage, endPage, or endJob before startJob
* will result in undefined behavior.
* </p>
*
* @param jobName the name of the print job to start
* @return true if the job started successfully and false otherwise.
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see #startPage
* @see #endPage
* @see #endJob
*/
public boolean startJob(String jobName) {
checkDevice();
/* Use the character encoding for the default locale */
byte [] buffer = Converter.wcsToMbcs(null, "*job-name: " + jobName, true);
OS.XpSetAttributes(xDisplay, printContext, OS.XPJobAttr, buffer, OS.XPAttrMerge);
OS.XpStartJob(xDisplay, OS.XPSpool);
return true;
}
/**
* Ends the current print job.
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see #startJob
* @see #startPage
* @see #endPage
*/
public void endJob() {
checkDevice();
OS.XpEndJob(xDisplay);
OS.XFlush(xDisplay);
}
/**
* Cancels a print job in progress.
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*/
public void cancelJob() {
checkDevice();
OS.XpCancelJob(xDisplay, true);
}
/**
* Starts a page and returns true if the page started successfully
* and false otherwise.
* <p>
* After calling startJob, this method may be called any number of times
* along with a matching endPage.
* </p>
*
* @return true if the page started successfully and false otherwise.
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see #endPage
* @see #startJob
* @see #endJob
*/
public boolean startPage() {
checkDevice();
OS.XpStartPage(xDisplay, xDrawable);
return true;
}
/**
* Ends the current page.
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see #startPage
* @see #startJob
* @see #endJob
*/
public void endPage() {
checkDevice();
OS.XpEndPage(xDisplay);
}
/**
* Returns a point whose x coordinate is the horizontal
* dots per inch of the printer, and whose y coordinate
* is the vertical dots per inch of the printer.
*
* @return the horizontal and vertical DPI
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*/
public Point getDPI() {
checkDevice();
/* Use the character encoding for the default locale */
byte [] buffer = Converter.wcsToMbcs(null, "default-printer-resolution", true);
int pool = OS.XpGetOneAttribute(xDisplay, printContext, OS.XPDocAttr, buffer);
int length = OS.strlen(pool);
buffer = new byte[length];
OS.memmove(buffer, pool, length);
OS.XtFree(pool);
String resolution = new String(buffer, 0, buffer.length);
int res = 300; // default
if (resolution.length() == 0) {
/* If we can't get the info from the DocAttrs, ask the printer. */
/* Use the character encoding for the default locale */
buffer = Converter.wcsToMbcs(null, "printer-resolutions-supported", true);
pool = OS.XpGetOneAttribute(xDisplay, printContext, OS.XPPrinterAttr, buffer);
length = OS.strlen(pool);
buffer = new byte[length];
OS.memmove(buffer, pool, length);
OS.XtFree(pool);
int n = 0;
while (!Compatibility.isWhitespace((char)buffer[n]) && n < buffer.length) n++;
resolution = new String(buffer, 0, n);
}
if (resolution.length() != 0) {
try {
res = Integer.parseInt(resolution);
} catch (NumberFormatException ex) {}
}
return new Point(res, res);
}
/**
* Returns a rectangle describing the receiver's size and location.
* <p>
* For a printer, this is the size of the physical page, in pixels.
* </p>
*
* @return the bounding rectangle
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see #getClientArea
* @see #computeTrim
*/
public Rectangle getBounds() {
checkDevice();
XRectangle rect = new XRectangle();
short [] width = new short [1];
short [] height = new short [1];
OS.XpGetPageDimensions(xDisplay, printContext, width, height, rect);
return new Rectangle(0, 0, width[0], height[0]);
}
/**
* Returns a rectangle which describes the area of the
* receiver which is capable of displaying data.
* <p>
* For a printer, this is the size of the printable area
* of the page, in pixels.
* </p>
*
* @return the client area
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see #getBounds
* @see #computeTrim
*/
public Rectangle getClientArea() {
checkDevice();
XRectangle rect = new XRectangle();
OS.XpGetPageDimensions(xDisplay, printContext, new short [1], new short [1], rect);
return new Rectangle(rect.x, rect.y, rect.width, rect.height);
}
/**
* Given a <em>client area</em> (as described by the arguments),
* returns a rectangle, relative to the client area's coordinates,
* that is the client area expanded by the printer's trim (or minimum margins).
* <p>
* Most printers have a minimum margin on each edge of the paper where the
* printer device is unable to print. This margin is known as the "trim."
* This method can be used to calculate the printer's minimum margins
* by passing in a client area of 0, 0, 0, 0 and then using the resulting
* x and y coordinates (which will be <= 0) to determine the minimum margins
* for the top and left edges of the paper, and the resulting width and height
* (offset by the resulting x and y) to determine the minimum margins for the
* bottom and right edges of the paper, as follows:
* <ul>
* <li>The left trim width is -x pixels</li>
* <li>The top trim height is -y pixels</li>
* <li>The right trim width is (x + width) pixels</li>
* <li>The bottom trim height is (y + height) pixels</li>
* </ul>
* </p>
*
* @param x the x coordinate of the client area
* @param y the y coordinate of the client area
* @param width the width of the client area
* @param height the height of the client area
* @return a rectangle, relative to the client area's coordinates, that is
* the client area expanded by the printer's trim (or minimum margins)
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see #getBounds
* @see #getClientArea
*/
public Rectangle computeTrim(int x, int y, int width, int height) {
checkDevice();
XRectangle rect = new XRectangle();
short [] paperWidth = new short [1];
short [] paperHeight = new short [1];
OS.XpGetPageDimensions(xDisplay, printContext, paperWidth, paperHeight, rect);
int hTrim = paperWidth[0] - rect.width;
int vTrim = paperHeight[0] - rect.height;
return new Rectangle(x - rect.x, y - rect.y, width + hTrim, height + vTrim);
}
/**
* Returns a <code>PrinterData</code> object representing the
* target printer for this print job.
*
* @return a PrinterData object describing the receiver
*/
public PrinterData getPrinterData() {
return data;
}
/**
* Returns a reasonable font for applications to use.
* On some platforms, this will match the "default font"
* or "system font" if such can be found. This font
* should not be free'd because it was allocated by the
* system, not the application.
* <p>
* Typically, applications which want the default look
* should simply not set the font on the widgets they
* create. Widgets are always created with the correct
* default font for the class of user-interface component
* they represent.
* </p>
*
* @return a font
*
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public Font getSystemFont () {
checkDevice ();
return defaultFont;
}
/**
* Checks the validity of this device.
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*/
protected void checkDevice() {
if (xDisplay == 0) SWT.error(SWT.ERROR_DEVICE_DISPOSED);
}
/**
* Releases any internal state prior to destroying this printer.
* This method is called internally by the dispose
* mechanism of the <code>Device</code> class.
*/
protected void release() {
super.release();
if (defaultFont != null) {
OS.XmFontListFree(defaultFont.handle);
defaultFont.handle = 0;
defaultFont = null;
}
if (printContext != 0) {
OS.XpDestroyContext(xDisplay, printContext);
printContext = 0;
}
if (xDrawable != 0) {
OS.XDestroyWindow(xDisplay, xDrawable);
xDrawable = 0;
}
xScreen = 0;
data = null;
}
}