Skip to content

Commit 612df6a

Browse files
committed
[FEATURE][composer] Auto wrapping for text in fixed width columns
in attribute tables Sponsored by City of Uster
1 parent 9bf0295 commit 612df6a

File tree

9 files changed

+243
-17
lines changed

9 files changed

+243
-17
lines changed

python/core/composer/qgscomposertablev2.sip

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ class QgsComposerTableV2: QgsComposerMultiFrame
5555
ShowMessage /*!< shows preset message instead of table contents*/
5656
};
5757

58+
/** Controls how long strings in the table are handled
59+
*/
60+
enum WrapBehaviour
61+
{
62+
TruncateText = 0, /*!< text which doesn't fit inside the cell is truncated */
63+
WrapText /*!< text which doesn't fit inside the cell is wrapped. Note that this only applies to text in columns with a fixed width. */
64+
};
65+
5866
QgsComposerTableV2( QgsComposition* composition /TransferThis/, bool createUndoCommands );
5967
QgsComposerTableV2();
6068

@@ -263,6 +271,22 @@ class QgsComposerTableV2: QgsComposerMultiFrame
263271
*/
264272
QColor backgroundColor() const;
265273

274+
/** Sets the wrap behaviour for the table, which controls how text within cells is
275+
* automatically wrapped.
276+
* @param behaviour wrap behaviour
277+
* @see wrapBehaviour
278+
* @note added in QGIS 2.12
279+
*/
280+
void setWrapBehaviour( WrapBehaviour behaviour );
281+
282+
/** Returns the wrap behaviour for the table, which controls how text within cells is
283+
* automatically wrapped.
284+
* @returns current wrap behaviour
285+
* @see setWrapBehaviour
286+
* @note added in QGIS 2.12
287+
*/
288+
WrapBehaviour wrapBehaviour() const;
289+
266290
/** Returns a pointer to the list of QgsComposerTableColumns shown in the table
267291
* @returns pointer to list of columns in table
268292
* @see setColumns

src/app/composer/qgscomposerattributetablewidget.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ QgsComposerAttributeTableWidget::QgsComposerAttributeTableWidget( QgsComposerAtt
4747
mEmptyModeComboBox->addItem( tr( "Hide entire table" ), QgsComposerTableV2::HideTable );
4848
mEmptyModeComboBox->addItem( tr( "Show set message" ), QgsComposerTableV2::ShowMessage );
4949

50+
mWrapBehaviourComboBox->addItem( tr( "Truncate text" ), QgsComposerTableV2::TruncateText );
51+
mWrapBehaviourComboBox->addItem( tr( "Wrap text" ), QgsComposerTableV2::WrapText );
52+
5053
bool atlasEnabled = atlasComposition() && atlasComposition()->enabled();
5154
mSourceComboBox->addItem( tr( "Layer features" ), QgsComposerAttributeTableV2::LayerAttributes );
5255
toggleAtlasSpecificControls( atlasEnabled );
@@ -519,6 +522,7 @@ void QgsComposerAttributeTableWidget::updateGuiElements()
519522
mEmptyMessageLabel->setEnabled( mComposerTable->emptyTableBehaviour() == QgsComposerTableV2::ShowMessage );
520523
mDrawEmptyCheckBox->setChecked( mComposerTable->showEmptyRows() );
521524
mWrapStringLineEdit->setText( mComposerTable->wrapString() );
525+
mWrapBehaviourComboBox->setCurrentIndex( mWrapBehaviourComboBox->findData( mComposerTable->wrapBehaviour() ) );
522526

523527
mResizeModeComboBox->setCurrentIndex( mResizeModeComboBox->findData( mComposerTable->resizeMode() ) );
524528
mAddFramePushButton->setEnabled( mComposerTable->resizeMode() == QgsComposerMultiFrame::UseExistingFrames );
@@ -636,6 +640,7 @@ void QgsComposerAttributeTableWidget::blockAllSignals( bool b )
636640
mHideEmptyBgCheckBox->blockSignals( b );
637641
mDrawEmptyCheckBox->blockSignals( b );
638642
mWrapStringLineEdit->blockSignals( b );
643+
mWrapBehaviourComboBox->blockSignals( b );
639644
}
640645

641646
void QgsComposerAttributeTableWidget::setMaximumNumberOfFeatures( int n )
@@ -1003,6 +1008,22 @@ void QgsComposerAttributeTableWidget::on_mEmptyModeComboBox_currentIndexChanged(
10031008
}
10041009
}
10051010

1011+
void QgsComposerAttributeTableWidget::on_mWrapBehaviourComboBox_currentIndexChanged( int index )
1012+
{
1013+
if ( !mComposerTable )
1014+
{
1015+
return;
1016+
}
1017+
1018+
QgsComposition* composition = mComposerTable->composition();
1019+
if ( composition )
1020+
{
1021+
composition->beginMultiFrameCommand( mComposerTable, tr( "Change table wrap mode" ) );
1022+
mComposerTable->setWrapBehaviour(( QgsComposerTableV2::WrapBehaviour ) mWrapBehaviourComboBox->itemData( index ).toInt() );
1023+
composition->endMultiFrameCommand();
1024+
}
1025+
}
1026+
10061027
void QgsComposerAttributeTableWidget::on_mDrawEmptyCheckBox_toggled( bool checked )
10071028
{
10081029
if ( !mComposerTable )

src/app/composer/qgscomposerattributetablewidget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class QgsComposerAttributeTableWidget: public QgsComposerItemBaseWidget, private
7979
void on_mUniqueOnlyCheckBox_stateChanged( int state );
8080
void on_mEmptyFrameCheckBox_toggled( bool checked );
8181
void on_mHideEmptyBgCheckBox_toggled( bool checked );
82+
void on_mWrapBehaviourComboBox_currentIndexChanged( int index );
8283

8384
/** Inserts a new maximum number of features into the spin box (without the spinbox emitting a signal)*/
8485
void setMaximumNumberOfFeatures( int n );

src/core/composer/qgscomposertablev2.cpp

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ QgsComposerTableV2::QgsComposerTableV2( QgsComposition *composition, bool create
3535
, mGridStrokeWidth( 0.5 )
3636
, mGridColor( Qt::black )
3737
, mBackgroundColor( Qt::white )
38+
, mWrapBehaviour( TruncateText )
3839
{
3940

4041
if ( mComposition )
@@ -65,6 +66,7 @@ QgsComposerTableV2::QgsComposerTableV2()
6566
, mGridStrokeWidth( 0.5 )
6667
, mGridColor( Qt::black )
6768
, mBackgroundColor( Qt::white )
69+
, mWrapBehaviour( TruncateText )
6870
{
6971

7072
}
@@ -91,6 +93,7 @@ bool QgsComposerTableV2::writeXML( QDomElement& elem, QDomDocument & doc, bool i
9193
elem.setAttribute( "gridColor", QgsSymbolLayerV2Utils::encodeColor( mGridColor ) );
9294
elem.setAttribute( "showGrid", mShowGrid );
9395
elem.setAttribute( "backgroundColor", QgsSymbolLayerV2Utils::encodeColor( mBackgroundColor ) );
96+
elem.setAttribute( "wrapBehaviour", QString::number(( int )mWrapBehaviour ) );
9497

9598
//columns
9699
QDomElement displayColumnsElem = doc.createElement( "displayColumns" );
@@ -142,6 +145,7 @@ bool QgsComposerTableV2::readXML( const QDomElement &itemElem, const QDomDocumen
142145
mShowGrid = itemElem.attribute( "showGrid", "1" ).toInt();
143146
mGridColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "gridColor", "0,0,0,255" ) );
144147
mBackgroundColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "backgroundColor", "255,255,255,0" ) );
148+
mWrapBehaviour = QgsComposerTableV2::WrapBehaviour( itemElem.attribute( "wrapBehaviour", "0" ).toInt() );
145149

146150
//restore column specifications
147151
qDeleteAll( mColumns );
@@ -459,20 +463,23 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &, const int frameInd
459463
// currentY = gridSize;
460464
currentX += mCellMargin;
461465

466+
QVariant cellContents = mTableContents.at( row ).at( col );
467+
QString str = cellContents.toString();
468+
462469
Qt::TextFlag textFlag = ( Qt::TextFlag )0;
463-
if (( *columnIt )->width() <= 0 )
470+
if (( *columnIt )->width() <= 0 && mWrapBehaviour == TruncateText )
464471
{
465472
//automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text
466473
//which may slightly exceed the calculated width
467474
//if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width
468475
textFlag = Qt::TextDontClip;
469476
}
477+
else if ( textRequiresWrapping( str, ( *columnIt )->width(), mContentFont ) )
478+
{
479+
str = wrappedText( str, ( *columnIt )->width(), mContentFont );
480+
}
470481

471482
cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], rowHeight );
472-
473-
QVariant cellContents = mTableContents.at( row ).at( col );
474-
QString str = cellContents.toString();
475-
476483
QgsComposerUtils::drawText( p, cell, str, mContentFont, mContentFontColor, ( *columnIt )->hAlignment(), ( *columnIt )->vAlignment(), textFlag );
477484

478485
currentX += mMaxColumnWidthMap[ col ];
@@ -701,6 +708,19 @@ void QgsComposerTableV2::setBackgroundColor( const QColor &color )
701708
emit changed();
702709
}
703710

711+
void QgsComposerTableV2::setWrapBehaviour( QgsComposerTableV2::WrapBehaviour behaviour )
712+
{
713+
if ( behaviour == mWrapBehaviour )
714+
{
715+
return;
716+
}
717+
718+
mWrapBehaviour = behaviour;
719+
recalculateTableSize();
720+
721+
emit changed();
722+
}
723+
704724
void QgsComposerTableV2::setColumns( QgsComposerTableColumns columns )
705725
{
706726
//remove existing columns
@@ -864,8 +884,15 @@ bool QgsComposerTableV2::calculateMaxRowHeights()
864884
col = 0;
865885
for ( ; colIt != rowIt->constEnd(); ++colIt )
866886
{
867-
//height
868-
heights[ row * cols + col ] = QgsComposerUtils::textHeightMM( mContentFont, ( *colIt ).toString() );
887+
if ( textRequiresWrapping(( *colIt ).toString(), mColumns.at( col )->width(), mContentFont ) )
888+
{
889+
//contents too wide for cell, need to wrap
890+
heights[ row * cols + col ] = QgsComposerUtils::textHeightMM( mContentFont, wrappedText(( *colIt ).toString(), mColumns.at( col )->width(), mContentFont ) );
891+
}
892+
else
893+
{
894+
heights[ row * cols + col ] = QgsComposerUtils::textHeightMM( mContentFont, ( *colIt ).toString() );
895+
}
869896

870897
col++;
871898
}
@@ -1018,6 +1045,79 @@ void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<in
10181045
drawVerticalGridLines( painter, maxWidthMap, 100000, 100000 + numberRows, hasHeader, mergeCells );
10191046
}
10201047

1048+
bool QgsComposerTableV2::textRequiresWrapping( const QString& text, double columnWidth, const QFont &font ) const
1049+
{
1050+
if ( columnWidth == 0 || mWrapBehaviour != WrapText )
1051+
return false;
1052+
1053+
QStringList multiLineSplit = text.split( "\n" );
1054+
double currentTextWidth = 0;
1055+
Q_FOREACH ( QString line, multiLineSplit )
1056+
{
1057+
currentTextWidth = qMax( currentTextWidth, QgsComposerUtils::textWidthMM( font, line ) );
1058+
}
1059+
1060+
return ( currentTextWidth > columnWidth );
1061+
}
1062+
1063+
QString QgsComposerTableV2::wrappedText( const QString &value, double columnWidth, const QFont &font ) const
1064+
{
1065+
QStringList lines = value.split( "\n" );
1066+
QStringList outLines;
1067+
Q_FOREACH ( QString line, lines )
1068+
{
1069+
if ( textRequiresWrapping( line, columnWidth, font ) )
1070+
{
1071+
//first step is to identify words which must be on their own line (too long to fit)
1072+
QStringList words = line.split( " " );
1073+
QStringList linesToProcess;
1074+
QString wordsInCurrentLine;
1075+
Q_FOREACH ( QString word, words )
1076+
{
1077+
if ( textRequiresWrapping( word, columnWidth, font ) )
1078+
{
1079+
//too long to fit
1080+
if ( !wordsInCurrentLine.isEmpty() )
1081+
linesToProcess << wordsInCurrentLine;
1082+
wordsInCurrentLine.clear();
1083+
linesToProcess << word;
1084+
}
1085+
else
1086+
{
1087+
if ( !wordsInCurrentLine.isEmpty() )
1088+
wordsInCurrentLine.append( " " );
1089+
wordsInCurrentLine.append( word );
1090+
}
1091+
}
1092+
if ( !wordsInCurrentLine.isEmpty() )
1093+
linesToProcess << wordsInCurrentLine;
1094+
1095+
Q_FOREACH ( QString line, linesToProcess )
1096+
{
1097+
QString remainingText = line;
1098+
int lastPos = remainingText.lastIndexOf( " " );
1099+
while ( lastPos > -1 )
1100+
{
1101+
if ( !textRequiresWrapping( remainingText.left( lastPos ), columnWidth, font ) )
1102+
{
1103+
outLines << remainingText.left( lastPos );
1104+
remainingText = remainingText.mid( lastPos + 1 );
1105+
lastPos = 0;
1106+
}
1107+
lastPos = remainingText.lastIndexOf( " ", lastPos - 1 );
1108+
}
1109+
outLines << remainingText;
1110+
}
1111+
}
1112+
else
1113+
{
1114+
outLines << line;
1115+
}
1116+
}
1117+
1118+
return outLines.join( "\n" );
1119+
}
1120+
10211121
void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
10221122
{
10231123
//vertical lines

src/core/composer/qgscomposertablev2.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
8080
ShowMessage /*!< shows preset message instead of table contents*/
8181
};
8282

83+
/** Controls how long strings in the table are handled
84+
*/
85+
enum WrapBehaviour
86+
{
87+
TruncateText = 0, /*!< text which doesn't fit inside the cell is truncated */
88+
WrapText /*!< text which doesn't fit inside the cell is wrapped. Note that this only applies to text in columns with a fixed width. */
89+
};
90+
8391
QgsComposerTableV2( QgsComposition* composition, bool createUndoCommands );
8492
QgsComposerTableV2();
8593

@@ -288,6 +296,22 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
288296
*/
289297
QColor backgroundColor() const { return mBackgroundColor; }
290298

299+
/** Sets the wrap behaviour for the table, which controls how text within cells is
300+
* automatically wrapped.
301+
* @param behaviour wrap behaviour
302+
* @see wrapBehaviour
303+
* @note added in QGIS 2.12
304+
*/
305+
void setWrapBehaviour( WrapBehaviour behaviour );
306+
307+
/** Returns the wrap behaviour for the table, which controls how text within cells is
308+
* automatically wrapped.
309+
* @returns current wrap behaviour
310+
* @see setWrapBehaviour
311+
* @note added in QGIS 2.12
312+
*/
313+
WrapBehaviour wrapBehaviour() const { return mWrapBehaviour; }
314+
291315
/** Returns a pointer to the list of QgsComposerTableColumns shown in the table
292316
* @returns pointer to list of columns in table
293317
* @see setColumns
@@ -398,6 +422,8 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
398422

399423
QSizeF mTableSize;
400424

425+
WrapBehaviour mWrapBehaviour;
426+
401427
/** Calculates the maximum width of text shown in columns.
402428
*/
403429
virtual bool calculateMaxColumnWidths();
@@ -537,6 +563,12 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
537563
*/
538564
Q_DECL_DEPRECATED void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells = false ) const;
539565

566+
private:
567+
568+
bool textRequiresWrapping( const QString& text, double columnWidth , const QFont &font ) const;
569+
570+
QString wrappedText( const QString &value, double columnWidth, const QFont &font ) const;
571+
540572
friend class TestQgsComposerTableV2;
541573
};
542574

0 commit comments

Comments
 (0)