[FEATURE][composer] Auto wrapping for text in fixed width columns

in attribute tables

Sponsored by City of Uster
This commit is contained in:
Nyall Dawson 2015-08-19 05:11:01 +10:00
parent 9bf0295c55
commit 612df6ae14
9 changed files with 243 additions and 17 deletions

View File

@ -55,6 +55,14 @@ class QgsComposerTableV2: QgsComposerMultiFrame
ShowMessage /*!< shows preset message instead of table contents*/
};
/** Controls how long strings in the table are handled
*/
enum WrapBehaviour
{
TruncateText = 0, /*!< text which doesn't fit inside the cell is truncated */
WrapText /*!< text which doesn't fit inside the cell is wrapped. Note that this only applies to text in columns with a fixed width. */
};
QgsComposerTableV2( QgsComposition* composition /TransferThis/, bool createUndoCommands );
QgsComposerTableV2();
@ -263,6 +271,22 @@ class QgsComposerTableV2: QgsComposerMultiFrame
*/
QColor backgroundColor() const;
/** Sets the wrap behaviour for the table, which controls how text within cells is
* automatically wrapped.
* @param behaviour wrap behaviour
* @see wrapBehaviour
* @note added in QGIS 2.12
*/
void setWrapBehaviour( WrapBehaviour behaviour );
/** Returns the wrap behaviour for the table, which controls how text within cells is
* automatically wrapped.
* @returns current wrap behaviour
* @see setWrapBehaviour
* @note added in QGIS 2.12
*/
WrapBehaviour wrapBehaviour() const;
/** Returns a pointer to the list of QgsComposerTableColumns shown in the table
* @returns pointer to list of columns in table
* @see setColumns

View File

@ -47,6 +47,9 @@ QgsComposerAttributeTableWidget::QgsComposerAttributeTableWidget( QgsComposerAtt
mEmptyModeComboBox->addItem( tr( "Hide entire table" ), QgsComposerTableV2::HideTable );
mEmptyModeComboBox->addItem( tr( "Show set message" ), QgsComposerTableV2::ShowMessage );
mWrapBehaviourComboBox->addItem( tr( "Truncate text" ), QgsComposerTableV2::TruncateText );
mWrapBehaviourComboBox->addItem( tr( "Wrap text" ), QgsComposerTableV2::WrapText );
bool atlasEnabled = atlasComposition() && atlasComposition()->enabled();
mSourceComboBox->addItem( tr( "Layer features" ), QgsComposerAttributeTableV2::LayerAttributes );
toggleAtlasSpecificControls( atlasEnabled );
@ -519,6 +522,7 @@ void QgsComposerAttributeTableWidget::updateGuiElements()
mEmptyMessageLabel->setEnabled( mComposerTable->emptyTableBehaviour() == QgsComposerTableV2::ShowMessage );
mDrawEmptyCheckBox->setChecked( mComposerTable->showEmptyRows() );
mWrapStringLineEdit->setText( mComposerTable->wrapString() );
mWrapBehaviourComboBox->setCurrentIndex( mWrapBehaviourComboBox->findData( mComposerTable->wrapBehaviour() ) );
mResizeModeComboBox->setCurrentIndex( mResizeModeComboBox->findData( mComposerTable->resizeMode() ) );
mAddFramePushButton->setEnabled( mComposerTable->resizeMode() == QgsComposerMultiFrame::UseExistingFrames );
@ -636,6 +640,7 @@ void QgsComposerAttributeTableWidget::blockAllSignals( bool b )
mHideEmptyBgCheckBox->blockSignals( b );
mDrawEmptyCheckBox->blockSignals( b );
mWrapStringLineEdit->blockSignals( b );
mWrapBehaviourComboBox->blockSignals( b );
}
void QgsComposerAttributeTableWidget::setMaximumNumberOfFeatures( int n )
@ -1003,6 +1008,22 @@ void QgsComposerAttributeTableWidget::on_mEmptyModeComboBox_currentIndexChanged(
}
}
void QgsComposerAttributeTableWidget::on_mWrapBehaviourComboBox_currentIndexChanged( int index )
{
if ( !mComposerTable )
{
return;
}
QgsComposition* composition = mComposerTable->composition();
if ( composition )
{
composition->beginMultiFrameCommand( mComposerTable, tr( "Change table wrap mode" ) );
mComposerTable->setWrapBehaviour(( QgsComposerTableV2::WrapBehaviour ) mWrapBehaviourComboBox->itemData( index ).toInt() );
composition->endMultiFrameCommand();
}
}
void QgsComposerAttributeTableWidget::on_mDrawEmptyCheckBox_toggled( bool checked )
{
if ( !mComposerTable )

View File

@ -79,6 +79,7 @@ class QgsComposerAttributeTableWidget: public QgsComposerItemBaseWidget, private
void on_mUniqueOnlyCheckBox_stateChanged( int state );
void on_mEmptyFrameCheckBox_toggled( bool checked );
void on_mHideEmptyBgCheckBox_toggled( bool checked );
void on_mWrapBehaviourComboBox_currentIndexChanged( int index );
/** Inserts a new maximum number of features into the spin box (without the spinbox emitting a signal)*/
void setMaximumNumberOfFeatures( int n );

View File

@ -35,6 +35,7 @@ QgsComposerTableV2::QgsComposerTableV2( QgsComposition *composition, bool create
, mGridStrokeWidth( 0.5 )
, mGridColor( Qt::black )
, mBackgroundColor( Qt::white )
, mWrapBehaviour( TruncateText )
{
if ( mComposition )
@ -65,6 +66,7 @@ QgsComposerTableV2::QgsComposerTableV2()
, mGridStrokeWidth( 0.5 )
, mGridColor( Qt::black )
, mBackgroundColor( Qt::white )
, mWrapBehaviour( TruncateText )
{
}
@ -91,6 +93,7 @@ bool QgsComposerTableV2::writeXML( QDomElement& elem, QDomDocument & doc, bool i
elem.setAttribute( "gridColor", QgsSymbolLayerV2Utils::encodeColor( mGridColor ) );
elem.setAttribute( "showGrid", mShowGrid );
elem.setAttribute( "backgroundColor", QgsSymbolLayerV2Utils::encodeColor( mBackgroundColor ) );
elem.setAttribute( "wrapBehaviour", QString::number(( int )mWrapBehaviour ) );
//columns
QDomElement displayColumnsElem = doc.createElement( "displayColumns" );
@ -142,6 +145,7 @@ bool QgsComposerTableV2::readXML( const QDomElement &itemElem, const QDomDocumen
mShowGrid = itemElem.attribute( "showGrid", "1" ).toInt();
mGridColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "gridColor", "0,0,0,255" ) );
mBackgroundColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "backgroundColor", "255,255,255,0" ) );
mWrapBehaviour = QgsComposerTableV2::WrapBehaviour( itemElem.attribute( "wrapBehaviour", "0" ).toInt() );
//restore column specifications
qDeleteAll( mColumns );
@ -459,20 +463,23 @@ void QgsComposerTableV2::render( QPainter *p, const QRectF &, const int frameInd
// currentY = gridSize;
currentX += mCellMargin;
QVariant cellContents = mTableContents.at( row ).at( col );
QString str = cellContents.toString();
Qt::TextFlag textFlag = ( Qt::TextFlag )0;
if (( *columnIt )->width() <= 0 )
if (( *columnIt )->width() <= 0 && mWrapBehaviour == 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, ( *columnIt )->width(), mContentFont ) )
{
str = wrappedText( str, ( *columnIt )->width(), mContentFont );
}
cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], rowHeight );
QVariant cellContents = mTableContents.at( row ).at( col );
QString str = cellContents.toString();
QgsComposerUtils::drawText( p, cell, str, mContentFont, mContentFontColor, ( *columnIt )->hAlignment(), ( *columnIt )->vAlignment(), textFlag );
currentX += mMaxColumnWidthMap[ col ];
@ -701,6 +708,19 @@ void QgsComposerTableV2::setBackgroundColor( const QColor &color )
emit changed();
}
void QgsComposerTableV2::setWrapBehaviour( QgsComposerTableV2::WrapBehaviour behaviour )
{
if ( behaviour == mWrapBehaviour )
{
return;
}
mWrapBehaviour = behaviour;
recalculateTableSize();
emit changed();
}
void QgsComposerTableV2::setColumns( QgsComposerTableColumns columns )
{
//remove existing columns
@ -864,8 +884,15 @@ bool QgsComposerTableV2::calculateMaxRowHeights()
col = 0;
for ( ; colIt != rowIt->constEnd(); ++colIt )
{
//height
heights[ row * cols + col ] = QgsComposerUtils::textHeightMM( mContentFont, ( *colIt ).toString() );
if ( textRequiresWrapping(( *colIt ).toString(), mColumns.at( col )->width(), mContentFont ) )
{
//contents too wide for cell, need to wrap
heights[ row * cols + col ] = QgsComposerUtils::textHeightMM( mContentFont, wrappedText(( *colIt ).toString(), mColumns.at( col )->width(), mContentFont ) );
}
else
{
heights[ row * cols + col ] = QgsComposerUtils::textHeightMM( mContentFont, ( *colIt ).toString() );
}
col++;
}
@ -1018,6 +1045,79 @@ void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<in
drawVerticalGridLines( painter, maxWidthMap, 100000, 100000 + numberRows, hasHeader, mergeCells );
}
bool QgsComposerTableV2::textRequiresWrapping( const QString& text, double columnWidth, const QFont &font ) const
{
if ( columnWidth == 0 || mWrapBehaviour != WrapText )
return false;
QStringList multiLineSplit = text.split( "\n" );
double currentTextWidth = 0;
Q_FOREACH ( QString line, multiLineSplit )
{
currentTextWidth = qMax( currentTextWidth, QgsComposerUtils::textWidthMM( font, line ) );
}
return ( currentTextWidth > columnWidth );
}
QString QgsComposerTableV2::wrappedText( const QString &value, double columnWidth, const QFont &font ) const
{
QStringList lines = value.split( "\n" );
QStringList outLines;
Q_FOREACH ( QString line, lines )
{
if ( textRequiresWrapping( line, columnWidth, font ) )
{
//first step is to identify words which must be on their own line (too long to fit)
QStringList words = line.split( " " );
QStringList linesToProcess;
QString wordsInCurrentLine;
Q_FOREACH ( QString word, words )
{
if ( textRequiresWrapping( word, columnWidth, font ) )
{
//too long to fit
if ( !wordsInCurrentLine.isEmpty() )
linesToProcess << wordsInCurrentLine;
wordsInCurrentLine.clear();
linesToProcess << word;
}
else
{
if ( !wordsInCurrentLine.isEmpty() )
wordsInCurrentLine.append( " " );
wordsInCurrentLine.append( word );
}
}
if ( !wordsInCurrentLine.isEmpty() )
linesToProcess << wordsInCurrentLine;
Q_FOREACH ( QString line, linesToProcess )
{
QString remainingText = line;
int lastPos = remainingText.lastIndexOf( " " );
while ( lastPos > -1 )
{
if ( !textRequiresWrapping( remainingText.left( lastPos ), columnWidth, font ) )
{
outLines << remainingText.left( lastPos );
remainingText = remainingText.mid( lastPos + 1 );
lastPos = 0;
}
lastPos = remainingText.lastIndexOf( " ", lastPos - 1 );
}
outLines << remainingText;
}
}
else
{
outLines << line;
}
}
return outLines.join( "\n" );
}
void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
{
//vertical lines

View File

@ -80,6 +80,14 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
ShowMessage /*!< shows preset message instead of table contents*/
};
/** Controls how long strings in the table are handled
*/
enum WrapBehaviour
{
TruncateText = 0, /*!< text which doesn't fit inside the cell is truncated */
WrapText /*!< text which doesn't fit inside the cell is wrapped. Note that this only applies to text in columns with a fixed width. */
};
QgsComposerTableV2( QgsComposition* composition, bool createUndoCommands );
QgsComposerTableV2();
@ -288,6 +296,22 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
*/
QColor backgroundColor() const { return mBackgroundColor; }
/** Sets the wrap behaviour for the table, which controls how text within cells is
* automatically wrapped.
* @param behaviour wrap behaviour
* @see wrapBehaviour
* @note added in QGIS 2.12
*/
void setWrapBehaviour( WrapBehaviour behaviour );
/** Returns the wrap behaviour for the table, which controls how text within cells is
* automatically wrapped.
* @returns current wrap behaviour
* @see setWrapBehaviour
* @note added in QGIS 2.12
*/
WrapBehaviour wrapBehaviour() const { return mWrapBehaviour; }
/** Returns a pointer to the list of QgsComposerTableColumns shown in the table
* @returns pointer to list of columns in table
* @see setColumns
@ -398,6 +422,8 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
QSizeF mTableSize;
WrapBehaviour mWrapBehaviour;
/** Calculates the maximum width of text shown in columns.
*/
virtual bool calculateMaxColumnWidths();
@ -537,6 +563,12 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
*/
Q_DECL_DEPRECATED void drawVerticalGridLines( QPainter* painter, const QMap<int, double>& maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells = false ) const;
private:
bool textRequiresWrapping( const QString& text, double columnWidth , const QFont &font ) const;
QString wrappedText( const QString &value, double columnWidth, const QFont &font ) const;
friend class TestQgsComposerTableV2;
};

View File

@ -54,9 +54,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-433</y>
<y>0</y>
<width>392</width>
<height>1225</height>
<height>1258</height>
</rect>
</property>
<layout class="QVBoxLayout" name="mainLayout">
@ -353,8 +353,12 @@
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="mEmptyMessageLineEdit"/>
<item row="7" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Background color</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="mDrawEmptyCheckBox">
@ -363,12 +367,8 @@
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Background color</string>
</property>
</widget>
<item row="6" column="1">
<widget class="QLineEdit" name="mEmptyMessageLineEdit"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="mEmptyMessageLabel">
@ -397,6 +397,16 @@
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Oversized text</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QComboBox" name="mWrapBehaviourComboBox"/>
</item>
</layout>
</widget>
</item>
@ -799,6 +809,7 @@
<tabstop>mEmptyMessageLineEdit</tabstop>
<tabstop>mBackgroundColorButton</tabstop>
<tabstop>mWrapStringLineEdit</tabstop>
<tabstop>mWrapBehaviourComboBox</tabstop>
<tabstop>mShowGridGroupCheckBox</tabstop>
<tabstop>mGridStrokeWidthSpinBox</tabstop>
<tabstop>mGridColorButton</tabstop>

View File

@ -72,6 +72,7 @@ class TestQgsComposerTableV2 : public QObject
void multiLineText(); //test rendering a table with multiline text
void align(); //test alignment of table cells
void wrapChar(); //test setting wrap character
void autoWrap(); //test auto word wrap
private:
QgsComposition* mComposition;
@ -727,5 +728,41 @@ void TestQgsComposerTableV2::wrapChar()
compareTable( expectedRows );
}
void TestQgsComposerTableV2::autoWrap()
{
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", "long 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", "a bit long triple line string" );
f4.setAttribute( "col2", "double toolongtofitononeline string with some more lines on the end andanotherreallylongline" );
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 );
mComposerAttributeTable->setWrapBehaviour( QgsComposerTableV2::WrapText );
mComposerAttributeTable->columns()->at( 0 )->setWidth( 25 );
mComposerAttributeTable->columns()->at( 1 )->setWidth( 25 );
QgsCompositionChecker checker( "composerattributetable_autowrap", mComposition );
bool result = checker.testComposition( mReport, 0 );
mComposerAttributeTable->columns()->at( 0 )->setWidth( 0 );
QVERIFY( result );
}
QTEST_MAIN( TestQgsComposerTableV2 )
#include "testqgscomposertablev2.moc"

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB