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; }