Home » openjdk-7 » javax » swing » [javadoc | source]

    1   /*
    2    * Copyright (c) 2003, 2008, 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 javax.swing;
   27   
   28   import javax.swing.table;
   29   import java.awt;
   30   import java.awt.print;
   31   import java.awt.geom;
   32   import java.text.MessageFormat;
   33   
   34   /**
   35    * An implementation of <code>Printable</code> for printing
   36    * <code>JTable</code>s.
   37    * <p>
   38    * This implementation spreads table rows naturally in sequence
   39    * across multiple pages, fitting as many rows as possible per page.
   40    * The distribution of columns, on the other hand, is controlled by a
   41    * printing mode parameter passed to the constructor. When
   42    * <code>JTable.PrintMode.NORMAL</code> is used, the implementation
   43    * handles columns in a similar manner to how it handles rows, spreading them
   44    * across multiple pages (in an order consistent with the table's
   45    * <code>ComponentOrientation</code>).
   46    * When <code>JTable.PrintMode.FIT_WIDTH</code> is given, the implementation
   47    * scales the output smaller if necessary, to ensure that all columns fit on
   48    * the page. (Note that width and height are scaled equally, ensuring that the
   49    * aspect ratio remains the same).
   50    * <p>
   51    * The portion of table printed on each page is headed by the
   52    * appropriate section of the table's <code>JTableHeader</code>.
   53    * <p>
   54    * Header and footer text can be added to the output by providing
   55    * <code>MessageFormat</code> instances to the constructor. The
   56    * printing code requests Strings from the formats by calling
   57    * their <code>format</code> method with a single parameter:
   58    * an <code>Object</code> array containing a single element of type
   59    * <code>Integer</code>, representing the current page number.
   60    * <p>
   61    * There are certain circumstances where this <code>Printable</code>
   62    * cannot fit items appropriately, resulting in clipped output.
   63    * These are:
   64    * <ul>
   65    *   <li>In any mode, when the header or footer text is too wide to
   66    *       fit completely in the printable area. The implementation
   67    *       prints as much of the text as possible starting from the beginning,
   68    *       as determined by the table's <code>ComponentOrientation</code>.
   69    *   <li>In any mode, when a row is too tall to fit in the
   70    *       printable area. The upper most portion of the row
   71    *       is printed and no lower border is shown.
   72    *   <li>In <code>JTable.PrintMode.NORMAL</code> when a column
   73    *       is too wide to fit in the printable area. The center of the
   74    *       column is printed and no left and right borders are shown.
   75    * </ul>
   76    * <p>
   77    * It is entirely valid for a developer to wrap this <code>Printable</code>
   78    * inside another in order to create complex reports and documents. They may
   79    * even request that different pages be rendered into different sized
   80    * printable areas. The implementation was designed to handle this by
   81    * performing most of its calculations on the fly. However, providing different
   82    * sizes works best when <code>JTable.PrintMode.FIT_WIDTH</code> is used, or
   83    * when only the printable width is changed between pages. This is because when
   84    * it is printing a set of rows in <code>JTable.PrintMode.NORMAL</code> and the
   85    * implementation determines a need to distribute columns across pages,
   86    * it assumes that all of those rows will fit on each subsequent page needed
   87    * to fit the columns.
   88    * <p>
   89    * It is the responsibility of the developer to ensure that the table is not
   90    * modified in any way after this <code>Printable</code> is created (invalid
   91    * modifications include changes in: size, renderers, or underlying data).
   92    * The behavior of this <code>Printable</code> is undefined if the table is
   93    * changed at any time after creation.
   94    *
   95    * @author  Shannon Hickey
   96    */
   97   class TablePrintable implements Printable {
   98   
   99       /** The table to print. */
  100       private JTable table;
  101   
  102       /** For quick reference to the table's header. */
  103       private JTableHeader header;
  104   
  105       /** For quick reference to the table's column model. */
  106       private TableColumnModel colModel;
  107   
  108       /** To save multiple calculations of total column width. */
  109       private int totalColWidth;
  110   
  111       /** The printing mode of this printable. */
  112       private JTable.PrintMode printMode;
  113   
  114       /** Provides the header text for the table. */
  115       private MessageFormat headerFormat;
  116   
  117       /** Provides the footer text for the table. */
  118       private MessageFormat footerFormat;
  119   
  120       /** The most recent page index asked to print. */
  121       private int last = -1;
  122   
  123       /** The next row to print. */
  124       private int row = 0;
  125   
  126       /** The next column to print. */
  127       private int col = 0;
  128   
  129       /** Used to store an area of the table to be printed. */
  130       private final Rectangle clip = new Rectangle(0, 0, 0, 0);
  131   
  132       /** Used to store an area of the table's header to be printed. */
  133       private final Rectangle hclip = new Rectangle(0, 0, 0, 0);
  134   
  135       /** Saves the creation of multiple rectangles. */
  136       private final Rectangle tempRect = new Rectangle(0, 0, 0, 0);
  137   
  138       /** Vertical space to leave between table and header/footer text. */
  139       private static final int H_F_SPACE = 8;
  140   
  141       /** Font size for the header text. */
  142       private static final float HEADER_FONT_SIZE = 18.0f;
  143   
  144       /** Font size for the footer text. */
  145       private static final float FOOTER_FONT_SIZE = 12.0f;
  146   
  147       /** The font to use in rendering header text. */
  148       private Font headerFont;
  149   
  150       /** The font to use in rendering footer text. */
  151       private Font footerFont;
  152   
  153       /**
  154        * Create a new <code>TablePrintable</code> for the given
  155        * <code>JTable</code>. Header and footer text can be specified using the
  156        * two <code>MessageFormat</code> parameters. When called upon to provide
  157        * a String, each format is given the current page number.
  158        *
  159        * @param  table         the table to print
  160        * @param  printMode     the printing mode for this printable
  161        * @param  headerFormat  a <code>MessageFormat</code> specifying the text to
  162        *                       be used in printing a header, or null for none
  163        * @param  footerFormat  a <code>MessageFormat</code> specifying the text to
  164        *                       be used in printing a footer, or null for none
  165        * @throws IllegalArgumentException if passed an invalid print mode
  166        */
  167       public TablePrintable(JTable table,
  168                             JTable.PrintMode printMode,
  169                             MessageFormat headerFormat,
  170                             MessageFormat footerFormat) {
  171   
  172           this.table = table;
  173   
  174           header = table.getTableHeader();
  175           colModel = table.getColumnModel();
  176           totalColWidth = colModel.getTotalColumnWidth();
  177   
  178           if (header != null) {
  179               // the header clip height can be set once since it's unchanging
  180               hclip.height = header.getHeight();
  181           }
  182   
  183           this.printMode = printMode;
  184   
  185           this.headerFormat = headerFormat;
  186           this.footerFormat = footerFormat;
  187   
  188           // derive the header and footer font from the table's font
  189           headerFont = table.getFont().deriveFont(Font.BOLD,
  190                                                   HEADER_FONT_SIZE);
  191           footerFont = table.getFont().deriveFont(Font.PLAIN,
  192                                                   FOOTER_FONT_SIZE);
  193       }
  194   
  195       /**
  196        * Prints the specified page of the table into the given {@link Graphics}
  197        * context, in the specified format.
  198        *
  199        * @param   graphics    the context into which the page is drawn
  200        * @param   pageFormat  the size and orientation of the page being drawn
  201        * @param   pageIndex   the zero based index of the page to be drawn
  202        * @return  PAGE_EXISTS if the page is rendered successfully, or
  203        *          NO_SUCH_PAGE if a non-existent page index is specified
  204        * @throws  PrinterException if an error causes printing to be aborted
  205        */
  206       public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
  207                                                          throws PrinterException {
  208   
  209           // for easy access to these values
  210           final int imgWidth = (int)pageFormat.getImageableWidth();
  211           final int imgHeight = (int)pageFormat.getImageableHeight();
  212   
  213           if (imgWidth <= 0) {
  214               throw new PrinterException("Width of printable area is too small.");
  215           }
  216   
  217           // to pass the page number when formatting the header and footer text
  218           Object[] pageNumber = new Object[]{Integer.valueOf(pageIndex + 1)};
  219   
  220           // fetch the formatted header text, if any
  221           String headerText = null;
  222           if (headerFormat != null) {
  223               headerText = headerFormat.format(pageNumber);
  224           }
  225   
  226           // fetch the formatted footer text, if any
  227           String footerText = null;
  228           if (footerFormat != null) {
  229               footerText = footerFormat.format(pageNumber);
  230           }
  231   
  232           // to store the bounds of the header and footer text
  233           Rectangle2D hRect = null;
  234           Rectangle2D fRect = null;
  235   
  236           // the amount of vertical space needed for the header and footer text
  237           int headerTextSpace = 0;
  238           int footerTextSpace = 0;
  239   
  240           // the amount of vertical space available for printing the table
  241           int availableSpace = imgHeight;
  242   
  243           // if there's header text, find out how much space is needed for it
  244           // and subtract that from the available space
  245           if (headerText != null) {
  246               graphics.setFont(headerFont);
  247               hRect = graphics.getFontMetrics().getStringBounds(headerText,
  248                                                                 graphics);
  249   
  250               headerTextSpace = (int)Math.ceil(hRect.getHeight());
  251               availableSpace -= headerTextSpace + H_F_SPACE;
  252           }
  253   
  254           // if there's footer text, find out how much space is needed for it
  255           // and subtract that from the available space
  256           if (footerText != null) {
  257               graphics.setFont(footerFont);
  258               fRect = graphics.getFontMetrics().getStringBounds(footerText,
  259                                                                 graphics);
  260   
  261               footerTextSpace = (int)Math.ceil(fRect.getHeight());
  262               availableSpace -= footerTextSpace + H_F_SPACE;
  263           }
  264   
  265           if (availableSpace <= 0) {
  266               throw new PrinterException("Height of printable area is too small.");
  267           }
  268   
  269           // depending on the print mode, we may need a scale factor to
  270           // fit the table's entire width on the page
  271           double sf = 1.0D;
  272           if (printMode == JTable.PrintMode.FIT_WIDTH &&
  273                   totalColWidth > imgWidth) {
  274   
  275               // if not, we would have thrown an acception previously
  276               assert imgWidth > 0;
  277   
  278               // it must be, according to the if-condition, since imgWidth > 0
  279               assert totalColWidth > 1;
  280   
  281               sf = (double)imgWidth / (double)totalColWidth;
  282           }
  283   
  284           // dictated by the previous two assertions
  285           assert sf > 0;
  286   
  287           // This is in a loop for two reasons:
  288           // First, it allows us to catch up in case we're called starting
  289           // with a non-zero pageIndex. Second, we know that we can be called
  290           // for the same page multiple times. The condition of this while
  291           // loop acts as a check, ensuring that we don't attempt to do the
  292           // calculations again when we are called subsequent times for the
  293           // same page.
  294           while (last < pageIndex) {
  295               // if we are finished all columns in all rows
  296               if (row >= table.getRowCount() && col == 0) {
  297                   return NO_SUCH_PAGE;
  298               }
  299   
  300               // rather than multiplying every row and column by the scale factor
  301               // in findNextClip, just pass a width and height that have already
  302               // been divided by it
  303               int scaledWidth = (int)(imgWidth / sf);
  304               int scaledHeight = (int)((availableSpace - hclip.height) / sf);
  305   
  306               // calculate the area of the table to be printed for this page
  307               findNextClip(scaledWidth, scaledHeight);
  308   
  309               last++;
  310           }
  311   
  312           // create a copy of the graphics so we don't affect the one given to us
  313           Graphics2D g2d = (Graphics2D)graphics.create();
  314   
  315           // translate into the co-ordinate system of the pageFormat
  316           g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
  317   
  318           // to save and store the transform
  319           AffineTransform oldTrans;
  320   
  321           // if there's footer text, print it at the bottom of the imageable area
  322           if (footerText != null) {
  323               oldTrans = g2d.getTransform();
  324   
  325               g2d.translate(0, imgHeight - footerTextSpace);
  326   
  327               printText(g2d, footerText, fRect, footerFont, imgWidth);
  328   
  329               g2d.setTransform(oldTrans);
  330           }
  331   
  332           // if there's header text, print it at the top of the imageable area
  333           // and then translate downwards
  334           if (headerText != null) {
  335               printText(g2d, headerText, hRect, headerFont, imgWidth);
  336   
  337               g2d.translate(0, headerTextSpace + H_F_SPACE);
  338           }
  339   
  340           // constrain the table output to the available space
  341           tempRect.x = 0;
  342           tempRect.y = 0;
  343           tempRect.width = imgWidth;
  344           tempRect.height = availableSpace;
  345           g2d.clip(tempRect);
  346   
  347           // if we have a scale factor, scale the graphics object to fit
  348           // the entire width
  349           if (sf != 1.0D) {
  350               g2d.scale(sf, sf);
  351   
  352           // otherwise, ensure that the current portion of the table is
  353           // centered horizontally
  354           } else {
  355               int diff = (imgWidth - clip.width) / 2;
  356               g2d.translate(diff, 0);
  357           }
  358   
  359           // store the old transform and clip for later restoration
  360           oldTrans = g2d.getTransform();
  361           Shape oldClip = g2d.getClip();
  362   
  363           // if there's a table header, print the current section and
  364           // then translate downwards
  365           if (header != null) {
  366               hclip.x = clip.x;
  367               hclip.width = clip.width;
  368   
  369               g2d.translate(-hclip.x, 0);
  370               g2d.clip(hclip);
  371               header.print(g2d);
  372   
  373               // restore the original transform and clip
  374               g2d.setTransform(oldTrans);
  375               g2d.setClip(oldClip);
  376   
  377               // translate downwards
  378               g2d.translate(0, hclip.height);
  379           }
  380   
  381           // print the current section of the table
  382           g2d.translate(-clip.x, -clip.y);
  383           g2d.clip(clip);
  384           table.print(g2d);
  385   
  386           // restore the original transform and clip
  387           g2d.setTransform(oldTrans);
  388           g2d.setClip(oldClip);
  389   
  390           // draw a box around the table
  391           g2d.setColor(Color.BLACK);
  392           g2d.drawRect(0, 0, clip.width, hclip.height + clip.height);
  393   
  394           // dispose the graphics copy
  395           g2d.dispose();
  396   
  397           return PAGE_EXISTS;
  398       }
  399   
  400       /**
  401        * A helper method that encapsulates common code for rendering the
  402        * header and footer text.
  403        *
  404        * @param  g2d       the graphics to draw into
  405        * @param  text      the text to draw, non null
  406        * @param  rect      the bounding rectangle for this text,
  407        *                   as calculated at the given font, non null
  408        * @param  font      the font to draw the text in, non null
  409        * @param  imgWidth  the width of the area to draw into
  410        */
  411       private void printText(Graphics2D g2d,
  412                              String text,
  413                              Rectangle2D rect,
  414                              Font font,
  415                              int imgWidth) {
  416   
  417               int tx;
  418   
  419               // if the text is small enough to fit, center it
  420               if (rect.getWidth() < imgWidth) {
  421                   tx = (int)((imgWidth - rect.getWidth()) / 2);
  422   
  423               // otherwise, if the table is LTR, ensure the left side of
  424               // the text shows; the right can be clipped
  425               } else if (table.getComponentOrientation().isLeftToRight()) {
  426                   tx = 0;
  427   
  428               // otherwise, ensure the right side of the text shows
  429               } else {
  430                   tx = -(int)(Math.ceil(rect.getWidth()) - imgWidth);
  431               }
  432   
  433               int ty = (int)Math.ceil(Math.abs(rect.getY()));
  434               g2d.setColor(Color.BLACK);
  435               g2d.setFont(font);
  436               g2d.drawString(text, tx, ty);
  437       }
  438   
  439       /**
  440        * Calculate the area of the table to be printed for
  441        * the next page. This should only be called if there
  442        * are rows and columns left to print.
  443        *
  444        * To avoid an infinite loop in printing, this will
  445        * always put at least one cell on each page.
  446        *
  447        * @param  pw  the width of the area to print in
  448        * @param  ph  the height of the area to print in
  449        */
  450       private void findNextClip(int pw, int ph) {
  451           final boolean ltr = table.getComponentOrientation().isLeftToRight();
  452   
  453           // if we're ready to start a new set of rows
  454           if (col == 0) {
  455               if (ltr) {
  456                   // adjust clip to the left of the first column
  457                   clip.x = 0;
  458               } else {
  459                   // adjust clip to the right of the first column
  460                   clip.x = totalColWidth;
  461               }
  462   
  463               // adjust clip to the top of the next set of rows
  464               clip.y += clip.height;
  465   
  466               // adjust clip width and height to be zero
  467               clip.width = 0;
  468               clip.height = 0;
  469   
  470               // fit as many rows as possible, and at least one
  471               int rowCount = table.getRowCount();
  472               int rowHeight = table.getRowHeight(row);
  473               do {
  474                   clip.height += rowHeight;
  475   
  476                   if (++row >= rowCount) {
  477                       break;
  478                   }
  479   
  480                   rowHeight = table.getRowHeight(row);
  481               } while (clip.height + rowHeight <= ph);
  482           }
  483   
  484           // we can short-circuit for JTable.PrintMode.FIT_WIDTH since
  485           // we'll always fit all columns on the page
  486           if (printMode == JTable.PrintMode.FIT_WIDTH) {
  487               clip.x = 0;
  488               clip.width = totalColWidth;
  489               return;
  490           }
  491   
  492           if (ltr) {
  493               // adjust clip to the left of the next set of columns
  494               clip.x += clip.width;
  495           }
  496   
  497           // adjust clip width to be zero
  498           clip.width = 0;
  499   
  500           // fit as many columns as possible, and at least one
  501           int colCount = table.getColumnCount();
  502           int colWidth = colModel.getColumn(col).getWidth();
  503           do {
  504               clip.width += colWidth;
  505               if (!ltr) {
  506                   clip.x -= colWidth;
  507               }
  508   
  509               if (++col >= colCount) {
  510                   // reset col to 0 to indicate we're finished all columns
  511                   col = 0;
  512   
  513                   break;
  514               }
  515   
  516               colWidth = colModel.getColumn(col).getWidth();
  517           } while (clip.width + colWidth <= pw);
  518   
  519       }
  520   
  521   }

Home » openjdk-7 » javax » swing » [javadoc | source]