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