QGIS API Documentation  3.25.0-Master (10b47c2603)
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 "qgsrendercontext.h"
22 #include <QPainter>
23 #include <QFont>
24 #include <QFontMetrics>
25 #include <QStyleOptionViewItem>
26 #include <QModelIndex>
27 #include <QCheckBox>
28 #include <QLinearGradient>
30  : QAbstractItemDelegate( parent )
31  , mpWidget( new QgsDetailedItemWidget() )
32  , mpCheckBox( new QCheckBox() )
33 
34 {
35  //mpWidget->setFixedHeight(80);
36  mpCheckBox->resize( mpCheckBox->sizeHint().height(), mpCheckBox->sizeHint().height() );
37  setVerticalSpacing( 3 );
39 }
40 
42 {
43  delete mpCheckBox;
44  delete mpWidget;
45 }
46 
47 void QgsDetailedItemDelegate::paint( QPainter *thepPainter,
48  const QStyleOptionViewItem &option,
49  const QModelIndex &index ) const
50 {
51  // After painting we need to restore the painter to its original state
52  const QgsScopedQPainterState painterState( thepPainter );
53  if ( index.data( Qt::UserRole ).canConvert<QgsDetailedItemData>() )
54  {
55  const QgsDetailedItemData myData =
56  index.data( Qt::UserRole ).value<QgsDetailedItemData>();
57  if ( myData.isRenderedAsWidget() )
58  {
59  paintAsWidget( thepPainter, option, myData );
60  }
61  else //render by manually painting
62  {
63  paintManually( thepPainter, option, myData );
64  }
65  } //can convert item data
66 }
67 
68 
69 
71  const QStyleOptionViewItem &option,
72  const QModelIndex &index ) const
73 {
74  if ( index.data( Qt::UserRole ).canConvert<QgsDetailedItemData>() )
75  {
76  const QgsDetailedItemData myData =
77  index.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( option, myData ) );
86  //return QSize(theOption.rect.width(), myHeight + myVerticalSpacer);
87  }
88  }
89  else //can't convert to qgsdetaileditemdata
90  {
91  return QSize( 50, 50 ); //fallback
92  }
93 }
94 
95 void QgsDetailedItemDelegate::paintManually( QPainter *thepPainter,
96  const QStyleOptionViewItem &option,
97  const QgsDetailedItemData &data ) const
98 {
99  //
100  // Get the strings and checkbox properties
101  //
102  //bool myCheckState = index.model()->data(theIndex, Qt::CheckStateRole).toBool();
103  mpCheckBox->setChecked( data.isChecked() );
104  mpCheckBox->setEnabled( data.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  const QFontMetrics myTitleMetrics( titleFont( option ) );
113  const QFontMetrics myDetailMetrics( detailFont( option ) );
114  int myTextStartX = option.rect.x() + horizontalSpacing();
115  int myTextStartY = option.rect.y() + verticalSpacing();
116  const int myHeight = myTitleMetrics.height() + verticalSpacing();
117 
118  //
119  // Draw the item background with a gradient if its highlighted
120  //
121  if ( option.state & QStyle::State_Selected )
122  {
123  drawHighlight( option, thepPainter, height( option, data ) );
124  thepPainter->setPen( option.palette.highlightedText().color() );
125  }
126  else
127  {
128  thepPainter->setPen( option.palette.text().color() );
129  }
130 
131 
132  //
133  // Draw the checkbox
134  //
135  if ( data.isCheckable() )
136  {
137  thepPainter->drawPixmap( option.rect.x(),
138  option.rect.y() + mpCheckBox->height(),
139  myCbxPixmap );
140  myTextStartX = option.rect.x() + myCbxPixmap.width() + horizontalSpacing();
141  }
142  //
143  // Draw the decoration (pixmap)
144  //
145  bool myIconFlag = false;
146  const QPixmap myDecoPixmap = data.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( option ) );
178  thepPainter->drawText( myTextStartX,
179  myTextStartY,
180  data.title() );
181  //
182  // Draw the description with word wrapping if needed
183  //
184  thepPainter->setFont( detailFont( option ) ); //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  const QStringList myList =
194  wordWrap( data.detail(), myDetailMetrics, option.rect.width() - myTextStartX );
195  QStringListIterator myLineWrapIterator( myList );
196  while ( myLineWrapIterator.hasNext() )
197  {
198  const 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( option ) ); //return to original font set by client
209  thepPainter->drawText( myTextStartX,
210  myTextStartY,
211  data.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( data.category(), myCategoryMetrics, option.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 &option,
243  const QgsDetailedItemData &data ) const
244 {
245 
246  mpWidget->setChecked( data.isChecked() );
247  mpWidget->setData( data );
248  mpWidget->resize( option.rect.width(), mpWidget->height() );
249  mpWidget->setAutoFillBackground( true );
250  //mpWidget->setAttribute(Qt::WA_OpaquePaintEvent);
251  mpWidget->repaint();
252  if ( option.state & QStyle::State_Selected )
253  {
254  drawHighlight( option, thepPainter, height( option, data ) );
255  }
256  const QPixmap myPixmap = mpWidget->grab();
257  thepPainter->drawPixmap( option.rect.x(),
258  option.rect.y(),
259  myPixmap );
260 }//render as widget
261 
262 void QgsDetailedItemDelegate::drawHighlight( const QStyleOptionViewItem &option,
263  QPainter *thepPainter,
264  int height ) const
265 {
266  const QColor myColor1 = option.palette.highlight().color();
267  QColor myColor2 = myColor1;
268  myColor2 = myColor2.lighter( 110 ); //10% lighter
269  QLinearGradient myGradient( QPointF( 0, option.rect.y() ),
270  QPointF( 0, option.rect.y() + height ) );
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( option.rect, QBrush( myGradient ) );
277 }
278 
279 int QgsDetailedItemDelegate::height( const QStyleOptionViewItem &option,
280  const QgsDetailedItemData &data ) const
281 {
282  const QFontMetrics myTitleMetrics( titleFont( option ) );
283  const QFontMetrics myDetailMetrics( detailFont( option ) );
284  const QFontMetrics myCategoryMetrics( categoryFont( option ) );
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  const QStringList myList = wordWrap( data.detail(),
289  myDetailMetrics,
290  option.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( data.category(),
297  myCategoryMetrics,
298  option.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 &option ) const
306 {
307  const QFont myFont = option.font;
308  return myFont;
309 }
310 
311 QFont QgsDetailedItemDelegate::categoryFont( const QStyleOptionViewItem &option ) const
312 {
313  QFont myFont = option.font;
314  myFont.setBold( true );
315  return myFont;
316 }
317 
318 QFont QgsDetailedItemDelegate::titleFont( const QStyleOptionViewItem &option ) const
319 {
320  QFont myTitleFont = detailFont( option );
321  myTitleFont.setBold( true );
322  myTitleFont.setPointSize( myTitleFont.pointSize() );
323  return myTitleFont;
324 }
325 
326 
327 QStringList QgsDetailedItemDelegate::wordWrap( const QString &string,
328  const QFontMetrics &metrics,
329  int width ) const
330 {
331  if ( string.isEmpty() )
332  return QStringList();
333  if ( 50 >= width )
334  return QStringList() << string;
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 < string.count(); ++i )
343  {
344  const QChar myChar = string.at( i );
345  if ( myChar == QChar( ' ' ) )
346  {
347  myStringToPreviousSpace = myCumulativeLine;
348  myPreviousSpacePos = i;
349  }
350  myCumulativeLine += myChar;
351  if ( metrics.boundingRect( myCumulativeLine ).width() >= width )
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.clear();
359  myCumulativeLine.clear();
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 = value;
386 }
387 
388 
390 {
391  return mHorizontalSpacing;
392 }
393 
394 
396 {
397  mHorizontalSpacing = value;
398 }
This class is the data only representation of a QgsDetailedItemWidget, designed to be used in custom ...
QString category() const
Returns the item's category.
bool isCheckable() const
Returns true if the item is checkable.
bool isEnabled() const
Returns true if the item is enabled.
QString title() const
Returns the item's title.
bool isRenderedAsWidget() const
Returns true if the item will be rendered using a widget.
QPixmap icon() const
Returns the item's icon.
QString detail() const
Returns the detailed description for the item.
bool isChecked() const
Returns true if the item is checked.
QgsDetailedItemDelegate(QObject *parent=nullptr)
Constructor for QgsDetailedItemDelegate.
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
Reimplement for parent class.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Reimplement for parent class.
A widget renderer for detailed item views.
void setData(const QgsDetailedItemData &data)
Scoped object for saving and restoring a QPainter object's state.