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