mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[FEATURE][composer] Auto wrapping for text in fixed width columns
in attribute tables Sponsored by City of Uster
This commit is contained in:
parent
9bf0295c55
commit
612df6ae14
@ -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
|
||||
|
@ -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 )
|
||||
|
@ -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 );
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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 |
Loading…
x
Reference in New Issue
Block a user