X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/a24327cd50ef17b953ecb908d260b73460158107..e0d9f9b9aad9313434597658d38c9b2c18382770:/src/kitemviews/kstandarditemlistwidget.cpp diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index 032a949c1..49d2f26bf 100644 --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -6,28 +6,33 @@ #include "kstandarditemlistwidget.h" +#include "dolphin_contentdisplaysettings.h" #include "kfileitemlistview.h" -#include "kfileitemmodel.h" #include "private/kfileitemclipboard.h" #include "private/kitemlistroleeditor.h" +#include "private/kitemviewsutils.h" #include "private/kpixmapmodifier.h" #include #include +#include #include #include +#include +#include #include #include #include -#include #include #include +#include +#include // #define KSTANDARDITEMLISTWIDGET_DEBUG -KStandardItemListWidgetInformant::KStandardItemListWidgetInformant() : - KItemListWidgetInformant() +KStandardItemListWidgetInformant::KStandardItemListWidgetInformant() + : KItemListWidgetInformant() { } @@ -35,9 +40,11 @@ KStandardItemListWidgetInformant::~KStandardItemListWidgetInformant() { } -void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const +void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector> &logicalHeightHints, + qreal &logicalWidthHint, + const KItemListView *view) const { - switch (static_cast(view)->itemLayout()) { + switch (static_cast(view)->itemLayout()) { case KStandardItemListView::IconsLayout: calculateIconsLayoutItemSizeHints(logicalHeightHints, logicalWidthHint, view); break; @@ -56,24 +63,22 @@ void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector& lo } } -qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArray& role, - int index, - const KItemListView* view) const +qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArray &role, int index, const KItemListView *view) const { const QHash values = view->model()->data(index); - const KItemListStyleOption& option = view->styleOption(); + const KItemListStyleOption &option = view->styleOption(); const QString text = roleText(role, values); qreal width = KStandardItemListWidget::columnPadding(option); - const QFontMetrics& normalFontMetrics = option.fontMetrics; + const QFontMetrics &normalFontMetrics = option.fontMetrics; const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font)); if (role == "rating") { width += KStandardItemListWidget::preferredRatingSize(option).width(); } else { // If current item is a link, we use the customized link font metrics instead of the normal font metrics. - const QFontMetrics& fontMetrics = itemIsLink(index, view) ? linkFontMetrics : normalFontMetrics; + const QFontMetrics &fontMetrics = itemIsLink(index, view) ? linkFontMetrics : normalFontMetrics; width += fontMetrics.horizontalAdvance(text); @@ -93,37 +98,47 @@ qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArra return width; } -QString KStandardItemListWidgetInformant::itemText(int index, const KItemListView* view) const +QString KStandardItemListWidgetInformant::itemText(int index, const KItemListView *view) const { return view->model()->data(index).value("text").toString(); } -bool KStandardItemListWidgetInformant::itemIsLink(int index, const KItemListView* view) const +bool KStandardItemListWidgetInformant::itemIsLink(int index, const KItemListView *view) const { Q_UNUSED(index) Q_UNUSED(view) return false; } -QString KStandardItemListWidgetInformant::roleText(const QByteArray& role, - const QHash& values) const +QString KStandardItemListWidgetInformant::roleText(const QByteArray &role, const QHash &values, ForUsageAs forUsageAs) const { if (role == "rating") { - // Always use an empty text, as the rating is shown by the image m_rating. - return QString(); + if (forUsageAs == ForUsageAs::DisplayedText) { + // Always use an empty text, as the rating is shown by the image m_rating. + return QString(); + } else { + const int rating{values.value(role).toInt()}; + // Check if there are half stars + if (rating % 2) { + return i18ncp("@accessible rating", "%1 and a half stars", "%1 and a half stars", rating / 2); + } + return i18ncp("@accessible rating", "%1 star", "%1 stars", rating / 2); + } } return values.value(role).toString(); } -QFont KStandardItemListWidgetInformant::customizedFontForLinks(const QFont& baseFont) const +QFont KStandardItemListWidgetInformant::customizedFontForLinks(const QFont &baseFont) const { return baseFont; } -void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const +void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector> &logicalHeightHints, + qreal &logicalWidthHint, + const KItemListView *view) const { - const KItemListStyleOption& option = view->styleOption(); - const QFont& normalFont = option.font; + const KItemListStyleOption &option = view->styleOption(); + const QFont &normalFont = option.font; const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); const qreal itemWidth = view->itemSize().width(); @@ -137,14 +152,14 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); for (int index = 0; index < logicalHeightHints.count(); ++index) { - if (logicalHeightHints.at(index) > 0.0) { + if (logicalHeightHints.at(index).first > 0.0) { continue; } // If the current item is a link, we use the customized link font instead of the normal font. - const QFont& font = itemIsLink(index, view) ? linkFont : normalFont; + const QFont &font = itemIsLink(index, view) ? linkFont : normalFont; - const QString& text = KStringHandler::preProcessWrap(itemText(index, view)); + const QString &text = KStringHandler::preProcessWrap(itemText(index, view)); // Calculate the number of lines required for wrapping the name qreal textHeight = 0; @@ -153,6 +168,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector layout.beginLayout(); QTextLine line; int lineCount = 0; + bool isElided = false; while ((line = layout.createLine()).isValid()) { line.setLineWidth(maxWidth); line.naturalTextWidth(); @@ -160,6 +176,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector ++lineCount; if (lineCount == option.maxTextLines) { + isElided = layout.createLine().isValid(); break; } } @@ -168,19 +185,22 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector // Add one line for each additional information textHeight += additionalRolesSpacing; - logicalHeightHints[index] = textHeight + spacingAndIconHeight; + logicalHeightHints[index].first = textHeight + spacingAndIconHeight; + logicalHeightHints[index].second = isElided; } logicalWidthHint = itemWidth; } -void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const +void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector> &logicalHeightHints, + qreal &logicalWidthHint, + const KItemListView *view) const { - const KItemListStyleOption& option = view->styleOption(); - const QFontMetrics& normalFontMetrics = option.fontMetrics; + const KItemListStyleOption &option = view->styleOption(); + const QFontMetrics &normalFontMetrics = option.fontMetrics; const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); - const QList& visibleRoles = view->visibleRoles(); + const QList &visibleRoles = view->visibleRoles(); const bool showOnlyTextRole = (visibleRoles.count() == 1) && (visibleRoles.first() == "text"); const qreal maxWidth = option.maxTextWidth; const qreal paddingAndIconWidth = option.padding * 4 + option.iconSize; @@ -189,12 +209,12 @@ void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVect const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font)); for (int index = 0; index < logicalHeightHints.count(); ++index) { - if (logicalHeightHints.at(index) > 0.0) { + if (logicalHeightHints.at(index).first > 0.0) { continue; } // If the current item is a link, we use the customized link font metrics instead of the normal font metrics. - const QFontMetrics& fontMetrics = itemIsLink(index, view) ? linkFontMetrics : normalFontMetrics; + const QFontMetrics &fontMetrics = itemIsLink(index, view) ? linkFontMetrics : normalFontMetrics; // For each row exactly one role is shown. Calculate the maximum required width that is necessary // to show all roles without horizontal clipping. @@ -203,9 +223,9 @@ void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVect if (showOnlyTextRole) { maximumRequiredWidth = fontMetrics.horizontalAdvance(itemText(index, view)); } else { - const QHash& values = view->model()->data(index); - for (const QByteArray& role : visibleRoles) { - const QString& text = roleText(role, values); + const QHash &values = view->model()->data(index); + for (const QByteArray &role : visibleRoles) { + const QString &text = roleText(role, values); const qreal requiredWidth = fontMetrics.horizontalAdvance(text); maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth); } @@ -216,47 +236,51 @@ void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVect width = maxWidth; } - logicalHeightHints[index] = width; + logicalHeightHints[index].first = width; } logicalWidthHint = height; } -void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const +void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector> &logicalHeightHints, + qreal &logicalWidthHint, + const KItemListView *view) const { - const KItemListStyleOption& option = view->styleOption(); + const KItemListStyleOption &option = view->styleOption(); const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height()); - logicalHeightHints.fill(height); + logicalHeightHints.fill(std::make_pair(height, false)); logicalWidthHint = -1.0; } -KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : - KItemListWidget(informant, parent), - m_isCut(false), - m_isHidden(false), - m_customizedFont(), - m_customizedFontMetrics(m_customizedFont), - m_isExpandable(false), - m_supportsItemExpanding(false), - m_dirtyLayout(true), - m_dirtyContent(true), - m_dirtyContentRoles(), - m_layout(IconsLayout), - m_pixmapPos(), - m_pixmap(), - m_scaledPixmapSize(), - m_iconRect(), - m_hoverPixmap(), - m_textInfo(), - m_textRect(), - m_sortedVisibleRoles(), - m_expansionArea(), - m_customTextColor(), - m_additionalInfoTextColor(), - m_overlay(), - m_rating(), - m_roleEditor(nullptr), - m_oldRoleEditor(nullptr) +KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant *informant, QGraphicsItem *parent) + : KItemListWidget(informant, parent) + , m_textInfo() + , m_isCut(false) + , m_isHidden(false) + , m_customizedFont() + , m_customizedFontMetrics(m_customizedFont) + , m_isExpandable(false) + , m_highlightEntireRow(false) + , m_supportsItemExpanding(false) + , m_dirtyLayout(true) + , m_dirtyContent(true) + , m_dirtyContentRoles() + , m_layout(IconsLayout) + , m_pixmapPos() + , m_pixmap() + , m_scaledPixmapSize() + , m_columnWidthSum() + , m_iconRect() + , m_hoverPixmap() + , m_textRect() + , m_sortedVisibleRoles() + , m_expansionArea() + , m_customTextColor() + , m_additionalInfoTextColor() + , m_overlays() + , m_rating() + , m_roleEditor(nullptr) + , m_oldRoleEditor(nullptr) { } @@ -284,9 +308,18 @@ void KStandardItemListWidget::setLayout(Layout layout) } } -KStandardItemListWidget::Layout KStandardItemListWidget::layout() const +void KStandardItemListWidget::setHighlightEntireRow(bool highlightEntireRow) { - return m_layout; + if (m_highlightEntireRow != highlightEntireRow) { + m_highlightEntireRow = highlightEntireRow; + m_dirtyLayout = true; + update(); + } +} + +bool KStandardItemListWidget::highlightEntireRow() const +{ + return m_highlightEntireRow; } void KStandardItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding) @@ -303,9 +336,9 @@ bool KStandardItemListWidget::supportsItemExpanding() const return m_supportsItemExpanding; } -void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - const_cast(this)->triggerCacheRefreshing(); + const_cast(this)->triggerCacheRefreshing(); KItemListWidget::paint(painter, option, widget); @@ -313,24 +346,39 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic drawSiblingsInformation(painter); } - const KItemListStyleOption& itemListStyleOption = styleOption(); - if (isHovered()) { + auto pixmap = isHovered() ? m_hoverPixmap : m_pixmap; + if (!m_overlays.isEmpty()) { + const qreal dpr = KItemViewsUtils::devicePixelRatio(this); + + const bool iconOnTop = (m_layout == IconsLayout); + const KItemListStyleOption &option = styleOption(); + const qreal padding = option.padding; + + const int widgetIconSize = iconSize(); + const int maxIconWidth = iconOnTop ? size().width() - 2 * padding : widgetIconSize; + const int maxIconHeight = widgetIconSize; + + pixmap = addOverlays(pixmap, m_overlays, QSize(maxIconWidth, maxIconHeight), dpr); + } + + const KItemListStyleOption &itemListStyleOption = styleOption(); + if (isHovered() && !pixmap.isNull()) { if (hoverOpacity() < 1.0) { /* - * Linear interpolation between m_pixmap and m_hoverPixmap. + * Linear interpolation between pixmap and m_hoverPixmap. * * Note that this cannot be achieved by painting m_hoverPixmap over - * m_pixmap, even if the opacities are adjusted. For details see + * pixmap, even if the opacities are adjusted. For details see * https://git.reviewboard.kde.org/r/109614/ */ - // Paint pixmap1 so that pixmap1 = m_pixmap * (1.0 - hoverOpacity()) - QPixmap pixmap1(m_pixmap.size()); - pixmap1.setDevicePixelRatio(m_pixmap.devicePixelRatio()); + // Paint pixmap1 so that pixmap1 = pixmap * (1.0 - hoverOpacity()) + QPixmap pixmap1(pixmap.size()); + pixmap1.setDevicePixelRatio(pixmap.devicePixelRatio()); pixmap1.fill(Qt::transparent); { QPainter p(&pixmap1); p.setOpacity(1.0 - hoverOpacity()); - p.drawPixmap(0, 0, m_pixmap); + p.drawPixmap(0, 0, pixmap); } // Paint pixmap2 so that pixmap2 = m_hoverPixmap * hoverOpacity() @@ -344,8 +392,8 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic } // Paint pixmap2 on pixmap1 using CompositionMode_Plus - // Now pixmap1 = pixmap2 + m_pixmap * (1.0 - hoverOpacity()) - // = m_hoverPixmap * hoverOpacity() + m_pixmap * (1.0 - hoverOpacity()) + // Now pixmap1 = pixmap2 + pixmap * (1.0 - hoverOpacity()) + // = m_hoverPixmap * hoverOpacity() + pixmap * (1.0 - hoverOpacity()) { QPainter p(&pixmap1); p.setCompositionMode(QPainter::CompositionMode_Plus); @@ -355,15 +403,15 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic // Finally paint pixmap1 on the widget drawPixmap(painter, pixmap1); } else { - drawPixmap(painter, m_hoverPixmap); + drawPixmap(painter, pixmap); } - } else { - drawPixmap(painter, m_pixmap); + } else if (!pixmap.isNull()) { + drawPixmap(painter, pixmap); } painter->setFont(m_customizedFont); - painter->setPen(textColor()); - const TextInfo* textInfo = m_textInfo.value("text"); + painter->setPen(textColor(*widget)); + const TextInfo *textInfo = m_textInfo.value("text"); if (!textInfo) { // It seems that we can end up here even if m_textInfo does not contain @@ -377,15 +425,26 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic painter->drawStaticText(textInfo->pos, textInfo->staticText); bool clipAdditionalInfoBounds = false; - if (m_supportsItemExpanding) { - // Prevent a possible overlapping of the additional-information texts - // with the icon. This can happen if the user has minimized the width - // of the name-column to a very small value. - const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding; - if (textInfo->pos.x() + columnWidth("text") > minX) { - clipAdditionalInfoBounds = true; - painter->save(); - painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip); + if (m_supportsItemExpanding && m_sortedVisibleRoles.count() > 1) { + // Prevent a possible overlapping of the additional-information-texts with the icon. + // This will happen if the user has resized the width of the name-column to be very narrow while having folders expanded. + // We only want to draw additional info text outside the area of the icon or expansion area, so we set a clip rect that does not contain the icon area. + // This needs to work both for left-to-right as well as right-to-left layout directions. + const TextInfo *potentiallyOverlappingRoleText = m_textInfo.value(m_sortedVisibleRoles[1]); // Only the first column after the name column can overlap. + if (layoutDirection() == Qt::LeftToRight) { // In left-to-right languages the left end of text would overlap. This is mirrored for right-to-left. + const qreal minX = m_iconRect.right() + 2 * itemListStyleOption.padding; + if (potentiallyOverlappingRoleText->pos.x() < minX) { + clipAdditionalInfoBounds = true; + painter->save(); + painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip); + } + } else { + const qreal maxX = m_iconRect.left() - 2 * itemListStyleOption.padding; + if (potentiallyOverlappingRoleText->pos.x() + m_customizedFontMetrics.horizontalAdvance(potentiallyOverlappingRoleText->staticText.text()) > maxX) { + clipAdditionalInfoBounds = true; + painter->save(); + painter->setClipRect(0, 0, maxX, size().height(), Qt::IntersectClip); + } } } @@ -393,16 +452,16 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic painter->setFont(m_customizedFont); for (int i = 1; i < m_sortedVisibleRoles.count(); ++i) { - const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles[i]); + const TextInfo *textInfo = m_textInfo.value(m_sortedVisibleRoles[i]); painter->drawStaticText(textInfo->pos, textInfo->staticText); } if (!m_rating.isNull()) { - const TextInfo* ratingTextInfo = m_textInfo.value("rating"); + const TextInfo *ratingTextInfo = m_textInfo.value("rating"); QPointF pos = ratingTextInfo->pos; const Qt::Alignment align = ratingTextInfo->staticText.textOption().alignment(); if (align & Qt::AlignHCenter) { - pos.rx() += (size().width() - m_rating.width()) / 2 - 2; + pos.rx() += (size().width() - m_rating.width() / m_rating.devicePixelRatioF()) / 2 - 2; } painter->drawPixmap(pos, m_rating); } @@ -427,13 +486,13 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic QRectF KStandardItemListWidget::iconRect() const { - const_cast(this)->triggerCacheRefreshing(); + const_cast(this)->triggerCacheRefreshing(); return m_iconRect; } QRectF KStandardItemListWidget::textRect() const { - const_cast(this)->triggerCacheRefreshing(); + const_cast(this)->triggerCacheRefreshing(); return m_textRect; } @@ -444,13 +503,13 @@ QRectF KStandardItemListWidget::textFocusRect() const // when having a quite large icon size but only one line of text. Still the // focus rectangle should be shown as narrow as possible around the text. - const_cast(this)->triggerCacheRefreshing(); + const_cast(this)->triggerCacheRefreshing(); switch (m_layout) { case CompactLayout: { QRectF rect = m_textRect; - const TextInfo* topText = m_textInfo.value(m_sortedVisibleRoles.first()); - const TextInfo* bottomText = m_textInfo.value(m_sortedVisibleRoles.last()); + const TextInfo *topText = m_textInfo.value(m_sortedVisibleRoles.first()); + const TextInfo *bottomText = m_textInfo.value(m_sortedVisibleRoles.last()); rect.setTop(topText->pos.y()); rect.setBottom(bottomText->pos.y() + bottomText->staticText.size().height()); return rect; @@ -458,11 +517,11 @@ QRectF KStandardItemListWidget::textFocusRect() const case DetailsLayout: { QRectF rect = m_textRect; - const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles.first()); + const TextInfo *textInfo = m_textInfo.value(m_sortedVisibleRoles.first()); rect.setTop(textInfo->pos.y()); rect.setBottom(textInfo->pos.y() + textInfo->staticText.size().height()); - const KItemListStyleOption& option = styleOption(); + const KItemListStyleOption &option = styleOption(); if (option.extendedSelectionRegion) { const QString text = textInfo->staticText.text(); rect.setWidth(m_customizedFontMetrics.horizontalAdvance(text) + 2 * option.padding); @@ -480,7 +539,7 @@ QRectF KStandardItemListWidget::textFocusRect() const QRectF KStandardItemListWidget::selectionRect() const { - const_cast(this)->triggerCacheRefreshing(); + const_cast(this)->triggerCacheRefreshing(); switch (m_layout) { case IconsLayout: @@ -490,7 +549,15 @@ QRectF KStandardItemListWidget::selectionRect() const case DetailsLayout: { const int padding = styleOption().padding; QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); - return adjustedIconRect | m_textRect; + QRectF result = adjustedIconRect | m_textRect; + if (m_highlightEntireRow) { + if (layoutDirection() == Qt::LeftToRight) { + result.setRight(leftPadding() + m_columnWidthSum); + } else { + result.setLeft(size().width() - m_columnWidthSum - rightPadding()); + } + } + return result; } default: @@ -503,24 +570,24 @@ QRectF KStandardItemListWidget::selectionRect() const QRectF KStandardItemListWidget::expansionToggleRect() const { - const_cast(this)->triggerCacheRefreshing(); + const_cast(this)->triggerCacheRefreshing(); return m_isExpandable ? m_expansionArea : QRectF(); } QRectF KStandardItemListWidget::selectionToggleRect() const { - const_cast(this)->triggerCacheRefreshing(); - - const int iconHeight = styleOption().iconSize; + const_cast(this)->triggerCacheRefreshing(); + const QRectF widgetIconRect = iconRect(); + const int widgetIconSize = iconSize(); int toggleSize = KIconLoader::SizeSmall; - if (iconHeight >= KIconLoader::SizeEnormous) { + if (widgetIconSize >= KIconLoader::SizeEnormous) { toggleSize = KIconLoader::SizeMedium; - } else if (iconHeight >= KIconLoader::SizeLarge) { + } else if (widgetIconSize >= KIconLoader::SizeLarge) { toggleSize = KIconLoader::SizeSmallMedium; } - QPointF pos = iconRect().topLeft(); + QPointF pos = widgetIconRect.topLeft(); // If the selection toggle has a very small distance to the // widget borders, the size of the selection toggle will get @@ -541,11 +608,14 @@ QRectF KStandardItemListWidget::selectionToggleRect() const pos.setX(0); } + if (QApplication::isRightToLeft()) { + pos.setX(widgetIconRect.right() - (pos.x() + toggleSize - widgetIconRect.left())); + } + return QRectF(pos, QSizeF(toggleSize, toggleSize)); } -QPixmap KStandardItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem* option, - QWidget* widget) +QPixmap KStandardItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem *option, QWidget *widget) { QPixmap pixmap = KItemListWidget::createDragPixmap(option, widget); if (m_layout != DetailsLayout) { @@ -555,10 +625,8 @@ QPixmap KStandardItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem // Only return the content of the text-column as pixmap const int leftClip = m_pixmapPos.x(); - const TextInfo* textInfo = m_textInfo.value("text"); - const int rightClip = textInfo->pos.x() + - textInfo->staticText.size().width() + - 2 * styleOption().padding; + const TextInfo *textInfo = m_textInfo.value("text"); + const int rightClip = textInfo->pos.x() + textInfo->staticText.size().width() + 2 * styleOption().padding; QPixmap clippedPixmap(rightClip - leftClip + 1, pixmap.height()); clippedPixmap.fill(Qt::transparent); @@ -569,8 +637,56 @@ QPixmap KStandardItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem return clippedPixmap; } +void KStandardItemListWidget::startActivateSoonAnimation(int timeUntilActivation) +{ + if (m_activateSoonAnimation) { + m_activateSoonAnimation->stop(); // automatically DeleteWhenStopped + } + + m_activateSoonAnimation = new QVariantAnimation{this}; + m_activateSoonAnimation->setStartValue(0.0); + m_activateSoonAnimation->setEndValue(1.0); + m_activateSoonAnimation->setDuration(timeUntilActivation); + + const QVariant originalIconName{value("iconName")}; + connect(m_activateSoonAnimation, &QVariantAnimation::valueChanged, this, [originalIconName, this](const QVariant &value) { + auto progress = value.toFloat(); + + QVariant wantedIconName; + if (progress < 0.333) { + wantedIconName = "folder-open"; + } else if (progress < 0.666) { + wantedIconName = originalIconName; + } else { + wantedIconName = "folder-open"; + } -KItemListWidgetInformant* KStandardItemListWidget::createInformant() + QHash itemData{data()}; + if (itemData["iconName"] != wantedIconName) { + itemData.insert("iconName", wantedIconName); + setData(itemData); + invalidateIconCache(); + } + }); + + connect(m_activateSoonAnimation, &QObject::destroyed, this, [originalIconName, this]() { + QHash itemData{data()}; + if (itemData["iconName"] == "folder-open") { + itemData.insert("iconName", originalIconName); + setData(itemData); + invalidateIconCache(); + } + }); + + m_activateSoonAnimation->start(QAbstractAnimation::DeleteWhenStopped); +} + +bool KStandardItemListWidget::isIconControlledByActivateSoonAnimation() const +{ + return m_activateSoonAnimation && value("iconName") == "folder-open"; +} + +KItemListWidgetInformant *KStandardItemListWidget::createInformant() { return new KStandardItemListWidgetInformant(); } @@ -581,11 +697,17 @@ void KStandardItemListWidget::invalidateCache() m_dirtyContent = true; } +void KStandardItemListWidget::invalidateIconCache() +{ + m_dirtyContent = true; + m_dirtyContentRoles.insert("iconPixmap"); +} + void KStandardItemListWidget::refreshCache() { } -bool KStandardItemListWidget::isRoleRightAligned(const QByteArray& role) const +bool KStandardItemListWidget::isRoleRightAligned(const QByteArray &role) const { Q_UNUSED(role) return false; @@ -596,7 +718,7 @@ bool KStandardItemListWidget::isHidden() const return false; } -QFont KStandardItemListWidget::customizedFont(const QFont& baseFont) const +QFont KStandardItemListWidget::customizedFont(const QFont &baseFont) const { return baseFont; } @@ -606,7 +728,7 @@ QPalette::ColorRole KStandardItemListWidget::normalTextColorRole() const return QPalette::Text; } -void KStandardItemListWidget::setTextColor(const QColor& color) +void KStandardItemListWidget::setTextColor(const QColor &color) { if (color != m_customTextColor) { m_customTextColor = color; @@ -615,7 +737,7 @@ void KStandardItemListWidget::setTextColor(const QColor& color) } } -QColor KStandardItemListWidget::textColor() const +QColor KStandardItemListWidget::textColor(const QWidget &widget) const { if (!isSelected()) { if (m_isHidden) { @@ -625,32 +747,34 @@ QColor KStandardItemListWidget::textColor() const } } - const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; + const QPalette::ColorGroup group = isActiveWindow() && widget.hasFocus() ? QPalette::Active : QPalette::Inactive; const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : normalTextColorRole(); return styleOption().palette.color(group, role); } -void KStandardItemListWidget::setOverlay(const QPixmap& overlay) +void KStandardItemListWidget::setOverlays(QHash &overlays) { - m_overlay = overlay; + if (overlays == m_overlays) { + return; + } + + m_overlays = overlays; m_dirtyContent = true; + m_dirtyContentRoles.insert("iconOverlays"); update(); } -QPixmap KStandardItemListWidget::overlay() const +QHash KStandardItemListWidget::overlays() const { - return m_overlay; + return m_overlays; } - -QString KStandardItemListWidget::roleText(const QByteArray& role, - const QHash& values) const +QString KStandardItemListWidget::roleText(const QByteArray &role, const QHash &values) const { - return static_cast(informant())->roleText(role, values); + return static_cast(informant())->roleText(role, values); } -void KStandardItemListWidget::dataChanged(const QHash& current, - const QSet& roles) +void KStandardItemListWidget::dataChanged(const QHash ¤t, const QSet &roles) { Q_UNUSED(current) @@ -666,7 +790,7 @@ void KStandardItemListWidget::dataChanged(const QHash& cur // The URL might have changed (i.e., if the sort order of the items has // been changed). Therefore, the "is cut" state must be updated. - KFileItemClipboard* clipboard = KFileItemClipboard::instance(); + KFileItemClipboard *clipboard = KFileItemClipboard::instance(); const QUrl itemUrl = data().value("url").toUrl(); m_isCut = clipboard->isCut(itemUrl); @@ -677,22 +801,19 @@ void KStandardItemListWidget::dataChanged(const QHash& cur QSetIterator it(dirtyRoles); while (it.hasNext()) { - const QByteArray& role = it.next(); + const QByteArray &role = it.next(); m_dirtyContentRoles.insert(role); } } -void KStandardItemListWidget::visibleRolesChanged(const QList& current, - const QList& previous) +void KStandardItemListWidget::visibleRolesChanged(const QList ¤t, const QList &previous) { Q_UNUSED(previous) m_sortedVisibleRoles = current; m_dirtyLayout = true; } -void KStandardItemListWidget::columnWidthChanged(const QByteArray& role, - qreal current, - qreal previous) +void KStandardItemListWidget::columnWidthChanged(const QByteArray &role, qreal current, qreal previous) { Q_UNUSED(role) Q_UNUSED(current) @@ -700,18 +821,26 @@ void KStandardItemListWidget::columnWidthChanged(const QByteArray& role, m_dirtyLayout = true; } -void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption& current, - const KItemListStyleOption& previous) +void KStandardItemListWidget::sidePaddingChanged(qreal leftPaddingWidth, qreal rightPaddingWidth) { - Q_UNUSED(current) - Q_UNUSED(previous) + Q_UNUSED(leftPaddingWidth) + Q_UNUSED(rightPaddingWidth) + m_dirtyLayout = true; +} + +void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption ¤t, const KItemListStyleOption &previous) +{ + KItemListWidget::styleOptionChanged(current, previous); + updateAdditionalInfoTextColor(); m_dirtyLayout = true; } void KStandardItemListWidget::hoveredChanged(bool hovered) { - Q_UNUSED(hovered) + if (!hovered && m_activateSoonAnimation) { + m_activateSoonAnimation->stop(); // automatically DeleteWhenStopped + } m_dirtyLayout = true; } @@ -722,48 +851,48 @@ void KStandardItemListWidget::selectedChanged(bool selected) m_dirtyContent = true; } -void KStandardItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous) +void KStandardItemListWidget::siblingsInformationChanged(const QBitArray ¤t, const QBitArray &previous) { Q_UNUSED(current) Q_UNUSED(previous) m_dirtyLayout = true; } -int KStandardItemListWidget::selectionLength(const QString& text) const +int KStandardItemListWidget::numberOfUnicodeCharactersIn(const QString &text) +{ + int count = 0; + QTextBoundaryFinder boundaryFinder(QTextBoundaryFinder::Grapheme, text); + while (boundaryFinder.toNextBoundary() != -1) { + ++count; + } + return count; +} + +int KStandardItemListWidget::selectionLength(const QString &text) const { - return text.length(); + return numberOfUnicodeCharactersIn(text); } -void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const QByteArray& previous) +void KStandardItemListWidget::editedRoleChanged(const QByteArray ¤t, const QByteArray &previous) { Q_UNUSED(previous) - QGraphicsView* parent = scene()->views()[0]; + QGraphicsView *parent = scene()->views()[0]; if (current.isEmpty() || !parent || current != "text") { if (m_roleEditor) { - emit roleEditingCanceled(index(), current, data().value(current)); - - disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, - this, &KStandardItemListWidget::slotRoleEditingCanceled); - disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, - this, &KStandardItemListWidget::slotRoleEditingFinished); - - if (m_oldRoleEditor) { - m_oldRoleEditor->deleteLater(); - } - m_oldRoleEditor = m_roleEditor; - m_roleEditor->hide(); - m_roleEditor = nullptr; + Q_EMIT roleEditingCanceled(index(), current, data().value(current)); + closeRoleEditor(); } return; } Q_ASSERT(!m_roleEditor); - const TextInfo* textInfo = m_textInfo.value("text"); + const TextInfo *textInfo = m_textInfo.value("text"); m_roleEditor = new KItemListRoleEditor(parent); m_roleEditor->setRole(current); + m_roleEditor->setAllowUpDownKeyChainEdit(m_layout != IconsLayout); m_roleEditor->setFont(styleOption().font); const QString text = data().value(current).toString(); @@ -781,10 +910,8 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const m_roleEditor->setTextCursor(cursor); } - connect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, - this, &KStandardItemListWidget::slotRoleEditingCanceled); - connect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, - this, &KStandardItemListWidget::slotRoleEditingFinished); + connect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, this, &KStandardItemListWidget::slotRoleEditingCanceled); + connect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, this, &KStandardItemListWidget::slotRoleEditingFinished); // Adjust the geometry of the editor QRectF rect = roleEditingRect(current); @@ -795,11 +922,21 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const rect.setWidth(parent->width() - rect.left()); } m_roleEditor->setGeometry(rect.toRect()); + m_roleEditor->autoAdjustSize(); m_roleEditor->show(); m_roleEditor->setFocus(); } -void KStandardItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) +void KStandardItemListWidget::iconSizeChanged(int current, int previous) +{ + KItemListWidget::iconSizeChanged(current, previous); + + invalidateIconCache(); + triggerCacheRefreshing(); + update(); +} + +void KStandardItemListWidget::resizeEvent(QGraphicsSceneResizeEvent *event) { if (m_roleEditor) { setEditedRole(QByteArray()); @@ -811,32 +948,29 @@ void KStandardItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) m_dirtyLayout = true; } -void KStandardItemListWidget::showEvent(QShowEvent* event) +void KStandardItemListWidget::showEvent(QShowEvent *event) { KItemListWidget::showEvent(event); // Listen to changes of the clipboard to mark the item as cut/uncut - KFileItemClipboard* clipboard = KFileItemClipboard::instance(); + KFileItemClipboard *clipboard = KFileItemClipboard::instance(); const QUrl itemUrl = data().value("url").toUrl(); m_isCut = clipboard->isCut(itemUrl); - connect(clipboard, &KFileItemClipboard::cutItemsChanged, - this, &KStandardItemListWidget::slotCutItemsChanged); + connect(clipboard, &KFileItemClipboard::cutItemsChanged, this, &KStandardItemListWidget::slotCutItemsChanged); } -void KStandardItemListWidget::hideEvent(QHideEvent* event) +void KStandardItemListWidget::hideEvent(QHideEvent *event) { - disconnect(KFileItemClipboard::instance(), &KFileItemClipboard::cutItemsChanged, - this, &KStandardItemListWidget::slotCutItemsChanged); + disconnect(KFileItemClipboard::instance(), &KFileItemClipboard::cutItemsChanged, this, &KStandardItemListWidget::slotCutItemsChanged); KItemListWidget::hideEvent(event); } bool KStandardItemListWidget::event(QEvent *event) { - if (event->type() == QEvent::WindowDeactivate || event->type() == QEvent::WindowActivate - || event->type() == QEvent::PaletteChange) { + if (event->type() == QEvent::WindowDeactivate || event->type() == QEvent::WindowActivate || event->type() == QEvent::PaletteChange) { m_dirtyContent = true; } @@ -862,19 +996,17 @@ void KStandardItemListWidget::slotCutItemsChanged() } } -void KStandardItemListWidget::slotRoleEditingCanceled(const QByteArray& role, - const QVariant& value) +void KStandardItemListWidget::slotRoleEditingCanceled(const QByteArray &role, const QVariant &value) { closeRoleEditor(); - emit roleEditingCanceled(index(), role, value); + Q_EMIT roleEditingCanceled(index(), role, value); setEditedRole(QByteArray()); } -void KStandardItemListWidget::slotRoleEditingFinished(const QByteArray& role, - const QVariant& value) +void KStandardItemListWidget::slotRoleEditingFinished(const QByteArray &role, const QVariant &value) { closeRoleEditor(); - emit roleEditingFinished(index(), role, value); + Q_EMIT roleEditingFinished(index(), role, value); setEditedRole(QByteArray()); } @@ -891,10 +1023,14 @@ void KStandardItemListWidget::triggerCacheRefreshing() m_isHidden = isHidden(); m_customizedFont = customizedFont(styleOption().font); m_customizedFontMetrics = QFontMetrics(m_customizedFont); + m_columnWidthSum = std::accumulate(m_sortedVisibleRoles.begin(), m_sortedVisibleRoles.end(), qreal(), [this](qreal sum, const auto &role) { + return sum + columnWidth(role); + }); updateExpansionArea(); updateTextsCache(); updatePixmapCache(); + clearHoverCache(); m_dirtyLayout = false; m_dirtyContent = false; @@ -907,12 +1043,18 @@ void KStandardItemListWidget::updateExpansionArea() const QHash values = data(); const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); if (expandedParentsCount >= 0) { - const KItemListStyleOption& option = styleOption(); + const int widgetIconSize = iconSize(); const qreal widgetHeight = size().height(); - const qreal inc = (widgetHeight - option.iconSize) / 2; + const qreal inc = (widgetHeight - widgetIconSize) / 2; const qreal x = expandedParentsCount * widgetHeight + inc; const qreal y = inc; - m_expansionArea = QRectF(x, y, option.iconSize, option.iconSize); + if (layoutDirection() == Qt::LeftToRight) { + const qreal leftPaddingWidth = m_highlightEntireRow ? leftPadding() : 0; + m_expansionArea = QRectF(leftPaddingWidth + x, y, widgetIconSize, widgetIconSize); + return; + } + const qreal rightPaddingWidth = m_highlightEntireRow ? rightPadding() : 0; + m_expansionArea = QRectF(size().width() - rightPaddingWidth - x - widgetIconSize, y, widgetIconSize, widgetIconSize); return; } } @@ -927,24 +1069,51 @@ void KStandardItemListWidget::updatePixmapCache() const QSizeF widgetSize = size(); const bool iconOnTop = (m_layout == IconsLayout); - const KItemListStyleOption& option = styleOption(); + const KItemListStyleOption &option = styleOption(); const qreal padding = option.padding; + const qreal dpr = KItemViewsUtils::devicePixelRatio(this); - const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : option.iconSize; - const int maxIconHeight = option.iconSize; + const int widgetIconSize = iconSize(); + const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : widgetIconSize; + const int maxIconHeight = widgetIconSize; const QHash values = data(); bool updatePixmap = (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight); if (!updatePixmap && m_dirtyContent) { - updatePixmap = m_dirtyContentRoles.isEmpty() - || m_dirtyContentRoles.contains("iconPixmap") - || m_dirtyContentRoles.contains("iconName") - || m_dirtyContentRoles.contains("iconOverlays"); + updatePixmap = m_dirtyContentRoles.isEmpty() || m_dirtyContentRoles.contains("iconPixmap") || m_dirtyContentRoles.contains("iconName") + || m_dirtyContentRoles.contains("iconOverlays"); } if (updatePixmap) { - m_pixmap = values["iconPixmap"].value(); + m_pixmap = QPixmap(); + + int sequenceIndex = hoverSequenceIndex(); + + if (values.contains("hoverSequencePixmaps") && !isIconControlledByActivateSoonAnimation()) { + // Use one of the hover sequence pixmaps instead of the default + // icon pixmap. + + const QVector pixmaps = values["hoverSequencePixmaps"].value>(); + + if (values.contains("hoverSequenceWraparoundPoint")) { + const float wap = values["hoverSequenceWraparoundPoint"].toFloat(); + if (wap >= 1.0f) { + sequenceIndex %= static_cast(wap); + } + } + + const int loadedIndex = qMax(qMin(sequenceIndex, pixmaps.size() - 1), 0); + + if (loadedIndex != 0) { + m_pixmap = pixmaps[loadedIndex]; + } + } + + if (m_pixmap.isNull() && !isIconControlledByActivateSoonAnimation()) { + m_pixmap = values["iconPixmap"].value(); + } + if (m_pixmap.isNull()) { // Use the icon that fits to the MIME-type QString iconName = values["iconName"].toString(); @@ -953,18 +1122,26 @@ void KStandardItemListWidget::updatePixmapCache() // use a generic icon as fallback iconName = QStringLiteral("unknown"); } - const QStringList overlays = values["iconOverlays"].toStringList(); - m_pixmap = pixmapForIcon(iconName, overlays, maxIconHeight, m_layout != IconsLayout && isActiveWindow() && isSelected() ? QIcon::Selected : QIcon::Normal); + const bool hasFocus = scene()->views()[0]->parentWidget()->hasFocus(); + m_pixmap = pixmapForIcon(iconName, + QSize(maxIconWidth, maxIconHeight), + m_layout != IconsLayout && isActiveWindow() && isSelected() && hasFocus ? QIcon::Selected : QIcon::Normal); - } else if (m_pixmap.width() / m_pixmap.devicePixelRatio() != maxIconWidth || m_pixmap.height() / m_pixmap.devicePixelRatio() != maxIconHeight) { - // A custom pixmap has been applied. Assure that the pixmap - // is scaled to the maximum available size. - KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight) * qApp->devicePixelRatio()); + } else { + if (m_pixmap.width() / m_pixmap.devicePixelRatio() != maxIconWidth || m_pixmap.height() / m_pixmap.devicePixelRatio() != maxIconHeight) { + // A custom pixmap has been applied. Assure that the pixmap + // is scaled to the maximum available size. + KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight) * dpr); + } + } + + if (m_pixmap.isNull()) { + m_hoverPixmap = QPixmap(); + return; } if (m_isCut) { - KIconEffect* effect = KIconLoader::global()->iconEffect(); - m_pixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); + KIconEffect::toDisabled(m_pixmap); } if (m_isHidden) { @@ -974,33 +1151,31 @@ void KStandardItemListWidget::updatePixmapCache() if (m_layout == IconsLayout && isSelected()) { const QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color(); QImage image = m_pixmap.toImage(); + if (image.isNull()) { + m_hoverPixmap = QPixmap(); + return; + } KIconEffect::colorize(image, color, 0.8f); m_pixmap = QPixmap::fromImage(image); } } - if (!m_overlay.isNull()) { - QPainter painter(&m_pixmap); - painter.drawPixmap(0, (m_pixmap.height() - m_overlay.height()) / m_pixmap.devicePixelRatio(), m_overlay); - } - int scaledIconSize = 0; if (iconOnTop) { - const TextInfo* textInfo = m_textInfo.value("text"); + const TextInfo *textInfo = m_textInfo.value("text"); scaledIconSize = static_cast(textInfo->pos.y() - 2 * padding); } else { const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1; const qreal requiredTextHeight = textRowsCount * m_customizedFontMetrics.height(); - scaledIconSize = (requiredTextHeight < maxIconHeight) ? - widgetSize.height() - 2 * padding : maxIconHeight; + scaledIconSize = (requiredTextHeight < maxIconHeight) ? widgetSize.height() - 2 * padding : maxIconHeight; } const int maxScaledIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : scaledIconSize; const int maxScaledIconHeight = scaledIconSize; m_scaledPixmapSize = m_pixmap.size(); - m_scaledPixmapSize.scale(maxScaledIconWidth * qApp->devicePixelRatio(), maxScaledIconHeight * qApp->devicePixelRatio(), Qt::KeepAspectRatio); - m_scaledPixmapSize = m_scaledPixmapSize / qApp->devicePixelRatio(); + m_scaledPixmapSize.scale(maxScaledIconWidth * dpr, maxScaledIconHeight * dpr, Qt::KeepAspectRatio); + m_scaledPixmapSize = m_scaledPixmapSize / dpr; if (iconOnTop) { // Center horizontally and align on bottom within the icon-area @@ -1008,31 +1183,33 @@ void KStandardItemListWidget::updatePixmapCache() m_pixmapPos.setY(padding + scaledIconSize - m_scaledPixmapSize.height()); } else { // Center horizontally and vertically within the icon-area - const TextInfo* textInfo = m_textInfo.value("text"); - m_pixmapPos.setX(textInfo->pos.x() - 2.0 * padding - - (scaledIconSize + m_scaledPixmapSize.width()) / 2.0); + const TextInfo *textInfo = m_textInfo.value("text"); + if (QApplication::isRightToLeft()) { + m_pixmapPos.setX(m_textRect.right() + 2.0 * padding); + } else { + m_pixmapPos.setX(textInfo->pos.x() - 2.0 * padding - (scaledIconSize + m_scaledPixmapSize.width()) / 2.0); + } // Derive icon's vertical center from the center of the text frame, including // any necessary adjustment if the font's midline is offset from the frame center - const qreal midlineShift = m_customizedFontMetrics.height() / 2.0 - - m_customizedFontMetrics.descent() - - m_customizedFontMetrics.capHeight() / 2.0; + const qreal midlineShift = m_customizedFontMetrics.height() / 2.0 - m_customizedFontMetrics.descent() - m_customizedFontMetrics.capHeight() / 2.0; m_pixmapPos.setY(m_textRect.center().y() + midlineShift - m_scaledPixmapSize.height() / 2.0); - } - m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)); + if (m_layout == IconsLayout) { + m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)); + } else { + const qreal widthOffset = widgetIconSize - m_scaledPixmapSize.width(); + const qreal heightOffset = widgetIconSize - m_scaledPixmapSize.height(); + const QPointF squareIconPos(m_pixmapPos.x() - 0.5 * widthOffset, m_pixmapPos.y() - 0.5 * heightOffset); + const QSizeF squareIconSize(widgetIconSize, widgetIconSize); + m_iconRect = QRectF(squareIconPos, squareIconSize); + } // Prepare the pixmap that is used when the item gets hovered if (isHovered()) { m_hoverPixmap = m_pixmap; - KIconEffect* effect = KIconLoader::global()->iconEffect(); - // In the KIconLoader terminology, active = hover. - if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) { - m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState); - } else { - m_hoverPixmap = m_pixmap; - } + KIconEffect::toActive(m_hoverPixmap); } else if (hoverOpacity() <= 0.0) { // No hover animation is ongoing. Clear m_hoverPixmap to save memory. m_hoverPixmap = QPixmap(); @@ -1048,6 +1225,9 @@ void KStandardItemListWidget::updateTextsCache() textOption.setAlignment(Qt::AlignHCenter); break; case CompactLayout: + textOption.setAlignment(QApplication::isRightToLeft() ? Qt::AlignRight : Qt::AlignLeft); + textOption.setWrapMode(QTextOption::NoWrap); + break; case DetailsLayout: textOption.setAlignment(Qt::AlignLeft); textOption.setWrapMode(QTextOption::NoWrap); @@ -1060,7 +1240,7 @@ void KStandardItemListWidget::updateTextsCache() qDeleteAll(m_textInfo); m_textInfo.clear(); for (int i = 0; i < m_sortedVisibleRoles.count(); ++i) { - TextInfo* textInfo = new TextInfo(); + TextInfo *textInfo = new TextInfo(); textInfo->staticText.setTextFormat(Qt::PlainText); textInfo->staticText.setPerformanceHint(QStaticText::AggressiveCaching); textInfo->staticText.setTextOption(textOption); @@ -1068,26 +1248,32 @@ void KStandardItemListWidget::updateTextsCache() } switch (m_layout) { - case IconsLayout: updateIconsLayoutTextCache(); break; - case CompactLayout: updateCompactLayoutTextCache(); break; - case DetailsLayout: updateDetailsLayoutTextCache(); break; - default: Q_ASSERT(false); break; + case IconsLayout: + updateIconsLayoutTextCache(); + break; + case CompactLayout: + updateCompactLayoutTextCache(); + break; + case DetailsLayout: + updateDetailsLayoutTextCache(); + break; + default: + Q_ASSERT(false); + break; } - const TextInfo* ratingTextInfo = m_textInfo.value("rating"); + const TextInfo *ratingTextInfo = m_textInfo.value("rating"); if (ratingTextInfo) { // The text of the rating-role has been set to empty to get // replaced by a rating-image showing the rating as stars. - const KItemListStyleOption& option = styleOption(); + const KItemListStyleOption &option = styleOption(); QSizeF ratingSize = preferredRatingSize(option); - const qreal availableWidth = (m_layout == DetailsLayout) - ? columnWidth("rating") - columnPadding(option) - : size().width(); + const qreal availableWidth = (m_layout == DetailsLayout) ? columnWidth("rating") - columnPadding(option) : size().width(); if (ratingSize.width() > availableWidth) { ratingSize.rwidth() = availableWidth; } - const qreal dpr = qApp->devicePixelRatio(); + const qreal dpr = KItemViewsUtils::devicePixelRatio(this); m_rating = QPixmap(ratingSize.toSize() * dpr); m_rating.setDevicePixelRatio(dpr); m_rating.fill(Qt::transparent); @@ -1101,24 +1287,39 @@ void KStandardItemListWidget::updateTextsCache() } } -QString KStandardItemListWidget::elideRightKeepExtension(const QString &text, int elidingWidth) const +QString KStandardItemListWidget::elideText(QString text, qreal maxWidth) const { - const auto extensionIndex = text.lastIndexOf('.'); - if (extensionIndex != -1) { - // has file extension - const auto extensionLength = text.length() - extensionIndex; - const auto extensionWidth = m_customizedFontMetrics.horizontalAdvance(text.right(extensionLength)); - if (elidingWidth > extensionWidth && extensionLength < 6 && (float(extensionWidth) / float(elidingWidth)) < 0.3) { - // if we have room to display the file extension and the extension is not too long - QString ret = m_customizedFontMetrics.elidedText(text.chopped(extensionLength), - Qt::ElideRight, - elidingWidth - extensionWidth); - ret.append(text.right(extensionLength)); - return ret; + if (ContentDisplaySettings::elidingMode() == ContentDisplaySettings::ElidingMode::Middle) { + return m_customizedFontMetrics.elidedText(text, Qt::ElideMiddle, maxWidth); + } + + if (ContentDisplaySettings::elidingMode() == ContentDisplaySettings::ElidingMode::Right) { + qsizetype lastDotPosition = text.lastIndexOf("."); + QString extension = text.mid(lastDotPosition); + + if (m_customizedFontMetrics.horizontalAdvance(QStringLiteral("…") + extension) > maxWidth) { + extension = ""; + lastDotPosition = text.size(); } + + maxWidth -= m_customizedFontMetrics.horizontalAdvance(extension); + QString leftPart = m_customizedFontMetrics.elidedText(text.left(lastDotPosition), Qt::ElideRight, maxWidth); + + return leftPart + extension; } - return m_customizedFontMetrics.elidedText(text,Qt::ElideRight, - elidingWidth); + + Q_UNREACHABLE(); + return text; +} + +QString KStandardItemListWidget::escapeString(const QString &text) const +{ + QString escaped(text); + + const QChar returnSymbol(0x21b5); + escaped.replace('\n', returnSymbol); + + return escaped; } void KStandardItemListWidget::updateIconsLayoutTextCache() @@ -1135,16 +1336,15 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() const QHash values = data(); - const KItemListStyleOption& option = styleOption(); + const KItemListStyleOption &option = styleOption(); const qreal padding = option.padding; const qreal maxWidth = size().width() - 2 * padding; - const qreal widgetHeight = size().height(); const qreal lineSpacing = m_customizedFontMetrics.lineSpacing(); // Initialize properties for the "text" role. It will be used as anchor // for initializing the position of the other roles. - TextInfo* nameTextInfo = m_textInfo.value("text"); - const QString nameText = KStringHandler::preProcessWrap(values["text"].toString()); + TextInfo *nameTextInfo = m_textInfo.value("text"); + const QString nameText = KStringHandler::preProcessWrap(escapeString(values["text"].toString())); nameTextInfo->staticText.setText(nameText); // Calculate the number of lines required for the name and the required width @@ -1172,7 +1372,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() qreal lastLineWidth; do { QString lastTextLine = nameText.mid(line.textStart()); - lastTextLine = elideRightKeepExtension(lastTextLine, elidingWidth); + lastTextLine = elideText(lastTextLine, elidingWidth); const QString elidedText = nameText.left(line.textStart()) + lastTextLine; nameTextInfo->staticText.setText(elidedText); @@ -1192,26 +1392,19 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() layout.endLayout(); // Use one line for each additional information - const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0); nameTextInfo->staticText.setTextWidth(maxWidth); - nameTextInfo->pos = QPointF(padding, widgetHeight - - nameHeight - - additionalRolesCount * lineSpacing - - padding); - m_textRect = QRectF(padding + (maxWidth - nameWidth) / 2, - nameTextInfo->pos.y(), - nameWidth, - nameHeight); + nameTextInfo->pos = QPointF(padding, iconSize() + 2 * padding); + m_textRect = QRectF(padding + (maxWidth - nameWidth) / 2, nameTextInfo->pos.y(), nameWidth, nameHeight); // Calculate the position for each additional information qreal y = nameTextInfo->pos.y() + nameHeight; - for (const QByteArray& role : qAsConst(m_sortedVisibleRoles)) { + for (const QByteArray &role : std::as_const(m_sortedVisibleRoles)) { if (role == "text") { continue; } const QString text = roleText(role, values); - TextInfo* textInfo = m_textInfo.value(role); + TextInfo *textInfo = m_textInfo.value(role); textInfo->staticText.setText(text); qreal requiredWidth = 0; @@ -1227,12 +1420,12 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() textLine.setLineWidth(maxWidth); requiredWidth = textLine.naturalTextWidth(); if (requiredWidth > maxWidth) { - const QString elidedText = elideRightKeepExtension(text, maxWidth); + const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, maxWidth); textInfo->staticText.setText(elidedText); requiredWidth = m_customizedFontMetrics.horizontalAdvance(elidedText); } else if (role == "rating") { // Use the width of the rating pixmap, because the rating text is empty. - requiredWidth = m_rating.width(); + requiredWidth = m_rating.width() / m_rating.devicePixelRatioF(); } } layout.endLayout(); @@ -1241,7 +1434,11 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() textInfo->staticText.setTextWidth(maxWidth); const QRectF textRect(padding + (maxWidth - requiredWidth) / 2, y, requiredWidth, lineSpacing); - m_textRect |= textRect; + + // Ignore empty roles. Avoids a text rect taller than the area that actually contains text. + if (!textRect.isEmpty()) { + m_textRect |= textRect; + } y += lineSpacing; } @@ -1258,26 +1455,30 @@ void KStandardItemListWidget::updateCompactLayoutTextCache() const QHash values = data(); - const KItemListStyleOption& option = styleOption(); + const KItemListStyleOption &option = styleOption(); const qreal widgetHeight = size().height(); const qreal lineSpacing = m_customizedFontMetrics.lineSpacing(); const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * lineSpacing; - const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.padding : option.iconSize; qreal maximumRequiredTextWidth = 0; - const qreal x = option.padding * 3 + scaledIconSize; + const qreal x = QApplication::isRightToLeft() ? option.padding : option.padding * 3 + iconSize(); qreal y = qRound((widgetHeight - textLinesHeight) / 2); - const qreal maxWidth = size().width() - x - option.padding; - for (const QByteArray& role : qAsConst(m_sortedVisibleRoles)) { - const QString text = roleText(role, values); - TextInfo* textInfo = m_textInfo.value(role); + const qreal maxWidth = size().width() - iconSize() - 4 * option.padding; + for (const QByteArray &role : std::as_const(m_sortedVisibleRoles)) { + const QString text = escapeString(roleText(role, values)); + TextInfo *textInfo = m_textInfo.value(role); textInfo->staticText.setText(text); qreal requiredWidth = m_customizedFontMetrics.horizontalAdvance(text); if (requiredWidth > maxWidth) { requiredWidth = maxWidth; - const QString elidedText = elideRightKeepExtension(text, maxWidth); - textInfo->staticText.setText(elidedText); + if (role == "text") { + const QString elidedText = elideText(text, maxWidth); + textInfo->staticText.setText(elidedText); + } else { + const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, maxWidth); + textInfo->staticText.setText(elidedText); + } } textInfo->pos = QPointF(x, y); @@ -1299,27 +1500,29 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() // +------+ // | Icon | Name role Additional role 1 Additional role 2 // +------+ + // Mirror the above for right-to-left languages. + const bool isLeftToRight = QApplication::layoutDirection() == Qt::LeftToRight; m_textRect = QRectF(); - const KItemListStyleOption& option = styleOption(); + const KItemListStyleOption &option = styleOption(); const QHash values = data(); const qreal widgetHeight = size().height(); - const int scaledIconSize = widgetHeight - 2 * option.padding; const int fontHeight = m_customizedFontMetrics.height(); const qreal columnWidthInc = columnPadding(option); - qreal firstColumnInc = scaledIconSize; + qreal firstColumnInc = iconSize(); if (m_supportsItemExpanding) { - firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2; + firstColumnInc += isLeftToRight ? (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2 + : ((size().width() - m_expansionArea.left()) + (size().width() - m_expansionArea.right()) + widgetHeight) / 2; } else { - firstColumnInc += option.padding; + firstColumnInc += option.padding + (isLeftToRight ? leftPadding() : rightPadding()); } qreal x = firstColumnInc; const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2); - for (const QByteArray& role : qAsConst(m_sortedVisibleRoles)) { + for (const QByteArray &role : std::as_const(m_sortedVisibleRoles)) { QString text = roleText(role, values); // Elide the text in case it does not fit into the available column-width @@ -1329,30 +1532,31 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() const bool isTextRole = (role == "text"); if (isTextRole) { - availableTextWidth -= firstColumnInc; + text = escapeString(text); + availableTextWidth -= firstColumnInc - (isLeftToRight ? leftPadding() : rightPadding()); } if (requiredWidth > availableTextWidth) { - text = elideRightKeepExtension(text, availableTextWidth); + if (isTextRole) { + text = elideText(text, availableTextWidth); + } else { + text = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth); + } requiredWidth = m_customizedFontMetrics.horizontalAdvance(text); } - TextInfo* textInfo = m_textInfo.value(role); + TextInfo *textInfo = m_textInfo.value(role); textInfo->staticText.setText(text); - textInfo->pos = QPointF(x + columnWidthInc / 2, y); + textInfo->pos = QPointF(isLeftToRight ? (x + columnWidthInc / 2) : (size().width() - (x + columnWidthInc / 2) - requiredWidth), y); x += roleWidth; if (isTextRole) { - const qreal textWidth = option.extendedSelectionRegion - ? size().width() - textInfo->pos.x() - : requiredWidth + 2 * option.padding; - m_textRect = QRectF(textInfo->pos.x() - option.padding, 0, - textWidth, size().height()); + m_textRect = QRectF(textInfo->pos.x() - option.padding, 0, requiredWidth + 2 * option.padding, size().height()); // The column after the name should always be aligned on the same x-position independent // from the expansion-level shown in the name column - x -= firstColumnInc; - } else if (isRoleRightAligned(role)) { + x -= firstColumnInc - (isLeftToRight ? leftPadding() : rightPadding()); + } else if (isRoleRightAligned(role) && isLeftToRight) { textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc; } } @@ -1361,10 +1565,14 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() void KStandardItemListWidget::updateAdditionalInfoTextColor() { QColor c1; + const bool hasFocus = scene()->views()[0]->parentWidget()->hasFocus(); if (m_customTextColor.isValid()) { c1 = m_customTextColor; - } else if (isSelected() && m_layout != DetailsLayout) { - c1 = styleOption().palette.highlightedText().color(); + } else if (isSelected() && hasFocus && (m_layout != DetailsLayout || m_highlightEntireRow)) { + // The detail text color needs to match the main text (HighlightedText) for the same level + // of readability. We short circuit early here to avoid interpolating with another color. + m_additionalInfoTextColor = styleOption().palette.color(QPalette::HighlightedText); + return; } else { c1 = styleOption().palette.text().color(); } @@ -1375,17 +1583,87 @@ void KStandardItemListWidget::updateAdditionalInfoTextColor() const QColor c2 = styleOption().palette.base().color(); const int p1 = 70; const int p2 = 100 - p1; - m_additionalInfoTextColor = QColor((c1.red() * p1 + c2.red() * p2) / 100, - (c1.green() * p1 + c2.green() * p2) / 100, - (c1.blue() * p1 + c2.blue() * p2) / 100); + m_additionalInfoTextColor = + QColor((c1.red() * p1 + c2.red() * p2) / 100, (c1.green() * p1 + c2.green() * p2) / 100, (c1.blue() * p1 + c2.blue() * p2) / 100); } -void KStandardItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap) +QPixmap +KStandardItemListWidget::addOverlays(const QPixmap &pixmap, const QHash &overlays, const QSize &size, qreal dpr, QIcon::Mode mode) const +{ + // similar to KIconUtils::addOverlays, keep in sync preferrably + if (overlays.isEmpty()) { + return pixmap; + } + + int width = size.width(); + int height = size.height(); + const int iconSize = qMin(width, height); + + // Determine the overlay icon + int overlaySize; + if (iconSize < 32) { + overlaySize = 8; + } else if (iconSize <= 48) { + overlaySize = 16; + } else if (iconSize <= 96) { + overlaySize = 22; + } else if (iconSize < 256) { + overlaySize = 32; + } else { + overlaySize = 64; + } + + auto phyiscalSize = QSize(std::clamp(pixmap.width(), qFloor(2 * overlaySize * dpr), qFloor(size.width() * dpr)), + std::clamp(pixmap.height(), qFloor(2 * overlaySize * dpr), qFloor(size.height() * dpr))); + + QPixmap output(phyiscalSize); + output.setDevicePixelRatio(dpr); + output.fill(Qt::transparent); + + QPainter painter(&output); + painter.drawPixmap(qFloor(phyiscalSize.width() / dpr / 2) - qFloor(pixmap.width() / pixmap.devicePixelRatio() / 2), + // align the icon to the bottom to match the behavior elsewhere + qFloor(phyiscalSize.height() / dpr) - qFloor(pixmap.height() / pixmap.devicePixelRatio()), + pixmap); + + width = qCeil(phyiscalSize.width() / dpr); + height = qCeil(phyiscalSize.height() / dpr); + + // Iterate over stored overlays + for (const auto &[corner, overlay] : overlays.asKeyValueRange()) { + const QPixmap overlayPixmap = QIcon::fromTheme(overlay).pixmap(QSize{overlaySize, overlaySize}, dpr, mode); + if (overlayPixmap.isNull()) { + continue; + } + + QPoint startPoint; + switch (corner) { + case Qt::BottomLeftCorner: + startPoint = QPoint{0, height - overlaySize}; + break; + case Qt::BottomRightCorner: + startPoint = QPoint{width - overlaySize, height - overlaySize}; + break; + case Qt::TopRightCorner: + startPoint = QPoint{width - overlaySize, 0}; + break; + case Qt::TopLeftCorner: + startPoint = QPoint{}; + break; + } + painter.drawPixmap(startPoint, overlayPixmap); + } + + return output; +} + +void KStandardItemListWidget::drawPixmap(QPainter *painter, const QPixmap &pixmap) { if (m_scaledPixmapSize != pixmap.size() / pixmap.devicePixelRatio()) { + const qreal dpr = KItemViewsUtils::devicePixelRatio(this); QPixmap scaledPixmap = pixmap; - KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize * qApp->devicePixelRatio()); - scaledPixmap.setDevicePixelRatio(qApp->devicePixelRatio()); + KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize * dpr); + scaledPixmap.setDevicePixelRatio(dpr); painter->drawPixmap(m_pixmapPos, scaledPixmap); #ifdef KSTANDARDITEMLISTWIDGET_DEBUG @@ -1397,21 +1675,21 @@ void KStandardItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixma } } -void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter) +void KStandardItemListWidget::drawSiblingsInformation(QPainter *painter) { const int siblingSize = size().height(); const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2; QRect siblingRect(x, 0, siblingSize, siblingSize); - QStyleOption option; - option.palette.setColor(QPalette::Text, option.palette.color(normalTextColorRole())); bool isItemSibling = true; const QBitArray siblings = siblingsInformation(); + QStyleOption option; + const auto normalColor = option.palette.color(normalTextColorRole()); + const auto highlightColor = option.palette.color(expansionAreaHovered() ? QPalette::Highlight : normalTextColorRole()); for (int i = siblings.count() - 1; i >= 0; --i) { option.rect = siblingRect; option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None; - if (isItemSibling) { option.state |= QStyle::State_Item; if (m_isExpandable) { @@ -1420,18 +1698,21 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter) if (data().value("isExpanded").toBool()) { option.state |= QStyle::State_Open; } + option.palette.setColor(QPalette::Text, highlightColor); isItemSibling = false; + } else { + option.palette.setColor(QPalette::Text, normalColor); } style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter); - siblingRect.translate(-siblingRect.width(), 0); + siblingRect.translate(layoutDirection() == Qt::LeftToRight ? -siblingRect.width() : siblingRect.width(), 0); } } -QRectF KStandardItemListWidget::roleEditingRect(const QByteArray& role) const +QRectF KStandardItemListWidget::roleEditingRect(const QByteArray &role) const { - const TextInfo* textInfo = m_textInfo.value(role); + const TextInfo *textInfo = m_textInfo.value(role); if (!textInfo) { return QRectF(); } @@ -1446,10 +1727,8 @@ QRectF KStandardItemListWidget::roleEditingRect(const QByteArray& role) const void KStandardItemListWidget::closeRoleEditor() { - disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, - this, &KStandardItemListWidget::slotRoleEditingCanceled); - disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, - this, &KStandardItemListWidget::slotRoleEditingFinished); + disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, this, &KStandardItemListWidget::slotRoleEditingCanceled); + disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, this, &KStandardItemListWidget::slotRoleEditingFinished); if (m_roleEditor->hasFocus()) { // If the editing was not ended by a FocusOut event, we have @@ -1465,13 +1744,15 @@ void KStandardItemListWidget::closeRoleEditor() m_roleEditor = nullptr; } -QPixmap KStandardItemListWidget::pixmapForIcon(const QString& name, const QStringList& overlays, int size, QIcon::Mode mode) +QPixmap KStandardItemListWidget::pixmapForIcon(const QString &name, const QSize &size, QIcon::Mode mode) const { static const QIcon fallbackIcon = QIcon::fromTheme(QStringLiteral("unknown")); + const qreal dpr = KItemViewsUtils::devicePixelRatio(this); - size *= qApp->devicePixelRatio(); + int iconHeight = size.height(); + QSize iconSize = QSize(iconHeight, iconHeight); - const QString key = "KStandardItemListWidget:" % name % ":" % overlays.join(QLatin1Char(':')) % ":" % QString::number(size) % ":" % QString::number(mode); + const QString key = "KStandardItemListWidget:" % name % ":" % QString::number(iconHeight) % "@" % QString::number(dpr) % ":" % QString::number(mode); QPixmap pixmap; if (!QPixmapCache::find(key, &pixmap)) { @@ -1479,60 +1760,33 @@ QPixmap KStandardItemListWidget::pixmapForIcon(const QString& name, const QStrin if (icon.isNull()) { icon = QIcon(name); } - if (icon.isNull()) { - icon = fallbackIcon; + if (!icon.isNull()) { + pixmap = icon.pixmap(iconSize, dpr, mode); } - - pixmap = icon.pixmap(size / qApp->devicePixelRatio(), size / qApp->devicePixelRatio(), mode); - if (pixmap.width() != size || pixmap.height() != size) { - KPixmapModifier::scale(pixmap, QSize(size, size)); + if (pixmap.isNull()) { + icon = fallbackIcon; + pixmap = icon.pixmap(iconSize, dpr, mode); } - - // Strangely KFileItem::overlays() returns empty string-values, so - // we need to check first whether an overlay must be drawn at all. - // It is more efficient to do it here, as KIconLoader::drawOverlays() - // assumes that an overlay will be drawn and has some additional - // setup time. - for (const QString& overlay : overlays) { - if (!overlay.isEmpty()) { - int state = KIconLoader::DefaultState; - - switch (mode) { - case QIcon::Normal: - break; - case QIcon::Active: - state = KIconLoader::ActiveState; - break; - case QIcon::Disabled: - state = KIconLoader::DisabledState; - break; - case QIcon::Selected: - state = KIconLoader::SelectedState; - break; - } - - // There is at least one overlay, draw all overlays above m_pixmap - // and cancel the check - KIconLoader::global()->drawOverlays(overlays, pixmap, KIconLoader::Desktop, state); - break; - } + if (pixmap.width() != iconHeight * dpr || pixmap.height() != iconHeight * dpr) { + KPixmapModifier::scale(pixmap, iconSize * dpr); } QPixmapCache::insert(key, pixmap); } - pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); + pixmap.setDevicePixelRatio(dpr); return pixmap; } -QSizeF KStandardItemListWidget::preferredRatingSize(const KItemListStyleOption& option) +QSizeF KStandardItemListWidget::preferredRatingSize(const KItemListStyleOption &option) { const qreal height = option.fontMetrics.ascent(); return QSizeF(height * 5, height); } -qreal KStandardItemListWidget::columnPadding(const KItemListStyleOption& option) +qreal KStandardItemListWidget::columnPadding(const KItemListStyleOption &option) { return option.padding * 6; } +#include "moc_kstandarditemlistwidget.cpp"