Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FEATURE][composer] Add choice of display style for empty tables. Opt…
…ions

include hiding the entire table, showing empty cells, or displaying a
set message in the table body. Sponsored by City of Uster, Switzerland.
  • Loading branch information
nyalldawson committed Sep 22, 2014
1 parent c3ec4b9 commit 14690d0
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 78 deletions.
8 changes: 8 additions & 0 deletions python/core/composer/qgscomposerattributetablev2.sip
Expand Up @@ -72,6 +72,14 @@ class QgsComposerAttributeTableV2 : QgsComposerTableV2
* @see setSource
*/
ContentSource source() const;

/**Returns the source layer for the table, considering the table source mode. Eg,
* if the table is set to atlas feature mode, then the source layer will be the
* atlas coverage layer. If the table is set to layer attributes mode, then
* the source layer will be the user specified vector layer.
* @returns actual source layer
*/
QgsVectorLayer* sourceLayer();

/**Sets the vector layer from which to display feature attributes
* @param layer Vector layer for attribute table
Expand Down
44 changes: 43 additions & 1 deletion python/core/composer/qgscomposertablev2.sip
Expand Up @@ -45,6 +45,16 @@ class QgsComposerTableV2: QgsComposerMultiFrame
AllFrames, /*!< headers shown on all frames */
NoHeaders /*!< no headers shown for table */
};

/*! Controls how empty tables are displayed
*/
enum EmptyTableMode
{
HeadersOnly, /*!< show header rows only */
HideTable, /*!< hides entire table if empty */
DrawEmptyCells, /*!< draws empty cells */
ShowMessage /*!< shows preset message instead of table contents*/
};

QgsComposerTableV2( QgsComposition* composition /TransferThis/, bool createUndoCommands );
QgsComposerTableV2();
Expand All @@ -62,6 +72,37 @@ class QgsComposerTableV2: QgsComposerMultiFrame
* @see setCellMargin
*/
double cellMargin() const;

/**Sets the behaviour for empty tables with no content rows.
* @param mode behaviour mode for empty tables
* @see emptyTableBehaviour
*/
void setEmptyTableBehaviour( const EmptyTableMode mode );

/**Returns the behaviour mode for empty tables. This property controls
* how the table is drawn if it contains no content rows.
* @returns behaviour mode for empty tables
* @see setEmptyTableBehaviour
*/
EmptyTableMode emptyTableBehaviour() const;

/**Sets the message for empty tables with no content rows. This message
* is displayed in the table body if the empty table behaviour is
* set to ShowMessage
* @param message message to show for empty tables
* @see emptyTableMessage
* @see setEmptyTableBehaviour
*/
void setEmptyTableMessage( const QString message );

/**Returns the message for empty tables with no content rows. This message
* is displayed in the table body if the empty table behaviour is
* set to ShowMessage
* @returns message to show for empty tables
* @see setEmptyTableMessage
* @see emptyTableBehaviour
*/
QString emptyTableMessage() const;

/**Sets the font used to draw header text in the table.
* @param font font for header cells
Expand Down Expand Up @@ -305,12 +346,13 @@ class QgsComposerTableV2: QgsComposerMultiFrame
* maximum width of text present in the column.
* @param numberRows number of rows of content in table frame
* @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
*/
//void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, const int numberRows, const bool hasHeader ) const;
//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.
*/
Expand Down
50 changes: 50 additions & 0 deletions src/app/composer/qgscomposerattributetablewidget.cpp
Expand Up @@ -47,6 +47,11 @@ QgsComposerAttributeTableWidget::QgsComposerAttributeTableWidget( QgsComposerAtt
mResizeModeComboBox->addItem( tr( "Extend to next page" ), QgsComposerMultiFrame::ExtendToNextPage );
mResizeModeComboBox->addItem( tr( "Repeat until finished" ), QgsComposerMultiFrame::RepeatUntilFinished );

mEmptyModeComboBox->addItem( tr( "Draw headers only" ), QgsComposerTableV2::HeadersOnly );
mEmptyModeComboBox->addItem( tr( "Hide entire table" ), QgsComposerTableV2::HideTable );
mEmptyModeComboBox->addItem( tr( "Draw empty cells" ), QgsComposerTableV2::DrawEmptyCells );
mEmptyModeComboBox->addItem( tr( "Show set message" ), QgsComposerTableV2::ShowMessage );

bool atlasEnabled = atlasComposition() && atlasComposition()->enabled();
mSourceComboBox->addItem( tr( "Layer features" ), QgsComposerAttributeTableV2::LayerAttributes );
toggleAtlasSpecificControls( atlasEnabled );
Expand Down Expand Up @@ -494,6 +499,11 @@ void QgsComposerAttributeTableWidget::updateGuiElements()
mHeaderHAlignmentComboBox->setCurrentIndex(( int )mComposerTable->headerHAlignment() );
mHeaderModeComboBox->setCurrentIndex(( int )mComposerTable->headerMode() );

mEmptyModeComboBox->setCurrentIndex( mEmptyModeComboBox->findData( mComposerTable->emptyTableBehaviour() ) );
mEmptyMessageLineEdit->setText( mComposerTable->emptyTableMessage() );
mEmptyMessageLineEdit->setEnabled( mComposerTable->emptyTableBehaviour() == QgsComposerTableV2::ShowMessage );
mEmptyMessageLabel->setEnabled( mComposerTable->emptyTableBehaviour() == QgsComposerTableV2::ShowMessage );

mResizeModeComboBox->setCurrentIndex( mResizeModeComboBox->findData( mComposerTable->resizeMode() ) );
mAddFramePushButton->setEnabled( mComposerTable->resizeMode() == QgsComposerMultiFrame::UseExistingFrames );

Expand Down Expand Up @@ -585,6 +595,8 @@ void QgsComposerAttributeTableWidget::blockAllSignals( bool b )
mContentFontColorButton->blockSignals( b );
mResizeModeComboBox->blockSignals( b );
mRelationsComboBox->blockSignals( b );
mEmptyModeComboBox->blockSignals( b );
mEmptyMessageLineEdit->blockSignals( b );
}

void QgsComposerAttributeTableWidget::setMaximumNumberOfFeatures( int n )
Expand Down Expand Up @@ -850,6 +862,44 @@ void QgsComposerAttributeTableWidget::on_mRelationsComboBox_currentIndexChanged(
}
}

void QgsComposerAttributeTableWidget::on_mEmptyModeComboBox_currentIndexChanged( int index )
{
if ( !mComposerTable )
{
return;
}

QgsComposition* composition = mComposerTable->composition();
if ( composition )
{
composition->beginMultiFrameCommand( mComposerTable, tr( "Change empty table behaviour" ) );
mComposerTable->setEmptyTableBehaviour(( QgsComposerTableV2::EmptyTableMode ) mEmptyModeComboBox->itemData( index ).toInt() );
composition->endMultiFrameCommand();
mEmptyMessageLineEdit->setEnabled( mComposerTable->emptyTableBehaviour() == QgsComposerTableV2::ShowMessage );
mEmptyMessageLabel->setEnabled( mComposerTable->emptyTableBehaviour() == QgsComposerTableV2::ShowMessage );
}
}

void QgsComposerAttributeTableWidget::on_mEmptyMessageLineEdit_editingFinished()
{
if ( !mComposerTable )
{
return;
}

QgsComposition* composition = mComposerTable->composition();
if ( composition )
{
composition->beginMultiFrameCommand( mComposerTable, tr( "Empty table message changed" ) );
}
mComposerTable->setEmptyTableMessage( mEmptyMessageLineEdit->text() );
mComposerTable->update();
if ( composition )
{
composition->endMultiFrameCommand();
}
}

void QgsComposerAttributeTableWidget::toggleSourceControls()
{
switch ( mComposerTable->source() )
Expand Down
2 changes: 2 additions & 0 deletions src/app/composer/qgscomposerattributetablewidget.h
Expand Up @@ -70,6 +70,8 @@ class QgsComposerAttributeTableWidget: public QgsComposerItemBaseWidget, private
void on_mResizeModeComboBox_currentIndexChanged( int index );
void on_mSourceComboBox_currentIndexChanged( int index );
void on_mRelationsComboBox_currentIndexChanged( int index );
void on_mEmptyModeComboBox_currentIndexChanged( int index );
void on_mEmptyMessageLineEdit_editingFinished();

/**Inserts a new maximum number of features into the spin box (without the spinbox emitting a signal)*/
void setMaximumNumberOfFeatures( int n );
Expand Down
109 changes: 94 additions & 15 deletions src/core/composer/qgscomposertablev2.cpp
Expand Up @@ -24,6 +24,7 @@
QgsComposerTableV2::QgsComposerTableV2( QgsComposition *composition, bool createUndoCommands )
: QgsComposerMultiFrame( composition, createUndoCommands )
, mCellMargin( 1.0 )
, mEmptyTableMode( HeadersOnly )
, mHeaderFontColor( Qt::black )
, mHeaderHAlignment( FollowColumn )
, mHeaderMode( FirstFrame )
Expand Down Expand Up @@ -63,6 +64,8 @@ QgsComposerTableV2::~QgsComposerTableV2()
bool QgsComposerTableV2::writeXML( QDomElement& elem, QDomDocument & doc, bool ignoreFrames ) const
{
elem.setAttribute( "cellMargin", QString::number( mCellMargin ) );
elem.setAttribute( "emptyTableMode", QString::number(( int )mEmptyTableMode ) );
elem.setAttribute( "emptyTableMessage", mEmptyTableMessage );
elem.setAttribute( "headerFont", mHeaderFont.toString() );
elem.setAttribute( "headerFontColor", QgsSymbolLayerV2Utils::encodeColor( mHeaderFontColor ) );
elem.setAttribute( "headerHAlignment", QString::number(( int )mHeaderHAlignment ) );
Expand Down Expand Up @@ -103,6 +106,8 @@ bool QgsComposerTableV2::readXML( const QDomElement &itemElem, const QDomDocumen
return false;
}

mEmptyTableMode = QgsComposerTableV2::EmptyTableMode( itemElem.attribute( "emptyTableMode", "0" ).toInt() );
mEmptyTableMessage = itemElem.attribute( "emptyTableMessage", tr( "No matching records" ) );
mHeaderFont.fromString( itemElem.attribute( "headerFont", "" ) );
mHeaderFontColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "headerFontColor", "0,0,0,255" ) );
mHeaderHAlignment = QgsComposerTableV2::HeaderHAlignment( itemElem.attribute( "headerHAlignment", "0" ).toInt() );
Expand Down Expand Up @@ -229,6 +234,13 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
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 );

Expand Down Expand Up @@ -260,6 +272,8 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
//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 )
{
Expand Down Expand Up @@ -304,18 +318,21 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
}

//draw the attribute values
for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
if ( drawContents )
{
cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellBodyHeight );
//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();
QVariant cellContents = mTableContents.at( row ).at( col );
QString str = cellContents.toString();

QgsComposerUtils::drawText( p, cell, str, mContentFont, mContentFontColor, ( *columnIt )->hAlignment(), Qt::AlignVCenter, textFlag );
QgsComposerUtils::drawText( p, cell, str, mContentFont, mContentFontColor, ( *columnIt )->hAlignment(), Qt::AlignVCenter, textFlag );

currentY += cellBodyHeight;
currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
currentY += cellBodyHeight;
currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
}
}

currentX += mMaxColumnWidthMap[ col ];
Expand All @@ -324,17 +341,39 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const
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, rowsToShow.second - rowsToShow.first, drawHeader );
drawVerticalGridLines( p, mMaxColumnWidthMap, rowsToShow.second - rowsToShow.first, drawHeader );
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();
Expand All @@ -356,6 +395,36 @@ void QgsComposerTableV2::setCellMargin( const double margin )
emit changed();
}

void QgsComposerTableV2::setEmptyTableBehaviour( const QgsComposerTableV2::EmptyTableMode mode )
{
if ( mode == mEmptyTableMode )
{
return;
}

mEmptyTableMode = mode;

//since appearance has changed, we need to recalculate the table size
recalculateTableSize();

emit changed();
}

void QgsComposerTableV2::setEmptyTableMessage( const QString message )
{
if ( message == mEmptyTableMessage )
{
return;
}

mEmptyTableMessage = message;

//since message has changed, we need to recalculate the table size
recalculateTableSize();

emit changed();
}

void QgsComposerTableV2::setHeaderFont( const QFont &font )
{
if ( font == mHeaderFont )
Expand Down Expand Up @@ -683,7 +752,7 @@ void QgsComposerTableV2::drawHorizontalGridLines( QPainter *painter, const int r
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
void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells ) const
{
//vertical lines
if ( numberRows < 1 && !hasHeader )
Expand All @@ -697,20 +766,30 @@ void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<in
{
tableHeight += ( mShowGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + QgsComposerUtils::fontAscentMM( mHeaderFont );
}

tableHeight += numberRows * (( mShowGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + QgsComposerUtils::fontAscentMM( mContentFont ) );
tableHeight += ( mShowGrid ? mGridStrokeWidth : 0 );
double headerHeight = tableHeight;
tableHeight += numberRows * (( mShowGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + QgsComposerUtils::fontAscentMM( mContentFont ) );

double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
double currentX = halfGridStrokeWidth;
painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
currentX += ( mShowGrid ? mGridStrokeWidth : 0 );
QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
int col = 1;
for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
{
currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
if ( col == maxWidthMap.size() || !mergeCells )
{
painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
}
else if ( hasHeader )
{
painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, headerHeight - halfGridStrokeWidth ) );
}

currentX += ( mShowGrid ? mGridStrokeWidth : 0 );
col++;
}
}

Expand Down

0 comments on commit 14690d0

Please sign in to comment.