package de.saring.sportstracker.gui.views; import java.util.logging.Logger; import javafx.concurrent.Task; import javafx.print.PageLayout; import javafx.print.Printer; import javafx.print.PrinterJob; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.image.ImageView; import javafx.scene.image.WritableImage; import javafx.scene.transform.Scale; import javax.inject.Inject; import javax.inject.Singleton; import de.saring.sportstracker.gui.STContext; /** * Class for printing SportsTracker views. It provides the printer action and remembers * the printer selection and page layout configuration. * * @author Stefan Saring */ @Singleton public class ViewPrinter { private static final Logger LOGGER = Logger.getLogger(ViewPrinter.class.getName()); private final STContext context; private Printer previousPrinter; private PageLayout previousPageLayout; /** * Standard c'tor for dependency injection. * * @param context the SportsTracker UI context */ @Inject public ViewPrinter(final STContext context) { this.context = context; } /** * Prints the specified view. The print action is executed asynchronously, so it does not block * the application UI thread. However, the application window will be blocked during printing * to avoid user actions (e.g. restart the print action). * * @param rootNode root node of the view to print */ public void printView(final Node rootNode) { LOGGER.info("Printing current view..."); context.blockMainWindow(true); new Thread(new CreatePrinterJobTask(rootNode)).start(); } /** * Executes the print action on the JavaFX UI thread after the PrinterJob has been created. * * @param rootNode root node of view to print * @param printerJob created printer job, can be null */ private void printViewInJob(final Node rootNode, final PrinterJob printerJob) { boolean success = false; try { // PrinterJob is null when no printer is available (most systems provide default printers) if (printerJob != null) { // use printer and page layout from previous printing, if available restorePrinterConfiguration(printerJob); // display print dialog for confirmation and configuration by the user final boolean printConfirmed = printerJob.showPrintDialog(context.getPrimaryStage()); storePrinterConfiguration(printerJob); if (printConfirmed) { // the printing itself is quite fast, no need to execute it asynchronously if (printViewPage(printerJob, rootNode)) { if (printerJob.endJob()) { success = true; } else { LOGGER.severe("Failed to end the print job!"); } } else { LOGGER.severe("Failed to execute the view print!"); } } else { success = true; } } } finally { context.blockMainWindow(false); if (!success) { final String errorKey = printerJob == null ? "st.main.error.print_view.no_printer" // : "st.main.error.print_view"; context.showMessageDialog(context.getPrimaryStage(), Alert.AlertType.ERROR, "common.error", errorKey); } } } private void restorePrinterConfiguration(final PrinterJob printerJob) { if (previousPrinter != null) { printerJob.setPrinter(previousPrinter); if (previousPageLayout != null) { printerJob.getJobSettings().setPageLayout(previousPageLayout); } } } private void storePrinterConfiguration(final PrinterJob printerJob) { previousPageLayout = printerJob.getJobSettings().getPageLayout(); previousPrinter = printerJob.getPrinter(); } /** * Prints the specified view node completely to one single page. * * @param printerJob printer job which defines the printer, page layout, ... * @param view view node to print * @return true when the view was printed successfully */ private boolean printViewPage(final PrinterJob printerJob, final Node view) { // the view needs to be scaled to fit the selected page layout of the PrinterJob // => the passed view node can't be scaled, this would scale the displayed UI // => solution: create a snapshot image for printing and scale this image final WritableImage snapshot = view.snapshot(null, null); final ImageView ivSnapshot = new ImageView(snapshot); // compute the needed scaling (aspect ratio must be kept) final PageLayout pageLayout = printerJob.getJobSettings().getPageLayout(); final double scaleX = pageLayout.getPrintableWidth() / ivSnapshot.getImage().getWidth(); final double scaleY = pageLayout.getPrintableHeight() / ivSnapshot.getImage().getHeight(); final double scale = Math.min(scaleX, scaleY); // scale the calendar image only when it's too big for the selected page if (scale < 1.0) { ivSnapshot.getTransforms().add(new Scale(scale, scale)); } return printerJob.printPage(ivSnapshot); } /** * This class creates a PrinterJob as a asynchronous Task to avoid blocking of the application UI. * The lookup of available printers can take some time, depending on the OS, network and printers * (mostly on the first run). */ private class CreatePrinterJobTask extends Task<PrinterJob> { private final Node view; public CreatePrinterJobTask(final Node view) { this.view = view; } @Override protected PrinterJob call() throws Exception { LOGGER.info("Creating printer job..."); return PrinterJob.createPrinterJob(); } @Override protected void succeeded() { super.succeeded(); printViewInJob(view, getValue()); } @Override protected void failed() { super.failed(); context.blockMainWindow(false); } } }