QGIS API Documentation 3.29.0-Master (8c80f25a4f)
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
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
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() );
39}
40
42{
43 delete mpCheckBox;
44 delete mpWidget;
45}
46
47void 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 ).userType() == QMetaType::type( "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 ).userType() == QMetaType::type( "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
95void 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
241void 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
262void 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
279int 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
305QFont QgsDetailedItemDelegate::detailFont( const QStyleOptionViewItem &option ) const
306{
307 const QFont myFont = option.font;
308 return myFont;
309}
310
311QFont QgsDetailedItemDelegate::categoryFont( const QStyleOptionViewItem &option ) const
312{
313 QFont myFont = option.font;
314 myFont.setBold( true );
315 return myFont;
316}
317
318QFont 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
327QStringList 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.