X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/0fa4b713aecbfc9035fdeb66fc64df56ce5ab321..fdab593c01964ff4e5bf6c7cc093b802d0a1bbf7:/src/kitemviews/kfileitemlistwidget.cpp diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp index 62a2383d4..80a3a3183 100644 --- a/src/kitemviews/kfileitemlistwidget.cpp +++ b/src/kitemviews/kfileitemlistwidget.cpp @@ -20,8 +20,8 @@ #include "kfileitemlistwidget.h" #include "kfileitemclipboard_p.h" +#include "kfileitemlistview.h" #include "kfileitemmodel.h" -#include "kitemlistview.h" #include "kpixmapmodifier_p.h" #include @@ -45,6 +45,7 @@ KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) : m_isCut(false), m_isHidden(false), m_isExpandable(false), + m_supportsItemExpanding(false), m_dirtyLayout(true), m_dirtyContent(true), m_dirtyContentRoles(), @@ -52,11 +53,9 @@ KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) : m_pixmapPos(), m_pixmap(), m_scaledPixmapSize(), - m_originalPixmapSize(), m_iconRect(), m_hoverPixmap(), - m_textPos(), - m_text(), + m_textInfo(), m_textRect(), m_sortedVisibleRoles(), m_expansionArea(), @@ -64,14 +63,12 @@ KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) : m_additionalInfoTextColor(), m_overlay() { - for (int i = 0; i < TextIdCount; ++i) { - m_text[i].setTextFormat(Qt::PlainText); - m_text[i].setPerformanceHint(QStaticText::AggressiveCaching); - } } KFileItemListWidget::~KFileItemListWidget() { + qDeleteAll(m_textInfo); + m_textInfo.clear(); } void KFileItemListWidget::setLayout(Layout layout) @@ -79,6 +76,7 @@ void KFileItemListWidget::setLayout(Layout layout) if (m_layout != layout) { m_layout = layout; m_dirtyLayout = true; + updateAdditionalInfoTextColor(); update(); } } @@ -88,19 +86,28 @@ KFileItemListWidget::Layout KFileItemListWidget::layout() const return m_layout; } +void KFileItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding) +{ + if (m_supportsItemExpanding != supportsItemExpanding) { + m_supportsItemExpanding = supportsItemExpanding; + m_dirtyLayout = true; + update(); + } +} + +bool KFileItemListWidget::supportsItemExpanding() const +{ + return m_supportsItemExpanding; +} + void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { const_cast(this)->triggerCacheRefreshing(); KItemListWidget::paint(painter, option, widget); - // Draw expansion toggle '>' or 'V' - if (m_isExpandable && !m_expansionArea.isEmpty()) { - QStyleOption arrowOption; - arrowOption.rect = m_expansionArea.toRect(); - const QStyle::PrimitiveElement arrow = data()["isExpanded"].toBool() - ? QStyle::PE_IndicatorArrowDown : QStyle::PE_IndicatorArrowRight; - style()->drawPrimitive(arrow, &arrowOption, painter); + if (!m_expansionArea.isEmpty()) { + drawSiblingsInformation(painter); } const KItemListStyleOption& itemListStyleOption = styleOption(); @@ -121,15 +128,16 @@ void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsIte painter->setFont(itemListStyleOption.font); painter->setPen(textColor()); - painter->drawStaticText(m_textPos[Name], m_text[Name]); + const TextInfo* textInfo = m_textInfo.value("name"); + painter->drawStaticText(textInfo->pos, textInfo->staticText); bool clipAdditionalInfoBounds = false; - if (m_layout == DetailsLayout) { + 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.margin; - if (m_textPos[Name + 1].x() < minX) { + const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding; + if (textInfo->pos.x() + columnWidth("name") > minX) { clipAdditionalInfoBounds = true; painter->save(); painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip); @@ -138,8 +146,10 @@ void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsIte painter->setPen(m_additionalInfoTextColor); painter->setFont(itemListStyleOption.font); - for (int i = Name + 1; i < TextIdCount; ++i) { - painter->drawStaticText(m_textPos[i], m_text[i]); + + for (int i = 1; i < m_sortedVisibleRoles.count(); ++i) { + const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles[i]); + painter->drawStaticText(textInfo->pos, textInfo->staticText); } if (clipAdditionalInfoBounds) { @@ -169,6 +179,24 @@ QRectF KFileItemListWidget::textRect() const return m_textRect; } +QRectF KFileItemListWidget::textFocusRect() const +{ + const_cast(this)->triggerCacheRefreshing(); + if (m_layout == CompactLayout) { + // In the compact layout a larger textRect() is returned to be aligned + // with the iconRect(). This is useful to have a larger selection/hover-area + // 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. + QRectF rect = m_textRect; + 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; + } + return m_textRect; +} + QRectF KFileItemListWidget::expansionToggleRect() const { const_cast(this)->triggerCacheRefreshing(); @@ -179,7 +207,7 @@ QRectF KFileItemListWidget::selectionToggleRect() const { const_cast(this)->triggerCacheRefreshing(); - const int iconHeight = m_pixmap.height(); + const int iconHeight = styleOption().iconSize; int toggleSize = KIconLoader::SizeSmall; if (iconHeight >= KIconLoader::SizeEnormous) { @@ -199,10 +227,12 @@ QRectF KFileItemListWidget::selectionToggleRect() const const int minMargin = 2; if (toggleSize + minMargin * 2 >= widgetHeight) { + pos.rx() -= (widgetHeight - toggleSize) / 2; toggleSize = widgetHeight; pos.setY(0); } if (toggleSize + minMargin * 2 >= widgetWidth) { + pos.ry() -= (widgetWidth - toggleSize) / 2; toggleSize = widgetWidth; pos.setX(0); } @@ -210,46 +240,61 @@ QRectF KFileItemListWidget::selectionToggleRect() const return QRectF(pos, QSizeF(toggleSize, toggleSize)); } -QString KFileItemListWidget::roleText(const QByteArray& role, const QHash& values) +QSizeF KFileItemListWidget::itemSizeHint(int index, const KItemListView* view) { - QString text; - const QVariant roleValue = values.value(role); + const QHash values = view->model()->data(index); + const KItemListStyleOption& option = view->styleOption(); + const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); - switch (roleTextId(role)) { - case Name: - case Permissions: - case Owner: - case Group: - case Type: - case Destination: - case Path: - text = roleValue.toString(); - break; + switch (static_cast(view)->itemLayout()) { + case IconsLayout: { + const QString text = KStringHandler::preProcessWrap(values["name"].toString()); - 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); + const qreal maxWidth = view->itemSize().width() - 2 * option.padding; + QTextLine line; + + // Calculate the number of lines required for wrapping the name + QTextOption textOption(Qt::AlignHCenter); + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + + qreal textHeight = 0; + QTextLayout layout(text, option.font); + layout.setTextOption(textOption); + layout.beginLayout(); + while ((line = layout.createLine()).isValid()) { + line.setLineWidth(maxWidth); + line.naturalTextWidth(); + textHeight += line.height(); } - break; + layout.endLayout(); + + // Add one line for each additional information + const qreal height = textHeight + + additionalRolesCount * option.fontMetrics.lineSpacing() + + option.iconSize + + option.padding * 3; + return QSizeF(view->itemSize().width(), height); } - case Date: { - const QDateTime dateTime = roleValue.toDateTime(); - text = KGlobal::locale()->formatDateTime(dateTime); - break; + case CompactLayout: { + // For each row exactly one role is shown. Calculate the maximum required width that is necessary + // to show all roles without horizontal clipping. + qreal maximumRequiredWidth = 0.0; + + foreach (const QByteArray& role, view->visibleRoles()) { + const QString text = KFileItemListWidget::roleText(role, values); + const qreal requiredWidth = option.fontMetrics.width(text); + maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth); + } + + const qreal width = option.padding * 4 + option.iconSize + maximumRequiredWidth; + const qreal height = option.padding * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * option.fontMetrics.lineSpacing()); + return QSizeF(width, height); + } + + case DetailsLayout: { + const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height()); + return QSizeF(-1, height); } default: @@ -257,7 +302,34 @@ QString KFileItemListWidget::roleText(const QByteArray& role, const QHash values = view->model()->data(index); + const KItemListStyleOption& option = view->styleOption(); + + const QString text = KFileItemListWidget::roleText(role, values); + if (!text.isEmpty()) { + const qreal columnPadding = option.padding * 3; + width = qMax(width, qreal(2 * columnPadding + option.fontMetrics.width(text))); + } + + if (role == "name") { + // Increase the width by the expansion-toggle and the current expansion level + const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); + width += option.padding + (expandedParentsCount + 1) * view->itemSize().height() + KIconLoader::SizeSmall; + + // Increase the width by the required space for the icon + width += option.padding * 2 + option.iconSize; + } + + return width; } void KFileItemListWidget::invalidateCache() @@ -305,7 +377,8 @@ QPixmap KFileItemListWidget::overlay() const void KFileItemListWidget::dataChanged(const QHash& current, const QSet& roles) { - KItemListWidget::dataChanged(current, roles); + Q_UNUSED(current); + m_dirtyContent = true; QSet dirtyRoles; @@ -327,22 +400,26 @@ void KFileItemListWidget::dataChanged(const QHash& current void KFileItemListWidget::visibleRolesChanged(const QList& current, const QList& previous) { - KItemListWidget::visibleRolesChanged(current, previous); + Q_UNUSED(previous); m_sortedVisibleRoles = current; m_dirtyLayout = true; } -void KFileItemListWidget::visibleRolesSizesChanged(const QHash& current, - const QHash& previous) +void KFileItemListWidget::columnWidthChanged(const QByteArray& role, + qreal current, + qreal previous) { - KItemListWidget::visibleRolesSizesChanged(current, previous); + Q_UNUSED(role); + Q_UNUSED(current); + Q_UNUSED(previous); m_dirtyLayout = true; } void KFileItemListWidget::styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) { - KItemListWidget::styleOptionChanged(current, previous); + Q_UNUSED(current); + Q_UNUSED(previous); updateAdditionalInfoTextColor(); m_dirtyLayout = true; } @@ -359,6 +436,14 @@ void KFileItemListWidget::selectedChanged(bool selected) updateAdditionalInfoTextColor(); } +void KFileItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + m_dirtyLayout = true; +} + + void KFileItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) { KItemListWidget::resizeEvent(event); @@ -408,7 +493,7 @@ void KFileItemListWidget::triggerCacheRefreshing() refreshCache(); const QHash values = data(); - m_isExpandable = values["isExpandable"].toBool(); + m_isExpandable = m_supportsItemExpanding && values["isExpandable"].toBool(); m_isHidden = values["name"].toString().startsWith(QLatin1Char('.')); updateExpansionArea(); @@ -422,17 +507,16 @@ void KFileItemListWidget::triggerCacheRefreshing() void KFileItemListWidget::updateExpansionArea() { - if (m_layout == DetailsLayout) { + if (m_supportsItemExpanding) { const QHash values = data(); - Q_ASSERT(values.contains("expansionLevel")); - const KItemListStyleOption& option = styleOption(); - const int expansionLevel = values.value("expansionLevel", 0).toInt(); - if (expansionLevel >= 0) { + Q_ASSERT(values.contains("expandedParentsCount")); + const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); + if (expandedParentsCount >= 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); + const qreal inc = (widgetHeight - KIconLoader::SizeSmall) / 2; + const qreal x = expandedParentsCount * widgetHeight + inc; + const qreal y = inc; + m_expansionArea = QRectF(x, y, KIconLoader::SizeSmall, KIconLoader::SizeSmall); return; } } @@ -445,23 +529,17 @@ void KFileItemListWidget::updatePixmapCache() // Precondition: Requires already updated m_textPos values to calculate // the remaining height when the alignment is vertical. + const QSizeF widgetSize = size(); const bool iconOnTop = (m_layout == IconsLayout); const KItemListStyleOption& option = styleOption(); - const int iconHeight = option.iconSize; + const qreal padding = option.padding; - const QHash values = data(); - const QSizeF widgetSize = size(); + const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : option.iconSize; + const int maxIconHeight = option.iconSize; - int scaledIconHeight = 0; - if (iconOnTop) { - scaledIconHeight = static_cast(m_textPos[Name].y() - 3 * option.margin); - } else { - const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1; - const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height(); - scaledIconHeight = (requiredTextHeight < iconHeight) ? widgetSize.height() - 2 * option.margin : iconHeight; - } + const QHash values = data(); - bool updatePixmap = (iconHeight != m_pixmap.height()); + bool updatePixmap = (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight); if (!updatePixmap && m_dirtyContent) { updatePixmap = m_dirtyContentRoles.isEmpty() || m_dirtyContentRoles.contains("iconPixmap") @@ -479,34 +557,11 @@ void KFileItemListWidget::updatePixmapCache() // use a generic icon as fallback iconName = QLatin1String("unknown"); } - m_pixmap = pixmapForIcon(iconName, iconHeight); - m_originalPixmapSize = m_pixmap.size(); - } else if (m_pixmap.size() != QSize(iconHeight, iconHeight)) { + m_pixmap = pixmapForIcon(iconName, maxIconHeight); + } else if (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight) { // A custom pixmap has been applied. Assure that the pixmap - // is scaled to the available size. - const bool scale = m_pixmap.width() > iconHeight || m_pixmap.height() > iconHeight || - (m_pixmap.width() < iconHeight && m_pixmap.height() < iconHeight); - if (scale) { - KPixmapModifier::scale(m_pixmap, QSize(iconHeight, iconHeight)); - } - m_originalPixmapSize = m_pixmap.size(); - - // To simplify the handling of scaling the original pixmap - // will be embedded into a square pixmap. - QPixmap squarePixmap(iconHeight, iconHeight); - squarePixmap.fill(Qt::transparent); - - QPainter painter(&squarePixmap); - const int x = (iconHeight - m_pixmap.width()) / 2; // Center horizontally - int y = iconHeight - m_pixmap.height(); // Move to bottom - if (!iconOnTop) { - y /= 2.0; // Center vertically - } - painter.drawPixmap(x, y, m_pixmap); - - m_pixmap = squarePixmap; - } else { - m_originalPixmapSize = m_pixmap.size(); + // is scaled to the maximum available size. + KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight)); } const QStringList overlays = values["iconOverlays"].toStringList(); @@ -532,42 +587,45 @@ void KFileItemListWidget::updatePixmapCache() if (m_isHidden) { applyHiddenEffect(m_pixmap); } - - Q_ASSERT(m_pixmap.height() == iconHeight); } + if (!m_overlay.isNull()) { QPainter painter(&m_pixmap); painter.drawPixmap(0, m_pixmap.height() - m_overlay.height(), m_overlay); } - m_scaledPixmapSize = QSize(scaledIconHeight, scaledIconHeight); + int scaledIconSize = 0; + if (iconOnTop) { + const TextInfo* textInfo = m_textInfo.value("name"); + scaledIconSize = static_cast(textInfo->pos.y() - 2 * padding); + } else { + const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1; + const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height(); + 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, maxScaledIconHeight, Qt::KeepAspectRatio); if (iconOnTop) { + // Center horizontally and align on bottom within the icon-area m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2); + m_pixmapPos.setY(padding + scaledIconSize - m_scaledPixmapSize.height()); } else { - m_pixmapPos.setX(m_textPos[Name].x() - 2 * option.margin - scaledIconHeight); + // Center horizontally and vertically within the icon-area + const TextInfo* textInfo = m_textInfo.value("name"); + m_pixmapPos.setX(textInfo->pos.x() - 2 * padding + - (scaledIconSize + m_scaledPixmapSize.width()) / 2); + m_pixmapPos.setY(padding + + (scaledIconSize - m_scaledPixmapSize.height()) / 2); } - m_pixmapPos.setY(option.margin); - // Center the hover rectangle horizontally and align it on bottom - 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; - qreal hoverY = m_scaledPixmapSize.height() - hoverHeight; - if (!iconOnTop) { - hoverY /= 2.0; - } - hoverY += m_pixmapPos.y(); + m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)); - 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; @@ -602,9 +660,14 @@ void KFileItemListWidget::updateTextsCache() break; } - for (int i = 0; i < TextIdCount; ++i) { - m_text[i].setText(QString()); - m_text[i].setTextOption(textOption); + qDeleteAll(m_textInfo); + m_textInfo.clear(); + for (int i = 0; i < m_sortedVisibleRoles.count(); ++i) { + TextInfo* textInfo = new TextInfo(); + textInfo->staticText.setTextFormat(Qt::PlainText); + textInfo->staticText.setPerformanceHint(QStaticText::AggressiveCaching); + textInfo->staticText.setTextOption(textOption); + m_textInfo.insert(m_sortedVisibleRoles[i], textInfo); } switch (m_layout) { @@ -630,56 +693,58 @@ void KFileItemListWidget::updateIconsLayoutTextCache() const QHash values = data(); const KItemListStyleOption& option = styleOption(); - const qreal maxWidth = size().width() - 2 * option.margin; + const qreal padding = option.padding; + const qreal maxWidth = size().width() - 2 * padding; const qreal widgetHeight = size().height(); - const qreal fontHeight = option.fontMetrics.height(); + const qreal lineSpacing = option.fontMetrics.lineSpacing(); // Initialize properties for the "name" role. It will be used as anchor // for initializing the position of the other roles. - m_text[Name].setText(KStringHandler::preProcessWrap(values["name"].toString())); + TextInfo* nameTextInfo = m_textInfo.value("name"); + nameTextInfo->staticText.setText(KStringHandler::preProcessWrap(values["name"].toString())); // Calculate the number of lines required for the name and the required width - int textLinesCountForName = 0; - qreal requiredWidthForName = 0; + qreal nameWidth = 0; + qreal nameHeight = 0; QTextLine line; - QTextLayout layout(m_text[Name].text(), option.font); - layout.setTextOption(m_text[Name].textOption()); + QTextLayout layout(nameTextInfo->staticText.text(), option.font); + layout.setTextOption(nameTextInfo->staticText.textOption()); layout.beginLayout(); while ((line = layout.createLine()).isValid()) { line.setLineWidth(maxWidth); - requiredWidthForName = qMax(requiredWidthForName, line.naturalTextWidth()); - ++textLinesCountForName; + nameWidth = qMax(nameWidth, line.naturalTextWidth()); + nameHeight += line.height(); } layout.endLayout(); // Use one line for each additional information - int textLinesCount = textLinesCountForName; const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0); - textLinesCount += additionalRolesCount; - - m_text[Name].setTextWidth(maxWidth); - m_textPos[Name] = QPointF(option.margin, widgetHeight - textLinesCount * fontHeight - option.margin); - m_textRect = QRectF(option.margin + (maxWidth - requiredWidthForName) / 2, - m_textPos[Name].y(), - requiredWidthForName, - textLinesCountForName * fontHeight); + 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); // Calculate the position for each additional information - qreal y = m_textPos[Name].y() + textLinesCountForName * fontHeight; + qreal y = nameTextInfo->pos.y() + nameHeight; foreach (const QByteArray& role, m_sortedVisibleRoles) { - const TextId textId = roleTextId(role); - if (textId == Name) { + if (role == "name") { continue; } const QString text = roleText(role, values); - m_text[textId].setText(text); + TextInfo* textInfo = m_textInfo.value(role); + textInfo->staticText.setText(text); qreal requiredWidth = 0; QTextLayout layout(text, option.font); - layout.setTextOption(m_text[textId].textOption()); + layout.setTextOption(textInfo->staticText.textOption()); layout.beginLayout(); QTextLine textLine = layout.createLine(); if (textLine.isValid()) { @@ -689,25 +754,24 @@ void KFileItemListWidget::updateIconsLayoutTextCache() // TODO: QFontMetrics::elidedText() works different regarding the given width // in comparison to QTextLine::setLineWidth(). It might happen that the text does // not get elided although it does not fit into the given width. As workaround - // the margin is substracted. - const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth - option.margin); - m_text[textId].setText(elidedText); + // the padding is substracted. + const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth - padding); + textInfo->staticText.setText(elidedText); } } layout.endLayout(); - m_textPos[textId] = QPointF(option.margin, y); - m_text[textId].setTextWidth(maxWidth); + textInfo->pos = QPointF(padding, y); + textInfo->staticText.setTextWidth(maxWidth); - const QRectF textRect(option.margin + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight); + const QRectF textRect(padding + (maxWidth - requiredWidth) / 2, y, requiredWidth, lineSpacing); m_textRect |= textRect; - y += fontHeight; + y += lineSpacing; } - // Add a margin to the text rectangle - const qreal margin = option.margin; - m_textRect.adjust(-margin, -margin, margin, margin); + // Add a padding to the text rectangle + m_textRect.adjust(-padding, -padding, padding, padding); } void KFileItemListWidget::updateCompactLayoutTextCache() @@ -720,36 +784,35 @@ void KFileItemListWidget::updateCompactLayoutTextCache() const KItemListStyleOption& option = styleOption(); const qreal widgetHeight = size().height(); - const qreal fontHeight = option.fontMetrics.height(); - const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * fontHeight; - const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.margin : option.iconSize; + const qreal lineSpacing = option.fontMetrics.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.margin * 3 + scaledIconSize; - qreal y = (widgetHeight - textLinesHeight) / 2; - const qreal maxWidth = size().width() - x - option.margin; + const qreal x = option.padding * 3 + scaledIconSize; + qreal y = qRound((widgetHeight - textLinesHeight) / 2); + const qreal maxWidth = size().width() - x - option.padding; foreach (const QByteArray& role, m_sortedVisibleRoles) { - const TextId textId = roleTextId(role); - const QString text = roleText(role, values); - m_text[textId].setText(text); + TextInfo* textInfo = m_textInfo.value(role); + textInfo->staticText.setText(text); qreal requiredWidth = option.fontMetrics.width(text); if (requiredWidth > maxWidth) { requiredWidth = maxWidth; const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth); - m_text[textId].setText(elidedText); + textInfo->staticText.setText(elidedText); } - m_textPos[textId] = QPointF(x, y); - m_text[textId].setTextWidth(maxWidth); + textInfo->pos = QPointF(x, y); + textInfo->staticText.setTextWidth(maxWidth); maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth); - y += fontHeight; + y += lineSpacing; } - m_textRect = QRectF(x - option.margin, 0, maximumRequiredTextWidth + 2 * option.margin, widgetHeight); + m_textRect = QRectF(x - option.padding, 0, maximumRequiredTextWidth + 2 * option.padding, widgetHeight); } void KFileItemListWidget::updateDetailsLayoutTextCache() @@ -766,24 +829,30 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() const QHash values = data(); const qreal widgetHeight = size().height(); - const int scaledIconSize = widgetHeight - 2 * option.margin; + const int scaledIconSize = widgetHeight - 2 * option.padding; const int fontHeight = option.fontMetrics.height(); - const qreal columnMargin = option.margin * 3; - const qreal firstColumnInc = m_expansionArea.right() + option.margin * 2 + scaledIconSize; + const qreal columnPadding = option.padding * 3; + qreal firstColumnInc = scaledIconSize; + if (m_supportsItemExpanding) { + firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2; + } else { + firstColumnInc += option.padding; + } + qreal x = firstColumnInc; - const qreal y = qMax(qreal(option.margin), (widgetHeight - fontHeight) / 2); + const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2); foreach (const QByteArray& role, m_sortedVisibleRoles) { - const TextId textId = roleTextId(role); + const RoleType type = roleType(role); 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) { + const qreal roleWidth = columnWidth(role); + qreal availableTextWidth = roleWidth - 2 * columnPadding; + if (type == Name) { availableTextWidth -= firstColumnInc; } @@ -792,14 +861,18 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() requiredWidth = option.fontMetrics.width(text); } - m_text[textId].setText(text); - m_textPos[textId] = QPointF(x + columnMargin, y); - x += columnWidth; + TextInfo* textInfo = m_textInfo.value(role); + textInfo->staticText.setText(text); + textInfo->pos = QPointF(x + columnPadding, y); + x += roleWidth; - switch (textId) { + switch (type) { case Name: { - m_textRect = QRectF(m_textPos[textId].x() - option.margin, 0, - requiredWidth + 2 * option.margin, size().height()); + 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()); // The column after the name should always be aligned on the same x-position independent // from the expansion-level shown in the name column @@ -808,7 +881,7 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() } case Size: // The values for the size should be right aligned - m_textPos[textId].rx() += columnWidth - requiredWidth - 2 * columnMargin; + textInfo->pos.rx() += roleWidth - requiredWidth - 2 * columnPadding; break; default: @@ -848,13 +921,44 @@ void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap) #ifdef KFILEITEMLISTWIDGET_DEBUG painter->setPen(Qt::blue); - painter->drawRect(QRectF(m_pixmapPos, QSizeF(scaledPixmap.size()))); + painter->drawRect(QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize))); #endif } else { painter->drawPixmap(m_pixmapPos, pixmap); } } +void KFileItemListWidget::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; + bool isItemSibling = true; + + const QBitArray siblings = siblingsInformation(); + 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) { + option.state |= QStyle::State_Children; + } + if (data()["isExpanded"].toBool()) { + option.state |= QStyle::State_Open; + } + isItemSibling = false; + } + + style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter); + + siblingRect.translate(-siblingRect.width(), 0); + } +} + QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size) { const KIcon icon(name); @@ -886,33 +990,73 @@ QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size) return pixmap; } -KFileItemListWidget::TextId KFileItemListWidget::roleTextId(const QByteArray& 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); +} + +KFileItemListWidget::RoleType KFileItemListWidget::roleType(const QByteArray& role) { - static QHash rolesHash; + static QHash rolesHash; if (rolesHash.isEmpty()) { rolesHash.insert("name", Name); rolesHash.insert("size", Size); rolesHash.insert("date", Date); - rolesHash.insert("permissions", Permissions); - rolesHash.insert("owner", Owner); - rolesHash.insert("group", Group); - rolesHash.insert("type", Type); - rolesHash.insert("destination", Destination); - rolesHash.insert("path", Path); } - return rolesHash.value(role); + return rolesHash.value(role, Generic); } -void KFileItemListWidget::applyCutEffect(QPixmap& pixmap) +QString KFileItemListWidget::roleText(const QByteArray& role, const QHash& values) { - KIconEffect* effect = KIconLoader::global()->iconEffect(); - pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); -} + QString text; + const QVariant roleValue = values.value(role); -void KFileItemListWidget::applyHiddenEffect(QPixmap& pixmap) -{ - KIconEffect::semiTransparent(pixmap); -} + switch (roleType(role)) { + 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()) { + const int count = roleValue.toInt(); + if (count < 0) { + text = i18nc("@item:intable", "Unknown"); + } else { + text = i18ncp("@item:intable", "%1 item", "%1 items", count); + } + } + } 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; + } + + case Name: + case Generic: + text = roleValue.toString(); + break; + + default: + Q_ASSERT(false); + break; + } + return text; +} #include "kfileitemlistwidget.moc"