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