Quantum GIS API Documentation  1.8
src/gui/qgsdetaileditemdelegate.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002      qgsdetailedlistwidget.cpp  -  A rich QItemDelegate subclass
00003                              -------------------
00004     begin                : Sat May 17 2008
00005     copyright            : (C) 2008 Tim Sutton
00006     email                : tim@linfiniti.com
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines