1 /* 2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.lwawt.macosx; 27 28 29 import java.awt.*; 30 import java.awt.geom.Rectangle2D; 31 import java.awt.image.BufferedImage; 32 import java.awt.print.*; 33 import java.security.AccessController; 34 import java.security.PrivilegedAction; 35 36 import javax.print.*; 37 import javax.print.attribute.PrintRequestAttributeSet; 38 import javax.print.attribute.HashPrintRequestAttributeSet; 39 import javax.print.attribute.standard.PageRanges; 40 41 import sun.java2d.*; 42 import sun.print.*; 43 44 public class CPrinterJob extends RasterPrinterJob { 45 // NOTE: This uses RasterPrinterJob as a base, but it doesn't use 46 // all of the RasterPrinterJob functions. RasterPrinterJob will 47 // break down printing to pieces that aren't necessary under MacOSX 48 // printing, such as controlling the # of copies and collating. These 49 // are handled by the native printing. RasterPrinterJob is kept for 50 // future compatibility and the state keeping that it handles. 51 52 private static String sShouldNotReachHere = "Should not reach here."; 53 54 private volatile SecondaryLoop printingLoop; 55 56 private boolean noDefaultPrinter = false; 57 58 private static Font defaultFont; 59 60 // This is the NSPrintInfo for this PrinterJob. Protect multi thread 61 // access to it. It is used by the pageDialog, jobDialog, and printLoop. 62 // This way the state of these items is shared across these calls. 63 // PageFormat data is passed in and set on the fNSPrintInfo on a per call 64 // basis. 65 private long fNSPrintInfo = -1; 66 private Object fNSPrintInfoLock = new Object(); 67 68 static { 69 // AWT has to be initialized for the native code to function correctly. 70 Toolkit.getDefaultToolkit(); 71 } 72 73 /** 74 * Presents a dialog to the user for changing the properties of 75 * the print job. 76 * This method will display a native dialog if a native print 77 * service is selected, and user choice of printers will be restricted 78 * to these native print services. 79 * To present the cross platform print dialog for all services, 80 * including native ones instead use 81 * <code>printDialog(PrintRequestAttributeSet)</code>. 82 * <p> 83 * PrinterJob implementations which can use PrintService's will update 84 * the PrintService for this PrinterJob to reflect the new service 85 * selected by the user. 86 * @return <code>true</code> if the user does not cancel the dialog; 87 * <code>false</code> otherwise. 88 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 89 * returns true. 90 * @see java.awt.GraphicsEnvironment#isHeadless 91 */ 92 public boolean printDialog() throws HeadlessException { 93 if (GraphicsEnvironment.isHeadless()) { 94 throw new HeadlessException(); 95 } 96 97 if (noDefaultPrinter) { 98 return false; 99 } 100 101 if (attributes == null) { 102 attributes = new HashPrintRequestAttributeSet(); 103 } 104 105 if (getPrintService() instanceof StreamPrintService) { 106 return super.printDialog(attributes); 107 } 108 109 return jobSetup(getPageable(), checkAllowedToPrintToFile()); 110 } 111 112 /** 113 * Displays a dialog that allows modification of a 114 * <code>PageFormat</code> instance. 115 * The <code>page</code> argument is used to initialize controls 116 * in the page setup dialog. 117 * If the user cancels the dialog then this method returns the 118 * original <code>page</code> object unmodified. 119 * If the user okays the dialog then this method returns a new 120 * <code>PageFormat</code> object with the indicated changes. 121 * In either case, the original <code>page</code> object is 122 * not modified. 123 * @param page the default <code>PageFormat</code> presented to the 124 * user for modification 125 * @return the original <code>page</code> object if the dialog 126 * is cancelled; a new <code>PageFormat</code> object 127 * containing the format indicated by the user if the 128 * dialog is acknowledged. 129 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 130 * returns true. 131 * @see java.awt.GraphicsEnvironment#isHeadless 132 * @since 1.2 133 */ 134 public PageFormat pageDialog(PageFormat page) throws HeadlessException { 135 if (GraphicsEnvironment.isHeadless()) { 136 throw new HeadlessException(); 137 } 138 139 if (noDefaultPrinter) { 140 return page; 141 } 142 143 if (getPrintService() instanceof StreamPrintService) { 144 return super.pageDialog(page); 145 } 146 147 PageFormat pageClone = (PageFormat) page.clone(); 148 boolean doIt = pageSetup(pageClone, null); 149 return doIt ? pageClone : page; 150 } 151 152 /** 153 * Clones the <code>PageFormat</code> argument and alters the 154 * clone to describe a default page size and orientation. 155 * @param page the <code>PageFormat</code> to be cloned and altered 156 * @return clone of <code>page</code>, altered to describe a default 157 * <code>PageFormat</code>. 158 */ 159 public PageFormat defaultPage(PageFormat page) { 160 PageFormat newPage = (PageFormat)page.clone(); 161 getDefaultPage(newPage); 162 return newPage; 163 } 164 165 protected void setAttributes(PrintRequestAttributeSet attributes) throws PrinterException { 166 super.setAttributes(attributes); 167 168 if (attributes == null) { 169 return; 170 } 171 172 // See if this has an NSPrintInfo in it. 173 NSPrintInfo nsPrintInfo = (NSPrintInfo)attributes.get(NSPrintInfo.class); 174 if (nsPrintInfo != null) { 175 fNSPrintInfo = nsPrintInfo.getValue(); 176 } 177 178 PageRanges pageRangesAttr = (PageRanges)attributes.get(PageRanges.class); 179 if (isSupportedValue(pageRangesAttr, attributes)) { 180 SunPageSelection rangeSelect = (SunPageSelection)attributes.get(SunPageSelection.class); 181 // If rangeSelect is not null, we are using AWT's print dialog that has 182 // All, Selection, and Range radio buttons 183 if (rangeSelect == null || rangeSelect == SunPageSelection.RANGE) { 184 int[][] range = pageRangesAttr.getMembers(); 185 // setPageRange will set firstPage and lastPage as called in getFirstPage 186 // and getLastPage 187 setPageRange(range[0][0] - 1, range[0][1] - 1); 188 } 189 } 190 } 191 192 volatile boolean onEventThread; 193 194 @Override 195 protected void cancelDoc() throws PrinterAbortException { 196 super.cancelDoc(); 197 if (printingLoop != null) { 198 printingLoop.exit(); 199 } 200 } 201 202 private void completePrintLoop() { 203 Runnable r = new Runnable() { public void run() { 204 synchronized(this) { 205 performingPrinting = false; 206 } 207 if (printingLoop != null) { 208 printingLoop.exit(); 209 } 210 }}; 211 212 if (onEventThread) { 213 try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); } 214 } else { 215 r.run(); 216 } 217 } 218 219 220 public void print(PrintRequestAttributeSet attributes) throws PrinterException { 221 // NOTE: Some of this code is copied from RasterPrinterJob. 222 223 224 // this code uses javax.print APIs 225 // this will make it print directly to the printer 226 // this will not work if the user clicks on the "Preview" button 227 // However if the printer is a StreamPrintService, its the right path. 228 PrintService psvc = getPrintService(); 229 if (psvc instanceof StreamPrintService) { 230 spoolToService(psvc, attributes); 231 return; 232 } 233 234 235 setAttributes(attributes); 236 // throw exception for invalid destination 237 if (destinationAttr != null) { 238 validateDestination(destinationAttr); 239 } 240 241 /* Get the range of pages we are to print. If the 242 * last page to print is unknown, then we print to 243 * the end of the document. Note that firstPage 244 * and lastPage are 0 based page indices. 245 */ 246 247 int firstPage = getFirstPage(); 248 int lastPage = getLastPage(); 249 if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) { 250 int totalPages = mDocument.getNumberOfPages(); 251 if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) { 252 lastPage = mDocument.getNumberOfPages() - 1; 253 } 254 } 255 256 try { 257 synchronized (this) { 258 performingPrinting = true; 259 userCancelled = false; 260 } 261 262 //Add support for PageRange 263 PageRanges pr = (attributes == null) ? null 264 : (PageRanges)attributes.get(PageRanges.class); 265 int[][] prMembers = (pr == null) ? new int[0][0] : pr.getMembers(); 266 int loopi = 0; 267 do { 268 if (EventQueue.isDispatchThread()) { 269 // This is an AWT EventQueue, and this print rendering loop needs to block it. 270 271 onEventThread = true; 272 273 printingLoop = AccessController.doPrivileged(new PrivilegedAction<SecondaryLoop>() { 274 @Override 275 public SecondaryLoop run() { 276 return Toolkit.getDefaultToolkit() 277 .getSystemEventQueue() 278 .createSecondaryLoop(); 279 } 280 }); 281 282 try { 283 // Fire off the print rendering loop on the AppKit thread, and don't have 284 // it wait and block this thread. 285 if (printLoop(false, firstPage, lastPage)) { 286 // Start a secondary loop on EDT until printing operation is finished or cancelled 287 printingLoop.enter(); 288 } 289 } catch (Exception e) { 290 e.printStackTrace(); 291 } 292 } else { 293 // Fire off the print rendering loop on the AppKit, and block this thread 294 // until it is done. 295 // But don't actually block... we need to come back here! 296 onEventThread = false; 297 298 try { 299 printLoop(true, firstPage, lastPage); 300 } catch (Exception e) { 301 e.printStackTrace(); 302 } 303 } 304 if (++loopi < prMembers.length) { 305 firstPage = prMembers[loopi][0]-1; 306 lastPage = prMembers[loopi][1] -1; 307 } 308 } while (loopi < prMembers.length); 309 } finally { 310 synchronized (this) { 311 // NOTE: Native code shouldn't allow exceptions out while 312 // printing. They should cancel the print loop. 313 performingPrinting = false; 314 notify(); 315 } 316 if (printingLoop != null) { 317 printingLoop.exit(); 318 } 319 } 320 321 // Normalize the collated, # copies, numPages, first/last pages. Need to 322 // make note of pageRangesAttr. 323 324 // Set up NSPrintInfo with the java settings (PageFormat & Paper). 325 326 // Create an NSView for printing. Have knowsPageRange return YES, and give the correct 327 // range, or MAX? if unknown. Have rectForPage do a peekGraphics check before returning 328 // the rectangle. Have drawRect do the real render of the page. Have printJobTitle do 329 // the right thing. 330 331 // Call NSPrintOperation, it will call NSView.drawRect: for each page. 332 333 // NSView.drawRect: will create a CPrinterGraphics with the current CGContextRef, and then 334 // pass this Graphics onto the Printable with the appropriate PageFormat and index. 335 336 // Need to be able to cancel the NSPrintOperation (using code from RasterPrinterJob, be 337 // sure to initialize userCancelled and performingPrinting member variables). 338 339 // Extensions available from AppKit: Print to PDF or EPS file! 340 } 341 342 /** 343 * Returns the resolution in dots per inch across the width 344 * of the page. 345 */ 346 protected double getXRes() { 347 // NOTE: This is not used in the CPrinterJob code path. 348 return 0; 349 } 350 351 /** 352 * Returns the resolution in dots per inch down the height 353 * of the page. 354 */ 355 protected double getYRes() { 356 // NOTE: This is not used in the CPrinterJob code path. 357 return 0; 358 } 359 360 /** 361 * Must be obtained from the current printer. 362 * Value is in device pixels. 363 * Not adjusted for orientation of the paper. 364 */ 365 protected double getPhysicalPrintableX(Paper p) { 366 // NOTE: This is not used in the CPrinterJob code path. 367 return 0; 368 } 369 370 /** 371 * Must be obtained from the current printer. 372 * Value is in device pixels. 373 * Not adjusted for orientation of the paper. 374 */ 375 protected double getPhysicalPrintableY(Paper p) { 376 // NOTE: This is not used in the CPrinterJob code path. 377 return 0; 378 } 379 380 /** 381 * Must be obtained from the current printer. 382 * Value is in device pixels. 383 * Not adjusted for orientation of the paper. 384 */ 385 protected double getPhysicalPrintableWidth(Paper p) { 386 // NOTE: This is not used in the CPrinterJob code path. 387 return 0; 388 } 389 390 /** 391 * Must be obtained from the current printer. 392 * Value is in device pixels. 393 * Not adjusted for orientation of the paper. 394 */ 395 protected double getPhysicalPrintableHeight(Paper p) { 396 // NOTE: This is not used in the CPrinterJob code path. 397 return 0; 398 } 399 400 /** 401 * Must be obtained from the current printer. 402 * Value is in device pixels. 403 * Not adjusted for orientation of the paper. 404 */ 405 protected double getPhysicalPageWidth(Paper p) { 406 // NOTE: This is not used in the CPrinterJob code path. 407 return 0; 408 } 409 410 /** 411 * Must be obtained from the current printer. 412 * Value is in device pixels. 413 * Not adjusted for orientation of the paper. 414 */ 415 protected double getPhysicalPageHeight(Paper p) { 416 // NOTE: This is not used in the CPrinterJob code path. 417 return 0; 418 } 419 420 /** 421 * Begin a new page. This call's Window's 422 * StartPage routine. 423 */ 424 protected void startPage(PageFormat format, Printable painter, int index) throws PrinterException { 425 // NOTE: This is not used in the CPrinterJob code path. 426 throw new PrinterException(sShouldNotReachHere); 427 } 428 429 /** 430 * End a page. 431 */ 432 protected void endPage(PageFormat format, Printable painter, int index) throws PrinterException { 433 // NOTE: This is not used in the CPrinterJob code path. 434 throw new PrinterException(sShouldNotReachHere); 435 } 436 437 /** 438 * Prints the contents of the array of ints, 'data' 439 * to the current page. The band is placed at the 440 * location (x, y) in device coordinates on the 441 * page. The width and height of the band is 442 * specified by the caller. 443 */ 444 protected void printBand(byte[] data, int x, int y, int width, int height) throws PrinterException { 445 // NOTE: This is not used in the CPrinterJob code path. 446 throw new PrinterException(sShouldNotReachHere); 447 } 448 449 /** 450 * Called by the print() method at the start of 451 * a print job. 452 */ 453 protected void startDoc() throws PrinterException { 454 // NOTE: This is not used in the CPrinterJob code path. 455 throw new PrinterException(sShouldNotReachHere); 456 } 457 458 /** 459 * Called by the print() method at the end of 460 * a print job. 461 */ 462 protected void endDoc() throws PrinterException { 463 // NOTE: This is not used in the CPrinterJob code path. 464 throw new PrinterException(sShouldNotReachHere); 465 } 466 467 /* Called by cancelDoc */ 468 protected native void abortDoc(); 469 470 /** 471 * Displays the page setup dialog placing the user's 472 * settings into 'page'. 473 */ 474 public boolean pageSetup(PageFormat page, Printable painter) { 475 CPrinterDialog printerDialog = new CPrinterPageDialog(null, this, page, painter); 476 printerDialog.setVisible(true); 477 boolean result = printerDialog.getRetVal(); 478 printerDialog.dispose(); 479 return result; 480 } 481 482 /** 483 * Displays the print dialog and records the user's settings 484 * into this object. Return false if the user cancels the 485 * dialog. 486 * If the dialog is to use a set of attributes, useAttributes is true. 487 */ 488 private boolean jobSetup(Pageable doc, boolean allowPrintToFile) { 489 CPrinterDialog printerDialog = new CPrinterJobDialog(null, this, doc, allowPrintToFile); 490 printerDialog.setVisible(true); 491 boolean result = printerDialog.getRetVal(); 492 printerDialog.dispose(); 493 return result; 494 } 495 496 /** 497 * Alters the orientation and Paper to match defaults obtained 498 * from a printer. 499 */ 500 private native void getDefaultPage(PageFormat page); 501 502 /** 503 * validate the paper size against the current printer. 504 */ 505 protected native void validatePaper(Paper origPaper, Paper newPaper ); 506 507 // The following methods are CPrinterJob specific. 508 509 protected void finalize() { 510 if (fNSPrintInfo != -1) { 511 dispose(fNSPrintInfo); 512 } 513 } 514 515 private native long createNSPrintInfo(); 516 private native void dispose(long printInfo); 517 518 private long getNSPrintInfo() { 519 // This is called from the native side. 520 synchronized (fNSPrintInfoLock) { 521 if (fNSPrintInfo == -1) { 522 fNSPrintInfo = createNSPrintInfo(); 523 } 524 return fNSPrintInfo; 525 } 526 } 527 528 private native boolean printLoop(boolean waitUntilDone, int firstPage, int lastPage) throws PrinterException; 529 530 private PageFormat getPageFormat(int pageIndex) { 531 // This is called from the native side. 532 PageFormat page; 533 try { 534 page = getPageable().getPageFormat(pageIndex); 535 } catch (Exception e) { 536 return null; 537 } 538 return page; 539 } 540 541 private Printable getPrintable(int pageIndex) { 542 // This is called from the native side. 543 Printable painter; 544 try { 545 painter = getPageable().getPrintable(pageIndex); 546 } catch (Exception e) { 547 return null; 548 } 549 return painter; 550 } 551 552 private String getPrinterName(){ 553 // This is called from the native side. 554 PrintService service = getPrintService(); 555 if (service == null) return null; 556 return service.getName(); 557 } 558 559 private void setPrinterServiceFromNative(String printerName) { 560 // This is called from the native side. 561 PrintService[] services = PrintServiceLookup.lookupPrintServices(DocFlavor.SERVICE_FORMATTED.PAGEABLE, null); 562 563 for (int i = 0; i < services.length; i++) { 564 PrintService service = services[i]; 565 566 if (printerName.equals(service.getName())) { 567 try { 568 setPrintService(service); 569 } catch (PrinterException e) { 570 // ignored 571 } 572 return; 573 } 574 } 575 } 576 577 private Rectangle2D getPageFormatArea(PageFormat page) { 578 Rectangle2D.Double pageFormatArea = 579 new Rectangle2D.Double(page.getImageableX(), 580 page.getImageableY(), 581 page.getImageableWidth(), 582 page.getImageableHeight()); 583 return pageFormatArea; 584 } 585 586 private boolean cancelCheck() { 587 // This is called from the native side. 588 589 // This is used to avoid deadlock 590 // We would like to just call if isCancelled(), 591 // but that will block the AppKit thread against whomever is holding the synchronized lock 592 boolean cancelled = (performingPrinting && userCancelled); 593 if (cancelled) { 594 try { 595 LWCToolkit.invokeLater(new Runnable() { public void run() { 596 try { 597 cancelDoc(); 598 } catch (PrinterAbortException pae) { 599 // no-op, let the native side handle it 600 } 601 }}, null); 602 } catch (java.lang.reflect.InvocationTargetException ite) {} 603 } 604 return cancelled; 605 } 606 607 private PeekGraphics createFirstPassGraphics(PrinterJob printerJob, PageFormat page) { 608 // This is called from the native side. 609 BufferedImage bimg = new BufferedImage((int)Math.round(page.getWidth()), (int)Math.round(page.getHeight()), BufferedImage.TYPE_INT_ARGB_PRE); 610 PeekGraphics peekGraphics = createPeekGraphics(bimg.createGraphics(), printerJob); 611 Rectangle2D pageFormatArea = getPageFormatArea(page); 612 initPrinterGraphics(peekGraphics, pageFormatArea); 613 return peekGraphics; 614 } 615 616 private void printToPathGraphics( final PeekGraphics graphics, // Always an actual PeekGraphics 617 final PrinterJob printerJob, // Always an actual CPrinterJob 618 final Printable painter, // Client class 619 final PageFormat page, // Client class 620 final int pageIndex, 621 final long context) throws PrinterException { 622 // This is called from the native side. 623 Runnable r = new Runnable() { public void run() { 624 try { 625 SurfaceData sd = CPrinterSurfaceData.createData(page, context); // Just stores page into an ivar 626 if (defaultFont == null) { 627 defaultFont = new Font("Dialog", Font.PLAIN, 12); 628 } 629 Graphics2D delegate = new SunGraphics2D(sd, Color.black, Color.white, defaultFont); 630 631 Graphics2D pathGraphics = new CPrinterGraphics(delegate, printerJob); // Just stores delegate into an ivar 632 Rectangle2D pageFormatArea = getPageFormatArea(page); 633 initPrinterGraphics(pathGraphics, pageFormatArea); 634 painter.print(pathGraphics, page, pageIndex); 635 delegate.dispose(); 636 delegate = null; 637 } catch (PrinterException pe) { throw new java.lang.reflect.UndeclaredThrowableException(pe); } 638 }}; 639 640 if (onEventThread) { 641 try { EventQueue.invokeAndWait(r); 642 } catch (java.lang.reflect.InvocationTargetException ite) { 643 Throwable te = ite.getTargetException(); 644 if (te instanceof PrinterException) throw (PrinterException)te; 645 else te.printStackTrace(); 646 } catch (Exception e) { e.printStackTrace(); } 647 } else { 648 r.run(); 649 } 650 651 } 652 653 // Returns either 1. an array of 3 object (PageFormat, Printable, PeekGraphics) or 2. null 654 private Object[] getPageformatPrintablePeekgraphics(final int pageIndex) { 655 final Object[] ret = new Object[3]; 656 final PrinterJob printerJob = this; 657 658 Runnable r = new Runnable() { public void run() { synchronized(ret) { 659 try { 660 Pageable pageable = getPageable(); 661 PageFormat pageFormat = pageable.getPageFormat(pageIndex); 662 if (pageFormat != null) { 663 Printable printable = pageable.getPrintable(pageIndex); 664 if (printable != null) { 665 BufferedImage bimg = new BufferedImage((int)Math.round(pageFormat.getWidth()), (int)Math.round(pageFormat.getHeight()), BufferedImage.TYPE_INT_ARGB_PRE); 666 PeekGraphics peekGraphics = createPeekGraphics(bimg.createGraphics(), printerJob); 667 Rectangle2D pageFormatArea = getPageFormatArea(pageFormat); 668 initPrinterGraphics(peekGraphics, pageFormatArea); 669 670 // Do the assignment here! 671 ret[0] = pageFormat; 672 ret[1] = printable; 673 ret[2] = peekGraphics; 674 } 675 } 676 } catch (Exception e) {} // Original code bailed on any exception 677 }}}; 678 679 if (onEventThread) { 680 try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); } 681 } else { 682 r.run(); 683 } 684 685 synchronized(ret) { 686 if (ret[2] != null) 687 return ret; 688 return null; 689 } 690 } 691 692 private Rectangle2D printAndGetPageFormatArea(final Printable printable, final Graphics graphics, final PageFormat pageFormat, final int pageIndex) { 693 final Rectangle2D[] ret = new Rectangle2D[1]; 694 695 Runnable r = new Runnable() { public void run() { synchronized(ret) { 696 try { 697 int pageResult = printable.print(graphics, pageFormat, pageIndex); 698 if (pageResult != Printable.NO_SUCH_PAGE) { 699 ret[0] = getPageFormatArea(pageFormat); 700 } 701 } catch (Exception e) {} // Original code bailed on any exception 702 }}}; 703 704 if (onEventThread) { 705 try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); } 706 } else { 707 r.run(); 708 } 709 710 synchronized(ret) { return ret[0]; } 711 } 712 713 // upcall from native 714 private static void detachPrintLoop(final long target, final long arg) { 715 new Thread() { public void run() { 716 _safePrintLoop(target, arg); 717 }}.start(); 718 } 719 private static native void _safePrintLoop(long target, long arg); 720 721 @Override 722 protected void startPage(PageFormat arg0, Printable arg1, int arg2, boolean arg3) throws PrinterException { 723 // TODO Auto-generated method stub 724 } 725 }