ReportDemo.java

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

package sos.examples;

import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.text.*;
import sos.preview.*;
import sos.reports.*;
import sos.reports.Element; // takes precedence over javax.swing.text.Element

/**
 * A program that demonstrates some of the capabilities of
 * Side of Software's Report Library.<p>
 *
 * @author Side of Software
 */
public class ReportDemo
{
  /** The style of the main table. */
  private Style tableStyle;

  /** The window. */
  private JFrame frame;
  
  /** The main report pane. */
  private JReportPane reportPane;
  
  /** The print preview pane. */
  private JPanel previewPane;

  /** The selection models. */
  private CompositeReportSelectionModel compositeSelectionModel;
  private TierReportSelectionModel rowSelectionModel;
  private TierReportSelectionModel columnSelectionModel;
  private CellReportSelectionModel cellSelectionModel;

  /** The content of the main report. */
  private CustomTableModel tableModel;
  
  /**
   * Changes the cell selection model for the specified tier type
   * after bringing up the selection dialog.
   */
  private void applyCellSelectionModelSettings( int tierType, String pluralTierString )
  {
    // get the current settings
    boolean headersSelectable = cellSelectionModel.isHeaderSelectable( tierType );
    boolean footersSelectable = cellSelectionModel.isFooterSelectable( tierType );
    int mode = cellSelectionModel.getSelectionMode( tierType );

    // show the selection dialog with the current settings
    Object[] result = showSelectionDetailsDialog( headersSelectable, "Cells in header " + 
      pluralTierString, footersSelectable, "Cells in footer " + pluralTierString, mode );
    
    // check if user canceled
    if( result == null )
      return;
    
    // the dialog returns its data in the returned array
    headersSelectable = ((Boolean)result[0]).booleanValue();
    footersSelectable = ((Boolean)result[1]).booleanValue();
    mode = ((Integer)result[2]).intValue();
    
    // update the cell selection model
    cellSelectionModel.setHeaderSelectable( tierType, headersSelectable );
    cellSelectionModel.setFooterSelectable( tierType, footersSelectable );
    cellSelectionModel.setSelectionMode( tierType, mode );
  }

  /**
   * Changes the specified tier selection model
   * after bringing up the selection dialog.
   */
  private void applyTierSelectionModelSettings( TierReportSelectionModel tierSelectionModel, String pluralTierString )
  {
    // get the current settings
    boolean headersSelectable = tierSelectionModel.isHeaderSelectable();
    boolean footersSelectable = tierSelectionModel.isFooterSelectable();
    int mode = tierSelectionModel.getSelectionMode();
    
    // show the selection dialog with the current settings
    Object[] result = showSelectionDetailsDialog( headersSelectable, "Header " + pluralTierString, footersSelectable, "Footer " + pluralTierString, mode );
    
    // check if user canceled
    if( result == null )
      return;
    
    // the dialog returns its data in the returned array
    headersSelectable = ((Boolean)result[0]).booleanValue();
    footersSelectable = ((Boolean)result[1]).booleanValue();
    mode = ((Integer)result[2]).intValue();
    
    // update the tier selection model
    tierSelectionModel.setHeaderSelectable( headersSelectable );
    tierSelectionModel.setFooterSelectable( footersSelectable );
    tierSelectionModel.setSelectionMode( mode );
  }

  /**
   * Allows the user to choose a color from the color chooser dialog.<p>
   *
   * @return the new color
   */  
  private Color chooseColor( Component parent, String title, Color initialColor )
  {
    return JColorChooser.showDialog( parent, title, initialColor );
  }

  /**
   * Creates a report the shows some of the default key
   * assignments for all report panes.
   */
  private Report createAccessoryReport()
  {
    // the accessory report's data, as an array
    Object[] keyAssignmentsData = new Object[] {
      "Up / Down / Left / Right :",
       "Moves the selection.",
       "Shift + Up / Down / Left / Right :",
       "Extends the selection.",
       "Home / End :",
       "Moves the selection to the first or last element.",
       "Shift + Home / End :",
       "Extends the selection to the first or last element.",
       "Ctrl + Home / End :",
       "Moves the selection to the beginning or end of the report.",
       "Alt + Up :",
       "Widens the cell selection to either the containing row or column.",
       "Alt + Down :",
       "Narrows the row or column selection to a cell.",
       "Ctrl + A :",
       "Selects all cells, rows, or columns.",
       "F2 :",
       "Begins editing the selection.",
       "Escape :",
       "Cancels editing.",
       "Tab :",
       "Advances to the next selectable element.",
       "Shift + Tab :",
       "Advances to the previous selectable element.",
       "Ctrl + Backslash :",
       "Clears the selection.",
    };

    // a report with a title is simply a table with two rows and one column, so use a table template
    ReportTemplate titledReportTemplate = new TableReportTemplate( "Titled Report Template" );

    // the accessory report's theme
    Theme titledReportTheme = createAccessoryReportTheme();
    
    // create the accessory report
    String title = "Key Assignments";
    Object[] titledReportData = new Object[] { title, keyAssignmentsData };
    Report sideReport = titledReportTemplate.createReport( titledReportData, titledReportTheme );
    return sideReport;
  }
  
  /**
   * Creates and returns the accessory report's body theme.
   */
  private Theme createAccessoryReportBodyTheme()
  {
    StyleContext styleContext = new StyleContext();
    
    // the odd rows are headings with font size 12; bold;
    // bottom border thickness of 2; bottom border gradient;
    // no line wrapping
    MutableAttributeSet keyRowStyle = new SimpleAttributeSet();
    StyleConstants.setFontSize( keyRowStyle, 12 );
    StyleConstants.setBold( keyRowStyle, true );
    ReportStyleConstants.setBottomBorderThickness( keyRowStyle, 2.0 );
    Color transparentGray = new Color( 255, 255, 255, 0 );
    Fill gradientFill = new DefaultFill( Color.gray, transparentGray, SwingConstants.EAST );
    ReportStyleConstants.setBottomBorderFill( keyRowStyle, gradientFill );
    ReportStyleConstants.setWrapped( keyRowStyle, false );
    
    // the even rows are descriptions with font size 11; space above of 3;
    // space below of 9; left indent of 6; line wrapping
    MutableAttributeSet descriptionRowStyle = new SimpleAttributeSet();
    StyleConstants.setFontSize( descriptionRowStyle, 11 );
    StyleConstants.setSpaceAbove( descriptionRowStyle, 3.0f );
    StyleConstants.setSpaceBelow( descriptionRowStyle, 9.0f );
    StyleConstants.setLeftIndent( descriptionRowStyle, 6.0f );
    ReportStyleConstants.setWrapped( descriptionRowStyle, true );
    
    // the table format alternates row attributes
    Style keyAssignmentTableStyle = styleContext.addStyle( "Table", null );
    TableFormat tableFormat = TableFormats.createRowOrientedTableFormat(
      null, new AttributeSet[] { keyRowStyle, descriptionRowStyle }, null );
    ReportStyleConstants.setTableFormat( keyAssignmentTableStyle, tableFormat );
    
    return new DefaultTheme( styleContext );
  }
  
  /**
   * Creates and returns the accessory report's theme.
   */
  private Theme createAccessoryReportTheme()
  {
    // DefaultTheme stores its attribute sets in a style context
    StyleContext styleContext = new StyleContext();
    
    // the title resides in cell 0,0
    //
    // font size 16; Arial; bold; centered; 6 spaces below
    //
    Style titleStyle = styleContext.addStyle( "Cell0,0Object", null );
    StyleConstants.setFontSize( titleStyle, 16 );
    StyleConstants.setFontFamily( titleStyle, "Arial" );
    StyleConstants.setBold( titleStyle, true );
    StyleConstants.setAlignment( titleStyle, StyleConstants.ALIGN_CENTER );
    StyleConstants.setSpaceBelow( titleStyle, 6.0f );

    // the content resides in cell 1,0
    //
    // use a nested report template and theme; default font Arial
    //
    Style bodyStyle = styleContext.addStyle( "Cell1,0Object", null );
    ReportTemplate bodyTemplate = new TableReportTemplate( "Key Assignments List" );
    ReportStyleConstants.setReportTemplate( bodyStyle, bodyTemplate );
    Theme bodyTheme = createAccessoryReportBodyTheme();
    ReportStyleConstants.setTheme( bodyStyle, bodyTheme );
    StyleConstants.setFontFamily( bodyStyle, "Arial" );
    
    return new DefaultTheme( styleContext );
  }
  
  /**
   * Creates and returns the panel that includes components to
   * change the report's appearance.
   */
  private JPanel createAppearanceTab( TableFormat[] tableFormats )
  {
    JPanel stylesPanel = new JPanel();
    stylesPanel.setLayout( new BoxLayout( stylesPanel, BoxLayout.LINE_AXIS ));
    
    // A combobox to choose a sample table format
    //
    JPanel tableFormatsPanel = new JPanel();
    
    JLabel tableFormatsLabel = new JLabel( "Sample Table Formats:" );
    tableFormatsPanel.add( tableFormatsLabel );
    
    final JComboBox tableFormatComboBox = new JComboBox( tableFormats );
    tableFormatComboBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        TableFormat newTableFormat = (TableFormat)tableFormatComboBox.getSelectedItem();
        if( ReportStyleConstants.getTableFormat( tableStyle ) != newTableFormat )
          ReportStyleConstants.setTableFormat( tableStyle, newTableFormat );
      }
    } );
    tableFormatComboBox.setToolTipText( "<html>Choose a table format</html>" );
    tableFormatsPanel.add( tableFormatComboBox );
    
    stylesPanel.add( tableFormatsPanel );
    stylesPanel.add( new JSeparator( SwingConstants.VERTICAL ));
    
    // A combobox to choose a color
    //
    JPanel colorsPanel = new JPanel();
    
    JLabel colorsLabel = new JLabel( "Colors:" );
    colorsPanel.add( colorsLabel );

    final JComboBox attributeComboBox = new JComboBox();
    attributeComboBox.addItem( "Background" );
    attributeComboBox.addItem( "Foreground" );
    attributeComboBox.addItem( "Selection Background" );
    attributeComboBox.addItem( "Selection Foreground" );
    
    attributeComboBox.setToolTipText( "<html>Choose an attribute</html>" );
    colorsPanel.add( attributeComboBox );

    // a button will allow the user to set the color of the selected attribute
    JButton editColorButton = createSmallButton( new AbstractAction( "Edit..." ) {
      public void actionPerformed( ActionEvent event )
      {
        Object attribute = attributeComboBox.getSelectedItem();
        
        if( "Background".equals( attribute ))
        {
          // change the background color with setBackground
          Color background = reportPane.getBackground();
          background = chooseColor( frame, "Pane Background Color", background );
          if( background != null )
            reportPane.setBackground( background );
        }
        else if( "Foreground".equals( attribute ))
        {
          // change the foreground color with setForeground
          Color foreground = reportPane.getForeground();
          foreground = chooseColor( frame, "Pane Foreground Color", foreground );
          if( foreground != null )
            reportPane.setForeground( foreground );
        }
        else if( "Selection Background".equals( attribute ))
        {
          // change the selection background color with setSelectionBackground
          Color background = reportPane.getSelectionBackground();
          background = chooseColor( frame, "Pane Selection Background Color", background );
          if( background != null )
            reportPane.setSelectionBackground( background );
        }
        else if( "Selection Foreground".equals( attribute ))
        {
          // change the selection foreground color with setSelectionForeground
          Color foreground = reportPane.getSelectionForeground();
          foreground = chooseColor( frame, "Pane Selection Foreground Color", foreground );
          if( foreground != null )
            reportPane.setSelectionForeground( foreground );
        }
      }
    } );
    editColorButton.setToolTipText( "<html>Change the color</html>" );
    colorsPanel.add( editColorButton );

    stylesPanel.add( colorsPanel );
    
    return stylesPanel;
  }

  /**
   * Creates and returns the editor model for the main report pane.
   * This editor model consists of a text field editor for column 0,
   * a combo box editor for column 1, a checkbox editor column 2,
   * and number-formatted text field editors for columns 3 to 6.
   */
  private ReportEditorModel createEditorModel()
  {
    // the editor to use depends on the column
    TierReportEditorModel tableEditorModel = new TierReportEditorModel( TableElement.COLUMN );

    // column 0: text field editor that does not allow an empty string
    //
    JTextField textField = new JTextField();
    DefaultElementEditor editor = new DefaultElementEditor( textField ) {
      public boolean stopCellEditing()
      {
        String value = (String)getCellEditorValue();
        
        if( value.length() == 0 )
        {
          cancelCellEditing();
          return true;
        }

        return super.stopCellEditing();
      }
    };
    tableEditorModel.setTierEditor( 0, editor );
    
    // column 1: combo box editor
    //
    JComboBox comboBox = new JComboBox( new Object[] { "public", "package", "private" } );
    DefaultElementEditor comboBoxEditor = new DefaultElementEditor( comboBox );
    tableEditorModel.setTierEditor( 1, comboBoxEditor );

    // column 2: check box editor
    //
    JCheckBox checkBox = new JCheckBox();
    checkBox.setOpaque( false );
    checkBox.setHorizontalAlignment( SwingConstants.CENTER );
    DefaultElementEditor checkBoxEditor = new DefaultElementEditor( checkBox );
    tableEditorModel.setTierEditor( 2, checkBoxEditor );

    // columns 3, 6: integer text field editor
    //
    JFormattedTextField integerTextField = new JFormattedTextField( NumberFormat.getIntegerInstance() );
	  integerTextField.setHorizontalAlignment( JTextField.RIGHT );
    DefaultElementEditor integerEditor = new DefaultElementEditor( integerTextField );
    tableEditorModel.setTierEditor( 3, integerEditor );
    tableEditorModel.setTierEditor( 6, integerEditor );
    
    // columns 4, 5: percent text field editor
    //
    JFormattedTextField percentTextField = new JFormattedTextField( NumberFormat.getPercentInstance() );
    percentTextField.setHorizontalAlignment( JTextField.RIGHT );
    DefaultElementEditor percentEditor = new DefaultElementEditor( percentTextField );
    tableEditorModel.setTierEditor( 4, percentEditor );
    tableEditorModel.setTierEditor( 5, percentEditor );
    
    // we want to edit the nested table report created by the
    // "Statistics Table" template, so use a template editor model
    TemplateReportEditorModel templateEditorModel = new TemplateReportEditorModel();
    templateEditorModel.setTemplateEditorModel( "Statistics Table", tableEditorModel );
    
    return templateEditorModel;
  }

  /**
   * Creates and returns the main report, which consists of a
   * table with a title.
   */
  private Report createMainReport( TableFormat initialTableFormat )
  {
    // initialize the table data
    tableModel = createMainReportBody();
    
    // create a theme for our report, using one of the table formats
    Theme theme = createMainReportTheme( initialTableFormat );

    // a report with a title is simply a table with two rows and one column, so use a table template
    ReportTemplate template = new TableReportTemplate( "Titled Report Template" );
    
    // create the report
    String title = "Project Statistics";
    Object[] titledReportData = new Object[] { title, tableModel };
    Report report = template.createReport( titledReportData, theme );
    
    return report;
  }
  
  /**
   * Creates and returns the program's menu bar.<p>
   *
   * @return the menu bar
   */
  private JMenuBar createMenuBar()
  {
    // File menu
    //
    JMenu fileMenu = new JMenu( "File" );

    // Print Preview menu option
    ClassLoader classLoader = this.getClass().getClassLoader();
    ImageIcon icon = new ImageIcon( classLoader.getResource( "toolbarButtonGraphics/general/PrintPreview16.gif" ));
    Action printPreviewAction = new AbstractAction( "Print Preview...", icon ) {
      public void actionPerformed( ActionEvent event )
      {
        printPreviewReport();
      }
    };
        
    // Exit menu operation
    Action exitAction = new AbstractAction( "Exit" ) {
      public void actionPerformed( ActionEvent event )
      {
        frame.dispose();
      }
    };
    
    fileMenu.add( printPreviewAction );    
    fileMenu.addSeparator();
    fileMenu.add( exitAction );

    JMenuBar menuBar = new JMenuBar();
    menuBar.add( fileMenu );
    
    return menuBar;
  }

  /**
   * Creates and returns a mouse listener that displays a pop-up
   * menu when right-clicking a cell or row.
   */
  private MouseListener createMouseListener()
  {
    MouseListener mouseListener = new MouseAdapter() {
      
      public void mousePressed( MouseEvent event )
      {
        maybeShowPopup( event );
      }

      public void mouseReleased( MouseEvent event )
      {
        maybeShowPopup( event );
      }

      public void maybeShowPopup( MouseEvent event )
      {
        if( !event.isPopupTrigger() )
          return;

        // get the selection at the mouse cursor
        final Element element = reportPane.getSelectionAt( event.getPoint() );
        
        // if nothing selected, then return
        if( element == null )
          return;
        
        // get all selected elements
        ReportSelectionModel selectionModel = reportPane.getSelectionModel();
        final Set selectedElements = selectionModel.getSelectedElements();

        // create a pop-up menu
        JPopupMenu popupMenu = new JPopupMenu();

        // the menu items depend on whether the selection is
        // a row or a cell
        if( element instanceof TierElement )
        {
          final TierElement tierElement = (TierElement)element;
          
          // nothing to do if a column is selected
          int type = tierElement.getType();
          if( type == TableElement.COLUMN )
            return;
          
          final int index = tierElement.getIndex();
          
          // action to add a row to the report
          popupMenu.add( new AbstractAction( "Insert Row Above" ) {
            public void actionPerformed( ActionEvent event )
            {
              // add a row with default values
              tableModel.insertRow( index, new Object[] { "Edit me", "public", Boolean.FALSE, new Integer( 0 ),
                new Double( 1.0 ), new Double( 0.0 ), new Integer( 0 ) } );
                
              // update the footer row
              tableModel.updateFooter();
            }
          } );
          
          // action to delete the selected row(s)
          popupMenu.add( new AbstractAction( selectedElements.size() == 1? "Delete Row" : "Delete Rows" ) {
            public void actionPerformed( ActionEvent event )
            {
              Iterator iter = selectedElements.iterator();
              while( iter.hasNext() )
              {
                TierElement tierElement = (TierElement)iter.next();
                int index = tierElement.getIndex();
                tableModel.removeRow( index );
              }
                
              // update the footer row
              tableModel.updateFooter();
            }
          } );
        }
        else
        {
          // action to edit the selection
          popupMenu.add( new AbstractAction( "Edit" ) {
            public void actionPerformed( ActionEvent event )
            {
              ActionMap actionMap = reportPane.getActionMap();
              
              // the report pane typically has an action that starts editing
              Action action = actionMap.get( ReportEditorKit.BEGIN_EDIT_ACTION_NAME );
              if( action != null )
                action.actionPerformed( event );
            }
          } );
        }

      // display the pop-up menu
      popupMenu.show( reportPane, event.getX(), event.getY() );
      return;
      }
    };
    
    return mouseListener;
  }
  
  /**
   * Creates and returns controls to change options, including
   * the margins, the ability to edit, the ability to
   * provide mouse-over hints, the ability to auto-scroll,
   * and the ability to drag text to other windows.
   */
  private JPanel createOptionsTab()
  {
    JPanel panePanel = new JPanel();
    panePanel.setLayout( new FlowLayout( FlowLayout.LEADING ));

    // Margins
    //
    JLabel marginsLabel = new JLabel( "Margins: " );
    panePanel.add( marginsLabel );
   
    // create four spinners initialized with the margins
    Insets insets = reportPane.getMargin();
    final JSpinner leftMarginsSpinner = createSpinner( new Integer( insets.left ));
    final JSpinner rightMarginsSpinner = createSpinner( new Integer( insets.right ));
    final JSpinner topMarginsSpinner = createSpinner( new Integer( insets.top ));
    final JSpinner bottomMarginsSpinner = createSpinner( new Integer( insets.bottom ));
    
    // respond to the left margin spinner
    leftMarginsSpinner.addChangeListener( new ChangeListener() {
      public void stateChanged( ChangeEvent event )
      {
        // change the margins by invoking setMargin
        Integer integer = (Integer)leftMarginsSpinner.getValue();
        Insets newInsets = (Insets)reportPane.getMargin().clone();
        newInsets.left = integer.intValue();
        reportPane.setMargin( newInsets );
      }
    } );
    leftMarginsSpinner.setToolTipText( "<html>Adjust the left margin</html>" );
    panePanel.add( leftMarginsSpinner );
  
    // respond to the right margin spinner
    rightMarginsSpinner.addChangeListener( new ChangeListener() {
      public void stateChanged( ChangeEvent event )
      {
        // change the margins by invoking setMargin
        Integer integer = (Integer)rightMarginsSpinner.getValue();
        Insets newInsets = (Insets)reportPane.getMargin().clone();
        newInsets.right = integer.intValue();
        reportPane.setMargin( newInsets );
      }
    } );
    rightMarginsSpinner.setToolTipText( "<html>Adjust the right margin</html>" );
    panePanel.add( rightMarginsSpinner );
  
    // respond to the top margin spinner
    topMarginsSpinner.addChangeListener( new ChangeListener() {
      public void stateChanged( ChangeEvent event )
      {
        // change the margins by invoking setMargin
        Integer integer = (Integer)topMarginsSpinner.getValue();
        Insets newInsets = (Insets)reportPane.getMargin().clone();
        newInsets.top = integer.intValue();
        reportPane.setMargin( newInsets );
      }
    } );
    topMarginsSpinner.setToolTipText( "<html>Adjust the top margin</html>" );
    panePanel.add( topMarginsSpinner );
  
    // respond to the bottom margin spinner
    bottomMarginsSpinner.addChangeListener( new ChangeListener() {
      public void stateChanged( ChangeEvent event )
      {
        // change the margins by invoking setMargin
        Integer integer = (Integer)bottomMarginsSpinner.getValue();
        Insets newInsets = (Insets)reportPane.getMargin().clone();
        newInsets.bottom = integer.intValue();
        reportPane.setMargin( newInsets );
      }
    } );
    bottomMarginsSpinner.setToolTipText( "<html>Adjust the bottom margin</html>" );
    panePanel.add( bottomMarginsSpinner );
  
    // Editability
    //
    final JCheckBox editableCheckBox = new JCheckBox( "Editable" );
    editableCheckBox.setSelected( true );
    editableCheckBox.setToolTipText( "<html>Toggle the ability to edit the report</html>" );
    editableCheckBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        reportPane.setEditable( editableCheckBox.isSelected() );
      }
    } );
    panePanel.add( editableCheckBox );
    
    // Hints
    //
    final JCheckBox mouseOverCheckBox = new JCheckBox( "Mouse-over Hints" );
    mouseOverCheckBox.setSelected( true );
    mouseOverCheckBox.setToolTipText( "<html>Toggle the showing of mouse-over hints</html>" );
    mouseOverCheckBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        reportPane.setMouseOverEnabled( mouseOverCheckBox.isSelected() );
      }
    } );
    panePanel.add( mouseOverCheckBox );
    
    // Auto-scrolling
    //
    final JCheckBox autoscrollsCheckBox = new JCheckBox( "Autoscrolling" );
    autoscrollsCheckBox.setSelected( reportPane.getAutoscrolls() );
    autoscrollsCheckBox.setToolTipText( "<html>Toggle the ability to scroll the report while dragging outside its area</html>" );
    autoscrollsCheckBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        reportPane.setAutoscrolls( autoscrollsCheckBox.isSelected() );
      }
    } );
    panePanel.add( autoscrollsCheckBox );
    
    // Dragging
    //
    final JCheckBox dragEnabledCheckBox = new JCheckBox( "Drag Enabled" );
    dragEnabledCheckBox.setSelected( reportPane.getDragEnabled() );
    dragEnabledCheckBox.setToolTipText( "<html>Toggle the ability to drag a cell to other windows</html>" );
    dragEnabledCheckBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        reportPane.setDragEnabled( dragEnabledCheckBox.isSelected() );
      }
    } );
    panePanel.add( dragEnabledCheckBox );
    
    return panePanel;
  }

  /**
   * Creates and returns the content of the main report.
   * The content consists of a table with seven columns.
   */
  private CustomTableModel createMainReportBody()
  {
    CustomTableModel tableModel = new CustomTableModel( new Object[] {
      "File Name Column", "Access Type Column", "Interface Column", 
      "Total Lines Column", "Lines of Code Column", 
      "Lines of Comments Column", "Coupling Between Objects Column"
    }, 0 );
    
    // the first two rows will be the header
    //
    // header row 1 (the fourth cell spans three columns)
    tableModel.addRow( new Object[] {
      "File Name", "Access Type", "Interface", "Lines", 
      TableReportTemplate.STRADDLE_PREVIOUS_COLUMN, 
      TableReportTemplate.STRADDLE_PREVIOUS_COLUMN, 
      "Coupling Between Objects"
    } );
    
    // header row 2 (several of the cells span two rows)
    tableModel.addRow( new Object[] {
      TableReportTemplate.STRADDLE_PREVIOUS_ROW, 
      TableReportTemplate.STRADDLE_PREVIOUS_ROW, 
      TableReportTemplate.STRADDLE_PREVIOUS_ROW, 
      "Total", "Code", "Comments", 
      TableReportTemplate.STRADDLE_PREVIOUS_ROW
    } );
    
    // The report's data. Typically the data will come from
    // the domain model, but, for the purpose of this demo, we simply 
    // hard-code everything. We use approximate code metrics
    // as of 6 Aug 2004.
    Object data[][] = new Object[][] {
      new Object[] { "AbstractReport", "public", Boolean.FALSE, new Integer( 1510 ), new Double( 0.45 ), new Double( 0.55 ), new Integer( 39 ) },
      new Object[] { "AbstractReportEditorModel", "public", Boolean.FALSE, new Integer( 36 ), new Double( 0.08 ), new Double( 0.92 ), new Integer( 4 ) },
      new Object[] { "AbstractReportSelectionModel", "public", Boolean.FALSE, new Integer( 130 ), new Double( 0.24 ), new Double( 0.76 ), new Integer( 14 ) },
      new Object[] { "AbstractReportTableModel", "public", Boolean.FALSE, new Integer( 57 ), new Double( 0.23 ), new Double( 0.77 ), new Integer( 11 ) },
      new Object[] { "AbstractReportTemplate", "public", Boolean.FALSE, new Integer( 84 ), new Double( 0.13 ), new Double( 0.87 ), new Integer( 8 ) },
      new Object[] { "AbstractView", "public", Boolean.FALSE, new Integer( 76 ), new Double( 0.30 ), new Double( 0.70 ), new Integer( 12 ) },
      new Object[] { "AccessibleReportRole", "public", Boolean.FALSE, new Integer( 43 ), new Double( 0.09 ), new Double( 0.91 ), new Integer( 3 ) },
      new Object[] { "ArrayBreakWeightRecorder", "public", Boolean.FALSE, new Integer( 574 ), new Double( 0.48 ), new Double( 0.52 ), new Integer( 12 ) },
      new Object[] { "BasicReportPaneUI", "public", Boolean.FALSE, new Integer( 1866 ), new Double( 0.32 ), new Double( 0.68 ), new Integer( 53 ) },
      new Object[] { "BreakWeightRecorder", "public", Boolean.TRUE, new Integer( 247 ), new Double( 0.09 ), new Double( 0.91 ), new Integer( 12 ) },
      new Object[] { "CellElement", "public", Boolean.TRUE, new Integer( 68 ), new Double( 0.07 ), new Double( 0.93 ), new Integer( 30 ) },
      new Object[] { "CellReportSelectionModel", "public", Boolean.FALSE, new Integer( 666 ), new Double( 0.44 ), new Double( 0.56 ), new Integer( 15 ) },
      new Object[] { "CompositeReportSelectionModel", "public", Boolean.FALSE, new Integer( 351 ), new Double( 0.36 ), new Double( 0.64 ), new Integer( 13 ) },
      new Object[] { "DefaultElementEditor", "public", Boolean.FALSE, new Integer( 436 ), new Double( 0.35 ), new Double( 0.65 ), new Integer( 14 ) },
      new Object[] { "DefaultFill", "public", Boolean.FALSE, new Integer( 207 ), new Double( 0.31 ), new Double( 0.69 ), new Integer( 5 ) },
      new Object[] { "DefaultReportEditorKit", "public", Boolean.FALSE, new Integer( 1735 ), new Double( 0.34 ), new Double( 0.66 ), new Integer( 26 ) },
      new Object[] { "DefaultReportEditorModel", "public", Boolean.FALSE, new Integer( 99 ), new Double( 0.25 ), new Double( 0.75 ), new Integer( 6 ) },
      new Object[] { "DefaultReportEvent", "public", Boolean.FALSE, new Integer( 147 ), new Double( 0.25 ), new Double( 0.75 ), new Integer( 12 ) },
      new Object[] { "DefaultTableFormat", "public", Boolean.FALSE, new Integer( 652 ), new Double( 0.27 ), new Double( 0.73 ), new Integer( 15 ) },
      new Object[] { "DefaultTextHighlighter", "public", Boolean.FALSE, new Integer( 76 ), new Double( 0.32 ), new Double( 0.68 ), new Integer( 9 ) },
      new Object[] { "DefaultTheme", "public", Boolean.FALSE, new Integer( 114 ), new Double( 0.25 ), new Double( 0.75 ), new Integer( 9 ) },
      new Object[] { "Element", "public", Boolean.TRUE, new Integer( 85 ), new Double( 0.09 ), new Double( 0.91 ), new Integer( 64 ) },
      new Object[] { "ElementEditor", "public", Boolean.TRUE, new Integer( 79 ), new Double( 0.05 ), new Double( 0.95 ), new Integer( 13 ) },
      new Object[] { "ElementRenderer", "public", Boolean.TRUE, new Integer( 46 ), new Double( 0.04 ), new Double( 0.96 ), new Integer( 5 ) },
      new Object[] { "Fill", "public", Boolean.TRUE, new Integer( 142 ), new Double( 0.14 ), new Double( 0.86 ), new Integer( 9 ) },
      new Object[] { "InvalidBreakPosition", "public", Boolean.FALSE, new Integer( 52 ), new Double( 0.12 ), new Double( 0.88 ), new Integer( 6 ) },
      new Object[] { "JReportPane", "public", Boolean.FALSE, new Integer( 1941 ), new Double( 0.30 ), new Double( 0.70 ), new Integer( 76 ) },
      new Object[] { "NotSelectableException", "public", Boolean.FALSE, new Integer( 40 ), new Double( 0.10 ), new Double( 0.90 ), new Integer( 7 ) },
      new Object[] { "ObjectElement", "public", Boolean.TRUE, new Integer( 42 ), new Double( 0.07 ), new Double( 0.93 ), new Integer( 8 ) },
      new Object[] { "Report", "public", Boolean.TRUE, new Integer( 112 ), new Double( 0.07 ), new Double( 0.93 ), new Integer( 18 ) },
      new Object[] { "ReportAction", "public", Boolean.TRUE, new Integer( 73 ), new Double( 0.15 ), new Double( 0.85 ), new Integer( 5 ) },
      new Object[] { "ReportEditorKit", "public", Boolean.TRUE, new Integer( 289 ), new Double( 0.18 ), new Double( 0.82 ), new Integer( 9 ) },
      new Object[] { "ReportEditorModel", "public", Boolean.TRUE, new Integer( 41 ), new Double( 0.07 ), new Double( 0.93 ), new Integer( 9 ) },
      new Object[] { "ReportEvent", "public", Boolean.TRUE, new Integer( 95 ), new Double( 0.15 ), new Double( 0.85 ), new Integer( 32 ) },
      new Object[] { "ReportListener", "public", Boolean.TRUE, new Integer( 33 ), new Double( 0.06 ), new Double( 0.94 ), new Integer( 8 ) },
      new Object[] { "ReportPaneUI", "public", Boolean.FALSE, new Integer( 68 ), new Double( 0.09 ), new Double( 0.91 ), new Integer( 9 ) },
      new Object[] { "ReportSelectionEvent", "public", Boolean.FALSE, new Integer( 77 ), new Double( 0.17 ), new Double( 0.83 ), new Integer( 10 ) },
      new Object[] { "ReportSelectionListener", "public", Boolean.TRUE, new Integer( 33 ), new Double( 0.06 ), new Double( 0.94 ), new Integer( 12 ) },
      new Object[] { "ReportSelectionModel", "public", Boolean.TRUE, new Integer( 166 ), new Double( 0.10 ), new Double( 0.90 ), new Integer( 13 ) },
      new Object[] { "ReportStyleConstants", "public", Boolean.FALSE, new Integer( 1978 ), new Double( 0.20 ), new Double( 0.80 ), new Integer( 22 ) },
      new Object[] { "ReportTableModel", "public", Boolean.TRUE, new Integer( 73 ), new Double( 0.10 ), new Double( 0.90 ), new Integer( 7 ) },
      new Object[] { "ReportTemplate", "public", Boolean.TRUE, new Integer( 72 ), new Double( 0.08 ), new Double( 0.92 ), new Integer( 10 ) },
      new Object[] { "TableElement", "public", Boolean.TRUE, new Integer( 208 ), new Double( 0.07 ), new Double( 0.93 ), new Integer( 21 ) },
      new Object[] { "TableFormat", "public", Boolean.TRUE, new Integer( 58 ), new Double( 0.07 ), new Double( 0.93 ), new Integer( 10 ) },
      new Object[] { "TableFormatAdapter", "public", Boolean.FALSE, new Integer( 44 ), new Double( 0.16 ), new Double( 0.84 ), new Integer( 9 ) },
      new Object[] { "TableFormats", "public", Boolean.FALSE, new Integer( 249 ), new Double( 0.31 ), new Double( 0.69 ), new Integer( 7 ) },
      new Object[] { "TemplateReportEditorModel", "public", Boolean.FALSE, new Integer( 115 ), new Double( 0.32 ), new Double( 0.68 ), new Integer( 9 ) },
      new Object[] { "TemplateReportSelectionModel", "public", Boolean.FALSE, new Integer( 318 ), new Double( 0.44 ), new Double( 0.56 ), new Integer( 17 ) },
      new Object[] { "TableReportTemplate", "public", Boolean.FALSE, new Integer( 633 ), new Double( 0.38 ), new Double( 0.62 ), new Integer( 18 ) },
      new Object[] { "TextHighlighter", "public", Boolean.TRUE, new Integer( 80 ), new Double( 0.08 ), new Double( 0.92 ), new Integer( 6 ) },
      new Object[] { "Theme", "public", Boolean.TRUE, new Integer( 49 ), new Double( 0.08 ), new Double( 0.92 ), new Integer( 12 ) },
      new Object[] { "TierElement", "public", Boolean.TRUE, new Integer( 64 ), new Double( 0.11 ), new Double( 0.89 ), new Integer( 24 ) },
      new Object[] { "TierReportEditorModel", "public", Boolean.FALSE, new Integer( 205 ), new Double( 0.33 ), new Double( 0.67 ), new Integer( 12 ) },
      new Object[] { "TierReportSelectionModel", "public", Boolean.FALSE, new Integer( 521 ), new Double( 0.39 ), new Double( 0.61 ), new Integer( 15 ) },
      new Object[] { "TraversalPolicy", "public", Boolean.TRUE, new Integer( 90 ), new Double( 0.06 ), new Double( 0.94 ), new Integer( 6 ) },
      new Object[] { "View", "public", Boolean.TRUE, new Integer( 437 ), new Double( 0.14 ), new Double( 0.86 ), new Integer( 32 ) },
      new Object[] { "ViewFactory", "public", Boolean.TRUE, new Integer( 36 ), new Double( 0.06 ), new Double( 0.94 ), new Integer( 16 ) },
    };

    for( int i = 0; i < data.length; i++ )
      tableModel.addRow( data[i] );
    
    // add a summary row
    tableModel.addRow( new Object[] {
      null, null, null, null, null, null, null
    } );
    
    // initialize the values in the summary row
    tableModel.updateFooter();

    return tableModel;
  }

  /**
   * Creates and returns the selection model of the main report pane.
   */
  private ReportSelectionModel createSelectionModel()
  {
    // initialize the row selection model
    rowSelectionModel = new TierReportSelectionModel( TableElement.ROW );
    
    // initialize the column selection model
    columnSelectionModel = new TierReportSelectionModel( TableElement.COLUMN );
    
    // initialize the cell selection model
    cellSelectionModel = new CellReportSelectionModel( true, false, true, false, true );

    // initially, rows and cells (not columns) are selectable
    compositeSelectionModel = new CompositeReportSelectionModel();
    compositeSelectionModel.addSelectionModel( cellSelectionModel );
    compositeSelectionModel.addSelectionModel( rowSelectionModel );
    
    // only elements of the "Statistics Table" report are selectable
    TemplateReportSelectionModel templateSelectionModel = new TemplateReportSelectionModel();
    templateSelectionModel.setTemplateSelectionModel( "Statistics Table", compositeSelectionModel );
    
    return templateSelectionModel;
  }
  
  /**
   * Creates and returns the panel that includes components to
   * change the report selection model.
   */
  private JPanel createSelectionTab()
  {
    JPanel selectionPanel = new JPanel();
    selectionPanel.setLayout( new BoxLayout( selectionPanel, BoxLayout.LINE_AXIS ));

    // Cell selection
    //
    JPanel cellSelectionPanel = new JPanel();
    final JCheckBox cellSelectionCheckBox = new JCheckBox( "Cells are selectable", true );
    cellSelectionCheckBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        updateSelectionModel( cellSelectionCheckBox, cellSelectionModel );
      }
    } );
    cellSelectionCheckBox.setToolTipText( "<html>Toggle the ability to select cells</html>" );
    JPanel cellButtonPanel = new JPanel();
    JButton cellRowDetailsButton = createSmallButton( new AbstractAction( "Rows..." ) {
      public void actionPerformed( ActionEvent event )
      {
        applyCellSelectionModelSettings( TableElement.ROW, "rows" );
      }
    } );
    cellRowDetailsButton.setToolTipText( "<html>Adjust the behavior of cell selection based on a cell's row</html>" );
    JButton cellColumnDetailsButton = createSmallButton( new AbstractAction( "Columns..." ) {
      public void actionPerformed( ActionEvent event )
      {
        applyCellSelectionModelSettings( TableElement.COLUMN, "columns" );
      }
    } );
    cellColumnDetailsButton.setToolTipText( "<html>Adjust the behavior of cell selection based on a cell's column</html>" );
    cellSelectionPanel.add( cellSelectionCheckBox );
    cellSelectionPanel.add( cellRowDetailsButton );
    cellSelectionPanel.add( cellColumnDetailsButton );

    // Row selection
    //
    JPanel rowSelectionPanel = new JPanel();
    final JCheckBox rowSelectionCheckBox = new JCheckBox( "Rows are selectable", true );
    rowSelectionCheckBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        updateSelectionModel( rowSelectionCheckBox, rowSelectionModel );
      }
    } );
    rowSelectionCheckBox.setToolTipText( "<html>Toggle the ability to select rows</html>" );
    JButton rowDetailsButton = createSmallButton( new AbstractAction( "Options..." ) {
      public void actionPerformed( ActionEvent event )
      {
        applyTierSelectionModelSettings( rowSelectionModel, "rows" );
      }
    } );
    rowDetailsButton.setToolTipText( "<html>Adjust the behavior of row selection</html>" );
    
    rowSelectionPanel.add( rowSelectionCheckBox );
    rowSelectionPanel.add( rowDetailsButton );
    
    // Column selection
    //
    JPanel columnSelectionPanel = new JPanel();
    final JCheckBox columnSelectionCheckBox = new JCheckBox( "Columns are selectable", false );
    columnSelectionCheckBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        updateSelectionModel( columnSelectionCheckBox, columnSelectionModel );
      }
    } );
    columnSelectionCheckBox.setToolTipText( "<html>Toggle the ability to select columns</html>" );
    JButton columnDetailsButton = createSmallButton( new AbstractAction( "Options..." ) {
      public void actionPerformed( ActionEvent event )
      {
        applyTierSelectionModelSettings( columnSelectionModel, "columns" );
      }
    } );
    columnDetailsButton.setToolTipText( "<html>Adjust the behavior of column selection</html>" );
    
    columnSelectionPanel.add( columnSelectionCheckBox );
    columnSelectionPanel.add( columnDetailsButton );
    
    // initialize the panel
    selectionPanel.add( cellSelectionPanel );
    selectionPanel.add( new JSeparator( SwingConstants.VERTICAL ));
    selectionPanel.add( rowSelectionPanel );
    selectionPanel.add( new JSeparator( SwingConstants.VERTICAL ));
    selectionPanel.add( columnSelectionPanel );
    
    return selectionPanel;
  }
  
  /**
   * Creates and returns a small button for the specified action.
   */
  private JButton createSmallButton( Action action )
  {
    JButton button = new JButton( action );
    button.setMargin( new Insets( 2, 2, 2, 2 ));
    Font font = button.getFont();
    Font smallFont = font.deriveFont( font.getSize() - 1.0f ); 
    button.setFont( smallFont );
    return button;
  }
  
  /**
   * Creates a JSpinner with the specified initial value.<p>
   *
   * @return a new spinner
   */
  private JSpinner createSpinner( Object initialValue )
  {
    JSpinner spinner = new JSpinner();
    spinner.setPreferredSize( new Dimension( 40, 24 ));
    spinner.setValue( initialValue );
    return spinner;
  }
  
  /**
   * Creates a number of sample table formats with names.
   */
  private TableFormat[] createTableFormats()
  {
    TableFormat[] tableFormats = new TableFormat[8];
    tableFormats[0] = TableFormatFactory.createYellowStripedTableFormat();
    tableFormats[1] = TableFormatFactory.createBlueStripedTableFormat();
    tableFormats[2] = TableFormatFactory.createPartialGridTableFormat();
    tableFormats[3] = TableFormatFactory.createGridTableFormat();
    tableFormats[4] = TableFormatFactory.createGrayGridTableFormat();
    tableFormats[5] = TableFormatFactory.createStripedGridTableFormat();
    tableFormats[6] = TableFormatFactory.createModestTableFormat();
    tableFormats[7] = new NamedTableFormat( "None", TableFormats.EMPTY_TABLE_FORMAT );
    
    return tableFormats;
  }

  /**
   * Creates and returns the theme describing the main report's body.
   */
  private Theme createMainReportBodyTheme( TableFormat initialTableFormat )
  {
    // DefaultTheme stores its attribute sets in a style context
    StyleContext tableReportStyleContext = new StyleContext();
    
    // the table format will describe all of the formatting
    tableStyle = tableReportStyleContext.addStyle( "Table", null );
    ReportStyleConstants.setTableFormat( tableStyle, initialTableFormat );
    
    return new DefaultTheme( tableReportStyleContext );
  }
  
  /**
   * Creates and returns the main report's theme.
   */
  private Theme createMainReportTheme( TableFormat initialTableFormat )
  {
    // DefaultTheme stores its attribute sets in a style context
    StyleContext styleContext = new StyleContext();
    
    // the title resides in cell 0,0
    //
    // font size 16; bold; centered; 12 spaces below
    //
    Style titleStyle = styleContext.addStyle( "Cell0,0Object", null );
    StyleConstants.setFontSize( titleStyle, 16 );
    StyleConstants.setBold( titleStyle, true );
    StyleConstants.setAlignment( titleStyle, StyleConstants.ALIGN_CENTER );
    StyleConstants.setSpaceBelow( titleStyle, 9.0f );
    
    // the content resides in cell 1,0
    //
    // use a nested report template and theme
    //
    Style bodyStyle = styleContext.addStyle( "Cell1,0Object", null );
    ReportTemplate bodyTemplate = new TableReportTemplate( "Statistics Table", 2, 1, 1, 1 );
    ReportStyleConstants.setReportTemplate( bodyStyle, bodyTemplate );
    Theme bodyTheme = createMainReportBodyTheme( initialTableFormat );
    ReportStyleConstants.setTheme( bodyStyle, bodyTheme );

    Style rowStyle = styleContext.addStyle( "Row", null );
    ReportStyleConstants.setFillProportion( rowStyle, 0 );
    styleContext.addStyle( "Row0", rowStyle );
    styleContext.addStyle( "Row1", rowStyle );
    
    return new DefaultTheme( styleContext );
  }
  
  /**
   * Serves as the program's entry point.<p>
   */
  public static void main(String[] args)
  {
    // initialize the gui on Swing's event dispatch thread
    SwingUtilities.invokeLater( new Runnable() {
      public void run()
      {
        ReportDemo reportDemo = new ReportDemo();
        reportDemo.run();
      }
    } );
  }
  
  /**
   * Print previews the report with a header, footer, and watermark.
   */
  private void printPreviewReport()
  {
    // page description
    PageFormat pageFormat = new PageFormat();
    
    // header
    final JLabel header = new JLabel( "Side of Software's Report Library" );
    header.setBorder( BorderFactory.createMatteBorder( 0, 0, 2, 0, Color.BLUE ));
    
    // footer
    final JLabel footer = new JLabel( "Page XX of YY" );
    footer.setBorder( BorderFactory.createMatteBorder( 2, 0, 0, 0, Color.RED ));
    
    // watermark
    JLabel watermark = new JLabel( "SAMPLE" );
    watermark.setVerticalAlignment( SwingConstants.CENTER );
    watermark.setHorizontalAlignment( SwingConstants.CENTER );
    watermark.setFont( new Font( "Arial", Font.BOLD, 110 ));
    watermark.setForeground( new Color( 192, 192, 192, 100 ));

    JReportPane.HeaderFooterFactory headerFooterFactory = new JReportPane.HeaderFooterFactory() {
      public Component getHeaderComponent( Pageable pageable, int pageIndex )
      {
        return header;
      }
      
      public Component getFooterComponent( Pageable pageable, int pageIndex )
      {
        footer.setText( "Page " + (pageIndex+1) + " of " + pageable.getNumberOfPages() );
        return footer;
      }
      
      public Component getPrototypeHeaderComponent()
      {
        return header;
      }
      
      public Component getPrototypeFooterComponent()
      {
        return footer;
      }
    };
    
    // convert the report into a Pageable object that the
    // print preview pane can handle
    Pageable pageable = reportPane.getFittedPageable( pageFormat, headerFooterFactory, null, watermark );

    final ComponentAdapter resizeListener = new ComponentAdapter () 
    {
      public void componentResized( ComponentEvent e )
      {
        previewPane.setBounds( frame.getLayeredPane().getBounds() );
        previewPane.revalidate();
        previewPane.repaint();
      }
    };

    final Action closeAction = new AbstractAction() {
      public void actionPerformed( ActionEvent event )
      {
        JLayeredPane layeredPane = frame.getLayeredPane();
        layeredPane.removeComponentListener( resizeListener );
        layeredPane.remove( previewPane );
        
        // Make the frame's Close button act like an exit again.
        // We must do this after the current event has been processed;
        // otherwise the frame may mistakenly exit during this event,
        // since we may be currently processing a "closing" event.
        SwingUtilities.invokeLater( new Runnable() {
          public void run()
          {
            frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
          }
        } );
        
        frame.repaint();
      }
    };
    
    WindowListener closeListener = new WindowAdapter() {
      public void windowClosing( WindowEvent event )
      {
        closeAction.actionPerformed( null );
        frame.removeWindowListener( this );
      }
    };
    frame.addWindowListener( closeListener );
    frame.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
    
    previewPane = PrintPreviewSupport.createZoomPanel( pageable, false, PrintPreviewSupport.ZOOM, closeAction, false );
    
    JLayeredPane layeredPane = frame.getLayeredPane();
    previewPane.setBounds( layeredPane.getBounds() );
    layeredPane.add( previewPane, new Integer( layeredPane.highestLayer() + 1 ) );
    layeredPane.addComponentListener( resizeListener );
    frame.repaint();
  }
  
  /**
   * Displays a window that demonstrates the use and customization of
   * the report pane.<p>
   */  
  public void run()
  {
    // one main window that will show two reports and a toolbar
    frame = new JFrame( "Report Demo by Side of Software" );

    // initialize the various table formats
    TableFormat[] tableStyles = createTableFormats();
    
    // create the main report
    Report mainReport = createMainReport( tableStyles[0] );

    // create the main report pane
    reportPane = new JReportPane( mainReport );
    reportPane.setSurrendersFocusOnKeystroke( true );
    reportPane.setSelectionNeededForDrag( false );
    
    // install the selection model
    ReportSelectionModel selectionModel = createSelectionModel();
    reportPane.setSelectionModel( selectionModel );
    
    // install the editor model
    ReportEditorModel editorModel = createEditorModel();
    reportPane.setEditorModel( editorModel );
    
    // put the report pane in a scroll pane
    JScrollPane scrollPane = new JScrollPane( reportPane );

    // add the scroll pane to the content pane
    JPanel contentPane = new JPanel( new BorderLayout() );
    contentPane.add( scrollPane, BorderLayout.CENTER );

    // create the second report
    Report sideReport = createAccessoryReport();
    
    // create the second, uneditable report pane
    final JReportPane sideReportPane = new JReportPane( sideReport );
    sideReportPane.setEditable( false );
    sideReportPane.setBackground( new Color( 253, 245, 230 ));
    sideReportPane.setToolTipText( "An uneditable report that shows the default key assignments." );
    
    // put the second report pane in a scroll pane
    final JScrollPane sideScrollPane = new JScrollPane( sideReportPane );
    
    // put the scroll pane in a separate panel so we can add a border
    JPanel sidePanel = new JPanel();
    sidePanel.setBorder( BorderFactory.createEmptyBorder( 10, 2, 10, 2 ));
    sidePanel.add( sideScrollPane );
    
    // Scrollpane layout does not seem to calculate the scrollpane's preferred size
    // correctly. It does not automatically add in the width of the vertical scrollbar.
    sideScrollPane.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS );
    
    // add the side panel to the content pane
    contentPane.add( sideScrollPane, BorderLayout.LINE_END );
    
    // set the content pane
    frame.getContentPane().add( contentPane );

    // we want to respond to right mouse clicks, so install a mouse listener
    MouseListener mouseListener = createMouseListener();
    reportPane.addMouseListener( mouseListener );
    
    // add a toolbar that has some common actions
    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: Appearance
    /////////////////////
    JPanel stylesPanel = createAppearanceTab( tableStyles );
    tabbedPane.addTab( "Appearance", stylesPanel );

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

    /////////////////////
    // Tab 3: Options
    /////////////////////
    JPanel panePanel = createOptionsTab();
    tabbedPane.addTab( "Options", panePanel );

    toolbar.add( tabbedPane );
    frame.getContentPane().add( toolbar, BorderLayout.BEFORE_FIRST_LINE );
    
    // add a menu to the frame
    JMenuBar menuBar = createMenuBar();
    frame.setJMenuBar( menuBar );
    
    frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
    frame.setSize( 800, 500 );
    frame.show();
  }
  
  /**
   * Displays the selection dialog and returns the values in the
   * returned array. If the user cancels, returns null.
   */
  private Object[] showSelectionDetailsDialog( boolean headersSelectable, 
    String headerText, boolean footersSelectable, String footerText, int mode )
  {
    // bring up an option pane with two checkboxes, a separator, and a combobox panel
    Object[] message = new Object[4];
    
    // checkbox to allow the user to make headers selectable
    JCheckBox headersCheckBox = new JCheckBox( headerText + " are selectable", headersSelectable );
    
    // checkbox to allow the user to make footers selectable
    JCheckBox footersCheckBox = new JCheckBox( footerText + " are selectable", footersSelectable );
    
    // combobox to allow the user to change the selection mode
    JPanel modePanel = new JPanel();
    JLabel modeLabel = new JLabel( "Mode:" );
    String multipleIntervalString = "Multiple Intervals";
    String singleIntervalString = "Single Interval";
    String singleString = "Single Selection";
    JComboBox modeComboBox = new JComboBox( new Object[] { multipleIntervalString,
      singleIntervalString, singleString } );
      
    // initialize the combobox
    switch( mode )
    {
      case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:
        modeComboBox.setSelectedIndex( 0 );
        break;
        
      case ListSelectionModel.SINGLE_INTERVAL_SELECTION:
        modeComboBox.setSelectedIndex( 1 );
        break;
        
      case ListSelectionModel.SINGLE_SELECTION:
      default:
        modeComboBox.setSelectedIndex( 2 );
        break;
    }
    
    modePanel.add( modeLabel );
    modePanel.add( modeComboBox );
    
    message[0] = headersCheckBox;
    message[1] = footersCheckBox;
    message[2] = new JSeparator();
    message[3] = modePanel;
    
    // show the modal dialog
    int response = JOptionPane.showOptionDialog( frame, message, "Selection Details", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null, null );
    
    // check if the user canceled
    if( response != JOptionPane.YES_OPTION )
      return null;
    
    headersSelectable = headersCheckBox.isSelected();
    footersSelectable = footersCheckBox.isSelected();
    
    Object modeItem = modeComboBox.getSelectedItem();
    if( multipleIntervalString.equals( modeItem ))
      mode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
    else if( singleIntervalString.equals( modeItem ))
      mode = ListSelectionModel.SINGLE_INTERVAL_SELECTION;
    else
      mode = ListSelectionModel.SINGLE_SELECTION;
    
    // return the data in an array
    Object[] result = new Object[3];
    result[0] = headersSelectable? Boolean.TRUE : Boolean.FALSE;
    result[1] = footersSelectable? Boolean.TRUE : Boolean.FALSE;
    result[2] = new Integer( mode );
    
    return result;
  }
  
  /**
   * Either adds or removes selectionModel from the composite
   * selection model, depending on the state of checkBox.
   */
  private void updateSelectionModel( JCheckBox checkBox, ReportSelectionModel selectionModel )
  {
    if( checkBox.isSelected() )
      compositeSelectionModel.addSelectionModel( selectionModel );
    else
    {
      compositeSelectionModel.removeSelectionModel( selectionModel );
      selectionModel.clearSelection();
    }
  }
}

/**
 * A table model that maintains a footer row. It assumes there
 * are two header rows, column 0 is a list of files, numbers in column 3
 * are summed, and numbers in columns 4, 5, and 6 are averaged.
 * It also assumes that the numbers in columns 4 and 5 must
 * total 1.0.
 */
class CustomTableModel extends DefaultTableModel
{
  public CustomTableModel( Object[] columnNames, int rowCount )
  {
    super( columnNames, rowCount );
  }
  
  /**
   * Adjust the value at the specified cell to be 1 - value.
   */
  private void adjust( Object value, int row, int column )
  {
    Number number = (Number)value;
    double val = number.doubleValue();

    double rest = 1.0 - val;
    super.setValueAt( new Double( rest ), row, column );
    recomputeAverage( column );
  }

  /**
   * Averages the values in the specified column and
   * sets the footer cell.
   */
  private void recomputeAverage( int column )
  {
    double avg = 0.0;

    int numRows = getRowCount();
    for( int i = 2; i < numRows - 1; i++ )
    {
      Number number = (Number)getValueAt( i, column );
      double value = number.doubleValue();
      avg += value;
    }

    int numBodyRows = numRows - 3;
    if( numBodyRows > 0 )
      avg = avg / numBodyRows;

    super.setValueAt( new Double( avg ), numRows - 1, column );
  }
  
  /**
   * Averages the values in the specified column and
   * sets the footer cell.
   */
  private void recomputeSum( int column )
  {
    int sum = 0;

    int numRows = getRowCount();
    for( int i = 2; i < numRows - 1; i++ )
    {
      Number number = (Number)getValueAt( i, column );
      int value = number.intValue();
      sum += value;
    }

    super.setValueAt( new Integer( sum ), numRows - 1, column );
  }
  
  /**
   * Sets the value of the specified cell to be <code>value</code>.
   */
  public void setValueAt( Object value, int row, int column )
  {
    super.setValueAt( value, row, column );

    switch( column )
    {
      case 3:
        recomputeSum( column );
        break;
        
      case 4:
        adjust( value, row, 5 );
        recomputeAverage( column );
        break;
        
      case 5:
        adjust( value, row, 4 );
        recomputeAverage( column );
        break;
        
      case 6:
        recomputeAverage( column );
        break;
    }
  }

  /**
   * Updates the footer value in column 0.
   */
  private void updateFileCount()
  {
    int numRows = getRowCount();
    String summary = "Total: " + (numRows - 3) + " Files";  // subtract two header rows and one footer row
    super.setValueAt( summary, numRows - 1, 0 );
  }
  
  /**
   * Updates the footer row.
   */
  public void updateFooter()
  {
    updateFileCount();
    recomputeSum( 3 );
    recomputeAverage( 4 );
    recomputeAverage( 5 );
    recomputeAverage( 6 );
  }
}

/**
 * A custom table format factory.
 */
class TableFormatFactory
{
  static final Fill yellow = new DefaultFill( new Color( 255, 255, 204 ));
  static final Color tan = new Color( 253, 245, 230 );
  static final Color pink = new Color( 0, 153, 153 );
  static final Color darkerPink = new Color( 204, 204, 204 );

  /**
   * Table style common to most of the sample table formats.
   */
  static final DefaultTableFormat commonTableFormat;
  
  static {
    // initialize the table format shared by other table formats
    commonTableFormat = new DefaultTableFormat();
    
    // column 2 renders true/false values as a checkbox
    MutableAttributeSet column2Attributes = new SimpleAttributeSet();
    ElementRenderer checkBoxRenderer = new CheckBoxElementRenderer();
    ReportStyleConstants.setRenderer( column2Attributes, checkBoxRenderer );
    commonTableFormat.setColumnCellAttributes( column2Attributes, 2, DefaultTableFormat.BODY );
    
    // column 3 is right aligned
    MutableAttributeSet column3Attributes = new SimpleAttributeSet();
    StyleConstants.setAlignment( column3Attributes, StyleConstants.ALIGN_RIGHT );
    commonTableFormat.setColumnAttributes( column3Attributes, 3 );
    
    // the footer in column 3 has a tool tip
    MutableAttributeSet column3FooterAttributes = (MutableAttributeSet)column3Attributes.copyAttributes();
    ReportStyleConstants.setToolTip( column3FooterAttributes, "Total line count" );
    commonTableFormat.setColumnCellAttributes( column3FooterAttributes, 3, DefaultTableFormat.FOOTER );
    
    // column 4 is right aligned and values are displayed as a percentage
    MutableAttributeSet column4Attributes = new SimpleAttributeSet();
    Format percentFormat = NumberFormat.getPercentInstance();
    ReportStyleConstants.setFormat( column4Attributes, percentFormat );
    StyleConstants.setAlignment( column4Attributes, StyleConstants.ALIGN_RIGHT );
    commonTableFormat.setColumnCellAttributes( column4Attributes, 4, DefaultTableFormat.BODY );
    
    // the footer in column 4 has a tool tip
    MutableAttributeSet column4FooterAttributes = (MutableAttributeSet)column4Attributes.copyAttributes();
    ReportStyleConstants.setToolTip( column4FooterAttributes, "Average code percentage" );
    commonTableFormat.setColumnCellAttributes( column4FooterAttributes, 4, DefaultTableFormat.FOOTER );
    
    // column 5 is right aligned and values are displayed as a percentage
    MutableAttributeSet column5Attributes = new SimpleAttributeSet();
    ReportStyleConstants.setFormat( column5Attributes, percentFormat );
    StyleConstants.setAlignment( column5Attributes, StyleConstants.ALIGN_RIGHT );
    commonTableFormat.setColumnCellAttributes( column5Attributes, 5, DefaultTableFormat.BODY );
    
    // the footer in column 5 has a tool tip
    MutableAttributeSet column5FooterAttributes = (MutableAttributeSet)column5Attributes.copyAttributes();
    ReportStyleConstants.setToolTip( column5FooterAttributes, "Average comment percentage" );
    commonTableFormat.setColumnCellAttributes( column5FooterAttributes, 5, DefaultTableFormat.FOOTER );

    // column 6 is right aligned
    MutableAttributeSet column6Attributes = new SimpleAttributeSet();
    StyleConstants.setAlignment( column6Attributes, StyleConstants.ALIGN_RIGHT );
    commonTableFormat.setColumnAttributes( column6Attributes, 6 );
    
    // the footer in column 6 has a tool tip and is displayed with at most one decimal
    MutableAttributeSet column6FooterAttributes = (MutableAttributeSet)column6Attributes.copyAttributes();
    NumberFormat numberFormat = NumberFormat.getNumberInstance();
    numberFormat.setMaximumFractionDigits( 1 );
    ReportStyleConstants.setFormat( column6FooterAttributes, numberFormat );
    ReportStyleConstants.setToolTip( column6FooterAttributes, "Average complexity" );
    commonTableFormat.setColumnCellAttributes( column6FooterAttributes, 6, DefaultTableFormat.FOOTER );
    
    // the header rows are bold and centered
    MutableAttributeSet headerAttributes = new SimpleAttributeSet();
    StyleConstants.setBold( headerAttributes, true );
    StyleConstants.setAlignment( headerAttributes, StyleConstants.ALIGN_CENTER );
    commonTableFormat.setRowAttributes( headerAttributes, DefaultTableFormat.HEADER );
    
    // no row should be padded to fill the available space
    MutableAttributeSet rowAttributes = new SimpleAttributeSet();
    ReportStyleConstants.setFillProportion( rowAttributes, 0 );
    commonTableFormat.setRowAttributes( rowAttributes );
  }
  
  static TableFormat createBlueStripedTableFormat()
  {
    Fill blue = new DefaultFill( new Color( 0, 153, 255 ));
    Fill lightBlue = new DefaultFill( new Color( 153, 204, 255 ));
    
    // The table itself has a thin light blue border. Its contents
    // are rendered in Arial size 10.
    MutableAttributeSet tableAttributes = new SimpleAttributeSet();
    ReportStyleConstants.setBorderThickness( tableAttributes, 1.0 );
    ReportStyleConstants.setBorderFill( tableAttributes, lightBlue );
    StyleConstants.setFontFamily( tableAttributes, "Arial" );
    StyleConstants.setFontSize( tableAttributes, 10 );
    DefaultTableFormat defaultTableFormat = new DefaultTableFormat();
    defaultTableFormat.setTableAttributes( tableAttributes );

    // column 0 is bold
    MutableAttributeSet column0Attributes = new SimpleAttributeSet();
    StyleConstants.setBold( column0Attributes, true );
    defaultTableFormat.setColumnAttributes( column0Attributes, 0 );
    
    // the header rows are white on blue, size 12
    MutableAttributeSet headerAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setBackgroundFill( headerAttributeSet, blue );
    StyleConstants.setForeground( headerAttributeSet, Color.white );
    StyleConstants.setFontSize( headerAttributeSet, 12 );
    
    // odd rows have a white background
    MutableAttributeSet oddRowAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setBackgroundFill( oddRowAttributeSet, Fill.WHITE );
    
    // even rows have a light blue background
    MutableAttributeSet evenRowAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setBackgroundFill( evenRowAttributeSet, lightBlue );
    
    // create the row-oriented table format
    TableFormat tableStyle = TableFormats.createRowOrientedTableFormat( new AttributeSet[] { headerAttributeSet },
     new AttributeSet[] { oddRowAttributeSet, evenRowAttributeSet }, new AttributeSet[] { headerAttributeSet } );
     
    // combine the two table formats
    TableFormat compositeTableFormat = TableFormats.createCompositeTableFormat( defaultTableFormat, tableStyle );
    
    // combine the result with the common table format
    compositeTableFormat = TableFormats.createCompositeTableFormat( compositeTableFormat, commonTableFormat );
    
    return new NamedTableFormat( "Blue Striped", compositeTableFormat );
  }

  static TableFormat createGrayGridTableFormat()
  {
    DefaultTableFormat defaultTableFormat = new DefaultTableFormat();
    
    // The table has a light blue border, white grid lines, and
    // a light gray background. Its contents are displayed in Arial size
    // 12.
    MutableAttributeSet tableAttributes = new SimpleAttributeSet();
    ReportStyleConstants.setBorderThickness( tableAttributes, 1.0 );
    Fill lightBlue = new DefaultFill( new Color( 153, 204, 255 ));
    ReportStyleConstants.setBorderFill( tableAttributes, lightBlue );
    ReportStyleConstants.setHorizontalGridThickness( tableAttributes, 1.0 );
    ReportStyleConstants.setHorizontalGridFill( tableAttributes, Fill.WHITE );
    ReportStyleConstants.setVerticalGridThickness( tableAttributes, 1.0 );
    ReportStyleConstants.setVerticalGridFill( tableAttributes, Fill.WHITE );
    ReportStyleConstants.setBackgroundFill( tableAttributes, Fill.LIGHT_GRAY );
    StyleConstants.setFontFamily( tableAttributes, "Arial" );
    StyleConstants.setFontSize( tableAttributes, 12 );
    defaultTableFormat.setTableAttributes( tableAttributes );
    
    // the header cells are alligned at the bottom and are in italics
    MutableAttributeSet headerAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setVerticalAlignment( headerAttributeSet, ReportStyleConstants.ALIGN_BOTTOM );
    StyleConstants.setItalic( headerAttributeSet, true );
    defaultTableFormat.setColumnCellAttributes( headerAttributeSet, DefaultTableFormat.HEADER );
    
    // combine this table format with the common table format
    TableFormat compositeTableFormat = TableFormats.createCompositeTableFormat( defaultTableFormat, commonTableFormat );
    
    return new NamedTableFormat( "Gray Grid", compositeTableFormat );
  }
  
  static TableFormat createGridTableFormat()
  {
    // The table itself has horizontal and vertical grid lines. Its
    // contents are displayed in Arial size 10.
    MutableAttributeSet gridAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setHorizontalGridThickness( gridAttributeSet, 1.0 );
    ReportStyleConstants.setVerticalGridThickness( gridAttributeSet, 1.0 );
    ReportStyleConstants.setBorderThickness( gridAttributeSet, 1.0 );
    StyleConstants.setFontFamily( gridAttributeSet, "Arial" );
    StyleConstants.setFontSize( gridAttributeSet, 10 );
    ReportStyleConstants.setAllCaps( gridAttributeSet, true );
    DefaultTableFormat defaultTableFormat = new DefaultTableFormat();
    defaultTableFormat.setTableAttributes( gridAttributeSet );
    
    // the header and footer is size 11
    MutableAttributeSet headerAttributes = new SimpleAttributeSet();
    StyleConstants.setFontSize( headerAttributes, 11 );
    defaultTableFormat.setRowAttributes( headerAttributes, DefaultTableFormat.HEADER );
    defaultTableFormat.setRowAttributes( headerAttributes, DefaultTableFormat.FOOTER );
    
    // combine the result with the common table format
    TableFormat compositeTableFormat = TableFormats.createCompositeTableFormat( defaultTableFormat, commonTableFormat );
    
    return new NamedTableFormat( "Grid", compositeTableFormat );
  }

  static TableFormat createModestTableFormat()
  {
    // header rows are in italics and header cells are centered
    // vertically and horizontally
    MutableAttributeSet headerAttributes = new SimpleAttributeSet();
    StyleConstants.setItalic( headerAttributes, true );
    ReportStyleConstants.setVerticalAlignment( headerAttributes, StyleConstants.ALIGN_CENTER );
    ReportStyleConstants.setHorizontalAlignment( headerAttributes, StyleConstants.ALIGN_CENTER );
    DefaultTableFormat defaultTableFormat = new DefaultTableFormat();
    defaultTableFormat.setRowAttributes( headerAttributes, DefaultTableFormat.HEADER );

    // the footer row has a top border
    MutableAttributeSet footerAttributes = new SimpleAttributeSet();
    ReportStyleConstants.setTopBorderThickness( footerAttributes, 1.0 );
    defaultTableFormat.setRowAttributes( footerAttributes, DefaultTableFormat.FOOTER );
    
    // combine this table format with the common table format
    TableFormat compositeTableFormat = TableFormats.createCompositeTableFormat( defaultTableFormat, commonTableFormat );
    
    return new NamedTableFormat( "Modest", compositeTableFormat );
  }

  static TableFormat createPartialGridTableFormat()
  {
    // the header rows have a bottom border
    MutableAttributeSet headerRowStyle = new SimpleAttributeSet();
    ReportStyleConstants.setBottomBorderThickness( headerRowStyle, 2.0 );
    
    // the footer row has a top border
    MutableAttributeSet footerRowStyle = new SimpleAttributeSet();
    ReportStyleConstants.setTopBorderThickness( footerRowStyle, 2.0 );
    
    // all columns but the last one has a right border
    MutableAttributeSet columnStyle = new SimpleAttributeSet();
    ReportStyleConstants.setRightBorderThickness( columnStyle, 2.0 );
    
    // column 0 is bold
    MutableAttributeSet column0Style = new SimpleAttributeSet();
    column0Style.setResolveParent( columnStyle );
    StyleConstants.setBold( column0Style, true );
    
    // create a table format based on rows
    TableFormat tableStyle = TableFormats.createRowOrientedTableFormat( new AttributeSet[] { headerRowStyle }, null, new AttributeSet[] { footerRowStyle } );
    
    // create a table format based on columns
    TableFormat tableStyle2 = TableFormats.createColumnOrientedTableFormat( new AttributeSet[] { column0Style }, new AttributeSet[] { columnStyle }, null );
    
    // combine the two table formats
    tableStyle = TableFormats.createCompositeTableFormat( tableStyle, tableStyle2 );
    
    // combine the result with the common table format
    TableFormat compositeTableFormat = TableFormats.createCompositeTableFormat( tableStyle, commonTableFormat );
    
    return new NamedTableFormat( "Partial Grid", compositeTableFormat );
  }

  static TableFormat createStripedGridTableFormat()
  {
    Fill green = new DefaultFill( new Color( 0, 153, 102 ));
    Fill orange = new DefaultFill( new Color( 255, 204, 153 ));
    
    // The table itself has a thin black border and
    // black grid lines. Its contents are rendered in Arial size 10.
    MutableAttributeSet tableAttributes = new SimpleAttributeSet();
    ReportStyleConstants.setBorderThickness( tableAttributes, 1.0 );
    StyleConstants.setFontFamily( tableAttributes, "Arial" );
    StyleConstants.setFontSize( tableAttributes, 10 );
    ReportStyleConstants.setHorizontalGridThickness( tableAttributes, 1.0 );
    ReportStyleConstants.setVerticalGridThickness( tableAttributes, 1.0 );
    DefaultTableFormat defaultTableFormat = new DefaultTableFormat();
    defaultTableFormat.setTableAttributes( tableAttributes );

    // the header and footer cells are white on green, size 12
    MutableAttributeSet headerAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setBackgroundFill( headerAttributeSet, green );
    StyleConstants.setForeground( headerAttributeSet, Color.white );
    StyleConstants.setFontSize( headerAttributeSet, 12 );
    ReportStyleConstants.setVerticalAlignment( headerAttributeSet, StyleConstants.ALIGN_CENTER );
    defaultTableFormat.setColumnCellAttributes( headerAttributeSet, DefaultTableFormat.HEADER );
    defaultTableFormat.setColumnCellAttributes( headerAttributeSet, DefaultTableFormat.FOOTER );
    
    // odd columns have a white background
    MutableAttributeSet oddColumnAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setBackgroundFill( oddColumnAttributeSet, Fill.WHITE );
    
    // even columns have a orange background
    MutableAttributeSet evenColumnAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setBackgroundFill( evenColumnAttributeSet, orange );
    
    // create the column-oriented table format
    TableFormat tableStyle = TableFormats.createColumnOrientedTableFormat( new AttributeSet[] { evenColumnAttributeSet },
     new AttributeSet[] { oddColumnAttributeSet, evenColumnAttributeSet }, new AttributeSet[] { evenColumnAttributeSet } );
    
    // combine the two table formats
    TableFormat compositeTableFormat = TableFormats.createCompositeTableFormat( defaultTableFormat, tableStyle );
    
    // combine the result with the common table format
    compositeTableFormat = TableFormats.createCompositeTableFormat( compositeTableFormat, commonTableFormat );
    
    return new NamedTableFormat( "Striped Grid", compositeTableFormat );
  }
  
  static TableFormat createYellowStripedTableFormat()
  {
    MutableAttributeSet headerAttributes = new SimpleAttributeSet();
    StyleConstants.setForeground( headerAttributes, Color.white );
    
    // the header has a vertical, cyclic gradient over two rows
    MutableAttributeSet firstHeaderRowStyle = new SimpleAttributeSet();
    firstHeaderRowStyle.setResolveParent( headerAttributes );
    ReportStyleConstants.setBackgroundFill( firstHeaderRowStyle, new DefaultFill( pink, pink, SwingConstants.SOUTH ));
    ReportStyleConstants.setBottomBorderFill( firstHeaderRowStyle, Fill.WHITE );
    ReportStyleConstants.setBottomBorderThickness( firstHeaderRowStyle, 1.0f );
    MutableAttributeSet secondHeaderRowStyle = new SimpleAttributeSet();
    secondHeaderRowStyle.setResolveParent( headerAttributes );
    ReportStyleConstants.setBackgroundFill( secondHeaderRowStyle, new DefaultFill( pink, pink, SwingConstants.SOUTH ));
    ReportStyleConstants.setBottomBorderFill( secondHeaderRowStyle, new DefaultFill( new Color( 102, 204, 204 )));
    ReportStyleConstants.setBottomBorderThickness( secondHeaderRowStyle, 2.0f );
    
    // even-numbered rows have a yellow background
    MutableAttributeSet evenRowAttributeSet = new SimpleAttributeSet();
    ReportStyleConstants.setBackgroundFill( evenRowAttributeSet, yellow );
    
    // the footer has a vertical gradient
    MutableAttributeSet footerRowStyle = new SimpleAttributeSet();
    ReportStyleConstants.setBackgroundFill( footerRowStyle, new DefaultFill( pink, pink, SwingConstants.SOUTH ));
    StyleConstants.setForeground( footerRowStyle, Color.white );
    StyleConstants.setBold( footerRowStyle, true );
    ReportStyleConstants.setTopBorderFill( footerRowStyle, new DefaultFill( new Color( 102, 204, 204 )));
    ReportStyleConstants.setTopBorderThickness( footerRowStyle, 2.0f );
    
    TableFormat tableStyle = TableFormats.createRowOrientedTableFormat(
      new AttributeSet[] { firstHeaderRowStyle, secondHeaderRowStyle },
      new AttributeSet[] { null, evenRowAttributeSet },
      new AttributeSet[] { footerRowStyle } );
      
    // combine the result with the common table format
    TableFormat compositeTableFormat = TableFormats.createCompositeTableFormat( tableStyle, commonTableFormat );
    
    return new NamedTableFormat( "Yellow Striped", compositeTableFormat );
  }
}


/**
 * An element renderer that displays Boolean values with
 * a check box.
 */
class CheckBoxElementRenderer implements ElementRenderer
{
  // if the value is null, we'll use a blank label; otherwise, we'll
  // use a checkbox
  static final private Component BLANK = new JLabel();
  private JCheckBox checkBox = new JCheckBox();

  public CheckBoxElementRenderer()
  {
    super();
    checkBox.setHorizontalAlignment( JLabel.CENTER );
    checkBox.setOpaque( false );
  }

  public Component getElementRendererComponent(JReportPane reportPane, Element element)
  {
    Boolean object = (Boolean)element.getObject();
    
    // don't use a checkbox if null
    if( object == null )
      return BLANK;
    
    // initialize the checkbox appropriately
    boolean selected = object != null && object.booleanValue();
    checkBox.setSelected( selected );
    return checkBox;
  }
}


/**
 * A named table format that delegates to another table format.
 * The toString method returns the name so that table formats
 * can be easily placed in comboboxes.
 */
class NamedTableFormat implements TableFormat
{
  private String name;
  private TableFormat tableFormat;

  NamedTableFormat( String name, TableFormat tableFormat )
  {
    this.name = name;
    this.tableFormat = tableFormat;
  }

  public AttributeSet getCellAttributes(CellElement cellElement)
  {
    return tableFormat.getCellAttributes( cellElement );
  }

  public AttributeSet getTableAttributes(TableElement tableElement)
  {
    return tableFormat.getTableAttributes( tableElement );
  }

  public AttributeSet getTierAttributes(TierElement tierElement)
  {
    return tableFormat.getTierAttributes( tierElement );
  }

  public String toString()
  {
    return name;
  }
}