Example #1
0
 void testBreaking()
 {
     QFETCH( qreals, widths );
     QFETCH( int, pages );
     QFETCH( ints, expectedResult );
     QFETCH( qreals, expectedWidthPerPage );
     KDReports::TableBreakingLogic logic;
     logic.setColumnWidths( widths.toVector() );
     logic.setPageCount( pages );
     const QVector<int> res = logic.columnsPerPage();
     if (res.toList() != expectedResult)
         qDebug() << "columnsPerPage:" << res.toList() << "expected" << expectedResult;
     QCOMPARE( res.toList(), expectedResult );
     const QVector<qreal> widthPerPage = logic.widthPerPage( res );
     if (widthPerPage.toList() != expectedWidthPerPage)
         qDebug() << "widthPerPage:" << widthPerPage.toList() << "expected" << expectedWidthPerPage;
     QCOMPARE( widthPerPage.toList(), expectedWidthPerPage );
 }
void KDReports::SpreadsheetReportLayout::ensureLayouted()
{
    if ( !m_layoutDirty )
        return;
    if ( m_pageContentSize.isEmpty() ) {
        qWarning( "No paper size specified!" );
        return;
    }

    m_tableLayout.setInitialFontScalingFactor( m_userRequestedFontScalingFactor );
    m_pageRects.clear();

    QAbstractItemModel* model = m_tableLayout.m_model;
    if ( !model )
        return;

    // Here's the whole layouting logic

    // Step 1: determine "ideal" column widths, based on contents

    m_tableLayout.updateColumnWidths();

    // Step 2: based on that and the number of horiz pages wanted,
    //         determine actual column widths (horizontal table breaking)

    KDReports::TableBreakingLogic optimizer;
    optimizer.setColumnWidths( m_tableLayout.m_columnWidths );
    optimizer.setPageCount( m_numHorizontalPages );
    const QVector<int> columnsPerPage = optimizer.columnsPerPage();
    QVector<qreal> widthPerPage = optimizer.widthPerPage( columnsPerPage );
    const int horizPages = columnsPerPage.count();
    bool scaled = false;

    // Step 3: check everything fits horizontally, otherwise calculate font scaling factor for this

    const qreal horizMargins = 0 /*m_leftMargin*/ + 0 /*m_rightMargin*/;
    const qreal verticalMargins = 0 /*m_topMargin*/ + 0 /*m_bottomMargin*/;
    const qreal usablePageWidth = m_pageContentSize.width() - horizMargins;
    const qreal usablePageHeight = m_pageContentSize.height() - verticalMargins - m_tableLayout.hHeaderHeight();

#ifdef DEBUG_LAYOUT
    qDebug() << "usablePageHeight=" << m_pageContentSize.height() << "minus hHeaderHeight" << m_tableLayout.hHeaderHeight();
#endif

    // for each page, if (sum of column widths) > usablePageWidth,
    // then we need to scale everything (font and padding)
    // by the ratio of those two numbers.
    qreal bestScalingFactor = 1000000;
    for ( int page = 0; page < horizPages; ++page ) {
        const qreal width = widthPerPage[page] + m_tableLayout.vHeaderWidth();
        if ( width > usablePageWidth ) {
            const qreal scalingFactor = usablePageWidth / width;
#ifdef DEBUG_LAYOUT
            qDebug() << "page" << page << "sum of column widths:" << width
                     << "usablePageWidth=" << usablePageWidth;
            qDebug() << "scaling factor so that it fits horizontally:" << scalingFactor;
#endif
            bestScalingFactor = qMin(bestScalingFactor, scalingFactor);
            scaled = true;
        }
    }

    if (scaled) {
        m_tableLayout.ensureScalingFactorForWidth( bestScalingFactor );
    }

    // Step 4: check everything fits vertically, otherwise calculate font scaling factor for this

    const int rowCount = m_tableLayout.m_model->rowCount();
    if ( m_numVerticalPages > 0 ) {
        const qreal rowHeight = m_tableLayout.rowHeight();

        // We can't do a global division of heights, it assumes rows can be over page borders, partially truncated
        // const qreal maxTotalHeight = m_numVerticalPages  * usablePageHeight;
        // const qreal maxRowHeight = maxTotalHeight / rowCount;

        // Example: 5 rows over 2 pages, and the usablePageHeight is 100. What do you do?
        // 2.5 rows per page means truncation. 2 rows per page (as in the division above) is not enough.
        // The right solution is qCeil, i.e. max 3 rows per page, and maxRowHeight = 100 / 3 = 33.3.

        const int maxRowsPerPage = qCeil( static_cast<qreal>(rowCount) / m_numVerticalPages );
        const qreal maxRowHeight = usablePageHeight / maxRowsPerPage;
#ifdef DEBUG_LAYOUT
        qDebug() << "usablePageHeight=" << usablePageHeight
                 << "rowHeight=" << rowHeight
                 << "maxRowsPerPage=" << maxRowsPerPage
                 << "maxRowHeight=" << usablePageHeight << "/" << maxRowsPerPage << "=" << maxRowHeight;
#endif
        if ( rowHeight > maxRowHeight ) { // more than authorized maximum
            m_tableLayout.ensureScalingFactorForHeight( maxRowHeight );
            scaled = true;
        }
    }

    // Step 5: update font and calculations based on final font scaling
    if (scaled) {
#ifdef DEBUG_LAYOUT
        qDebug() << "final scaling factor" << m_tableLayout.scalingFactor();
        qDebug() << "final fonts: cells:" << m_tableLayout.scaledFont().pointSizeF()
                << "hHeader:" << m_tableLayout.horizontalHeaderScaledFont().pointSizeF()
                << "vHeader:" << m_tableLayout.verticalHeaderScaledFont().pointSizeF();
#endif
        // With this new scaling factor [when step 4 changed the factor], what should be the column widths?
        // If we just call
        // m_tableLayout.updateColumnWidthsByFactor( m_tableLayout.scalingFactor() / m_userRequestedFontScalingFactor );
        // then we risk truncating column text (because fonts are not proportional).
        // Testcase: LongReport with font size 8, padding 3, 10 columns, 300 rows, and scaleTo(1,10) (or none);
        m_tableLayout.updateColumnWidths();

#ifdef DEBUG_LAYOUT
        qDebug() << "New total width:" << totalWidth();
#endif

#if 0 // not used right now, but could be useful, especially if we want to goto step 3 again, to resize down
        // Update the widthPerPage array
        int column = 0;
        for ( int page = 0; page < horizPages; ++page ) {
            const int numColumnsInPage = columnsPerPage[page];
            widthPerPage[page] = 0;
            for ( int col = column; col < column + numColumnsInPage; ++col) {
                widthPerPage[page] += m_tableLayout.m_columnWidths[col];
            }

            const qreal width = widthPerPage[page] + m_tableLayout.vHeaderWidth();
            if ( width > usablePageWidth ) {
                qWarning() << "Too much width on page" << page;
            }

            column += numColumnsInPage;
        }
        qDebug() << "widthPerPage:" << widthPerPage;
#endif
    }

    const qreal rowHeight = m_tableLayout.rowHeight(); // do it now so that the scaling is included

    // Step 6: determine number of pages for all rows to fit

    const int maxRowsPerPage = qFloor(usablePageHeight / rowHeight); // no qCeil here, the last row would be truncated...
    int verticPages = qCeil( qreal( rowCount ) / qreal( maxRowsPerPage ) );

#ifdef DEBUG_LAYOUT
    qDebug() << "maxRowsPerPage=" << usablePageHeight << "/" << rowHeight << "=" <<  maxRowsPerPage;
    qDebug() << "pages:" << horizPages << "x" << verticPages;
    qDebug() << "verticPages = qCeil(" << rowCount << "/" << maxRowsPerPage << ") =" << verticPages;
#endif

    // avoid rounding problems (or the font not zooming down enough vertically),
    // obey m_numVerticalPages in all cases
    if ( m_numVerticalPages > 0 ) {
        Q_ASSERT( verticPages <= m_numVerticalPages );
        //    verticPages = qMin( m_numVerticalPages, verticPages );
    }

    // Step 7: now we can record all this in terms of cell areas

    if ( m_tableBreakingPageOrder == Report::RightThenDown ) {
        //qDebug() << "Doing right then down layout";
        int row = 0;
        for ( int y = 0; y < verticPages; ++y ) {
            int column = 0;
            const int numRowsInPage = qMin( maxRowsPerPage, rowCount - row );
            for ( int x = 0; x < horizPages; ++x ) {
                const int numColumnsInPage = columnsPerPage[x];
                m_pageRects.append( QRect( column, row, numColumnsInPage, numRowsInPage ) );
                column += numColumnsInPage;
            }
            row += maxRowsPerPage;
        }
    } else {
        //qDebug() << "Doing down then right layout";
        int column = 0;
        for ( int x = 0; x < horizPages; ++x ) {
            int row = 0;
            const int numColumnsInPage = columnsPerPage[x];
            for ( int y = 0; y < verticPages; ++y ) {
                const int numRowsInPage = qMin( maxRowsPerPage, rowCount - row );
                m_pageRects.append( QRect( column, row, numColumnsInPage, numRowsInPage ) );
                row += maxRowsPerPage;
            }
            column += numColumnsInPage;
        }
    }

    m_layoutDirty = false;
}