QGIS API Documentation  2.5.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsdetaileditemdelegate.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdetailedlistwidget.cpp - A rich QItemDelegate subclass
3  -------------------
4  begin : Sat May 17 2008
5  copyright : (C) 2008 Tim Sutton
6  email : tim@linfiniti.com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
19 #include "qgsdetaileditemwidget.h"
20 #include "qgsdetaileditemdata.h"
21 #include <QPainter>
22 #include <QFont>
23 #include <QFontMetrics>
24 #include <QStyleOptionViewItem>
25 #include <QModelIndex>
26 #include <QCheckBox>
27 #include <QLinearGradient>
29  QAbstractItemDelegate( parent ),
30  mpWidget( new QgsDetailedItemWidget() ),
31  mpCheckBox( new QCheckBox() )
32 
33 {
34  //mpWidget->setFixedHeight(80);
35  mpCheckBox->resize( mpCheckBox->sizeHint().height(), mpCheckBox->sizeHint().height() );
36  setVerticalSpacing( 3 );
38 }
39 
41 {
42  delete mpCheckBox;
43  delete mpWidget;
44 }
45 
46 void QgsDetailedItemDelegate::paint( QPainter * thepPainter,
47  const QStyleOptionViewItem & theOption,
48  const QModelIndex & theIndex ) const
49 {
50  // After painting we need to restore the painter to its original state
51  thepPainter->save();
52  if ( theIndex.data( Qt::UserRole ).canConvert<QgsDetailedItemData>() )
53  {
54  QgsDetailedItemData myData =
55  theIndex.data( Qt::UserRole ).value<QgsDetailedItemData>();
56  if ( myData.isRenderedAsWidget() )
57  {
58  paintAsWidget( thepPainter, theOption, myData );
59  }
60  else //render by manually painting
61  {
62  paintManually( thepPainter, theOption, myData );
63  }
64  } //can convert item data
65  thepPainter->restore();
66 }
67 
68 
69 
71  const QStyleOptionViewItem & theOption,
72  const QModelIndex & theIndex ) const
73 {
74  if ( theIndex.data( Qt::UserRole ).canConvert<QgsDetailedItemData>() )
75  {
76  QgsDetailedItemData myData =
77  theIndex.data( Qt::UserRole ).value<QgsDetailedItemData>();
78  if ( myData.isRenderedAsWidget() )
79  {
80  return QSize( 378, mpWidget->height() );
81  }
82  else // fall back to hand calculated & hand drawn item
83  {
84  //for some reason itmes are non selectable if using rect.width() on osx and win
85  return QSize( 50, height( theOption, myData ) );
86  //return QSize(theOption.rect.width(), myHeight + myVerticalSpacer);
87  }
88  }
89  else //cant convert to qgsdetaileditemdata
90  {
91  return QSize( 50, 50 ); //fallback
92  }
93 }
94 
95 void QgsDetailedItemDelegate::paintManually( QPainter * thepPainter,
96  const QStyleOptionViewItem & theOption,
97  const QgsDetailedItemData theData ) const
98 {
99  //
100  // Get the strings and check box properties
101  //
102  //bool myCheckState = theIndex.model()->data(theIndex, Qt::CheckStateRole).toBool();
103  mpCheckBox->setChecked( theData.isChecked() );
104  mpCheckBox->setEnabled( theData.isEnabled() );
105  QPixmap myCbxPixmap( mpCheckBox->size() );
106  mpCheckBox->render( &myCbxPixmap ); //we will draw this onto the widget further down
107 
108  //
109  // Calculate the widget height and other metrics
110  //
111 
112  QFontMetrics myTitleMetrics( titleFont( theOption ) );
113  QFontMetrics myDetailMetrics( detailFont( theOption ) );
114  QFontMetrics myCategoryMetrics( categoryFont( theOption ) );
115  int myTextStartX = theOption.rect.x() + horizontalSpacing();
116  int myTextStartY = theOption.rect.y() + verticalSpacing();
117  int myHeight = myTitleMetrics.height() + verticalSpacing();
118 
119  //
120  // Draw the item background with a gradient if its highlighted
121  //
122  if ( theOption.state & QStyle::State_Selected )
123  {
124  drawHighlight( theOption, thepPainter, height( theOption, theData ) );
125  thepPainter->setPen( theOption.palette.highlightedText().color() );
126  }
127  else
128  {
129  thepPainter->setPen( theOption.palette.text().color() );
130  }
131 
132 
133  //
134  // Draw the checkbox
135  //
136  if ( theData.isCheckable() )
137  {
138  thepPainter->drawPixmap( theOption.rect.x(),
139  theOption.rect.y() + mpCheckBox->height(),
140  myCbxPixmap );
141  myTextStartX = theOption.rect.x() + myCbxPixmap.width() + horizontalSpacing();
142  }
143  //
144  // Draw the decoration (pixmap)
145  //
146  bool myIconFlag = false;
147  QPixmap myDecoPixmap = theData.icon();
148  if ( !myDecoPixmap.isNull() )
149  {
150  int iconWidth = 32, iconHeight = 32;
151 
152  if ( myDecoPixmap.width() <= iconWidth && myDecoPixmap.height() <= iconHeight )
153  {
154  // the pixmap has reasonable size
155  int offsetX = 0, offsetY = 0;
156  if ( myDecoPixmap.width() < iconWidth )
157  offsetX = ( iconWidth - myDecoPixmap.width() ) / 2;
158  if ( myDecoPixmap.height() < iconHeight )
159  offsetY = ( iconHeight - myDecoPixmap.height() ) / 2;
160 
161  thepPainter->drawPixmap( myTextStartX + offsetX,
162  myTextStartY + offsetY,
163  myDecoPixmap );
164  }
165  else
166  {
167  // shrink the pixmap, it's too big
168  thepPainter->drawPixmap( myTextStartX, myTextStartY, iconWidth, iconHeight, myDecoPixmap );
169  }
170 
171  myTextStartX += iconWidth + horizontalSpacing();
172  }
173  //
174  // Draw the title
175  //
176  myTextStartY += myHeight / 2;
177  thepPainter->setFont( titleFont( theOption ) );
178  thepPainter->drawText( myTextStartX,
179  myTextStartY,
180  theData.title() );
181  //
182  // Draw the description with word wrapping if needed
183  //
184  thepPainter->setFont( detailFont( theOption ) ); //return to original font set by client
185  if ( myIconFlag )
186  {
187  myTextStartY += verticalSpacing();
188  }
189  else
190  {
191  myTextStartY += myDetailMetrics.height() + verticalSpacing();
192  }
193  QStringList myList =
194  wordWrap( theData.detail(), myDetailMetrics, theOption.rect.width() - myTextStartX );
195  QStringListIterator myLineWrapIterator( myList );
196  while ( myLineWrapIterator.hasNext() )
197  {
198  QString myLine = myLineWrapIterator.next();
199  thepPainter->drawText( myTextStartX,
200  myTextStartY,
201  myLine );
202  myTextStartY += myDetailMetrics.height() - verticalSpacing();
203  }
204 
205  //
206  // Draw the category. Not sure if we need word wrapping for it.
207  //
208  thepPainter->setFont( categoryFont( theOption ) ); //return to original font set by client
209  thepPainter->drawText( myTextStartX,
210  myTextStartY,
211  theData.category() );
212 
213  //
214  // Draw the category with word wrapping if needed
215  //
216  /*
217  myTextStartY += verticalSpacing();
218  if ( myIconFlag )
219  {
220  myTextStartY += verticalSpacing();
221  }
222  else
223  {
224  myTextStartY += myCategoryMetrics.height() + verticalSpacing();
225  }
226  myList =
227  wordWrap( theData.category(), myCategoryMetrics, theOption.rect.width() - myTextStartX );
228  QStringListIterator myLineWrapIter( myList );
229  while ( myLineWrapIter.hasNext() )
230  {
231  QString myLine = myLineWrapIter.next();
232  thepPainter->drawText( myTextStartX,
233  myTextStartY,
234  myLine );
235  myTextStartY += myCategoryMetrics.height() - verticalSpacing();
236  }
237  */
238 } //render by manual painting
239 
240 
241 void QgsDetailedItemDelegate::paintAsWidget( QPainter * thepPainter,
242  const QStyleOptionViewItem & theOption,
243  const QgsDetailedItemData theData ) const
244 {
245 
246  mpWidget->setChecked( theData.isChecked() );
247  mpWidget->setData( theData );
248  mpWidget->resize( theOption.rect.width(), mpWidget->height() );
249  mpWidget->setAutoFillBackground( true );
250  //mpWidget->setAttribute(Qt::WA_OpaquePaintEvent);
251  mpWidget->repaint();
252  if ( theOption.state & QStyle::State_Selected )
253  {
254  drawHighlight( theOption, thepPainter, height( theOption, theData ) );
255  }
256  QPixmap myPixmap = QPixmap::grabWidget( mpWidget );
257  thepPainter->drawPixmap( theOption.rect.x(),
258  theOption.rect.y(),
259  myPixmap );
260 }//render as widget
261 
262 void QgsDetailedItemDelegate::drawHighlight( const QStyleOptionViewItem &theOption,
263  QPainter * thepPainter,
264  int theHeight ) const
265 {
266  QColor myColor1 = theOption.palette.highlight().color();
267  QColor myColor2 = myColor1;
268  myColor2 = myColor2.lighter( 110 ); //10% lighter
269  QLinearGradient myGradient( QPointF( 0, theOption.rect.y() ),
270  QPointF( 0, theOption.rect.y() + theHeight ) );
271  myGradient.setColorAt( 0, myColor1 );
272  myGradient.setColorAt( 0.1, myColor2 );
273  myGradient.setColorAt( 0.5, myColor1 );
274  myGradient.setColorAt( 0.9, myColor2 );
275  myGradient.setColorAt( 1, myColor2 );
276  thepPainter->fillRect( theOption.rect, QBrush( myGradient ) );
277 }
278 
279 int QgsDetailedItemDelegate::height( const QStyleOptionViewItem & theOption,
280  const QgsDetailedItemData theData ) const
281 {
282  QFontMetrics myTitleMetrics( titleFont( theOption ) );
283  QFontMetrics myDetailMetrics( detailFont( theOption ) );
284  QFontMetrics myCategoryMetrics( categoryFont( theOption ) );
285  //we don't word wrap the title so its easy to measure
286  int myHeight = myTitleMetrics.height() + verticalSpacing();
287  //the detail needs to be measured though
288  QStringList myList = wordWrap( theData.detail(),
289  myDetailMetrics,
290  theOption.rect.width() - ( mpCheckBox->width() + horizontalSpacing() ) );
291  myHeight += ( myList.count() + 1 ) * ( myDetailMetrics.height() - verticalSpacing() );
292  //we don't word wrap the category so its easy to measure
293  myHeight += myCategoryMetrics.height() + verticalSpacing();
294  // if category should be wrapped use this code
295  //~ myList = wordWrap( theData.category(),
296  //~ myCategoryMetrics,
297  //~ theOption.rect.width() - ( mpCheckBox->width() + horizontalSpacing() ) );
298  //~ myHeight += ( myList.count() + 1 ) * ( myCategoryMetrics.height() - verticalSpacing() );
299  return myHeight;
300 }
301 
302 
303 QFont QgsDetailedItemDelegate::detailFont( const QStyleOptionViewItem &theOption ) const
304 {
305  QFont myFont = theOption.font;
306  return myFont;
307 }
308 
309 QFont QgsDetailedItemDelegate::categoryFont( const QStyleOptionViewItem &theOption ) const
310 {
311  QFont myFont = theOption.font;
312  myFont.setBold( true );
313  return myFont;
314 }
315 
316 QFont QgsDetailedItemDelegate::titleFont( const QStyleOptionViewItem &theOption ) const
317 {
318  QFont myTitleFont = detailFont( theOption );
319  myTitleFont.setBold( true );
320  myTitleFont.setPointSize( myTitleFont.pointSize() );
321  return myTitleFont;
322 }
323 
324 
325 QStringList QgsDetailedItemDelegate::wordWrap( QString theString,
326  QFontMetrics theMetrics,
327  int theWidth ) const
328 {
329  if ( theString.isEmpty() )
330  return QStringList();
331  if ( 50 >= theWidth )
332  return QStringList() << theString;
333  //QString myDebug = QString("Word wrapping: %1 into %2 pixels").arg(theString).arg(theWidth);
334  //qDebug(myDebug.toLocal8Bit());
335  //iterate the string
336  QStringList myList;
337  QString myCumulativeLine = "";
338  QString myStringToPreviousSpace = "";
339  int myPreviousSpacePos = 0;
340  for ( int i = 0; i < theString.count(); ++i )
341  {
342  QChar myChar = theString.at( i );
343  if ( myChar == QChar( ' ' ) )
344  {
345  myStringToPreviousSpace = myCumulativeLine;
346  myPreviousSpacePos = i;
347  }
348  myCumulativeLine += myChar;
349  if ( theMetrics.width( myCumulativeLine ) >= theWidth )
350  {
351  //time to wrap
352  //@todo deal with long strings that have no spaces
353  //forcing a break at current pos...
354  myList << myStringToPreviousSpace.trimmed();
355  i = myPreviousSpacePos;
356  myStringToPreviousSpace = "";
357  myCumulativeLine = "";
358  }
359  }//end of i loop
360  //add whatever is left in the string to the list
361  if ( !myCumulativeLine.trimmed().isEmpty() )
362  {
363  myList << myCumulativeLine.trimmed();
364  }
365 
366  //qDebug("Wrapped legend entry:");
367  //qDebug(theString);
368  //qDebug(myList.join("\n").toLocal8Bit());
369  return myList;
370 
371 }
372 
373 
374 
376 {
377  return mVerticalSpacing;
378 }
379 
380 
382 {
383  mVerticalSpacing = theValue;
384 }
385 
386 
388 {
389  return mHorizontalSpacing;
390 }
391 
392 
394 {
395  mHorizontalSpacing = theValue;
396 }
QFont titleFont(const QStyleOptionViewItem &theOption) const
QSize sizeHint(const QStyleOptionViewItem &theOption, const QModelIndex &theIndex) const
reimplement for parent class
This class is the data only representation of a QgsDetailedItemWidget, designed to be used in custom ...
QFont detailFont(const QStyleOptionViewItem &theOption) const
QgsDetailedItemDelegate(QObject *parent=0)
A widget renderer for detailed item views.
void setData(QgsDetailedItemData theData)
void setHorizontalSpacing(int theValue)
QStringList wordWrap(QString theString, QFontMetrics theMetrics, int theWidth) const
void paint(QPainter *thePainter, const QStyleOptionViewItem &theOption, const QModelIndex &theIndex) const
reimplement for parent class
QString category() const
bool isRenderedAsWidget() const
QFont categoryFont(const QStyleOptionViewItem &theOption) const
QgsDetailedItemWidget * mpWidget
void paintAsWidget(QPainter *thePainter, const QStyleOptionViewItem &theOption, const QgsDetailedItemData theData) const
void drawHighlight(const QStyleOptionViewItem &theOption, QPainter *thepPainter, int theHeight) const
int height(const QStyleOptionViewItem &theOption, const QgsDetailedItemData theData) const
void paintManually(QPainter *thePainter, const QStyleOptionViewItem &theOption, const QgsDetailedItemData theData) const
void setChecked(bool theFlag)