QGIS API Documentation  master-3f58142
src/core/composer/qgscomposerlegend.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002                          qgscomposerlegend.cpp  -  description
00003                          ---------------------
00004     begin                : June 2008
00005     copyright            : (C) 2008 by Marco Hugentobler
00006     email                : marco dot hugentobler at karto dot baug dot ethz dot ch
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  *                                                                         *
00011  *   This program is free software; you can redistribute it and/or modify  *
00012  *   it under the terms of the GNU General Public License as published by  *
00013  *   the Free Software Foundation; either version 2 of the License, or     *
00014  *   (at your option) any later version.                                   *
00015  *                                                                         *
00016  ***************************************************************************/
00017 #include <limits>
00018 
00019 #include "qgscomposerlegendstyle.h"
00020 #include "qgscomposerlegend.h"
00021 #include "qgscomposerlegenditem.h"
00022 #include "qgscomposermap.h"
00023 #include "qgscomposition.h"
00024 #include "qgslogger.h"
00025 #include "qgsmaplayer.h"
00026 #include "qgsmaplayerregistry.h"
00027 #include "qgsmaprenderer.h"
00028 #include "qgssymbolv2.h"
00029 #include <QDomDocument>
00030 #include <QDomElement>
00031 #include <QPainter>
00032 
00033 QgsComposerLegend::QgsComposerLegend( QgsComposition* composition )
00034     : QgsComposerItem( composition )
00035     , mTitle( tr( "Legend" ) )
00036     , mFontColor( QColor( 0, 0, 0 ) )
00037     , mBoxSpace( 2 )
00038     , mColumnSpace( 2 )
00039     , mColumnCount( 1 )
00040     , mComposerMap( 0 )
00041     , mSplitLayer( false )
00042     , mEqualColumnWidth( false )
00043 {
00044   setStyleMargin( QgsComposerLegendStyle::Group, QgsComposerLegendStyle::Top, 2 );
00045   setStyleMargin( QgsComposerLegendStyle::Subgroup, QgsComposerLegendStyle::Top, 2 );
00046   setStyleMargin( QgsComposerLegendStyle::Symbol, QgsComposerLegendStyle::Top, 2 );
00047   setStyleMargin( QgsComposerLegendStyle::SymbolLabel, QgsComposerLegendStyle::Top, 2 );
00048   setStyleMargin( QgsComposerLegendStyle::SymbolLabel, QgsComposerLegendStyle::Left, 2 );
00049   rstyle( QgsComposerLegendStyle::Title ).rfont().setPointSizeF( 16.0 );
00050   rstyle( QgsComposerLegendStyle::Group ).rfont().setPointSizeF( 14.0 );
00051   rstyle( QgsComposerLegendStyle::Subgroup ).rfont().setPointSizeF( 12.0 );
00052   rstyle( QgsComposerLegendStyle::SymbolLabel ).rfont().setPointSizeF( 12.0 );
00053 
00054   mSymbolWidth = 7;
00055   mSymbolHeight = 4;
00056   mWrapChar = "";
00057   mlineSpacing = 1.5;
00058   adjustBoxSize();
00059 
00060   connect( &mLegendModel, SIGNAL( layersChanged() ), this, SLOT( synchronizeWithModel() ) );
00061 }
00062 
00063 QgsComposerLegend::QgsComposerLegend(): QgsComposerItem( 0 ), mComposerMap( 0 )
00064 {
00065 
00066 }
00067 
00068 QgsComposerLegend::~QgsComposerLegend()
00069 {
00070 
00071 }
00072 
00073 void QgsComposerLegend::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
00074 {
00075   Q_UNUSED( itemStyle );
00076   Q_UNUSED( pWidget );
00077   paintAndDetermineSize( painter );
00078 }
00079 
00080 QSizeF QgsComposerLegend::paintAndDetermineSize( QPainter* painter )
00081 {
00082   QSizeF size( 0, 0 );
00083   QStandardItem* rootItem = mLegendModel.invisibleRootItem();
00084   if ( !rootItem ) return size;
00085 
00086   if ( painter )
00087   {
00088     painter->save();
00089     drawBackground( painter );
00090     painter->setPen( QPen( QColor( 0, 0, 0 ) ) );
00091   }
00092 
00093   QList<Atom> atomList = createAtomList( rootItem, mSplitLayer );
00094 
00095   setColumns( atomList );
00096 
00097   qreal maxColumnWidth = 0;
00098   if ( mEqualColumnWidth )
00099   {
00100     foreach ( Atom atom, atomList )
00101     {
00102       maxColumnWidth = qMax( atom.size.width(), maxColumnWidth );
00103     }
00104   }
00105 
00106   QSizeF titleSize = drawTitle();
00107   // Using mGroupSpace as space between legend title and first atom in column
00108   //double columnTop = mBoxSpace + titleSize.height() + mGroupSpace;
00109   // TODO: use margin of first used style
00110   double columnTop = mBoxSpace + titleSize.height() + style( QgsComposerLegendStyle::Group ).margin( QgsComposerLegendStyle::Top );
00111 
00112   QPointF point( mBoxSpace, columnTop );
00113   // bool firstInColumn = true;
00114   double columnMaxHeight = 0;
00115   qreal columnWidth = 0;
00116   int column = 0;
00117   foreach ( Atom atom, atomList )
00118   {
00119     if ( atom.column > column )
00120     {
00121       // Switch to next column
00122       if ( mEqualColumnWidth )
00123       {
00124         point.rx() += mColumnSpace + maxColumnWidth;
00125       }
00126       else
00127       {
00128         point.rx() += mColumnSpace + columnWidth;
00129       }
00130       point.ry() = columnTop;
00131       columnWidth = 0;
00132       column++;
00133       // firstInColumn = true;
00134     }
00135     // Add space if necessary, unfortunately it depends on first nucleon
00136     //if ( !firstInColumn )
00137     //{
00138     point.ry() += spaceAboveAtom( atom );
00139     //}
00140 
00141     QSizeF atomSize = drawAtom( atom, painter, point );
00142     columnWidth = qMax( atomSize.width(), columnWidth );
00143 
00144     point.ry() += atom.size.height();
00145     columnMaxHeight = qMax( point.y() - columnTop, columnMaxHeight );
00146 
00147     // firstInColumn = false;
00148   }
00149   point.rx() += columnWidth + mBoxSpace;
00150 
00151   size.rheight() = columnTop + columnMaxHeight + mBoxSpace;
00152   size.rwidth() = point.x();
00153 
00154   // Now we know total width and can draw the title centered
00155   if ( !mTitle.isEmpty() )
00156   {
00157     // For multicolumn center if we stay in totalWidth, otherwise allign to left
00158     // and expand total width. With single column keep alligned to left be cause
00159     // it looks better alligned with items bellow instead of centered
00160     Qt::AlignmentFlag halignment;
00161     if ( mColumnCount > 1 && titleSize.width() + 2 * mBoxSpace < size.width() )
00162     {
00163       halignment = Qt::AlignHCenter;
00164       point.rx() = mBoxSpace + size.rwidth() / 2;
00165     }
00166     else
00167     {
00168       halignment = Qt::AlignLeft;
00169       point.rx() = mBoxSpace;
00170       size.rwidth() = qMax( titleSize.width() + 2 * mBoxSpace, size.width() );
00171     }
00172     point.ry() = mBoxSpace;
00173     drawTitle( painter, point, halignment );
00174   }
00175 
00176   //adjust box if width or height is to small
00177   if ( painter && size.height() > rect().height() )
00178   {
00179     setSceneRect( QRectF( transform().dx(), transform().dy(), rect().width(), size.height() ) );
00180   }
00181   if ( painter && size.width() > rect().width() )
00182   {
00183     setSceneRect( QRectF( transform().dx(), transform().dy(), size.width(), rect().height() ) );
00184   }
00185 
00186   if ( painter )
00187   {
00188     painter->restore();
00189 
00190     //draw frame and selection boxes if necessary
00191     drawFrame( painter );
00192     if ( isSelected() )
00193     {
00194       drawSelectionBoxes( painter );
00195     }
00196   }
00197 
00198   return size;
00199 }
00200 
00201 QSizeF QgsComposerLegend::drawTitle( QPainter* painter, QPointF point, Qt::AlignmentFlag halignment )
00202 {
00203   QSizeF size( 0, 0 );
00204   if ( mTitle.isEmpty() ) return size;
00205 
00206   QStringList lines = splitStringForWrapping( mTitle );
00207 
00208   double y = point.y();
00209 
00210   if ( painter ) painter->setPen( mFontColor );
00211 
00212   for ( QStringList::Iterator titlePart = lines.begin(); titlePart != lines.end(); ++titlePart )
00213   {
00214     // it does not draw the last world if rectangle width is exactly text width
00215     qreal width = textWidthMillimeters( styleFont( QgsComposerLegendStyle::Title ), *titlePart ) + 1;
00216     qreal height = fontAscentMillimeters( styleFont( QgsComposerLegendStyle::Title ) ) + fontDescentMillimeters( styleFont( QgsComposerLegendStyle::Title ) );
00217 
00218     double left = halignment == Qt::AlignLeft ?  point.x() : point.x() - width / 2;
00219 
00220     QRectF rect( left, y, width, height );
00221 
00222     if ( painter ) drawText( painter, rect, *titlePart, styleFont( QgsComposerLegendStyle::Title ), halignment, Qt::AlignVCenter );
00223 
00224     size.rwidth() = qMax( width, size.width() );
00225 
00226     y += height;
00227     if ( titlePart != lines.end() )
00228     {
00229       y += mlineSpacing;
00230     }
00231   }
00232   size.rheight() = y - point.y();
00233 
00234   return size;
00235 }
00236 
00237 
00238 QSizeF QgsComposerLegend::drawGroupItemTitle( QgsComposerGroupItem* groupItem, QPainter* painter, QPointF point )
00239 {
00240   QSizeF size( 0, 0 );
00241   if ( !groupItem ) return size;
00242 
00243   double y = point.y();
00244 
00245   if ( painter ) painter->setPen( mFontColor );
00246 
00247   QStringList lines = splitStringForWrapping( groupItem->text() );
00248   for ( QStringList::Iterator groupPart = lines.begin(); groupPart != lines.end(); ++groupPart )
00249   {
00250     y += fontAscentMillimeters( styleFont( groupItem->style() ) );
00251     if ( painter ) drawText( painter, point.x(), y, *groupPart, styleFont( groupItem->style() ) );
00252     qreal width = textWidthMillimeters( styleFont( groupItem->style() ), *groupPart );
00253     size.rwidth() = qMax( width, size.width() );
00254     if ( groupPart != lines.end() )
00255     {
00256       y += mlineSpacing;
00257     }
00258   }
00259   size.rheight() = y - point.y();
00260   return size;
00261 }
00262 
00263 QSizeF QgsComposerLegend::drawLayerItemTitle( QgsComposerLayerItem* layerItem, QPainter* painter, QPointF point )
00264 {
00265   QSizeF size( 0, 0 );
00266   if ( !layerItem ) return size;
00267 
00268   //Let the user omit the layer title item by having an empty layer title string
00269   if ( layerItem->text().isEmpty() ) return size;
00270 
00271   double y = point.y();
00272 
00273   if ( painter ) painter->setPen( mFontColor );
00274 
00275   QStringList lines = splitStringForWrapping( layerItem->text() );
00276   for ( QStringList::Iterator layerItemPart = lines.begin(); layerItemPart != lines.end(); ++layerItemPart )
00277   {
00278     y += fontAscentMillimeters( styleFont( layerItem->style() ) );
00279     if ( painter ) drawText( painter, point.x(), y, *layerItemPart , styleFont( layerItem->style() ) );
00280     qreal width = textWidthMillimeters( styleFont( layerItem->style() ), *layerItemPart );
00281     size.rwidth() = qMax( width, size.width() );
00282     if ( layerItemPart != lines.end() )
00283     {
00284       y += mlineSpacing;
00285     }
00286   }
00287   size.rheight() = y - point.y();
00288 
00289   return size;
00290 }
00291 
00292 void QgsComposerLegend::adjustBoxSize()
00293 {
00294   QSizeF size = paintAndDetermineSize( 0 );
00295   QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
00296   if ( size.isValid() )
00297   {
00298     setSceneRect( QRectF( transform().dx(), transform().dy(), size.width(), size.height() ) );
00299   }
00300 }
00301 
00302 QgsComposerLegend::Nucleon QgsComposerLegend::drawSymbolItem( QgsComposerLegendItem* symbolItem, QPainter* painter, QPointF point, double labelXOffset )
00303 {
00304   QSizeF symbolSize( 0, 0 );
00305   QSizeF labelSize( 0, 0 );
00306   if ( !symbolItem ) return Nucleon();
00307 
00308   double textHeight = fontHeightCharacterMM( styleFont( QgsComposerLegendStyle::SymbolLabel ), QChar( '0' ) );
00309   // itemHeight here is not realy item height, it is only for symbol
00310   // vertical alignment purpose, i.e. ok take single line height
00311   // if there are more lines, thos run under the symbol
00312   double itemHeight = qMax( mSymbolHeight, textHeight );
00313 
00314   //real symbol height. Can be different from standard height in case of point symbols
00315   double realSymbolHeight;
00316 
00317   QgsComposerLayerItem* layerItem = dynamic_cast<QgsComposerLayerItem*>( symbolItem->parent() );
00318 
00319 #if 0
00320   int opacity = 255;
00321   if ( layerItem )
00322   {
00323     QgsMapLayer* currentLayer = QgsMapLayerRegistry::instance()->mapLayer( layerItem->layerID() );
00324     if ( currentLayer )
00325     {
00326       opacity = currentLayer->getTransparency();
00327     }
00328   }
00329 #endif
00330 
00331   QString text = symbolItem->text();
00332   if ( text.isEmpty() )
00333   {
00334     // Use layer label, used for single symbols
00335     text = layerItem->text();
00336   }
00337 
00338   QStringList lines = splitStringForWrapping( text );
00339 
00340   QgsSymbolV2* symbolNg = 0;
00341   QgsComposerSymbolV2Item* symbolV2Item = dynamic_cast<QgsComposerSymbolV2Item*>( symbolItem );
00342   if ( symbolV2Item )
00343   {
00344     symbolNg = symbolV2Item->symbolV2();
00345   }
00346   QgsComposerRasterSymbolItem* rasterItem = dynamic_cast<QgsComposerRasterSymbolItem*>( symbolItem );
00347 
00348   double x = point.x();
00349   if ( symbolNg ) //item with symbol NG?
00350   {
00351     // must be called also with painter=0 to get real size
00352     drawSymbolV2( painter, symbolNg, point.y() + ( itemHeight - mSymbolHeight ) / 2, x, realSymbolHeight );
00353     symbolSize.rwidth() = qMax( x - point.x(), mSymbolWidth );
00354     symbolSize.rheight() = qMax( realSymbolHeight, mSymbolHeight );
00355   }
00356   else if ( rasterItem )
00357   {
00358     if ( painter )
00359     {
00360       painter->setBrush( rasterItem->color() );
00361       painter->drawRect( QRectF( point.x(), point.y() + ( itemHeight - mSymbolHeight ) / 2, mSymbolWidth, mSymbolHeight ) );
00362     }
00363     symbolSize.rwidth() = mSymbolWidth;
00364     symbolSize.rheight() = mSymbolHeight;
00365   }
00366   else //item with icon?
00367   {
00368     QIcon symbolIcon = symbolItem->icon();
00369     if ( !symbolIcon.isNull() )
00370     {
00371       if ( painter ) symbolIcon.paint( painter, point.x(), point.y() + ( itemHeight - mSymbolHeight ) / 2, mSymbolWidth, mSymbolHeight );
00372       symbolSize.rwidth() = mSymbolWidth;
00373       symbolSize.rheight() = mSymbolHeight;
00374     }
00375   }
00376 
00377   if ( painter ) painter->setPen( mFontColor );
00378 
00379   //double labelX = point.x() + labelXOffset; // + mIconLabelSpace;
00380   double labelX = point.x() + qMax(( double ) symbolSize.width(), labelXOffset );
00381 
00382   // Vertical alignment of label with symbol:
00383   // a) label height < symbol height: label centerd with symbol
00384   // b) label height > symbol height: label starts at top and runs under symbol
00385 
00386   labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * mlineSpacing;
00387 
00388   double labelY;
00389   if ( labelSize.height() < symbolSize.height() )
00390   {
00391     labelY = point.y() +  symbolSize.height() / 2 + textHeight / 2;
00392   }
00393   else
00394   {
00395     labelY = point.y() + textHeight;
00396   }
00397 
00398   for ( QStringList::Iterator itemPart = lines.begin(); itemPart != lines.end(); ++itemPart )
00399   {
00400     if ( painter ) drawText( painter, labelX, labelY, *itemPart , styleFont( QgsComposerLegendStyle::SymbolLabel ) );
00401     labelSize.rwidth() = qMax( textWidthMillimeters( styleFont( QgsComposerLegendStyle::SymbolLabel ),  *itemPart ), double( labelSize.width() ) );
00402     if ( itemPart != lines.end() )
00403     {
00404       labelY += mlineSpacing + textHeight;
00405     }
00406   }
00407 
00408   Nucleon nucleon;
00409   nucleon.item = symbolItem;
00410   nucleon.symbolSize = symbolSize;
00411   nucleon.labelSize = labelSize;
00412   //QgsDebugMsg( QString( "symbol height = %1 label height = %2").arg( symbolSize.height()).arg( labelSize.height() ));
00413   double width = qMax(( double ) symbolSize.width(), labelXOffset ) + labelSize.width();
00414   double height = qMax( symbolSize.height(), labelSize.height() );
00415   nucleon.size = QSizeF( width, height );
00416   return nucleon;
00417 }
00418 
00419 
00420 void QgsComposerLegend::drawSymbolV2( QPainter* p, QgsSymbolV2* s, double currentYCoord, double& currentXPosition, double& symbolHeight ) const
00421 {
00422   if ( !s )
00423   {
00424     return;
00425   }
00426 
00427   double rasterScaleFactor = 1.0;
00428   if ( p )
00429   {
00430     QPaintDevice* paintDevice = p->device();
00431     if ( !paintDevice )
00432     {
00433       return;
00434     }
00435     rasterScaleFactor = ( paintDevice->logicalDpiX() + paintDevice->logicalDpiY() ) / 2.0 / 25.4;
00436   }
00437 
00438   //consider relation to composer map for symbol sizes in mm
00439   bool sizeInMapUnits = s->outputUnit() == QgsSymbolV2::MapUnit;
00440   double mmPerMapUnit = 1;
00441   if ( mComposerMap )
00442   {
00443     mmPerMapUnit = mComposerMap->mapUnitsToMM();
00444   }
00445   QgsMarkerSymbolV2* markerSymbol = dynamic_cast<QgsMarkerSymbolV2*>( s );
00446 
00447   //Consider symbol size for point markers
00448   double height = mSymbolHeight;
00449   double width = mSymbolWidth;
00450   double size = 0;
00451   //Center small marker symbols
00452   double widthOffset = 0;
00453   double heightOffset = 0;
00454 
00455   if ( markerSymbol )
00456   {
00457     size = markerSymbol->size();
00458     height = size;
00459     width = size;
00460     if ( mComposerMap && sizeInMapUnits )
00461     {
00462       height *= mmPerMapUnit;
00463       width *= mmPerMapUnit;
00464       markerSymbol->setSize( width );
00465     }
00466     if ( width < mSymbolWidth )
00467     {
00468       widthOffset = ( mSymbolWidth - width ) / 2.0;
00469     }
00470     if ( height < mSymbolHeight )
00471     {
00472       heightOffset = ( mSymbolHeight - height ) / 2.0;
00473     }
00474   }
00475 
00476   if ( p )
00477   {
00478     p->save();
00479     p->translate( currentXPosition + widthOffset, currentYCoord + heightOffset );
00480     p->scale( 1.0 / rasterScaleFactor, 1.0 / rasterScaleFactor );
00481 
00482     if ( markerSymbol && sizeInMapUnits )
00483     {
00484       s->setOutputUnit( QgsSymbolV2::MM );
00485     }
00486 
00487     s->drawPreviewIcon( p, QSize( width * rasterScaleFactor, height * rasterScaleFactor ) );
00488 
00489     if ( markerSymbol && sizeInMapUnits )
00490     {
00491       s->setOutputUnit( QgsSymbolV2::MapUnit );
00492       markerSymbol->setSize( size );
00493     }
00494     p->restore();
00495   }
00496   currentXPosition += width;
00497   currentXPosition += 2 * widthOffset;
00498   symbolHeight = height + 2 * heightOffset;
00499 }
00500 
00501 
00502 QStringList QgsComposerLegend::layerIdList() const
00503 {
00504   //take layer list from map renderer (to have legend order)
00505   if ( mComposition )
00506   {
00507     QgsMapRenderer* r = mComposition->mapRenderer();
00508     if ( r )
00509     {
00510       return r->layerSet();
00511     }
00512   }
00513   return QStringList();
00514 }
00515 
00516 void QgsComposerLegend::synchronizeWithModel()
00517 {
00518   QgsDebugMsg( "Entered" );
00519   adjustBoxSize();
00520   update();
00521 }
00522 
00523 void QgsComposerLegend::setStyleFont( QgsComposerLegendStyle::Style s, const QFont& f )
00524 {
00525   rstyle( s ).setFont( f );
00526 }
00527 
00528 void QgsComposerLegend::setStyleMargin( QgsComposerLegendStyle::Style s, double margin )
00529 {
00530   rstyle( s ).setMargin( margin );
00531 }
00532 
00533 void QgsComposerLegend::setStyleMargin( QgsComposerLegendStyle::Style s, QgsComposerLegendStyle::Side side, double margin )
00534 {
00535   rstyle( s ).setMargin( side, margin );
00536 }
00537 
00538 void QgsComposerLegend::updateLegend()
00539 {
00540   mLegendModel.setLayerSet( layerIdList() );
00541   adjustBoxSize();
00542   update();
00543 }
00544 
00545 bool QgsComposerLegend::writeXML( QDomElement& elem, QDomDocument & doc ) const
00546 {
00547   if ( elem.isNull() )
00548   {
00549     return false;
00550   }
00551 
00552   QDomElement composerLegendElem = doc.createElement( "ComposerLegend" );
00553   elem.appendChild( composerLegendElem );
00554 
00555   //write general properties
00556   composerLegendElem.setAttribute( "title", mTitle );
00557   composerLegendElem.setAttribute( "columnCount", QString::number( mColumnCount ) );
00558   composerLegendElem.setAttribute( "splitLayer", QString::number( mSplitLayer ) );
00559   composerLegendElem.setAttribute( "equalColumnWidth", QString::number( mEqualColumnWidth ) );
00560 
00561   composerLegendElem.setAttribute( "boxSpace", QString::number( mBoxSpace ) );
00562   composerLegendElem.setAttribute( "columnSpace", QString::number( mColumnSpace ) );
00563 
00564   composerLegendElem.setAttribute( "symbolWidth", QString::number( mSymbolWidth ) );
00565   composerLegendElem.setAttribute( "symbolHeight", QString::number( mSymbolHeight ) );
00566   composerLegendElem.setAttribute( "wrapChar", mWrapChar );
00567   composerLegendElem.setAttribute( "fontColor", mFontColor.name() );
00568 
00569   if ( mComposerMap )
00570   {
00571     composerLegendElem.setAttribute( "map", mComposerMap->id() );
00572   }
00573 
00574   QDomElement composerLegendStyles = doc.createElement( "styles" );
00575   composerLegendElem.appendChild( composerLegendStyles );
00576 
00577   style( QgsComposerLegendStyle::Title ).writeXML( "title", composerLegendStyles, doc );
00578   style( QgsComposerLegendStyle::Group ).writeXML( "group", composerLegendStyles, doc );
00579   style( QgsComposerLegendStyle::Subgroup ).writeXML( "subgroup", composerLegendStyles, doc );
00580   style( QgsComposerLegendStyle::Symbol ).writeXML( "symbol", composerLegendStyles, doc );
00581   style( QgsComposerLegendStyle::SymbolLabel ).writeXML( "symbolLabel", composerLegendStyles, doc );
00582 
00583   //write model properties
00584   mLegendModel.writeXML( composerLegendElem, doc );
00585 
00586   return _writeXML( composerLegendElem, doc );
00587 }
00588 
00589 bool QgsComposerLegend::readXML( const QDomElement& itemElem, const QDomDocument& doc )
00590 {
00591   if ( itemElem.isNull() )
00592   {
00593     return false;
00594   }
00595 
00596   //read general properties
00597   mTitle = itemElem.attribute( "title" );
00598   mColumnCount = itemElem.attribute( "columnCount", "1" ).toInt();
00599   if ( mColumnCount < 1 ) mColumnCount = 1;
00600   mSplitLayer = itemElem.attribute( "splitLayer", "0" ).toInt() == 1;
00601   mEqualColumnWidth = itemElem.attribute( "equalColumnWidth", "0" ).toInt() == 1;
00602 
00603   QDomNodeList stylesNodeList = itemElem.elementsByTagName( "styles" );
00604   if ( stylesNodeList.size() > 0 )
00605   {
00606     QDomNode stylesNode = stylesNodeList.at( 0 );
00607     for ( int i = 0; i < stylesNode.childNodes().size(); i++ )
00608     {
00609       QDomElement styleElem = stylesNode.childNodes().at( i ).toElement();
00610       QgsComposerLegendStyle style;
00611       style.readXML( styleElem, doc );
00612       QString name = styleElem.attribute( "name" );
00613       QgsComposerLegendStyle::Style s;
00614       if ( name == "title" ) s = QgsComposerLegendStyle::Title;
00615       else if ( name == "group" ) s = QgsComposerLegendStyle::Group;
00616       else if ( name == "subgroup" ) s = QgsComposerLegendStyle::Subgroup;
00617       else if ( name == "symbol" ) s = QgsComposerLegendStyle::Symbol;
00618       else if ( name == "symbolLabel" ) s = QgsComposerLegendStyle::SymbolLabel;
00619       else continue;
00620       setStyle( s, style );
00621     }
00622   }
00623 
00624   //font color
00625   mFontColor.setNamedColor( itemElem.attribute( "fontColor", "#000000" ) );
00626 
00627   //spaces
00628   mBoxSpace = itemElem.attribute( "boxSpace", "2.0" ).toDouble();
00629   mColumnSpace = itemElem.attribute( "columnSpace", "2.0" ).toDouble();
00630 
00631   mSymbolWidth = itemElem.attribute( "symbolWidth", "7.0" ).toDouble();
00632   mSymbolHeight = itemElem.attribute( "symbolHeight", "14.0" ).toDouble();
00633 
00634   mWrapChar = itemElem.attribute( "wrapChar" );
00635 
00636   //composer map
00637   if ( !itemElem.attribute( "map" ).isEmpty() )
00638   {
00639     mComposerMap = mComposition->getComposerMapById( itemElem.attribute( "map" ).toInt() );
00640   }
00641 
00642   //read model properties
00643   QDomNodeList modelNodeList = itemElem.elementsByTagName( "Model" );
00644   if ( modelNodeList.size() > 0 )
00645   {
00646     QDomElement modelElem = modelNodeList.at( 0 ).toElement();
00647     mLegendModel.readXML( modelElem, doc );
00648   }
00649 
00650   //restore general composer item properties
00651   QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
00652   if ( composerItemList.size() > 0 )
00653   {
00654     QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
00655     _readXML( composerItemElem, doc );
00656   }
00657 
00658   // < 2.0 projects backward compatibility >>>>>
00659   //title font
00660   QString titleFontString = itemElem.attribute( "titleFont" );
00661   if ( !titleFontString.isEmpty() )
00662   {
00663     rstyle( QgsComposerLegendStyle::Title ).rfont().fromString( titleFontString );
00664   }
00665   //group font
00666   QString groupFontString = itemElem.attribute( "groupFont" );
00667   if ( !groupFontString.isEmpty() )
00668   {
00669     rstyle( QgsComposerLegendStyle::Group ).rfont().fromString( groupFontString );
00670   }
00671 
00672   //layer font
00673   QString layerFontString = itemElem.attribute( "layerFont" );
00674   if ( !layerFontString.isEmpty() )
00675   {
00676     rstyle( QgsComposerLegendStyle::Subgroup ).rfont().fromString( layerFontString );
00677   }
00678   //item font
00679   QString itemFontString = itemElem.attribute( "itemFont" );
00680   if ( !itemFontString.isEmpty() )
00681   {
00682     rstyle( QgsComposerLegendStyle::SymbolLabel ).rfont().fromString( itemFontString );
00683   }
00684 
00685   if ( !itemElem.attribute( "groupSpace" ).isEmpty() )
00686   {
00687     rstyle( QgsComposerLegendStyle::Group ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "groupSpace", "3.0" ).toDouble() );
00688   }
00689   if ( !itemElem.attribute( "layerSpace" ).isEmpty() )
00690   {
00691     rstyle( QgsComposerLegendStyle::Subgroup ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "layerSpace", "3.0" ).toDouble() );
00692   }
00693   if ( !itemElem.attribute( "symbolSpace" ).isEmpty() )
00694   {
00695     rstyle( QgsComposerLegendStyle::Symbol ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "symbolSpace", "2.0" ).toDouble() );
00696     rstyle( QgsComposerLegendStyle::SymbolLabel ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "symbolSpace", "2.0" ).toDouble() );
00697   }
00698   // <<<<<<< < 2.0 projects backward compatibility
00699 
00700   emit itemChanged();
00701   return true;
00702 }
00703 
00704 void QgsComposerLegend::setComposerMap( const QgsComposerMap* map )
00705 {
00706   mComposerMap = map;
00707   if ( map )
00708   {
00709     QObject::connect( map, SIGNAL( destroyed( QObject* ) ), this, SLOT( invalidateCurrentMap() ) );
00710   }
00711 }
00712 
00713 void QgsComposerLegend::invalidateCurrentMap()
00714 {
00715   if ( mComposerMap )
00716   {
00717     disconnect( mComposerMap, SIGNAL( destroyed( QObject* ) ), this, SLOT( invalidateCurrentMap() ) );
00718   }
00719   mComposerMap = 0;
00720 }
00721 
00722 QStringList QgsComposerLegend::splitStringForWrapping( QString stringToSplt )
00723 {
00724   QStringList list;
00725   // If the string contains nothing then just return the string without spliting.
00726   if ( mWrapChar.count() == 0 )
00727     list << stringToSplt;
00728   else
00729     list = stringToSplt.split( mWrapChar );
00730   return list;
00731 }
00732 
00733 QList<QgsComposerLegend::Atom> QgsComposerLegend::createAtomList( QStandardItem* rootItem, bool splitLayer )
00734 {
00735   QList<Atom> atoms;
00736 
00737   if ( !rootItem ) return atoms;
00738 
00739   Atom atom;
00740 
00741   for ( int i = 0; i < rootItem->rowCount(); i++ )
00742   {
00743     QStandardItem* currentLayerItem = rootItem->child( i );
00744     QgsComposerLegendItem* currentLegendItem = dynamic_cast<QgsComposerLegendItem*>( currentLayerItem );
00745     if ( !currentLegendItem ) continue;
00746 
00747     QgsComposerLegendItem::ItemType type = currentLegendItem->itemType();
00748     if ( type == QgsComposerLegendItem::GroupItem )
00749     {
00750       // Group subitems
00751       QList<Atom> groupAtoms = createAtomList( currentLayerItem, splitLayer );
00752 
00753       Nucleon nucleon;
00754       nucleon.item = currentLegendItem;
00755       nucleon.size = drawGroupItemTitle( dynamic_cast<QgsComposerGroupItem*>( currentLegendItem ) );
00756 
00757       if ( groupAtoms.size() > 0 )
00758       {
00759         // Add internal space between this group title and the next nucleon
00760         groupAtoms[0].size.rheight() += spaceAboveAtom( groupAtoms[0] );
00761         // Prepend this group title to the first atom
00762         groupAtoms[0].nucleons.prepend( nucleon );
00763         groupAtoms[0].size.rheight() += nucleon.size.height();
00764         groupAtoms[0].size.rwidth() = qMax( nucleon.size.width(), groupAtoms[0].size.width() );
00765       }
00766       else
00767       {
00768         // no subitems, append new atom
00769         Atom atom;
00770         atom.nucleons.append( nucleon );
00771         atom.size.rwidth() += nucleon.size.width();
00772         atom.size.rheight() += nucleon.size.height();
00773         atom.size.rwidth() = qMax( nucleon.size.width(), atom.size.width() );
00774         groupAtoms.append( atom );
00775       }
00776       atoms.append( groupAtoms );
00777     }
00778     else if ( type == QgsComposerLegendItem::LayerItem )
00779     {
00780       Atom atom;
00781 
00782       if ( currentLegendItem->style() != QgsComposerLegendStyle::Hidden )
00783       {
00784         Nucleon nucleon;
00785         nucleon.item = currentLegendItem;
00786         nucleon.size = drawLayerItemTitle( dynamic_cast<QgsComposerLayerItem*>( currentLegendItem ) );
00787         atom.nucleons.append( nucleon );
00788         atom.size.rwidth() = nucleon.size.width();
00789         atom.size.rheight() = nucleon.size.height();
00790       }
00791 
00792       QList<Atom> layerAtoms;
00793 
00794       for ( int j = 0; j < currentLegendItem->rowCount(); j++ )
00795       {
00796         QgsComposerLegendItem * symbolItem = dynamic_cast<QgsComposerLegendItem*>( currentLegendItem->child( j, 0 ) );
00797         if ( !symbolItem ) continue;
00798 
00799         Nucleon symbolNucleon = drawSymbolItem( symbolItem );
00800 
00801         if ( !mSplitLayer || j == 0 )
00802         {
00803           // append to layer atom
00804           // the width is not correct at this moment, we must align all symbol labels
00805           atom.size.rwidth() = qMax( symbolNucleon.size.width(), atom.size.width() );
00806           //if ( currentLegendItem->rowCount() > 1 )
00807           //if ( currentLegendItem->style() != QgsComposerLegendStyle::Hidden )
00808           //{
00809           //atom.size.rheight() += mSymbolSpace;
00810           // TODO: for now we keep Symbol and SymbolLabel Top margin in sync
00811           atom.size.rheight() += style( QgsComposerLegendStyle::Symbol ).margin( QgsComposerLegendStyle::Top );
00812           //}
00813           atom.size.rheight() += symbolNucleon.size.height();
00814           atom.nucleons.append( symbolNucleon );
00815         }
00816         else
00817         {
00818           Atom symbolAtom;
00819           symbolAtom.nucleons.append( symbolNucleon );
00820           symbolAtom.size.rwidth() = symbolNucleon.size.width();
00821           symbolAtom.size.rheight() = symbolNucleon.size.height();
00822           layerAtoms.append( symbolAtom );
00823         }
00824       }
00825       layerAtoms.prepend( atom );
00826       atoms.append( layerAtoms );
00827     }
00828   }
00829 
00830   return atoms;
00831 }
00832 
00833 // Draw atom and expand its size (using actual nucleons labelXOffset)
00834 QSizeF QgsComposerLegend::drawAtom( Atom atom, QPainter* painter, QPointF point )
00835 {
00836   // bool first = true;
00837   QSizeF size = QSizeF( atom.size );
00838   foreach ( Nucleon nucleon, atom.nucleons )
00839   {
00840     QgsComposerLegendItem* item = nucleon.item;
00841     //QgsDebugMsg( "text: " + item->text() );
00842     if ( !item ) continue;
00843     QgsComposerLegendItem::ItemType type = item->itemType();
00844     if ( type == QgsComposerLegendItem::GroupItem )
00845     {
00846       QgsComposerGroupItem* groupItem = dynamic_cast<QgsComposerGroupItem*>( item );
00847       if ( !groupItem ) continue;
00848       // TODO: is it better to avoid marginand align all types of items to the same top like it was before?
00849       //if ( !first ) point.ry() += style(groupItem->style()).margin(QgsComposerLegendStyle::Top);
00850       if ( groupItem->style() != QgsComposerLegendStyle::Hidden )
00851       {
00852         point.ry() += style( groupItem->style() ).margin( QgsComposerLegendStyle::Top );
00853         drawGroupItemTitle( groupItem, painter, point );
00854       }
00855     }
00856     else if ( type == QgsComposerLegendItem::LayerItem )
00857     {
00858       QgsComposerLayerItem* layerItem = dynamic_cast<QgsComposerLayerItem*>( item );
00859       if ( !layerItem ) continue;
00860       //if ( !first ) point.ry() += style(layerItem->style()).margin(QgsComposerLegendStyle::Top);
00861       if ( layerItem->style() != QgsComposerLegendStyle::Hidden )
00862       {
00863         point.ry() += style( layerItem->style() ).margin( QgsComposerLegendStyle::Top );
00864         drawLayerItemTitle( layerItem, painter, point );
00865       }
00866     }
00867     else if ( type == QgsComposerLegendItem::SymbologyV2Item ||
00868               type == QgsComposerLegendItem::RasterSymbolItem )
00869     {
00870       //if ( !first )
00871       point.ry() += style( QgsComposerLegendStyle::Symbol ).margin( QgsComposerLegendStyle::Top );
00872       //}
00873       double labelXOffset = nucleon.labelXOffset;
00874       Nucleon symbolNucleon = drawSymbolItem( item, painter, point, labelXOffset );
00875       // expand width, it may be wider because of labelXOffset
00876       size.rwidth() = qMax( symbolNucleon.size.width(), size.width() );
00877     }
00878     point.ry() += nucleon.size.height();
00879     // first = false;
00880   }
00881   return size;
00882 }
00883 
00884 double QgsComposerLegend::spaceAboveAtom( Atom atom )
00885 {
00886   if ( atom.nucleons.size() == 0 ) return 0;
00887 
00888   Nucleon nucleon = atom.nucleons.first();
00889 
00890   QgsComposerLegendItem* item = nucleon.item;
00891   if ( !item ) return 0;
00892 
00893   QgsComposerLegendItem::ItemType type = item->itemType();
00894   switch ( type )
00895   {
00896     case QgsComposerLegendItem::GroupItem:
00897       return style( item->style() ).margin( QgsComposerLegendStyle::Top );
00898       break;
00899     case QgsComposerLegendItem::LayerItem:
00900       return style( item->style() ).margin( QgsComposerLegendStyle::Top );
00901       break;
00902     case QgsComposerLegendItem::SymbologyV2Item:
00903     case QgsComposerLegendItem::RasterSymbolItem:
00904       // TODO: use Symbol or SymbolLabel Top margin
00905       return style( QgsComposerLegendStyle::Symbol ).margin( QgsComposerLegendStyle::Top );
00906       break;
00907     default:
00908       break;
00909   }
00910   return 0;
00911 }
00912 
00913 void QgsComposerLegend::setColumns( QList<Atom>& atomList )
00914 {
00915   if ( mColumnCount == 0 ) return;
00916 
00917   // Divide atoms to columns
00918   double totalHeight = 0;
00919   // bool first = true;
00920   qreal maxAtomHeight = 0;
00921   foreach ( Atom atom, atomList )
00922   {
00923     //if ( !first )
00924     //{
00925     totalHeight += spaceAboveAtom( atom );
00926     //}
00927     totalHeight += atom.size.height();
00928     maxAtomHeight = qMax( atom.size.height(), maxAtomHeight );
00929     // first  = false;
00930   }
00931 
00932   // We know height of each atom and we have to split them into columns
00933   // minimizing max column height. It is sort of bin packing problem, NP-hard.
00934   // We are using simple heuristic, brute fore appeared to be to slow,
00935   // the number of combinations is N = n!/(k!*(n-k)!) where n = atomsCount-1
00936   // and k = columnsCount-1
00937 
00938   double avgColumnHeight = totalHeight / mColumnCount;
00939   int currentColumn = 0;
00940   int currentColumnAtomCount = 0; // number of atoms in current column
00941   double currentColumnHeight = 0;
00942   double maxColumnHeight = 0;
00943   double closedColumnsHeight = 0;
00944   // first = true; // first in column
00945   for ( int i = 0; i < atomList.size(); i++ )
00946   {
00947     Atom atom = atomList[i];
00948     double currentHeight = currentColumnHeight;
00949     //if ( !first )
00950     //{
00951     currentHeight += spaceAboveAtom( atom );
00952     //}
00953     currentHeight += atom.size.height();
00954 
00955     // Recalc average height for remaining columns including current
00956     avgColumnHeight = ( totalHeight - closedColumnsHeight ) / ( mColumnCount - currentColumn );
00957     if (( currentHeight - avgColumnHeight ) > atom.size.height() / 2 // center of current atom is over average height
00958         && currentColumnAtomCount > 0 // do not leave empty column
00959         && currentHeight > maxAtomHeight  // no sense to make smaller columns than max atom height
00960         && currentHeight > maxColumnHeight  // no sense to make smaller columns than max column already created
00961         && currentColumn < mColumnCount - 1 ) // must not exceed max number of columns
00962     {
00963       // New column
00964       currentColumn++;
00965       currentColumnAtomCount = 0;
00966       closedColumnsHeight += currentColumnHeight;
00967       currentColumnHeight = atom.size.height();
00968     }
00969     else
00970     {
00971       currentColumnHeight = currentHeight;
00972     }
00973     atomList[i].column = currentColumn;
00974     currentColumnAtomCount++;
00975     maxColumnHeight = qMax( currentColumnHeight, maxColumnHeight );
00976 
00977     // first  = false;
00978   }
00979 
00980   // Alling labels of symbols for each layr/column to the same labelXOffset
00981   QMap<QString, qreal> maxSymbolWidth;
00982   for ( int i = 0; i < atomList.size(); i++ )
00983   {
00984     for ( int j = 0; j < atomList[i].nucleons.size(); j++ )
00985     {
00986       QgsComposerLegendItem* item = atomList[i].nucleons[j].item;
00987       if ( !item ) continue;
00988       QgsComposerLegendItem::ItemType type = item->itemType();
00989       if ( type == QgsComposerLegendItem::SymbologyV2Item ||
00990            type == QgsComposerLegendItem::RasterSymbolItem )
00991       {
00992         QString key = QString( "%1-%2" ).arg(( qulonglong )item->parent() ).arg( atomList[i].column );
00993         maxSymbolWidth[key] = qMax( atomList[i].nucleons[j].symbolSize.width(), maxSymbolWidth[key] );
00994       }
00995     }
00996   }
00997   for ( int i = 0; i < atomList.size(); i++ )
00998   {
00999     for ( int j = 0; j < atomList[i].nucleons.size(); j++ )
01000     {
01001       QgsComposerLegendItem* item = atomList[i].nucleons[j].item;
01002       if ( !item ) continue;
01003       QgsComposerLegendItem::ItemType type = item->itemType();
01004       if ( type == QgsComposerLegendItem::SymbologyV2Item ||
01005            type == QgsComposerLegendItem::RasterSymbolItem )
01006       {
01007         QString key = QString( "%1-%2" ).arg(( qulonglong )item->parent() ).arg( atomList[i].column );
01008         double space = style( QgsComposerLegendStyle::Symbol ).margin( QgsComposerLegendStyle::Right ) +
01009                        style( QgsComposerLegendStyle::SymbolLabel ).margin( QgsComposerLegendStyle::Left );
01010         atomList[i].nucleons[j].labelXOffset =  maxSymbolWidth[key] + space;
01011         atomList[i].nucleons[j].size.rwidth() =  maxSymbolWidth[key] + space + atomList[i].nucleons[j].labelSize.width();
01012       }
01013     }
01014   }
01015 }
01016 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines