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