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"