|
QGIS API Documentation
master-3f58142
|
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