KIO

kfileitemdelegate.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2009 Shaun Reich <shaun.reich@kdemail.net>
4 SPDX-FileCopyrightText: 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "kfileitemdelegate.h"
10#include "imagefilter_p.h"
11
12#include <QApplication>
13#include <QCache>
14#include <QImage>
15#include <QListView>
16#include <QLocale>
17#include <QMimeDatabase>
18#include <QModelIndex>
19#include <QPaintEngine>
20#include <QPainter>
21#include <QPainterPath>
22#include <QStyle>
23#include <QTextEdit>
24#include <QTextLayout>
25#include <qmath.h>
26
27#include <KIconEffect>
28#include <KIconLoader>
29#include <KLocalizedString>
30#include <KStatefulBrush>
31#include <KStringHandler>
32#include <kdirmodel.h>
33#include <kfileitem.h>
34
35#include "delegateanimationhandler_p.h"
36
37struct Margin {
38 int left, right, top, bottom;
39};
40
41class Q_DECL_HIDDEN KFileItemDelegate::Private
42{
43public:
44 enum MarginType {
45 ItemMargin = 0,
46 TextMargin,
47 IconMargin,
48 NMargins
49 };
50
51 explicit Private(KFileItemDelegate *parent);
52 ~Private()
53 {
54 }
55
56 QSize decorationSizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
57 QSize displaySizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
58 QString replaceNewlines(const QString &string) const;
59 inline KFileItem fileItem(const QModelIndex &index) const;
60 QString elidedText(QTextLayout &layout, const QStyleOptionViewItem &option, const QSize &maxSize) const;
61 QSize layoutText(QTextLayout &layout, const QStyleOptionViewItem &option, const QString &text, const QSize &constraints) const;
62 QSize layoutText(QTextLayout &layout, const QString &text, int maxWidth) const;
63 inline void setLayoutOptions(QTextLayout &layout, const QStyleOptionViewItem &options) const;
64 inline bool verticalLayout(const QStyleOptionViewItem &option) const;
65 inline QBrush brush(const QVariant &value, const QStyleOptionViewItem &option) const;
66 QBrush foregroundBrush(const QStyleOptionViewItem &option, const QModelIndex &index) const;
67 inline void setActiveMargins(Qt::Orientation layout);
68 void setVerticalMargin(MarginType type, int left, int right, int top, int bottom);
69 void setHorizontalMargin(MarginType type, int left, int right, int top, int bottom);
70 inline void setVerticalMargin(MarginType type, int hor, int ver);
71 inline void setHorizontalMargin(MarginType type, int hor, int ver);
72 inline QRect addMargin(const QRect &rect, MarginType type) const;
73 inline QRect subtractMargin(const QRect &rect, MarginType type) const;
74 inline QSize addMargin(const QSize &size, MarginType type) const;
75 inline QSize subtractMargin(const QSize &size, MarginType type) const;
76 QString itemSize(const QModelIndex &index, const KFileItem &item) const;
77 QString information(const QStyleOptionViewItem &option, const QModelIndex &index, const KFileItem &item) const;
78 bool isListView(const QStyleOptionViewItem &option) const;
79 QString display(const QModelIndex &index) const;
80 QIcon decoration(const QStyleOptionViewItem &option, const QModelIndex &index) const;
81 QPoint iconPosition(const QStyleOptionViewItem &option) const;
82 QRect labelRectangle(const QStyleOptionViewItem &option, const QModelIndex &index) const;
83 void layoutTextItems(const QStyleOptionViewItem &option,
84 const QModelIndex &index,
85 QTextLayout *labelLayout,
86 QTextLayout *infoLayout,
87 QRect *textBoundingRect) const;
88 void drawTextItems(QPainter *painter,
89 const QTextLayout &labelLayout,
90 const QColor &labelColor,
91 const QTextLayout &infoLayout,
92 const QColor &infoColor,
93 const QRect &textBoundingRect) const;
94 KIO::AnimationState *animationState(const QStyleOptionViewItem &option, const QModelIndex &index, const QAbstractItemView *view) const;
95 void restartAnimation(KIO::AnimationState *state);
96 QPixmap applyHoverEffect(const QPixmap &icon) const;
97 QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount) const;
98 void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const;
99 void drawFocusRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const;
100
101 void gotNewIcon(const QModelIndex &index);
102
103 void paintJobTransfers(QPainter *painter, const qreal &jobAnimationAngle, const QPoint &iconPos, const QStyleOptionViewItem &opt);
104 int scaledEmblemSize(int iconSize) const;
105
106public:
107 KFileItemDelegate::InformationList informationList;
108 QColor shadowColor;
109 QPointF shadowOffset;
110 qreal shadowBlur;
111 QSize maximumSize;
112 bool showToolTipWhenElided;
113 QTextOption::WrapMode wrapMode;
114 bool jobTransfersVisible;
115 QIcon downArrowIcon;
116 QRect emblemRect;
117
118private:
119 KIO::DelegateAnimationHandler *animationHandler;
120 Margin verticalMargin[NMargins];
121 Margin horizontalMargin[NMargins];
122 Margin *activeMargins;
123};
124
125KFileItemDelegate::Private::Private(KFileItemDelegate *parent)
126 : shadowColor(Qt::transparent)
127 , shadowOffset(1, 1)
128 , shadowBlur(2)
129 , maximumSize(0, 0)
130 , showToolTipWhenElided(true)
131 , wrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere)
132 , jobTransfersVisible(false)
133 , emblemRect(QRect())
134 , animationHandler(new KIO::DelegateAnimationHandler(parent))
135 , activeMargins(nullptr)
136{
137}
138
139void KFileItemDelegate::Private::setActiveMargins(Qt::Orientation layout)
140{
141 activeMargins = (layout == Qt::Horizontal ? horizontalMargin : verticalMargin);
142}
143
144void KFileItemDelegate::Private::setVerticalMargin(MarginType type, int left, int top, int right, int bottom)
145{
146 verticalMargin[type].left = left;
147 verticalMargin[type].right = right;
148 verticalMargin[type].top = top;
149 verticalMargin[type].bottom = bottom;
150}
151
152void KFileItemDelegate::Private::setHorizontalMargin(MarginType type, int left, int top, int right, int bottom)
153{
154 horizontalMargin[type].left = left;
155 horizontalMargin[type].right = right;
156 horizontalMargin[type].top = top;
157 horizontalMargin[type].bottom = bottom;
158}
159
160void KFileItemDelegate::Private::setVerticalMargin(MarginType type, int horizontal, int vertical)
161{
162 setVerticalMargin(type, horizontal, vertical, horizontal, vertical);
163}
164
165void KFileItemDelegate::Private::setHorizontalMargin(MarginType type, int horizontal, int vertical)
166{
167 setHorizontalMargin(type, horizontal, vertical, horizontal, vertical);
168}
169
170QRect KFileItemDelegate::Private::addMargin(const QRect &rect, MarginType type) const
171{
172 Q_ASSERT(activeMargins != nullptr);
173 const Margin &m = activeMargins[type];
174 return rect.adjusted(-m.left, -m.top, m.right, m.bottom);
175}
176
177QRect KFileItemDelegate::Private::subtractMargin(const QRect &rect, MarginType type) const
178{
179 Q_ASSERT(activeMargins != nullptr);
180 const Margin &m = activeMargins[type];
181 return rect.adjusted(m.left, m.top, -m.right, -m.bottom);
182}
183
184QSize KFileItemDelegate::Private::addMargin(const QSize &size, MarginType type) const
185{
186 Q_ASSERT(activeMargins != nullptr);
187 const Margin &m = activeMargins[type];
188 return QSize(size.width() + m.left + m.right, size.height() + m.top + m.bottom);
189}
190
191QSize KFileItemDelegate::Private::subtractMargin(const QSize &size, MarginType type) const
192{
193 Q_ASSERT(activeMargins != nullptr);
194 const Margin &m = activeMargins[type];
195 return QSize(size.width() - m.left - m.right, size.height() - m.top - m.bottom);
196}
197
198// Returns the size of a file, or the number of items in a directory, as a QString
199QString KFileItemDelegate::Private::itemSize(const QModelIndex &index, const KFileItem &item) const
200{
201 // Return a formatted string containing the file size, if the item is a file
202 if (item.isFile()) {
203 return KIO::convertSize(item.size());
204 }
205
206 // Return the number of items in the directory
207 const QVariant value = index.data(KDirModel::ChildCountRole);
208 const int count = value.typeId() == QMetaType::Int ? value.toInt() : KDirModel::ChildCountUnknown;
209
210 if (count == KDirModel::ChildCountUnknown) {
211 // was: i18nc("Items in a folder", "? items");
212 // but this just looks useless in a remote directory listing,
213 // better not show anything.
214 return QString();
215 }
216
217 return i18ncp("Items in a folder", "1 item", "%1 items", count);
218}
219
220// Returns the additional information string, if one should be shown, or an empty string otherwise
221QString KFileItemDelegate::Private::information(const QStyleOptionViewItem &option, const QModelIndex &index, const KFileItem &item) const
222{
223 QString string;
224
225 if (informationList.isEmpty() || item.isNull() || !isListView(option)) {
226 return string;
227 }
228
229 for (KFileItemDelegate::Information info : informationList) {
231 continue;
232 }
233
234 if (!string.isEmpty()) {
235 string += QChar::LineSeparator;
236 }
237
238 switch (info) {
240 string += itemSize(index, item);
241 break;
242
244 string += item.permissionsString();
245 break;
246
248 string += QLatin1Char('0') + QString::number(item.permissions(), 8);
249 break;
250
252 string += item.user();
253 break;
254
256 string += item.user() + QLatin1Char(':') + item.group();
257 break;
258
260 string += item.timeString(KFileItem::CreationTime);
261 break;
262
264 string += item.timeString(KFileItem::ModificationTime);
265 break;
266
268 string += item.timeString(KFileItem::AccessTime);
269 break;
270
272 string += item.isMimeTypeKnown() ? item.mimetype() : i18nc("@info mimetype", "Unknown");
273 break;
274
276 string += item.isMimeTypeKnown() ? item.mimeComment() : i18nc("@info mimetype", "Unknown");
277 break;
278
280 string += item.linkDest();
281 break;
282
284 if (!item.localPath().isEmpty()) {
285 string += item.localPath();
286 } else {
287 string += item.url().toDisplayString();
288 }
289 break;
290
292 string += item.comment();
293 break;
294
295 default:
296 break;
297 } // switch (info)
298 } // for (info, list)
299
300 return string;
301}
302
303// Returns the KFileItem for the index
304KFileItem KFileItemDelegate::Private::fileItem(const QModelIndex &index) const
305{
306 const QVariant value = index.data(KDirModel::FileItemRole);
307 return qvariant_cast<KFileItem>(value);
308}
309
310// Replaces any newline characters in the provided string, with QChar::LineSeparator
311QString KFileItemDelegate::Private::replaceNewlines(const QString &text) const
312{
313 QString string = text;
314 string.replace(QLatin1Char('\n'), QChar::LineSeparator);
315 return string;
316}
317
318// Lays the text out in a rectangle no larger than constraints, eliding it as necessary
319QSize KFileItemDelegate::Private::layoutText(QTextLayout &layout, const QStyleOptionViewItem &option, const QString &text, const QSize &constraints) const
320{
321 const QSize size = layoutText(layout, text, constraints.width());
322
323 if (size.width() > constraints.width() || size.height() > constraints.height()) {
324 const QString elided = elidedText(layout, option, constraints);
325 return layoutText(layout, elided, constraints.width());
326 }
327
328 return size;
329}
330
331// Lays the text out in a rectangle no wider than maxWidth
332QSize KFileItemDelegate::Private::layoutText(QTextLayout &layout, const QString &text, int maxWidth) const
333{
334 QFontMetrics metrics(layout.font());
335 int leading = metrics.leading();
336 int height = 0;
337 qreal widthUsed = 0;
338 QTextLine line;
339
340 layout.setText(text);
341
342 layout.beginLayout();
343 while ((line = layout.createLine()).isValid()) {
344 line.setLineWidth(maxWidth);
345 height += leading;
346 line.setPosition(QPoint(0, height));
347 height += int(line.height());
348 widthUsed = qMax(widthUsed, line.naturalTextWidth());
349 }
350 layout.endLayout();
351
352 return QSize(qCeil(widthUsed), height);
353}
354
355// Elides the text in the layout, by iterating over each line in the layout, eliding
356// or word breaking the line if it's wider than the max width, and finally adding an
357// ellipses at the end of the last line, if there are more lines than will fit within
358// the vertical size constraints.
359QString KFileItemDelegate::Private::elidedText(QTextLayout &layout, const QStyleOptionViewItem &option, const QSize &size) const
360{
361 const QString text = layout.text();
362 int maxWidth = size.width();
363 int maxHeight = size.height();
364 qreal height = 0;
365 bool wrapText = (option.features & QStyleOptionViewItem::WrapText);
366
367 // If the string contains a single line of text that shouldn't be word wrapped
368 if (!wrapText && text.indexOf(QChar::LineSeparator) == -1) {
369 return option.fontMetrics.elidedText(text, option.textElideMode, maxWidth);
370 }
371
372 // Elide each line that has already been laid out in the layout.
373 QString elided;
374 elided.reserve(text.length());
375
376 for (int i = 0; i < layout.lineCount(); i++) {
377 QTextLine line = layout.lineAt(i);
378 const int start = line.textStart();
379 const int length = line.textLength();
380
381 height += option.fontMetrics.leading();
382 if (height + line.height() + option.fontMetrics.lineSpacing() > maxHeight) {
383 // Unfortunately, if the line ends because of a line separator, elidedText() will be too
384 // clever and keep adding lines until it finds one that's too wide.
385 if (line.naturalTextWidth() < maxWidth && text[start + length - 1] == QChar::LineSeparator) {
386 elided += QStringView(text).mid(start, length - 1);
387 } else {
388 elided += option.fontMetrics.elidedText(text.mid(start), option.textElideMode, maxWidth);
389 }
390 break;
391 } else if (line.naturalTextWidth() > maxWidth) {
392 elided += option.fontMetrics.elidedText(text.mid(start, length), option.textElideMode, maxWidth);
393 if (!elided.endsWith(QChar::LineSeparator)) {
394 elided += QChar::LineSeparator;
395 }
396 } else {
397 elided += QStringView(text).mid(start, length);
398 }
399
400 height += line.height();
401 }
402
403 return elided;
404}
405
406void KFileItemDelegate::Private::setLayoutOptions(QTextLayout &layout, const QStyleOptionViewItem &option) const
407{
408 QTextOption textoption;
409 textoption.setTextDirection(option.direction);
410 textoption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment));
412
413 layout.setFont(option.font);
414 layout.setTextOption(textoption);
415}
416
417QSize KFileItemDelegate::Private::displaySizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
418{
419 QString label = option.text;
420 int maxWidth = 0;
421 if (maximumSize.isEmpty()) {
422 maxWidth = verticalLayout(option) && (option.features & QStyleOptionViewItem::WrapText) ? option.decorationSize.width() + 10 : 32757;
423 } else {
424 const Margin &itemMargin = activeMargins[ItemMargin];
425 const Margin &textMargin = activeMargins[TextMargin];
426 maxWidth = maximumSize.width() - (itemMargin.left + itemMargin.right) - (textMargin.left + textMargin.right);
427 }
428
429 KFileItem item = fileItem(index);
430
431 // To compute the nominal size for the label + info, we'll just append
432 // the information string to the label
433 const QString info = information(option, index, item);
434 if (!info.isEmpty()) {
435 label += QChar(QChar::LineSeparator) + info;
436 }
437
438 QTextLayout layout;
439 setLayoutOptions(layout, option);
440
441 QSize size = layoutText(layout, label, maxWidth);
442 if (!info.isEmpty()) {
443 // As soon as additional information is shown, it might be necessary that
444 // the label and/or the additional information must get elided. To prevent
445 // an expensive eliding in the scope of displaySizeHint, the maximum
446 // width is reserved instead.
447 size.setWidth(maxWidth);
448 }
449
450 return addMargin(size, TextMargin);
451}
452
453QSize KFileItemDelegate::Private::decorationSizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
454{
455 if (index.column() > 0) {
456 return QSize(0, 0);
457 }
458
459 QSize iconSize = option.icon.actualSize(option.decorationSize);
460 if (!verticalLayout(option)) {
461 iconSize.rwidth() = option.decorationSize.width();
462 } else if (iconSize.width() < option.decorationSize.width()) {
463 iconSize.rwidth() = qMin(iconSize.width() + 10, option.decorationSize.width());
464 }
465 if (iconSize.height() < option.decorationSize.height()) {
466 iconSize.rheight() = option.decorationSize.height();
467 }
468
469 return addMargin(iconSize, IconMargin);
470}
471
472bool KFileItemDelegate::Private::verticalLayout(const QStyleOptionViewItem &option) const
473{
474 return (option.decorationPosition == QStyleOptionViewItem::Top || option.decorationPosition == QStyleOptionViewItem::Bottom);
475}
476
477// Converts a QVariant of type Brush or Color to a QBrush
478QBrush KFileItemDelegate::Private::brush(const QVariant &value, const QStyleOptionViewItem &option) const
479{
480 if (value.userType() == qMetaTypeId<KStatefulBrush>()) {
481 return qvariant_cast<KStatefulBrush>(value).brush(option.palette);
482 }
483 switch (value.typeId()) {
485 return QBrush(qvariant_cast<QColor>(value));
486
488 return qvariant_cast<QBrush>(value);
489
490 default:
491 return QBrush(Qt::NoBrush);
492 }
493}
494
495QBrush KFileItemDelegate::Private::foregroundBrush(const QStyleOptionViewItem &option, const QModelIndex &index) const
496{
498 if (!(option.state & QStyle::State_Enabled)) {
500 } else if (!(option.state & QStyle::State_Active)) {
502 }
503
504 // Always use the highlight color for selected items
505 if (option.state & QStyle::State_Selected) {
506 return option.palette.brush(cg, QPalette::HighlightedText);
507 }
508
509 // If the model provides its own foreground color/brush for this item
510 const QVariant value = index.data(Qt::ForegroundRole);
511 if (value.isValid()) {
512 return brush(value, option);
513 }
514
515 return option.palette.brush(cg, QPalette::Text);
516}
517
518bool KFileItemDelegate::Private::isListView(const QStyleOptionViewItem &option) const
519{
520 if (qobject_cast<const QListView *>(option.widget) || verticalLayout(option)) {
521 return true;
522 }
523
524 return false;
525}
526
527QPixmap KFileItemDelegate::Private::applyHoverEffect(const QPixmap &icon) const
528{
529 QPixmap result = icon;
530 KIconEffect::toActive(result);
531 return result;
532}
533
534void KFileItemDelegate::Private::gotNewIcon(const QModelIndex &index)
535{
536 animationHandler->gotNewIcon(index);
537}
538
539void KFileItemDelegate::Private::restartAnimation(KIO::AnimationState *state)
540{
541 animationHandler->restartAnimation(state);
542}
543
544KIO::AnimationState *
545KFileItemDelegate::Private::animationState(const QStyleOptionViewItem &option, const QModelIndex &index, const QAbstractItemView *view) const
546{
547 if (!option.widget->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, option.widget)) {
548 return nullptr;
549 }
550
551 if (index.column() == KDirModel::Name) {
552 return animationHandler->animationState(option, index, view);
553 }
554
555 return nullptr;
556}
557
558QPixmap KFileItemDelegate::Private::transition(const QPixmap &from, const QPixmap &to, qreal amount) const
559{
560 int value = int(0xff * amount);
561
562 if (value == 0 || to.isNull()) {
563 return from;
564 }
565
566 if (value == 0xff || from.isNull()) {
567 return to;
568 }
569
570 QColor color;
571 color.setAlphaF(amount);
572
573// FIXME: Somehow this doesn't work on Mac OS..
574#if defined(Q_OS_MAC)
575 const bool usePixmap = false;
576#else
578#endif
579
580 // If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus
581 if (usePixmap) {
582 QPixmap under = from;
583 QPixmap over = to;
584
585 QPainter p;
586 p.begin(&over);
588 p.fillRect(over.rect(), color);
589 p.end();
590
591 p.begin(&under);
593 p.fillRect(under.rect(), color);
595 p.drawPixmap(0, 0, over);
596 p.end();
597
598 return under;
599 } else {
600 // Fall back to using QRasterPaintEngine to do the transition.
601 QImage under = from.toImage();
602 QImage over = to.toImage();
603
604 QPainter p;
605 p.begin(&over);
607 p.fillRect(over.rect(), color);
608 p.end();
609
610 p.begin(&under);
612 p.fillRect(under.rect(), color);
614 p.drawImage(0, 0, over);
615 p.end();
616
617 return QPixmap::fromImage(under);
618 }
619}
620
621void KFileItemDelegate::Private::layoutTextItems(const QStyleOptionViewItem &option,
622 const QModelIndex &index,
623 QTextLayout *labelLayout,
624 QTextLayout *infoLayout,
625 QRect *textBoundingRect) const
626{
627 KFileItem item = fileItem(index);
628 const QString info = information(option, index, item);
629 bool showInformation = false;
630
631 setLayoutOptions(*labelLayout, option);
632
633 const QRect textArea = labelRectangle(option, index);
634 QRect textRect = subtractMargin(textArea, Private::TextMargin);
635
636 // Sizes and constraints for the different text parts
637 QSize maxLabelSize = textRect.size();
638 QSize maxInfoSize = textRect.size();
639 QSize labelSize;
640 QSize infoSize;
641
642 // If we have additional info text, and there's space for at least two lines of text,
643 // adjust the max label size to make room for at least one line of the info text
644 if (!info.isEmpty() && textRect.height() >= option.fontMetrics.lineSpacing() * 2) {
645 infoLayout->setFont(labelLayout->font());
646 infoLayout->setTextOption(labelLayout->textOption());
647
648 maxLabelSize.rheight() -= option.fontMetrics.lineSpacing();
649 showInformation = true;
650 }
651
652 // Lay out the label text, and adjust the max info size based on the label size
653 labelSize = layoutText(*labelLayout, option, option.text, maxLabelSize);
654 maxInfoSize.rheight() -= labelSize.height();
655
656 // Lay out the info text
657 if (showInformation) {
658 infoSize = layoutText(*infoLayout, option, info, maxInfoSize);
659 } else {
660 infoSize = QSize(0, 0);
661 }
662
663 // Compute the bounding rect of the text
664 const QSize size(qMax(labelSize.width(), infoSize.width()), labelSize.height() + infoSize.height());
665 *textBoundingRect = QStyle::alignedRect(option.direction, option.displayAlignment, size, textRect);
666
667 // Compute the positions where we should draw the layouts
668 labelLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y()));
669 infoLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y() + labelSize.height()));
670}
671
672void KFileItemDelegate::Private::drawTextItems(QPainter *painter,
673 const QTextLayout &labelLayout,
674 const QColor &labelColor,
675 const QTextLayout &infoLayout,
676 const QColor &infoColor,
677 const QRect &boundingRect) const
678{
679 if (shadowColor.alpha() > 0) {
680 QPixmap pixmap(boundingRect.size());
681 pixmap.fill(Qt::transparent);
682
683 QPainter p(&pixmap);
684 p.translate(-boundingRect.topLeft());
685 p.setPen(labelColor);
686 labelLayout.draw(&p, QPoint());
687
688 if (!infoLayout.text().isEmpty()) {
689 p.setPen(infoColor);
690 infoLayout.draw(&p, QPoint());
691 }
692 p.end();
693
694 int padding = qCeil(shadowBlur);
695 int blurFactor = qRound(shadowBlur);
696
697 QImage image(boundingRect.size() + QSize(padding * 2, padding * 2), QImage::Format_ARGB32_Premultiplied);
698 image.fill(0);
699 p.begin(&image);
700 p.drawImage(padding, padding, pixmap.toImage());
701 p.end();
702
703 KIO::ImageFilter::shadowBlur(image, blurFactor, shadowColor);
704
705 painter->drawImage(boundingRect.topLeft() - QPoint(padding, padding) + shadowOffset.toPoint(), image);
706 painter->drawPixmap(boundingRect.topLeft(), pixmap);
707 return;
708 }
709
710 painter->save();
711 painter->setPen(labelColor);
712
713 labelLayout.draw(painter, QPoint());
714 if (!infoLayout.text().isEmpty()) {
715 // TODO - for apps not doing funny things with the color palette,
716 // KColorScheme::InactiveText would be a much more correct choice. We
717 // should provide an API to specify what color to use for information.
718 painter->setPen(infoColor);
719 infoLayout.draw(painter, QPoint());
720 }
721
722 painter->restore();
723}
724
725void KFileItemDelegate::Private::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const
726{
727 const KFileItem item = fileItem(index);
728 bool updateFontMetrics = false;
729
730 // Try to get the font from the model
731 QVariant value = index.data(Qt::FontRole);
732 if (value.isValid()) {
733 option->font = qvariant_cast<QFont>(value).resolve(option->font);
734 updateFontMetrics = true;
735 }
736
737 // Use an italic font for symlinks
738 if (!item.isNull() && item.isLink()) {
739 option->font.setItalic(true);
740 updateFontMetrics = true;
741 }
742
743 if (updateFontMetrics) {
744 option->fontMetrics = QFontMetrics(option->font);
745 }
746
747 // Try to get the alignment for the item from the model
748 value = index.data(Qt::TextAlignmentRole);
749 if (value.isValid()) {
750 option->displayAlignment = Qt::Alignment(value.toInt());
751 }
752
753 value = index.data(Qt::BackgroundRole);
754 if (value.isValid()) {
755 option->backgroundBrush = brush(value, *option);
756 }
757
758 option->text = display(index);
759 if (!option->text.isEmpty()) {
760 option->features |= QStyleOptionViewItem::HasDisplay;
761 }
762
763 option->icon = decoration(*option, index);
764 // Note that even null icons are still drawn for alignment
765 if (!option->icon.isNull()) {
766 option->features |= QStyleOptionViewItem::HasDecoration;
767 }
768
769 // ### Make sure this value is always true for now
770 option->showDecorationSelected = true;
771}
772
773void KFileItemDelegate::Private::paintJobTransfers(QPainter *painter, const qreal &jobAnimationAngle, const QPoint &iconPos, const QStyleOptionViewItem &opt)
774{
775 painter->save();
776 QSize iconSize = opt.icon.actualSize(opt.decorationSize);
777 QPixmap downArrow = downArrowIcon.pixmap(iconSize * 0.30);
778 // corner (less x and y than bottom-right corner) that we will center the painter around
779 QPoint bottomRightCorner = QPoint(iconPos.x() + iconSize.width() * 0.75, iconPos.y() + iconSize.height() * 0.60);
780
781 QPainter pixmapPainter(&downArrow);
782 // make the icon transparent and such
783 pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
784 pixmapPainter.fillRect(downArrow.rect(), QColor(255, 255, 255, 110));
785
786 painter->translate(bottomRightCorner);
787
788 painter->drawPixmap(-downArrow.size().width() * .50, -downArrow.size().height() * .50, downArrow);
789
790 // animate the circles by rotating the painter around the center point..
791 painter->rotate(jobAnimationAngle);
792 painter->setPen(QColor(20, 20, 20, 80));
793 painter->setBrush(QColor(250, 250, 250, 90));
794
795 int radius = iconSize.width() * 0.04;
796 int spacing = radius * 4.5;
797
798 // left
799 painter->drawEllipse(QPoint(-spacing, 0), radius, radius);
800 // right
801 painter->drawEllipse(QPoint(spacing, 0), radius, radius);
802 // up
803 painter->drawEllipse(QPoint(0, -spacing), radius, radius);
804 // down
805 painter->drawEllipse(QPoint(0, spacing), radius, radius);
806 painter->restore();
807}
808
809// ---------------------------------------------------------------------------
810
813 , d(new Private(this))
814{
817
818 // Margins for horizontal mode (list views, tree views, table views)
819 const int textMargin = focusHMargin * 4;
821 d->setHorizontalMargin(Private::TextMargin, textMargin, focusVMargin, focusHMargin, focusVMargin);
822 } else {
823 d->setHorizontalMargin(Private::TextMargin, focusHMargin, focusVMargin, textMargin, focusVMargin);
824 }
825
826 d->setHorizontalMargin(Private::IconMargin, focusHMargin, focusVMargin);
827 d->setHorizontalMargin(Private::ItemMargin, 0, 0);
828
829 // Margins for vertical mode (icon views)
830 d->setVerticalMargin(Private::TextMargin, 6, 2);
831 d->setVerticalMargin(Private::IconMargin, focusHMargin, focusVMargin);
832 d->setVerticalMargin(Private::ItemMargin, 0, 0);
833
835}
836
838
840{
841 // If the model wants to provide its own size hint for the item
842 const QVariant value = index.data(Qt::SizeHintRole);
843 if (value.isValid()) {
844 return qvariant_cast<QSize>(value);
845 }
846
847 QStyleOptionViewItem opt(option);
848 d->initStyleOption(&opt, index);
849 d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
850
851 const QSize displaySize = d->displaySizeHint(opt, index);
852 const QSize decorationSize = d->decorationSizeHint(opt, index);
853
854 QSize size;
855
856 if (d->verticalLayout(opt)) {
857 size.rwidth() = qMax(displaySize.width(), decorationSize.width());
858 size.rheight() = decorationSize.height() + displaySize.height() + 1;
859 } else {
860 size.rwidth() = decorationSize.width() + displaySize.width() + 1;
861 size.rheight() = qMax(decorationSize.height(), displaySize.height());
862 }
863
864 size = d->addMargin(size, Private::ItemMargin);
865 if (!d->maximumSize.isEmpty()) {
866 size = size.boundedTo(d->maximumSize);
867 }
868
869 return size;
870}
871
872QString KFileItemDelegate::Private::display(const QModelIndex &index) const
873{
874 const QVariant value = index.data(Qt::DisplayRole);
875
876 switch (value.typeId()) {
877 case QMetaType::QString: {
878 if (index.column() == KDirModel::Size) {
879 return itemSize(index, fileItem(index));
880 } else {
881 const QString text = replaceNewlines(value.toString());
883 }
884 }
885
887 return QLocale().toString(value.toDouble(), 'f');
888
889 case QMetaType::Int:
890 case QMetaType::UInt:
891 return QLocale().toString(value.toInt());
892
893 default:
894 return QString();
895 }
896}
897
898void KFileItemDelegate::setShowInformation(const InformationList &list)
899{
900 d->informationList = list;
901}
902
904{
905 if (value != NoInformation) {
906 d->informationList = InformationList() << value;
907 } else {
908 d->informationList = InformationList();
909 }
910}
911
912KFileItemDelegate::InformationList KFileItemDelegate::showInformation() const
913{
914 return d->informationList;
915}
916
918{
919 d->shadowColor = color;
920}
921
923{
924 return d->shadowColor;
925}
926
928{
929 d->shadowOffset = offset;
930}
931
933{
934 return d->shadowOffset;
935}
936
938{
939 d->shadowBlur = factor;
940}
941
943{
944 return d->shadowBlur;
945}
946
948{
949 d->maximumSize = size;
950}
951
953{
954 return d->maximumSize;
955}
956
958{
959 d->showToolTipWhenElided = showToolTip;
960}
961
963{
964 return d->showToolTipWhenElided;
965}
966
971
973{
974 return d->wrapMode;
975}
976
978{
979 if (index.column() > 0) {
980 return QRect(0, 0, 0, 0);
981 }
982 QStyleOptionViewItem opt(option);
983 d->initStyleOption(&opt, index);
984 return QRect(d->iconPosition(opt), opt.icon.actualSize(opt.decorationSize));
985}
986
988{
989 d->downArrowIcon = QIcon::fromTheme(QStringLiteral("go-down"));
990 d->jobTransfersVisible = jobTransfersVisible;
991}
992
994{
995 return d->jobTransfersVisible;
996}
997
998QIcon KFileItemDelegate::Private::decoration(const QStyleOptionViewItem &option, const QModelIndex &index) const
999{
1000 const QVariant value = index.data(Qt::DecorationRole);
1001 QIcon icon;
1002
1003 switch (value.typeId()) {
1004 case QMetaType::QIcon:
1005 icon = qvariant_cast<QIcon>(value);
1006 break;
1007
1008 case QMetaType::QPixmap:
1009 icon.addPixmap(qvariant_cast<QPixmap>(value));
1010 break;
1011
1012 case QMetaType::QColor: {
1013 QPixmap pixmap(option.decorationSize);
1014 pixmap.fill(qvariant_cast<QColor>(value));
1015 icon.addPixmap(pixmap);
1016 break;
1017 }
1018
1019 default:
1020 break;
1021 }
1022
1023 return icon;
1024}
1025
1026QRect KFileItemDelegate::Private::labelRectangle(const QStyleOptionViewItem &option, const QModelIndex &index) const
1027{
1028 const QSize decoSize = (index.column() == 0) ? addMargin(option.decorationSize, Private::IconMargin) : QSize(0, 0);
1029 const QRect itemRect = subtractMargin(option.rect, Private::ItemMargin);
1030 QRect textArea(QPoint(0, 0), itemRect.size());
1031
1032 switch (option.decorationPosition) {
1034 textArea.setTop(decoSize.height() + 1);
1035 break;
1036
1038 textArea.setBottom(itemRect.height() - decoSize.height() - 1);
1039 break;
1040
1042 textArea.setLeft(decoSize.width() + 1);
1043 break;
1044
1046 textArea.setRight(itemRect.width() - decoSize.width() - 1);
1047 break;
1048 }
1049
1050 textArea.translate(itemRect.topLeft());
1051 return QStyle::visualRect(option.direction, option.rect, textArea);
1052}
1053
1054QPoint KFileItemDelegate::Private::iconPosition(const QStyleOptionViewItem &option) const
1055{
1056 if (option.index.column() > 0) {
1057 return QPoint(0, 0);
1058 }
1059
1060 const QRect itemRect = subtractMargin(option.rect, Private::ItemMargin);
1061 Qt::Alignment alignment;
1062
1063 // Convert decorationPosition to the alignment the decoration will have in option.rect
1064 switch (option.decorationPosition) {
1066 alignment = Qt::AlignHCenter | Qt::AlignTop;
1067 break;
1068
1070 alignment = Qt::AlignHCenter | Qt::AlignBottom;
1071 break;
1072
1074 alignment = Qt::AlignVCenter | Qt::AlignLeft;
1075 break;
1076
1078 alignment = Qt::AlignVCenter | Qt::AlignRight;
1079 break;
1080 }
1081
1082 // Compute the nominal decoration rectangle
1083 const QSize size = addMargin(option.decorationSize, Private::IconMargin);
1084 const QRect rect = QStyle::alignedRect(option.direction, alignment, size, itemRect);
1085
1086 // Position the icon in the center of the rectangle
1087 QRect iconRect = QRect(QPoint(), option.icon.actualSize(option.decorationSize));
1088 iconRect.moveCenter(rect.center());
1089
1090 return iconRect.topLeft();
1091}
1092
1093void KFileItemDelegate::Private::drawFocusRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const
1094{
1095 if (!(option.state & QStyle::State_HasFocus)) {
1096 return;
1097 }
1098
1099 QStyleOptionFocusRect opt;
1100 opt.direction = option.direction;
1101 opt.fontMetrics = option.fontMetrics;
1102 opt.palette = option.palette;
1103 opt.rect = rect;
1104 opt.state = option.state | QStyle::State_KeyboardFocusChange | QStyle::State_Item;
1105 opt.backgroundColor = option.palette.color(option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base);
1106
1107 // Apparently some widget styles expect this hint to not be set
1108 painter->setRenderHint(QPainter::Antialiasing, false);
1109
1110 QStyle *style = option.widget ? option.widget->style() : QApplication::style();
1111 style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget);
1112
1114}
1115
1116void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
1117{
1118 if (!index.isValid()) {
1119 return;
1120 }
1121
1122 QStyleOptionViewItem opt(option);
1123 d->initStyleOption(&opt, index);
1124 d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
1125
1126 if (!(option.state & QStyle::State_Enabled)) {
1127 opt.palette.setCurrentColorGroup(QPalette::Disabled);
1128 }
1129
1130 // Unset the mouse over bit if we're not drawing the first column
1131 if (index.column() > 0) {
1132 opt.state &= ~QStyle::State_MouseOver;
1133 } else {
1134 opt.viewItemPosition = QStyleOptionViewItem::OnlyOne;
1135 }
1136
1138
1139 // Check if the item is being animated
1140 // ========================================================================
1141 KIO::AnimationState *state = d->animationState(opt, index, view);
1142 KIO::CachedRendering *cache = nullptr;
1143 qreal progress = ((opt.state & QStyle::State_MouseOver) && index.column() == KDirModel::Name) ? 1.0 : 0.0;
1144 const QPoint iconPos = d->iconPosition(opt);
1145 QIcon::Mode iconMode;
1146
1147 if (!(option.state & QStyle::State_Enabled)) {
1148 iconMode = QIcon::Disabled;
1149 } else if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) {
1150 iconMode = QIcon::Selected;
1151 } else {
1152 iconMode = QIcon::Normal;
1153 }
1154
1155 QIcon::State iconState = option.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
1156 QPixmap icon = opt.icon.pixmap(opt.decorationSize, iconMode, iconState);
1157
1158 const KFileItem fileItem = d->fileItem(index);
1159 const bool isDir = fileItem.isDir();
1160 if (fileItem.isHidden()) {
1162 }
1163
1164 if (state && !state->hasJobAnimation()) {
1165 cache = state->cachedRendering();
1166 progress = state->hoverProgress();
1167 // Clear the mouse over bit temporarily
1168 opt.state &= ~QStyle::State_MouseOver;
1169
1170 // If we have a cached rendering, draw the item from the cache
1171 if (cache) {
1172 if (cache->checkValidity(opt.state) && cache->regular.size() == opt.rect.size()) {
1173 QPixmap pixmap = d->transition(cache->regular, cache->hover, progress);
1174
1175 if (state->cachedRenderingFadeFrom() && state->fadeProgress() != 1.0) {
1176 // Apply icon fading animation
1177 KIO::CachedRendering *fadeFromCache = state->cachedRenderingFadeFrom();
1178 const QPixmap fadeFromPixmap = d->transition(fadeFromCache->regular, fadeFromCache->hover, progress);
1179
1180 pixmap = d->transition(fadeFromPixmap, pixmap, state->fadeProgress());
1181 }
1182 painter->drawPixmap(option.rect.topLeft(), pixmap);
1183 drawSelectionEmblem(option, painter, index);
1184 if (d->jobTransfersVisible && index.column() == 0) {
1185 if (index.data(KDirModel::HasJobRole).toBool()) {
1186 d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
1187 }
1188 }
1189 return;
1190 }
1191
1192 if (!cache->checkValidity(opt.state)) {
1193 if (opt.widget->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, opt.widget)) {
1194 // Fade over from the old icon to the new one
1195 // Only start a new fade if the previous one is ready
1196 // Else we may start racing when checkValidity() always returns false
1197 if (state->fadeProgress() == 1) {
1198 state->setCachedRenderingFadeFrom(state->takeCachedRendering());
1199 }
1200 }
1201 d->gotNewIcon(index);
1202 }
1203 // If it wasn't valid, delete it
1204 state->setCachedRendering(nullptr);
1205 } else {
1206 // The cache may have been discarded, but the animation handler still needs to know about new icons
1207 d->gotNewIcon(index);
1208 }
1209 }
1210
1211 // Compute the metrics, and lay out the text items
1212 // ========================================================================
1213 QColor labelColor = d->foregroundBrush(opt, index).color();
1214 QColor infoColor = labelColor;
1215 if (!(option.state & QStyle::State_Selected)) {
1216 // the code below is taken from Dolphin
1217 const QColor c2 = option.palette.base().color();
1218 const int p1 = 70;
1219 const int p2 = 100 - p1;
1220 infoColor = QColor((labelColor.red() * p1 + c2.red() * p2) / 100,
1221 (labelColor.green() * p1 + c2.green() * p2) / 100,
1222 (labelColor.blue() * p1 + c2.blue() * p2) / 100);
1223
1224 if (fileItem.isHidden()) {
1225 labelColor = infoColor;
1226 }
1227 }
1228
1229 // ### Apply the selection effect to the icon when the item is selected and
1230 // showDecorationSelected is false.
1231
1232 QTextLayout labelLayout;
1233 QTextLayout infoLayout;
1234 QRect textBoundingRect;
1235
1236 d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
1237
1238 QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
1239
1240 int focusHMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
1241 int focusVMargin = style->pixelMetric(QStyle::PM_FocusFrameVMargin);
1242 QRect focusRect = textBoundingRect.adjusted(-focusHMargin, -focusVMargin, +focusHMargin, +focusVMargin);
1243
1244 // Create a new cached rendering of a hovered and an unhovered item.
1245 // We don't create a new cache for a fully hovered item, since we don't
1246 // know yet if a hover out animation will be run.
1247 // ========================================================================
1248 if (state && (state->hoverProgress() < 1 || state->fadeProgress() < 1)) {
1249 const qreal dpr = painter->device()->devicePixelRatioF();
1250
1251 cache = new KIO::CachedRendering(opt.state, option.rect.size(), index, dpr);
1252
1253 QPainter p;
1254 p.begin(&cache->regular);
1255 p.translate(-option.rect.topLeft());
1257 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);
1258 p.drawPixmap(iconPos, icon);
1259 drawSelectionEmblem(option, painter, index);
1260 d->drawTextItems(&p, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
1261 d->drawFocusRect(&p, opt, focusRect);
1262 p.end();
1263
1264 opt.state |= QStyle::State_MouseOver;
1265 icon = d->applyHoverEffect(icon);
1266
1267 p.begin(&cache->hover);
1268 p.translate(-option.rect.topLeft());
1270 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);
1271 p.drawPixmap(iconPos, icon);
1272 drawSelectionEmblem(option, painter, index);
1273 d->drawTextItems(&p, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
1274 d->drawFocusRect(&p, opt, focusRect);
1275 p.end();
1276
1277 state->setCachedRendering(cache);
1278
1279 QPixmap pixmap = d->transition(cache->regular, cache->hover, progress);
1280
1281 if (state->cachedRenderingFadeFrom() && state->fadeProgress() == 0) {
1282 // Apply icon fading animation
1283 KIO::CachedRendering *fadeFromCache = state->cachedRenderingFadeFrom();
1284 const QPixmap fadeFromPixmap = d->transition(fadeFromCache->regular, fadeFromCache->hover, progress);
1285
1286 pixmap = d->transition(fadeFromPixmap, pixmap, state->fadeProgress());
1287
1288 d->restartAnimation(state);
1289 }
1290
1291 painter->drawPixmap(option.rect.topLeft(), pixmap);
1292 drawSelectionEmblem(option, painter, index);
1294 if (d->jobTransfersVisible && index.column() == 0) {
1295 if (index.data(KDirModel::HasJobRole).toBool()) {
1296 d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
1297 }
1298 }
1299 return;
1300 }
1301
1302 // Render the item directly if we're not using a cached rendering
1303 // ========================================================================
1304 painter->save();
1306
1307 if (progress > 0 && !(opt.state & QStyle::State_MouseOver)) {
1308 opt.state |= QStyle::State_MouseOver;
1309 icon = d->applyHoverEffect(icon);
1310 }
1311
1312 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
1313 painter->drawPixmap(iconPos, icon);
1314
1315 d->drawTextItems(painter, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
1316 d->drawFocusRect(painter, opt, focusRect);
1317 drawSelectionEmblem(option, painter, index);
1318
1319 if (d->jobTransfersVisible && index.column() == 0 && state) {
1320 if (index.data(KDirModel::HasJobRole).toBool()) {
1321 d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
1322 }
1323 }
1324 painter->restore();
1325}
1326
1327void KFileItemDelegate::drawSelectionEmblem(QStyleOptionViewItem option, QPainter *painter, const QModelIndex &index) const
1328{
1329 if (index.column() != 0 || !qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
1330 return;
1331 }
1332 const auto state = option.state;
1333 if ((state & QStyle::State_MouseOver && !fileItem(index).isDir()) || (state & QStyle::State_Selected)) {
1334 const QString selectionEmblem = state & QStyle::State_Selected ? QStringLiteral("emblem-remove") : QStringLiteral("emblem-added");
1335 const auto emblem = QIcon::fromTheme(selectionEmblem).pixmap(d->emblemRect.size(), state & QStyle::State_MouseOver ? QIcon::Active : QIcon::Disabled);
1336
1337 painter->drawPixmap(d->emblemRect.topLeft(), emblem);
1338 }
1339}
1340
1341int KFileItemDelegate::Private::scaledEmblemSize(int iconSize) const
1342{
1343 if (iconSize <= KIconLoader::SizeSmallMedium) {
1345 } else if (iconSize <= KIconLoader::SizeHuge) {
1347 } else if (iconSize <= KIconLoader::SizeEnormous) {
1349 }
1350
1351 return KIconLoader::SizeHuge;
1352}
1353
1355{
1356 QStyleOptionViewItem opt(option);
1357 d->initStyleOption(&opt, index);
1358
1359 QTextEdit *edit = new QTextEdit(parent);
1360 edit->setAcceptRichText(false);
1363 edit->setAlignment(opt.displayAlignment);
1364 edit->setEnabled(false); // Disable the text-edit to mark it as un-initialized
1365 return edit;
1366}
1367
1369{
1370 Q_UNUSED(event)
1371 Q_UNUSED(model)
1372 Q_UNUSED(option)
1373 Q_UNUSED(index)
1374
1375 return false;
1376}
1377
1379{
1380 QTextEdit *textedit = qobject_cast<QTextEdit *>(editor);
1381 Q_ASSERT(textedit != nullptr);
1382
1383 // Do not update existing text that the user may already have edited.
1384 // The models will call setEditorData(..) whenever the icon has changed,
1385 // and this makes the editing work correctly despite that.
1386 if (textedit->isEnabled()) {
1387 return;
1388 }
1389 textedit->setEnabled(true); // Enable the text-edit to mark it as initialized
1390
1391 const QVariant value = index.data(Qt::EditRole);
1392 const QString text = value.toString();
1393 textedit->insertPlainText(text);
1394 textedit->selectAll();
1395
1396 QMimeDatabase db;
1397 const QString extension = db.suffixForFileName(text);
1398 if (!extension.isEmpty()) {
1399 // The filename contains an extension. Assure that only the filename
1400 // gets selected.
1401 const int selectionLength = text.length() - extension.length() - 1;
1402 QTextCursor cursor = textedit->textCursor();
1405 textedit->setTextCursor(cursor);
1406 }
1407}
1408
1410{
1411 QTextEdit *textedit = qobject_cast<QTextEdit *>(editor);
1412 Q_ASSERT(textedit != nullptr);
1413
1414 model->setData(index, textedit->toPlainText(), Qt::EditRole);
1415}
1416
1418{
1419 QStyleOptionViewItem opt(option);
1420 d->initStyleOption(&opt, index);
1421 d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
1422
1423 QRect r = d->labelRectangle(opt, index);
1424
1425 // Use the full available width for the editor when maximumSize is set
1426 if (!d->maximumSize.isEmpty()) {
1427 if (d->verticalLayout(option)) {
1428 int diff = qMax(r.width(), d->maximumSize.width()) - r.width();
1429 if (diff > 1) {
1430 r.adjust(-(diff / 2), 0, diff / 2, 0);
1431 }
1432 } else {
1433 int diff = qMax(r.width(), d->maximumSize.width() - opt.decorationSize.width()) - r.width();
1434 if (diff > 0) {
1435 if (opt.decorationPosition == QStyleOptionViewItem::Left) {
1436 r.adjust(0, 0, diff, 0);
1437 } else {
1438 r.adjust(-diff, 0, 0, 0);
1439 }
1440 }
1441 }
1442 }
1443
1444 QTextEdit *textedit = qobject_cast<QTextEdit *>(editor);
1445 Q_ASSERT(textedit != nullptr);
1446 const int frame = textedit->frameWidth();
1447 r.adjust(-frame, -frame, frame, frame);
1448
1449 editor->setGeometry(r);
1450}
1451
1453{
1454 Q_UNUSED(event)
1455 Q_UNUSED(view)
1456
1457 // if the tooltip information the model keeps is different from the display information,
1458 // show it always
1459 const QVariant toolTip = index.data(Qt::ToolTipRole);
1460
1461 if (!toolTip.isValid()) {
1462 return false;
1463 }
1464
1465 if (index.data() != toolTip) {
1466 return QAbstractItemDelegate::helpEvent(event, view, option, index);
1467 }
1468
1469 if (!d->showToolTipWhenElided) {
1470 return false;
1471 }
1472
1473 // in the case the tooltip information is the same as the display information,
1474 // show it only in the case the display information is elided
1475 QStyleOptionViewItem opt(option);
1476 d->initStyleOption(&opt, index);
1477 d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
1478
1479 QTextLayout labelLayout;
1480 QTextLayout infoLayout;
1481 QRect textBoundingRect;
1482 d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
1483 const QString elidedText = d->elidedText(labelLayout, opt, textBoundingRect.size());
1484
1485 if (elidedText != d->display(index)) {
1486 return QAbstractItemDelegate::helpEvent(event, view, option, index);
1487 }
1488
1489 return false;
1490}
1491
1493{
1494 QStyleOptionViewItem opt(option);
1495 d->initStyleOption(&opt, index);
1496 d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
1497
1498 QTextLayout labelLayout;
1499 QTextLayout infoLayout;
1500 QRect textBoundingRect;
1501 d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
1502
1503 const QPoint pos = d->iconPosition(opt);
1504 QRect iconRect = QRect(pos, opt.icon.actualSize(opt.decorationSize));
1505
1506 // Extend the icon rect so it touches the text rect
1507 switch (opt.decorationPosition) {
1509 if (iconRect.width() < textBoundingRect.width()) {
1510 iconRect.setBottom(textBoundingRect.top());
1511 } else {
1512 textBoundingRect.setTop(iconRect.bottom());
1513 }
1514 break;
1516 if (iconRect.width() < textBoundingRect.width()) {
1517 iconRect.setTop(textBoundingRect.bottom());
1518 } else {
1519 textBoundingRect.setBottom(iconRect.top());
1520 }
1521 break;
1523 iconRect.setRight(textBoundingRect.left());
1524 break;
1526 iconRect.setLeft(textBoundingRect.right());
1527 break;
1528 }
1529
1530 QRegion region;
1531 region += iconRect;
1532 region += textBoundingRect;
1533 return region;
1534}
1535
1537{
1538 QTextEdit *editor = qobject_cast<QTextEdit *>(object);
1539 if (!editor) {
1540 return false;
1541 }
1542
1543 switch (event->type()) {
1544 case QEvent::KeyPress: {
1545 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
1546 switch (keyEvent->key()) {
1547 case Qt::Key_Tab:
1548 case Qt::Key_Backtab:
1549 Q_EMIT commitData(editor);
1550 Q_EMIT closeEditor(editor, NoHint);
1551 return true;
1552
1553 case Qt::Key_Enter:
1554 case Qt::Key_Return: {
1555 const QString text = editor->toPlainText();
1556 if (text.isEmpty() || (text == QLatin1Char('.')) || (text == QLatin1String(".."))) {
1557 return true; // So a newline doesn't get inserted
1558 }
1559
1560 Q_EMIT commitData(editor);
1562 return true;
1563 }
1564
1565 case Qt::Key_Escape:
1567 return true;
1568
1569 default:
1570 return false;
1571 } // switch (keyEvent->key())
1572 } // case QEvent::KeyPress
1573
1574 case QEvent::FocusOut: {
1576 if (!w || w->parent() != editor) {
1577 Q_EMIT commitData(editor);
1578 Q_EMIT closeEditor(editor, NoHint);
1579 return true;
1580 } else {
1581 return false;
1582 }
1583 }
1584
1585 default:
1586 return false;
1587 } // switch (event->type())
1588}
1589
1591{
1592 const auto emblemSize = d->scaledEmblemSize(iconSize);
1593
1594 // With small icons, try to center the emblem on top of the icon
1595 if (iconSize <= KIconLoader::SizeSmallMedium) {
1596 d->emblemRect = QRect(rect.topLeft().x() + emblemSize / 4, rect.topLeft().y() + emblemSize / 4, emblemSize, emblemSize);
1597 } else {
1598 d->emblemRect = QRect(rect.topLeft().x(), rect.topLeft().y(), emblemSize, emblemSize);
1599 }
1600}
1601
1603{
1604 return d->emblemRect;
1605}
1606
1607KFileItem KFileItemDelegate::fileItem(const QModelIndex &index) const
1608{
1609 return d->fileItem(index);
1610}
1611
1612#include "moc_kfileitemdelegate.cpp"
@ FileItemRole
returns the KFileItem for a given index. roleName is "fileItem".
Definition kdirmodel.h:160
@ ChildCountRole
returns the number of items in a directory, or ChildCountUnknown. roleName is "childCount".
Definition kdirmodel.h:161
@ HasJobRole
returns whether or not there is a job on an item (file/directory). roleName is "hasJob".
Definition kdirmodel.h:162
KFileItemDelegate is intended to be used to provide a KDE file system view, when using one of the sta...
void setShowInformation(const InformationList &list)
Sets the list of information lines that are shown below the icon label in list views.
void setShadowColor(const QColor &color)
Sets the color used for drawing the text shadow.
QPointF shadowOffset
This property holds the horizontal and vertical offset for the text shadow.
QColor shadowColor
This property holds the color used for the text shadow.
void setSelectionEmblemRect(QRect rect, int iconSize)
Set the rectangle where selectionEmblem should be drawn in.
InformationList information
This property holds which additional information (if any) should be shown below items in icon views.
void setShadowOffset(const QPointF &offset)
Sets the horizontal and vertical offset for the text shadow.
qreal shadowBlur
This property holds the blur radius for the text shadow.
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
Reimplemented from QAbstractItemDelegate.
QRect selectionEmblemRect() const
void setMaximumSize(const QSize &size)
Sets the maximum size for KFileItemDelegate::sizeHint().
void setWrapMode(QTextOption::WrapMode wrapMode)
When the contents text needs to be wrapped, wrapMode strategy will be followed.
QRect iconRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
Returns the rectangle of the icon that is aligned inside the decoration rectangle.
Information
This enum defines the additional information that can be displayed below item labels in icon views.
@ Comment
A simple comment that can be displayed to the user as is.
@ OctalPermissions
The permissions as an octal value, e.g. 0644.
@ OwnerAndGroup
The user and group that owns the file, e.g. root:root.
@ LocalPathOrUrl
The local path to the file or the URL in case it is not a local file.
@ AccessTime
The date and time the file/folder was last accessed.
@ FriendlyMimeType
The descriptive name for the MIME type, e.g. HTML Document.
@ CreationTime
The date and time the file/folder was created.
@ ModificationTime
The date and time the file/folder was last modified.
@ Permissions
A UNIX permissions string, e.g. -rwxr-xr-x.
@ Size
The file size for files, and the number of items for folders.
@ MimeType
The MIME type for the item, e.g. text/html.
@ LinkDest
The destination of a symbolic link.
@ NoInformation
No additional information will be shown for items.
@ Owner
The user name of the file owner, e.g. root.
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Reimplemented from QAbstractItemDelegate.
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
Returns the nominal size for the item referred to by index, given the provided options.
QSize maximumSize
This property holds the maximum size that can be returned by KFileItemDelegate::sizeHint().
bool showToolTipWhenElided
This property determines whether a tooltip will be shown by the delegate if the display role is elide...
KFileItemDelegate(QObject *parent=nullptr)
Constructs a new KFileItemDelegate.
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Reimplemented from QAbstractItemDelegate.
void setJobTransfersVisible(bool jobTransfersVisible)
Enable/Disable the displaying of an animated overlay that is shown for any destination urls (in the v...
void setShadowBlur(qreal radius)
Sets the blur radius for the text shadow.
bool jobTransfersVisible
This property determines if there are KIO jobs on a destination URL visible, then they will have a sm...
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
Reimplemented from QAbstractItemDelegate.
~KFileItemDelegate() override
Destroys the item delegate.
InformationList showInformation() const
Returns the file item information that should be shown below item labels in list views.
void setShowToolTipWhenElided(bool showToolTip)
Sets whether a tooltip should be shown if the display role is elided containing the full display role...
QTextOption::WrapMode wrapMode() const
Returns the wrapping strategy followed to show text when it needs wrapping.
QRegion shape(const QStyleOptionViewItem &option, const QModelIndex &index)
Returns the shape of the item as a region.
bool eventFilter(QObject *object, QEvent *event) override
Reimplemented from QAbstractItemDelegate.
void setEditorData(QWidget *editor, const QModelIndex &index) const override
Reimplemented from QAbstractItemDelegate.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Paints the item indicated by index, using painter.
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override
Reimplemented from QAbstractItemDelegate.
A KFileItem is a generic class to handle a file, local or remote.
Definition kfileitem.h:36
KIO::filesize_t size() const
Returns the size of the file, if known.
Q_INVOKABLE QString timeString(KFileItem::FileTimes which=ModificationTime) const
Requests the modification, access or creation time as a string, depending on which.
mode_t permissions() const
Returns the permissions of the file (stat.st_mode containing only permissions).
bool isNull() const
Return true if default-constructed.
QString permissionsString() const
Returns the access permissions for the file as a string.
static void toActive(QImage &image)
static void semiTransparent(QImage &image)
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
Type type(const QSqlDatabase &db)
A namespace for KIO globals.
KIOCORE_EXPORT QString convertSize(KIO::filesize_t size)
Converts size from bytes to the string representation.
Definition global.cpp:43
void informationList(QWidget *parent, const QString &text, const QStringList &strlist, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
QString label(StandardShortcut id)
KCOREADDONS_EXPORT QString preProcessWrap(const QString &text)
QAbstractItemDelegate(QObject *parent)
void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)
void commitData(QWidget *editor)
virtual bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy)
void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy)
QWidget * activePopupWidget()
QStyle * style()
int blue() const const
int green() const const
int red() const const
void setAlphaF(float alpha)
bool isRightToLeft()
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
void addPixmap(const QPixmap &pixmap, Mode mode, State state)
QIcon fromTheme(const QString &name)
Format_ARGB32_Premultiplied
QRect rect() const const
QString suffixForFileName(const QString &fileName) const const
int column() const const
QVariant data(int role) const const
bool isValid() const const
QObject(QObject *parent)
Q_EMITQ_EMIT
virtual bool event(QEvent *e)
QObject * parent() const const
T qobject_cast(QObject *object)
qreal devicePixelRatioF() const const
virtual QPaintEngine * paintEngine() const const=0
bool hasFeature(PaintEngineFeatures feature) const const
CompositionMode_DestinationIn
bool begin(QPaintDevice *device)
QPaintDevice * device() const const
void drawEllipse(const QPoint &center, int rx, int ry)
void drawImage(const QPoint &point, const QImage &image)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
bool end()
void fillRect(const QRect &rectangle, QGradient::Preset preset)
void restore()
void rotate(qreal angle)
void save()
void setBrush(Qt::BrushStyle style)
void setCompositionMode(CompositionMode mode)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
void translate(const QPoint &offset)
QPixmap fromImage(QImage &&image, Qt::ImageConversionFlags flags)
bool isNull() const const
QRect rect() const const
QSize size() const const
QImage toImage() const const
int x() const const
int y() const const
void adjust(int dx1, int dy1, int dx2, int dy2)
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
int bottom() const const
QPoint center() const const
int height() const const
int left() const const
int right() const const
void setBottom(int y)
void setLeft(int x)
void setRight(int x)
void setTop(int y)
QSize size() const const
int top() const const
QPoint topLeft() const const
void translate(const QPoint &offset)
int width() const const
int x() const const
int y() const const
QSize boundedTo(const QSize &otherSize) const const
int height() const const
int & rheight()
int & rwidth()
void setWidth(int width)
int width() const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
PM_FocusFrameHMargin
PE_FrameFocusRect
SH_Widget_Animate
QRect alignedRect(Qt::LayoutDirection direction, Qt::Alignment alignment, const QSize &size, const QRect &rectangle)
virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const=0
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
Qt::Alignment visualAlignment(Qt::LayoutDirection direction, Qt::Alignment alignment)
QRect visualRect(Qt::LayoutDirection direction, const QRect &boundingRectangle, const QRect &logicalRectangle)
typedef Alignment
transparent
ForegroundRole
Orientation
ScrollBarAlwaysOff
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
bool movePosition(MoveOperation operation, MoveMode mode, int n)
void setAcceptRichText(bool accept)
void insertPlainText(const QString &text)
void selectAll()
void setAlignment(Qt::Alignment a)
void setTextCursor(const QTextCursor &cursor)
QTextCursor textCursor() const const
QString toPlainText() const const
void beginLayout()
QTextLine createLine()
void draw(QPainter *p, const QPointF &pos, const QList< FormatRange > &selections, const QRectF &clip) const const
void endLayout()
QFont font() const const
QTextLine lineAt(int i) const const
int lineCount() const const
void setFont(const QFont &font)
void setPosition(const QPointF &p)
void setText(const QString &string)
void setTextOption(const QTextOption &option)
QString text() const const
const QTextOption & textOption() const const
qreal height() const const
qreal naturalTextWidth() const const
void setLineWidth(qreal width)
void setPosition(const QPointF &pos)
int textLength() const const
int textStart() const const
void setAlignment(Qt::Alignment alignment)
void setTextDirection(Qt::LayoutDirection direction)
void setWrapMode(WrapMode mode)
QString toDisplayString(FormattingOptions options) const const
bool isValid() const const
bool toBool() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const
int typeId() const const
int userType() const const
void setEnabled(bool)
void setGeometry(const QRect &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:51:43 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.