X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/ed2d352c42a6d517d4f29b3582c0e00aa34fe647..517cc98e1574fdff4944f256d14ff10543b7c718:/src/kitemviews/kstandarditemlistwidget.cpp diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index c4eab6d74..bc7023e12 100644 --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -10,10 +10,12 @@ #include "kfileitemmodel.h" #include "private/kfileitemclipboard.h" #include "private/kitemlistroleeditor.h" +#include "private/kitemviewsutils.h" #include "private/kpixmapmodifier.h" #include #include +#include #include #include @@ -23,6 +25,8 @@ #include #include #include +#include +#include // #define KSTANDARDITEMLISTWIDGET_DEBUG @@ -105,11 +109,20 @@ bool KStandardItemListWidgetInformant::itemIsLink(int index, const KItemListView 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(); } @@ -294,11 +307,6 @@ void KStandardItemListWidget::setLayout(Layout layout) } } -KStandardItemListWidget::Layout KStandardItemListWidget::layout() const -{ - return m_layout; -} - void KStandardItemListWidget::setHighlightEntireRow(bool highlightEntireRow) { if (m_highlightEntireRow != highlightEntireRow) { @@ -386,7 +394,7 @@ void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphic } painter->setFont(m_customizedFont); - painter->setPen(textColor()); + painter->setPen(textColor(*widget)); const TextInfo *textInfo = m_textInfo.value("text"); if (!textInfo) { @@ -539,6 +547,7 @@ QRectF KStandardItemListWidget::selectionToggleRect() const { const_cast(this)->triggerCacheRefreshing(); + const QRectF widgetIconRect = iconRect(); const int widgetIconSize = iconSize(); int toggleSize = KIconLoader::SizeSmall; if (widgetIconSize >= KIconLoader::SizeEnormous) { @@ -547,7 +556,7 @@ QRectF KStandardItemListWidget::selectionToggleRect() const 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 @@ -568,6 +577,10 @@ 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)); } @@ -593,6 +606,55 @@ 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{data()["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"; + } + + 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 && data()["iconName"] == "folder-open"; +} + KItemListWidgetInformant *KStandardItemListWidget::createInformant() { return new KStandardItemListWidgetInformant(); @@ -645,7 +707,7 @@ void KStandardItemListWidget::setTextColor(const QColor &color) } } -QColor KStandardItemListWidget::textColor() const +QColor KStandardItemListWidget::textColor(const QWidget &widget) const { if (!isSelected()) { if (m_isHidden) { @@ -655,7 +717,7 @@ 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); } @@ -740,7 +802,9 @@ void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption &cur void KStandardItemListWidget::hoveredChanged(bool hovered) { - Q_UNUSED(hovered) + if (!hovered && m_activateSoonAnimation) { + m_activateSoonAnimation->stop(); // automatically DeleteWhenStopped + } m_dirtyLayout = true; } @@ -758,9 +822,19 @@ void KStandardItemListWidget::siblingsInformationChanged(const QBitArray ¤ m_dirtyLayout = true; } +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 ¤t, const QByteArray &previous) @@ -771,16 +845,7 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray ¤t, const if (current.isEmpty() || !parent || current != "text") { if (m_roleEditor) { Q_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; + closeRoleEditor(); } return; } @@ -821,6 +886,7 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray ¤t, const rect.setWidth(parent->width() - rect.left()); } m_roleEditor->setGeometry(rect.toRect()); + m_roleEditor->autoAdjustSize(); m_roleEditor->show(); m_roleEditor->setFocus(); } @@ -964,6 +1030,7 @@ void KStandardItemListWidget::updatePixmapCache() const bool iconOnTop = (m_layout == IconsLayout); const KItemListStyleOption &option = styleOption(); const qreal padding = option.padding; + const qreal dpr = KItemViewsUtils::devicePixelRatio(this); const int widgetIconSize = iconSize(); const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : widgetIconSize; @@ -982,7 +1049,7 @@ void KStandardItemListWidget::updatePixmapCache() int sequenceIndex = hoverSequenceIndex(); - if (values.contains("hoverSequencePixmaps")) { + if (values.contains("hoverSequencePixmaps") && !isIconControlledByActivateSoonAnimation()) { // Use one of the hover sequence pixmaps instead of the default // icon pixmap. @@ -1002,7 +1069,7 @@ void KStandardItemListWidget::updatePixmapCache() } } - if (m_pixmap.isNull()) { + if (m_pixmap.isNull() && !isIconControlledByActivateSoonAnimation()) { m_pixmap = values["iconPixmap"].value(); } @@ -1015,13 +1082,16 @@ void KStandardItemListWidget::updatePixmapCache() 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, + overlays, + 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()); + KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight) * dpr); } if (m_pixmap.isNull()) { @@ -1030,8 +1100,14 @@ void KStandardItemListWidget::updatePixmapCache() } if (m_isCut) { - KIconEffect *effect = KIconLoader::global()->iconEffect(); - m_pixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); +#if KICONTHEMES_VERSION >= QT_VERSION_CHECK(6, 5, 0) + KIconEffect::toDisabled(m_pixmap); +#else + QImage img = m_pixmap.toImage(); + KIconEffect::toGray(img, 1); + KIconEffect::semiTransparent(img); + m_pixmap = QPixmap::fromImage(img); +#endif } if (m_isHidden) { @@ -1069,8 +1145,8 @@ void KStandardItemListWidget::updatePixmapCache() 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 @@ -1079,7 +1155,11 @@ void KStandardItemListWidget::updatePixmapCache() } 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); + if (QApplication::isRightToLeft() && m_layout == CompactLayout) { + m_pixmapPos.setX(size().width() - padding - (scaledIconSize + m_scaledPixmapSize.width()) / 2.0); + } 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 @@ -1100,13 +1180,13 @@ void KStandardItemListWidget::updatePixmapCache() // 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; - } +#if KICONTHEMES_VERSION >= QT_VERSION_CHECK(6, 5, 0) + KIconEffect::toActive(m_hoverPixmap); +#else + QImage img = m_pixmap.toImage(); + KIconEffect::toGamma(img, 0.7); + m_hoverPixmap = QPixmap::fromImage(img); +#endif } else if (hoverOpacity() <= 0.0) { // No hover animation is ongoing. Clear m_hoverPixmap to save memory. m_hoverPixmap = QPixmap(); @@ -1122,6 +1202,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); @@ -1167,7 +1250,7 @@ void KStandardItemListWidget::updateTextsCache() 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); @@ -1191,17 +1274,23 @@ QString KStandardItemListWidget::elideRightKeepExtension(const QString &text, in 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); -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - ret.append(text.rightRef(extensionLength)); -#else ret.append(QStringView(text).right(extensionLength)); -#endif return ret; } } return m_customizedFontMetrics.elidedText(text, Qt::ElideRight, elidingWidth); } +QString KStandardItemListWidget::escapeString(const QString &text) const +{ + QString escaped(text); + + const QChar returnSymbol(0x21b5); + escaped.replace('\n', returnSymbol); + + return escaped; +} + void KStandardItemListWidget::updateIconsLayoutTextCache() { // +------+ @@ -1224,7 +1313,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() // 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()); + 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 @@ -1278,7 +1367,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() // 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; } @@ -1341,11 +1430,11 @@ void KStandardItemListWidget::updateCompactLayoutTextCache() const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * lineSpacing; qreal maximumRequiredTextWidth = 0; - const qreal x = option.padding * 3 + iconSize(); + 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); + 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); @@ -1394,7 +1483,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() 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 @@ -1404,6 +1493,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() const bool isTextRole = (role == "text"); if (isTextRole) { + text = escapeString(text); availableTextWidth -= firstColumnInc - sidePadding(); } @@ -1433,11 +1523,12 @@ 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 || m_highlightEntireRow)) { - // The detail text colour needs to match the main text (HighlightedText) for the same level - // of readability. We short circuit early here to avoid interpolating with another colour. + } 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 { @@ -1457,9 +1548,10 @@ void KStandardItemListWidget::updateAdditionalInfoTextColor() 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 @@ -1540,13 +1632,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 QStringList &overlays, int size, QIcon::Mode mode) const { static const QIcon fallbackIcon = QIcon::fromTheme(QStringLiteral("unknown")); + const qreal dpr = KItemViewsUtils::devicePixelRatio(this); - size *= qApp->devicePixelRatio(); + size *= dpr; - const QString key = "KStandardItemListWidget:" % name % ":" % overlays.join(QLatin1Char(':')) % ":" % QString::number(size) % ":" % QString::number(mode); + const QString key = "KStandardItemListWidget:" % name % ":" % overlays.join(QLatin1Char(':')) % ":" % QString::number(size) % "@" % QString::number(dpr) + % ":" % QString::number(mode); QPixmap pixmap; if (!QPixmapCache::find(key, &pixmap)) { @@ -1554,48 +1648,30 @@ QPixmap KStandardItemListWidget::pixmapForIcon(const QString &name, const QStrin if (icon.isNull()) { icon = QIcon(name); } - if (icon.isNull() || icon.pixmap(size / qApp->devicePixelRatio(), size / qApp->devicePixelRatio(), mode).isNull()) { + if (icon.isNull() || icon.pixmap(size / dpr, size / dpr, mode).isNull()) { icon = fallbackIcon; } - pixmap = icon.pixmap(size / qApp->devicePixelRatio(), size / qApp->devicePixelRatio(), mode); + pixmap = icon.pixmap(QSize(size / dpr, size / dpr), dpr, mode); if (pixmap.width() != size || pixmap.height() != size) { KPixmapModifier::scale(pixmap, QSize(size, size)); } // 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); + const QSize size = pixmap.size(); + pixmap = KIconUtils::addOverlays(pixmap, overlays).pixmap(size, mode); break; } } QPixmapCache::insert(key, pixmap); } - pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); + pixmap.setDevicePixelRatio(dpr); return pixmap; }