File indexing completed on 2024-12-01 03:41:15

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2009 Shaun Reich <shaun.reich@kdemail.net>
0004     SPDX-FileCopyrightText: 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "kfileitemdelegate.h"
0010 #include "imagefilter_p.h"
0011 
0012 #include <QApplication>
0013 #include <QCache>
0014 #include <QImage>
0015 #include <QListView>
0016 #include <QLocale>
0017 #include <QMimeDatabase>
0018 #include <QModelIndex>
0019 #include <QPaintEngine>
0020 #include <QPainter>
0021 #include <QPainterPath>
0022 #include <QStyle>
0023 #include <QTextEdit>
0024 #include <QTextLayout>
0025 #include <qmath.h>
0026 
0027 #include <KIconEffect>
0028 #include <KIconLoader>
0029 #include <KLocalizedString>
0030 #include <KStatefulBrush>
0031 #include <KStringHandler>
0032 #include <kdirmodel.h>
0033 #include <kfileitem.h>
0034 
0035 #include "delegateanimationhandler_p.h"
0036 
0037 struct Margin {
0038     int left, right, top, bottom;
0039 };
0040 
0041 class Q_DECL_HIDDEN KFileItemDelegate::Private
0042 {
0043 public:
0044     enum MarginType { ItemMargin = 0, TextMargin, IconMargin, NMargins };
0045 
0046     explicit Private(KFileItemDelegate *parent);
0047     ~Private()
0048     {
0049     }
0050 
0051     QSize decorationSizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
0052     QSize displaySizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
0053     QString replaceNewlines(const QString &string) const;
0054     inline KFileItem fileItem(const QModelIndex &index) const;
0055     QString elidedText(QTextLayout &layout, const QStyleOptionViewItem &option, const QSize &maxSize) const;
0056     QSize layoutText(QTextLayout &layout, const QStyleOptionViewItem &option, const QString &text, const QSize &constraints) const;
0057     QSize layoutText(QTextLayout &layout, const QString &text, int maxWidth) const;
0058     inline void setLayoutOptions(QTextLayout &layout, const QStyleOptionViewItem &options) const;
0059     inline bool verticalLayout(const QStyleOptionViewItem &option) const;
0060     inline QBrush brush(const QVariant &value, const QStyleOptionViewItem &option) const;
0061     QBrush foregroundBrush(const QStyleOptionViewItem &option, const QModelIndex &index) const;
0062     inline void setActiveMargins(Qt::Orientation layout);
0063     void setVerticalMargin(MarginType type, int left, int right, int top, int bottom);
0064     void setHorizontalMargin(MarginType type, int left, int right, int top, int bottom);
0065     inline void setVerticalMargin(MarginType type, int hor, int ver);
0066     inline void setHorizontalMargin(MarginType type, int hor, int ver);
0067     inline QRect addMargin(const QRect &rect, MarginType type) const;
0068     inline QRect subtractMargin(const QRect &rect, MarginType type) const;
0069     inline QSize addMargin(const QSize &size, MarginType type) const;
0070     inline QSize subtractMargin(const QSize &size, MarginType type) const;
0071     QString itemSize(const QModelIndex &index, const KFileItem &item) const;
0072     QString information(const QStyleOptionViewItem &option, const QModelIndex &index, const KFileItem &item) const;
0073     bool isListView(const QStyleOptionViewItem &option) const;
0074     QString display(const QModelIndex &index) const;
0075     QIcon decoration(const QStyleOptionViewItem &option, const QModelIndex &index) const;
0076     QPoint iconPosition(const QStyleOptionViewItem &option) const;
0077     QRect labelRectangle(const QStyleOptionViewItem &option, const QModelIndex &index) const;
0078     void layoutTextItems(const QStyleOptionViewItem &option,
0079                          const QModelIndex &index,
0080                          QTextLayout *labelLayout,
0081                          QTextLayout *infoLayout,
0082                          QRect *textBoundingRect) const;
0083     void drawTextItems(QPainter *painter,
0084                        const QTextLayout &labelLayout,
0085                        const QColor &labelColor,
0086                        const QTextLayout &infoLayout,
0087                        const QColor &infoColor,
0088                        const QRect &textBoundingRect) const;
0089     KIO::AnimationState *animationState(const QStyleOptionViewItem &option, const QModelIndex &index, const QAbstractItemView *view) const;
0090     void restartAnimation(KIO::AnimationState *state);
0091     QPixmap applyHoverEffect(const QPixmap &icon) const;
0092     QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount) const;
0093     void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const;
0094     void drawFocusRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const;
0095 
0096     void gotNewIcon(const QModelIndex &index);
0097 
0098     void paintJobTransfers(QPainter *painter, const qreal &jobAnimationAngle, const QPoint &iconPos, const QStyleOptionViewItem &opt);
0099 
0100 public:
0101     KFileItemDelegate::InformationList informationList;
0102     QColor shadowColor;
0103     QPointF shadowOffset;
0104     qreal shadowBlur;
0105     QSize maximumSize;
0106     bool showToolTipWhenElided;
0107     QTextOption::WrapMode wrapMode;
0108     bool jobTransfersVisible;
0109     QIcon downArrowIcon;
0110 
0111 private:
0112     KIO::DelegateAnimationHandler *animationHandler;
0113     Margin verticalMargin[NMargins];
0114     Margin horizontalMargin[NMargins];
0115     Margin *activeMargins;
0116 };
0117 
0118 KFileItemDelegate::Private::Private(KFileItemDelegate *parent)
0119     : shadowColor(Qt::transparent)
0120     , shadowOffset(1, 1)
0121     , shadowBlur(2)
0122     , maximumSize(0, 0)
0123     , showToolTipWhenElided(true)
0124     , wrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere)
0125     , jobTransfersVisible(false)
0126     , animationHandler(new KIO::DelegateAnimationHandler(parent))
0127     , activeMargins(nullptr)
0128 {
0129 }
0130 
0131 void KFileItemDelegate::Private::setActiveMargins(Qt::Orientation layout)
0132 {
0133     activeMargins = (layout == Qt::Horizontal ? horizontalMargin : verticalMargin);
0134 }
0135 
0136 void KFileItemDelegate::Private::setVerticalMargin(MarginType type, int left, int top, int right, int bottom)
0137 {
0138     verticalMargin[type].left = left;
0139     verticalMargin[type].right = right;
0140     verticalMargin[type].top = top;
0141     verticalMargin[type].bottom = bottom;
0142 }
0143 
0144 void KFileItemDelegate::Private::setHorizontalMargin(MarginType type, int left, int top, int right, int bottom)
0145 {
0146     horizontalMargin[type].left = left;
0147     horizontalMargin[type].right = right;
0148     horizontalMargin[type].top = top;
0149     horizontalMargin[type].bottom = bottom;
0150 }
0151 
0152 void KFileItemDelegate::Private::setVerticalMargin(MarginType type, int horizontal, int vertical)
0153 {
0154     setVerticalMargin(type, horizontal, vertical, horizontal, vertical);
0155 }
0156 
0157 void KFileItemDelegate::Private::setHorizontalMargin(MarginType type, int horizontal, int vertical)
0158 {
0159     setHorizontalMargin(type, horizontal, vertical, horizontal, vertical);
0160 }
0161 
0162 QRect KFileItemDelegate::Private::addMargin(const QRect &rect, MarginType type) const
0163 {
0164     Q_ASSERT(activeMargins != nullptr);
0165     const Margin &m = activeMargins[type];
0166     return rect.adjusted(-m.left, -m.top, m.right, m.bottom);
0167 }
0168 
0169 QRect KFileItemDelegate::Private::subtractMargin(const QRect &rect, MarginType type) const
0170 {
0171     Q_ASSERT(activeMargins != nullptr);
0172     const Margin &m = activeMargins[type];
0173     return rect.adjusted(m.left, m.top, -m.right, -m.bottom);
0174 }
0175 
0176 QSize KFileItemDelegate::Private::addMargin(const QSize &size, MarginType type) const
0177 {
0178     Q_ASSERT(activeMargins != nullptr);
0179     const Margin &m = activeMargins[type];
0180     return QSize(size.width() + m.left + m.right, size.height() + m.top + m.bottom);
0181 }
0182 
0183 QSize KFileItemDelegate::Private::subtractMargin(const QSize &size, MarginType type) const
0184 {
0185     Q_ASSERT(activeMargins != nullptr);
0186     const Margin &m = activeMargins[type];
0187     return QSize(size.width() - m.left - m.right, size.height() - m.top - m.bottom);
0188 }
0189 
0190 // Returns the size of a file, or the number of items in a directory, as a QString
0191 QString KFileItemDelegate::Private::itemSize(const QModelIndex &index, const KFileItem &item) const
0192 {
0193     // Return a formatted string containing the file size, if the item is a file
0194     if (item.isFile()) {
0195         return KIO::convertSize(item.size());
0196     }
0197 
0198     // Return the number of items in the directory
0199     const QVariant value = index.data(KDirModel::ChildCountRole);
0200     const int count = value.typeId() == QMetaType::Int ? value.toInt() : KDirModel::ChildCountUnknown;
0201 
0202     if (count == KDirModel::ChildCountUnknown) {
0203         // was: i18nc("Items in a folder", "? items");
0204         // but this just looks useless in a remote directory listing,
0205         // better not show anything.
0206         return QString();
0207     }
0208 
0209     return i18ncp("Items in a folder", "1 item", "%1 items", count);
0210 }
0211 
0212 // Returns the additional information string, if one should be shown, or an empty string otherwise
0213 QString KFileItemDelegate::Private::information(const QStyleOptionViewItem &option, const QModelIndex &index, const KFileItem &item) const
0214 {
0215     QString string;
0216 
0217     if (informationList.isEmpty() || item.isNull() || !isListView(option)) {
0218         return string;
0219     }
0220 
0221     for (KFileItemDelegate::Information info : informationList) {
0222         if (info == KFileItemDelegate::NoInformation) {
0223             continue;
0224         }
0225 
0226         if (!string.isEmpty()) {
0227             string += QChar::LineSeparator;
0228         }
0229 
0230         switch (info) {
0231         case KFileItemDelegate::Size:
0232             string += itemSize(index, item);
0233             break;
0234 
0235         case KFileItemDelegate::Permissions:
0236             string += item.permissionsString();
0237             break;
0238 
0239         case KFileItemDelegate::OctalPermissions:
0240             string += QLatin1Char('0') + QString::number(item.permissions(), 8);
0241             break;
0242 
0243         case KFileItemDelegate::Owner:
0244             string += item.user();
0245             break;
0246 
0247         case KFileItemDelegate::OwnerAndGroup:
0248             string += item.user() + QLatin1Char(':') + item.group();
0249             break;
0250 
0251         case KFileItemDelegate::CreationTime:
0252             string += item.timeString(KFileItem::CreationTime);
0253             break;
0254 
0255         case KFileItemDelegate::ModificationTime:
0256             string += item.timeString(KFileItem::ModificationTime);
0257             break;
0258 
0259         case KFileItemDelegate::AccessTime:
0260             string += item.timeString(KFileItem::AccessTime);
0261             break;
0262 
0263         case KFileItemDelegate::MimeType:
0264             string += item.isMimeTypeKnown() ? item.mimetype() : i18nc("@info mimetype", "Unknown");
0265             break;
0266 
0267         case KFileItemDelegate::FriendlyMimeType:
0268             string += item.isMimeTypeKnown() ? item.mimeComment() : i18nc("@info mimetype", "Unknown");
0269             break;
0270 
0271         case KFileItemDelegate::LinkDest:
0272             string += item.linkDest();
0273             break;
0274 
0275         case KFileItemDelegate::LocalPathOrUrl:
0276             if (!item.localPath().isEmpty()) {
0277                 string += item.localPath();
0278             } else {
0279                 string += item.url().toDisplayString();
0280             }
0281             break;
0282 
0283         case KFileItemDelegate::Comment:
0284             string += item.comment();
0285             break;
0286 
0287         default:
0288             break;
0289         } // switch (info)
0290     } // for (info, list)
0291 
0292     return string;
0293 }
0294 
0295 // Returns the KFileItem for the index
0296 KFileItem KFileItemDelegate::Private::fileItem(const QModelIndex &index) const
0297 {
0298     const QVariant value = index.data(KDirModel::FileItemRole);
0299     return qvariant_cast<KFileItem>(value);
0300 }
0301 
0302 // Replaces any newline characters in the provided string, with QChar::LineSeparator
0303 QString KFileItemDelegate::Private::replaceNewlines(const QString &text) const
0304 {
0305     QString string = text;
0306     string.replace(QLatin1Char('\n'), QChar::LineSeparator);
0307     return string;
0308 }
0309 
0310 // Lays the text out in a rectangle no larger than constraints, eliding it as necessary
0311 QSize KFileItemDelegate::Private::layoutText(QTextLayout &layout, const QStyleOptionViewItem &option, const QString &text, const QSize &constraints) const
0312 {
0313     const QSize size = layoutText(layout, text, constraints.width());
0314 
0315     if (size.width() > constraints.width() || size.height() > constraints.height()) {
0316         const QString elided = elidedText(layout, option, constraints);
0317         return layoutText(layout, elided, constraints.width());
0318     }
0319 
0320     return size;
0321 }
0322 
0323 // Lays the text out in a rectangle no wider than maxWidth
0324 QSize KFileItemDelegate::Private::layoutText(QTextLayout &layout, const QString &text, int maxWidth) const
0325 {
0326     QFontMetrics metrics(layout.font());
0327     int leading = metrics.leading();
0328     int height = 0;
0329     qreal widthUsed = 0;
0330     QTextLine line;
0331 
0332     layout.setText(text);
0333 
0334     layout.beginLayout();
0335     while ((line = layout.createLine()).isValid()) {
0336         line.setLineWidth(maxWidth);
0337         height += leading;
0338         line.setPosition(QPoint(0, height));
0339         height += int(line.height());
0340         widthUsed = qMax(widthUsed, line.naturalTextWidth());
0341     }
0342     layout.endLayout();
0343 
0344     return QSize(qCeil(widthUsed), height);
0345 }
0346 
0347 // Elides the text in the layout, by iterating over each line in the layout, eliding
0348 // or word breaking the line if it's wider than the max width, and finally adding an
0349 // ellipses at the end of the last line, if there are more lines than will fit within
0350 // the vertical size constraints.
0351 QString KFileItemDelegate::Private::elidedText(QTextLayout &layout, const QStyleOptionViewItem &option, const QSize &size) const
0352 {
0353     const QString text = layout.text();
0354     int maxWidth = size.width();
0355     int maxHeight = size.height();
0356     qreal height = 0;
0357     bool wrapText = (option.features & QStyleOptionViewItem::WrapText);
0358 
0359     // If the string contains a single line of text that shouldn't be word wrapped
0360     if (!wrapText && text.indexOf(QChar::LineSeparator) == -1) {
0361         return option.fontMetrics.elidedText(text, option.textElideMode, maxWidth);
0362     }
0363 
0364     // Elide each line that has already been laid out in the layout.
0365     QString elided;
0366     elided.reserve(text.length());
0367 
0368     for (int i = 0; i < layout.lineCount(); i++) {
0369         QTextLine line = layout.lineAt(i);
0370         const int start = line.textStart();
0371         const int length = line.textLength();
0372 
0373         height += option.fontMetrics.leading();
0374         if (height + line.height() + option.fontMetrics.lineSpacing() > maxHeight) {
0375             // Unfortunately, if the line ends because of a line separator, elidedText() will be too
0376             // clever and keep adding lines until it finds one that's too wide.
0377             if (line.naturalTextWidth() < maxWidth && text[start + length - 1] == QChar::LineSeparator) {
0378                 elided += QStringView(text).mid(start, length - 1);
0379             } else {
0380                 elided += option.fontMetrics.elidedText(text.mid(start), option.textElideMode, maxWidth);
0381             }
0382             break;
0383         } else if (line.naturalTextWidth() > maxWidth) {
0384             elided += option.fontMetrics.elidedText(text.mid(start, length), option.textElideMode, maxWidth);
0385             if (!elided.endsWith(QChar::LineSeparator)) {
0386                 elided += QChar::LineSeparator;
0387             }
0388         } else {
0389             elided += QStringView(text).mid(start, length);
0390         }
0391 
0392         height += line.height();
0393     }
0394 
0395     return elided;
0396 }
0397 
0398 void KFileItemDelegate::Private::setLayoutOptions(QTextLayout &layout, const QStyleOptionViewItem &option) const
0399 {
0400     QTextOption textoption;
0401     textoption.setTextDirection(option.direction);
0402     textoption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment));
0403     textoption.setWrapMode((option.features & QStyleOptionViewItem::WrapText) ? wrapMode : QTextOption::NoWrap);
0404 
0405     layout.setFont(option.font);
0406     layout.setTextOption(textoption);
0407 }
0408 
0409 QSize KFileItemDelegate::Private::displaySizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0410 {
0411     QString label = option.text;
0412     int maxWidth = 0;
0413     if (maximumSize.isEmpty()) {
0414         maxWidth = verticalLayout(option) && (option.features & QStyleOptionViewItem::WrapText) ? option.decorationSize.width() + 10 : 32757;
0415     } else {
0416         const Margin &itemMargin = activeMargins[ItemMargin];
0417         const Margin &textMargin = activeMargins[TextMargin];
0418         maxWidth = maximumSize.width() - (itemMargin.left + itemMargin.right) - (textMargin.left + textMargin.right);
0419     }
0420 
0421     KFileItem item = fileItem(index);
0422 
0423     // To compute the nominal size for the label + info, we'll just append
0424     // the information string to the label
0425     const QString info = information(option, index, item);
0426     if (!info.isEmpty()) {
0427         label += QChar(QChar::LineSeparator) + info;
0428     }
0429 
0430     QTextLayout layout;
0431     setLayoutOptions(layout, option);
0432 
0433     QSize size = layoutText(layout, label, maxWidth);
0434     if (!info.isEmpty()) {
0435         // As soon as additional information is shown, it might be necessary that
0436         // the label and/or the additional information must get elided. To prevent
0437         // an expensive eliding in the scope of displaySizeHint, the maximum
0438         // width is reserved instead.
0439         size.setWidth(maxWidth);
0440     }
0441 
0442     return addMargin(size, TextMargin);
0443 }
0444 
0445 QSize KFileItemDelegate::Private::decorationSizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0446 {
0447     if (index.column() > 0) {
0448         return QSize(0, 0);
0449     }
0450 
0451     QSize iconSize = option.icon.actualSize(option.decorationSize);
0452     if (!verticalLayout(option)) {
0453         iconSize.rwidth() = option.decorationSize.width();
0454     } else if (iconSize.width() < option.decorationSize.width()) {
0455         iconSize.rwidth() = qMin(iconSize.width() + 10, option.decorationSize.width());
0456     }
0457     if (iconSize.height() < option.decorationSize.height()) {
0458         iconSize.rheight() = option.decorationSize.height();
0459     }
0460 
0461     return addMargin(iconSize, IconMargin);
0462 }
0463 
0464 bool KFileItemDelegate::Private::verticalLayout(const QStyleOptionViewItem &option) const
0465 {
0466     return (option.decorationPosition == QStyleOptionViewItem::Top || option.decorationPosition == QStyleOptionViewItem::Bottom);
0467 }
0468 
0469 // Converts a QVariant of type Brush or Color to a QBrush
0470 QBrush KFileItemDelegate::Private::brush(const QVariant &value, const QStyleOptionViewItem &option) const
0471 {
0472     if (value.userType() == qMetaTypeId<KStatefulBrush>()) {
0473         return qvariant_cast<KStatefulBrush>(value).brush(option.palette);
0474     }
0475     switch (value.typeId()) {
0476     case QMetaType::QColor:
0477         return QBrush(qvariant_cast<QColor>(value));
0478 
0479     case QMetaType::QBrush:
0480         return qvariant_cast<QBrush>(value);
0481 
0482     default:
0483         return QBrush(Qt::NoBrush);
0484     }
0485 }
0486 
0487 QBrush KFileItemDelegate::Private::foregroundBrush(const QStyleOptionViewItem &option, const QModelIndex &index) const
0488 {
0489     QPalette::ColorGroup cg = QPalette::Active;
0490     if (!(option.state & QStyle::State_Enabled)) {
0491         cg = QPalette::Disabled;
0492     } else if (!(option.state & QStyle::State_Active)) {
0493         cg = QPalette::Inactive;
0494     }
0495 
0496     // Always use the highlight color for selected items
0497     if (option.state & QStyle::State_Selected) {
0498         return option.palette.brush(cg, QPalette::HighlightedText);
0499     }
0500 
0501     // If the model provides its own foreground color/brush for this item
0502     const QVariant value = index.data(Qt::ForegroundRole);
0503     if (value.isValid()) {
0504         return brush(value, option);
0505     }
0506 
0507     return option.palette.brush(cg, QPalette::Text);
0508 }
0509 
0510 bool KFileItemDelegate::Private::isListView(const QStyleOptionViewItem &option) const
0511 {
0512     if (qobject_cast<const QListView *>(option.widget) || verticalLayout(option)) {
0513         return true;
0514     }
0515 
0516     return false;
0517 }
0518 
0519 QPixmap KFileItemDelegate::Private::applyHoverEffect(const QPixmap &icon) const
0520 {
0521     KIconEffect *effect = KIconLoader::global()->iconEffect();
0522 
0523     // Note that in KIconLoader terminology, active = hover.
0524     // ### We're assuming that the icon group is desktop/filemanager, since this
0525     //     is KFileItemDelegate.
0526     if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) {
0527         return effect->apply(icon, KIconLoader::Desktop, KIconLoader::ActiveState);
0528     }
0529 
0530     return icon;
0531 }
0532 
0533 void KFileItemDelegate::Private::gotNewIcon(const QModelIndex &index)
0534 {
0535     animationHandler->gotNewIcon(index);
0536 }
0537 
0538 void KFileItemDelegate::Private::restartAnimation(KIO::AnimationState *state)
0539 {
0540     animationHandler->restartAnimation(state);
0541 }
0542 
0543 KIO::AnimationState *
0544 KFileItemDelegate::Private::animationState(const QStyleOptionViewItem &option, const QModelIndex &index, const QAbstractItemView *view) const
0545 {
0546     if (!option.widget->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, option.widget)) {
0547         return nullptr;
0548     }
0549 
0550     if (index.column() == KDirModel::Name) {
0551         return animationHandler->animationState(option, index, view);
0552     }
0553 
0554     return nullptr;
0555 }
0556 
0557 QPixmap KFileItemDelegate::Private::transition(const QPixmap &from, const QPixmap &to, qreal amount) const
0558 {
0559     int value = int(0xff * amount);
0560 
0561     if (value == 0 || to.isNull()) {
0562         return from;
0563     }
0564 
0565     if (value == 0xff || from.isNull()) {
0566         return to;
0567     }
0568 
0569     QColor color;
0570     color.setAlphaF(amount);
0571 
0572 // FIXME: Somehow this doesn't work on Mac OS..
0573 #if defined(Q_OS_MAC)
0574     const bool usePixmap = false;
0575 #else
0576     const bool usePixmap = from.paintEngine()->hasFeature(QPaintEngine::PorterDuff) && from.paintEngine()->hasFeature(QPaintEngine::BlendModes);
0577 #endif
0578 
0579     // If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus
0580     if (usePixmap) {
0581         QPixmap under = from;
0582         QPixmap over = to;
0583 
0584         QPainter p;
0585         p.begin(&over);
0586         p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0587         p.fillRect(over.rect(), color);
0588         p.end();
0589 
0590         p.begin(&under);
0591         p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
0592         p.fillRect(under.rect(), color);
0593         p.setCompositionMode(QPainter::CompositionMode_Plus);
0594         p.drawPixmap(0, 0, over);
0595         p.end();
0596 
0597         return under;
0598     } else {
0599         // Fall back to using QRasterPaintEngine to do the transition.
0600         QImage under = from.toImage();
0601         QImage over = to.toImage();
0602 
0603         QPainter p;
0604         p.begin(&over);
0605         p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0606         p.fillRect(over.rect(), color);
0607         p.end();
0608 
0609         p.begin(&under);
0610         p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
0611         p.fillRect(under.rect(), color);
0612         p.setCompositionMode(QPainter::CompositionMode_Plus);
0613         p.drawImage(0, 0, over);
0614         p.end();
0615 
0616         return QPixmap::fromImage(under);
0617     }
0618 }
0619 
0620 void KFileItemDelegate::Private::layoutTextItems(const QStyleOptionViewItem &option,
0621                                                  const QModelIndex &index,
0622                                                  QTextLayout *labelLayout,
0623                                                  QTextLayout *infoLayout,
0624                                                  QRect *textBoundingRect) const
0625 {
0626     KFileItem item = fileItem(index);
0627     const QString info = information(option, index, item);
0628     bool showInformation = false;
0629 
0630     setLayoutOptions(*labelLayout, option);
0631 
0632     const QRect textArea = labelRectangle(option, index);
0633     QRect textRect = subtractMargin(textArea, Private::TextMargin);
0634 
0635     // Sizes and constraints for the different text parts
0636     QSize maxLabelSize = textRect.size();
0637     QSize maxInfoSize = textRect.size();
0638     QSize labelSize;
0639     QSize infoSize;
0640 
0641     // If we have additional info text, and there's space for at least two lines of text,
0642     // adjust the max label size to make room for at least one line of the info text
0643     if (!info.isEmpty() && textRect.height() >= option.fontMetrics.lineSpacing() * 2) {
0644         infoLayout->setFont(labelLayout->font());
0645         infoLayout->setTextOption(labelLayout->textOption());
0646 
0647         maxLabelSize.rheight() -= option.fontMetrics.lineSpacing();
0648         showInformation = true;
0649     }
0650 
0651     // Lay out the label text, and adjust the max info size based on the label size
0652     labelSize = layoutText(*labelLayout, option, option.text, maxLabelSize);
0653     maxInfoSize.rheight() -= labelSize.height();
0654 
0655     // Lay out the info text
0656     if (showInformation) {
0657         infoSize = layoutText(*infoLayout, option, info, maxInfoSize);
0658     } else {
0659         infoSize = QSize(0, 0);
0660     }
0661 
0662     // Compute the bounding rect of the text
0663     const QSize size(qMax(labelSize.width(), infoSize.width()), labelSize.height() + infoSize.height());
0664     *textBoundingRect = QStyle::alignedRect(option.direction, option.displayAlignment, size, textRect);
0665 
0666     // Compute the positions where we should draw the layouts
0667     labelLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y()));
0668     infoLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y() + labelSize.height()));
0669 }
0670 
0671 void KFileItemDelegate::Private::drawTextItems(QPainter *painter,
0672                                                const QTextLayout &labelLayout,
0673                                                const QColor &labelColor,
0674                                                const QTextLayout &infoLayout,
0675                                                const QColor &infoColor,
0676                                                const QRect &boundingRect) const
0677 {
0678     if (shadowColor.alpha() > 0) {
0679         QPixmap pixmap(boundingRect.size());
0680         pixmap.fill(Qt::transparent);
0681 
0682         QPainter p(&pixmap);
0683         p.translate(-boundingRect.topLeft());
0684         p.setPen(labelColor);
0685         labelLayout.draw(&p, QPoint());
0686 
0687         if (!infoLayout.text().isEmpty()) {
0688             p.setPen(infoColor);
0689             infoLayout.draw(&p, QPoint());
0690         }
0691         p.end();
0692 
0693         int padding = qCeil(shadowBlur);
0694         int blurFactor = qRound(shadowBlur);
0695 
0696         QImage image(boundingRect.size() + QSize(padding * 2, padding * 2), QImage::Format_ARGB32_Premultiplied);
0697         image.fill(0);
0698         p.begin(&image);
0699         p.drawImage(padding, padding, pixmap.toImage());
0700         p.end();
0701 
0702         KIO::ImageFilter::shadowBlur(image, blurFactor, shadowColor);
0703 
0704         painter->drawImage(boundingRect.topLeft() - QPoint(padding, padding) + shadowOffset.toPoint(), image);
0705         painter->drawPixmap(boundingRect.topLeft(), pixmap);
0706         return;
0707     }
0708 
0709     painter->save();
0710     painter->setPen(labelColor);
0711 
0712     labelLayout.draw(painter, QPoint());
0713     if (!infoLayout.text().isEmpty()) {
0714         // TODO - for apps not doing funny things with the color palette,
0715         // KColorScheme::InactiveText would be a much more correct choice. We
0716         // should provide an API to specify what color to use for information.
0717         painter->setPen(infoColor);
0718         infoLayout.draw(painter, QPoint());
0719     }
0720 
0721     painter->restore();
0722 }
0723 
0724 void KFileItemDelegate::Private::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const
0725 {
0726     const KFileItem item = fileItem(index);
0727     bool updateFontMetrics = false;
0728 
0729     // Try to get the font from the model
0730     QVariant value = index.data(Qt::FontRole);
0731     if (value.isValid()) {
0732         option->font = qvariant_cast<QFont>(value).resolve(option->font);
0733         updateFontMetrics = true;
0734     }
0735 
0736     // Use an italic font for symlinks
0737     if (!item.isNull() && item.isLink()) {
0738         option->font.setItalic(true);
0739         updateFontMetrics = true;
0740     }
0741 
0742     if (updateFontMetrics) {
0743         option->fontMetrics = QFontMetrics(option->font);
0744     }
0745 
0746     // Try to get the alignment for the item from the model
0747     value = index.data(Qt::TextAlignmentRole);
0748     if (value.isValid()) {
0749         option->displayAlignment = Qt::Alignment(value.toInt());
0750     }
0751 
0752     value = index.data(Qt::BackgroundRole);
0753     if (value.isValid()) {
0754         option->backgroundBrush = brush(value, *option);
0755     }
0756 
0757     option->text = display(index);
0758     if (!option->text.isEmpty()) {
0759         option->features |= QStyleOptionViewItem::HasDisplay;
0760     }
0761 
0762     option->icon = decoration(*option, index);
0763     // Note that even null icons are still drawn for alignment
0764     if (!option->icon.isNull()) {
0765         option->features |= QStyleOptionViewItem::HasDecoration;
0766     }
0767 
0768     // ### Make sure this value is always true for now
0769     option->showDecorationSelected = true;
0770 }
0771 
0772 void KFileItemDelegate::Private::paintJobTransfers(QPainter *painter, const qreal &jobAnimationAngle, const QPoint &iconPos, const QStyleOptionViewItem &opt)
0773 {
0774     painter->save();
0775     QSize iconSize = opt.icon.actualSize(opt.decorationSize);
0776     QPixmap downArrow = downArrowIcon.pixmap(iconSize * 0.30);
0777     // corner (less x and y than bottom-right corner) that we will center the painter around
0778     QPoint bottomRightCorner = QPoint(iconPos.x() + iconSize.width() * 0.75, iconPos.y() + iconSize.height() * 0.60);
0779 
0780     QPainter pixmapPainter(&downArrow);
0781     // make the icon transparent and such
0782     pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0783     pixmapPainter.fillRect(downArrow.rect(), QColor(255, 255, 255, 110));
0784 
0785     painter->translate(bottomRightCorner);
0786 
0787     painter->drawPixmap(-downArrow.size().width() * .50, -downArrow.size().height() * .50, downArrow);
0788 
0789     // animate the circles by rotating the painter around the center point..
0790     painter->rotate(jobAnimationAngle);
0791     painter->setPen(QColor(20, 20, 20, 80));
0792     painter->setBrush(QColor(250, 250, 250, 90));
0793 
0794     int radius = iconSize.width() * 0.04;
0795     int spacing = radius * 4.5;
0796 
0797     // left
0798     painter->drawEllipse(QPoint(-spacing, 0), radius, radius);
0799     // right
0800     painter->drawEllipse(QPoint(spacing, 0), radius, radius);
0801     // up
0802     painter->drawEllipse(QPoint(0, -spacing), radius, radius);
0803     // down
0804     painter->drawEllipse(QPoint(0, spacing), radius, radius);
0805     painter->restore();
0806 }
0807 
0808 // ---------------------------------------------------------------------------
0809 
0810 KFileItemDelegate::KFileItemDelegate(QObject *parent)
0811     : QAbstractItemDelegate(parent)
0812     , d(new Private(this))
0813 {
0814     int focusHMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
0815     int focusVMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
0816 
0817     // Margins for horizontal mode (list views, tree views, table views)
0818     const int textMargin = focusHMargin * 4;
0819     if (QApplication::isRightToLeft()) {
0820         d->setHorizontalMargin(Private::TextMargin, textMargin, focusVMargin, focusHMargin, focusVMargin);
0821     } else {
0822         d->setHorizontalMargin(Private::TextMargin, focusHMargin, focusVMargin, textMargin, focusVMargin);
0823     }
0824 
0825     d->setHorizontalMargin(Private::IconMargin, focusHMargin, focusVMargin);
0826     d->setHorizontalMargin(Private::ItemMargin, 0, 0);
0827 
0828     // Margins for vertical mode (icon views)
0829     d->setVerticalMargin(Private::TextMargin, 6, 2);
0830     d->setVerticalMargin(Private::IconMargin, focusHMargin, focusVMargin);
0831     d->setVerticalMargin(Private::ItemMargin, 0, 0);
0832 
0833     setShowInformation(NoInformation);
0834 }
0835 
0836 KFileItemDelegate::~KFileItemDelegate() = default;
0837 
0838 QSize KFileItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0839 {
0840     // If the model wants to provide its own size hint for the item
0841     const QVariant value = index.data(Qt::SizeHintRole);
0842     if (value.isValid()) {
0843         return qvariant_cast<QSize>(value);
0844     }
0845 
0846     QStyleOptionViewItem opt(option);
0847     d->initStyleOption(&opt, index);
0848     d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
0849 
0850     const QSize displaySize = d->displaySizeHint(opt, index);
0851     const QSize decorationSize = d->decorationSizeHint(opt, index);
0852 
0853     QSize size;
0854 
0855     if (d->verticalLayout(opt)) {
0856         size.rwidth() = qMax(displaySize.width(), decorationSize.width());
0857         size.rheight() = decorationSize.height() + displaySize.height() + 1;
0858     } else {
0859         size.rwidth() = decorationSize.width() + displaySize.width() + 1;
0860         size.rheight() = qMax(decorationSize.height(), displaySize.height());
0861     }
0862 
0863     size = d->addMargin(size, Private::ItemMargin);
0864     if (!d->maximumSize.isEmpty()) {
0865         size = size.boundedTo(d->maximumSize);
0866     }
0867 
0868     return size;
0869 }
0870 
0871 QString KFileItemDelegate::Private::display(const QModelIndex &index) const
0872 {
0873     const QVariant value = index.data(Qt::DisplayRole);
0874 
0875     switch (value.typeId()) {
0876     case QMetaType::QString: {
0877         if (index.column() == KDirModel::Size) {
0878             return itemSize(index, fileItem(index));
0879         } else {
0880             const QString text = replaceNewlines(value.toString());
0881             return KStringHandler::preProcessWrap(text);
0882         }
0883     }
0884 
0885     case QMetaType::Double:
0886         return QLocale().toString(value.toDouble(), 'f');
0887 
0888     case QMetaType::Int:
0889     case QMetaType::UInt:
0890         return QLocale().toString(value.toInt());
0891 
0892     default:
0893         return QString();
0894     }
0895 }
0896 
0897 void KFileItemDelegate::setShowInformation(const InformationList &list)
0898 {
0899     d->informationList = list;
0900 }
0901 
0902 void KFileItemDelegate::setShowInformation(Information value)
0903 {
0904     if (value != NoInformation) {
0905         d->informationList = InformationList() << value;
0906     } else {
0907         d->informationList = InformationList();
0908     }
0909 }
0910 
0911 KFileItemDelegate::InformationList KFileItemDelegate::showInformation() const
0912 {
0913     return d->informationList;
0914 }
0915 
0916 void KFileItemDelegate::setShadowColor(const QColor &color)
0917 {
0918     d->shadowColor = color;
0919 }
0920 
0921 QColor KFileItemDelegate::shadowColor() const
0922 {
0923     return d->shadowColor;
0924 }
0925 
0926 void KFileItemDelegate::setShadowOffset(const QPointF &offset)
0927 {
0928     d->shadowOffset = offset;
0929 }
0930 
0931 QPointF KFileItemDelegate::shadowOffset() const
0932 {
0933     return d->shadowOffset;
0934 }
0935 
0936 void KFileItemDelegate::setShadowBlur(qreal factor)
0937 {
0938     d->shadowBlur = factor;
0939 }
0940 
0941 qreal KFileItemDelegate::shadowBlur() const
0942 {
0943     return d->shadowBlur;
0944 }
0945 
0946 void KFileItemDelegate::setMaximumSize(const QSize &size)
0947 {
0948     d->maximumSize = size;
0949 }
0950 
0951 QSize KFileItemDelegate::maximumSize() const
0952 {
0953     return d->maximumSize;
0954 }
0955 
0956 void KFileItemDelegate::setShowToolTipWhenElided(bool showToolTip)
0957 {
0958     d->showToolTipWhenElided = showToolTip;
0959 }
0960 
0961 bool KFileItemDelegate::showToolTipWhenElided() const
0962 {
0963     return d->showToolTipWhenElided;
0964 }
0965 
0966 void KFileItemDelegate::setWrapMode(QTextOption::WrapMode wrapMode)
0967 {
0968     d->wrapMode = wrapMode;
0969 }
0970 
0971 QTextOption::WrapMode KFileItemDelegate::wrapMode() const
0972 {
0973     return d->wrapMode;
0974 }
0975 
0976 QRect KFileItemDelegate::iconRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0977 {
0978     if (index.column() > 0) {
0979         return QRect(0, 0, 0, 0);
0980     }
0981     QStyleOptionViewItem opt(option);
0982     d->initStyleOption(&opt, index);
0983     return QRect(d->iconPosition(opt), opt.icon.actualSize(opt.decorationSize));
0984 }
0985 
0986 void KFileItemDelegate::setJobTransfersVisible(bool jobTransfersVisible)
0987 {
0988     d->downArrowIcon = QIcon::fromTheme(QStringLiteral("go-down"));
0989     d->jobTransfersVisible = jobTransfersVisible;
0990 }
0991 
0992 bool KFileItemDelegate::jobTransfersVisible() const
0993 {
0994     return d->jobTransfersVisible;
0995 }
0996 
0997 QIcon KFileItemDelegate::Private::decoration(const QStyleOptionViewItem &option, const QModelIndex &index) const
0998 {
0999     const QVariant value = index.data(Qt::DecorationRole);
1000     QIcon icon;
1001 
1002     switch (value.typeId()) {
1003     case QMetaType::QIcon:
1004         icon = qvariant_cast<QIcon>(value);
1005         break;
1006 
1007     case QMetaType::QPixmap:
1008         icon.addPixmap(qvariant_cast<QPixmap>(value));
1009         break;
1010 
1011     case QMetaType::QColor: {
1012         QPixmap pixmap(option.decorationSize);
1013         pixmap.fill(qvariant_cast<QColor>(value));
1014         icon.addPixmap(pixmap);
1015         break;
1016     }
1017 
1018     default:
1019         break;
1020     }
1021 
1022     return icon;
1023 }
1024 
1025 QRect KFileItemDelegate::Private::labelRectangle(const QStyleOptionViewItem &option, const QModelIndex &index) const
1026 {
1027     const QSize decoSize = (index.column() == 0) ? addMargin(option.decorationSize, Private::IconMargin) : QSize(0, 0);
1028     const QRect itemRect = subtractMargin(option.rect, Private::ItemMargin);
1029     QRect textArea(QPoint(0, 0), itemRect.size());
1030 
1031     switch (option.decorationPosition) {
1032     case QStyleOptionViewItem::Top:
1033         textArea.setTop(decoSize.height() + 1);
1034         break;
1035 
1036     case QStyleOptionViewItem::Bottom:
1037         textArea.setBottom(itemRect.height() - decoSize.height() - 1);
1038         break;
1039 
1040     case QStyleOptionViewItem::Left:
1041         textArea.setLeft(decoSize.width() + 1);
1042         break;
1043 
1044     case QStyleOptionViewItem::Right:
1045         textArea.setRight(itemRect.width() - decoSize.width() - 1);
1046         break;
1047     }
1048 
1049     textArea.translate(itemRect.topLeft());
1050     return QStyle::visualRect(option.direction, option.rect, textArea);
1051 }
1052 
1053 QPoint KFileItemDelegate::Private::iconPosition(const QStyleOptionViewItem &option) const
1054 {
1055     if (option.index.column() > 0) {
1056         return QPoint(0, 0);
1057     }
1058 
1059     const QRect itemRect = subtractMargin(option.rect, Private::ItemMargin);
1060     Qt::Alignment alignment;
1061 
1062     // Convert decorationPosition to the alignment the decoration will have in option.rect
1063     switch (option.decorationPosition) {
1064     case QStyleOptionViewItem::Top:
1065         alignment = Qt::AlignHCenter | Qt::AlignTop;
1066         break;
1067 
1068     case QStyleOptionViewItem::Bottom:
1069         alignment = Qt::AlignHCenter | Qt::AlignBottom;
1070         break;
1071 
1072     case QStyleOptionViewItem::Left:
1073         alignment = Qt::AlignVCenter | Qt::AlignLeft;
1074         break;
1075 
1076     case QStyleOptionViewItem::Right:
1077         alignment = Qt::AlignVCenter | Qt::AlignRight;
1078         break;
1079     }
1080 
1081     // Compute the nominal decoration rectangle
1082     const QSize size = addMargin(option.decorationSize, Private::IconMargin);
1083     const QRect rect = QStyle::alignedRect(option.direction, alignment, size, itemRect);
1084 
1085     // Position the icon in the center of the rectangle
1086     QRect iconRect = QRect(QPoint(), option.icon.actualSize(option.decorationSize));
1087     iconRect.moveCenter(rect.center());
1088 
1089     return iconRect.topLeft();
1090 }
1091 
1092 void KFileItemDelegate::Private::drawFocusRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const
1093 {
1094     if (!(option.state & QStyle::State_HasFocus)) {
1095         return;
1096     }
1097 
1098     QStyleOptionFocusRect opt;
1099     opt.direction = option.direction;
1100     opt.fontMetrics = option.fontMetrics;
1101     opt.palette = option.palette;
1102     opt.rect = rect;
1103     opt.state = option.state | QStyle::State_KeyboardFocusChange | QStyle::State_Item;
1104     opt.backgroundColor = option.palette.color(option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base);
1105 
1106     // Apparently some widget styles expect this hint to not be set
1107     painter->setRenderHint(QPainter::Antialiasing, false);
1108 
1109     QStyle *style = option.widget ? option.widget->style() : QApplication::style();
1110     style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget);
1111 
1112     painter->setRenderHint(QPainter::Antialiasing);
1113 }
1114 
1115 void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
1116 {
1117     if (!index.isValid()) {
1118         return;
1119     }
1120 
1121     QStyleOptionViewItem opt(option);
1122     d->initStyleOption(&opt, index);
1123     d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
1124 
1125     if (!(option.state & QStyle::State_Enabled)) {
1126         opt.palette.setCurrentColorGroup(QPalette::Disabled);
1127     }
1128 
1129     // Unset the mouse over bit if we're not drawing the first column
1130     if (index.column() > 0) {
1131         opt.state &= ~QStyle::State_MouseOver;
1132     } else {
1133         opt.viewItemPosition = QStyleOptionViewItem::OnlyOne;
1134     }
1135 
1136     const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget);
1137 
1138     // Check if the item is being animated
1139     // ========================================================================
1140     KIO::AnimationState *state = d->animationState(opt, index, view);
1141     KIO::CachedRendering *cache = nullptr;
1142     qreal progress = ((opt.state & QStyle::State_MouseOver) && index.column() == KDirModel::Name) ? 1.0 : 0.0;
1143     const QPoint iconPos = d->iconPosition(opt);
1144     QIcon::Mode iconMode;
1145 
1146     if (!(option.state & QStyle::State_Enabled)) {
1147         iconMode = QIcon::Disabled;
1148     } else if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) {
1149         iconMode = QIcon::Selected;
1150     } else {
1151         iconMode = QIcon::Normal;
1152     }
1153 
1154     QIcon::State iconState = option.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
1155     QPixmap icon = opt.icon.pixmap(opt.decorationSize, iconMode, iconState);
1156 
1157     const KFileItem fileItem = d->fileItem(index);
1158     if (fileItem.isHidden()) {
1159         KIconEffect::semiTransparent(icon);
1160     }
1161 
1162     if (state && !state->hasJobAnimation()) {
1163         cache = state->cachedRendering();
1164         progress = state->hoverProgress();
1165         // Clear the mouse over bit temporarily
1166         opt.state &= ~QStyle::State_MouseOver;
1167 
1168         // If we have a cached rendering, draw the item from the cache
1169         if (cache) {
1170             if (cache->checkValidity(opt.state) && cache->regular.size() == opt.rect.size()) {
1171                 QPixmap pixmap = d->transition(cache->regular, cache->hover, progress);
1172 
1173                 if (state->cachedRenderingFadeFrom() && state->fadeProgress() != 1.0) {
1174                     // Apply icon fading animation
1175                     KIO::CachedRendering *fadeFromCache = state->cachedRenderingFadeFrom();
1176                     const QPixmap fadeFromPixmap = d->transition(fadeFromCache->regular, fadeFromCache->hover, progress);
1177 
1178                     pixmap = d->transition(fadeFromPixmap, pixmap, state->fadeProgress());
1179                 }
1180                 painter->drawPixmap(option.rect.topLeft(), pixmap);
1181                 if (d->jobTransfersVisible && index.column() == 0) {
1182                     if (index.data(KDirModel::HasJobRole).toBool()) {
1183                         d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
1184                     }
1185                 }
1186                 return;
1187             }
1188 
1189             if (!cache->checkValidity(opt.state)) {
1190                 if (opt.widget->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, opt.widget)) {
1191                     // Fade over from the old icon to the new one
1192                     // Only start a new fade if the previous one is ready
1193                     // Else we may start racing when checkValidity() always returns false
1194                     if (state->fadeProgress() == 1) {
1195                         state->setCachedRenderingFadeFrom(state->takeCachedRendering());
1196                     }
1197                 }
1198                 d->gotNewIcon(index);
1199             }
1200             // If it wasn't valid, delete it
1201             state->setCachedRendering(nullptr);
1202         } else {
1203             // The cache may have been discarded, but the animation handler still needs to know about new icons
1204             d->gotNewIcon(index);
1205         }
1206     }
1207 
1208     // Compute the metrics, and lay out the text items
1209     // ========================================================================
1210     QColor labelColor = d->foregroundBrush(opt, index).color();
1211     QColor infoColor = labelColor;
1212     if (!(option.state & QStyle::State_Selected)) {
1213         // the code below is taken from Dolphin
1214         const QColor c2 = option.palette.base().color();
1215         const int p1 = 70;
1216         const int p2 = 100 - p1;
1217         infoColor = QColor((labelColor.red() * p1 + c2.red() * p2) / 100,
1218                            (labelColor.green() * p1 + c2.green() * p2) / 100,
1219                            (labelColor.blue() * p1 + c2.blue() * p2) / 100);
1220 
1221         if (fileItem.isHidden()) {
1222             labelColor = infoColor;
1223         }
1224     }
1225 
1226     // ### Apply the selection effect to the icon when the item is selected and
1227     //      showDecorationSelected is false.
1228 
1229     QTextLayout labelLayout;
1230     QTextLayout infoLayout;
1231     QRect textBoundingRect;
1232 
1233     d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
1234 
1235     QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
1236 
1237     int focusHMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
1238     int focusVMargin = style->pixelMetric(QStyle::PM_FocusFrameVMargin);
1239     QRect focusRect = textBoundingRect.adjusted(-focusHMargin, -focusVMargin, +focusHMargin, +focusVMargin);
1240 
1241     // Create a new cached rendering of a hovered and an unhovered item.
1242     // We don't create a new cache for a fully hovered item, since we don't
1243     // know yet if a hover out animation will be run.
1244     // ========================================================================
1245     if (state && (state->hoverProgress() < 1 || state->fadeProgress() < 1)) {
1246         const qreal dpr = painter->device()->devicePixelRatioF();
1247 
1248         cache = new KIO::CachedRendering(opt.state, option.rect.size(), index, dpr);
1249 
1250         QPainter p;
1251         p.begin(&cache->regular);
1252         p.translate(-option.rect.topLeft());
1253         p.setRenderHint(QPainter::Antialiasing);
1254         style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);
1255         p.drawPixmap(iconPos, icon);
1256         d->drawTextItems(&p, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
1257         d->drawFocusRect(&p, opt, focusRect);
1258         p.end();
1259 
1260         opt.state |= QStyle::State_MouseOver;
1261         icon = d->applyHoverEffect(icon);
1262 
1263         p.begin(&cache->hover);
1264         p.translate(-option.rect.topLeft());
1265         p.setRenderHint(QPainter::Antialiasing);
1266         style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);
1267         p.drawPixmap(iconPos, icon);
1268         d->drawTextItems(&p, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
1269         d->drawFocusRect(&p, opt, focusRect);
1270         p.end();
1271 
1272         state->setCachedRendering(cache);
1273 
1274         QPixmap pixmap = d->transition(cache->regular, cache->hover, progress);
1275 
1276         if (state->cachedRenderingFadeFrom() && state->fadeProgress() == 0) {
1277             // Apply icon fading animation
1278             KIO::CachedRendering *fadeFromCache = state->cachedRenderingFadeFrom();
1279             const QPixmap fadeFromPixmap = d->transition(fadeFromCache->regular, fadeFromCache->hover, progress);
1280 
1281             pixmap = d->transition(fadeFromPixmap, pixmap, state->fadeProgress());
1282 
1283             d->restartAnimation(state);
1284         }
1285 
1286         painter->drawPixmap(option.rect.topLeft(), pixmap);
1287         painter->setRenderHint(QPainter::Antialiasing);
1288         if (d->jobTransfersVisible && index.column() == 0) {
1289             if (index.data(KDirModel::HasJobRole).toBool()) {
1290                 d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
1291             }
1292         }
1293         return;
1294     }
1295 
1296     // Render the item directly if we're not using a cached rendering
1297     // ========================================================================
1298     painter->save();
1299     painter->setRenderHint(QPainter::Antialiasing);
1300 
1301     if (progress > 0 && !(opt.state & QStyle::State_MouseOver)) {
1302         opt.state |= QStyle::State_MouseOver;
1303         icon = d->applyHoverEffect(icon);
1304     }
1305 
1306     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
1307     painter->drawPixmap(iconPos, icon);
1308 
1309     d->drawTextItems(painter, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
1310     d->drawFocusRect(painter, opt, focusRect);
1311 
1312     if (d->jobTransfersVisible && index.column() == 0 && state) {
1313         if (index.data(KDirModel::HasJobRole).toBool()) {
1314             d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
1315         }
1316     }
1317     painter->restore();
1318 }
1319 
1320 QWidget *KFileItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
1321 {
1322     QStyleOptionViewItem opt(option);
1323     d->initStyleOption(&opt, index);
1324 
1325     QTextEdit *edit = new QTextEdit(parent);
1326     edit->setAcceptRichText(false);
1327     edit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1328     edit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1329     edit->setAlignment(opt.displayAlignment);
1330     edit->setEnabled(false); // Disable the text-edit to mark it as un-initialized
1331     return edit;
1332 }
1333 
1334 bool KFileItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
1335 {
1336     Q_UNUSED(event)
1337     Q_UNUSED(model)
1338     Q_UNUSED(option)
1339     Q_UNUSED(index)
1340 
1341     return false;
1342 }
1343 
1344 void KFileItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
1345 {
1346     QTextEdit *textedit = qobject_cast<QTextEdit *>(editor);
1347     Q_ASSERT(textedit != nullptr);
1348 
1349     // Do not update existing text that the user may already have edited.
1350     // The models will call setEditorData(..) whenever the icon has changed,
1351     // and this makes the editing work correctly despite that.
1352     if (textedit->isEnabled()) {
1353         return;
1354     }
1355     textedit->setEnabled(true); // Enable the text-edit to mark it as initialized
1356 
1357     const QVariant value = index.data(Qt::EditRole);
1358     const QString text = value.toString();
1359     textedit->insertPlainText(text);
1360     textedit->selectAll();
1361 
1362     QMimeDatabase db;
1363     const QString extension = db.suffixForFileName(text);
1364     if (!extension.isEmpty()) {
1365         // The filename contains an extension. Assure that only the filename
1366         // gets selected.
1367         const int selectionLength = text.length() - extension.length() - 1;
1368         QTextCursor cursor = textedit->textCursor();
1369         cursor.movePosition(QTextCursor::StartOfBlock);
1370         cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectionLength);
1371         textedit->setTextCursor(cursor);
1372     }
1373 }
1374 
1375 void KFileItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
1376 {
1377     QTextEdit *textedit = qobject_cast<QTextEdit *>(editor);
1378     Q_ASSERT(textedit != nullptr);
1379 
1380     model->setData(index, textedit->toPlainText(), Qt::EditRole);
1381 }
1382 
1383 void KFileItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
1384 {
1385     QStyleOptionViewItem opt(option);
1386     d->initStyleOption(&opt, index);
1387     d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
1388 
1389     QRect r = d->labelRectangle(opt, index);
1390 
1391     // Use the full available width for the editor when maximumSize is set
1392     if (!d->maximumSize.isEmpty()) {
1393         if (d->verticalLayout(option)) {
1394             int diff = qMax(r.width(), d->maximumSize.width()) - r.width();
1395             if (diff > 1) {
1396                 r.adjust(-(diff / 2), 0, diff / 2, 0);
1397             }
1398         } else {
1399             int diff = qMax(r.width(), d->maximumSize.width() - opt.decorationSize.width()) - r.width();
1400             if (diff > 0) {
1401                 if (opt.decorationPosition == QStyleOptionViewItem::Left) {
1402                     r.adjust(0, 0, diff, 0);
1403                 } else {
1404                     r.adjust(-diff, 0, 0, 0);
1405                 }
1406             }
1407         }
1408     }
1409 
1410     QTextEdit *textedit = qobject_cast<QTextEdit *>(editor);
1411     Q_ASSERT(textedit != nullptr);
1412     const int frame = textedit->frameWidth();
1413     r.adjust(-frame, -frame, frame, frame);
1414 
1415     editor->setGeometry(r);
1416 }
1417 
1418 bool KFileItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
1419 {
1420     Q_UNUSED(event)
1421     Q_UNUSED(view)
1422 
1423     // if the tooltip information the model keeps is different from the display information,
1424     // show it always
1425     const QVariant toolTip = index.data(Qt::ToolTipRole);
1426 
1427     if (!toolTip.isValid()) {
1428         return false;
1429     }
1430 
1431     if (index.data() != toolTip) {
1432         return QAbstractItemDelegate::helpEvent(event, view, option, index);
1433     }
1434 
1435     if (!d->showToolTipWhenElided) {
1436         return false;
1437     }
1438 
1439     // in the case the tooltip information is the same as the display information,
1440     // show it only in the case the display information is elided
1441     QStyleOptionViewItem opt(option);
1442     d->initStyleOption(&opt, index);
1443     d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
1444 
1445     QTextLayout labelLayout;
1446     QTextLayout infoLayout;
1447     QRect textBoundingRect;
1448     d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
1449     const QString elidedText = d->elidedText(labelLayout, opt, textBoundingRect.size());
1450 
1451     if (elidedText != d->display(index)) {
1452         return QAbstractItemDelegate::helpEvent(event, view, option, index);
1453     }
1454 
1455     return false;
1456 }
1457 
1458 QRegion KFileItemDelegate::shape(const QStyleOptionViewItem &option, const QModelIndex &index)
1459 {
1460     QStyleOptionViewItem opt(option);
1461     d->initStyleOption(&opt, index);
1462     d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
1463 
1464     QTextLayout labelLayout;
1465     QTextLayout infoLayout;
1466     QRect textBoundingRect;
1467     d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
1468 
1469     const QPoint pos = d->iconPosition(opt);
1470     QRect iconRect = QRect(pos, opt.icon.actualSize(opt.decorationSize));
1471 
1472     // Extend the icon rect so it touches the text rect
1473     switch (opt.decorationPosition) {
1474     case QStyleOptionViewItem::Top:
1475         if (iconRect.width() < textBoundingRect.width()) {
1476             iconRect.setBottom(textBoundingRect.top());
1477         } else {
1478             textBoundingRect.setTop(iconRect.bottom());
1479         }
1480         break;
1481     case QStyleOptionViewItem::Bottom:
1482         if (iconRect.width() < textBoundingRect.width()) {
1483             iconRect.setTop(textBoundingRect.bottom());
1484         } else {
1485             textBoundingRect.setBottom(iconRect.top());
1486         }
1487         break;
1488     case QStyleOptionViewItem::Left:
1489         iconRect.setRight(textBoundingRect.left());
1490         break;
1491     case QStyleOptionViewItem::Right:
1492         iconRect.setLeft(textBoundingRect.right());
1493         break;
1494     }
1495 
1496     QRegion region;
1497     region += iconRect;
1498     region += textBoundingRect;
1499     return region;
1500 }
1501 
1502 bool KFileItemDelegate::eventFilter(QObject *object, QEvent *event)
1503 {
1504     QTextEdit *editor = qobject_cast<QTextEdit *>(object);
1505     if (!editor) {
1506         return false;
1507     }
1508 
1509     switch (event->type()) {
1510     case QEvent::KeyPress: {
1511         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
1512         switch (keyEvent->key()) {
1513         case Qt::Key_Tab:
1514         case Qt::Key_Backtab:
1515             Q_EMIT commitData(editor);
1516             Q_EMIT closeEditor(editor, NoHint);
1517             return true;
1518 
1519         case Qt::Key_Enter:
1520         case Qt::Key_Return: {
1521             const QString text = editor->toPlainText();
1522             if (text.isEmpty() || (text == QLatin1Char('.')) || (text == QLatin1String(".."))) {
1523                 return true; // So a newline doesn't get inserted
1524             }
1525 
1526             Q_EMIT commitData(editor);
1527             Q_EMIT closeEditor(editor, SubmitModelCache);
1528             return true;
1529         }
1530 
1531         case Qt::Key_Escape:
1532             Q_EMIT closeEditor(editor, RevertModelCache);
1533             return true;
1534 
1535         default:
1536             return false;
1537         } // switch (keyEvent->key())
1538     } // case QEvent::KeyPress
1539 
1540     case QEvent::FocusOut: {
1541         const QWidget *w = QApplication::activePopupWidget();
1542         if (!w || w->parent() != editor) {
1543             Q_EMIT commitData(editor);
1544             Q_EMIT closeEditor(editor, NoHint);
1545             return true;
1546         } else {
1547             return false;
1548         }
1549     }
1550 
1551     default:
1552         return false;
1553     } // switch (event->type())
1554 }
1555 
1556 #include "moc_kfileitemdelegate.cpp"