001package jmri.util.davidflanagan; 002 003import java.awt.*; 004import java.awt.JobAttributes.DefaultSelectionType; 005import java.awt.JobAttributes.SidesType; 006import java.awt.event.ActionEvent; 007import java.awt.font.FontRenderContext; 008import java.awt.geom.Rectangle2D; 009import java.io.IOException; 010import java.io.Writer; 011import java.text.DateFormat; 012import java.util.*; 013 014import javax.swing.*; 015import javax.swing.border.EmptyBorder; 016 017import jmri.util.JmriJFrame; 018import jmri.util.PaperUtils; 019 020/** 021 * Provide graphic output to a screen/printer. 022 * <p> 023 * This is from Chapter 12 of the O'Reilly Java book by David Flanagan with the 024 * alligator on the front. 025 * 026 * @author David Flanagan 027 * @author Dennis Miller 028 */ 029public class HardcopyWriter extends Writer { 030 031 // instance variables 032 protected PrintJob job; 033 protected Graphics page; 034 protected String jobname; 035 protected String line; 036 protected int fontsize; 037 protected String time; 038 protected Dimension pagesizePixels; 039 protected Dimension pagesizePoints; 040 protected Font font, headerfont; 041 protected String fontName = "Monospaced"; 042 protected int fontStyle = Font.PLAIN; 043 protected FontMetrics metrics; 044 protected FontMetrics headermetrics; 045 protected int x0, y0; 046 protected int height, width; 047 protected int headery; 048 protected float charwidth; 049 protected int lineheight; 050 protected int lineascent; 051 protected int chars_per_line; 052 protected int lines_per_page; 053 protected int charnum = 0, linenum = 0; 054 protected float charoffset = 0; 055 protected int pagenum = 0; 056 protected int prFirst = 1; 057 protected Color color = Color.black; 058 protected boolean printHeader = true; 059 060 protected boolean isPreview; 061 protected Image previewImage; 062 protected Vector<Image> pageImages = new Vector<>(3, 3); 063 protected JmriJFrame previewFrame; 064 protected JPanel previewPanel; 065 protected ImageIcon previewIcon = new ImageIcon(); 066 protected JLabel previewLabel = new JLabel(); 067 protected JToolBar previewToolBar = new JToolBar(); 068 protected Frame frame; 069 protected JButton nextButton; 070 protected JButton previousButton; 071 protected JButton closeButton; 072 protected JLabel pageCount = new JLabel(); 073 074 // save state between invocations of write() 075 private boolean last_char_was_return = false; 076 077 // A static variable to hold prefs between print jobs 078 // private static Properties printprops = new Properties(); 079 // Job and Page attributes 080 JobAttributes jobAttributes = new JobAttributes(); 081 PageAttributes pageAttributes = new PageAttributes(); 082 083 // constructor modified to add print preview parameter 084 public HardcopyWriter(Frame frame, String jobname, int fontsize, double leftmargin, double rightmargin, 085 double topmargin, double bottommargin, boolean isPreview) throws HardcopyWriter.PrintCanceledException { 086 hardcopyWriter(frame, jobname, fontsize, leftmargin, rightmargin, topmargin, bottommargin, isPreview); 087 } 088 089 // constructor modified to add default printer name, page orientation, print header, print duplex, and page size 090 public HardcopyWriter(Frame frame, String jobname, int fontsize, double leftmargin, double rightmargin, 091 double topmargin, double bottommargin, boolean isPreview, String printerName, boolean isLandscape, 092 boolean isPrintHeader, SidesType sidesType, Dimension pagesize) 093 throws HardcopyWriter.PrintCanceledException { 094 095 // print header? 096 this.printHeader = isPrintHeader; 097 098 // set default print name 099 jobAttributes.setPrinter(printerName); 100 101 if (sidesType != null) { 102 jobAttributes.setSides(sidesType); 103 } 104 if (isLandscape) { 105 pageAttributes.setOrientationRequested(PageAttributes.OrientationRequestedType.LANDSCAPE); 106 } 107 108 hardcopyWriter(frame, jobname, fontsize, leftmargin, rightmargin, topmargin, bottommargin, isPreview); 109 } 110 111 private void hardcopyWriter(Frame frame, String jobname, int fontsize, double leftmargin, double rightmargin, 112 double topmargin, double bottommargin, boolean isPreview) throws HardcopyWriter.PrintCanceledException { 113 114 this.isPreview = isPreview; 115 this.frame = frame; 116 117 // set default to color 118 pageAttributes.setColor(PageAttributes.ColorType.COLOR); 119 120 pagesizePixels = getPagesizePixels(); 121 pagesizePoints = getPagesizePoints(); 122 123 // skip printer selection if preview 124 if (!isPreview) { 125 Toolkit toolkit = frame.getToolkit(); 126 127 PaperUtils.syncPageAttributesToPrinter(pageAttributes); 128 129 job = toolkit.getPrintJob(frame, jobname, jobAttributes, pageAttributes); 130 131 if (job == null) { 132 throw new PrintCanceledException("User cancelled print request"); 133 } 134 135 pagesizePixels = job.getPageDimension(); 136 int printerDpi = job.getPageResolution(); 137 pagesizePoints = 138 new Dimension((72 * pagesizePixels.width) / printerDpi, (72 * pagesizePixels.height) / printerDpi); 139 // determine if user selected a range of pages to print out, note that page becomes null if range 140 // selected is less than the total number of pages, that's the reason for the page null checks 141 if (jobAttributes.getDefaultSelection().equals(DefaultSelectionType.RANGE)) { 142 prFirst = jobAttributes.getPageRanges()[0][0]; 143 } 144 } 145 146 x0 = (int) (leftmargin * 72); 147 y0 = (int) (topmargin * 72); 148 width = pagesizePoints.width - (int) ((leftmargin + rightmargin) * 72); 149 height = pagesizePoints.height - (int) ((topmargin + bottommargin) * 72); 150 151 // get body font and font size 152 font = new Font(fontName, fontStyle, fontsize); 153 metrics = frame.getFontMetrics(font); 154 lineheight = metrics.getHeight(); 155 lineascent = metrics.getAscent(); 156 Rectangle2D bounds = metrics.getStringBounds("mmmmmmmmmm", frame.getGraphics()); 157 charwidth = (float) (bounds.getWidth() / 10); 158 159 // compute lines and columns within margins 160 chars_per_line = (int) (width / charwidth); 161 lines_per_page = height / lineheight; 162 163 // header font info 164 headerfont = new Font("SansSerif", Font.ITALIC, fontsize); 165 headermetrics = frame.getFontMetrics(headerfont); 166 headery = y0 - (int) (0.125 * 72) - headermetrics.getHeight() + headermetrics.getAscent(); 167 168 // compute date/time for header 169 DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT); 170 df.setTimeZone(TimeZone.getDefault()); 171 time = df.format(new Date()); 172 173 this.jobname = jobname; 174 this.fontsize = fontsize; 175 176 if (isPreview) { 177 previewFrame = new JmriJFrame(Bundle.getMessage("PrintPreviewTitle") + " " + jobname); 178 previewFrame.getContentPane().setLayout(new BorderLayout()); 179 toolBarInit(); 180 previewToolBar.setFloatable(false); 181 previewFrame.getContentPane().add(previewToolBar, BorderLayout.NORTH); 182 previewPanel = new JPanel(); 183 previewPanel.setSize(pagesizePixels.width, pagesizePixels.height); 184 // add the panel to the frame and make visible, otherwise creating the image will fail. 185 // use a scroll pane to handle print images bigger than the window 186 previewFrame.getContentPane().add(new JScrollPane(previewPanel), BorderLayout.CENTER); 187 // page width 660 for portrait 188 previewFrame.setSize(pagesizePixels.width + 48, pagesizePixels.height + 100); 189 previewFrame.setVisible(true); 190 } 191 } 192 193 /** 194 * Create a print preview toolbar. 195 */ 196 protected void toolBarInit() { 197 previousButton = new JButton(Bundle.getMessage("ButtonPreviousPage")); 198 previewToolBar.add(previousButton); 199 previousButton.addActionListener((ActionEvent actionEvent) -> { 200 pagenum--; 201 displayPage(); 202 }); 203 nextButton = new JButton(Bundle.getMessage("ButtonNextPage")); 204 previewToolBar.add(nextButton); 205 nextButton.addActionListener((ActionEvent actionEvent) -> { 206 pagenum++; 207 displayPage(); 208 }); 209 pageCount = new JLabel(Bundle.getMessage("HeaderPageNum", pagenum, pageImages.size())); 210 pageCount.setBorder(new EmptyBorder(0, 10, 0, 10)); 211 previewToolBar.add(pageCount); 212 closeButton = new JButton(Bundle.getMessage("ButtonClose")); 213 previewToolBar.add(closeButton); 214 closeButton.addActionListener((ActionEvent actionEvent) -> { 215 if (page != null) { 216 page.dispose(); 217 } 218 previewFrame.dispose(); 219 }); 220 } 221 222 /** 223 * Display a page image in the preview pane. 224 * <p> 225 * Not part of the original HardcopyWriter class. 226 */ 227 protected void displayPage() { 228 // limit the pages to the actual range 229 if (pagenum > pageImages.size()) { 230 pagenum = pageImages.size(); 231 } 232 if (pagenum < 1) { 233 pagenum = 1; 234 } 235 // enable/disable the previous/next buttons as appropriate 236 previousButton.setEnabled(true); 237 nextButton.setEnabled(true); 238 if (pagenum == pageImages.size()) { 239 nextButton.setEnabled(false); 240 } 241 if (pagenum == 1) { 242 previousButton.setEnabled(false); 243 } 244 previewImage = pageImages.elementAt(pagenum - 1); 245 previewFrame.setVisible(false); 246 previewIcon.setImage(previewImage); 247 previewLabel.setIcon(previewIcon); 248 // put the label in the panel (already has a scroll pane) 249 previewPanel.add(previewLabel); 250 // set the page count info 251 pageCount.setText(Bundle.getMessage("HeaderPageNum", pagenum, pageImages.size())); 252 // repaint the frame but don't use pack() as we don't want resizing 253 previewFrame.invalidate(); 254 previewFrame.revalidate(); 255 previewFrame.setVisible(true); 256 } 257 258 /** 259 * Function to get the current page size if this is a preview. This is the 260 * pagesize in points (logical units). If this is not a preview, it still 261 * returns the page size for the display. It makes use of the PaperUtils 262 * class to get the default paper size (based on locale and/or printer 263 * settings). 264 * 265 * @return The page size in points 266 */ 267 private Dimension getPagesizePoints() { 268 return PaperUtils.getPaperSizeDimension(); 269 } 270 271 /** 272 * Function to get the current page size if this is a preview. This is the 273 * pagesize in pixels (and not points). If this is not a preview, it still 274 * returns the page size for the display. 275 * 276 * @return The page size in pixels 277 */ 278 private Dimension getPagesizePixels() { 279 int dpi = Toolkit.getDefaultToolkit().getScreenResolution(); 280 Dimension pagesizePoints = getPagesizePoints(); 281 return new Dimension(pagesizePoints.width * dpi / 72, pagesizePoints.height * dpi / 72); 282 } 283 284 /** 285 * Send text to Writer output. 286 * 287 * @param buffer block of text characters 288 * @param index position to start printing 289 * @param len length (number of characters) of output 290 */ 291 @Override 292 public void write(char[] buffer, int index, int len) { 293 synchronized (this.lock) { 294 // loop through all characters passed to us 295 line = ""; 296 for (int i = index; i < index + len; i++) { 297 // if we haven't begun a new page, do that now 298 if (page == null) { 299 newpage(); 300 } 301 302 // if the character is a line terminator, begin a new line 303 // unless its \n after \r 304 if (buffer[i] == '\n') { 305 if (!last_char_was_return) { 306 newline(); 307 } 308 continue; 309 } 310 if (buffer[i] == '\r') { 311 newline(); 312 last_char_was_return = true; 313 continue; 314 } else { 315 last_char_was_return = false; 316 } 317 318 if (buffer[i] == '\f') { 319 pageBreak(); 320 } 321 322 // if some other non-printing char, ignore it 323 if (Character.isWhitespace(buffer[i]) && !Character.isSpaceChar(buffer[i]) && (buffer[i] != '\t')) { 324 continue; 325 } 326 // if no more characters will fit on the line, start new line 327 if (charoffset >= width) { 328 newline(); 329 // also start a new page if needed 330 if (page == null) { 331 newpage(); 332 } 333 } 334 335 // now print the page 336 // if a space, skip one space 337 // if a tab, skip the necessary number 338 // otherwise print the character 339 // We need to position each character one-at-a-time to 340 // match the FontMetrics 341 if (buffer[i] == '\t') { 342 int tab = 8 - (charnum % 8); 343 charnum += tab; 344 charoffset = charnum * charwidth; 345 for (int t = 0; t < tab; t++) { 346 line += " "; 347 } 348 } else { 349 line += buffer[i]; 350 charnum++; 351 charoffset += charwidth; 352 } 353 } 354 if (page != null && pagenum >= prFirst) { 355 page.drawString(line, x0, y0 + (linenum * lineheight) + lineascent); 356 } 357 } 358 } 359 360 /** 361 * Write a given String with the desired color. 362 * <p> 363 * Reset the text color back to the default after the string is written. 364 * 365 * @param c the color desired for this String 366 * @param s the String 367 * @throws java.io.IOException if unable to write to printer 368 */ 369 public void write(Color c, String s) throws IOException { 370 charoffset = 0; 371 if (page == null) { 372 newpage(); 373 } 374 if (page != null) { 375 page.setColor(c); 376 } 377 write(s); 378 // note that the above write(s) can cause the page to become null! 379 if (page != null) { 380 page.setColor(color); // reset color 381 } 382 } 383 384 @Override 385 public void flush() { 386 } 387 388 /** 389 * Handle close event of pane. Modified to clean up the added preview 390 * capability. 391 */ 392 @Override 393 public void close() { 394 synchronized (this.lock) { 395 if (isPreview) { 396 // new JMRI code using try / catch declaration can call this close twice 397 // writer.close() is no longer needed. Work around next line. 398 if (!pageImages.contains(previewImage)) { 399 pageImages.addElement(previewImage); 400 } 401 // set up first page for display in preview frame 402 // to get the image displayed, put it in an icon and the icon in a label 403 pagenum = 1; 404 displayPage(); 405 } 406 if (page != null) { 407 page.dispose(); 408 } 409 if (job != null) { 410 job.end(); 411 } 412 } 413 } 414 415 /** 416 * Free up resources . 417 * <p> 418 * Added so that a preview can be canceled. 419 */ 420 public void dispose() { 421 synchronized (this.lock) { 422 if (page != null) { 423 page.dispose(); 424 } 425 previewFrame.dispose(); 426 if (job != null) { 427 job.end(); 428 } 429 } 430 } 431 432 public void setFontStyle(int style) { 433 synchronized (this.lock) { 434 // try to set a new font, but restore current one if it fails 435 Font current = font; 436 try { 437 font = new Font(fontName, style, fontsize); 438 fontStyle = style; 439 } catch (Exception e) { 440 font = current; 441 } 442 // if a page is pending, set the new font, else newpage() will 443 if (page != null) { 444 page.setFont(font); 445 } 446 } 447 } 448 449 public int getLineHeight() { 450 return this.lineheight; 451 } 452 453 public int getFontSize() { 454 return this.fontsize; 455 } 456 457 public float getCharWidth() { 458 return this.charwidth; 459 } 460 461 public int getLineAscent() { 462 return this.lineascent; 463 } 464 465 public void setFontName(String name) { 466 synchronized (this.lock) { 467 // try to set a new font, but restore current one if it fails 468 Font current = font; 469 try { 470 font = new Font(name, fontStyle, fontsize); 471 fontName = name; 472 metrics = frame.getFontMetrics(font); 473 lineheight = metrics.getHeight(); 474 lineascent = metrics.getAscent(); 475 Rectangle2D bounds = metrics.getStringBounds("mmmmmmmmmm", frame.getGraphics()); 476 charwidth = (float) (bounds.getWidth() / 10); 477 478 // compute lines and columns within margins 479 chars_per_line = (int) (width / charwidth); 480 lines_per_page = height / lineheight; 481 } catch (RuntimeException e) { 482 font = current; 483 } 484 // if a page is pending, set the new font, else newpage() will 485 if (page != null) { 486 page.setFont(font); 487 } 488 } 489 } 490 491 /** 492 * sets the default text color 493 * 494 * @param c the new default text color 495 */ 496 public void setTextColor(Color c) { 497 color = c; 498 } 499 500 /** 501 * End the current page. Subsequent output will be on a new page 502 */ 503 public void pageBreak() { 504 synchronized (this.lock) { 505 if (isPreview) { 506 pageImages.addElement(previewImage); 507 } 508 if (page != null) { 509 page.dispose(); 510 } 511 page = null; 512 newpage(); 513 } 514 } 515 516 /** 517 * Return the number of columns of characters that fit on a page. 518 * 519 * @return the number of characters in a line 520 */ 521 public int getCharactersPerLine() { 522 return this.chars_per_line; 523 } 524 525 /** 526 * Return the number of lines that fit on a page. 527 * 528 * @return the number of lines in a page 529 */ 530 public int getLinesPerPage() { 531 return this.lines_per_page; 532 } 533 534 /** 535 * Internal method begins a new line method modified by Dennis Miller to add 536 * preview capability 537 */ 538 protected void newline() { 539 if (page != null && pagenum >= prFirst) { 540 page.drawString(line, x0, y0 + (linenum * lineheight) + lineascent); 541 } 542 line = ""; 543 charnum = 0; 544 charoffset = 0; 545 linenum++; 546 if (linenum >= lines_per_page) { 547 if (isPreview) { 548 pageImages.addElement(previewImage); 549 } 550 if (page != null) { 551 page.dispose(); 552 } 553 page = null; 554 newpage(); 555 } 556 } 557 558 /** 559 * Internal method beings a new page and prints the header method modified 560 * by Dennis Miller to add preview capability 561 */ 562 protected void newpage() { 563 pagenum++; 564 linenum = 0; 565 charnum = 0; 566 // get a page graphics or image graphics object depending on output destination 567 if (page == null) { 568 if (!isPreview) { 569 if (pagenum >= prFirst) { 570 page = job.getGraphics(); 571 } else { 572 // The job.getGraphics() method will return null if the number of pages requested is greater than 573 // the number the user selected. Since the code checks for a null page in many places, we need to 574 // create a "dummy" page for the pages the user has decided to skip. 575 JFrame f = new JFrame(); 576 f.pack(); 577 page = f.createImage(pagesizePixels.width, pagesizePixels.height).getGraphics(); 578 Graphics2D g2d = (Graphics2D) page; 579 double scale = Toolkit.getDefaultToolkit().getScreenResolution() / 72.0; 580 g2d.scale(scale, scale); 581 } 582 } else { // Preview 583 previewImage = previewPanel.createImage(pagesizePixels.width, pagesizePixels.height); 584 page = previewImage.getGraphics(); 585 Graphics2D g2d = (Graphics2D) page; 586 double scale = Toolkit.getDefaultToolkit().getScreenResolution() / 72.0; 587 g2d.scale(scale, scale); 588 589 // Enable Antialiasing (Smooths the edges) 590 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 591 RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 592 593 // Enable Fractional Metrics (Improves character spacing) 594 g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, 595 RenderingHints.VALUE_FRACTIONALMETRICS_ON); 596 597 // High Quality Rendering 598 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 599 RenderingHints.VALUE_RENDER_QUALITY); 600 601 // Set Interpolation for the Image (The most important for images) 602 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 603 RenderingHints.VALUE_INTERPOLATION_BICUBIC); 604 605 // Enable Antialiasing (Smooths the edges) 606 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 607 RenderingHints.VALUE_ANTIALIAS_ON); 608 609 page.setColor(Color.white); 610 page.fillRect(0, 0, previewImage.getWidth(previewPanel), previewImage.getHeight(previewPanel)); 611 page.setColor(color); 612 } 613 } 614 if (printHeader && page != null && pagenum >= prFirst) { 615 page.setFont(headerfont); 616 page.drawString(jobname, x0, headery); 617 618 FontRenderContext frc = ((Graphics2D) page).getFontRenderContext(); 619 620 String s = "- " + pagenum + " -"; // print page number centered 621 Rectangle2D bounds = headerfont.getStringBounds(s, frc); 622 page.drawString(s, (int) (x0 + (this.width - bounds.getWidth()) / 2), headery); 623 624 bounds = headerfont.getStringBounds(time, frc); 625 page.drawString(time, (int) (x0 + width - bounds.getWidth()), headery); 626 627 // draw a line under the header 628 int y = headery + headermetrics.getDescent() + 1; 629 page.drawLine(x0, y, x0 + width, y); 630 } 631 // set basic font 632 if (page != null) { 633 page.setFont(font); 634 } 635 } 636 637 /** 638 * Write a graphic to the printout. 639 * <p> 640 * This was not in the original class, but was added afterwards by Bob 641 * Jacobsen. Modified by D Miller. Modified by P Gladstone. The image well 642 * be rendered at 1.5 pixels per point. 643 * <p> 644 * The image is positioned on the right side of the paper, at the current 645 * height. 646 * 647 * @param c image to write 648 * @param i ignored, but maintained for API compatibility 649 */ 650 public void write(Image c, Component i) { 651 writeSpecificSize(c, new Dimension((int) (c.getWidth(null) / 1.5), (int) (c.getHeight(null) / 1.5))); 652 } 653 654 /** 655 * Write the decoder pro icon to the output. Method added by P Gladstone. 656 * This actually uses the high resolution image. It also advances the 657 * linenum appropriately (unless no_advance is True) 658 * <p> 659 * The image is positioned on the right side of the paper, at the current 660 * height. 661 * 662 * @param no_advance if true, do not advance the linenum 663 * @return The actual size in points of the icon that was rendered. 664 */ 665 public Dimension writeDecoderProIcon(boolean no_advance) { 666 ImageIcon hiresIcon = 667 new ImageIcon(HardcopyWriter.class.getResource("/resources/decoderpro_large.png")); 668 Image icon = hiresIcon.getImage(); 669 Dimension size = writeSpecificSize(icon, new Dimension(icon.getWidth(null) / 6, icon.getHeight(null) / 6)); 670 if (!no_advance) { 671 // Advance the linenum by the number of lines the icon takes up, plus one to leave some white space below it 672 linenum += (int) Math.ceil((double) size.height / lineheight) + 1; 673 } 674 return size; 675 } 676 677 /** 678 * Write the decoder pro icon to the output. Method added by P Gladstone. 679 * This actually uses the high resolution image. It also advances the 680 * linenum appropriately. 681 * <p> 682 * The image is positioned on the right side of the paper, at the current 683 * height. 684 * 685 * @return The actual size in points of the icon that was rendered. 686 */ 687 public Dimension writeDecoderProIcon() { 688 return writeDecoderProIcon(false); 689 } 690 691 /** 692 * Write a graphic to the printout at a specific size (in points) 693 * <p> 694 * This was not in the original class, but was added afterwards by Kevin 695 * Dickerson. Heavily modified by P Gladstone. 696 * <p> 697 * The image is positioned on the right side of the paper, at the current 698 * height. The image aspect ratio is maintained. 699 * 700 * @param c the image to print 701 * @param requiredSize the dimensions to scale the image to. The image will 702 * fit inside the bounding box. 703 * @return the dimensions of the image in points 704 */ 705 public Dimension writeSpecificSize(Image c, Dimension requiredSize) { 706 // if we haven't begun a new page, do that now 707 if (page == null) { 708 newpage(); 709 } 710 711 float widthScale = (float) requiredSize.width / c.getWidth(null); 712 float heightScale = (float) requiredSize.height / c.getHeight(null); 713 float scale = Math.min(widthScale, heightScale); 714 715 int x = x0 + width - (int) (Math.round(c.getWidth(null) * scale) + charwidth); 716 int y = y0 + (linenum * lineheight) + lineascent; 717 718 Dimension d = new Dimension(Math.round(c.getWidth(null) * scale), Math.round(c.getHeight(null) * scale)); 719 720 if (page != null && pagenum >= prFirst) { 721 page.drawImage(c, x, y, 722 d.width, d.height, 723 null); 724 } 725 return d; 726 } 727 728 /** 729 * A Method to allow a JWindow to print itself at the current line position 730 * <p> 731 * This was not in the original class, but was added afterwards by Dennis 732 * Miller. 733 * <p> 734 * Intended to allow for a graphic printout of the speed table, but can be 735 * used to print any window. The JWindow is passed to the method and prints 736 * itself at the current line and aligned at the left margin. The calling 737 * method should check for sufficient space left on the page and move it to 738 * the top of the next page if there isn't enough space. 739 * 740 * @param jW the window to print 741 */ 742 public void write(JWindow jW) { 743 // if we haven't begun a new page, do that now 744 if (page == null) { 745 newpage(); 746 } 747 if (page != null && pagenum >= prFirst) { 748 int x = x0; 749 int y = y0 + (linenum * lineheight); 750 // shift origin to current printing position 751 page.translate(x, y); 752 // Window must be visible to print 753 jW.setVisible(true); 754 // Have the window print itself 755 jW.printAll(page); 756 // Make it invisible again 757 jW.setVisible(false); 758 // Get rid of the window now that it's printed and put the origin back where it was 759 jW.dispose(); 760 page.translate(-x, -y); 761 } 762 } 763 764 /** 765 * Draw a line on the printout. 766 * <p> 767 * This was not in the original class, but was added afterwards by Dennis 768 * Miller. 769 * <p> 770 * colStart and colEnd represent the horizontal character positions. The 771 * lines actually start in the middle of the character position to make it 772 * easy to draw vertical lines and space them between printed characters. 773 * <p> 774 * rowStart and rowEnd represent the vertical character positions. 775 * Horizontal lines are drawn underneath the row (line) number. They are 776 * offset so they appear evenly spaced, although they don't take into 777 * account any space needed for descenders, so they look best with all caps 778 * text 779 * 780 * @param rowStart vertical starting position 781 * @param colStart horizontal starting position 782 * @param rowEnd vertical ending position 783 * @param colEnd horizontal ending position 784 */ 785 public void write(int rowStart, int colStart, int rowEnd, int colEnd) { 786 // if we haven't begun a new page, do that now 787 if (page == null) { 788 newpage(); 789 } 790 int xStart = (int) (x0 + (colStart - 1) * charwidth + charwidth / 2); 791 int xEnd = (int) (x0 + (colEnd - 1) * charwidth + charwidth / 2); 792 int yStart = y0 + rowStart * lineheight + (lineheight - lineascent) / 2; 793 int yEnd = y0 + rowEnd * lineheight + (lineheight - lineascent) / 2; 794 if (page != null && pagenum >= prFirst) { 795 page.drawLine(xStart, yStart, xEnd, yEnd); 796 } 797 } 798 799 /** 800 * Get the current linenumber. 801 * <p> 802 * This was not in the original class, but was added afterwards by Dennis 803 * Miller. 804 * 805 * @return the line number within the page 806 */ 807 public int getCurrentLineNumber() { 808 return this.linenum; 809 } 810 811 /** 812 * Print vertical borders on the current line at the left and right sides of 813 * the page at character positions 0 and chars_per_line + 1. Border lines 814 * are one text line in height 815 * <p> 816 * This was not in the original class, but was added afterwards by Dennis 817 * Miller. 818 */ 819 public void writeBorders() { 820 write(this.linenum, 0, this.linenum + 1, 0); 821 write(this.linenum, this.chars_per_line + 1, this.linenum + 1, this.chars_per_line + 1); 822 } 823 824 /** 825 * Increase line spacing by a percentage 826 * <p> 827 * This method should be invoked immediately after a new HardcopyWriter is 828 * created. 829 * <p> 830 * This method was added to improve appearance when printing tables 831 * <p> 832 * This was not in the original class, added afterwards by DaveDuchamp. 833 * 834 * @param percent percentage by which to increase line spacing 835 */ 836 public void increaseLineSpacing(int percent) { 837 int delta = (lineheight * percent) / 100; 838 lineheight = lineheight + delta; 839 lineascent = lineascent + delta; 840 lines_per_page = height / lineheight; 841 } 842 843 public static class PrintCanceledException extends Exception { 844 845 public PrintCanceledException(String msg) { 846 super(msg); 847 } 848 } 849 850 // private final static Logger log = LoggerFactory.getLogger(HardcopyWriter.class); 851}