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