/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community 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.osedu.org/licenses/ECL-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.
*/
package tufts.vue.action;
import tufts.vue.*;
import javax.swing.Action;
import javax.swing.AbstractAction;
import java.awt.*;
import java.awt.print.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.event.ActionEvent;
/**
* PrintAction and inner class PrintJob. PrintAction handles figuring
* out the print style we want (whole map or just visible view) and
* creating a PrintJob to handle it, as well as caching the last used
* PageFormat. PrintJob handles the actual printing. PrintJob can be
* threaded, tho this is currently disabled due to java bugs that can
* leave the VUE app raised over the print dialogs sometimes. This
* means that VUE can't repaint itself while the print dialogs are
* active (not true on Mac OS X, but true at least on W2K/JVM1.4.2).
*
* @version $Revision: 1.48 $ / $Date: 2010-02-03 19:13:45 $ / $Author: mike $
* @author Scott Fraize
*/
public class PrintAction extends tufts.vue.VueAction
{
private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(PrintAction.class);
private static PrintAction singleton;
public static PrintAction getPrintAction() {
if (singleton == null)
singleton = new PrintAction(VueResources.getString("menu.file.printdot"));
return singleton;
}
private boolean isPrintUnderway;
private PrintAction(String label) {
super(label, null, ":general/Print");
}
// either make private or get rid of getPrintAction singleton
public PrintAction() {
this("Print");
}
private PrinterJob printerJob;
private PrinterJob getPrinterJob()
{
//if (printerJob == null)
printerJob = PrinterJob.getPrinterJob();
return printerJob;
}
private PageFormat pageFormat;
private PageFormat getPageFormat(PrinterJob job, Rectangle2D bounds)
{
if (pageFormat == null) {
pageFormat = job.defaultPage();
// todo: keep a list of print formats for each map?
if (bounds.getWidth() > bounds.getHeight())
pageFormat.setOrientation(PageFormat.LANDSCAPE);
else
pageFormat.setOrientation(PageFormat.PORTRAIT);
}
return pageFormat;
}
/**
* Run the system page format dialog using the last used
* PageFormat as the default setup. If user hits cancel
* on the dialog, null is returned.
*/
private PageFormat getPageFormatInteractive(PrinterJob job, Rectangle2D bounds)
{
PageFormat initial = getPageFormat(job, bounds);
PageFormat result = job.pageDialog(initial);
if (result != initial)
return pageFormat = result;
else
return null; // dialog was canceled
}
/** If there's anything to print, initiate a print job */
public synchronized void actionPerformed(ActionEvent ae)
{
if (isPrintUnderway) {
out("ignoring [" + ae.getActionCommand() + "]; print already underway.");
return;
}
LWMap map = VUE.getActiveMap();
Rectangle2D bounds = map.getBounds();
if (bounds.isEmpty()) {
out("nothing to print in " + map);
return;
}
boolean viewerPrint = false;
if (ae.getActionCommand() != null &&
ae.getActionCommand().indexOf("Visible") >= 0)
viewerPrint = true;
// if any tool windows open in W2K/1.4.2 when start this thread,
// the print dialog get's obscured!
isPrintUnderway = true;
setEnabled(false);
try {
new PrintJob(VUE.getActiveViewer(), viewerPrint).start();
// if (DEBUG.Enabled)
// new PrintJob(VUE.getActiveViewer(), viewerPrint).start();
// else
// new PrintJob(VUE.getActiveViewer(), viewerPrint).runPrint();
} catch (Throwable t) {
out("exception creating or running PrintJob: " + t);
t.printStackTrace();
isPrintUnderway = false;
setEnabled(true);
}
}
private static volatile int JobCount = 1;
private class PrintJob extends Thread implements Printable
{
private LWMap map;
private LWComponent focal;
private String jobName;
private boolean isPrintingView;
private Rectangle2D bounds; // map bounds of print job
private PrintJob(MapViewer viewer, boolean viewerPrint) {
super("PrintJob#" + JobCount++);
this.map = viewer.getMap();
this.focal = viewerPrint ? viewer.getFocal() : map;
this.jobName = map.getDisplayLabel();
this.isPrintingView = viewerPrint;
// be sure to grab bounds info now -- sometimes it's possible
// that the viewer bounds go negative once the model print dialog
// boxes go active and VUE is hung without being able to reshape
// or repaint itself. (That's not a problem when we run this
// in a thread, but there's a java bug with that right now, tho
// it's safer to do it this way anyway).
if (isPrintingView()) {
if (focal == map)
this.bounds = viewer.getVisibleMapBounds();
else
this.bounds = focal.getBorderBounds();
} else
this.bounds = map.getBounds();
out(viewerPrint ? "printing: viewer contents" : "printing: whole map");
out("requested map bounds: " + bounds);
}
private boolean isPrintingView() {
return isPrintingView;
}
public void run() {
out("print thread active");
runPrint();
out("print thread complete");
}
private void runPrint() {
try {
runDialogsAndPrint();
} catch (Exception e) {
out("print exception: " + e);
e.printStackTrace();
} finally {
isPrintUnderway = false;
}
PrintAction.this.setEnabled(true);
}
private void runDialogsAndPrint()
throws java.awt.print.PrinterException
{
out("job starting for " + this.map);
PrinterJob job = getPrinterJob();
out("got OS job: " + tufts.Util.tags(job));
if (job.printDialog()) {
out("printDialog ran, waiting for format...");
//PageFormat format = getPageFormat(job, bounds);
PageFormat format = getPageFormatInteractive(job, bounds);
out("format: " + outpf(format));
if (format != null) {
job.setJobName(jobName);
job.setPrintable(this, format);
// try setting pageable to see if it then
// skips system dialog (we'd like a no-dialog option)
//job.setPrintService(job.getPrintService());
// this only *sometimes* works as a workaround
// for the vue ap mysteriously being raised above
// the system print dialog...
//VUE.frame.toBack();
out("printing...");
job.print();
}
}
out("job complete.");
}
public int print(Graphics gc, PageFormat format, int pageIndex)
throws java.awt.print.PrinterException
{
if (pageIndex > 0) {
out("page " + pageIndex + " requested, ending print job.");
return Printable.NO_SUCH_PAGE;
}
Dimension page = new Dimension((int) format.getImageableWidth() - 1,
(int) format.getImageableHeight() - 1);
Graphics2D g = (Graphics2D) gc;
out("asked to render page " + pageIndex + " in " + outpf(format) + " w/transform " + g.getTransform());
// note: supposedly, perhaps on Windows with JVM's 1.5 and newer, the
// transform scale provided can actually allow us to derive the DPI of the
// print device, which we could use for image rendering optimization's
// during prints. Mac OS X Snow Leopard w/JVM 1.6 always reports a 1.0
// scale though. And operations like "print preview" or "print to PDF"
// wouldn't have a fixed DPI anyway.
if (DEBUG.CONTAINMENT) {
g.setColor(Color.lightGray);
g.fillRect(0,0, 9999,9999);
}
g.translate(format.getImageableX(), format.getImageableY());
// Don't need to clip if printing whole map, as computed zoom
// should have made sure everything is within page size
//if (!isPrintingView())
//g.clipRect(0, 0, page.width, page.height);
if (DEBUG.CONTAINMENT) {
//g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
// draw border outline of page
g.setColor(Color.gray);
g.setStroke(VueConstants.STROKE_TWO);
g.drawRect(0, 0, page.width, page.height);
//g.setComposite(AlphaComposite.Src);
}
// compute zoom & offset for visible map components
Point2D.Float offset = new Point2D.Float();
// center vertically only if landscape mode
//if (format.getOrientation() == PageFormat.LANDSCAPE)
// TODO: allow horizontal centering, but not vertical centering (handle in computeZoomFit)
double scale = ZoomTool.computeZoomFit(page, 5, bounds, offset, true);
out("rendering at scale " + scale);
// set up the DrawContext
DrawContext dc = new DrawContext(g,
scale,
-offset.x,
-offset.y,
null, // frame would be the PageFormat offset & size rectangle
focal,
false); // todo: absolute links shouldn't be spec'd here
dc.setMapDrawing();
dc.setPrintQuality();
if (isPrintingView() && map == focal)
g.clipRect((int) Math.floor(bounds.getX()),
(int) Math.floor(bounds.getY()),
(int) Math.ceil(bounds.getWidth()),
(int) Math.ceil(bounds.getHeight()));
if (DEBUG.CONTAINMENT) {
g.setColor(Color.red);
g.setStroke(VueConstants.STROKE_TWO);
g.draw(bounds);
}
// render the map
if (map == focal)
map.draw(dc);
else
focal.draw(dc);
out("page " + pageIndex + " rendered.");
return Printable.PAGE_EXISTS;
}
private void out(String s) {
Log.info(String.format("PrintJob@%x[%s] %s", hashCode(), jobName, s));
//System.out.println("PrintJob@" + Integer.toHexString(hashCode()) + "[" + jobName + "] " + s);
}
}
private static String outpf(PageFormat p) {
if (p == null) return null;
final String[] o = {"LANDSCAPE", "PORTRAIT", "REVERSE LANDSCAPE"};
return
String.format("PageFormat[%s xoff=%.1fpt (%.2fin); yoff=%.1fpt (%.2fin); imageable-area: %.1fpt x %.1fpt (%.2f x %.2f inches)]",
o[p.getOrientation()],
p.getImageableX(),
p.getImageableX() / 72.0,
p.getImageableY(),
p.getImageableY() / 72.0,
p.getImageableWidth(),
p.getImageableHeight(),
p.getImageableWidth() / 72.0,
p.getImageableHeight() / 72.0
);
}
}