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

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