839 lines · 30.2 KB · Java
RawDownload
/* * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package sun.swing.text; import java.awt.ComponentOrientation;import java.awt.Dimension;import java.awt.Font;import java.awt.FontMetrics;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.Insets;import java.awt.Rectangle;import java.awt.Component;import java.awt.Container;import java.awt.font.FontRenderContext;import java.awt.print.PageFormat;import java.awt.print.Printable;import java.awt.print.PrinterException;import java.text.MessageFormat;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;import java.util.concurrent.atomic.AtomicReference; import javax.swing.BorderFactory;import javax.swing.CellRendererPane;import javax.swing.JTextField;import javax.swing.JTextArea;import javax.swing.JEditorPane;import javax.swing.JViewport;import javax.swing.JScrollPane;import javax.swing.JTextPane;import javax.swing.SwingUtilities;import javax.swing.border.Border;import javax.swing.border.TitledBorder;import javax.swing.text.BadLocationException;import javax.swing.text.JTextComponent;import javax.swing.text.Document;import javax.swing.text.EditorKit;import javax.swing.text.AbstractDocument;import javax.swing.text.html.HTMLDocument;import javax.swing.text.html.HTML; import sun.font.FontDesignMetrics; import sun.swing.text.html.FrameEditorPaneTag; /** * An implementation of {@code Printable} to print {@code JTextComponent} with * the header and footer. * * <h1> * WARNING: this class is to be used in * javax.swing.text.JTextComponent only. * </h1> * * <p> * The implementation creates a new {@code JTextComponent} ({@code printShell}) * to print the content using the {@code Document}, {@code EditorKit} and * rendering-affecting properties from the original {@code JTextComponent}. * * <p> * {@code printShell} is laid out on the first {@code print} invocation. * * <p> * This class can be used on any thread. Part of the implementation is executed * on the EDT though. * * @author Igor Kushnirskiy * * @since 1.6 */public class TextComponentPrintable implements CountingPrintable {      private static final int LIST_SIZE = 1000;     private boolean isLayouted = false;     /*     * The text component to print.     */    private final JTextComponent textComponentToPrint;     /*     * The FontRenderContext to layout and print with     */    private final AtomicReference<FontRenderContext> frc =        new AtomicReference<FontRenderContext>(null);     /**     * Special text component used to print to the printer.     */    private final JTextComponent printShell;     private final MessageFormat headerFormat;    private final MessageFormat footerFormat;     private static final float HEADER_FONT_SIZE = 18.0f;    private static final float FOOTER_FONT_SIZE = 12.0f;     private final Font headerFont;    private final Font footerFont;     /**     * stores metrics for the unhandled rows. The only metrics we need are     * yStart and yEnd when row is handled by updatePagesMetrics it is removed     * from the list. Thus the head of the list is the fist row to handle.     *     * sorted     */    private final List<IntegerSegment> rowsMetrics;     /**     * thread-safe list for storing pages metrics. The only metrics we need are     * yStart and yEnd.     * It has to be thread-safe since metrics are calculated on     * the printing thread and accessed on the EDT thread.     *     * sorted     */    private final List<IntegerSegment> pagesMetrics;     /**     * Returns {@code TextComponentPrintable} to print {@code textComponent}.     *     * @param textComponent {@code JTextComponent} to print     * @param headerFormat the page header, or {@code null} for none     * @param footerFormat the page footer, or {@code null} for none     * @return {@code TextComponentPrintable} to print {@code textComponent}     */    public static Printable getPrintable(final JTextComponent textComponent,            final MessageFormat headerFormat,            final MessageFormat footerFormat) {         if (textComponent instanceof JEditorPane                && isFrameSetDocument(textComponent.getDocument())) {            //for document with frames we create one printable per            //frame and merge them with the CompoundPrintable.            List<JEditorPane> frames = getFrames((JEditorPane) textComponent);            List<CountingPrintable> printables =                new ArrayList<CountingPrintable>();            for (JEditorPane frame : frames) {                printables.add((CountingPrintable)                               getPrintable(frame, headerFormat, footerFormat));            }            return new CompoundPrintable(printables);        } else {            return new TextComponentPrintable(textComponent,               headerFormat, footerFormat);        }    }     /**     * Checks whether the document has frames. Only HTMLDocument might     * have frames.     *     * @param document the {@code Document} to check     * @return {@code true} if the {@code document} has frames     */    private static boolean isFrameSetDocument(final Document document) {        boolean ret = false;        if (document instanceof HTMLDocument) {            HTMLDocument htmlDocument = (HTMLDocument)document;            if (htmlDocument.getIterator(HTML.Tag.FRAME).isValid()) {                ret = true;            }        }        return ret;    }      /**     * Returns frames under the {@code editor}.     * The frames are created if necessary.     *     * @param editor the {@JEditorPane} to find the frames for     * @return list of all frames     */    private static List<JEditorPane> getFrames(final JEditorPane editor) {        List<JEditorPane> list = new ArrayList<JEditorPane>();        getFrames(editor, list);        if (list.size() == 0) {            //the frames have not been created yet.            //let's trigger the frames creation.            createFrames(editor);            getFrames(editor, list);        }        return list;    }     /**     * Adds all {@code JEditorPanes} under {@code container} tagged by {@code     * FrameEditorPaneTag} to the {@code list}. It adds only top     * level {@code JEditorPanes}.  For instance if there is a frame     * inside the frame it will return the top frame only.     *     * @param c the container to find all frames under     * @param list {@code List} to append the results too     */    private static void getFrames(final Container container, List<JEditorPane> list) {        for (Component c : container.getComponents()) {            if (c instanceof FrameEditorPaneTag                && c instanceof JEditorPane ) { //it should be always JEditorPane                list.add((JEditorPane) c);            } else {                if (c instanceof Container) {                    getFrames((Container) c, list);                }            }        }    }     /**     * Triggers the frames creation for {@code JEditorPane}     *     * @param editor the {@code JEditorPane} to create frames for     */    private static void createFrames(final JEditorPane editor) {        Runnable doCreateFrames =            new Runnable() {                public void run() {                    final int WIDTH = 500;                    final int HEIGHT = 500;                    CellRendererPane rendererPane = new CellRendererPane();                    rendererPane.add(editor);                    //the values do not matter                    //we only need to get frames created                    rendererPane.setSize(WIDTH, HEIGHT);                };            };        if (SwingUtilities.isEventDispatchThread()) {            doCreateFrames.run();        } else {            try {                SwingUtilities.invokeAndWait(doCreateFrames);            } catch (Exception e) {                if (e instanceof RuntimeException) {                    throw (RuntimeException) e;                } else {                    throw new RuntimeException(e);                }            }        }    }     /**     * Constructs  {@code TextComponentPrintable} to print {@code JTextComponent}     * {@code textComponent} with {@code headerFormat} and {@code footerFormat}.     *     * @param textComponent {@code JTextComponent} to print     * @param headerFormat the page header or {@code null} for none     * @param footerFormat the page footer or {@code null} for none     */    private TextComponentPrintable(JTextComponent textComponent,            MessageFormat headerFormat,            MessageFormat footerFormat) {        this.textComponentToPrint = textComponent;        this.headerFormat = headerFormat;        this.footerFormat = footerFormat;        headerFont = textComponent.getFont().deriveFont(Font.BOLD,            HEADER_FONT_SIZE);        footerFont = textComponent.getFont().deriveFont(Font.PLAIN,            FOOTER_FONT_SIZE);        this.pagesMetrics =            Collections.synchronizedList(new ArrayList<IntegerSegment>());        this.rowsMetrics = new ArrayList<IntegerSegment>(LIST_SIZE);        this.printShell = createPrintShell(textComponent);    }      /**     * creates a printShell.     * It creates closest text component to {@code textComponent}     * which uses {@code frc} from the {@code TextComponentPrintable}     * for the {@code getFontMetrics} method.     *     * @param textComponent {@code JTextComponent} to create a     *        printShell for     * @return the print shell     */    private JTextComponent createPrintShell(final JTextComponent textComponent) {        if (SwingUtilities.isEventDispatchThread()) {            return createPrintShellOnEDT(textComponent);        } else {            FutureTask<JTextComponent> futureCreateShell =                new FutureTask<JTextComponent>(                    new Callable<JTextComponent>() {                        public JTextComponent call() throws Exception {                            return createPrintShellOnEDT(textComponent);                        }                    });            SwingUtilities.invokeLater(futureCreateShell);            try {                return futureCreateShell.get();            } catch (InterruptedException e) {                throw new RuntimeException(e);            } catch (ExecutionException e) {                Throwable cause = e.getCause();                if (cause instanceof Error) {                    throw (Error) cause;                }                if (cause instanceof RuntimeException) {                    throw (RuntimeException) cause;                }                throw new AssertionError(cause);            }        }    }    private JTextComponent createPrintShellOnEDT(final JTextComponent textComponent) {        assert SwingUtilities.isEventDispatchThread();         JTextComponent ret = null;        if (textComponent instanceof JTextField) {            ret =                new JTextField() {                    {                        setHorizontalAlignment(                            ((JTextField) textComponent).getHorizontalAlignment());                    }                    @Override                    public FontMetrics getFontMetrics(Font font) {                        return (frc.get() == null)                            ? super.getFontMetrics(font)                            : FontDesignMetrics.getMetrics(font, frc.get());                    }                };        } else if (textComponent instanceof JTextArea) {            ret =                new JTextArea() {                    {                        JTextArea textArea = (JTextArea) textComponent;                        setLineWrap(textArea.getLineWrap());                        setWrapStyleWord(textArea.getWrapStyleWord());                        setTabSize(textArea.getTabSize());                    }                    @Override                    public FontMetrics getFontMetrics(Font font) {                        return (frc.get() == null)                            ? super.getFontMetrics(font)                            : FontDesignMetrics.getMetrics(font, frc.get());                    }                };        } else if (textComponent instanceof JTextPane) {            ret =                new JTextPane() {                    @Override                    public FontMetrics getFontMetrics(Font font) {                        return (frc.get() == null)                            ? super.getFontMetrics(font)                            : FontDesignMetrics.getMetrics(font, frc.get());                    }                    @Override                    public EditorKit getEditorKit() {                        if (getDocument() == textComponent.getDocument()) {                            return ((JTextPane) textComponent).getEditorKit();                        } else {                            return super.getEditorKit();                        }                    }                };        } else if (textComponent instanceof JEditorPane) {            ret =                new JEditorPane() {                    @Override                    public FontMetrics getFontMetrics(Font font) {                        return (frc.get() == null)                            ? super.getFontMetrics(font)                            : FontDesignMetrics.getMetrics(font, frc.get());                    }                    @Override                    public EditorKit getEditorKit() {                        if (getDocument() == textComponent.getDocument()) {                            return ((JEditorPane) textComponent).getEditorKit();                        } else {                            return super.getEditorKit();                        }                    }                };        }        //want to occupy the whole width and height by text        ret.setBorder(null);         //set properties from the component to print        ret.setOpaque(textComponent.isOpaque());        ret.setEditable(textComponent.isEditable());        ret.setEnabled(textComponent.isEnabled());        ret.setFont(textComponent.getFont());        ret.setBackground(textComponent.getBackground());        ret.setForeground(textComponent.getForeground());        ret.setComponentOrientation(            textComponent.getComponentOrientation());         if (ret instanceof JEditorPane) {            ret.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES,                textComponent.getClientProperty(                JEditorPane.HONOR_DISPLAY_PROPERTIES));            ret.putClientProperty(JEditorPane.W3C_LENGTH_UNITS,                textComponent.getClientProperty(JEditorPane.W3C_LENGTH_UNITS));            ret.putClientProperty("charset",                textComponent.getClientProperty("charset"));        }        ret.setDocument(textComponent.getDocument());        return ret;    }        /**     * Returns the number of pages in this printable.     * <p>     * This number is defined only after {@code print} returns NO_SUCH_PAGE.     *     * @return the number of pages.     */    public int getNumberOfPages() {        return pagesMetrics.size();    }     /**     * See Printable.print for the API description.     *     * There are two parts in the implementation.     * First part (print) is to be called on the printing thread.     * Second part (printOnEDT) is to be called on the EDT only.     *     * print triggers printOnEDT     */    public int print(final Graphics graphics,            final PageFormat pf,            final int pageIndex) throws PrinterException {        if (!isLayouted) {            if (graphics instanceof Graphics2D) {                frc.set(((Graphics2D)graphics).getFontRenderContext());            }            layout((int)Math.floor(pf.getImageableWidth()));            calculateRowsMetrics();        }        int ret;        if (!SwingUtilities.isEventDispatchThread()) {            Callable<Integer> doPrintOnEDT = new Callable<Integer>() {                public Integer call() throws Exception {                    return printOnEDT(graphics, pf, pageIndex);                }            };            FutureTask<Integer> futurePrintOnEDT =                new FutureTask<Integer>(doPrintOnEDT);            SwingUtilities.invokeLater(futurePrintOnEDT);            try {                ret = futurePrintOnEDT.get();            } catch (InterruptedException e) {                throw new RuntimeException(e);            } catch (ExecutionException e) {                Throwable cause = e.getCause();                if (cause instanceof PrinterException) {                    throw (PrinterException)cause;                } else if (cause instanceof RuntimeException) {                    throw (RuntimeException) cause;                } else if (cause instanceof Error) {                    throw (Error) cause;                } else {                    throw new RuntimeException(cause);                }            }        } else {            ret = printOnEDT(graphics, pf, pageIndex);        }        return ret;    }      /**     * The EDT part of the print method.     *

Lines 1–500 of 839.