[composer] Add proper handling of multiline text in attribute tables

Previously multi-line text would overflow onto neighbouring cells.
Now rows will be expanded to fit required height of text.
Fix #10273.

Sponsored by City of Uster
This commit is contained in:
Nyall Dawson 2015-08-16 19:43:58 +10:00
parent 29cc0642a4
commit 372534eb89
6 changed files with 452 additions and 94 deletions

View File

@ -322,6 +322,11 @@ class QgsComposerTableV2: QgsComposerMultiFrame
*/
virtual bool calculateMaxColumnWidths();
/** Calculates the maximum height of text shown in rows.
* @note added in QGIS 2.12
*/
virtual bool calculateMaxRowHeights();
/** Returns total width of table contents.
* @returns table width
* @see totalHeight
@ -333,13 +338,86 @@ class QgsComposerTableV2: QgsComposerMultiFrame
* @returns total height
* @see totalWidth
*/
double totalHeight() const;
//not const, as needs to call calculateMaxRowHeights()
double totalHeight();
/** Calculates how many content rows would be visible within a frame of the specified
* height.
* @param frameHeight height of frame
* @param firstRow index of first row visible in frame (where 0 = first row in table)
* @param includeHeader set to true if frame would include a header row
* @param includeEmptyRows set to true to also include rows which would be empty in the returned count. For instance,
* if the frame would include all table content rows and have space left for extra rows then setting this parameter
* to true would also include a count of these extra blank rows.
* @returns number of visible content rows (excluding header row)
* @note added in QGIS 2.12
*/
int rowsVisible( double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const;
/** Calculates how many content rows are visible within a given frame.
* @param frameIndex index number for frame
* @param firstRow index of first row visible in frame (where 0 = first row in table)
* @param includeEmptyRows set to true to also include rows which would be empty in the returned count. For instance,
* if the frame would include all table content rows and have space left for extra rows then setting this parameter
* to true would also include a count of these extra blank rows.
* @returns number of visible content rows (excludes header rows)
* @note added in QGIS 2.12
*/
int rowsVisible( int frameIndex, int firstRow, bool includeEmptyRows ) const;
/** Calculates a range of rows which should be visible in a given frame.
* @param frameIndex index number for frame
* @returns row range
* @note added in QGIS 2.12
*/
QPair<int, int> rowRange( int frameIndex ) const;
/** Draws the horizontal grid lines for the table.
* @param painter destination painter for grid lines
* @param firstRow index corresponding to first row shown in frame
* @param lastRow index corresponding to last row shown in frame. If greater than the number of content rows in the
* table, then the default row height will be used for the remaining rows.
* @param drawHeaderLines set to true to include for the table header
* @see drawVerticalGridLines
* @note added in QGIS 2.12
*/
void drawHorizontalGridLines( QPainter* painter, int firstRow, int lastRow, bool drawHeaderLines ) const;
/** Draws the vertical grid lines for the table.
* @param painter destination painter for grid lines
* @param maxWidthMap QMap of int to double, where the int contains the column number and the double is the
* maximum width of text present in the column.
* @param firstRow index corresponding to first row shown in frame
* @param lastRow index corresponding to last row shown in frame. If greater than the number of content rows in the
* table, then the default row height will be used for the remaining rows.
* @param hasHeader set to true if table frame includes header cells
* @param mergeCells set to true to merge table content cells
* @note not available in python bindings
* @see drawVerticalGridLines
* @see calculateMaxColumnWidths
* @note not available in python bindings
* @note added in QGIS 2.12
*/
//void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells = false ) const;
/** Recalculates and updates the size of the table and all table frames.
*/
void recalculateTableSize();
/** Checks whether a table contents contains a given row
* @param contents table contents to check
* @param row row to check for
* @returns true if contents contains rows
*/
bool contentsContainsRow( const QgsComposerTableContents &contents, const QgsComposerTableRow &row ) const;
//deprecated methods
/** Calculates how many content rows are visible within a given frame
* @param frameIndex index number for frame
* @returns number of visible content rows (excludes header rows)
*/
int rowsVisible( const int frameIndex ) const;
int rowsVisible( const int frameIndex ) const /Deprecated/;
/** Calculates how many content rows would be visible within a specified
* height.
@ -347,7 +425,7 @@ class QgsComposerTableV2: QgsComposerMultiFrame
* @param includeHeader set to true if frame would include a header row
* @returns number of visible content rows (excluding header row)
*/
int rowsVisible( const double frameHeight, const bool includeHeader ) const;
int rowsVisible( const double frameHeight, const bool includeHeader ) const /Deprecated/;
/** Calculates a range of rows which should be visible in a given
* frame extent.
@ -355,7 +433,7 @@ class QgsComposerTableV2: QgsComposerMultiFrame
* @param frameIndex index number for frame
* @returns row range
*/
QPair<int, int> rowRange( const QRectF &extent, const int frameIndex ) const;
QPair<int, int> rowRange( const QRectF &extent, const int frameIndex ) const /Deprecated/;
/** Draws the horizontal grid lines for the table.
* @param painter destination painter for grid lines
@ -363,7 +441,7 @@ class QgsComposerTableV2: QgsComposerMultiFrame
* @param drawHeaderLines set to true to include for the table header
* @see drawVerticalGridLines
*/
void drawHorizontalGridLines( QPainter* painter, const int rows, const bool drawHeaderLines ) const;
void drawHorizontalGridLines( QPainter* painter, const int rows, const bool drawHeaderLines ) const /Deprecated/;
/** Draws the vertical grid lines for the table.
* @param painter destination painter for grid lines
@ -377,17 +455,7 @@ class QgsComposerTableV2: QgsComposerMultiFrame
* @see calculateMaxColumnWidths
* @note not available in python bindings
*/
// void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells = false ) const;
// void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells = false ) const /Deprecated/;
/** Recalculates and updates the size of the table and all table frames.
*/
void recalculateTableSize();
/** Checks whether a table contents contains a given row
* @param contents table contents to check
* @param row row to check for
* @returns true if contents contains rows
*/
bool contentsContainsRow( const QgsComposerTableContents &contents, const QgsComposerTableRow &row ) const;
};

View File

@ -183,7 +183,9 @@ int QgsComposerTableV2::rowsVisible( const int frameIndex ) const
{
includeHeader = true;
}
Q_NOWARN_DEPRECATED_PUSH
return rowsVisible( frameExtent.height(), includeHeader );
Q_NOWARN_DEPRECATED_POP
}
int QgsComposerTableV2::rowsVisible( const double frameHeight, const bool includeHeader ) const
@ -209,7 +211,62 @@ int QgsComposerTableV2::rowsVisible( const double frameHeight, const bool includ
return qMax( floor( contentHeight / rowHeight ), 0.0 );
}
QPair< int, int > QgsComposerTableV2::rowRange( const QRectF &extent, const int frameIndex ) const
int QgsComposerTableV2::rowsVisible( double frameHeight, int firstRow, bool includeHeader , bool includeEmptyRows ) const
{
//calculate header height
double headerHeight = 0;
if ( includeHeader )
{
//frame has a header
headerHeight = 2 * ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mHeaderFont );
}
else
{
//frame has no header text, just the stroke
headerHeight = ( mShowGrid ? mGridStrokeWidth : 0 );
}
//remaining height available for content rows
double contentHeight = frameHeight - headerHeight;
double gridHeight = ( mShowGrid ? mGridStrokeWidth : 0 );
int currentRow = firstRow;
while ( contentHeight > 0 && currentRow <= mTableContents.count() )
{
double currentRowHeight = mMaxRowHeightMap.value( currentRow + 1 ) + gridHeight + 2 * mCellMargin;
contentHeight -= currentRowHeight;
currentRow++;
}
if ( includeEmptyRows && contentHeight > 0 )
{
double rowHeight = ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mContentFont );
currentRow += qMax( floor( contentHeight / rowHeight ), 0.0 );
}
return currentRow - firstRow - 1;
}
int QgsComposerTableV2::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 == QgsComposerTableV2::FirstFrame && frameIndex < 1 )
|| ( mHeaderMode == QgsComposerTableV2::AllFrames ) )
{
includeHeader = true;
}
return rowsVisible( frameExtent.height(), firstRow, includeHeader, includeEmptyRows );
}
QPair<int, int> QgsComposerTableV2::rowRange( const int frameIndex ) const
{
//calculate row height
if ( frameIndex >= frameCount() )
@ -223,35 +280,25 @@ QPair< int, int > QgsComposerTableV2::rowRange( const QRectF &extent, const int
int rowsAlreadyShown = 0;
for ( int idx = 0; idx < frameIndex; ++idx )
{
rowsAlreadyShown += rowsVisible( idx );
rowsAlreadyShown += rowsVisible( idx, rowsAlreadyShown, false );
}
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() );
int possibleRowsVisible = rowsVisible( frameIndex, rowsAlreadyShown, false );
int lastVisible = qMin( firstVisible + possibleRowsVisible, mTableContents.length() );
return qMakePair( firstVisible, lastVisible );
}
QPair< int, int > QgsComposerTableV2::rowRange( const QRectF &extent, const int frameIndex ) const
{
Q_UNUSED( extent );
return rowRange( frameIndex );
}
void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const int frameIndex )
void QgsComposerTableV2::render( QPainter *p, const QRectF &, const int frameIndex )
{
if ( !p )
{
@ -274,7 +321,7 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
}
//calculate which rows to show in this frame
QPair< int, int > rowsToShow = rowRange( renderExtent, frameIndex );
QPair< int, int > rowsToShow = rowRange( frameIndex );
double gridSize = mShowGrid ? mGridStrokeWidth : 0;
double cellHeaderHeight = QgsComposerUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin;
@ -288,15 +335,18 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
bool drawContents = !( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage );
int numberRowsToDraw = rowsToShow.second - rowsToShow.first;
int numberEmptyRows = 0;
if ( drawContents && mShowEmptyRows )
{
numberRowsToDraw = rowsVisible( frameIndex );
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;
}
@ -311,8 +361,23 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
p->setPen( Qt::NoPen );
p->setBrush( QBrush( mBackgroundColor ) );
double totalHeight = ( drawHeader || ( numberRowsToDraw > 0 ) ? gridSize : 0 ) +
( drawHeader ? cellHeaderHeight + gridSize : 0.0 ) +
( drawContents ? numberRowsToDraw : 1 ) * ( cellBodyHeight + gridSize );
( drawHeader ? cellHeaderHeight + gridSize : 0.0 );
if ( drawContents )
{
for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
{
totalHeight += mMaxRowHeightMap[ row + 1 ] + 2 * mCellMargin + gridSize;
}
if ( numberEmptyRows > 0 )
{
//draw empty rows
totalHeight += ( cellBodyHeight + gridSize ) * numberEmptyRows;
}
}
else
{
totalHeight += cellBodyHeight + gridSize;
}
if ( totalHeight > 0 )
{
@ -384,6 +449,11 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
{
currentX = gridSize;
int col = 0;
//calculate row height
double rowHeight = mMaxRowHeightMap[row + 1] + 2 * mCellMargin;
for ( QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin(); columnIt != mColumns.constEnd(); ++columnIt )
{
// currentY = gridSize;
@ -398,7 +468,7 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
textFlag = Qt::TextDontClip;
}
cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellBodyHeight );
cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], rowHeight );
QVariant cellContents = mTableContents.at( row ).at( col );
QString str = cellContents.toString();
@ -410,7 +480,7 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
currentX += gridSize;
col++;
}
currentY += cellBodyHeight;
currentY += rowHeight;
currentY += gridSize;
}
}
@ -423,8 +493,8 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
gridPen.setColor( mGridColor );
gridPen.setJoinStyle( Qt::MiterJoin );
p->setPen( gridPen );
drawHorizontalGridLines( p, numberRowsToDraw, drawHeader );
drawVerticalGridLines( p, mMaxColumnWidthMap, numberRowsToDraw, drawHeader, mergeCells );
drawHorizontalGridLines( p, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
drawVerticalGridLines( p, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
}
//special case - no records and table is set to ShowMessage mode
@ -675,6 +745,7 @@ QSizeF QgsComposerTableV2::minFrameSize( const int frameIndex ) const
void QgsComposerTableV2::refreshAttributes()
{
mMaxColumnWidthMap.clear();
mMaxRowHeightMap.clear();
mTableContents.clear();
//get new contents
@ -694,43 +765,122 @@ bool QgsComposerTableV2::calculateMaxColumnWidths()
{
mMaxColumnWidthMap.clear();
//first, go through all the column headers and calculate the max width values
//total number of cells (rows + 1 for header)
int cols = mColumns.count();
int cells = cols * ( mTableContents.count() + 1 );
QVector< double > widths( cells );
//first, go through all the column headers and calculate the sizes
QgsComposerTableColumns::const_iterator columnIt = mColumns.constBegin();
int col = 0;
for ( ; columnIt != mColumns.constEnd(); ++columnIt )
{
double width = 0;
if (( *columnIt )->width() > 0 )
{
//column has manually specified width
width = ( *columnIt )->width();
widths[col] = ( *columnIt )->width();
}
else if ( mHeaderMode != QgsComposerTableV2::NoHeaders )
{
width = QgsComposerUtils::textWidthMM( mHeaderFont, ( *columnIt )->heading() );
widths[col] = QgsComposerUtils::textWidthMM( mHeaderFont, ( *columnIt )->heading() );
}
else
{
widths[col] = 0.0;
}
mMaxColumnWidthMap.insert( col, width );
col++;
}
//next, go through all the table contents and calculate the max width values
//next, go through all the table contents and calculate the sizes
QgsComposerTableContents::const_iterator rowIt = mTableContents.constBegin();
double currentCellTextWidth;
int row = 1;
for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
{
QgsComposerTableRow::const_iterator colIt = rowIt->constBegin();
int columnNumber = 0;
col = 0;
for ( ; colIt != rowIt->constEnd(); ++colIt )
{
if ( mColumns.at( columnNumber )->width() <= 0 )
if ( mColumns.at( col )->width() <= 0 )
{
//column width set to automatic, so check content size
currentCellTextWidth = QgsComposerUtils::textWidthMM( mContentFont, ( *colIt ).toString() );
mMaxColumnWidthMap[ columnNumber ] = qMax( currentCellTextWidth, mMaxColumnWidthMap[ columnNumber ] );
QStringList multiLineSplit = ( *colIt ).toString().split( "\n" );
currentCellTextWidth = 0;
Q_FOREACH ( QString line, multiLineSplit )
{
currentCellTextWidth = qMax( currentCellTextWidth, QgsComposerUtils::textWidthMM( mContentFont, line ) );
}
widths[ row * cols + col ] = currentCellTextWidth;
}
columnNumber++;
else
{
widths[ row * cols + col ] = 0;
}
col++;
}
row++;
}
//calculate maximum
for ( int col = 0; col < cols; ++col )
{
double maxColWidth = 0;
for ( int row = 0; row < mTableContents.count() + 1; ++row )
{
maxColWidth = qMax( widths[ row * cols + col ], maxColWidth );
}
mMaxColumnWidthMap.insert( col, maxColWidth );
}
return true;
}
bool QgsComposerTableV2::calculateMaxRowHeights()
{
mMaxRowHeightMap.clear();
//total number of cells (rows + 1 for header)
int cols = mColumns.count();
int cells = cols * ( mTableContents.count() + 1 );
QVector< double > heights( cells );
//first, go through all the column headers and calculate the sizes
QgsComposerTableColumns::const_iterator columnIt = mColumns.constBegin();
int col = 0;
for ( ; columnIt != mColumns.constEnd(); ++columnIt )
{
//height
heights[col] = mHeaderMode != QgsComposerTableV2::NoHeaders ? QgsComposerUtils::textHeightMM( mHeaderFont, ( *columnIt )->heading() ) : 0;
col++;
}
//next, go through all the table contents and calculate the sizes
QgsComposerTableContents::const_iterator rowIt = mTableContents.constBegin();
int row = 1;
for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
{
QgsComposerTableRow::const_iterator colIt = rowIt->constBegin();
col = 0;
for ( ; colIt != rowIt->constEnd(); ++colIt )
{
//height
heights[ row * cols + col ] = QgsComposerUtils::textHeightMM( mContentFont, ( *colIt ).toString() );
col++;
}
row++;
}
//calculate maximum
for ( int row = 0; row < mTableContents.count() + 1; ++row )
{
double maxRowHeight = 0;
for ( int col = 0; col < cols; ++col )
{
maxRowHeight = qMax( heights[ row * cols + col ], maxRowHeight );
}
mMaxRowHeightMap.insert( row, maxRowHeight );
}
return true;
@ -757,8 +907,14 @@ double QgsComposerTableV2::totalWidth()
return totalWidth;
}
double QgsComposerTableV2::totalHeight() const
double QgsComposerTableV2::totalHeight()
{
//check how much space each row needs
if ( !calculateMaxRowHeights() )
{
return 0;
}
double height = 0;
//loop through all existing frames to calculate how many rows are visible in each
@ -772,7 +928,7 @@ double QgsComposerTableV2::totalHeight() const
bool hasHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && idx == 0 )
|| ( mHeaderMode == QgsComposerTableV2::AllFrames ) );
heightOfLastFrame = frame( idx )->rect().height();
rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, hasHeader );
rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
rowsAlreadyShown += rowsVisibleInLastFrame;
height += heightOfLastFrame;
if ( rowsAlreadyShown >= mTableContents.length() )
@ -782,14 +938,6 @@ double QgsComposerTableV2::totalHeight() const
}
}
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;
@ -799,28 +947,52 @@ double QgsComposerTableV2::totalHeight() const
return height;
}
if ( rowsVisibleInLastFrame < 1 )
if ( mResizeMode == QgsComposerMultiFrame::ExtendToNextPage )
{
//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;
heightOfLastFrame = mComposition->paperHeight();
}
bool hasHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && numberExistingFrames < 1 )
|| ( mHeaderMode == QgsComposerTableV2::AllFrames ) );
int numberFramesMissing = 0;
while ( remainingRows > 0 )
{
numberFramesMissing++;
rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
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;
}
rowsAlreadyShown += rowsVisibleInLastFrame;
remainingRows = mTableContents.length() - rowsAlreadyShown;
}
//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;
}
void QgsComposerTableV2::drawHorizontalGridLines( QPainter *painter, const int rows, const bool drawHeaderLines ) const
{
//hacky shortcut to maintain 2.10 API without adding code - whooo!
drawHorizontalGridLines( painter, 100000, 100000 + rows, drawHeaderLines );
}
void QgsComposerTableV2::drawHorizontalGridLines( QPainter *painter, int firstRow, int lastRow, bool drawHeaderLines ) const
{
//horizontal lines
if ( rows < 1 && !drawHeaderLines )
if ( lastRow - firstRow < 1 && !drawHeaderLines )
{
return;
}
double cellBodyHeight = QgsComposerUtils::fontAscentMM( mContentFont );
double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
double currentY = 0;
currentY = halfGridStrokeWidth;
@ -830,19 +1002,26 @@ void QgsComposerTableV2::drawHorizontalGridLines( QPainter *painter, const int r
currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
currentY += ( QgsComposerUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin );
}
for ( int row = 0; row < rows; ++row )
for ( int row = firstRow; row < lastRow; ++row )
{
painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
currentY += ( QgsComposerUtils::fontAscentMM( mContentFont ) + 2 * mCellMargin );
double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeight;
currentY += ( rowHeight + 2 * mCellMargin );
}
painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
}
void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells ) const
{
//hacky shortcut to maintain 2.10 API without adding code - whooo!
drawVerticalGridLines( painter, maxWidthMap, 100000, 100000 + numberRows, hasHeader, mergeCells );
}
void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
{
//vertical lines
if ( numberRows < 1 && !hasHeader )
if ( lastRow - firstRow < 1 && !hasHeader )
{
return;
}
@ -855,7 +1034,13 @@ void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<in
}
tableHeight += ( mShowGrid ? mGridStrokeWidth : 0 );
double headerHeight = tableHeight;
tableHeight += numberRows * (( mShowGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + QgsComposerUtils::fontAscentMM( mContentFont ) );
double cellBodyHeight = QgsComposerUtils::fontAscentMM( mContentFont );
for ( int row = firstRow; row < lastRow; ++row )
{
double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeight;
tableHeight += rowHeight + ( mShowGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
}
double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
double currentX = halfGridStrokeWidth;

View File

@ -393,12 +393,20 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
/** Map of maximum width for each column*/
QMap<int, double> mMaxColumnWidthMap;
/** Map of maximum height for each row*/
QMap<int, double> mMaxRowHeightMap;
QSizeF mTableSize;
/** Calculates the maximum width of text shown in columns.
*/
virtual bool calculateMaxColumnWidths();
/** Calculates the maximum height of text shown in rows.
* @note added in QGIS 2.12
*/
virtual bool calculateMaxRowHeights();
/** Returns total width of table contents.
* @returns table width
* @see totalHeight
@ -410,13 +418,86 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
* @returns total height
* @see totalWidth
*/
double totalHeight() const;
//not const, as needs to call calculateMaxRowHeights()
double totalHeight();
/** Calculates how many content rows would be visible within a frame of the specified
* height.
* @param frameHeight height of frame
* @param firstRow index of first row visible in frame (where 0 = first row in table)
* @param includeHeader set to true if frame would include a header row
* @param includeEmptyRows set to true to also include rows which would be empty in the returned count. For instance,
* if the frame would include all table content rows and have space left for extra rows then setting this parameter
* to true would also include a count of these extra blank rows.
* @returns number of visible content rows (excluding header row)
* @note added in QGIS 2.12
*/
int rowsVisible( double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const;
/** Calculates how many content rows are visible within a given frame.
* @param frameIndex index number for frame
* @param firstRow index of first row visible in frame (where 0 = first row in table)
* @param includeEmptyRows set to true to also include rows which would be empty in the returned count. For instance,
* if the frame would include all table content rows and have space left for extra rows then setting this parameter
* to true would also include a count of these extra blank rows.
* @returns number of visible content rows (excludes header rows)
* @note added in QGIS 2.12
*/
int rowsVisible( int frameIndex, int firstRow, bool includeEmptyRows ) const;
/** Calculates a range of rows which should be visible in a given frame.
* @param frameIndex index number for frame
* @returns row range
* @note added in QGIS 2.12
*/
QPair<int, int> rowRange( const int frameIndex ) const;
/** Draws the horizontal grid lines for the table.
* @param painter destination painter for grid lines
* @param firstRow index corresponding to first row shown in frame
* @param lastRow index corresponding to last row shown in frame. If greater than the number of content rows in the
* table, then the default row height will be used for the remaining rows.
* @param drawHeaderLines set to true to include for the table header
* @see drawVerticalGridLines
* @note added in QGIS 2.12
*/
void drawHorizontalGridLines( QPainter* painter, int firstRow, int lastRow, bool drawHeaderLines ) const;
/** Draws the vertical grid lines for the table.
* @param painter destination painter for grid lines
* @param maxWidthMap QMap of int to double, where the int contains the column number and the double is the
* maximum width of text present in the column.
* @param firstRow index corresponding to first row shown in frame
* @param lastRow index corresponding to last row shown in frame. If greater than the number of content rows in the
* table, then the default row height will be used for the remaining rows.
* @param hasHeader set to true if table frame includes header cells
* @param mergeCells set to true to merge table content cells
* @note not available in python bindings
* @see drawVerticalGridLines
* @see calculateMaxColumnWidths
* @note not available in python bindings
* @note added in QGIS 2.12
*/
void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells = false ) const;
/** Recalculates and updates the size of the table and all table frames.
*/
void recalculateTableSize();
/** Checks whether a table contents contains a given row
* @param contents table contents to check
* @param row row to check for
* @returns true if contents contains rows
*/
bool contentsContainsRow( const QgsComposerTableContents &contents, const QgsComposerTableRow &row ) const;
//deprecated methods
/** Calculates how many content rows are visible within a given frame
* @param frameIndex index number for frame
* @returns number of visible content rows (excludes header rows)
*/
int rowsVisible( const int frameIndex ) const;
Q_DECL_DEPRECATED int rowsVisible( const int frameIndex ) const;
/** Calculates how many content rows would be visible within a specified
* height.
@ -424,7 +505,7 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
* @param includeHeader set to true if frame would include a header row
* @returns number of visible content rows (excluding header row)
*/
int rowsVisible( const double frameHeight, const bool includeHeader ) const;
Q_DECL_DEPRECATED int rowsVisible( const double frameHeight, const bool includeHeader ) const;
/** Calculates a range of rows which should be visible in a given
* frame extent.
@ -432,7 +513,7 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
* @param frameIndex index number for frame
* @returns row range
*/
QPair<int, int> rowRange( const QRectF &extent, const int frameIndex ) const;
Q_DECL_DEPRECATED QPair<int, int> rowRange( const QRectF &extent, const int frameIndex ) const;
/** Draws the horizontal grid lines for the table.
* @param painter destination painter for grid lines
@ -440,7 +521,7 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
* @param drawHeaderLines set to true to include for the table header
* @see drawVerticalGridLines
*/
void drawHorizontalGridLines( QPainter* painter, const int rows, const bool drawHeaderLines ) const;
Q_DECL_DEPRECATED void drawHorizontalGridLines( QPainter* painter, const int rows, const bool drawHeaderLines ) const;
/** Draws the vertical grid lines for the table.
* @param painter destination painter for grid lines
@ -454,18 +535,7 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
* @see calculateMaxColumnWidths
* @note not available in python bindings
*/
void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells = false ) const;
/** Recalculates and updates the size of the table and all table frames.
*/
void recalculateTableSize();
/** Checks whether a table contents contains a given row
* @param contents table contents to check
* @param row row to check for
* @returns true if contents contains rows
*/
bool contentsContainsRow( const QgsComposerTableContents &contents, const QgsComposerTableRow &row ) const;
Q_DECL_DEPRECATED void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells = false ) const;
friend class TestQgsComposerTableV2;
};

View File

@ -70,6 +70,8 @@ class TestQgsComposerTableV2 : public QObject
void contentsContainsRow(); //test the contentsContainsRow function
void removeDuplicates(); //test removing duplicate rows
void multiLineText(); //test rendering a table with multiline text
private:
QgsComposition* mComposition;
QgsMapSettings *mMapSettings;
@ -628,5 +630,38 @@ void TestQgsComposerTableV2::removeDuplicates()
delete dupesLayer;
}
void TestQgsComposerTableV2::multiLineText()
{
QgsVectorLayer* multiLineLayer = new QgsVectorLayer( "Point?field=col1:string&field=col2:string&field=col3:string", "multiline", "memory" );
QVERIFY( multiLineLayer->isValid() );
QgsFeature f1( multiLineLayer->dataProvider()->fields(), 1 );
f1.setAttribute( "col1", "multiline\nstring" );
f1.setAttribute( "col2", "singleline string" );
f1.setAttribute( "col3", "singleline" );
QgsFeature f2( multiLineLayer->dataProvider()->fields(), 2 );
f2.setAttribute( "col1", "singleline string" );
f2.setAttribute( "col2", "multiline\nstring" );
f2.setAttribute( "col3", "singleline" );
QgsFeature f3( multiLineLayer->dataProvider()->fields(), 3 );
f3.setAttribute( "col1", "singleline" );
f3.setAttribute( "col2", "singleline" );
f3.setAttribute( "col3", "multiline\nstring" );
QgsFeature f4( multiLineLayer->dataProvider()->fields(), 4 );
f4.setAttribute( "col1", "long triple\nline\nstring" );
f4.setAttribute( "col2", "double\nlinestring" );
f4.setAttribute( "col3", "singleline" );
multiLineLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 );
mFrame2->setSceneRect( QRectF( 5, 40, 100, 90 ) );
mComposerAttributeTable->setMaximumNumberOfFeatures( 20 );
mComposerAttributeTable->setVectorLayer( multiLineLayer );
QgsCompositionChecker checker( "composerattributetable_multiline", mComposition );
bool result = checker.testComposition( mReport );
QVERIFY( result );
delete multiLineLayer;
}
QTEST_MAIN( TestQgsComposerTableV2 )
#include "testqgscomposertablev2.moc"

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB