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}