mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-17 00:04:02 -04:00
It was necessary to add a new 'table background' color option to prevent regressions when loading old compositions, since users may desire the old behaviour where the entire frame is filled.
850 lines
26 KiB
C++
850 lines
26 KiB
C++
/***************************************************************************
|
|
qgscomposertablev2.cpp
|
|
------------------
|
|
begin : July 2014
|
|
copyright : (C) 2014 by Nyall Dawson, Marco Hugentobler
|
|
email : nyall dot dawson at gmail dot com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgscomposertablev2.h"
|
|
#include "qgscomposerutils.h"
|
|
#include "qgscomposertablecolumn.h"
|
|
#include "qgssymbollayerv2utils.h"
|
|
#include "qgscomposerframe.h"
|
|
|
|
QgsComposerTableV2::QgsComposerTableV2( QgsComposition *composition, bool createUndoCommands )
|
|
: QgsComposerMultiFrame( composition, createUndoCommands )
|
|
, mCellMargin( 1.0 )
|
|
, mEmptyTableMode( HeadersOnly )
|
|
, mHeaderFontColor( Qt::black )
|
|
, mHeaderHAlignment( FollowColumn )
|
|
, mHeaderMode( FirstFrame )
|
|
, mContentFontColor( Qt::black )
|
|
, mShowGrid( true )
|
|
, mGridStrokeWidth( 0.5 )
|
|
, mGridColor( Qt::black )
|
|
, mBackgroundColor( Qt::white )
|
|
{
|
|
|
|
if ( mComposition )
|
|
{
|
|
QObject::connect( mComposition, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SLOT( handleFrameRemoval( QgsComposerItem* ) ) );
|
|
}
|
|
|
|
//get default composer font from settings
|
|
QSettings settings;
|
|
QString defaultFontString = settings.value( "/Composer/defaultFont" ).toString();
|
|
if ( !defaultFontString.isEmpty() )
|
|
{
|
|
mHeaderFont.setFamily( defaultFontString );
|
|
mContentFont.setFamily( defaultFontString );
|
|
}
|
|
}
|
|
|
|
QgsComposerTableV2::QgsComposerTableV2()
|
|
: QgsComposerMultiFrame( 0, false )
|
|
{
|
|
|
|
}
|
|
|
|
QgsComposerTableV2::~QgsComposerTableV2()
|
|
{
|
|
qDeleteAll( mColumns );
|
|
mColumns.clear();
|
|
}
|
|
|
|
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 ) );
|
|
elem.setAttribute( "headerMode", QString::number(( int )mHeaderMode ) );
|
|
elem.setAttribute( "contentFont", mContentFont.toString() );
|
|
elem.setAttribute( "contentFontColor", QgsSymbolLayerV2Utils::encodeColor( mContentFontColor ) );
|
|
elem.setAttribute( "gridStrokeWidth", QString::number( mGridStrokeWidth ) );
|
|
elem.setAttribute( "gridColor", QgsSymbolLayerV2Utils::encodeColor( mGridColor ) );
|
|
elem.setAttribute( "showGrid", mShowGrid );
|
|
elem.setAttribute( "backgroundColor", QgsSymbolLayerV2Utils::encodeColor( mBackgroundColor ) );
|
|
|
|
//columns
|
|
QDomElement displayColumnsElem = doc.createElement( "displayColumns" );
|
|
QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
|
|
for ( ; columnIt != mColumns.constEnd(); ++columnIt )
|
|
{
|
|
QDomElement columnElem = doc.createElement( "column" );
|
|
( *columnIt )->writeXML( columnElem, doc );
|
|
displayColumnsElem.appendChild( columnElem );
|
|
}
|
|
elem.appendChild( displayColumnsElem );
|
|
|
|
bool state = _writeXML( elem, doc, ignoreFrames );
|
|
return state;
|
|
}
|
|
|
|
bool QgsComposerTableV2::readXML( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames )
|
|
{
|
|
deleteFrames();
|
|
|
|
//first create the frames
|
|
if ( !_readXML( itemElem, doc, ignoreFrames ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( itemElem.isNull() )
|
|
{
|
|
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() );
|
|
mHeaderMode = QgsComposerTableV2::HeaderMode( itemElem.attribute( "headerMode", "0" ).toInt() );
|
|
mContentFont.fromString( itemElem.attribute( "contentFont", "" ) );
|
|
mContentFontColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "contentFontColor", "0,0,0,255" ) );
|
|
mCellMargin = itemElem.attribute( "cellMargin", "1.0" ).toDouble();
|
|
mGridStrokeWidth = itemElem.attribute( "gridStrokeWidth", "0.5" ).toDouble();
|
|
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" ) );
|
|
|
|
//restore column specifications
|
|
qDeleteAll( mColumns );
|
|
mColumns.clear();
|
|
QDomNodeList columnsList = itemElem.elementsByTagName( "displayColumns" );
|
|
if ( columnsList.size() > 0 )
|
|
{
|
|
QDomElement columnsElem = columnsList.at( 0 ).toElement();
|
|
QDomNodeList columnEntryList = columnsElem.elementsByTagName( "column" );
|
|
for ( int i = 0; i < columnEntryList.size(); ++i )
|
|
{
|
|
QDomElement columnElem = columnEntryList.at( i ).toElement();
|
|
QgsComposerTableColumn* column = new QgsComposerTableColumn;
|
|
column->readXML( columnElem );
|
|
mColumns.append( column );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QSizeF QgsComposerTableV2::totalSize() const
|
|
{
|
|
return mTableSize;
|
|
}
|
|
|
|
int QgsComposerTableV2::rowsVisible( const int frameIndex ) 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(), includeHeader );
|
|
}
|
|
|
|
int QgsComposerTableV2::rowsVisible( const double frameHeight, const bool includeHeader ) 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;
|
|
|
|
//calculate number of visible rows
|
|
double rowHeight = ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mContentFont );
|
|
return qMax( floor( contentHeight / rowHeight ), 0.0 );
|
|
}
|
|
|
|
QPair< int, int > QgsComposerTableV2::rowRange( const QRectF extent, const int frameIndex ) const
|
|
{
|
|
//calculate row height
|
|
if ( frameIndex >= frameCount() )
|
|
{
|
|
//bad frame index
|
|
return qMakePair( 0 , 0 );
|
|
}
|
|
|
|
//loop through all previous frames to calculate how many rows are visible in each
|
|
//as the entire height of a frame may not be utilised for content rows
|
|
int rowsAlreadyShown = 0;
|
|
for ( int idx = 0; idx < frameIndex; ++idx )
|
|
{
|
|
rowsAlreadyShown += rowsVisible( idx );
|
|
}
|
|
|
|
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() );
|
|
|
|
return qMakePair( firstVisible, lastVisible );
|
|
}
|
|
|
|
|
|
void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const int frameIndex )
|
|
{
|
|
if ( !p )
|
|
{
|
|
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 );
|
|
|
|
if ( mComposition->plotStyle() == QgsComposition::Print ||
|
|
mComposition->plotStyle() == QgsComposition::Postscript )
|
|
{
|
|
//exporting composition, so force an attribute refresh
|
|
//we do this in case vector layer has changed via an external source (eg, another database user)
|
|
refreshAttributes();
|
|
}
|
|
|
|
double gridSize = mShowGrid ? mGridStrokeWidth : 0;
|
|
QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
|
|
|
|
int col = 0;
|
|
double cellHeaderHeight = QgsComposerUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin;
|
|
double cellBodyHeight = QgsComposerUtils::fontAscentMM( mContentFont ) + 2 * mCellMargin;
|
|
QRectF cell;
|
|
|
|
//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 );
|
|
|
|
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;
|
|
}
|
|
|
|
p->save();
|
|
//antialiasing on
|
|
p->setRenderHint( QPainter::Antialiasing, true );
|
|
|
|
//draw table background
|
|
if ( mBackgroundColor.alpha() > 0 )
|
|
{
|
|
p->save();
|
|
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 );
|
|
|
|
if ( totalHeight > 0 )
|
|
{
|
|
QRectF backgroundRect( 0, 0, mTableSize.width(), totalHeight );
|
|
p->drawRect( backgroundRect );
|
|
}
|
|
p->restore();
|
|
}
|
|
|
|
//now draw the text
|
|
double currentX = gridSize;
|
|
double currentY;
|
|
p->setPen( Qt::SolidLine );
|
|
for ( ; columnIt != mColumns.constEnd(); ++columnIt )
|
|
{
|
|
currentY = gridSize;
|
|
currentX += mCellMargin;
|
|
|
|
Qt::TextFlag textFlag = ( Qt::TextFlag )0;
|
|
if (( *columnIt )->width() <= 0 )
|
|
{
|
|
//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;
|
|
}
|
|
|
|
if ( drawHeader )
|
|
{
|
|
//draw the header
|
|
cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
|
|
|
|
//calculate alignment of header
|
|
Qt::AlignmentFlag headerAlign = Qt::AlignLeft;
|
|
switch ( mHeaderHAlignment )
|
|
{
|
|
case FollowColumn:
|
|
headerAlign = ( *columnIt )->hAlignment();
|
|
break;
|
|
case HeaderLeft:
|
|
headerAlign = Qt::AlignLeft;
|
|
break;
|
|
case HeaderCenter:
|
|
headerAlign = Qt::AlignHCenter;
|
|
break;
|
|
case HeaderRight:
|
|
headerAlign = Qt::AlignRight;
|
|
break;
|
|
}
|
|
|
|
QgsComposerUtils::drawText( p, cell, ( *columnIt )->heading(), mHeaderFont, mHeaderFontColor, headerAlign, Qt::AlignVCenter, textFlag );
|
|
|
|
currentY += cellHeaderHeight;
|
|
currentY += gridSize;
|
|
}
|
|
|
|
if ( drawContents )
|
|
{
|
|
//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();
|
|
|
|
QgsComposerUtils::drawText( p, cell, str, mContentFont, mContentFontColor, ( *columnIt )->hAlignment(), Qt::AlignVCenter, textFlag );
|
|
|
|
currentY += cellBodyHeight;
|
|
currentY += gridSize;
|
|
}
|
|
}
|
|
|
|
currentX += mMaxColumnWidthMap[ col ];
|
|
currentX += mCellMargin;
|
|
currentX += gridSize;
|
|
col++;
|
|
}
|
|
|
|
//and the borders
|
|
if ( mShowGrid )
|
|
{
|
|
QPen gridPen;
|
|
gridPen.setWidthF( mGridStrokeWidth );
|
|
gridPen.setColor( mGridColor );
|
|
gridPen.setJoinStyle( Qt::MiterJoin );
|
|
p->setPen( gridPen );
|
|
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 = gridSize + mCellMargin;
|
|
double messageY = gridSize + ( drawHeader ? cellHeaderHeight + gridSize : 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();
|
|
|
|
}
|
|
|
|
void QgsComposerTableV2::setCellMargin( const double margin )
|
|
{
|
|
if ( margin == mCellMargin )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mCellMargin = margin;
|
|
|
|
//since spacing has changed, we need to recalculate the table size
|
|
recalculateTableSize();
|
|
|
|
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 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mHeaderFont = font;
|
|
//since font attributes have changed, we need to recalculate the table size
|
|
recalculateTableSize();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setHeaderFontColor( const QColor &color )
|
|
{
|
|
if ( color == mHeaderFontColor )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mHeaderFontColor = color;
|
|
repaint();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setHeaderHAlignment( const QgsComposerTableV2::HeaderHAlignment alignment )
|
|
{
|
|
if ( alignment == mHeaderHAlignment )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mHeaderHAlignment = alignment;
|
|
repaint();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setHeaderMode( const QgsComposerTableV2::HeaderMode mode )
|
|
{
|
|
if ( mode == mHeaderMode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mHeaderMode = mode;
|
|
recalculateTableSize();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setContentFont( const QFont &font )
|
|
{
|
|
if ( font == mContentFont )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mContentFont = font;
|
|
//since font attributes have changed, we need to recalculate the table size
|
|
recalculateTableSize();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setContentFontColor( const QColor &color )
|
|
{
|
|
if ( color == mContentFontColor )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mContentFontColor = color;
|
|
repaint();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setShowGrid( const bool showGrid )
|
|
{
|
|
if ( showGrid == mShowGrid )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mShowGrid = showGrid;
|
|
//since grid spacing has changed, we need to recalculate the table size
|
|
recalculateTableSize();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setGridStrokeWidth( const double width )
|
|
{
|
|
if ( width == mGridStrokeWidth )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mGridStrokeWidth = width;
|
|
//since grid spacing has changed, we need to recalculate the table size
|
|
recalculateTableSize();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setGridColor( const QColor &color )
|
|
{
|
|
if ( color == mGridColor )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mGridColor = color;
|
|
repaint();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setBackgroundColor( const QColor &color )
|
|
{
|
|
if ( color == mBackgroundColor )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mBackgroundColor = color;
|
|
repaint();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void QgsComposerTableV2::setColumns( QgsComposerTableColumns columns )
|
|
{
|
|
//remove existing columns
|
|
qDeleteAll( mColumns );
|
|
mColumns.clear();
|
|
|
|
mColumns.append( columns );
|
|
}
|
|
|
|
QMap<int, QString> QgsComposerTableV2::headerLabels() const
|
|
{
|
|
QMap<int, QString> headers;
|
|
|
|
QgsComposerTableColumns::const_iterator columnIt = mColumns.constBegin();
|
|
int col = 0;
|
|
for ( ; columnIt != mColumns.constEnd(); ++columnIt )
|
|
{
|
|
headers.insert( col, ( *columnIt )->heading() );
|
|
col++;
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
QSizeF QgsComposerTableV2::fixedFrameSize( const int frameIndex ) const
|
|
{
|
|
Q_UNUSED( frameIndex );
|
|
return QSizeF( mTableSize.width(), 0 );
|
|
}
|
|
|
|
QSizeF QgsComposerTableV2::minFrameSize( const int frameIndex ) const
|
|
{
|
|
double height = 0;
|
|
if (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 )
|
|
|| ( mHeaderMode == QgsComposerTableV2::AllFrames ) )
|
|
{
|
|
//header required, force frame to be high enough for header
|
|
height = 2 * ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mHeaderFont );
|
|
}
|
|
return QSizeF( 0, height );
|
|
}
|
|
|
|
void QgsComposerTableV2::refreshAttributes()
|
|
{
|
|
mMaxColumnWidthMap.clear();
|
|
mTableContents.clear();
|
|
|
|
//get new contents
|
|
if ( !getTableContents( mTableContents ) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
void QgsComposerTableV2::recalculateFrameSizes()
|
|
{
|
|
mTableSize = QSizeF( totalWidth(), totalHeight() );
|
|
QgsComposerMultiFrame::recalculateFrameSizes();
|
|
}
|
|
|
|
bool QgsComposerTableV2::calculateMaxColumnWidths()
|
|
{
|
|
mMaxColumnWidthMap.clear();
|
|
|
|
//first, go through all the column headers and calculate the max width values
|
|
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();
|
|
}
|
|
else if ( mHeaderMode != QgsComposerTableV2::NoHeaders )
|
|
{
|
|
width = QgsComposerUtils::textWidthMM( mHeaderFont, ( *columnIt )->heading() );
|
|
}
|
|
|
|
mMaxColumnWidthMap.insert( col, width );
|
|
col++;
|
|
}
|
|
|
|
//next, go through all the table contents and calculate the max width values
|
|
QgsComposerTableContents::const_iterator rowIt = mTableContents.constBegin();
|
|
double currentCellTextWidth;
|
|
for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
|
|
{
|
|
QgsComposerTableRow::const_iterator colIt = rowIt->constBegin();
|
|
int columnNumber = 0;
|
|
for ( ; colIt != rowIt->constEnd(); ++colIt )
|
|
{
|
|
if ( mColumns.at( columnNumber )->width() <= 0 )
|
|
{
|
|
//column width set to automatic, so check content size
|
|
currentCellTextWidth = QgsComposerUtils::textWidthMM( mContentFont, ( *colIt ).toString() );
|
|
mMaxColumnWidthMap[ columnNumber ] = qMax( currentCellTextWidth, mMaxColumnWidthMap[ columnNumber ] );
|
|
}
|
|
columnNumber++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
double QgsComposerTableV2::totalWidth()
|
|
{
|
|
//check how much space each column needs
|
|
if ( !calculateMaxColumnWidths() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//adapt frame to total width
|
|
double totalWidth = 0;
|
|
QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
|
|
for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
|
|
{
|
|
totalWidth += maxColWidthIt.value();
|
|
}
|
|
totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
|
|
totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid ? mGridStrokeWidth : 0 );
|
|
|
|
return totalWidth;
|
|
}
|
|
|
|
double QgsComposerTableV2::totalHeight() const
|
|
{
|
|
double height = 0;
|
|
|
|
//loop through all existing frames to calculate how many rows are visible in each
|
|
//as the entire height of a frame may not be utilised for content rows
|
|
int rowsAlreadyShown = 0;
|
|
int numberExistingFrames = frameCount();
|
|
int rowsVisibleInLastFrame = 0;
|
|
double heightOfLastFrame = 0;
|
|
for ( int idx = 0; idx < numberExistingFrames; ++idx )
|
|
{
|
|
bool hasHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && idx == 0 )
|
|
|| ( mHeaderMode == QgsComposerTableV2::AllFrames ) );
|
|
heightOfLastFrame = frame( idx )->rect().height();
|
|
rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, hasHeader );
|
|
rowsAlreadyShown += rowsVisibleInLastFrame;
|
|
height += heightOfLastFrame;
|
|
if ( rowsAlreadyShown >= mTableContents.length() )
|
|
{
|
|
//shown entire contents of table, nothing remaining
|
|
return height;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if ( remainingRows <= 0 )
|
|
{
|
|
//no remaining rows
|
|
return height;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//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
|
|
{
|
|
//horizontal lines
|
|
if ( rows < 1 && !drawHeaderLines )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
|
|
double currentY = 0;
|
|
currentY = halfGridStrokeWidth;
|
|
if ( drawHeaderLines )
|
|
{
|
|
painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
|
|
currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
|
|
currentY += ( QgsComposerUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin );
|
|
}
|
|
for ( int row = 0; row < rows; ++row )
|
|
{
|
|
painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
|
|
currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
|
|
currentY += ( QgsComposerUtils::fontAscentMM( mContentFont ) + 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
|
|
{
|
|
//vertical lines
|
|
if ( numberRows < 1 && !hasHeader )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//calculate height of table within frame
|
|
double tableHeight = 0;
|
|
if ( hasHeader )
|
|
{
|
|
tableHeight += ( mShowGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + QgsComposerUtils::fontAscentMM( mHeaderFont );
|
|
}
|
|
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 );
|
|
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++;
|
|
}
|
|
}
|
|
|
|
void QgsComposerTableV2::recalculateTableSize()
|
|
{
|
|
recalculateFrameSizes();
|
|
|
|
//force recalculation of frame rects, so that they are set to the correct
|
|
//fixed and minimum frame sizes
|
|
recalculateFrameRects();
|
|
}
|
|
|
|
bool QgsComposerTableV2::contentsContainsRow( const QgsComposerTableContents &contents, const QgsComposerTableRow &row ) const
|
|
{
|
|
if ( contents.indexOf( row ) >= 0 )
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|