private void generateReport(Date startDate, Date endDate, CategoryRecord category,
                                int sortMode) throws ReportException {

        //
        // Create the report data model
        //
        ReportModel reportModel = new TransactionModel(startDate, endDate, category, sortMode);
        
        //
        // Create the list of loan account names
        //
        List<String> loanAccountNames = new ArrayList<>(10);
        for (AccountRecord a : AccountRecord.accounts) {
            if (a.getType() == AccountRecord.LOAN)
                loanAccountNames.add("["+a.getName()+"]");
        }

        //
        // Create the report
        //
        Report report = new Report("Transaction Report", reportModel);
        ReportState reportState = report.getState();
        ReportGroup defaultGroup = reportState.getGroup(reportState.getGroupCount()-1);

        //
        // Highlight the column headers
        //
        defaultGroup.getHeader().setBackgroundColor(new Color(235, 235, 235));
        defaultGroup.getHeader().setBorderColor(Color.BLACK);

        //
        // Set the page format using 8.5x11 paper in landscape mode with 1/2" side
        // margins and 1/4" top margins.
        //
        // All measurements are in points (a point is 1/72 of an inch).  Report element
        // coordinates are relative to the imageable area.  The printWidth and printHeight
        // variables refer to the rotated page, thus printWidth is along the paper y-axis
        // and printHeight is along the paper x-axis.
        //
        int paperWidth = 612;
        int paperHeight = 792;
        int topMargin = 36;
        int leftMargin = 18;
        int printWidth = paperHeight - 2*topMargin;
        int printHeight = paperWidth - 2*leftMargin;

        Paper paper = new Paper();
        paper.setSize(paperWidth, paperHeight);
        paper.setImageableArea(leftMargin, topMargin, printHeight, printWidth);

        PageFormat pageFormat = new PageFormat();
        pageFormat.setPaper(paper);
        pageFormat.setOrientation(PageFormat.LANDSCAPE);
        reportState.setPageFormat(pageFormat);

        //
        // Create the report fonts
        //
        Font plainFont = new Font("SansSerif", Font.PLAIN, 10);
        Font boldFont = new Font("SansSerif", Font.BOLD, 10);
        reportState.setDefaultFont(plainFont);
        ReportLabel label;
        ReportField field;

        //
        // Create the page header containing the report title
        //
        label = new ReportLabel(String.format("Transaction Report for %s to %s",
                                              Main.getDateString(startDate),
                                              Main.getDateString(endDate)));
        label.setFont(boldFont);
        label.setBounds(new Rectangle(0, 0, printWidth, 25));
        label.setHorizontalAlignment(ReportElement.CENTER_ALIGNMENT);
        label.setVerticalAlignment(ReportElement.TOP_ALIGNMENT);
        reportState.getPageHeader().addElement(label);

        //
        // Create the page footer containing the page number
        //
        reportState.addExpression(new PageFunction("Page Number"));

        label = new ReportLabel("Page");
        label.setFont(boldFont);
        label.setBounds(new Rectangle(printWidth/2-30, 0, 25, 25));
        label.setHorizontalAlignment(ReportElement.RIGHT_ALIGNMENT);
        label.setVerticalAlignment(ReportElement.BOTTOM_ALIGNMENT);
        reportState.getPageFooter().addElement(label);

        field = new ReportField("Page Number");
        field.setFont(boldFont);
        field.setBounds(new Rectangle(printWidth/2, 0, 25, 25));
        field.setHorizontalAlignment(ReportElement.LEFT_ALIGNMENT);
        field.setVerticalAlignment(ReportElement.BOTTOM_ALIGNMENT);
        reportState.getPageFooter().addElement(field);

        //
        // Create the report footer containing the transaction total
        //
        AmountSumFunction totalFunction = new AmountSumFunction("Report Total");
        totalFunction.setField(columnNames[6]);
        totalFunction.setTransferAccountField(columnNames[4]);
        totalFunction.setLoanAccountNames(loanAccountNames);
        reportState.addExpression(totalFunction);

        label = new ReportLabel("Income/Expense Total");
        label.setFont(boldFont);
        label.setBounds(new Rectangle(elementPositions[6]-145, 0, 140, 12));
        label.setHorizontalAlignment(ReportElement.RIGHT_ALIGNMENT);
        reportState.getReportFooter().addElement(label);

        field = new ReportField("Report Total");
        field.setFont(boldFont);
        field.setBounds(new Rectangle(elementPositions[6], 0, elementSizes[6], 12));
        field.setHorizontalAlignment(ReportElement.RIGHT_ALIGNMENT);
        field.setRenderer(new ReportAmountRenderer());
        reportState.getReportFooter().addElement(field);

        //
        // Create the sort group footer containing the group subtotal
        //
        // We will not generate subtotals when sorting by date
        //
        if (sortMode != SORT_BY_DATE) {
            int fieldIndex;
            if (sortMode == SORT_BY_ACCOUNT)
                fieldIndex = 2;
            else if (sortMode == SORT_BY_NAME)
                fieldIndex = 3;
            else
                fieldIndex = 4;

            ReportGroup sortGroup = new ReportGroup("Sort Group", columnNames[fieldIndex]);
            reportState.addGroup(sortGroup);

            AmountSumFunction subtotalFunction = new AmountSumFunction("Group Total");
            subtotalFunction.setGroupName("Sort Group");
            subtotalFunction.setField(columnNames[6]);
            subtotalFunction.setTransferAccountField(columnNames[4]);
            reportState.addExpression(subtotalFunction);

            label = new ReportLabel("Income/Expense Subtotal");
            label.setFont(boldFont);
            label.setBounds(new Rectangle(elementPositions[6]-166, 0, 160, 12));
            label.setHorizontalAlignment(ReportElement.RIGHT_ALIGNMENT);
            sortGroup.getFooter().addElement(label);

            field = new ReportField("Group Total");
            field.setFont(boldFont);
            field.setBounds(new Rectangle(elementPositions[6], 0, elementSizes[6], 12));
            field.setHorizontalAlignment(ReportElement.RIGHT_ALIGNMENT);
            field.setRenderer(new ReportAmountRenderer());
            sortGroup.getFooter().addElement(field);

            label = new ReportLabel(" ");
            label.setBounds(new Rectangle(0, 12, 7, 12));
            sortGroup.getFooter().addElement(label);
        }

        //
        // Create the text elements for the report
        //
        for (int i=0; i<columnNames.length; i++) {

            //
            // Set the column header label
            //
            label = new ReportLabel(columnNames[i]);
            label.setBounds(new Rectangle(elementPositions[i], 0, elementSizes[i], 14));
            label.setFont(boldFont);
            label.setHorizontalAlignment(elementAlignments[i]);
            defaultGroup.getHeader().addElement(label);

            //
            // Set the column field
            //
            field = new ReportField(columnNames[i]);
            field.setBounds(new Rectangle(elementPositions[i], 0, elementSizes[i], 12));
            field.setHorizontalAlignment(elementAlignments[i]);
            if (i == 6)
                field.setRenderer(new ReportAmountRenderer());

            reportState.getRowBand().addElement(field);
        }

        //
        // Display the print preview dialog
        //
        report.showPreview(this);
    }