QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 
46 void QgsDetailedItemDelegate::paint( QPainter *thepPainter,
47  const QStyleOptionViewItem &option,
48  const QModelIndex &index ) const
49 {
50  // After painting we need to restore the painter to its original state
51  thepPainter->save();
52  if ( index.data( Qt::UserRole ).canConvert<QgsDetailedItemData>() )
53  {
54  QgsDetailedItemData myData =
55  index.data( Qt::UserRole ).value<QgsDetailedItemData>();
56  if ( myData.isRenderedAsWidget() )
57  {
58  paintAsWidget( thepPainter, option, myData );
59  }
60  else //render by manually painting
61  {
62  paintManually( thepPainter, option, myData );
63  }
64  } //can convert item data
65  thepPainter->restore();
66 }
67 
68 
69 
71  const QStyleOptionViewItem &option,
72  const QModelIndex &index ) const
73 {
74  if ( index.data( Qt::UserRole ).canConvert<QgsDetailedItemData>() )
75  {
76  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  QFontMetrics myTitleMetrics( titleFont( option ) );
113  QFontMetrics myDetailMetrics( detailFont( option ) );
114  int myTextStartX = option.rect.x() + horizontalSpacing();
115  int myTextStartY = option.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 ( 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  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  QStringList myList =
194  wordWrap( data.detail(), myDetailMetrics, option.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( 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  QPixmap myPixmap = QPixmap::grabWidget( mpWidget );
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  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  QFontMetrics myTitleMetrics( titleFont( option ) );
283  QFontMetrics myDetailMetrics( detailFont( option ) );
284  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  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  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  QChar myChar = string.at( i );
345  if ( myChar == QChar( ' ' ) )
346  {
347  myStringToPreviousSpace = myCumulativeLine;
348  myPreviousSpacePos = i;
349  }
350  myCumulativeLine += myChar;
351  if ( metrics.width( myCumulativeLine ) >= 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 }
QgsDetailedItemDelegate(QObject *parent=nullptr)
Constructor for QgsDetailedItemDelegate.
This class is the data only representation of a QgsDetailedItemWidget, designed to be used in custom ...
bool isRenderedAsWidget() const
Returns true if the item will be rendered using a widget.
bool isEnabled() const
Returns true if the item is enabled.
A widget renderer for detailed item views.
bool isChecked() const
Returns true if the item is checked.
QString category() const
Returns the item&#39;s category.
QPixmap icon() const
Returns the item&#39;s icon.
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
Reimplement for parent class.
void setData(const QgsDetailedItemData &data)
QString title() const
Returns the item&#39;s title.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Reimplement for parent class.
bool isCheckable() const
Returns true if the item is checkable.
QString detail() const
Returns the detailed description for the item.