double QgsComposerTableV2::totalHeight() const { double height = 0; //loop through all existing frames to calculate how many rows are visible in each //as the entire height of a frame may not be utilised for content rows int rowsAlreadyShown = 0; int numberExistingFrames = frameCount(); int rowsVisibleInLastFrame = 0; double heightOfLastFrame = 0; for ( int idx = 0; idx < numberExistingFrames; ++idx ) { bool hasHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && idx == 0 ) || ( mHeaderMode == QgsComposerTableV2::AllFrames ) ); heightOfLastFrame = frame( idx )->rect().height(); rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, hasHeader ); rowsAlreadyShown += rowsVisibleInLastFrame; height += heightOfLastFrame; if ( rowsAlreadyShown >= mTableContents.length() ) { //shown entire contents of table, nothing remaining return height; } } if ( mResizeMode == QgsComposerMultiFrame::ExtendToNextPage ) { heightOfLastFrame = mComposition->paperHeight(); bool hasHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && numberExistingFrames < 1 ) || ( mHeaderMode == QgsComposerTableV2::AllFrames ) ); rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, hasHeader ); } //calculate how many rows left to show int remainingRows = mTableContents.length() - rowsAlreadyShown; if ( remainingRows <= 0 ) { //no remaining rows return height; } if ( rowsVisibleInLastFrame < 1 ) { //if no rows are visible in the last frame, calculation of missing frames //is impossible. So just return total height of existing frames return height; } //rows remain unshown -- how many extra frames would we need to complete the table? //assume all added frames are same size as final frame int numberFramesMissing = ceil(( double )remainingRows / ( double )rowsVisibleInLastFrame ); height += heightOfLastFrame * numberFramesMissing; return height; }
QPair<int, int> QgsLayoutTable::rowRange( const int frameIndex ) const { //calculate row height if ( frameIndex >= frameCount() ) { //bad frame index return qMakePair( 0, 0 ); } //loop through all previous frames to calculate how many rows are visible in each //as the entire height of a frame may not be utilized for content rows int rowsAlreadyShown = 0; for ( int idx = 0; idx < frameIndex; ++idx ) { rowsAlreadyShown += rowsVisible( idx, rowsAlreadyShown, false ); } //using zero based indexes int firstVisible = std::min( rowsAlreadyShown, mTableContents.length() ); int possibleRowsVisible = rowsVisible( frameIndex, rowsAlreadyShown, false ); int lastVisible = std::min( firstVisible + possibleRowsVisible, mTableContents.length() ); return qMakePair( firstVisible, lastVisible ); }
int QgsComposerTableV2::rowsVisible( const int frameIndex ) const { //get frame extent if ( frameIndex >= frameCount() ) { return 0; } QRectF frameExtent = frame( frameIndex )->extent(); bool includeHeader = false; if (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 ) || ( mHeaderMode == QgsComposerTableV2::AllFrames ) ) { includeHeader = true; } return rowsVisible( frameExtent.height(), includeHeader ); }
int QgsLayoutTable::rowsVisible( int frameIndex, int firstRow, bool includeEmptyRows ) const { //get frame extent if ( frameIndex >= frameCount() ) { return 0; } QRectF frameExtent = frame( frameIndex )->extent(); bool includeHeader = false; if ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 ) || ( mHeaderMode == QgsLayoutTable::AllFrames ) ) { includeHeader = true; } return rowsVisible( frameExtent.height(), firstRow, includeHeader, includeEmptyRows ); }
QPair< int, int > QgsComposerTableV2::rowRange( const QRectF extent, const int frameIndex ) const { //calculate row height if ( frameIndex >= frameCount() ) { //bad frame index return qMakePair( 0 , 0 ); } //loop through all previous frames to calculate how many rows are visible in each //as the entire height of a frame may not be utilised for content rows int rowsAlreadyShown = 0; for ( int idx = 0; idx < frameIndex; ++idx ) { rowsAlreadyShown += rowsVisible( idx ); } double headerHeight = 0; if (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 ) || ( mHeaderMode == QgsComposerTableV2::AllFrames ) ) { //frame has a header headerHeight = 2 * ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mHeaderFont ); } else { headerHeight = ( mShowGrid ? mGridStrokeWidth : 0 ); } //remaining height available for content rows double contentHeight = extent.height() - headerHeight; double rowHeight = ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mContentFont ); //using zero based indexes int firstVisible = qMin( rowsAlreadyShown, mTableContents.length() ); int rowsVisible = qMax( floor( contentHeight / rowHeight ), 0.0 ); int lastVisible = qMin( firstVisible + rowsVisible, mTableContents.length() ); return qMakePair( firstVisible, lastVisible ); }
void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const int frameIndex ) { if ( !p ) { return; } bool emptyTable = mTableContents.length() == 0; if ( emptyTable && mEmptyTableMode == QgsComposerTableV2::HideTable ) { //empty table set to hide table mode, so don't draw anything return; } //calculate which rows to show in this frame QPair< int, int > rowsToShow = rowRange( renderExtent, frameIndex ); if ( mComposition->plotStyle() == QgsComposition::Print || mComposition->plotStyle() == QgsComposition::Postscript ) { //exporting composition, so force an attribute refresh //we do this in case vector layer has changed via an external source (eg, another database user) refreshAttributes(); } p->save(); //antialiasing on p->setRenderHint( QPainter::Antialiasing, true ); p->setPen( Qt::SolidLine ); //now draw the text double currentX = ( mShowGrid ? mGridStrokeWidth : 0 ); double currentY; QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin(); int col = 0; double cellHeaderHeight = QgsComposerUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin; double cellBodyHeight = QgsComposerUtils::fontAscentMM( mContentFont ) + 2 * mCellMargin; QRectF cell; //calculate whether a header is required bool drawHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 ) || ( mHeaderMode == QgsComposerTableV2::AllFrames ) ); //calculate whether drawing table contents is required bool drawContents = !( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage ); for ( ; columnIt != mColumns.constEnd(); ++columnIt ) { currentY = ( mShowGrid ? mGridStrokeWidth : 0 ); currentX += mCellMargin; Qt::TextFlag textFlag = ( Qt::TextFlag )0; if (( *columnIt )->width() <= 0 ) { //automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text //which may slightly exceed the calculated width //if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width textFlag = Qt::TextDontClip; } if ( drawHeader ) { //draw the header cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight ); //calculate alignment of header Qt::AlignmentFlag headerAlign = Qt::AlignLeft; switch ( mHeaderHAlignment ) { case FollowColumn: headerAlign = ( *columnIt )->hAlignment(); break; case HeaderLeft: headerAlign = Qt::AlignLeft; break; case HeaderCenter: headerAlign = Qt::AlignHCenter; break; case HeaderRight: headerAlign = Qt::AlignRight; break; } QgsComposerUtils::drawText( p, cell, ( *columnIt )->heading(), mHeaderFont, mHeaderFontColor, headerAlign, Qt::AlignVCenter, textFlag ); currentY += cellHeaderHeight; currentY += ( mShowGrid ? mGridStrokeWidth : 0 ); } if ( drawContents ) { //draw the attribute values for ( int row = rowsToShow.first; row < rowsToShow.second; ++row ) { cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellBodyHeight ); QVariant cellContents = mTableContents.at( row ).at( col ); QString str = cellContents.toString(); QgsComposerUtils::drawText( p, cell, str, mContentFont, mContentFontColor, ( *columnIt )->hAlignment(), Qt::AlignVCenter, textFlag ); currentY += cellBodyHeight; currentY += ( mShowGrid ? mGridStrokeWidth : 0 ); } } currentX += mMaxColumnWidthMap[ col ]; currentX += mCellMargin; currentX += ( mShowGrid ? mGridStrokeWidth : 0 ); col++; } //and the borders if ( mShowGrid ) { int numberRowsToDraw = rowsToShow.second - rowsToShow.first; if ( mEmptyTableMode == QgsComposerTableV2::DrawEmptyCells ) { numberRowsToDraw = rowsVisible( frameIndex ); } bool mergeCells = false; if ( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage ) { //draw a merged row for the empty table message numberRowsToDraw++; mergeCells = true; } QPen gridPen; gridPen.setWidthF( mGridStrokeWidth ); gridPen.setColor( mGridColor ); gridPen.setJoinStyle( Qt::MiterJoin ); p->setPen( gridPen ); drawHorizontalGridLines( p, numberRowsToDraw, drawHeader ); drawVerticalGridLines( p, mMaxColumnWidthMap, numberRowsToDraw, drawHeader, mergeCells ); } //special case - no records and table is set to ShowMessage mode if ( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage ) { double messageX = ( mShowGrid ? mGridStrokeWidth : 0 ) + mCellMargin; double messageY = ( mShowGrid ? mGridStrokeWidth : 0 ) + ( drawHeader ? cellHeaderHeight + ( mShowGrid ? mGridStrokeWidth : 0 ) : 0 ); cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeight ); QgsComposerUtils::drawText( p, cell, mEmptyTableMessage, mContentFont, mContentFontColor, Qt::AlignHCenter, Qt::AlignVCenter, ( Qt::TextFlag )0 ); } p->restore(); }
void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &, const int frameIndex ) { bool emptyTable = mTableContents.length() == 0; if ( emptyTable && mEmptyTableMode == QgsLayoutTable::HideTable ) { //empty table set to hide table mode, so don't draw anything return; } if ( !mLayout->renderContext().isPreviewRender() ) { //exporting composition, so force an attribute refresh //we do this in case vector layer has changed via an external source (e.g., another database user) refreshAttributes(); } //calculate which rows to show in this frame QPair< int, int > rowsToShow = rowRange( frameIndex ); double gridSizeX = mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0; double gridSizeY = mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0; double cellHeaderHeight = QgsLayoutUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin; double cellBodyHeight = QgsLayoutUtils::fontAscentMM( mContentFont ) + 2 * mCellMargin; QRectF cell; //calculate whether a header is required bool drawHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 ) || ( mHeaderMode == QgsLayoutTable::AllFrames ) ); //calculate whether drawing table contents is required bool drawContents = !( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage ); int numberRowsToDraw = rowsToShow.second - rowsToShow.first; int numberEmptyRows = 0; if ( drawContents && mShowEmptyRows ) { numberRowsToDraw = rowsVisible( frameIndex, rowsToShow.first, true ); numberEmptyRows = numberRowsToDraw - rowsToShow.second + rowsToShow.first; } bool mergeCells = false; if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage ) { //draw a merged row for the empty table message numberRowsToDraw++; rowsToShow.second++; mergeCells = true; } QPainter *p = context.renderContext().painter(); p->save(); // painter is scaled to dots, so scale back to layout units p->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() ); //draw the text p->setPen( Qt::SolidLine ); double currentX = gridSizeX; double currentY = gridSizeY; if ( drawHeader ) { //draw the headers int col = 0; for ( const QgsLayoutTableColumn *column : qgis::as_const( mColumns ) ) { //draw background p->save(); p->setPen( Qt::NoPen ); p->setBrush( backgroundColor( -1, col ) ); p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellHeaderHeight ) ); p->restore(); currentX += mCellMargin; Qt::TextFlag textFlag = static_cast< Qt::TextFlag >( 0 ); if ( column->width() <= 0 ) { //automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text //which may slightly exceed the calculated width //if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width textFlag = Qt::TextDontClip; } cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight ); //calculate alignment of header Qt::AlignmentFlag headerAlign = Qt::AlignLeft; switch ( mHeaderHAlignment ) { case FollowColumn: headerAlign = column->hAlignment(); break; case HeaderLeft: headerAlign = Qt::AlignLeft; break; case HeaderCenter: headerAlign = Qt::AlignHCenter; break; case HeaderRight: headerAlign = Qt::AlignRight; break; } QgsLayoutUtils::drawText( p, cell, column->heading(), mHeaderFont, mHeaderFontColor, headerAlign, Qt::AlignVCenter, textFlag ); currentX += mMaxColumnWidthMap[ col ]; currentX += mCellMargin; currentX += gridSizeX; col++; } currentY += cellHeaderHeight; currentY += gridSizeY; } //now draw the body cells int rowsDrawn = 0; if ( drawContents ) { //draw the attribute values for ( int row = rowsToShow.first; row < rowsToShow.second; ++row ) { rowsDrawn++; currentX = gridSizeX; int col = 0; //calculate row height double rowHeight = mMaxRowHeightMap[row + 1] + 2 * mCellMargin; for ( const QgsLayoutTableColumn *column : qgis::as_const( mColumns ) ) { //draw background p->save(); p->setPen( Qt::NoPen ); p->setBrush( backgroundColor( row, col ) ); p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight ) ); p->restore(); // currentY = gridSize; currentX += mCellMargin; QVariant cellContents = mTableContents.at( row ).at( col ); QString str = cellContents.toString(); Qt::TextFlag textFlag = static_cast< Qt::TextFlag >( 0 ); if ( column->width() <= 0 && mWrapBehavior == TruncateText ) { //automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text //which may slightly exceed the calculated width //if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width textFlag = Qt::TextDontClip; } else if ( textRequiresWrapping( str, column->width(), mContentFont ) ) { str = wrappedText( str, column->width(), mContentFont ); } cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], rowHeight ); QgsLayoutUtils::drawText( p, cell, str, mContentFont, mContentFontColor, column->hAlignment(), column->vAlignment(), textFlag ); currentX += mMaxColumnWidthMap[ col ]; currentX += mCellMargin; currentX += gridSizeX; col++; } currentY += rowHeight; currentY += gridSizeY; } } if ( numberRowsToDraw > rowsDrawn ) { p->save(); p->setPen( Qt::NoPen ); //draw background of empty rows for ( int row = rowsDrawn; row < numberRowsToDraw; ++row ) { currentX = gridSizeX; int col = 0; if ( mergeCells ) { p->setBrush( backgroundColor( row + 10000, 0 ) ); p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeight ) ); } else { for ( QgsLayoutTableColumn *column : qgis::as_const( mColumns ) ) { Q_UNUSED( column ); //draw background //we use a bit of a hack here - since we don't want these extra blank rows to match the firstrow/lastrow rule, add 10000 to row number p->setBrush( backgroundColor( row + 10000, col ) ); p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeight ) ); // currentY = gridSize; currentX += mMaxColumnWidthMap[ col ] + 2 * mCellMargin; currentX += gridSizeX; col++; } } currentY += cellBodyHeight + gridSizeY; } p->restore(); } //and the borders if ( mShowGrid ) { QPen gridPen; gridPen.setWidthF( mGridStrokeWidth ); gridPen.setColor( mGridColor ); gridPen.setJoinStyle( Qt::MiterJoin ); p->setPen( gridPen ); if ( mHorizontalGrid ) { drawHorizontalGridLines( p, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader ); } if ( mVerticalGrid ) { drawVerticalGridLines( p, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells ); } } //special case - no records and table is set to ShowMessage mode if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage ) { double messageX = gridSizeX + mCellMargin; double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 ); cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeight ); QgsLayoutUtils::drawText( p, cell, mEmptyTableMessage, mContentFont, mContentFontColor, Qt::AlignHCenter, Qt::AlignVCenter, static_cast< Qt::TextFlag >( 0 ) ); } p->restore(); }
void QgsComposerTableV2::render( QPainter *p, const QRectF &, const int frameIndex ) { if ( !p ) { return; } bool emptyTable = mTableContents.length() == 0; if ( emptyTable && mEmptyTableMode == QgsComposerTableV2::HideTable ) { //empty table set to hide table mode, so don't draw anything return; } if ( mComposition->plotStyle() == QgsComposition::Print || mComposition->plotStyle() == QgsComposition::Postscript ) { //exporting composition, so force an attribute refresh //we do this in case vector layer has changed via an external source (eg, another database user) refreshAttributes(); } //calculate which rows to show in this frame QPair< int, int > rowsToShow = rowRange( frameIndex ); double gridSize = mShowGrid ? mGridStrokeWidth : 0; double cellHeaderHeight = QgsComposerUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin; double cellBodyHeight = QgsComposerUtils::fontAscentMM( mContentFont ) + 2 * mCellMargin; QRectF cell; //calculate whether a header is required bool drawHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 ) || ( mHeaderMode == QgsComposerTableV2::AllFrames ) ); //calculate whether drawing table contents is required bool drawContents = !( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage ); int numberRowsToDraw = rowsToShow.second - rowsToShow.first; int numberEmptyRows = 0; if ( drawContents && mShowEmptyRows ) { numberRowsToDraw = rowsVisible( frameIndex, rowsToShow.first, true ); numberEmptyRows = numberRowsToDraw - rowsToShow.second + rowsToShow.first; } bool mergeCells = false; if ( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage ) { //draw a merged row for the empty table message numberRowsToDraw++; rowsToShow.second++; mergeCells = true; } p->save(); //antialiasing on p->setRenderHint( QPainter::Antialiasing, true ); //draw the text p->setPen( Qt::SolidLine ); double currentX = gridSize; double currentY = gridSize; if ( drawHeader ) { //draw the headers int col = 0; Q_FOREACH ( const QgsComposerTableColumn* column, mColumns ) { //draw background p->save(); p->setPen( Qt::NoPen ); p->setBrush( backgroundColor( -1, col ) ); p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellHeaderHeight ) ); p->restore(); currentX += mCellMargin; Qt::TextFlag textFlag = static_cast< Qt::TextFlag >( 0 ); if ( column->width() <= 0 ) { //automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text //which may slightly exceed the calculated width //if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width textFlag = Qt::TextDontClip; } cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight ); //calculate alignment of header Qt::AlignmentFlag headerAlign = Qt::AlignLeft; switch ( mHeaderHAlignment ) { case FollowColumn: headerAlign = column->hAlignment(); break; case HeaderLeft: headerAlign = Qt::AlignLeft; break; case HeaderCenter: headerAlign = Qt::AlignHCenter; break; case HeaderRight: headerAlign = Qt::AlignRight; break; } QgsComposerUtils::drawText( p, cell, column->heading(), mHeaderFont, mHeaderFontColor, headerAlign, Qt::AlignVCenter, textFlag ); currentX += mMaxColumnWidthMap[ col ]; currentX += mCellMargin; currentX += gridSize; col++; } currentY += cellHeaderHeight; currentY += gridSize; }