1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qitemdelegate.h"
5
6#include <qabstractitemmodel.h>
7#include <qapplication.h>
8#include <qbrush.h>
9#include <qpainter.h>
10#include <qpalette.h>
11#include <qpoint.h>
12#include <qrect.h>
13#include <qsize.h>
14#include <qstyle.h>
15#include <qdatetime.h>
16#include <qstyleoption.h>
17#include <qevent.h>
18#include <qpixmap.h>
19#include <qbitmap.h>
20#include <qpixmapcache.h>
21#include <qitemeditorfactory.h>
22#include <qmetaobject.h>
23#include <qtextlayout.h>
24#include <private/qabstractitemdelegate_p.h>
25#include <private/qabstractitemmodel_p.h>
26#include <private/qtextengine_p.h>
27#include <qdebug.h>
28#include <qlocale.h>
29#include <qmath.h>
30
31#include <limits.h>
32
33// keep in sync with QAbstractItemDelegate::helpEvent()
34#ifndef DBL_DIG
35# define DBL_DIG 10
36#endif
37
38QT_BEGIN_NAMESPACE
39
40class QItemDelegatePrivate : public QAbstractItemDelegatePrivate
41{
42 Q_DECLARE_PUBLIC(QItemDelegate)
43
44public:
45 QItemDelegatePrivate() : f(nullptr), clipPainting(true) {}
46
47 inline const QItemEditorFactory *editorFactory() const
48 { return f ? f : QItemEditorFactory::defaultFactory(); }
49
50 inline QIcon::Mode iconMode(QStyle::State state) const
51 {
52 if (!(state & QStyle::State_Enabled)) return QIcon::Disabled;
53 if (state & QStyle::State_Selected) return QIcon::Selected;
54 return QIcon::Normal;
55 }
56
57 inline QIcon::State iconState(QStyle::State state) const
58 { return state & QStyle::State_Open ? QIcon::On : QIcon::Off; }
59
60 inline static QString replaceNewLine(QString text)
61 {
62 text.replace(before: u'\n', after: QChar::LineSeparator);
63 return text;
64 }
65
66 QString valueToText(const QVariant &value, const QStyleOptionViewItem &option) const;
67
68 QItemEditorFactory *f;
69 bool clipPainting;
70
71 QRect displayRect(const QModelIndex &index, const QStyleOptionViewItem &option,
72 const QRect &decorationRect, const QRect &checkRect) const;
73 QRect textLayoutBounds(const QStyleOptionViewItem &option,
74 const QRect &decorationRect, const QRect &checkRect) const;
75 QSizeF doTextLayout(int lineWidth) const;
76 mutable QTextLayout textLayout;
77 mutable QTextOption textOption;
78
79 const QWidget *widget(const QStyleOptionViewItem &option) const
80 {
81 return option.widget;
82 }
83
84 // ### temporary hack until we have QStandardItemDelegate
85 mutable struct Icon {
86 QIcon icon;
87 QIcon::Mode mode;
88 QIcon::State state;
89 } tmp;
90};
91
92QRect QItemDelegatePrivate::displayRect(const QModelIndex &index, const QStyleOptionViewItem &option,
93 const QRect &decorationRect, const QRect &checkRect) const
94{
95 Q_Q(const QItemDelegate);
96 const QVariant value = index.data(arole: Qt::DisplayRole);
97 if (!value.isValid() || value.isNull())
98 return QRect();
99
100 const QString text = valueToText(value, option);
101 const QVariant fontVal = index.data(arole: Qt::FontRole);
102 const QFont fnt = qvariant_cast<QFont>(v: fontVal).resolve(option.font);
103 return q->textRectangle(painter: nullptr,
104 rect: textLayoutBounds(option, decorationRect, checkRect),
105 font: fnt, text);
106}
107
108// similar to QCommonStylePrivate::viewItemSize(Qt::DisplayRole)
109QRect QItemDelegatePrivate::textLayoutBounds(const QStyleOptionViewItem &option,
110 const QRect &decorationRect, const QRect &checkRect) const
111{
112 QRect rect = option.rect;
113 const QWidget *w = widget(option);
114 QStyle *style = w ? w->style() : QApplication::style();
115 const bool wrapText = option.features & QStyleOptionViewItem::WrapText;
116 // see QItemDelegate::drawDisplay
117 const int textMargin = style->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: nullptr, widget: w) + 1;
118 switch (option.decorationPosition) {
119 case QStyleOptionViewItem::Left:
120 case QStyleOptionViewItem::Right:
121 rect.setWidth(wrapText && rect.isValid() ? rect.width() - 2 * textMargin : (QFIXED_MAX));
122 break;
123 case QStyleOptionViewItem::Top:
124 case QStyleOptionViewItem::Bottom:
125 rect.setWidth(wrapText ? option.decorationSize.width() - 2 * textMargin : (QFIXED_MAX));
126 break;
127 }
128
129 if (wrapText) {
130 if (!decorationRect.isNull())
131 rect.setWidth(rect.width() - decorationRect.width() - 2 * textMargin);
132 if (!checkRect.isNull())
133 rect.setWidth(rect.width() - checkRect.width() - 2 * textMargin);
134 // adjust height to be sure that the text fits
135 const QSizeF size = doTextLayout(lineWidth: rect.width());
136 rect.setHeight(qCeil(v: size.height()));
137 }
138
139 return rect;
140}
141
142QSizeF QItemDelegatePrivate::doTextLayout(int lineWidth) const
143{
144 qreal height = 0;
145 qreal widthUsed = 0;
146 textLayout.beginLayout();
147 while (true) {
148 QTextLine line = textLayout.createLine();
149 if (!line.isValid())
150 break;
151 line.setLineWidth(lineWidth);
152 line.setPosition(QPointF(0, height));
153 height += line.height();
154 widthUsed = qMax(a: widthUsed, b: line.naturalTextWidth());
155 }
156 textLayout.endLayout();
157 return QSizeF(widthUsed, height);
158}
159
160/*!
161 \class QItemDelegate
162
163 \brief The QItemDelegate class provides display and editing facilities for
164 data items from a model.
165
166 \ingroup model-view
167 \inmodule QtWidgets
168
169 QItemDelegate can be used to provide custom display features and editor
170 widgets for item views based on QAbstractItemView subclasses. Using a
171 delegate for this purpose allows the display and editing mechanisms to be
172 customized and developed independently from the model and view.
173
174 The QItemDelegate class is one of the \l{Model/View Classes} and
175 is part of Qt's \l{Model/View Programming}{model/view framework}.
176 Note that QStyledItemDelegate has taken over the job of drawing
177 Qt's item views. We recommend the use of QStyledItemDelegate when
178 creating new delegates.
179
180 When displaying items from a custom model in a standard view, it is
181 often sufficient to simply ensure that the model returns appropriate
182 data for each of the \l{Qt::ItemDataRole}{roles} that determine the
183 appearance of items in views. The default delegate used by Qt's
184 standard views uses this role information to display items in most
185 of the common forms expected by users. However, it is sometimes
186 necessary to have even more control over the appearance of items than
187 the default delegate can provide.
188
189 This class provides default implementations of the functions for
190 painting item data in a view and editing data from item models.
191 Default implementations of the paint() and sizeHint() virtual
192 functions, defined in QAbstractItemDelegate, are provided to
193 ensure that the delegate implements the correct basic behavior
194 expected by views. You can reimplement these functions in
195 subclasses to customize the appearance of items.
196
197 When editing data in an item view, QItemDelegate provides an
198 editor widget, which is a widget that is placed on top of the view
199 while editing takes place. Editors are created with a
200 QItemEditorFactory; a default static instance provided by
201 QItemEditorFactory is installed on all item delegates. You can set
202 a custom factory using setItemEditorFactory() or set a new default
203 factory with QItemEditorFactory::setDefaultFactory(). It is the
204 data stored in the item model with the Qt::EditRole that is edited.
205
206 Only the standard editing functions for widget-based delegates are
207 reimplemented here:
208
209 \list
210 \li createEditor() returns the widget used to change data from the model
211 and can be reimplemented to customize editing behavior.
212 \li setEditorData() provides the widget with data to manipulate.
213 \li updateEditorGeometry() ensures that the editor is displayed correctly
214 with respect to the item view.
215 \li setModelData() returns updated data to the model.
216 \endlist
217
218 The closeEditor() signal indicates that the user has completed editing the data,
219 and that the editor widget can be destroyed.
220
221 \section1 Standard Roles and Data Types
222
223 The default delegate used by the standard views supplied with Qt
224 associates each standard role (defined by Qt::ItemDataRole) with certain
225 data types. Models that return data in these types can influence the
226 appearance of the delegate as described in the following table.
227
228 \table
229 \header \li Role \li Accepted Types
230 \omit
231 \row \li \l Qt::AccessibleDescriptionRole \li QString
232 \row \li \l Qt::AccessibleTextRole \li QString
233 \endomit
234 \row \li \l Qt::BackgroundRole \li QBrush (\since 4.2)
235 \row \li \l Qt::CheckStateRole \li Qt::CheckState
236 \row \li \l Qt::DecorationRole \li QIcon, QPixmap and QColor
237 \row \li \l Qt::DisplayRole \li QString and types with a string representation
238 \row \li \l Qt::EditRole \li See QItemEditorFactory for details
239 \row \li \l Qt::FontRole \li QFont
240 \row \li \l Qt::SizeHintRole \li QSize
241 \omit
242 \row \li \l Qt::StatusTipRole \li
243 \endomit
244 \row \li \l Qt::TextAlignmentRole \li Qt::Alignment
245 \row \li \l Qt::ForegroundRole \li QBrush (\since 4.2)
246 \omit
247 \row \li \l Qt::ToolTipRole
248 \row \li \l Qt::WhatsThisRole
249 \endomit
250 \endtable
251
252 If the default delegate does not allow the level of customization that
253 you need, either for display purposes or for editing data, it is possible to
254 subclass QItemDelegate to implement the desired behavior.
255
256 \section1 Subclassing
257
258 When subclassing QItemDelegate to create a delegate that displays items
259 using a custom renderer, it is important to ensure that the delegate can
260 render items suitably for all the required states; such as selected,
261 disabled, checked. The documentation for the paint() function contains
262 some hints to show how this can be achieved.
263
264 You can provide custom editors by using a QItemEditorFactory. The following
265 code shows how a custom editor can be made available to delegates with the
266 default item editor factory.
267
268 \snippet code/src_gui_itemviews_qitemeditorfactory.cpp setDefaultFactory
269
270 After the default factory has been set, all standard item delegates
271 will use it (also the delegates that were created before setting the
272 default factory).
273
274 This way, you can avoid subclassing QItemDelegate, and all values of the
275 specified type (for example QMetaType::QDateTime) will be edited using the
276 provided editor (like \c{MyFancyDateTimeEdit} in the above example).
277
278 An alternative is to reimplement createEditor(), setEditorData(),
279 setModelData(), and updateEditorGeometry(). This process is described
280 in the \l{A simple delegate}{Model/View Programming overview documentation}.
281
282 \section1 QStyledItemDelegate vs. QItemDelegate
283
284 Since Qt 4.4, there are two delegate classes: QItemDelegate and
285 QStyledItemDelegate. However, the default delegate is QStyledItemDelegate.
286 These two classes are independent alternatives to painting and providing
287 editors for items in views. The difference between them is that
288 QStyledItemDelegate uses the current style to paint its items. We therefore
289 recommend using QStyledItemDelegate as the base class when implementing
290 custom delegates or when working with Qt style sheets. The code required
291 for either class should be equal unless the custom delegate needs to use
292 the style for drawing.
293
294 \sa {Delegate Classes}, QStyledItemDelegate, QAbstractItemDelegate
295*/
296
297/*!
298 Constructs an item delegate with the given \a parent.
299*/
300
301QItemDelegate::QItemDelegate(QObject *parent)
302 : QAbstractItemDelegate(*new QItemDelegatePrivate(), parent)
303{
304
305}
306
307/*!
308 Destroys the item delegate.
309*/
310
311QItemDelegate::~QItemDelegate()
312{
313}
314
315/*!
316 \property QItemDelegate::clipping
317 \brief if the delegate should clip the paint events
318 \since 4.2
319
320 This property will set the paint clip to the size of the item.
321 The default value is on. It is useful for cases such
322 as when images are larger than the size of the item.
323*/
324
325bool QItemDelegate::hasClipping() const
326{
327 Q_D(const QItemDelegate);
328 return d->clipPainting;
329}
330
331void QItemDelegate::setClipping(bool clip)
332{
333 Q_D(QItemDelegate);
334 d->clipPainting = clip;
335}
336
337QString QItemDelegatePrivate::valueToText(const QVariant &value, const QStyleOptionViewItem &option) const
338{
339 return textForRole(role: Qt::DisplayRole, value, locale: option.locale, DBL_DIG);
340}
341
342/*!
343 Renders the delegate using the given \a painter and style \a option for
344 the item specified by \a index.
345
346 When reimplementing this function in a subclass, you should update the area
347 held by the option's \l{QStyleOption::rect}{rect} variable, using the
348 option's \l{QStyleOption::state}{state} variable to determine the state of
349 the item to be displayed, and adjust the way it is painted accordingly.
350
351 For example, a selected item may need to be displayed differently to
352 unselected items, as shown in the following code:
353
354 \code
355 if (option.state & QStyle::State_Selected)
356 painter->fillRect(option.rect, option.palette.highlight());
357 \endcode
358
359 After painting, you should ensure that the painter is returned to its
360 the state it was supplied in when this function was called. For example,
361 it may be useful to call QPainter::save() before painting and
362 QPainter::restore() afterwards.
363
364 \sa QStyle::State
365*/
366void QItemDelegate::paint(QPainter *painter,
367 const QStyleOptionViewItem &option,
368 const QModelIndex &index) const
369{
370 Q_D(const QItemDelegate);
371 Q_ASSERT(index.isValid());
372
373 QStyleOptionViewItem opt = setOptions(index, option);
374
375 // prepare
376 painter->save();
377 if (d->clipPainting)
378 painter->setClipRect(opt.rect);
379
380 // get the data and the rectangles
381
382 QVariant value;
383
384 QPixmap pixmap;
385 QRect decorationRect;
386 value = index.data(arole: Qt::DecorationRole);
387 if (value.isValid()) {
388 // ### we need the pixmap to call the virtual function
389 pixmap = decoration(option: opt, variant: value);
390 if (value.userType() == QMetaType::QIcon) {
391 d->tmp.icon = qvariant_cast<QIcon>(v: value);
392 d->tmp.mode = d->iconMode(state: option.state);
393 d->tmp.state = d->iconState(state: option.state);
394 const QSize size = d->tmp.icon.actualSize(size: option.decorationSize,
395 mode: d->tmp.mode, state: d->tmp.state);
396 decorationRect = QRect(QPoint(0, 0), size);
397 } else {
398 d->tmp.icon = QIcon();
399 decorationRect = QRect(QPoint(0, 0), pixmap.size());
400 }
401 } else {
402 d->tmp.icon = QIcon();
403 decorationRect = QRect();
404 }
405
406 QRect checkRect;
407 Qt::CheckState checkState = Qt::Unchecked;
408 value = index.data(arole: Qt::CheckStateRole);
409 if (value.isValid()) {
410 checkState = QtPrivate::legacyEnumValueFromModelData<Qt::CheckState>(data: value);
411 checkRect = doCheck(option: opt, bounding: opt.rect, variant: value);
412 }
413
414 QString text;
415 QRect displayRect;
416 value = index.data(arole: Qt::DisplayRole);
417 if (value.isValid() && !value.isNull()) {
418 text = d->valueToText(value, option: opt);
419 displayRect = d->displayRect(index, option: opt, decorationRect, checkRect);
420 }
421
422 // do the layout
423
424 doLayout(option: opt, checkRect: &checkRect, iconRect: &decorationRect, textRect: &displayRect, hint: false);
425
426 // draw the item
427
428 drawBackground(painter, option: opt, index);
429 drawCheck(painter, option: opt, rect: checkRect, state: checkState);
430 drawDecoration(painter, option: opt, rect: decorationRect, pixmap);
431 drawDisplay(painter, option: opt, rect: displayRect, text);
432 drawFocus(painter, option: opt, rect: displayRect);
433
434 // done
435 painter->restore();
436}
437
438/*!
439 Returns the size needed by the delegate to display the item
440 specified by \a index, taking into account the style information
441 provided by \a option.
442
443 When reimplementing this function, note that in case of text
444 items, QItemDelegate adds a margin (i.e. 2 *
445 QStyle::PM_FocusFrameHMargin) to the length of the text.
446*/
447
448QSize QItemDelegate::sizeHint(const QStyleOptionViewItem &option,
449 const QModelIndex &index) const
450{
451 Q_D(const QItemDelegate);
452 QVariant value = index.data(arole: Qt::SizeHintRole);
453 if (value.isValid())
454 return qvariant_cast<QSize>(v: value);
455 QRect decorationRect = rect(option, index, role: Qt::DecorationRole);
456 QRect checkRect = rect(option, index, role: Qt::CheckStateRole);
457 QRect displayRect = d->displayRect(index, option, decorationRect, checkRect);
458
459 doLayout(option, checkRect: &checkRect, iconRect: &decorationRect, textRect: &displayRect, hint: true);
460
461 return (decorationRect|displayRect|checkRect).size();
462}
463
464/*!
465 Returns the widget used to edit the item specified by \a index
466 for editing. The \a parent widget and style \a option are used to
467 control how the editor widget appears.
468
469 \sa QAbstractItemDelegate::createEditor()
470*/
471
472QWidget *QItemDelegate::createEditor(QWidget *parent,
473 const QStyleOptionViewItem &,
474 const QModelIndex &index) const
475{
476 Q_D(const QItemDelegate);
477 if (!index.isValid())
478 return nullptr;
479 const QItemEditorFactory *factory = d->f;
480 if (factory == nullptr)
481 factory = QItemEditorFactory::defaultFactory();
482 QWidget *w = factory->createEditor(userType: index.data(arole: Qt::EditRole).userType(), parent);
483 if (w)
484 w->setFocusPolicy(Qt::WheelFocus);
485 return w;
486}
487
488/*!
489 Sets the data to be displayed and edited by the \a editor from the
490 data model item specified by the model \a index.
491
492 The default implementation stores the data in the \a editor
493 widget's \l {Qt's Property System} {user property}.
494
495 \sa QMetaProperty::isUser()
496*/
497
498void QItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
499{
500 QVariant v = index.data(arole: Qt::EditRole);
501 QByteArray n = editor->metaObject()->userProperty().name();
502
503 if (!n.isEmpty()) {
504 if (!v.isValid())
505 v = QVariant(editor->property(name: n).metaType());
506 editor->setProperty(name: n, value: v);
507 }
508}
509
510/*!
511 Gets data from the \a editor widget and stores it in the specified
512 \a model at the item \a index.
513
514 The default implementation gets the value to be stored in the data
515 model from the \a editor widget's \l {Qt's Property System} {user
516 property}.
517
518 \sa QMetaProperty::isUser()
519*/
520
521void QItemDelegate::setModelData(QWidget *editor,
522 QAbstractItemModel *model,
523 const QModelIndex &index) const
524{
525 Q_D(const QItemDelegate);
526 Q_ASSERT(model);
527 Q_ASSERT(editor);
528 QByteArray n = editor->metaObject()->userProperty().name();
529 if (n.isEmpty())
530 n = d->editorFactory()->valuePropertyName(
531 userType: model->data(index, role: Qt::EditRole).userType());
532 if (!n.isEmpty())
533 model->setData(index, value: editor->property(name: n), role: Qt::EditRole);
534}
535
536/*!
537 Updates the \a editor for the item specified by \a index
538 according to the style \a option given.
539*/
540
541void QItemDelegate::updateEditorGeometry(QWidget *editor,
542 const QStyleOptionViewItem &option,
543 const QModelIndex &index) const
544{
545 if (!editor)
546 return;
547 Q_ASSERT(index.isValid());
548 QPixmap pixmap = decoration(option, variant: index.data(arole: Qt::DecorationRole));
549 QString text = QItemDelegatePrivate::replaceNewLine(text: index.data(arole: Qt::DisplayRole).toString());
550 QRect pixmapRect = QRect(QPoint(0, 0), option.decorationSize).intersected(other: pixmap.rect());
551 QRect textRect = textRectangle(painter: nullptr, rect: option.rect, font: option.font, text);
552 QRect checkRect = doCheck(option, bounding: textRect, variant: index.data(arole: Qt::CheckStateRole));
553 QStyleOptionViewItem opt = option;
554 opt.showDecorationSelected = true; // let the editor take up all available space
555 doLayout(option: opt, checkRect: &checkRect, iconRect: &pixmapRect, textRect: &textRect, hint: false);
556 editor->setGeometry(textRect);
557}
558
559/*!
560 Returns the editor factory used by the item delegate.
561 If no editor factory is set, the function will return null.
562
563 \sa setItemEditorFactory()
564*/
565QItemEditorFactory *QItemDelegate::itemEditorFactory() const
566{
567 Q_D(const QItemDelegate);
568 return d->f;
569}
570
571/*!
572 Sets the editor factory to be used by the item delegate to be the \a factory
573 specified. If no editor factory is set, the item delegate will use the
574 default editor factory.
575
576 \sa itemEditorFactory()
577*/
578void QItemDelegate::setItemEditorFactory(QItemEditorFactory *factory)
579{
580 Q_D(QItemDelegate);
581 d->f = factory;
582}
583
584/*!
585 Renders the item view \a text within the rectangle specified by \a rect
586 using the given \a painter and style \a option.
587*/
588
589void QItemDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option,
590 const QRect &rect, const QString &text) const
591{
592 Q_D(const QItemDelegate);
593
594 QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
595 ? QPalette::Normal : QPalette::Disabled;
596 if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
597 cg = QPalette::Inactive;
598 if (option.state & QStyle::State_Selected) {
599 painter->fillRect(rect, option.palette.brush(cg, cr: QPalette::Highlight));
600 painter->setPen(option.palette.color(cg, cr: QPalette::HighlightedText));
601 } else {
602 painter->setPen(option.palette.color(cg, cr: QPalette::Text));
603 }
604
605 if (text.isEmpty())
606 return;
607
608 if (option.state & QStyle::State_Editing) {
609 painter->save();
610 painter->setPen(option.palette.color(cg, cr: QPalette::Text));
611 painter->drawRect(r: rect.adjusted(xp1: 0, yp1: 0, xp2: -1, yp2: -1));
612 painter->restore();
613 }
614
615 const QStyleOptionViewItem opt = option;
616
617 const QWidget *widget = d->widget(option);
618 QStyle *style = widget ? widget->style() : QApplication::style();
619 const int textMargin = style->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: nullptr, widget) + 1;
620 QRect textRect = rect.adjusted(xp1: textMargin, yp1: 0, xp2: -textMargin, yp2: 0); // remove width padding
621 const bool wrapText = opt.features & QStyleOptionViewItem::WrapText;
622 d->textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap);
623 d->textOption.setTextDirection(option.direction);
624 d->textOption.setAlignment(QStyle::visualAlignment(direction: option.direction, alignment: option.displayAlignment));
625 d->textLayout.setTextOption(d->textOption);
626 d->textLayout.setFont(option.font);
627 d->textLayout.setText(QItemDelegatePrivate::replaceNewLine(text));
628
629 QSizeF textLayoutSize = d->doTextLayout(lineWidth: textRect.width());
630
631 if (textRect.width() < textLayoutSize.width()
632 || textRect.height() < textLayoutSize.height()) {
633 QString elided;
634 int start = 0;
635 int end = text.indexOf(ch: QChar::LineSeparator, from: start);
636 if (end == -1) {
637 elided += option.fontMetrics.elidedText(text, mode: option.textElideMode, width: textRect.width());
638 } else {
639 while (end != -1) {
640 elided += option.fontMetrics.elidedText(text: text.mid(position: start, n: end - start),
641 mode: option.textElideMode, width: textRect.width());
642 elided += QChar::LineSeparator;
643 start = end + 1;
644 end = text.indexOf(ch: QChar::LineSeparator, from: start);
645 }
646 //let's add the last line (after the last QChar::LineSeparator)
647 elided += option.fontMetrics.elidedText(text: text.mid(position: start),
648 mode: option.textElideMode, width: textRect.width());
649 }
650 d->textLayout.setText(elided);
651 textLayoutSize = d->doTextLayout(lineWidth: textRect.width());
652 }
653
654 const QSize layoutSize(textRect.width(), int(textLayoutSize.height()));
655 const QRect layoutRect = QStyle::alignedRect(direction: option.direction, alignment: option.displayAlignment,
656 size: layoutSize, rectangle: textRect);
657 // if we still overflow even after eliding the text, enable clipping
658 if (!hasClipping() && (textRect.width() < textLayoutSize.width()
659 || textRect.height() < textLayoutSize.height())) {
660 painter->save();
661 painter->setClipRect(layoutRect);
662 d->textLayout.draw(p: painter, pos: layoutRect.topLeft(), selections: QList<QTextLayout::FormatRange>(),
663 clip: layoutRect);
664 painter->restore();
665 } else {
666 d->textLayout.draw(p: painter, pos: layoutRect.topLeft(), selections: QList<QTextLayout::FormatRange>(),
667 clip: layoutRect);
668 }
669}
670
671/*!
672 Renders the decoration \a pixmap within the rectangle specified by
673 \a rect using the given \a painter and style \a option.
674*/
675void QItemDelegate::drawDecoration(QPainter *painter, const QStyleOptionViewItem &option,
676 const QRect &rect, const QPixmap &pixmap) const
677{
678 Q_D(const QItemDelegate);
679 // if we have an icon, we ignore the pixmap
680 if (!d->tmp.icon.isNull()) {
681 d->tmp.icon.paint(painter, rect, alignment: option.decorationAlignment,
682 mode: d->tmp.mode, state: d->tmp.state);
683 return;
684 }
685
686 if (pixmap.isNull() || !rect.isValid())
687 return;
688 QPoint p = QStyle::alignedRect(direction: option.direction, alignment: option.decorationAlignment,
689 size: pixmap.size(), rectangle: rect).topLeft();
690 if (option.state & QStyle::State_Selected) {
691 const QPixmap pm = selectedPixmap(pixmap, palette: option.palette, enabled: option.state & QStyle::State_Enabled);
692 painter->drawPixmap(p, pm);
693 } else {
694 painter->drawPixmap(p, pm: pixmap);
695 }
696}
697
698/*!
699 Renders the region within the rectangle specified by \a rect, indicating
700 that it has the focus, using the given \a painter and style \a option.
701*/
702
703void QItemDelegate::drawFocus(QPainter *painter,
704 const QStyleOptionViewItem &option,
705 const QRect &rect) const
706{
707 Q_D(const QItemDelegate);
708 if ((option.state & QStyle::State_HasFocus) == 0 || !rect.isValid())
709 return;
710 QStyleOptionFocusRect o;
711 o.QStyleOption::operator=(other: option);
712 o.rect = rect;
713 o.state |= QStyle::State_KeyboardFocusChange;
714 o.state |= QStyle::State_Item;
715 QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
716 ? QPalette::Normal : QPalette::Disabled;
717 o.backgroundColor = option.palette.color(cg, cr: (option.state & QStyle::State_Selected)
718 ? QPalette::Highlight : QPalette::Window);
719 const QWidget *widget = d->widget(option);
720 QStyle *style = widget ? widget->style() : QApplication::style();
721 style->drawPrimitive(pe: QStyle::PE_FrameFocusRect, opt: &o, p: painter, w: widget);
722}
723
724/*!
725 Renders a check indicator within the rectangle specified by \a
726 rect, using the given \a painter and style \a option, using the
727 given \a state.
728*/
729
730void QItemDelegate::drawCheck(QPainter *painter,
731 const QStyleOptionViewItem &option,
732 const QRect &rect, Qt::CheckState state) const
733{
734 Q_D(const QItemDelegate);
735 if (!rect.isValid())
736 return;
737
738 QStyleOptionViewItem opt(option);
739 opt.rect = rect;
740 opt.state = opt.state & ~QStyle::State_HasFocus;
741
742 switch (state) {
743 case Qt::Unchecked:
744 opt.state |= QStyle::State_Off;
745 break;
746 case Qt::PartiallyChecked:
747 opt.state |= QStyle::State_NoChange;
748 break;
749 case Qt::Checked:
750 opt.state |= QStyle::State_On;
751 break;
752 }
753
754 const QWidget *widget = d->widget(option);
755 QStyle *style = widget ? widget->style() : QApplication::style();
756 style->drawPrimitive(pe: QStyle::PE_IndicatorItemViewItemCheck, opt: &opt, p: painter, w: widget);
757}
758
759/*!
760 \since 4.2
761
762 Renders the item background for the given \a index,
763 using the given \a painter and style \a option.
764*/
765
766void QItemDelegate::drawBackground(QPainter *painter,
767 const QStyleOptionViewItem &option,
768 const QModelIndex &index) const
769{
770 if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) {
771 QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
772 ? QPalette::Normal : QPalette::Disabled;
773 if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
774 cg = QPalette::Inactive;
775
776 painter->fillRect(option.rect, option.palette.brush(cg, cr: QPalette::Highlight));
777 } else {
778 QVariant value = index.data(arole: Qt::BackgroundRole);
779 if (value.canConvert<QBrush>()) {
780 QPointF oldBO = painter->brushOrigin();
781 painter->setBrushOrigin(option.rect.topLeft());
782 painter->fillRect(option.rect, qvariant_cast<QBrush>(v: value));
783 painter->setBrushOrigin(oldBO);
784 }
785 }
786}
787
788
789/*!
790 \internal
791
792 Code duplicated in QCommonStylePrivate::viewItemLayout
793*/
794
795void QItemDelegate::doLayout(const QStyleOptionViewItem &option,
796 QRect *checkRect, QRect *pixmapRect, QRect *textRect,
797 bool hint) const
798{
799 Q_ASSERT(checkRect && pixmapRect && textRect);
800 Q_D(const QItemDelegate);
801 const QWidget *widget = d->widget(option);
802 QStyle *style = widget ? widget->style() : QApplication::style();
803 const bool hasCheck = checkRect->isValid();
804 const bool hasPixmap = pixmapRect->isValid();
805 const bool hasText = textRect->isValid();
806 const bool hasMargin = (hasText | hasPixmap | hasCheck);
807 const int frameHMargin = hasMargin ?
808 style->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: nullptr, widget) + 1 : 0;
809 const int textMargin = hasText ? frameHMargin : 0;
810 const int pixmapMargin = hasPixmap ? frameHMargin : 0;
811 const int checkMargin = hasCheck ? frameHMargin : 0;
812 const int x = option.rect.left();
813 const int y = option.rect.top();
814 int w, h;
815
816 textRect->adjust(dx1: -textMargin, dy1: 0, dx2: textMargin, dy2: 0); // add width padding
817 if (textRect->height() == 0 && (!hasPixmap || !hint)) {
818 //if there is no text, we still want to have a decent height for the item sizeHint and the editor size
819 textRect->setHeight(option.fontMetrics.height());
820 }
821
822 QSize pm(0, 0);
823 if (hasPixmap) {
824 pm = pixmapRect->size();
825 pm.rwidth() += 2 * pixmapMargin;
826 }
827 if (hint) {
828 h = qMax(a: checkRect->height(), b: qMax(a: textRect->height(), b: pm.height()));
829 if (option.decorationPosition == QStyleOptionViewItem::Left
830 || option.decorationPosition == QStyleOptionViewItem::Right) {
831 w = textRect->width() + pm.width();
832 } else {
833 w = qMax(a: textRect->width(), b: pm.width());
834 }
835 } else {
836 w = option.rect.width();
837 h = option.rect.height();
838 }
839
840 int cw = 0;
841 QRect check;
842 if (hasCheck) {
843 cw = checkRect->width() + 2 * checkMargin;
844 if (hint) w += cw;
845 if (option.direction == Qt::RightToLeft) {
846 check.setRect(ax: x + w - cw, ay: y, aw: cw, ah: h);
847 } else {
848 check.setRect(ax: x, ay: y, aw: cw, ah: h);
849 }
850 }
851
852 // at this point w should be the *total* width
853
854 QRect display;
855 QRect decoration;
856 switch (option.decorationPosition) {
857 case QStyleOptionViewItem::Top: {
858 if (hasPixmap)
859 pm.setHeight(pm.height() + pixmapMargin); // add space
860 h = hint ? textRect->height() : h - pm.height();
861
862 if (option.direction == Qt::RightToLeft) {
863 decoration.setRect(ax: x, ay: y, aw: w - cw, ah: pm.height());
864 display.setRect(ax: x, ay: y + pm.height(), aw: w - cw, ah: h);
865 } else {
866 decoration.setRect(ax: x + cw, ay: y, aw: w - cw, ah: pm.height());
867 display.setRect(ax: x + cw, ay: y + pm.height(), aw: w - cw, ah: h);
868 }
869 break; }
870 case QStyleOptionViewItem::Bottom: {
871 if (hasText)
872 textRect->setHeight(textRect->height() + textMargin); // add space
873 h = hint ? textRect->height() + pm.height() : h;
874
875 if (option.direction == Qt::RightToLeft) {
876 display.setRect(ax: x, ay: y, aw: w - cw, ah: textRect->height());
877 decoration.setRect(ax: x, ay: y + textRect->height(), aw: w - cw, ah: h - textRect->height());
878 } else {
879 display.setRect(ax: x + cw, ay: y, aw: w - cw, ah: textRect->height());
880 decoration.setRect(ax: x + cw, ay: y + textRect->height(), aw: w - cw, ah: h - textRect->height());
881 }
882 break; }
883 case QStyleOptionViewItem::Left: {
884 if (option.direction == Qt::LeftToRight) {
885 decoration.setRect(ax: x + cw, ay: y, aw: pm.width(), ah: h);
886 display.setRect(ax: decoration.right() + 1, ay: y, aw: w - pm.width() - cw, ah: h);
887 } else {
888 display.setRect(ax: x, ay: y, aw: w - pm.width() - cw, ah: h);
889 decoration.setRect(ax: display.right() + 1, ay: y, aw: pm.width(), ah: h);
890 }
891 break; }
892 case QStyleOptionViewItem::Right: {
893 if (option.direction == Qt::LeftToRight) {
894 display.setRect(ax: x + cw, ay: y, aw: w - pm.width() - cw, ah: h);
895 decoration.setRect(ax: display.right() + 1, ay: y, aw: pm.width(), ah: h);
896 } else {
897 decoration.setRect(ax: x, ay: y, aw: pm.width(), ah: h);
898 display.setRect(ax: decoration.right() + 1, ay: y, aw: w - pm.width() - cw, ah: h);
899 }
900 break; }
901 default:
902 qWarning(msg: "doLayout: decoration position is invalid");
903 decoration = *pixmapRect;
904 break;
905 }
906
907 if (!hint) { // we only need to do the internal layout if we are going to paint
908 *checkRect = QStyle::alignedRect(direction: option.direction, alignment: Qt::AlignCenter,
909 size: checkRect->size(), rectangle: check);
910 *pixmapRect = QStyle::alignedRect(direction: option.direction, alignment: option.decorationAlignment,
911 size: pixmapRect->size(), rectangle: decoration);
912 // the text takes up all available space, unless the decoration is not shown as selected
913 if (option.showDecorationSelected)
914 *textRect = display;
915 else
916 *textRect = QStyle::alignedRect(direction: option.direction, alignment: option.displayAlignment,
917 size: textRect->size().boundedTo(otherSize: display.size()), rectangle: display);
918 } else {
919 *checkRect = check;
920 *pixmapRect = decoration;
921 *textRect = display;
922 }
923}
924
925/*!
926 \internal
927
928 Returns the pixmap used to decorate the root of the item view.
929 The style \a option controls the appearance of the root; the \a variant
930 refers to the data associated with an item.
931*/
932
933QPixmap QItemDelegate::decoration(const QStyleOptionViewItem &option, const QVariant &variant) const
934{
935 Q_D(const QItemDelegate);
936 switch (variant.userType()) {
937 case QMetaType::QIcon: {
938 QIcon::Mode mode = d->iconMode(state: option.state);
939 QIcon::State state = d->iconState(state: option.state);
940 return qvariant_cast<QIcon>(v: variant).pixmap(size: option.decorationSize, mode, state); }
941 case QMetaType::QColor: {
942 static QPixmap pixmap(option.decorationSize);
943 pixmap.fill(fillColor: qvariant_cast<QColor>(v: variant));
944 return pixmap; }
945 default:
946 break;
947 }
948
949 return qvariant_cast<QPixmap>(v: variant);
950}
951
952// hacky but faster version of "QString::asprintf("%d-%d", i, enabled)"
953static QString qPixmapSerial(quint64 i, bool enabled)
954{
955 ushort arr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', ushort('0' + enabled) };
956 ushort *ptr = &arr[16];
957
958 while (i > 0) {
959 // hey - it's our internal representation, so use the ascii character after '9'
960 // instead of 'a' for hex
961 *(--ptr) = '0' + i % 16;
962 i >>= 4;
963 }
964
965 return QString((const QChar *)ptr, int(&arr[sizeof(arr) / sizeof(ushort)] - ptr));
966}
967
968
969/*!
970 \internal
971 Returns the selected version of the given \a pixmap using the given \a palette.
972 The \a enabled argument decides whether the normal or disabled highlight color of
973 the palette is used.
974*/
975QPixmap QItemDelegate::selectedPixmap(const QPixmap &pixmap, const QPalette &palette, bool enabled)
976{
977 const QString key = qPixmapSerial(i: pixmap.cacheKey(), enabled);
978 QPixmap pm;
979 if (!QPixmapCache::find(key, pixmap: &pm)) {
980 QImage img = pixmap.toImage().convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
981
982 QColor color = palette.color(cg: enabled ? QPalette::Normal : QPalette::Disabled,
983 cr: QPalette::Highlight);
984 color.setAlphaF(0.3f);
985
986 QPainter painter(&img);
987 painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
988 painter.fillRect(x: 0, y: 0, w: img.width(), h: img.height(), b: color);
989 painter.end();
990
991 pm = QPixmap(QPixmap::fromImage(image: img));
992 const int n = (img.sizeInBytes() >> 10) + 1;
993 if (QPixmapCache::cacheLimit() < n)
994 QPixmapCache::setCacheLimit(n);
995
996 QPixmapCache::insert(key, pixmap: pm);
997 }
998 return pm;
999}
1000
1001/*!
1002 \internal
1003 Only used (and usable) for Qt::DecorationRole and Qt::CheckStateRole
1004*/
1005QRect QItemDelegate::rect(const QStyleOptionViewItem &option,
1006 const QModelIndex &index, int role) const
1007{
1008 Q_D(const QItemDelegate);
1009 QVariant value = index.data(arole: role);
1010 if (role == Qt::CheckStateRole)
1011 return doCheck(option, bounding: option.rect, variant: value);
1012 if (value.isValid() && !value.isNull()) {
1013 switch (value.userType()) {
1014 case QMetaType::UnknownType:
1015 break;
1016 case QMetaType::QPixmap: {
1017 const QPixmap &pixmap = qvariant_cast<QPixmap>(v: value);
1018 return QRect(QPoint(0, 0), pixmap.deviceIndependentSize().toSize()); }
1019 case QMetaType::QImage: {
1020 const QImage &image = qvariant_cast<QImage>(v: value);
1021 return QRect(QPoint(0, 0), image.deviceIndependentSize().toSize()); }
1022 case QMetaType::QIcon: {
1023 QIcon::Mode mode = d->iconMode(state: option.state);
1024 QIcon::State state = d->iconState(state: option.state);
1025 QIcon icon = qvariant_cast<QIcon>(v: value);
1026 QSize size = icon.actualSize(size: option.decorationSize, mode, state);
1027 return QRect(QPoint(0, 0), size); }
1028 case QMetaType::QColor:
1029 return QRect(QPoint(0, 0), option.decorationSize);
1030 case QMetaType::QString:
1031 default: {
1032 const QString text = d->valueToText(value, option);
1033 value = index.data(arole: Qt::FontRole);
1034 QFont fnt = qvariant_cast<QFont>(v: value).resolve(option.font);
1035 return textRectangle(painter: nullptr,
1036 rect: d->textLayoutBounds(option, decorationRect: QRect(), checkRect: QRect()),
1037 font: fnt, text); }
1038 }
1039 }
1040 return QRect();
1041}
1042
1043/*!
1044 \internal
1045*/
1046QRect QItemDelegate::doCheck(const QStyleOptionViewItem &option,
1047 const QRect &bounding, const QVariant &value) const
1048{
1049 if (value.isValid()) {
1050 Q_D(const QItemDelegate);
1051 QStyleOptionButton opt;
1052 opt.QStyleOption::operator=(other: option);
1053 opt.rect = bounding;
1054 const QWidget *widget = d->widget(option); // cast
1055 QStyle *style = widget ? widget->style() : QApplication::style();
1056 return style->subElementRect(subElement: QStyle::SE_ItemViewItemCheckIndicator, option: &opt, widget);
1057 }
1058 return QRect();
1059}
1060
1061/*!
1062 \internal
1063*/
1064QRect QItemDelegate::textRectangle(QPainter * /*painter*/, const QRect &rect,
1065 const QFont &font, const QString &text) const
1066{
1067 Q_D(const QItemDelegate);
1068 d->textOption.setWrapMode(QTextOption::WordWrap);
1069 d->textLayout.setTextOption(d->textOption);
1070 d->textLayout.setFont(font);
1071 d->textLayout.setText(QItemDelegatePrivate::replaceNewLine(text));
1072 QSizeF fpSize = d->doTextLayout(lineWidth: rect.width());
1073 const QSize size = QSize(qCeil(v: fpSize.width()), qCeil(v: fpSize.height()));
1074 // ###: textRectangle should take style option as argument
1075 const int textMargin = QApplication::style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: nullptr) + 1;
1076 return QRect(0, 0, size.width() + 2 * textMargin, size.height());
1077}
1078
1079/*!
1080 \fn bool QItemDelegate::eventFilter(QObject *editor, QEvent *event)
1081
1082 Returns \c true if the given \a editor is a valid QWidget and the
1083 given \a event is handled; otherwise returns \c false. The following
1084 key press events are handled by default:
1085
1086 \list
1087 \li \uicontrol Tab
1088 \li \uicontrol Backtab
1089 \li \uicontrol Enter
1090 \li \uicontrol Return
1091 \li \uicontrol Esc
1092 \endlist
1093
1094 In the case of \uicontrol Tab, \uicontrol Backtab, \uicontrol Enter and \uicontrol Return
1095 key press events, the \a editor's data is committed to the model
1096 and the editor is closed. If the \a event is a \uicontrol Tab key press
1097 the view will open an editor on the next item in the
1098 view. Likewise, if the \a event is a \uicontrol Backtab key press the
1099 view will open an editor on the \e previous item in the view.
1100
1101 If the event is a \uicontrol Esc key press event, the \a editor is
1102 closed \e without committing its data.
1103
1104 \sa commitData(), closeEditor()
1105*/
1106
1107bool QItemDelegate::eventFilter(QObject *object, QEvent *event)
1108{
1109 Q_D(QItemDelegate);
1110 return d->editorEventFilter(object, event);
1111}
1112
1113/*!
1114 \reimp
1115*/
1116
1117bool QItemDelegate::editorEvent(QEvent *event,
1118 QAbstractItemModel *model,
1119 const QStyleOptionViewItem &option,
1120 const QModelIndex &index)
1121{
1122 Q_ASSERT(event);
1123 Q_ASSERT(model);
1124
1125 // make sure that the item is checkable
1126 Qt::ItemFlags flags = model->flags(index);
1127 if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled)
1128 || !(flags & Qt::ItemIsEnabled))
1129 return false;
1130
1131 // make sure that we have a check state
1132 QVariant value = index.data(arole: Qt::CheckStateRole);
1133 if (!value.isValid())
1134 return false;
1135
1136 // make sure that we have the right event type
1137 if ((event->type() == QEvent::MouseButtonRelease)
1138 || (event->type() == QEvent::MouseButtonDblClick)
1139 || (event->type() == QEvent::MouseButtonPress)) {
1140 QRect checkRect = doCheck(option, bounding: option.rect, value: Qt::Checked);
1141 QRect emptyRect;
1142 doLayout(option, checkRect: &checkRect, pixmapRect: &emptyRect, textRect: &emptyRect, hint: false);
1143 QMouseEvent *me = static_cast<QMouseEvent*>(event);
1144 if (me->button() != Qt::LeftButton || !checkRect.contains(p: me->position().toPoint()))
1145 return false;
1146
1147 // eat the double click events inside the check rect
1148 if ((event->type() == QEvent::MouseButtonPress)
1149 || (event->type() == QEvent::MouseButtonDblClick))
1150 return true;
1151
1152 } else if (event->type() == QEvent::KeyPress) {
1153 if (static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space
1154 && static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select)
1155 return false;
1156 } else {
1157 return false;
1158 }
1159
1160 Qt::CheckState state = QtPrivate::legacyEnumValueFromModelData<Qt::CheckState>(data: value);
1161 if (flags & Qt::ItemIsUserTristate)
1162 state = ((Qt::CheckState)((state + 1) % 3));
1163 else
1164 state = (state == Qt::Checked) ? Qt::Unchecked : Qt::Checked;
1165 return model->setData(index, value: state, role: Qt::CheckStateRole);
1166}
1167
1168/*!
1169 \internal
1170*/
1171
1172QStyleOptionViewItem QItemDelegate::setOptions(const QModelIndex &index,
1173 const QStyleOptionViewItem &option) const
1174{
1175 QStyleOptionViewItem opt = option;
1176
1177 // set font
1178 QVariant value = index.data(arole: Qt::FontRole);
1179 if (value.isValid()){
1180 opt.font = qvariant_cast<QFont>(v: value).resolve(opt.font);
1181 opt.fontMetrics = QFontMetrics(opt.font);
1182 }
1183
1184 // set text alignment
1185 value = index.data(arole: Qt::TextAlignmentRole);
1186 if (value.isValid())
1187 opt.displayAlignment = QtPrivate::legacyFlagValueFromModelData<Qt::Alignment>(data: value);
1188
1189 // set foreground brush
1190 value = index.data(arole: Qt::ForegroundRole);
1191 if (value.canConvert<QBrush>())
1192 opt.palette.setBrush(acr: QPalette::Text, abrush: qvariant_cast<QBrush>(v: value));
1193
1194 // disable style animations for checkboxes etc. within itemviews (QTBUG-30146)
1195 opt.styleObject = nullptr;
1196
1197 return opt;
1198}
1199
1200QT_END_NAMESPACE
1201
1202#include "moc_qitemdelegate.cpp"
1203

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtbase/src/widgets/itemviews/qitemdelegate.cpp