QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposertablev2.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposertablev2.cpp
3  ------------------
4  begin : July 2014
5  copyright : (C) 2014 by Nyall Dawson, Marco Hugentobler
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgscomposertablev2.h"
19 #include "qgscomposerutils.h"
20 #include "qgscomposertablecolumn.h"
21 #include "qgssymbollayerv2utils.h"
22 #include "qgscomposerframe.h"
23 
24 QgsComposerTableV2::QgsComposerTableV2( QgsComposition *composition, bool createUndoCommands )
25  : QgsComposerMultiFrame( composition, createUndoCommands )
26  , mCellMargin( 1.0 )
27  , mEmptyTableMode( HeadersOnly )
28  , mShowEmptyRows( false )
29  , mHeaderFontColor( Qt::black )
30  , mHeaderHAlignment( FollowColumn )
31  , mHeaderMode( FirstFrame )
32  , mContentFontColor( Qt::black )
33  , mShowGrid( true )
34  , mGridStrokeWidth( 0.5 )
35  , mGridColor( Qt::black )
36  , mBackgroundColor( Qt::white )
37 {
38 
39  if ( mComposition )
40  {
41  QObject::connect( mComposition, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SLOT( handleFrameRemoval( QgsComposerItem* ) ) );
42  }
43 
44  //get default composer font from settings
45  QSettings settings;
46  QString defaultFontString = settings.value( "/Composer/defaultFont" ).toString();
47  if ( !defaultFontString.isEmpty() )
48  {
49  mHeaderFont.setFamily( defaultFontString );
50  mContentFont.setFamily( defaultFontString );
51  }
52 }
53 
55  : QgsComposerMultiFrame( 0, false )
56  , mCellMargin( 1.0 )
57  , mEmptyTableMode( HeadersOnly )
58  , mShowEmptyRows( false )
59  , mHeaderFontColor( Qt::black )
60  , mHeaderHAlignment( FollowColumn )
61  , mHeaderMode( FirstFrame )
62  , mContentFontColor( Qt::black )
63  , mShowGrid( true )
64  , mGridStrokeWidth( 0.5 )
65  , mGridColor( Qt::black )
66  , mBackgroundColor( Qt::white )
67 {
68 
69 }
70 
72 {
73  qDeleteAll( mColumns );
74  mColumns.clear();
75 }
76 
77 bool QgsComposerTableV2::writeXML( QDomElement& elem, QDomDocument & doc, bool ignoreFrames ) const
78 {
79  elem.setAttribute( "cellMargin", QString::number( mCellMargin ) );
80  elem.setAttribute( "emptyTableMode", QString::number(( int )mEmptyTableMode ) );
81  elem.setAttribute( "emptyTableMessage", mEmptyTableMessage );
82  elem.setAttribute( "showEmptyRows", mShowEmptyRows );
83  elem.setAttribute( "headerFont", mHeaderFont.toString() );
84  elem.setAttribute( "headerFontColor", QgsSymbolLayerV2Utils::encodeColor( mHeaderFontColor ) );
85  elem.setAttribute( "headerHAlignment", QString::number(( int )mHeaderHAlignment ) );
86  elem.setAttribute( "headerMode", QString::number(( int )mHeaderMode ) );
87  elem.setAttribute( "contentFont", mContentFont.toString() );
88  elem.setAttribute( "contentFontColor", QgsSymbolLayerV2Utils::encodeColor( mContentFontColor ) );
89  elem.setAttribute( "gridStrokeWidth", QString::number( mGridStrokeWidth ) );
90  elem.setAttribute( "gridColor", QgsSymbolLayerV2Utils::encodeColor( mGridColor ) );
91  elem.setAttribute( "showGrid", mShowGrid );
92  elem.setAttribute( "backgroundColor", QgsSymbolLayerV2Utils::encodeColor( mBackgroundColor ) );
93 
94  //columns
95  QDomElement displayColumnsElem = doc.createElement( "displayColumns" );
96  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
97  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
98  {
99  QDomElement columnElem = doc.createElement( "column" );
100  ( *columnIt )->writeXML( columnElem, doc );
101  displayColumnsElem.appendChild( columnElem );
102  }
103  elem.appendChild( displayColumnsElem );
104 
105  bool state = _writeXML( elem, doc, ignoreFrames );
106  return state;
107 }
108 
109 bool QgsComposerTableV2::readXML( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames )
110 {
111  deleteFrames();
112 
113  //first create the frames
114  if ( !_readXML( itemElem, doc, ignoreFrames ) )
115  {
116  return false;
117  }
118 
119  if ( itemElem.isNull() )
120  {
121  return false;
122  }
123 
124  mEmptyTableMode = QgsComposerTableV2::EmptyTableMode( itemElem.attribute( "emptyTableMode", "0" ).toInt() );
125  mEmptyTableMessage = itemElem.attribute( "emptyTableMessage", tr( "No matching records" ) );
126  mShowEmptyRows = itemElem.attribute( "showEmptyRows", "0" ).toInt();
127  mHeaderFont.fromString( itemElem.attribute( "headerFont", "" ) );
128  mHeaderFontColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "headerFontColor", "0,0,0,255" ) );
129  mHeaderHAlignment = QgsComposerTableV2::HeaderHAlignment( itemElem.attribute( "headerHAlignment", "0" ).toInt() );
130  mHeaderMode = QgsComposerTableV2::HeaderMode( itemElem.attribute( "headerMode", "0" ).toInt() );
131  mContentFont.fromString( itemElem.attribute( "contentFont", "" ) );
132  mContentFontColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "contentFontColor", "0,0,0,255" ) );
133  mCellMargin = itemElem.attribute( "cellMargin", "1.0" ).toDouble();
134  mGridStrokeWidth = itemElem.attribute( "gridStrokeWidth", "0.5" ).toDouble();
135  mShowGrid = itemElem.attribute( "showGrid", "1" ).toInt();
136  mGridColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "gridColor", "0,0,0,255" ) );
137  mBackgroundColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "backgroundColor", "255,255,255,0" ) );
138 
139  //restore column specifications
140  qDeleteAll( mColumns );
141  mColumns.clear();
142  QDomNodeList columnsList = itemElem.elementsByTagName( "displayColumns" );
143  if ( columnsList.size() > 0 )
144  {
145  QDomElement columnsElem = columnsList.at( 0 ).toElement();
146  QDomNodeList columnEntryList = columnsElem.elementsByTagName( "column" );
147  for ( int i = 0; i < columnEntryList.size(); ++i )
148  {
149  QDomElement columnElem = columnEntryList.at( i ).toElement();
151  column->readXML( columnElem );
152  mColumns.append( column );
153  }
154  }
155 
156  return true;
157 }
158 
160 {
161  return mTableSize;
162 }
163 
164 int QgsComposerTableV2::rowsVisible( const int frameIndex ) const
165 {
166  //get frame extent
167  if ( frameIndex >= frameCount() )
168  {
169  return 0;
170  }
171  QRectF frameExtent = frame( frameIndex )->extent();
172 
173  bool includeHeader = false;
174  if (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 )
176  {
177  includeHeader = true;
178  }
179  return rowsVisible( frameExtent.height(), includeHeader );
180 }
181 
182 int QgsComposerTableV2::rowsVisible( const double frameHeight, const bool includeHeader ) const
183 {
184  //calculate header height
185  double headerHeight = 0;
186  if ( includeHeader )
187  {
188  //frame has a header
189  headerHeight = 2 * ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mHeaderFont );
190  }
191  else
192  {
193  //frame has no header text, just the stroke
194  headerHeight = ( mShowGrid ? mGridStrokeWidth : 0 );
195  }
196 
197  //remaining height available for content rows
198  double contentHeight = frameHeight - headerHeight;
199 
200  //calculate number of visible rows
201  double rowHeight = ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mContentFont );
202  return qMax( floor( contentHeight / rowHeight ), 0.0 );
203 }
204 
205 QPair< int, int > QgsComposerTableV2::rowRange( const QRectF &extent, const int frameIndex ) const
206 {
207  //calculate row height
208  if ( frameIndex >= frameCount() )
209  {
210  //bad frame index
211  return qMakePair( 0, 0 );
212  }
213 
214  //loop through all previous frames to calculate how many rows are visible in each
215  //as the entire height of a frame may not be utilised for content rows
216  int rowsAlreadyShown = 0;
217  for ( int idx = 0; idx < frameIndex; ++idx )
218  {
219  rowsAlreadyShown += rowsVisible( idx );
220  }
221 
222  double headerHeight = 0;
223  if (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 )
225  {
226  //frame has a header
227  headerHeight = 2 * ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mHeaderFont );
228  }
229  else
230  {
231  headerHeight = ( mShowGrid ? mGridStrokeWidth : 0 );
232  }
233 
234  //remaining height available for content rows
235  double contentHeight = extent.height() - headerHeight;
236  double rowHeight = ( mShowGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + QgsComposerUtils::fontAscentMM( mContentFont );
237 
238  //using zero based indexes
239  int firstVisible = qMin( rowsAlreadyShown, mTableContents.length() );
240  int rowsVisible = qMax( floor( contentHeight / rowHeight ), 0.0 );
241  int lastVisible = qMin( firstVisible + rowsVisible, mTableContents.length() );
242 
243  return qMakePair( firstVisible, lastVisible );
244 }
245 
246 
247 void QgsComposerTableV2::render( QPainter *p, const QRectF &renderExtent, const int frameIndex )
248 {
249  if ( !p )
250  {
251  return;
252  }
253 
254  bool emptyTable = mTableContents.length() == 0;
255  if ( emptyTable && mEmptyTableMode == QgsComposerTableV2::HideTable )
256  {
257  //empty table set to hide table mode, so don't draw anything
258  return;
259  }
260 
261  //calculate which rows to show in this frame
262  QPair< int, int > rowsToShow = rowRange( renderExtent, frameIndex );
263 
266  {
267  //exporting composition, so force an attribute refresh
268  //we do this in case vector layer has changed via an external source (eg, another database user)
270  }
271 
272  double gridSize = mShowGrid ? mGridStrokeWidth : 0;
273  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
274 
275  int col = 0;
276  double cellHeaderHeight = QgsComposerUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin;
277  double cellBodyHeight = QgsComposerUtils::fontAscentMM( mContentFont ) + 2 * mCellMargin;
278  QRectF cell;
279 
280  //calculate whether a header is required
281  bool drawHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 )
283  //calculate whether drawing table contents is required
284  bool drawContents = !( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage );
285 
286  int numberRowsToDraw = rowsToShow.second - rowsToShow.first;
287  if ( drawContents && mShowEmptyRows )
288  {
289  numberRowsToDraw = rowsVisible( frameIndex );
290  }
291  bool mergeCells = false;
292  if ( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage )
293  {
294  //draw a merged row for the empty table message
295  numberRowsToDraw++;
296  mergeCells = true;
297  }
298 
299  p->save();
300  //antialiasing on
301  p->setRenderHint( QPainter::Antialiasing, true );
302 
303  //draw table background
304  if ( mBackgroundColor.alpha() > 0 )
305  {
306  p->save();
307  p->setPen( Qt::NoPen );
308  p->setBrush( QBrush( mBackgroundColor ) );
309  double totalHeight = ( drawHeader || ( numberRowsToDraw > 0 ) ? gridSize : 0 ) +
310  ( drawHeader ? cellHeaderHeight + gridSize : 0.0 ) +
311  ( drawContents ? numberRowsToDraw : 1 ) * ( cellBodyHeight + gridSize );
312 
313  if ( totalHeight > 0 )
314  {
315  QRectF backgroundRect( 0, 0, mTableSize.width(), totalHeight );
316  p->drawRect( backgroundRect );
317  }
318  p->restore();
319  }
320 
321  //now draw the text
322  double currentX = gridSize;
323  double currentY;
324  p->setPen( Qt::SolidLine );
325  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
326  {
327  currentY = gridSize;
328  currentX += mCellMargin;
329 
330  Qt::TextFlag textFlag = ( Qt::TextFlag )0;
331  if (( *columnIt )->width() <= 0 )
332  {
333  //automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text
334  //which may slightly exceed the calculated width
335  //if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width
336  textFlag = Qt::TextDontClip;
337  }
338 
339  if ( drawHeader )
340  {
341  //draw the header
342  cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
343 
344  //calculate alignment of header
345  Qt::AlignmentFlag headerAlign = Qt::AlignLeft;
346  switch ( mHeaderHAlignment )
347  {
348  case FollowColumn:
349  headerAlign = ( *columnIt )->hAlignment();
350  break;
351  case HeaderLeft:
352  headerAlign = Qt::AlignLeft;
353  break;
354  case HeaderCenter:
355  headerAlign = Qt::AlignHCenter;
356  break;
357  case HeaderRight:
358  headerAlign = Qt::AlignRight;
359  break;
360  }
361 
362  QgsComposerUtils::drawText( p, cell, ( *columnIt )->heading(), mHeaderFont, mHeaderFontColor, headerAlign, Qt::AlignVCenter, textFlag );
363 
364  currentY += cellHeaderHeight;
365  currentY += gridSize;
366  }
367 
368  if ( drawContents )
369  {
370  //draw the attribute values
371  for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
372  {
373  cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellBodyHeight );
374 
375  QVariant cellContents = mTableContents.at( row ).at( col );
376  QString str = cellContents.toString();
377 
378  QgsComposerUtils::drawText( p, cell, str, mContentFont, mContentFontColor, ( *columnIt )->hAlignment(), Qt::AlignVCenter, textFlag );
379 
380  currentY += cellBodyHeight;
381  currentY += gridSize;
382  }
383  }
384 
385  currentX += mMaxColumnWidthMap[ col ];
386  currentX += mCellMargin;
387  currentX += gridSize;
388  col++;
389  }
390 
391  //and the borders
392  if ( mShowGrid )
393  {
394  QPen gridPen;
395  gridPen.setWidthF( mGridStrokeWidth );
396  gridPen.setColor( mGridColor );
397  gridPen.setJoinStyle( Qt::MiterJoin );
398  p->setPen( gridPen );
399  drawHorizontalGridLines( p, numberRowsToDraw, drawHeader );
400  drawVerticalGridLines( p, mMaxColumnWidthMap, numberRowsToDraw, drawHeader, mergeCells );
401  }
402 
403  //special case - no records and table is set to ShowMessage mode
404  if ( emptyTable && mEmptyTableMode == QgsComposerTableV2::ShowMessage )
405  {
406  double messageX = gridSize + mCellMargin;
407  double messageY = gridSize + ( drawHeader ? cellHeaderHeight + gridSize : 0 );
408  cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeight );
409  QgsComposerUtils::drawText( p, cell, mEmptyTableMessage, mContentFont, mContentFontColor, Qt::AlignHCenter, Qt::AlignVCenter, ( Qt::TextFlag )0 );
410  }
411 
412  p->restore();
413 
414 }
415 
416 void QgsComposerTableV2::setCellMargin( const double margin )
417 {
418  if ( margin == mCellMargin )
419  {
420  return;
421  }
422 
423  mCellMargin = margin;
424 
425  //since spacing has changed, we need to recalculate the table size
427 
428  emit changed();
429 }
430 
432 {
433  if ( mode == mEmptyTableMode )
434  {
435  return;
436  }
437 
438  mEmptyTableMode = mode;
439 
440  //since appearance has changed, we need to recalculate the table size
442 
443  emit changed();
444 }
445 
446 void QgsComposerTableV2::setEmptyTableMessage( const QString message )
447 {
448  if ( message == mEmptyTableMessage )
449  {
450  return;
451  }
452 
453  mEmptyTableMessage = message;
454 
455  //since message has changed, we need to recalculate the table size
457 
458  emit changed();
459 }
460 
461 void QgsComposerTableV2::setShowEmptyRows( const bool showEmpty )
462 {
463  if ( showEmpty == mShowEmptyRows )
464  {
465  return;
466  }
467 
468  mShowEmptyRows = showEmpty;
469  update();
470  emit changed();
471 }
472 
473 void QgsComposerTableV2::setHeaderFont( const QFont &font )
474 {
475  if ( font == mHeaderFont )
476  {
477  return;
478  }
479 
480  mHeaderFont = font;
481  //since font attributes have changed, we need to recalculate the table size
483 
484  emit changed();
485 }
486 
487 void QgsComposerTableV2::setHeaderFontColor( const QColor &color )
488 {
489  if ( color == mHeaderFontColor )
490  {
491  return;
492  }
493 
494  mHeaderFontColor = color;
495  update();
496 
497  emit changed();
498 }
499 
501 {
502  if ( alignment == mHeaderHAlignment )
503  {
504  return;
505  }
506 
507  mHeaderHAlignment = alignment;
508  update();
509 
510  emit changed();
511 }
512 
514 {
515  if ( mode == mHeaderMode )
516  {
517  return;
518  }
519 
520  mHeaderMode = mode;
522 
523  emit changed();
524 }
525 
526 void QgsComposerTableV2::setContentFont( const QFont &font )
527 {
528  if ( font == mContentFont )
529  {
530  return;
531  }
532 
533  mContentFont = font;
534  //since font attributes have changed, we need to recalculate the table size
536 
537  emit changed();
538 }
539 
540 void QgsComposerTableV2::setContentFontColor( const QColor &color )
541 {
542  if ( color == mContentFontColor )
543  {
544  return;
545  }
546 
547  mContentFontColor = color;
548  update();
549 
550  emit changed();
551 }
552 
553 void QgsComposerTableV2::setShowGrid( const bool showGrid )
554 {
555  if ( showGrid == mShowGrid )
556  {
557  return;
558  }
559 
561  //since grid spacing has changed, we need to recalculate the table size
563 
564  emit changed();
565 }
566 
567 void QgsComposerTableV2::setGridStrokeWidth( const double width )
568 {
569  if ( width == mGridStrokeWidth )
570  {
571  return;
572  }
573 
574  mGridStrokeWidth = width;
575  //since grid spacing has changed, we need to recalculate the table size
577 
578  emit changed();
579 }
580 
581 void QgsComposerTableV2::setGridColor( const QColor &color )
582 {
583  if ( color == mGridColor )
584  {
585  return;
586  }
587 
588  mGridColor = color;
589  update();
590 
591  emit changed();
592 }
593 
594 void QgsComposerTableV2::setBackgroundColor( const QColor &color )
595 {
596  if ( color == mBackgroundColor )
597  {
598  return;
599  }
600 
601  mBackgroundColor = color;
602  update();
603 
604  emit changed();
605 }
606 
608 {
609  //remove existing columns
610  qDeleteAll( mColumns );
611  mColumns.clear();
612 
613  mColumns.append( columns );
614 }
615 
616 QMap<int, QString> QgsComposerTableV2::headerLabels() const
617 {
618  QMap<int, QString> headers;
619 
620  QgsComposerTableColumns::const_iterator columnIt = mColumns.constBegin();
621  int col = 0;
622  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
623  {
624  headers.insert( col, ( *columnIt )->heading() );
625  col++;
626  }
627  return headers;
628 }
629 
630 QSizeF QgsComposerTableV2::fixedFrameSize( const int frameIndex ) const
631 {
632  Q_UNUSED( frameIndex );
633  return QSizeF( mTableSize.width(), 0 );
634 }
635 
636 QSizeF QgsComposerTableV2::minFrameSize( const int frameIndex ) const
637 {
638  double height = 0;
639  if (( mHeaderMode == QgsComposerTableV2::FirstFrame && frameIndex < 1 )
641  {
642  //header required, force frame to be high enough for header
644  }
645  return QSizeF( 0, height );
646 }
647 
649 {
650  mMaxColumnWidthMap.clear();
651  mTableContents.clear();
652 
653  //get new contents
655  {
656  return;
657  }
658 }
659 
661 {
662  mTableSize = QSizeF( totalWidth(), totalHeight() );
664 }
665 
667 {
668  mMaxColumnWidthMap.clear();
669 
670  //first, go through all the column headers and calculate the max width values
671  QgsComposerTableColumns::const_iterator columnIt = mColumns.constBegin();
672  int col = 0;
673  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
674  {
675  double width = 0;
676  if (( *columnIt )->width() > 0 )
677  {
678  //column has manually specified width
679  width = ( *columnIt )->width();
680  }
682  {
683  width = QgsComposerUtils::textWidthMM( mHeaderFont, ( *columnIt )->heading() );
684  }
685 
686  mMaxColumnWidthMap.insert( col, width );
687  col++;
688  }
689 
690  //next, go through all the table contents and calculate the max width values
691  QgsComposerTableContents::const_iterator rowIt = mTableContents.constBegin();
692  double currentCellTextWidth;
693  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
694  {
695  QgsComposerTableRow::const_iterator colIt = rowIt->constBegin();
696  int columnNumber = 0;
697  for ( ; colIt != rowIt->constEnd(); ++colIt )
698  {
699  if ( mColumns.at( columnNumber )->width() <= 0 )
700  {
701  //column width set to automatic, so check content size
702  currentCellTextWidth = QgsComposerUtils::textWidthMM( mContentFont, ( *colIt ).toString() );
703  mMaxColumnWidthMap[ columnNumber ] = qMax( currentCellTextWidth, mMaxColumnWidthMap[ columnNumber ] );
704  }
705  columnNumber++;
706  }
707  }
708 
709  return true;
710 }
711 
713 {
714  //check how much space each column needs
715  if ( !calculateMaxColumnWidths() )
716  {
717  return 0;
718  }
719 
720  //adapt frame to total width
721  double totalWidth = 0;
722  QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
723  for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
724  {
725  totalWidth += maxColWidthIt.value();
726  }
727  totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
728  totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid ? mGridStrokeWidth : 0 );
729 
730  return totalWidth;
731 }
732 
734 {
735  double height = 0;
736 
737  //loop through all existing frames to calculate how many rows are visible in each
738  //as the entire height of a frame may not be utilised for content rows
739  int rowsAlreadyShown = 0;
740  int numberExistingFrames = frameCount();
741  int rowsVisibleInLastFrame = 0;
742  double heightOfLastFrame = 0;
743  for ( int idx = 0; idx < numberExistingFrames; ++idx )
744  {
745  bool hasHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && idx == 0 )
747  heightOfLastFrame = frame( idx )->rect().height();
748  rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, hasHeader );
749  rowsAlreadyShown += rowsVisibleInLastFrame;
750  height += heightOfLastFrame;
751  if ( rowsAlreadyShown >= mTableContents.length() )
752  {
753  //shown entire contents of table, nothing remaining
754  return height;
755  }
756  }
757 
759  {
760  heightOfLastFrame = mComposition->paperHeight();
761  bool hasHeader = (( mHeaderMode == QgsComposerTableV2::FirstFrame && numberExistingFrames < 1 )
763  rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, hasHeader );
764  }
765 
766  //calculate how many rows left to show
767  int remainingRows = mTableContents.length() - rowsAlreadyShown;
768 
769  if ( remainingRows <= 0 )
770  {
771  //no remaining rows
772  return height;
773  }
774 
775  if ( rowsVisibleInLastFrame < 1 )
776  {
777  //if no rows are visible in the last frame, calculation of missing frames
778  //is impossible. So just return total height of existing frames
779  return height;
780  }
781 
782  //rows remain unshown -- how many extra frames would we need to complete the table?
783  //assume all added frames are same size as final frame
784  int numberFramesMissing = ceil(( double )remainingRows / ( double )rowsVisibleInLastFrame );
785  height += heightOfLastFrame * numberFramesMissing;
786  return height;
787 }
788 
789 void QgsComposerTableV2::drawHorizontalGridLines( QPainter *painter, const int rows, const bool drawHeaderLines ) const
790 {
791  //horizontal lines
792  if ( rows < 1 && !drawHeaderLines )
793  {
794  return;
795  }
796 
797  double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
798  double currentY = 0;
799  currentY = halfGridStrokeWidth;
800  if ( drawHeaderLines )
801  {
802  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
803  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
804  currentY += ( QgsComposerUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin );
805  }
806  for ( int row = 0; row < rows; ++row )
807  {
808  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
809  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
811  }
812  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
813 }
814 
815 void QgsComposerTableV2::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, const int numberRows, const bool hasHeader, const bool mergeCells ) const
816 {
817  //vertical lines
818  if ( numberRows < 1 && !hasHeader )
819  {
820  return;
821  }
822 
823  //calculate height of table within frame
824  double tableHeight = 0;
825  if ( hasHeader )
826  {
828  }
829  tableHeight += ( mShowGrid ? mGridStrokeWidth : 0 );
830  double headerHeight = tableHeight;
831  tableHeight += numberRows * (( mShowGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + QgsComposerUtils::fontAscentMM( mContentFont ) );
832 
833  double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
834  double currentX = halfGridStrokeWidth;
835  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
836  currentX += ( mShowGrid ? mGridStrokeWidth : 0 );
837  QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
838  int col = 1;
839  for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
840  {
841  currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
842  if ( col == maxWidthMap.size() || !mergeCells )
843  {
844  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
845  }
846  else if ( hasHeader )
847  {
848  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, headerHeight - halfGridStrokeWidth ) );
849  }
850 
851  currentX += ( mShowGrid ? mGridStrokeWidth : 0 );
852  col++;
853  }
854 }
855 
857 {
859 
860  //force recalculation of frame rects, so that they are set to the correct
861  //fixed and minimum frame sizes
863 }
864 
866 {
867  if ( contents.indexOf( row ) >= 0 )
868  {
869  return true;
870  }
871  else
872  {
873  return false;
874  }
875 }