Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.print;
027
028import javafx.beans.property.ObjectProperty;
029import javafx.beans.property.Property;
030import javafx.beans.property.ReadOnlyObjectProperty;
031import javafx.beans.property.ReadOnlyObjectWrapper;
032import javafx.beans.property.SimpleObjectProperty;
033import javafx.beans.value.ObservableValue;
034import javafx.scene.Node;
035import javafx.stage.Window;
036
037import com.sun.javafx.print.PrinterJobImpl;
038import com.sun.javafx.tk.PrintPipeline;
039
040/**
041 * PrinterJob is the starting place for JavaFX scenegraph printing.
042 * <p>
043 * It includes
044 * <ul>
045 * <li>Printer discovery
046 * <li>Job creation
047 * <li>Job configuration based on supported printer capabilities
048 * <li>Page setup
049 * <li>Rendering of a node hierachy to a page.
050 * </ul>
051 * <p>
052 * Here ia a very simple example, which prints a single node.
053 * <pre>
054 * Node node = new Circle(100, 200, 200);
055 * PrinterJob job = PrinterJob.createPrinterJob();
056 * if (job != null) {
057 *    boolean success = job.printPage(node);
058 *    if (success) {
059 *        job.endJob();
060 *    }
061 * }
062 * </pre>
063 * <b>Points to note</b>
064 * <p>
065 * In the example above the node was not added to a scene.
066 * Since most printing scenarios are printing content that's either
067 * not displayed at all, or must be prepared and formatted differently,
068 * this is perfectly acceptable.
069 * <p>
070 * If content that is currently part of a Scene and is being displayed,
071 * is printed, then because printing a job or even a single page
072 * of the job may span over multiple screen "pulses" or frames, it is
073 * important for the application to ensure that the node being printed
074 * is not updated during the printing process, else partial or smeared
075 * rendering is probable.
076 * <p>
077 * It should be apparent that the same applies even to nodes that are
078 * not displayed - updating them concurrent with printing them is not
079 * a good idea.
080 * <p>
081 * There is no requirement to do printing on the FX application thread.
082 * A node may be prepared for printing on any thread, the job may
083 * be invoked on any thread. However, minimising the amount of work
084 * done on the FX application thread is generally desirable,
085 * so as not to affect the responsiveness of the application UI.
086 * So the recommendation is to perform printing on a new thread
087 * and let the implementation internally schedule any tasks that
088 * need to be performed on the FX thread to be run on that thread.
089 * <p>
090 * @since JavaFX 8
091 */
092
093public final class PrinterJob {
094
095    // Delegating all the work keeps whatever classes
096    // are being used out of the API packages.
097    private PrinterJobImpl jobImpl;
098
099    private ObjectProperty<Printer> printer;
100
101    private JobSettings settings;
102
103    /**
104     * Factory method to create a job.
105     * If there are no printers available, this will return null.
106     * Some platforms may provide a pseudo printer, which creates
107     * a document. These will be enumerated here so long as the
108     * platform also enumerates them as if they are printers.
109     * @return a new PrinterJob instance, or null.
110     * @throws SecurityException if a job does not have permission
111     * to initiate a printer job.
112     */
113    public static final PrinterJob createPrinterJob() {
114        SecurityManager security = System.getSecurityManager();
115        if (security != null) {
116            security.checkPrintJobAccess();
117        }
118        Printer printer = Printer.getDefaultPrinter();
119        if (printer == null) {
120            return null;
121        } else {
122            return new PrinterJob(printer, null);
123        }
124    }
125
126    /**
127     * Factory method to create a job for a specified printer.
128     * <p>
129     * The <code>printer</code> argument determines the initial printer
130     * @param printer to use for the job. If the printer is currently
131     * unavailable (eg offline) then this may return null.
132     * @return a new PrinterJob, or null.
133     * @throws SecurityException if a job does not have permission
134     * to initiate a printer job.
135     */
136    public static final PrinterJob createPrinterJob(Printer printer) {
137        SecurityManager security = System.getSecurityManager();
138        if (security != null) {
139            security.checkPrintJobAccess();
140        }
141        return new PrinterJob(printer, null);
142    }
143
144    private PrinterJob(Printer printer, JobSettings jobSettings) {
145        this.printer = new SimpleObjectProperty<Printer>(printer) {
146
147            @Override
148            public void bind(ObservableValue<? extends Printer> rawObservable)
149            {
150                throw new RuntimeException("Printer property cannot be bound");
151            }
152
153            @Override
154            public void bindBidirectional(Property<Printer> other) {
155                throw new RuntimeException("Printer property cannot be bound");
156            }
157        };
158        if (jobSettings == null) {
159            jobSettings = printer.getDefaultJobSettings();
160        } else {
161            jobSettings.updateForPrinter(printer);
162        }
163        settings = jobSettings;
164        settings.setPrinterJob(this);
165        createImplJob(printer, settings);
166    }
167
168    synchronized private PrinterJobImpl createImplJob(Printer printer,
169                                                      JobSettings settings) {
170        if (jobImpl == null) {
171            jobImpl = PrintPipeline.getPrintPipeline().createPrinterJob(this);
172        }
173        return jobImpl;
174    }
175
176    /**
177     * Updating settings or printer is only allowed on a new job,
178     * meaning beforee you start printing or cancel etc.
179     * The implementation needs to check this wherever job state
180     * updates are received.
181     */
182    boolean isJobNew() {
183        return getJobStatus() == JobStatus.NOT_STARTED;
184    }
185
186    /**
187     * Property representing the <code>Printer</code> for this job.
188     */
189    public final ObjectProperty<Printer> printerProperty() {
190        if (printer == null) {
191            printer = new SimpleObjectProperty<Printer>() {
192
193                @Override
194                public void set(Printer value) {
195                    if (value == get() || !isJobNew()) {
196                        return;
197                    }
198                    if (value == null) {
199                        value = Printer.getDefaultPrinter();
200                    }
201                    super.set(value);
202                    jobImpl.setPrinterImpl(value.getPrinterImpl());
203                    settings.updateForPrinter(value);
204                }
205
206                @Override
207                public Object getBean() {
208                    return PrinterJob.this;
209                }
210
211                @Override
212                public String getName() {
213                    return "printer";
214                }
215            };
216        }
217        return printer;
218    }
219
220    /**
221     * Gets the printer currently associated with this job.
222     * @return printer for the job.
223     */
224    public synchronized Printer getPrinter() {
225        return printer.get();
226    }
227
228    /**
229     * Change the printer for this job.
230     * If the new printer does not support the current job settings,
231     * (for example if DUPLEX printing is requested but the new printer
232     * does not support this), then the values are reset to the default
233     * for the new printer, or in some cases a similar value. For example
234     * this might mean REVERSE_LANDSCAPE is updated to LANDSCAPE, however
235     * this implementation optimisation is allowed, but not required.
236     * <p>
237     * The above applies whether the printer is changed by directly calling
238     * this method, or as a side-effect of user interaction with a print
239     * dialog.
240     * <p>
241     * Setting a null printer or the current printer is ignored.
242     * @param printer to be used for this print job.
243     */
244    public synchronized void setPrinter(Printer printer) {
245         printerProperty().set(printer);
246    }
247
248    /**
249     * The <code>JobSettings</code> encapsulates all the API supported job
250     * configuration options such as number of copies,
251     * collation option, duplex option, etc.
252     * The initial values are based on the current settings for
253     * the initial printer.
254     * @return current job settings.
255     */
256    public synchronized JobSettings getJobSettings() {
257        return settings;
258    }
259
260    /**
261     * Displays a Print Dialog.
262     * Allow the user to update job state such as printer and settings.
263     * These changes will be available in the appropriate properties
264     * after the print dialog has returned.
265     * The print dialog is also typically used to confirm the user
266     * wants to proceed with printing. This is not binding on the
267     * application but generally should be obeyed.
268     * <p>
269     * In the case that there is no UI available then this method
270     * returns true, with no options changed, as if the user had
271     * confirmed to proceed with printing.
272     * <p>
273     * If the job is not in a state to display the dialog, such
274     * as already printing, cancelled or done, then the dialog will
275     * not be displayed and the method will return false.
276     * <p>
277     * The window <code>owner</code> may be null, but
278     * if it is a visible Window, it will be used as the parent.
279     * @param owner to which to block input, or null.
280     * @return false if the user opts to cancel printing, or the job
281     * is not in the new state. That is if it has already started,
282     * has failed, or has been cancelled, or ended.
283     */
284    public synchronized boolean showPrintDialog(Window owner) {
285        // TBD handle owner
286        if (!isJobNew()) {
287            return false;
288        } else {
289            return jobImpl.showPrintDialog(owner);
290        }
291    }
292
293    /**
294     * Displays a Page Setup dialog.
295     * A page set up dialog is primarily to allow an end user
296     * to configure the layout of a page. Paper size and orientation
297     * are the most common and most important components of this.
298     * <p>
299     * This will display the most appropriate available dialog for
300     * this purpose.
301     * However there may be still be access to other settings,
302     * including changing the current printer.
303     * Therefore a side effect of this dialog display method may be to
304     * update that and any other current job settings.
305     * The method returns true if the user confirmed the dialog whether or
306     * not any changes are made.
307     * <p>
308     * If the job is not in a state to display the dialog, such
309     * as already printing, cancelled or done, then the dialog will
310     * not be displayed and the method will return false.
311     * <p>
312     * The window <code>owner</code> may be null, but
313     * if it is a visible Window, it will be used as the parent.
314     * @param owner to block input, or null.
315     * @return false if the user opts to cancel the dialog, or the job
316     * is not in the new state. That is if it has already started,
317     * has failed, or has been cancelled, or ended.
318     */
319    public synchronized boolean showPageSetupDialog(Window owner) {
320        // TBD handle owner
321        if (!isJobNew()) {
322            return false;
323        } else {
324            return jobImpl.showPageDialog(owner);
325        }
326    }
327
328    /**
329     * This method can be used to check if a page configuration
330     * is possible in the current job configuration. For example
331     * if the specified paper size is supported. If the original
332     * PageLayout is supported it will be returned. If not, a new PageLayout
333     * will be returned that attempts to honour the supplied
334     * PageLayout, but adjusted to match the current job configuration.
335     * <p>
336     * This method does not update the job configuration.
337     * @param pageLayout to be validated
338     * @return a <code>PageLayout</code> that is supported in the
339     * current job configuration.
340     * @throws NullPointerException if the pageLayout parameter is null.
341     */
342    synchronized PageLayout validatePageLayout(PageLayout pageLayout) {
343        if (pageLayout == null) {
344            throw new NullPointerException("pageLayout cannot be null");
345        }
346        return jobImpl.validatePageLayout(pageLayout);
347    }
348
349    /**
350     * Print the specified node using the specified page layout.
351     * The page layout will override the job default for this page only.
352     * If the job state is CANCELED, ERROR or DONE, this method will
353     * return false.
354     * @param pageLayout Layout for this page.
355     * @param node The node to print.
356     * @return whether rendering was successful.
357     * @throws NullPointerException if either parameter is null.
358     */
359    public synchronized boolean printPage(PageLayout pageLayout, Node node) {
360        if (jobStatus.get().ordinal() > JobStatus.PRINTING.ordinal()) {
361            return false;
362        }
363        if (jobStatus.get() == JobStatus.NOT_STARTED) {
364            jobStatus.set(JobStatus.PRINTING);
365        }
366        if (pageLayout == null || node == null) {
367            jobStatus.set(JobStatus.ERROR);
368            throw new NullPointerException("Parameters cannot be null");
369        }
370        boolean rv = jobImpl.print(pageLayout, node);
371        if (!rv) {
372            jobStatus.set(JobStatus.ERROR);
373        }
374        return rv;
375    }
376
377     /**
378     * Print the specified node. The page layout is the job default.
379     * If the job state is CANCELED, ERROR or DONE, this method will
380     * return false.
381     * @param node The node to print.
382     * @return whether rendering was successful.
383     * @throws NullPointerException if the node parameter is null.
384     */
385    public synchronized boolean printPage(Node node) {
386        return printPage(settings.getPageLayout(), node);
387    }
388
389    /**
390     * An enum class used in reporting status of a print job.
391     * Applications can listen to the job status via the
392     * {@link #jobStatus jobStatus} property, or may query it directly
393     * using {@link javafx.print.PrinterJob#getJobStatus getJobStatus()}.
394     * <p>
395     * The typical life cycle of a job is as follows :
396     * <ul>
397     * <li>job will be created with status <code>NOT_STARTED</code> and
398     * will stay there during configuration via dialogs etc.
399     * <li>job will enter state <code>PRINTING</code> when the first page
400     * is printed.
401     * <li>job will enter state <code>DONE</code> once the job is
402     * successfully completed without being cancelled or encountering
403     * an error. The job is now completed.
404     * <li>A job that encounters an <code>ERROR</code> or is
405     * <code>CANCELED</code> is also considered completed.
406     * </ul>
407     * <p>
408     * A job may not revert to an earlier status in its life cycle and
409     * the current job state affects operations that may be performed.
410     * For example a job may not begin printing again if it has previously
411     * passed that state and entered any of the termination states.
412     *
413     * @since JavaFX 8
414     */
415    public static enum JobStatus {
416
417        /**
418         * The new job status. May display print dialogs and
419         * configure the job and initiate printing.
420         */
421        NOT_STARTED,
422
423        /**
424         * The job has requested to print at least one page,
425         * and has not terminated printing. May no longer
426         * display print dialogs.
427         */
428        PRINTING,
429
430        /**
431         * The job has been cancelled by the application.
432         * May not display dialogs or initiate printing.
433         * Job should be discarded. There is no need to
434         * call endJob().
435         */
436        CANCELED,
437
438        /**
439         * The job encountered an error.
440         * Job should be discarded. There is no need to
441         * call endJob().
442         */
443        ERROR,
444
445        /**
446         * The job initiated printing and later called endJob()
447         * which reported success. The job can be discarded
448         * as it cannot be re-used.
449         */
450        DONE
451    };
452
453    private ReadOnlyObjectWrapper<JobStatus> jobStatus =
454        new ReadOnlyObjectWrapper(JobStatus.NOT_STARTED);
455
456    /**
457     * A read only object property representing the current
458     * <code>JobStatus</code>
459     */
460    public ReadOnlyObjectProperty<JobStatus> jobStatusProperty() {
461        return jobStatus.getReadOnlyProperty();
462    }
463
464    /**
465     * Obtain the current status of the job.
466     * @return the current <code>JobStatus</code>
467     */
468    public JobStatus getJobStatus() {
469        return jobStatus.get();
470    }
471
472    /**
473     * Cancel the underlying print job at the earliest opportunity.
474     * It may return immediately without waiting for the job cancellation
475     * to be complete in case this would block the FX user thread
476     * for any period of time.
477     * If printing is in process at that time, then typically
478     * this means cancellation is after the current page is rendered.
479     * The job status is updated to CANCELED only once that has happened.
480     * Thus determining that the job is CANCELED requires monitoring
481     * the job status.
482     * <p>
483     * The call has no effect if the job has already been requested
484     * to be CANCELED, or is in the state ERROR or DONE.
485     * For example it will not de-queue from the printer a job that
486     * has already been spooled for printing.
487     * Once a job is cancelled, it is not valid to call any methods
488     * which render new content or change job state.
489     */
490    public void cancelJob() {
491        if (jobStatus.get().ordinal() <= JobStatus.PRINTING.ordinal()) {
492            jobStatus.set(JobStatus.CANCELED);
493            jobImpl.cancelJob();
494        }
495    }
496
497    /**
498     * If the job can be successfully spooled to the printer queue
499     * this will return true. Note : this does not mean the job already
500     * printed as that could entail waiting for minutes or longer,
501     * even where implementable.
502     * <p>
503     * A return value of false means the job could not be spooled,
504     * or was already completed.
505     * <p>
506     * Successful completion will also update job status to <code>DONE</code>,
507     * at which point the job can no longer be used.
508     * <p>
509     * Calling endJob() on a job for which no pages have been printed
510     * is equivalent to calling {code cancelJob()}.
511     * @return true if job is spooled, false if its not, or the job
512     * was already in a completed state.
513     */
514    public synchronized boolean endJob() {
515        if (jobStatus.get() == JobStatus.NOT_STARTED) {
516            cancelJob();
517            return false;
518        } else if (jobStatus.get() == JobStatus.PRINTING) {
519            boolean rv = jobImpl.endJob();
520            jobStatus.set(rv ? JobStatus.DONE : JobStatus.ERROR);
521            return rv;
522        } else {
523            return false;
524        }
525    }
526
527    @Override
528    public String toString() {
529        return "JavaFX PrinterJob " +
530            getPrinter() + "\n" +
531            getJobSettings() + "\n" +
532            "Job Status = " + getJobStatus();
533    }
534
535}