Quantum GIS API Documentation
1.8
|
00001 /*************************************************************************** 00002 qgsdetailedlistwidget.cpp - A rich QItemDelegate subclass 00003 ------------------- 00004 begin : Sat May 17 2008 00005 copyright : (C) 2008 Tim Sutton 00006 email : [email protected] 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 00018 #include "qgsdetaileditemdelegate.h" 00019 #include "qgsdetaileditemwidget.h" 00020 #include "qgsdetaileditemdata.h" 00021 #include <QPainter> 00022 #include <QFont> 00023 #include <QFontMetrics> 00024 #include <QStyleOptionViewItem> 00025 #include <QModelIndex> 00026 #include <QCheckBox> 00027 #include <QLinearGradient> 00028 QgsDetailedItemDelegate::QgsDetailedItemDelegate( QObject * parent ) : 00029 QAbstractItemDelegate( parent ), 00030 mpWidget( new QgsDetailedItemWidget() ), 00031 mpCheckBox( new QCheckBox() ) 00032 00033 { 00034 //mpWidget->setFixedHeight(80); 00035 mpCheckBox->resize( mpCheckBox->sizeHint().height(), mpCheckBox->sizeHint().height() ); 00036 setVerticalSpacing( 3 ); 00037 setHorizontalSpacing( 5 ); 00038 } 00039 00040 QgsDetailedItemDelegate::~QgsDetailedItemDelegate() 00041 { 00042 delete mpCheckBox; 00043 delete mpWidget; 00044 } 00045 00046 void QgsDetailedItemDelegate::paint( QPainter * thepPainter, 00047 const QStyleOptionViewItem & theOption, 00048 const QModelIndex & theIndex ) const 00049 { 00050 // After painting we need to restore the painter to its original state 00051 thepPainter->save(); 00052 if ( qVariantCanConvert<QgsDetailedItemData>( theIndex.data( Qt::UserRole ) ) ) 00053 { 00054 QgsDetailedItemData myData = 00055 qVariantValue<QgsDetailedItemData>( theIndex.data( Qt::UserRole ) ); 00056 if ( myData.isRenderedAsWidget() ) 00057 { 00058 paintAsWidget( thepPainter, theOption, myData ); 00059 } 00060 else //render by manually painting 00061 { 00062 paintManually( thepPainter, theOption, myData ); 00063 } 00064 } //can convert item data 00065 thepPainter->restore(); 00066 } 00067 00068 00069 00070 QSize QgsDetailedItemDelegate::sizeHint( 00071 const QStyleOptionViewItem & theOption, 00072 const QModelIndex & theIndex ) const 00073 { 00074 if ( qVariantCanConvert<QgsDetailedItemData>( theIndex.data( Qt::UserRole ) ) ) 00075 { 00076 QgsDetailedItemData myData = 00077 qVariantValue<QgsDetailedItemData>( theIndex.data( Qt::UserRole ) ); 00078 if ( myData.isRenderedAsWidget() ) 00079 { 00080 return QSize( 378, mpWidget->height() ); 00081 } 00082 else // fall back to hand calculated & hand drawn item 00083 { 00084 //for some reason itmes are non selectable if using rect.width() on osx and win 00085 return QSize( 50, height( theOption, myData ) ); 00086 //return QSize(theOption.rect.width(), myHeight + myVerticalSpacer); 00087 } 00088 } 00089 else //cant convert to qgsdetaileditemdata 00090 { 00091 return QSize( 50, 50 ); //fallback 00092 } 00093 } 00094 00095 void QgsDetailedItemDelegate::paintManually( QPainter * thepPainter, 00096 const QStyleOptionViewItem & theOption, 00097 const QgsDetailedItemData theData ) const 00098 { 00099 // 00100 // Get the strings and check box properties 00101 // 00102 //bool myCheckState = theIndex.model()->data(theIndex, Qt::CheckStateRole).toBool(); 00103 mpCheckBox->setChecked( theData.isChecked() ); 00104 mpCheckBox->setEnabled( theData.isEnabled() ); 00105 QPixmap myCbxPixmap( mpCheckBox->size() ); 00106 mpCheckBox->render( &myCbxPixmap ); //we will draw this onto the widget further down 00107 00108 // 00109 // Calculate the widget height and other metrics 00110 // 00111 00112 QFontMetrics myTitleMetrics( titleFont( theOption ) ); 00113 QFontMetrics myDetailMetrics( detailFont( theOption ) ); 00114 QFontMetrics myCategoryMetrics( categoryFont( theOption ) ); 00115 int myTextStartX = theOption.rect.x() + horizontalSpacing(); 00116 int myTextStartY = theOption.rect.y() + verticalSpacing(); 00117 int myHeight = myTitleMetrics.height() + verticalSpacing(); 00118 00119 // 00120 // Draw the item background with a gradient if its highlighted 00121 // 00122 if ( theOption.state & QStyle::State_Selected ) 00123 { 00124 drawHighlight( theOption, thepPainter, height( theOption, theData ) ); 00125 thepPainter->setPen( theOption.palette.highlightedText().color() ); 00126 } 00127 else 00128 { 00129 thepPainter->setPen( theOption.palette.text().color() ); 00130 } 00131 00132 00133 // 00134 // Draw the checkbox 00135 // 00136 if ( theData.isCheckable() ) 00137 { 00138 thepPainter->drawPixmap( theOption.rect.x(), 00139 theOption.rect.y() + mpCheckBox->height(), 00140 myCbxPixmap ); 00141 myTextStartX = theOption.rect.x() + myCbxPixmap.width() + horizontalSpacing(); 00142 } 00143 // 00144 // Draw the decoration (pixmap) 00145 // 00146 bool myIconFlag = false; 00147 QPixmap myDecoPixmap = theData.icon(); 00148 if ( !myDecoPixmap.isNull() ) 00149 { 00150 int iconWidth = 32, iconHeight = 32; 00151 00152 if ( myDecoPixmap.width() <= iconWidth && myDecoPixmap.height() <= iconHeight ) 00153 { 00154 // the pixmap has reasonable size 00155 int offsetX = 0, offsetY = 0; 00156 if ( myDecoPixmap.width() < iconWidth ) 00157 offsetX = ( iconWidth - myDecoPixmap.width() ) / 2; 00158 if ( myDecoPixmap.height() < iconHeight ) 00159 offsetY = ( iconHeight - myDecoPixmap.height() ) / 2; 00160 00161 thepPainter->drawPixmap( myTextStartX + offsetX, 00162 myTextStartY + offsetY, 00163 myDecoPixmap ); 00164 } 00165 else 00166 { 00167 // shrink the pixmap, it's too big 00168 thepPainter->drawPixmap( myTextStartX, myTextStartY, iconWidth, iconHeight, myDecoPixmap ); 00169 } 00170 00171 myTextStartX += iconWidth + horizontalSpacing(); 00172 } 00173 // 00174 // Draw the title 00175 // 00176 myTextStartY += myHeight / 2; 00177 thepPainter->setFont( titleFont( theOption ) ); 00178 thepPainter->drawText( myTextStartX, 00179 myTextStartY, 00180 theData.title() ); 00181 // 00182 // Draw the description with word wrapping if needed 00183 // 00184 thepPainter->setFont( detailFont( theOption ) ); //return to original font set by client 00185 if ( myIconFlag ) 00186 { 00187 myTextStartY += verticalSpacing(); 00188 } 00189 else 00190 { 00191 myTextStartY += myDetailMetrics.height() + verticalSpacing(); 00192 } 00193 QStringList myList = 00194 wordWrap( theData.detail(), myDetailMetrics, theOption.rect.width() - myTextStartX ); 00195 QStringListIterator myLineWrapIterator( myList ); 00196 while ( myLineWrapIterator.hasNext() ) 00197 { 00198 QString myLine = myLineWrapIterator.next(); 00199 thepPainter->drawText( myTextStartX, 00200 myTextStartY, 00201 myLine ); 00202 myTextStartY += myDetailMetrics.height() - verticalSpacing(); 00203 } 00204 00205 // 00206 // Draw the category. Not sure if we need word wrapping for it. 00207 // 00208 thepPainter->setFont( categoryFont( theOption ) ); //return to original font set by client 00209 thepPainter->drawText( myTextStartX, 00210 myTextStartY, 00211 theData.category() ); 00212 00213 // 00214 // Draw the category with word wrapping if needed 00215 // 00216 /* 00217 myTextStartY += verticalSpacing(); 00218 if ( myIconFlag ) 00219 { 00220 myTextStartY += verticalSpacing(); 00221 } 00222 else 00223 { 00224 myTextStartY += myCategoryMetrics.height() + verticalSpacing(); 00225 } 00226 myList = 00227 wordWrap( theData.category(), myCategoryMetrics, theOption.rect.width() - myTextStartX ); 00228 QStringListIterator myLineWrapIter( myList ); 00229 while ( myLineWrapIter.hasNext() ) 00230 { 00231 QString myLine = myLineWrapIter.next(); 00232 thepPainter->drawText( myTextStartX, 00233 myTextStartY, 00234 myLine ); 00235 myTextStartY += myCategoryMetrics.height() - verticalSpacing(); 00236 } 00237 */ 00238 } //render by manual painting 00239 00240 00241 void QgsDetailedItemDelegate::paintAsWidget( QPainter * thepPainter, 00242 const QStyleOptionViewItem & theOption, 00243 const QgsDetailedItemData theData ) const 00244 { 00245 00246 mpWidget->setChecked( theData.isChecked() ); 00247 mpWidget->setData( theData ); 00248 mpWidget->resize( theOption.rect.width(), mpWidget->height() ); 00249 mpWidget->setAutoFillBackground( true ); 00250 //mpWidget->setAttribute(Qt::WA_OpaquePaintEvent); 00251 mpWidget->repaint(); 00252 if ( theOption.state & QStyle::State_Selected ) 00253 { 00254 drawHighlight( theOption, thepPainter, height( theOption, theData ) ); 00255 } 00256 QPixmap myPixmap = QPixmap::grabWidget( mpWidget ); 00257 thepPainter->drawPixmap( theOption.rect.x(), 00258 theOption.rect.y(), 00259 myPixmap ); 00260 }//render as widget 00261 00262 void QgsDetailedItemDelegate::drawHighlight( const QStyleOptionViewItem &theOption, 00263 QPainter * thepPainter, 00264 int theHeight ) const 00265 { 00266 QColor myColor1 = theOption.palette.highlight().color(); 00267 QColor myColor2 = myColor1; 00268 myColor2 = myColor2.lighter( 110 ); //10% lighter 00269 QLinearGradient myGradient( QPointF( 0, theOption.rect.y() ), 00270 QPointF( 0, theOption.rect.y() + theHeight ) ); 00271 myGradient.setColorAt( 0, myColor1 ); 00272 myGradient.setColorAt( 0.1, myColor2 ); 00273 myGradient.setColorAt( 0.5, myColor1 ); 00274 myGradient.setColorAt( 0.9, myColor2 ); 00275 myGradient.setColorAt( 1, myColor2 ); 00276 thepPainter->fillRect( theOption.rect, QBrush( myGradient ) ); 00277 } 00278 00279 int QgsDetailedItemDelegate::height( const QStyleOptionViewItem & theOption, 00280 const QgsDetailedItemData theData ) const 00281 { 00282 QFontMetrics myTitleMetrics( titleFont( theOption ) ); 00283 QFontMetrics myDetailMetrics( detailFont( theOption ) ); 00284 QFontMetrics myCategoryMetrics( categoryFont( theOption ) ); 00285 //we don't word wrap the title so its easy to measure 00286 int myHeight = myTitleMetrics.height() + verticalSpacing(); 00287 //the detail needs to be measured though 00288 QStringList myList = wordWrap( theData.detail(), 00289 myDetailMetrics, 00290 theOption.rect.width() - ( mpCheckBox->width() + horizontalSpacing() ) ); 00291 myHeight += ( myList.count() + 1 ) * ( myDetailMetrics.height() - verticalSpacing() ); 00292 //we don't word wrap the category so its easy to measure 00293 myHeight += myCategoryMetrics.height() + verticalSpacing(); 00294 // if category should be wrapped use this code 00295 //~ myList = wordWrap( theData.category(), 00296 //~ myCategoryMetrics, 00297 //~ theOption.rect.width() - ( mpCheckBox->width() + horizontalSpacing() ) ); 00298 //~ myHeight += ( myList.count() + 1 ) * ( myCategoryMetrics.height() - verticalSpacing() ); 00299 return myHeight; 00300 } 00301 00302 00303 QFont QgsDetailedItemDelegate::detailFont( const QStyleOptionViewItem &theOption ) const 00304 { 00305 QFont myFont = theOption.font; 00306 return myFont; 00307 } 00308 00309 QFont QgsDetailedItemDelegate::categoryFont( const QStyleOptionViewItem &theOption ) const 00310 { 00311 QFont myFont = theOption.font; 00312 myFont.setBold( true ); 00313 return myFont; 00314 } 00315 00316 QFont QgsDetailedItemDelegate::titleFont( const QStyleOptionViewItem &theOption ) const 00317 { 00318 QFont myTitleFont = detailFont( theOption ); 00319 myTitleFont.setBold( true ); 00320 myTitleFont.setPointSize( myTitleFont.pointSize() ); 00321 return myTitleFont; 00322 } 00323 00324 00325 QStringList QgsDetailedItemDelegate::wordWrap( QString theString, 00326 QFontMetrics theMetrics, 00327 int theWidth ) const 00328 { 00329 if ( theString.isEmpty() ) 00330 return QStringList(); 00331 if ( 50 >= theWidth ) 00332 return QStringList() << theString; 00333 //QString myDebug = QString("Word wrapping: %1 into %2 pixels").arg(theString).arg(theWidth); 00334 //qDebug(myDebug.toLocal8Bit()); 00335 //iterate the string 00336 QStringList myList; 00337 QString myCumulativeLine = ""; 00338 QString myStringToPreviousSpace = ""; 00339 int myPreviousSpacePos = 0; 00340 for ( int i = 0; i < theString.count(); ++i ) 00341 { 00342 QChar myChar = theString.at( i ); 00343 if ( myChar == QChar( ' ' ) ) 00344 { 00345 myStringToPreviousSpace = myCumulativeLine; 00346 myPreviousSpacePos = i; 00347 } 00348 myCumulativeLine += myChar; 00349 if ( theMetrics.width( myCumulativeLine ) >= theWidth ) 00350 { 00351 //time to wrap 00352 //@todo deal with long strings that have no spaces 00353 //forcing a break at current pos... 00354 myList << myStringToPreviousSpace.trimmed(); 00355 i = myPreviousSpacePos; 00356 myStringToPreviousSpace = ""; 00357 myCumulativeLine = ""; 00358 } 00359 }//end of i loop 00360 //add whatever is left in the string to the list 00361 if ( !myCumulativeLine.trimmed().isEmpty() ) 00362 { 00363 myList << myCumulativeLine.trimmed(); 00364 } 00365 00366 //qDebug("Wrapped legend entry:"); 00367 //qDebug(theString); 00368 //qDebug(myList.join("\n").toLocal8Bit()); 00369 return myList; 00370 00371 } 00372 00373 00374 00375 int QgsDetailedItemDelegate::verticalSpacing() const 00376 { 00377 return mVerticalSpacing; 00378 } 00379 00380 00381 void QgsDetailedItemDelegate::setVerticalSpacing( int theValue ) 00382 { 00383 mVerticalSpacing = theValue; 00384 } 00385 00386 00387 int QgsDetailedItemDelegate::horizontalSpacing() const 00388 { 00389 return mHorizontalSpacing; 00390 } 00391 00392 00393 void QgsDetailedItemDelegate::setHorizontalSpacing( int theValue ) 00394 { 00395 mHorizontalSpacing = theValue; 00396 }