QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 : [email protected]
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 
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  int myTextStartX = theOption.rect.x() + horizontalSpacing();
115  int myTextStartY = theOption.rect.y() + verticalSpacing();
116  int myHeight = myTitleMetrics.height() + verticalSpacing();
117 
118  //
119  // Draw the item background with a gradient if its highlighted
120  //
121  if ( theOption.state & QStyle::State_Selected )
122  {
123  drawHighlight( theOption, thepPainter, height( theOption, theData ) );
124  thepPainter->setPen( theOption.palette.highlightedText().color() );
125  }
126  else
127  {
128  thepPainter->setPen( theOption.palette.text().color() );
129  }
130 
131 
132  //
133  // Draw the checkbox
134  //
135  if ( theData.isCheckable() )
136  {
137  thepPainter->drawPixmap( theOption.rect.x(),
138  theOption.rect.y() + mpCheckBox->height(),
139  myCbxPixmap );
140  myTextStartX = theOption.rect.x() + myCbxPixmap.width() + horizontalSpacing();
141  }
142  //
143  // Draw the decoration (pixmap)
144  //
145  bool myIconFlag = false;
146  QPixmap myDecoPixmap = theData.icon();
147  if ( !myDecoPixmap.isNull() )
148  {
149  myIconFlag = true;
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 0
295  // if category should be wrapped use this code
296  myList = wordWrap( theData.category(),
297  myCategoryMetrics,
298  theOption.rect.width() - ( mpCheckBox->width() + horizontalSpacing() ) );
299  myHeight += ( myList.count() + 1 ) * ( myCategoryMetrics.height() - verticalSpacing() );
300 #endif
301  return myHeight;
302 }
303 
304 
305 QFont QgsDetailedItemDelegate::detailFont( const QStyleOptionViewItem &theOption ) const
306 {
307  QFont myFont = theOption.font;
308  return myFont;
309 }
310 
311 QFont QgsDetailedItemDelegate::categoryFont( const QStyleOptionViewItem &theOption ) const
312 {
313  QFont myFont = theOption.font;
314  myFont.setBold( true );
315  return myFont;
316 }
317 
318 QFont QgsDetailedItemDelegate::titleFont( const QStyleOptionViewItem &theOption ) const
319 {
320  QFont myTitleFont = detailFont( theOption );
321  myTitleFont.setBold( true );
322  myTitleFont.setPointSize( myTitleFont.pointSize() );
323  return myTitleFont;
324 }
325 
326 
327 QStringList QgsDetailedItemDelegate::wordWrap( const QString& theString,
328  const QFontMetrics& theMetrics,
329  int theWidth ) const
330 {
331  if ( theString.isEmpty() )
332  return QStringList();
333  if ( 50 >= theWidth )
334  return QStringList() << theString;
335  //QString myDebug = QString("Word wrapping: %1 into %2 pixels").arg(theString).arg(theWidth);
336  //qDebug(myDebug.toLocal8Bit());
337  //iterate the string
338  QStringList myList;
339  QString myCumulativeLine = "";
340  QString myStringToPreviousSpace = "";
341  int myPreviousSpacePos = 0;
342  for ( int i = 0; i < theString.count(); ++i )
343  {
344  QChar myChar = theString.at( i );
345  if ( myChar == QChar( ' ' ) )
346  {
347  myStringToPreviousSpace = myCumulativeLine;
348  myPreviousSpacePos = i;
349  }
350  myCumulativeLine += myChar;
351  if ( theMetrics.width( myCumulativeLine ) >= theWidth )
352  {
353  //time to wrap
354  //@todo deal with long strings that have no spaces
355  //forcing a break at current pos...
356  myList << myStringToPreviousSpace.trimmed();
357  i = myPreviousSpacePos;
358  myStringToPreviousSpace = "";
359  myCumulativeLine = "";
360  }
361  }//end of i loop
362  //add whatever is left in the string to the list
363  if ( !myCumulativeLine.trimmed().isEmpty() )
364  {
365  myList << myCumulativeLine.trimmed();
366  }
367 
368  //qDebug("Wrapped legend entry:");
369  //qDebug(theString);
370  //qDebug(myList.join("\n").toLocal8Bit());
371  return myList;
372 
373 }
374 
375 
376 
378 {
379  return mVerticalSpacing;
380 }
381 
382 
384 {
385  mVerticalSpacing = theValue;
386 }
387 
388 
390 {
391  return mHorizontalSpacing;
392 }
393 
394 
396 {
397  mHorizontalSpacing = theValue;
398 }
bool canConvert(Type t) const
void setPointSize(int pointSize)
int width() const
void fillRect(const QRectF &rectangle, const QBrush &brush)
QgsDetailedItemDelegate(QObject *parent=nullptr)
This class is the data only representation of a QgsDetailedItemWidget, designed to be used in custom ...
void setColorAt(qreal position, const QColor &color)
A widget renderer for detailed item views.
virtual QSize sizeHint() const
void save()
T value() const
void setHorizontalSpacing(int theValue)
void paint(QPainter *thePainter, const QStyleOptionViewItem &theOption, const QModelIndex &theIndex) const override
Reimplement for parent class.
void setBold(bool enable)
void resize(int w, int h)
void setFont(const QFont &font)
void setEnabled(bool)
int count(const T &value) const
void setPen(const QColor &color)
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
bool isEmpty() const
QString trimmed() const
void drawText(const QPointF &position, const QString &text)
QSize sizeHint(const QStyleOptionViewItem &theOption, const QModelIndex &theIndex) const override
Reimplement for parent class.
bool isNull() const
int height() const
int width(const QString &text, int len) const
QColor lighter(int factor) const
void repaint()
void restore()
void setChecked(bool)
QVariant data(int role) const
int count() const
const QChar at(int position) const
int height() const
int height() const
QPixmap grabWidget(QWidget *widget, const QRect &rectangle)
void setAutoFillBackground(bool enabled)
void setData(const QgsDetailedItemData &theData)
int pointSize() const
void setChecked(bool theFlag)
void render(QPaintDevice *target, const QPoint &targetOffset, const QRegion &sourceRegion, QFlags< QWidget::RenderFlag > renderFlags)