X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/63e1e3bc14b5538027471ee76b6e92d6425ecf0b..b2cb38214ec403dfc68e5231e0006fe59833515a:/src/kitemviews/kfileitemlistwidget.cpp diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp index a9193fbc7..d3e792ea0 100644 --- a/src/kitemviews/kfileitemlistwidget.cpp +++ b/src/kitemviews/kfileitemlistwidget.cpp @@ -19,6 +19,7 @@ #include "kfileitemlistwidget.h" +#include "kfileitemclipboard_p.h" #include "kfileitemmodel.h" #include "kitemlistview.h" #include "kpixmapmodifier_p.h" @@ -37,11 +38,13 @@ #include #include -//#define KFILEITEMLISTWIDGET_DEBUG +// #define KFILEITEMLISTWIDGET_DEBUG KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) : KItemListWidget(parent), - m_isDir(false), + m_isCut(false), + m_isHidden(false), + m_isExpandable(false), m_dirtyLayout(true), m_dirtyContent(true), m_dirtyContentRoles(), @@ -49,11 +52,12 @@ KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) : m_pixmapPos(), m_pixmap(), m_scaledPixmapSize(), - m_hoverPixmapRect(), + m_originalPixmapSize(), + m_iconRect(), m_hoverPixmap(), m_textPos(), m_text(), - m_textBoundingRect(), + m_textRect(), m_sortedVisibleRoles(), m_expansionArea(), m_customTextColor(), @@ -86,12 +90,12 @@ KFileItemListWidget::Layout KFileItemListWidget::layout() const void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { - KItemListWidget::paint(painter, option, widget); - const_cast(this)->triggerCacheRefreshing(); + KItemListWidget::paint(painter, option, widget); + // Draw expansion toggle '>' or 'V' - if (m_isDir && !m_expansionArea.isEmpty()) { + if (m_isExpandable) { QStyleOption arrowOption; arrowOption.rect = m_expansionArea.toRect(); const QStyle::PrimitiveElement arrow = data()["isExpanded"].toBool() @@ -119,12 +123,29 @@ void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsIte painter->setPen(textColor()); painter->drawStaticText(m_textPos[Name], m_text[Name]); + bool clipAdditionalInfoBounds = false; + if (m_layout == DetailsLayout) { + // 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.margin; + if (m_textPos[Name + 1].x() < minX) { + clipAdditionalInfoBounds = true; + painter->save(); + painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip); + } + } + painter->setPen(m_additionalInfoTextColor); painter->setFont(itemListStyleOption.font); for (int i = Name + 1; i < TextIdCount; ++i) { painter->drawStaticText(m_textPos[i], m_text[i]); } + if (clipAdditionalInfoBounds) { + painter->restore(); + } + #ifdef KFILEITEMLISTWIDGET_DEBUG painter->setPen(Qt::red); painter->setBrush(Qt::NoBrush); @@ -133,26 +154,107 @@ void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsIte #endif } -QRectF KFileItemListWidget::iconBoundingRect() const +QRectF KFileItemListWidget::iconRect() const { const_cast(this)->triggerCacheRefreshing(); - - QRectF bounds = m_hoverPixmapRect; - const qreal margin = styleOption().margin; - bounds.adjust(-margin, -margin, margin, margin); - return bounds; + return m_iconRect; } -QRectF KFileItemListWidget::textBoundingRect() const +QRectF KFileItemListWidget::textRect() const { const_cast(this)->triggerCacheRefreshing(); - return m_textBoundingRect; + return m_textRect; } QRectF KFileItemListWidget::expansionToggleRect() const { const_cast(this)->triggerCacheRefreshing(); - return m_isDir ? m_expansionArea : QRectF(); + return m_isExpandable ? m_expansionArea : QRectF(); +} + +QRectF KFileItemListWidget::selectionToggleRect() const +{ + const_cast(this)->triggerCacheRefreshing(); + + const int iconHeight = m_pixmap.height(); + + int toggleSize = KIconLoader::SizeSmall; + if (iconHeight >= KIconLoader::SizeEnormous) { + toggleSize = KIconLoader::SizeMedium; + } else if (iconHeight >= KIconLoader::SizeLarge) { + toggleSize = KIconLoader::SizeSmallMedium; + } + + QPointF pos = iconRect().topLeft(); + + // If the selection toggle has a very small distance to the + // widget borders, the size of the selection toggle will get + // increased to prevent an accidental clicking of the item + // when trying to hit the toggle. + const int widgetHeight = size().height(); + const int widgetWidth = size().width(); + const int minMargin = 2; + + if (toggleSize + minMargin * 2 >= widgetHeight) { + toggleSize = widgetHeight; + pos.setY(0); + } + if (toggleSize + minMargin * 2 >= widgetWidth) { + toggleSize = widgetWidth; + pos.setX(0); + } + + return QRectF(pos, QSizeF(toggleSize, toggleSize)); +} + +QString KFileItemListWidget::roleText(const QByteArray& role, const QHash& values) +{ + QString text; + const QVariant roleValue = values.value(role); + + switch (roleTextId(role)) { + case Name: + case Permissions: + case Owner: + case Group: + case Type: + case Destination: + case Path: + text = roleValue.toString(); + break; + + case Size: { + if (values.value("isDir").toBool()) { + // The item represents a directory. Show the number of sub directories + // instead of the file size of the directory. + if (roleValue.isNull()) { + text = i18nc("@item:intable", "Unknown"); + } else { + const KIO::filesize_t size = roleValue.value(); + text = i18ncp("@item:intable", "%1 item", "%1 items", size); + } + } else { + // Show the size in kilobytes (always round up) + const KLocale* locale = KGlobal::locale(); + const int roundInc = (locale->binaryUnitDialect() == KLocale::MetricBinaryDialect) ? 499 : 511; + const KIO::filesize_t size = roleValue.value() + roundInc; + text = locale->formatByteSize(size, 0, KLocale::DefaultBinaryDialect, KLocale::UnitKiloByte); + } + break; + } + + case Date: { + const QDateTime dateTime = roleValue.toDateTime(); + text = KGlobal::locale()->formatDateTime(dateTime); + break; + } + + default: + Q_ASSERT(false); + break; + } + + return text; } void KFileItemListWidget::invalidateCache() @@ -176,7 +278,13 @@ void KFileItemListWidget::setTextColor(const QColor& color) QColor KFileItemListWidget::textColor() const { - return m_customTextColor.isValid() ? m_customTextColor : styleOption().palette.text().color(); + if (m_customTextColor.isValid() && !isSelected()) { + return m_customTextColor; + } + + const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; + const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : QPalette::Text; + return styleOption().palette.brush(group, role).color(); } void KFileItemListWidget::setOverlay(const QPixmap& overlay) @@ -242,12 +350,52 @@ void KFileItemListWidget::hoveredChanged(bool hovered) m_dirtyLayout = true; } +void KFileItemListWidget::selectedChanged(bool selected) +{ + Q_UNUSED(selected); + updateAdditionalInfoTextColor(); +} + void KFileItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) { KItemListWidget::resizeEvent(event); m_dirtyLayout = true; } +void KFileItemListWidget::showEvent(QShowEvent* event) +{ + KItemListWidget::showEvent(event); + + // Listen to changes of the clipboard to mark the item as cut/uncut + KFileItemClipboard* clipboard = KFileItemClipboard::instance(); + + const KUrl itemUrl = data().value("url").value(); + m_isCut = clipboard->isCut(itemUrl); + + connect(clipboard, SIGNAL(cutItemsChanged()), + this, SLOT(slotCutItemsChanged())); +} + +void KFileItemListWidget::hideEvent(QHideEvent* event) +{ + disconnect(KFileItemClipboard::instance(), SIGNAL(cutItemsChanged()), + this, SLOT(slotCutItemsChanged())); + + KItemListWidget::hideEvent(event); +} + +void KFileItemListWidget::slotCutItemsChanged() +{ + const KUrl itemUrl = data().value("url").value(); + const bool isCut = KFileItemClipboard::instance()->isCut(itemUrl); + if (m_isCut != isCut) { + m_isCut = isCut; + m_pixmap = QPixmap(); + m_dirtyContent = true; + update(); + } +} + void KFileItemListWidget::triggerCacheRefreshing() { if ((!m_dirtyContent && !m_dirtyLayout) || index() < 0) { @@ -256,7 +404,9 @@ void KFileItemListWidget::triggerCacheRefreshing() refreshCache(); - m_isDir = data()["isDir"].toBool(); + const QHash values = data(); + m_isExpandable = values["isExpandable"].toBool(); + m_isHidden = values["name"].toString().startsWith(QLatin1Char('.')); updateExpansionArea(); updateTextsCache(); @@ -274,15 +424,17 @@ void KFileItemListWidget::updateExpansionArea() Q_ASSERT(values.contains("expansionLevel")); const KItemListStyleOption& option = styleOption(); const int expansionLevel = values.value("expansionLevel", 0).toInt(); - - const qreal widgetHeight = size().height(); - const qreal expansionLevelSize = KIconLoader::SizeSmall; - const qreal x = option.margin + expansionLevel * widgetHeight; - const qreal y = (widgetHeight - expansionLevelSize) / 2; - m_expansionArea = QRectF(x, y, expansionLevelSize, expansionLevelSize); - } else { - m_expansionArea = QRectF(); + if (expansionLevel >= 0) { + const qreal widgetHeight = size().height(); + const qreal expansionLevelSize = KIconLoader::SizeSmall; + const qreal x = option.margin + expansionLevel * widgetHeight; + const qreal y = (widgetHeight - expansionLevelSize) / 2; + m_expansionArea = QRectF(x, y, expansionLevelSize, expansionLevelSize); + return; + } } + + m_expansionArea = QRectF(); } void KFileItemListWidget::updatePixmapCache() @@ -310,7 +462,8 @@ void KFileItemListWidget::updatePixmapCache() if (!updatePixmap && m_dirtyContent) { updatePixmap = m_dirtyContentRoles.isEmpty() || m_dirtyContentRoles.contains("iconPixmap") - || m_dirtyContentRoles.contains("iconName"); + || m_dirtyContentRoles.contains("iconName") + || m_dirtyContentRoles.contains("iconOverlays"); } if (updatePixmap) { @@ -324,7 +477,7 @@ void KFileItemListWidget::updatePixmapCache() iconName = QLatin1String("unknown"); } m_pixmap = pixmapForIcon(iconName, iconHeight); - m_hoverPixmapRect.setSize(m_pixmap.size()); + m_originalPixmapSize = m_pixmap.size(); } else if (m_pixmap.size() != QSize(iconHeight, iconHeight)) { // A custom pixmap has been applied. Assure that the pixmap // is scaled to the available size. @@ -333,7 +486,7 @@ void KFileItemListWidget::updatePixmapCache() if (scale) { KPixmapModifier::scale(m_pixmap, QSize(iconHeight, iconHeight)); } - m_hoverPixmapRect.setSize(m_pixmap.size()); + m_originalPixmapSize = m_pixmap.size(); // To simplify the handling of scaling the original pixmap // will be embedded into a square pixmap. @@ -341,19 +494,44 @@ void KFileItemListWidget::updatePixmapCache() squarePixmap.fill(Qt::transparent); QPainter painter(&squarePixmap); + int x, y; if (iconOnTop) { - const int x = (iconHeight - m_pixmap.width()) / 2; // Center horizontally - const int y = iconHeight - m_pixmap.height(); // Align on bottom + x = (iconHeight - m_pixmap.width()) / 2; // Center horizontally + y = iconHeight - m_pixmap.height(); // Align on bottom painter.drawPixmap(x, y, m_pixmap); } else { - const int x = iconHeight - m_pixmap.width(); // Align right - const int y = (iconHeight - m_pixmap.height()) / 2; // Center vertically + x = iconHeight - m_pixmap.width(); // Align right + y = (iconHeight - m_pixmap.height()) / 2; // Center vertically painter.drawPixmap(x, y, m_pixmap); } m_pixmap = squarePixmap; } else { - m_hoverPixmapRect.setSize(m_pixmap.size()); + m_originalPixmapSize = m_pixmap.size(); + } + + const QStringList overlays = values["iconOverlays"].toStringList(); + + // 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. + foreach (const QString& overlay, overlays) { + if (!overlay.isEmpty()) { + // There is at least one overlay, draw all overlays above m_pixmap + // and cancel the check + KIconLoader::global()->drawOverlays(overlays, m_pixmap, KIconLoader::Desktop); + break; + } + } + + if (m_isCut) { + applyCutEffect(m_pixmap); + } + + if (m_isHidden) { + applyHiddenEffect(m_pixmap); } Q_ASSERT(m_pixmap.height() == iconHeight); @@ -373,17 +551,26 @@ void KFileItemListWidget::updatePixmapCache() m_pixmapPos.setY(option.margin); // Center the hover rectangle horizontally and align it on bottom - const qreal x = m_pixmapPos.x() + (m_scaledPixmapSize.width() - m_hoverPixmapRect.width()) / 2.0; - const qreal y = m_pixmapPos.y() + m_scaledPixmapSize.height() - m_hoverPixmapRect.height(); - m_hoverPixmapRect.moveTopLeft(QPointF(x, y)); - + qreal hoverWidth = m_originalPixmapSize.width(); + qreal hoverHeight = m_originalPixmapSize.height(); + if (scaledIconHeight != m_pixmap.height()) { + const qreal scaleFactor = qreal(scaledIconHeight) / qreal(m_pixmap.height()); + hoverWidth *= scaleFactor; + hoverHeight *= scaleFactor; + } + const qreal hoverX = m_pixmapPos.x() + (m_scaledPixmapSize.width() - hoverWidth) / 2.0; + const qreal hoverY = m_pixmapPos.y() + m_scaledPixmapSize.height() - hoverHeight; + m_iconRect = QRectF(hoverX, hoverY, hoverWidth, hoverHeight); + const qreal margin = option.margin; + m_iconRect.adjust(-margin, -margin, margin, margin); + // 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); + m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState); } else { m_hoverPixmap = m_pixmap; } @@ -469,10 +656,10 @@ void KFileItemListWidget::updateIconsLayoutTextCache() m_text[Name].setTextWidth(maxWidth); m_textPos[Name] = QPointF(option.margin, widgetHeight - textLinesCount * fontHeight - option.margin); - m_textBoundingRect = QRectF(option.margin + (maxWidth - requiredWidthForName) / 2, - m_textPos[Name].y(), - requiredWidthForName, - m_text[Name].size().height()); + m_textRect = QRectF(option.margin + (maxWidth - requiredWidthForName) / 2, + m_textPos[Name].y(), + requiredWidthForName, + textLinesCountForName * fontHeight); // Calculate the position for each additional information qreal y = m_textPos[Name].y() + textLinesCountForName * fontHeight; @@ -482,7 +669,7 @@ void KFileItemListWidget::updateIconsLayoutTextCache() continue; } - const QString text = roleText(textId, values[role]); + const QString text = roleText(role, values); m_text[textId].setText(text); qreal requiredWidth = 0; @@ -508,15 +695,15 @@ void KFileItemListWidget::updateIconsLayoutTextCache() m_textPos[textId] = QPointF(option.margin, y); m_text[textId].setTextWidth(maxWidth); - const QRectF textBoundingRect(option.margin + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight); - m_textBoundingRect |= textBoundingRect; + const QRectF textRect(option.margin + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight); + m_textRect |= textRect; y += fontHeight; } - // Add a margin to the text bounding rectangle + // Add a margin to the text rectangle const qreal margin = option.margin; - m_textBoundingRect.adjust(-margin, -margin, margin, margin); + m_textRect.adjust(-margin, -margin, margin, margin); } void KFileItemListWidget::updateCompactLayoutTextCache() @@ -540,7 +727,7 @@ void KFileItemListWidget::updateCompactLayoutTextCache() foreach (const QByteArray& role, m_sortedVisibleRoles) { const TextId textId = roleTextId(role); - const QString text = roleText(textId, values[role]); + const QString text = roleText(role, values); m_text[textId].setText(text); qreal requiredWidth = option.fontMetrics.width(text); @@ -558,7 +745,7 @@ void KFileItemListWidget::updateCompactLayoutTextCache() y += fontHeight; } - m_textBoundingRect = QRectF(x - option.margin, 0, maximumRequiredTextWidth + 2 * option.margin, widgetHeight); + m_textRect = QRectF(x - option.margin, 0, maximumRequiredTextWidth + 2 * option.margin, widgetHeight); } void KFileItemListWidget::updateDetailsLayoutTextCache() @@ -569,7 +756,7 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() // +------+ // | Icon | Name role Additional role 1 Additional role 2 // +------+ - m_textBoundingRect = QRectF(); + m_textRect = QRectF(); const KItemListStyleOption& option = styleOption(); const QHash values = data(); @@ -578,34 +765,46 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() const int scaledIconSize = widgetHeight - 2 * option.margin; const int fontHeight = option.fontMetrics.height(); - qreal x = m_expansionArea.right() + option.margin * 3 + scaledIconSize; + const qreal columnMargin = option.margin * 3; + const qreal firstColumnInc = m_expansionArea.right() + option.margin * 2 + scaledIconSize; + qreal x = firstColumnInc; const qreal y = qMax(qreal(option.margin), (widgetHeight - fontHeight) / 2); foreach (const QByteArray& role, m_sortedVisibleRoles) { const TextId textId = roleTextId(role); - const QString text = roleText(textId, values[role]); - m_text[textId].setText(text); - - const qreal requiredWidth = option.fontMetrics.width(text); - m_textPos[textId] = QPointF(x, y); + QString text = roleText(role, values); + // Elide the text in case it does not fit into the available column-width + qreal requiredWidth = option.fontMetrics.width(text); const qreal columnWidth = visibleRolesSizes().value(role, QSizeF(0, 0)).width(); + qreal availableTextWidth = columnWidth - 2 * columnMargin; + if (textId == Name) { + availableTextWidth -= firstColumnInc; + } + + if (requiredWidth > availableTextWidth) { + text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth); + requiredWidth = option.fontMetrics.width(text); + } + + m_text[textId].setText(text); + m_textPos[textId] = QPointF(x + columnMargin, y); x += columnWidth; switch (textId) { case Name: { - m_textBoundingRect = QRectF(m_textPos[textId].x() - option.margin, 0, - requiredWidth + 2 * option.margin, size().height()); + m_textRect = QRectF(m_textPos[textId].x() - option.margin, 0, + requiredWidth + 2 * option.margin, 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 -= m_expansionArea.right(); + x -= firstColumnInc; break; } case Size: // The values for the size should be right aligned - m_textPos[textId].rx() += columnWidth - requiredWidth - 2 * option.margin; + m_textPos[textId].rx() += columnWidth - requiredWidth - 2 * columnMargin; break; default: @@ -616,11 +815,19 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() void KFileItemListWidget::updateAdditionalInfoTextColor() { + QColor c1; + if (m_customTextColor.isValid()) { + c1 = m_customTextColor; + } else if (isSelected() && m_layout != DetailsLayout) { + c1 = styleOption().palette.highlightedText().color(); + } else { + c1 = styleOption().palette.text().color(); + } + // For the color of the additional info the inactive text color // is not used as this might lead to unreadable text for some color schemes. Instead - // the text color is slightly mixed with the background color. - const QColor c1 = textColor(); - const QColor c2 = styleOption().palette.background().color(); + // the text color c1 is slightly mixed with the background color. + 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, @@ -628,59 +835,8 @@ void KFileItemListWidget::updateAdditionalInfoTextColor() (c1.blue() * p1 + c2.blue() * p2) / 100); } -QString KFileItemListWidget::roleText(TextId textId, const QVariant& roleValue) const -{ - QString text; - - switch (textId) { - case Name: - case Permissions: - case Owner: - case Group: - case Type: - case Destination: - case Path: - text = roleValue.toString(); - break; - - case Size: { - if (data().value("isDir").toBool()) { - // The item represents a directory. Show the number of sub directories - // instead of the file size of the directory. - if (!roleValue.isNull()) { - const KIO::filesize_t size = roleValue.value(); - text = i18ncp("@item:intable", "%1 item", "%1 items", size); - } - } else { - const KIO::filesize_t size = roleValue.value(); - text = KIO::convertSize(size); - } - break; - } - - case Date: { - const QDateTime dateTime = roleValue.toDateTime(); - text = KGlobal::locale()->formatDateTime(dateTime); - break; - } - - default: - Q_ASSERT(false); - break; - } - - return text; -} - void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap) { - const bool isHiddenItem = m_text[Name].text().startsWith(QLatin1Char('.')); - qreal opacity; - if (isHiddenItem) { - opacity = painter->opacity(); - painter->setOpacity(opacity * 0.3); - } - if (m_scaledPixmapSize != pixmap.size()) { QPixmap scaledPixmap = pixmap; KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize); @@ -693,10 +849,6 @@ void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap) } else { painter->drawPixmap(m_pixmapPos, pixmap); } - - if (isHiddenItem) { - painter->setOpacity(opacity); - } } QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size) @@ -748,4 +900,15 @@ KFileItemListWidget::TextId KFileItemListWidget::roleTextId(const QByteArray& ro return rolesHash.value(role); } +void KFileItemListWidget::applyCutEffect(QPixmap& pixmap) +{ + KIconEffect* effect = KIconLoader::global()->iconEffect(); + pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); +} + +void KFileItemListWidget::applyHiddenEffect(QPixmap& pixmap) +{ + KIconEffect::semiTransparent(pixmap); +} + #include "kfileitemlistwidget.moc"