PrintPreviewDemo.java

/*
 * PrintPreviewDemo.java
 *
 * Copyright (C) 2003-05 Side of Software (SOS)
 * All rights reserved.
 *
 *    http://www.sideofsoftware.com
 *    info@sideofsoftware.com
 */

package examples;

import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import java.beans.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import sos.preview.*;

/**
 * An application that demonstrates the use and customization of
 * Side of Software's Print Preview Library.<p>
 *
 * @author Side of Software
 */
public class PrintPreviewDemo
{
  /**
   * The main window.
   */
  private JFrame frame;
  
  /**
   * The print preview pane.
   */
  private JPrintPreviewPane printPreviewPane;
  
  /**
   * The pages to be previewed.
   */
  private SampleBook sampleBook;
  
  /**
   * The current print preview page background color.
   */
  private Color pageBackground = Color.white;
  
  /** 
   * Creates a new instance of <code>PrintPreviewDemo</code>.
   */
  public PrintPreviewDemo()
  {
  }
  
  /**
   * Allows the user to choose a color from the color chooser dialog.<p>
   *
   * @param parent the dialog's parent
   * @param title the dialog's title
   * @param initialColor the initial color displayed in the dialog
   * @return the new color
   */  
  private Color chooseColor( Component parent, String title, Color initialColor )
  {
    return JColorChooser.showDialog( parent, title, initialColor );
  }
  
  /**
   * Creates and returns a copy of field <code>sampleBook</code>.
   * A call to this method is typically necessary before invoking 
   * <code>setPageable</code> in <code>JPrintPreviewPane</code> 
   * because <code>JPrintPreviewPane</code> tests if the
   * new <code>Pageable</code> is different from the existing one. A
   * mutated <code>sampleBook</code> would not look different and would
   * thus be ignored.<p>
   *
   * @return a copy of the pages in this demo's <code>sampleBook</code>
   */  
  private Pageable copyBook()
  {
    // create a new Book
    Book book = new Book();
    
    // copy the pages of sampleBook into book
    int numPages = sampleBook.getNumberOfPages();
    for( int i = 0; i < numPages; i++ )
    {
      Printable printable = sampleBook.getPrintable( i );
      PageFormat pageFormat = sampleBook.getPageFormat( i );
      book.append( printable, pageFormat );
    }
    
    return book;
  }

  /**
   * Creates and returns the panel that includes components to
   * change the color properties.<p>
   *
   * @return the "Colors" tab
   */
  private JPanel createColorTab()
  {
    JPanel colorPanel = new JPanel();
    colorPanel.setLayout( new FlowLayout( FlowLayout.LEADING ));
    
    // a combobox will allow the user to choose the color attribute
    final JComboBox attributeComboBox = new JComboBox();
    
    attributeComboBox.addItem( "Background" );
    attributeComboBox.addItem( "Foreground" );
    attributeComboBox.addItem( "Selection Foreground" );
    attributeComboBox.addItem( "Page Background" );
    attributeComboBox.addItem( "Page Shadow" );
    attributeComboBox.addItem( "Margins" );
    
    attributeComboBox.setToolTipText( "<html>Choose an attribute</html>" );
    colorPanel.add( attributeComboBox );

    // a button will allow the user to set the color of the selected attribute
    JButton editColorButton = new JButton( new AbstractAction( "Edit..." ) {
      public void actionPerformed( ActionEvent event )
      {
        Object attribute = attributeComboBox.getSelectedItem();
        
        if( "Background".equals( attribute ))
        {
          // change the background color with setBackground
          Color background = printPreviewPane.getBackground();
          background = chooseColor( frame, "Pane Background Color", background );
          if( background != null )
            printPreviewPane.setBackground( background );
        }
        else if( "Foreground".equals( attribute ))
        {
          // change the foreground color with setForeground
          Color foreground = printPreviewPane.getForeground();
          foreground = chooseColor( frame, "Pane Foreground Color", foreground );
          if( foreground != null )
            printPreviewPane.setForeground( foreground );
        }
        else if( "Selection Foreground".equals( attribute ))
        {
          // change the selection foreground color with setSelectionForeground
          Color foreground = printPreviewPane.getSelectionForeground();
          foreground = chooseColor( frame, "Pane Selection Foreground Color", foreground );
          if( foreground != null )
            printPreviewPane.setSelectionForeground( foreground );
        }
        else if( "Page Background".equals( attribute ))
        {
          // change the page background color by invoking setBackground on
          // each page compoment. We must reset the page backgrounds whenever
          // new pages are added or removed
          Color background = chooseColor( frame, "Page Background Color", pageBackground );
          if( background != null )
          {
            pageBackground = background;
            installPageColor();
          }
        }
        else if( "Page Shadow".equals( attribute ))
        {
          // change the page shadow color by setting the LAF default and updating the LAF
          Color shadow = UIManager.getColor( BasicPrintPreviewPageUI.SHADOW_COLOR_KEY );
          shadow = chooseColor( frame, "Page Shadow Color", shadow );
          if( shadow != null )
          {
            UIManager.put( BasicPrintPreviewPageUI.SHADOW_COLOR_KEY, shadow );
            printPreviewPane.updateUI();
          }
        }
        else if( "Margins".equals( attribute ))
        {
          // change the margin color by setting the LAF default and updating the LAF
          Color margin = UIManager.getColor( BasicPrintPreviewPageUI.MARGIN_COLOR_KEY );
          margin = chooseColor( frame, "Margin Color", margin );
          if( margin != null )
          {
            UIManager.put( BasicPrintPreviewPageUI.MARGIN_COLOR_KEY, margin );
            printPreviewPane.updateUI();
          }
        }
      }
    } );
    editColorButton.setToolTipText( "<html>Change the color</html>" );
    colorPanel.add( editColorButton );

    return colorPanel;
  }
  
  /**
   * Creates and returns the panel that includes components to
   * change the page numbering properties.<p>
   *
   * @return the "Page Numbers" tab
   */
  private JPanel createPageNumberTab()
  {
    JPanel pageNumberPanel = new JPanel();
    pageNumberPanel.setLayout( new FlowLayout( FlowLayout.LEADING ));
    
    // a checkbox will toggle between showing the page numbers and not showing them
    final JCheckBox showPageNumbers = new JCheckBox();
    
    // by default, page numbers are shown, so select the checkbox
    showPageNumbers.setSelected( true );
    
    showPageNumbers.setAction( new AbstractAction( "Show page numbers" ) {
      public void actionPerformed( ActionEvent event )
      {
        printPreviewPane.setPageNumbersAreShown( showPageNumbers.isSelected() );
      }      
    } );
    showPageNumbers.setToolTipText( "<html>Toggle page number rendering</html>" );
    pageNumberPanel.add( showPageNumbers );
    
    // a combobox will allow the user to change the look of the page numbers
    final JComboBox rendererComboBox = new JComboBox();
    
    // install four custom renderers--the first one being active
    PageNumberRenderer initialRenderer = new CustomPageNumberRenderer1();
    printPreviewPane.setPageNumberRenderer( initialRenderer );
    rendererComboBox.addItem( initialRenderer );
    rendererComboBox.addItem( new CustomPageNumberRenderer2() );
    rendererComboBox.addItem( new CustomPageNumberRenderer3() );
    rendererComboBox.addItem( new CustomPageNumberRenderer4() );
    
    rendererComboBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        PageNumberRenderer pageNumberRenderer = (PageNumberRenderer)rendererComboBox.getSelectedItem();
        printPreviewPane.setPageNumberRenderer( pageNumberRenderer );
      }
    } );
    rendererComboBox.setToolTipText( "<html>Change the page number renderer</html>" );
    pageNumberPanel.add( rendererComboBox );

    return pageNumberPanel;
  }
  
  /**
   * Creates and returns the panel that includes components to
   * change the page properties.<p>
   *
   * @return the "Pages" tab
   */
  private JPanel createPageTab()
  {
    JPanel pagePanel = new JPanel();
    pagePanel.setLayout( new FlowLayout( FlowLayout.LEADING ));
    
    // a button will allow the user to add another page (either
    // portrait or landscape) to the book
    JButton addButton = new JButton( new AbstractAction( "Add Page..." ) {
      public void actionPerformed( ActionEvent event )
      {
        PageFormat pageFormat = new PageFormat();

        // show a dialog asking the user to choose either Portrait or Landscape
        Object[] options = { "Portrait", "Landscape" };
        int response = JOptionPane.showOptionDialog(frame, "Choose a page orientation for the new page.", "Add Page", 
          JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE,
          null, options, options[0]);
        
        if( response == 0 )
          pageFormat.setOrientation( PageFormat.PORTRAIT );
        else
          pageFormat.setOrientation( PageFormat.LANDSCAPE );          

        // add the next page of the book
        sampleBook.addPage( pageFormat );
        
        // hand a copy of the book to the print preview pane
        Pageable book = copyBook();
        printPreviewPane.setPageable( book );

        // this must be invoked after the page has recalculated its
        // layout, so invoke it later
        SwingUtilities.invokeLater( new Runnable() {
          public void run()
          {
            printPreviewPane.centerPage( sampleBook.getNumberOfPages() - 1 );
          }
        } );
      }
    } );
    addButton.setToolTipText( "<html>Append a new page with the desired orientation</html>" );
    pagePanel.add( addButton );

    // a button will allow the user to remove selected pages from the book
    JButton removeButton = new JButton( new AbstractAction( "Remove Page(s)..." ) {
      public void actionPerformed( ActionEvent event )
      {
        // ask the user to confirm deletion
        int response = JOptionPane.showConfirmDialog( frame, "Are you sure you want to delete the selected page(s)?" );
        if( response != JOptionPane.YES_OPTION )
          return;
        
        // remove the selected pages
        int numPages = sampleBook.getNumberOfPages();
        for( int i = numPages - 1; i >= 0; i-- )
        {
          if( printPreviewPane.isPageSelected( i ))
            sampleBook.removePage( i );
        }
        
        // hand a copy of the book to the print preview pane
        Pageable book = copyBook();
        printPreviewPane.setPageable( book );
      }
    } );
    removeButton.setToolTipText( "<html>Remove the selected pages</html>" );
    pagePanel.add( removeButton );

    // a checkbox will toggle between showing page margins and not showing them
    final JCheckBox showMargins = new JCheckBox();
    showMargins.setAction( new AbstractAction( "Show margins" ) {
      public void actionPerformed( ActionEvent event )
      {
        printPreviewPane.setMarginsAreShown( showMargins.isSelected() );
      }      
    } );
    showMargins.setToolTipText( "<html>Toggle page margin rendering</html>" );
    pagePanel.add( showMargins );

    // a spinner will allow the user to set the border thickness, which
    // is a LAF property
    JLabel borderThicknessLabel = new JLabel( "Page Border: " );
    pagePanel.add( borderThicknessLabel );
   
    final JSpinner borderThicknessSpinner = new JSpinner();
    borderThicknessSpinner.setPreferredSize( new Dimension( 40, 24 ));
    
    // initialize the spinner with the current LAF value
    borderThicknessSpinner.setValue( UIManager.get( BasicPrintPreviewPageUI.BORDER_THICKNESS_KEY ));
    
    borderThicknessSpinner.addChangeListener( new ChangeListener() {
      public void stateChanged( ChangeEvent event )
      {
        // change the border thickness by setting the LAF property and updating the LAF
        Integer value = (Integer)borderThicknessSpinner.getValue();
        UIManager.put( BasicPrintPreviewPageUI.BORDER_THICKNESS_KEY, value );
        printPreviewPane.updateUI();
      }
    } );
    borderThicknessSpinner.setToolTipText( "<html>Adjust the size of the page border</html>" );
    pagePanel.add( borderThicknessSpinner );
  
    // a spinner will allow the user to set the shadow thickness (depth), which
    // is a LAF property
    JLabel shadowThicknessLabel = new JLabel( "Shadow Thickness:" );
    pagePanel.add( shadowThicknessLabel );
    
    final JSpinner shadowThicknessSpinner = new JSpinner();
    shadowThicknessSpinner.setPreferredSize( new Dimension( 40, 24 ));
    
    // initialize the spinner with the current LAF value
    shadowThicknessSpinner.setValue( UIManager.get( BasicPrintPreviewPageUI.SHADOW_THICKNESS_KEY ));
    
    shadowThicknessSpinner.addChangeListener( new ChangeListener() {
      public void stateChanged( ChangeEvent event )
      {
        // change the border thickness by setting the LAF property and updating the LAF
        Integer value = (Integer)shadowThicknessSpinner.getValue();
        UIManager.put( BasicPrintPreviewPageUI.SHADOW_THICKNESS_KEY, value );
        printPreviewPane.updateUI();
      }
    } );
    shadowThicknessSpinner.setToolTipText( "<html>Adjust the depth of the page shadow</html>" );
    pagePanel.add( shadowThicknessSpinner );

    return pagePanel;
  }

  /**
   * Creates and returns the panel that includes components to
   * change the print preview pane's selection properties.<p>
   *
   * @return the "Selection" tab
   */
  private JPanel createSelectionTab()
  {
    JPanel selectionPanel = new JPanel();
    selectionPanel.setLayout( new FlowLayout( FlowLayout.LEADING ));
    
    // a checkbox will toggle between allowing page selection and not allowing it
    final JCheckBox selectionAllowedCheckBox = new JCheckBox();
    selectionAllowedCheckBox.setSelected( true );
    selectionAllowedCheckBox.setAction( new AbstractAction( "Allow Page Selection" ) {
      public void actionPerformed( ActionEvent event )
      {
        printPreviewPane.setPageSelectionAllowed( selectionAllowedCheckBox.isSelected() );
      }      
    } );
    selectionAllowedCheckBox.setToolTipText( "<html>Toggle page selection property</html>" );
    selectionPanel.add( selectionAllowedCheckBox );

    // a combobox will allow the user to change the page selection mode
    final JComboBox selectionModelComboBox = new JComboBox();
    selectionModelComboBox.addItem( "Single" );
    selectionModelComboBox.addItem( "Single Interval" );
    selectionModelComboBox.addItem( "Multiple Interval" );
    selectionModelComboBox.setSelectedItem( "Multiple Interval" );
    selectionModelComboBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        ListSelectionModel model = printPreviewPane.getPageSelectionModel();
        
        String selectionType = (String)selectionModelComboBox.getSelectedItem();
          
        if( "Single".equals( selectionType ))
          model.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
        else if( "Single Interval".equals( selectionType ))
          model.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION );
        else
          model.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
      }
    } );
    selectionModelComboBox.setToolTipText( "<html>Change the page selection model</html>" );
    selectionPanel.add( selectionModelComboBox );

    return selectionPanel;
  }
  
  /**
   * Creates and returns the panel that includes components to
   * change the print preview pane's page arrangement.<p>
   *
   * @return the "Zoom" tab
   */
  private JPanel createZoomTab( PrintPreviewSupport support )
  {
    JPanel zoomingPanel = new JPanel();
    zoomingPanel.setLayout( new FlowLayout( FlowLayout.LEADING ));

    // Have four toggle buttons that toggle among JPrintPreviewPane's
    // four modes
    //
    // -- Selection Mode
    JToggleButton selectionButton = new JToggleButton( new AbstractAction( "Selection Mode" ) {
      public void actionPerformed( ActionEvent event )
      {
        printPreviewPane.setMode( JPrintPreviewPane.SELECTION_MODE );
      }
    } );
    selectionButton.setSelected( true );
    selectionButton.setToolTipText( "<html>Switch to selection mode<blockquote><font size=2>In selection mode, you can highlight pages by left-clicking them.</font></blockquote></html>" );
    zoomingPanel.add( selectionButton );
    
    //
    // -- Zoom In Mode
    JToggleButton zoomInButton = new JToggleButton( new AbstractAction( "Zoom-In Mode" ) {
      public void actionPerformed( ActionEvent event )
      {
        printPreviewPane.setMode( JPrintPreviewPane.ZOOM_IN_MODE );
      }
    } );
    zoomInButton.setToolTipText( "<html>Switch to zoom-in mode<blockquote><font size=2>In zoom-in mode, you can enlarge pages by left-clicking them.</font></blockquote></html>" );
    zoomingPanel.add( zoomInButton );
    
    //
    // -- Zoom Out Mode
    JToggleButton zoomOutButton = new JToggleButton( new AbstractAction( "Zoom-Out Mode" ) {
      public void actionPerformed( ActionEvent event )
      {
        printPreviewPane.setMode( JPrintPreviewPane.ZOOM_OUT_MODE );
      }
    } );
    zoomOutButton.setToolTipText( "<html>Switch to zoom-out mode<blockquote><font size=2>In zoom-out mode, you can shrink pages by left-clicking them.</font></blockquote></html>" );
    zoomingPanel.add( zoomOutButton );
    
    //
    // -- Zoom Mode
    JToggleButton zoomButton = new JToggleButton( new AbstractAction( "Zoom Mode" ) {
      public void actionPerformed( ActionEvent event )
      {
        printPreviewPane.setMode( JPrintPreviewPane.ZOOM_MODE );
      }
    } );
    zoomButton.setToolTipText( "<html>Switch to zoom mode<blockquote><font size=2>In zoom mode, left-clicking enlarges pages and right-clicking shrinks pages.</font></blockquote></html>" );
    zoomingPanel.add( zoomButton );
    
    ButtonGroup buttonGroup = new ButtonGroup();
    buttonGroup.add( selectionButton );
    buttonGroup.add( zoomInButton );
    buttonGroup.add( zoomOutButton );
    buttonGroup.add( zoomButton );
    
    // include the default zoom combo box, and let's make it editable and let's
    // have it include special layouts like "Fit All" and "Two Pages"
    JComboBox zoomComboBox = support.getZoomComboBox( true, true );
    zoomingPanel.add( zoomComboBox );
    
    return zoomingPanel;
  }
  
  /**
   * Sets the background color of the print preview pages. This
   * method should be called everytime the pages in the
   * print preview pane have changed.<p>
   */  
  private void installPageColor()
  {
    // for each page
    Pageable pageable = printPreviewPane.getPageable();
    int numPages = pageable.getNumberOfPages();
    for( int i = 0; i < numPages; i++ )
    {
      // fetch the page component
      Component page = printPreviewPane.getPageComponent( i );
      
      // set its background
      page.setBackground( pageBackground );
    }
  }
  
  /**
   * Runs the print preview demo.<p>
   *
   * @param args ignored
   * @see #run
   */  
  public static void main( String[] args )
  {
    // invoke on the event dispatch thread
    SwingUtilities.invokeLater( new Runnable() {
      public void run()
      {
        try
        {
          PrintPreviewDemo demo = new PrintPreviewDemo();
          demo.run();
        }
        catch( IOException ioe )
        {
          ioe.printStackTrace();
        }
      }
    } );
  }
  
  /**
   * Displays a window that demonstrates the use and customization of
   * print preview.<p>
   */  
  public void run() throws IOException
  {
    // create a window to display
    frame = new JFrame( "Print Preview Demo by Side of Software" );
    frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
    
    // create a book to display in the print preview pane
    sampleBook = new SampleBook( "Earnest.txt" );
    
    // add a number of initial pages to our book using the default page format
    PageFormat pageFormat = new PageFormat();
    for( int i = 0; i < 7; i++ )
      sampleBook.addPage( pageFormat );
    Pageable book = copyBook();

    // create the print preview pane
    printPreviewPane = new JPrintPreviewPane( book );
    
    // initialize the page background color
    installPageColor();
    
    // listen to when the pages within the print preview pane have changed,
    // so we can reinstall the page background color
    printPreviewPane.addPropertyChangeListener( JPrintPreviewPane.PAGEABLE_PROPERTY, new PropertyChangeListener() {
      public void propertyChange( PropertyChangeEvent event )
      {
        installPageColor();
      }
    } );
    
    // place the print preview pane in a scroll pane
    JScrollPane scrollPane = new JScrollPane( printPreviewPane );

    // create support for building a print preview toolbar
    // because we want to use the built-in zoom combo box
    PrintPreviewSupport support = new PrintPreviewSupport( printPreviewPane, false );
    
    // create our custom toolbar
    JToolBar toolbar = new JToolBar();
    
    // our toolbar will have so many options that, for space reasons, we'll group
    // related options on tabs
    JTabbedPane tabbedPane = new JTabbedPane();
    
    /////////////////////
    // Tab 1: Zooming
    /////////////////////
    JPanel zoomingPanel = createZoomTab( support );
    tabbedPane.addTab( "Zooming", zoomingPanel );    

    /////////////////////
    // Tab 2: Pages
    /////////////////////
    JPanel pagePanel = createPageTab();
    tabbedPane.addTab( "Pages", pagePanel );

    /////////////////////
    // Tab 3: Selection
    /////////////////////
    JPanel selectionPanel = createSelectionTab();
    tabbedPane.addTab( "Selection", selectionPanel );

    ////////////////////////
    // Tab 4: Page numbering
    ////////////////////////
    JPanel pageNumberPanel = createPageNumberTab();
    tabbedPane.addTab( "Page Numbers", pageNumberPanel );
    
    ///////////////////////
    // Tab 5: Custom colors
    ///////////////////////
    JPanel colorPanel = createColorTab();
    tabbedPane.addTab( "Colors", colorPanel );
    
    toolbar.add( tabbedPane );
    
    frame.getContentPane().setLayout( new BorderLayout() );
    frame.getContentPane().add( scrollPane, BorderLayout.CENTER );
    frame.getContentPane().add( toolbar, BorderLayout.NORTH );
    
    frame.setSize( 800, 500 );
    
    Locale locale = Locale.getDefault();
    ComponentOrientation componentOrientation = ComponentOrientation.getOrientation( locale );
    frame.applyComponentOrientation( componentOrientation );
    
    frame.show();
  }
}

/**
 * A custom page number renderer that puts hyphens before and
 * after the page number.<p>
 */
class CustomPageNumberRenderer1 extends DefaultPageNumberRenderer
{
  protected String pageNumberToString( int pageNumber )
  {
    return "- " + pageNumber + " -";
  }
  
  public String toString()
  {
    return "- N - (centered)";
  }
}

/**
 * A custom page number renderer that puts the word "Page" before
 * the page number and that justifies the text on the right.<p>
 */
class CustomPageNumberRenderer2 extends DefaultPageNumberRenderer
{
  protected String pageNumberToString( int pageNumber )
  {
    return "Page " + pageNumber;
  }
  
  protected void prepareLabel( JLabel label, JPrintPreviewPane printPreviewPane, int pageNumber, boolean isSelected, boolean pageHasFocus )
  {
    super.prepareLabel( label, printPreviewPane, pageNumber, isSelected, pageHasFocus );
    label.setHorizontalAlignment( JLabel.TRAILING );
  }

  public String toString()
  {
    return "Page N (trailing)";
  }
}

/**
 * A custom page number renderer that places odd page numbers to
 * the right and even page numbers to the left.<p>
 */
class CustomPageNumberRenderer3 extends DefaultPageNumberRenderer
{
  protected void prepareLabel( JLabel label, JPrintPreviewPane printPreviewPane, int pageNumber, boolean isSelected, boolean pageHasFocus )
  {
    super.prepareLabel( label, printPreviewPane, pageNumber, isSelected, pageHasFocus );
    
    if( pageNumber % 2 == 1 )
      label.setHorizontalAlignment( JLabel.RIGHT );
    else
      label.setHorizontalAlignment( JLabel.LEFT );
  }

  public String toString()
  {
    return "N (opposite)";
  }
}

/**
 * A custom page number renderer that puts hyphens before and
 * after the page number.<p>
 */
class CustomPageNumberRenderer4 extends DefaultPageNumberRenderer
{
  private Icon icon;
  private Border border;
  
  CustomPageNumberRenderer4()
  {
    ClassLoader classLoader = this.getClass().getClassLoader();
    icon = new ImageIcon( classLoader.getResource( "examples/PrintPreviewLibrary17.gif" ));
    border = BorderFactory.createEtchedBorder();
  }
  
  protected String pageNumberToString( int pageNumber )
  {
    return String.valueOf( pageNumber );
  }
  
  protected void prepareLabel( JLabel label, JPrintPreviewPane printPreviewPane, int pageNumber, boolean isSelected, boolean pageHasFocus )
  {
    super.prepareLabel( label, printPreviewPane, pageNumber, isSelected, pageHasFocus );
    
    label.setIcon( icon );
    label.setHorizontalAlignment( JLabel.LEFT );
    label.setBorder( border );
  }

  public String toString()
  {
    return "N (with icon and border)";
  }
}

/**
 * An implementation of <code>Pageable</code> that reads lines of text and
 * produces pages.<p>
 */
class SampleBook implements Pageable
{
  /**
   * The font of all the text.
   */
  private static Font font = new Font( "Arial", Font.PLAIN, 13 );

  /**
   * The active reader of the text
   */
  private BufferedReader reader;
  
  /**
   * The <code>Printable</code> objects.
   */
  private List pages = new ArrayList();
  
  /**
   * The <code>PageFormat</code> objects.
   */
  private List pageFormats = new ArrayList();
  
  /**
   * Creates a new instance of <code>SampleBook</code> for the specified text resource.
   * The resource method be accessible from the current class loader.<p>
   *
   * @param resourceName name of the resource with lines of text
   * @throws IOException if an error occurs while opening a stream to the resource
   * @throws NullPointerException if <code>resourceName</code> is <code>null</code> 
   *    or if the resource is not found
   */
  public SampleBook( String resourceName ) throws IOException
  {
    ClassLoader classLoader = this.getClass().getClassLoader();
    URL fileUrl = classLoader.getResource( resourceName );
    InputStream inputStream = fileUrl.openStream();
    Reader inputStreamReader = new InputStreamReader( inputStream );
    reader = new BufferedReader( inputStreamReader );
  }

  /**
   * Reads more lines of the resource and adds a new page to this <code>Pageable</code>.
   * The new page will have the specified page format.<p>
   *
   * @param pageFormat page format of the page to add
   */  
  public void addPage( PageFormat pageFormat )
  {
    try
    {
      // add this page format to the list
      pageFormats.add( pageFormat );
      
      // create a new <code>Printable</code> and add it to the list
      PagePrintable printable = new PagePrintable();
      pages.add( printable );
      
      // add as many lines of text that will fit on the page
      int height = 0;
      int maxHeight = (int)pageFormat.getImageableHeight() - 11;

      while( height < maxHeight )
      {
        String line = reader.readLine();
        
        if( line == null )
          break;

        printable.addLine( line );

        // approximate height of each line plus a little space
        height += 15;
      }
    }
    catch( IOException ioe )
    {
      // just swallow all exceptions
      ioe.printStackTrace();
    }
  }
  
  /**
   * Removes the specified page from this <code>Pageable</code>.<p>
   *
   * @param pageIndex page to remove
   * @throws IndexOutOfBoundsException if the <code>Pageable</code> 
   *    does not contain the requested page.
   */  
  public void removePage( int pageIndex ) throws IndexOutOfBoundsException
  {
    pages.remove( pageIndex );
    pageFormats.remove( pageIndex );
  }
  
  /** 
   * Returns the number of pages.<p>
   *
   * @return the number of pages in this <code>Pageable</code>.
   */
  public int getNumberOfPages()
  {
    return pages.size();
  }
  
  /** 
   * Returns the <code>PageFormat</code> of the page specified by
   * <code>pageIndex</code>.<p>
   *
   * @param pageIndex the zero based index of the page whose
   *    <code>PageFormat</code> is being requested
   * @return the <code>PageFormat</code> describing the size and
   * 		orientation.
   * @throws IndexOutOfBoundsException if the <code>Pageable</code> 
   *    does not contain the requested page.
   */
  public PageFormat getPageFormat(int pageIndex) throws IndexOutOfBoundsException
  {
    return (PageFormat)pageFormats.get( pageIndex );
  }
  
  /** 
   * Returns the <code>Printable</code> instance responsible for
   * rendering the page specified by <code>pageIndex</code>.<p>
   *
   * @param pageIndex the zero based index of the page whose
   *    <code>Printable</code> is being requested
   * @return the <code>Printable</code> that renders the page.
   * @throws IndexOutOfBoundsException if the <code>Pageable</code> 
   *    does not contain the requested page.
   */
  public Printable getPrintable(int pageIndex) throws IndexOutOfBoundsException
  {
    return (Printable)pages.get( pageIndex );
  }

  /**
   * Reads the entire resource and adds as many pages as necessary
   * to this <code>Paintable</code>.<p>
   *
   * @param pageFormat page format for all the new pages
   * @throws IOException if an error reading the resource occurred
   */  
  public void load( PageFormat pageFormat ) throws IOException
  {
    while( reader.ready() )
      addPage( pageFormat );
  }
  
  /**
   * An implementation of Printable that renders lines of text
   * on the page.
   */
  class PagePrintable implements Printable
  {
    /**
     * Lines of text.
     */
    private List lines = new ArrayList();

    /**
     * Adds a line of text to be printed.<p>
     *
     * @param line line of text to be appended for printing
     */    
    void addLine( String line )
    {
      lines.add( line );
    }
    
    /** 
     * Prints the page at the specified index into the specified
     * Graphics context in the specified
     * format.<p>
     *
     * @param graphics the context into which the page is drawn
     * @param pageFormat the size and orientation of the page being drawn
     * @param pageIndex the zero based index of the page to be drawn
     * @return PAGE_EXISTS if the page is rendered successfully
     *    or NO_SUCH_PAGE if <code>pageIndex</code> specifies a
     * 	  non-existent page.
     * @exception java.awt.print.PrinterException
     *    thrown when the print job is terminated.
     */
    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException
    {
      Graphics2D g2 = (Graphics2D)graphics;
      
      // use black
      g2.setColor( Color.black );
      
      // use a 13-point font
      g2.setFont( font );

      // start at the left and top margins
      int x = (int)pageFormat.getImageableX();
      int y = (int)pageFormat.getImageableY() + 11;

      // for each line
      Iterator iter = lines.iterator();
      while( iter.hasNext() )
      {
        String line = (String)iter.next();
        
        // When the text gets really small, drawString (for some reason) throws an exception
        // if the line is empty, so don't call drawString with an empty line
        //
        // Also, drawString seems to do some optimizing. If the text gets really
        // small, it doesn't attempt to display it.
        if( line.length() != 0 )
          g2.drawString( line, x, y );

        // move to the next line
        y += 15;
      }
      
      return PAGE_EXISTS;
    }
  }
}