X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/d76b113ad10fe207ef23d5dd44c63ee076c71521..20e13c31df64f5fe3dfd410a1b0a0bd78c07ba32:/src/kitemviews/kstandarditemlistwidget.cpp diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index 14a3db066..998acc066 100644 --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -42,6 +42,7 @@ #include #include #include +#include // #define KSTANDARDITEMLISTWIDGET_DEBUG @@ -54,77 +55,25 @@ KStandardItemListWidgetInformant::~KStandardItemListWidgetInformant() { } -QSizeF KStandardItemListWidgetInformant::itemSizeHint(int index, const KItemListView* view) const +void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const { - const QHash values = view->model()->data(index); - const KItemListStyleOption& option = view->styleOption(); - const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); - switch (static_cast(view)->itemLayout()) { - case KStandardItemListWidget::IconsLayout: { - const QString text = KStringHandler::preProcessWrap(values["text"].toString()); - - const qreal itemWidth = view->itemSize().width(); - const qreal maxWidth = itemWidth - 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(); - } - layout.endLayout(); - - // Add one line for each additional information - textHeight += additionalRolesCount * option.fontMetrics.lineSpacing(); - - const qreal maxTextHeight = option.maxTextSize.height(); - if (maxTextHeight > 0 && textHeight > maxTextHeight) { - textHeight = maxTextHeight; - } - - return QSizeF(itemWidth, textHeight + option.iconSize + option.padding * 3); - } - - case KStandardItemListWidget::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 = roleText(role, values); - const qreal requiredWidth = option.fontMetrics.width(text); - maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth); - } + case KStandardItemListWidget::IconsLayout: + calculateIconsLayoutItemSizeHints(logicalHeightHints, logicalWidthHint, view); + break; - qreal width = option.padding * 4 + option.iconSize + maximumRequiredWidth; - const qreal maxWidth = option.maxTextSize.width(); - if (maxWidth > 0 && width > maxWidth) { - width = maxWidth; - } - const qreal height = option.padding * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * option.fontMetrics.lineSpacing()); - return QSizeF(width, height); - } + case KStandardItemListWidget::CompactLayout: + calculateCompactLayoutItemSizeHints(logicalHeightHints, logicalWidthHint, view); + break; - case KStandardItemListWidget::DetailsLayout: { - const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height()); - return QSizeF(-1, height); - } + case KStandardItemListWidget::DetailsLayout: + calculateDetailsLayoutItemSizeHints(logicalHeightHints, logicalWidthHint, view); + break; default: Q_ASSERT(false); break; } - - return QSize(); } qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArray& role, @@ -137,16 +86,22 @@ qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArra const QString text = roleText(role, values); qreal width = KStandardItemListWidget::columnPadding(option); + const QFontMetrics& normalFontMetrics = option.fontMetrics; + const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font)); + if (role == "rating") { width += KStandardItemListWidget::preferredRatingSize(option).width(); } else { - width += option.fontMetrics.width(text); + // 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; + + width += fontMetrics.width(text); if (role == "text") { if (view->supportsItemExpanding()) { // Increase the width by the expansion-toggle and the current expansion level const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); - const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height()); + const qreal height = option.padding * 2 + qMax(option.iconSize, fontMetrics.height()); width += (expandedParentsCount + 1) * height; } @@ -158,6 +113,16 @@ qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArra return width; } +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 +{ + return false; +} + QString KStandardItemListWidgetInformant::roleText(const QByteArray& role, const QHash& values) const { @@ -168,10 +133,127 @@ QString KStandardItemListWidgetInformant::roleText(const QByteArray& role, return values.value(role).toString(); } +QFont KStandardItemListWidgetInformant::customizedFontForLinks(const QFont& baseFont) const +{ + return baseFont; +} + +void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const +{ + 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(); + const qreal maxWidth = itemWidth - 2 * option.padding; + const qreal additionalRolesSpacing = additionalRolesCount * option.fontMetrics.lineSpacing(); + const qreal spacingAndIconHeight = option.iconSize + option.padding * 3; + + const QFont linkFont = customizedFontForLinks(normalFont); + + QTextOption textOption(Qt::AlignHCenter); + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + + for (int index = 0; index < logicalHeightHints.count(); ++index) { + if (logicalHeightHints.at(index) > 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 QString& text = KStringHandler::preProcessWrap(itemText(index, view)); + + // Calculate the number of lines required for wrapping the name + qreal textHeight = 0; + QTextLayout layout(text, font); + layout.setTextOption(textOption); + layout.beginLayout(); + QTextLine line; + int lineCount = 0; + while ((line = layout.createLine()).isValid()) { + line.setLineWidth(maxWidth); + line.naturalTextWidth(); + textHeight += line.height(); + + ++lineCount; + if (lineCount == option.maxTextLines) { + break; + } + } + layout.endLayout(); + + // Add one line for each additional information + textHeight += additionalRolesSpacing; + + logicalHeightHints[index] = textHeight + spacingAndIconHeight; + } + + logicalWidthHint = itemWidth; +} + +void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const +{ + 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 bool showOnlyTextRole = (visibleRoles.count() == 1) && (visibleRoles.first() == "text"); + const qreal maxWidth = option.maxTextWidth; + const qreal paddingAndIconWidth = option.padding * 4 + option.iconSize; + const qreal height = option.padding * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * normalFontMetrics.lineSpacing()); + + const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font)); + + for (int index = 0; index < logicalHeightHints.count(); ++index) { + if (logicalHeightHints.at(index) > 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; + + // 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; + + if (showOnlyTextRole) { + maximumRequiredWidth = fontMetrics.width(itemText(index, view)); + } else { + const QHash& values = view->model()->data(index); + foreach (const QByteArray& role, visibleRoles) { + const QString& text = roleText(role, values); + const qreal requiredWidth = fontMetrics.width(text); + maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth); + } + } + + qreal width = paddingAndIconWidth + maximumRequiredWidth; + if (maxWidth > 0 && width > maxWidth) { + width = maxWidth; + } + + logicalHeightHints[index] = width; + } + + logicalWidthHint = height; +} + +void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const +{ + const KItemListStyleOption& option = view->styleOption(); + const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height()); + logicalHeightHints.fill(height); + 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), @@ -191,7 +273,8 @@ KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant* infor m_additionalInfoTextColor(), m_overlay(), m_rating(), - m_roleEditor(0) + m_roleEditor(0), + m_oldRoleEditor(0) { } @@ -200,7 +283,13 @@ KStandardItemListWidget::~KStandardItemListWidget() qDeleteAll(m_textInfo); m_textInfo.clear(); - delete m_roleEditor; + if (m_roleEditor) { + m_roleEditor->deleteLater(); + } + + if (m_oldRoleEditor) { + m_oldRoleEditor->deleteLater(); + } } void KStandardItemListWidget::setLayout(Layout layout) @@ -244,23 +333,63 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic const KItemListStyleOption& itemListStyleOption = styleOption(); if (isHovered()) { - // Blend the unhovered and hovered pixmap if the hovering - // animation is ongoing if (hoverOpacity() < 1.0) { - drawPixmap(painter, m_pixmap); - } + /* + * Linear interpolation between m_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 + * https://git.reviewboard.kde.org/r/109614/ + */ + // Paint pixmap1 so that pixmap1 = m_pixmap * (1.0 - hoverOpacity()) + QPixmap pixmap1(m_pixmap.size()); + pixmap1.fill(Qt::transparent); + { + QPainter p(&pixmap1); + p.setOpacity(1.0 - hoverOpacity()); + p.drawPixmap(0, 0, m_pixmap); + } + + // Paint pixmap2 so that pixmap2 = m_hoverPixmap * hoverOpacity() + QPixmap pixmap2(pixmap1.size()); + pixmap2.fill(Qt::transparent); + { + QPainter p(&pixmap2); + p.setOpacity(hoverOpacity()); + p.drawPixmap(0, 0, m_hoverPixmap); + } + + // Paint pixmap2 on pixmap1 using CompositionMode_Plus + // Now pixmap1 = pixmap2 + m_pixmap * (1.0 - hoverOpacity()) + // = m_hoverPixmap * hoverOpacity() + m_pixmap * (1.0 - hoverOpacity()) + { + QPainter p(&pixmap1); + p.setCompositionMode(QPainter::CompositionMode_Plus); + p.drawPixmap(0, 0, pixmap2); + } - const qreal opacity = painter->opacity(); - painter->setOpacity(hoverOpacity() * opacity); - drawPixmap(painter, m_hoverPixmap); - painter->setOpacity(opacity); + // Finally paint pixmap1 on the widget + drawPixmap(painter, pixmap1); + } else { + drawPixmap(painter, m_hoverPixmap); + } } else { drawPixmap(painter, m_pixmap); } - painter->setFont(itemListStyleOption.font); + painter->setFont(m_customizedFont); painter->setPen(textColor()); const TextInfo* textInfo = m_textInfo.value("text"); + + if (!textInfo) { + // It seems that we can end up here even if m_textInfo does not contain + // the key "text", see bug 306167. According to triggerCacheRefreshing(), + // this can only happen if the index is negative. This can happen when + // the item is about to be removed, see KItemListView::slotItemsRemoved(). + // TODO: try to reproduce the crash and find a better fix. + return; + } + painter->drawStaticText(textInfo->pos, textInfo->staticText); bool clipAdditionalInfoBounds = false; @@ -277,7 +406,7 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic } painter->setPen(m_additionalInfoTextColor); - painter->setFont(itemListStyleOption.font); + painter->setFont(m_customizedFont); for (int i = 1; i < m_sortedVisibleRoles.count(); ++i) { const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles[i]); @@ -289,7 +418,7 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic QPointF pos = ratingTextInfo->pos; const Qt::Alignment align = ratingTextInfo->staticText.textOption().alignment(); if (align & Qt::AlignHCenter) { - pos.rx() += (size().width() - m_rating.width()) / 2; + pos.rx() += (size().width() - m_rating.width()) / 2 - 2; } painter->drawPixmap(pos, m_rating); } @@ -303,8 +432,11 @@ void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphic painter->setPen(Qt::green); painter->drawRect(m_iconRect); + painter->setPen(Qt::blue); + painter->drawRect(m_textRect); + painter->setPen(Qt::red); - painter->drawText(QPointF(0, itemListStyleOption.fontMetrics.height()), QString::number(index())); + painter->drawText(QPointF(0, m_customizedFontMetrics.height()), QString::number(index())); painter->drawRect(rect()); #endif } @@ -349,7 +481,7 @@ QRectF KStandardItemListWidget::textFocusRect() const const KItemListStyleOption& option = styleOption(); if (option.extendedSelectionRegion) { const QString text = textInfo->staticText.text(); - rect.setWidth(option.fontMetrics.width(text) + 2 * option.padding); + rect.setWidth(m_customizedFontMetrics.width(text) + 2 * option.padding); } return rect; @@ -362,6 +494,29 @@ QRectF KStandardItemListWidget::textFocusRect() const return m_textRect; } +QRectF KStandardItemListWidget::selectionRect() const +{ + const_cast(this)->triggerCacheRefreshing(); + + switch (m_layout) { + case IconsLayout: + return m_textRect; + + case CompactLayout: + case DetailsLayout: { + const int padding = styleOption().padding; + QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); + return adjustedIconRect | m_textRect; + } + + default: + Q_ASSERT(false); + break; + } + + return m_textRect; +} + QRectF KStandardItemListWidget::expansionToggleRect() const { const_cast(this)->triggerCacheRefreshing(); @@ -405,6 +560,32 @@ QRectF KStandardItemListWidget::selectionToggleRect() const return QRectF(pos, QSizeF(toggleSize, toggleSize)); } +QPixmap KStandardItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem* option, + QWidget* widget) +{ + QPixmap pixmap = KItemListWidget::createDragPixmap(option, widget); + if (m_layout != DetailsLayout) { + return pixmap; + } + + // 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; + + QPixmap clippedPixmap(rightClip - leftClip + 1, pixmap.height()); + clippedPixmap.fill(Qt::transparent); + + QPainter painter(&clippedPixmap); + painter.drawPixmap(-leftClip, 0, pixmap); + + return clippedPixmap; +} + + KItemListWidgetInformant* KStandardItemListWidget::createInformant() { return new KStandardItemListWidgetInformant(); @@ -431,6 +612,16 @@ bool KStandardItemListWidget::isHidden() const return false; } +QFont KStandardItemListWidget::customizedFont(const QFont& baseFont) const +{ + return baseFont; +} + +QPalette::ColorRole KStandardItemListWidget::normalTextColorRole() const +{ + return QPalette::Text; +} + void KStandardItemListWidget::setTextColor(const QColor& color) { if (color != m_customTextColor) { @@ -442,13 +633,17 @@ void KStandardItemListWidget::setTextColor(const QColor& color) QColor KStandardItemListWidget::textColor() const { - if (m_customTextColor.isValid() && !isSelected()) { - return m_customTextColor; + if (!isSelected()) { + if (m_isHidden) { + return m_additionalInfoTextColor; + } else if (m_customTextColor.isValid()) { + 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(); + const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : normalTextColorRole(); + return styleOption().palette.color(group, role); } void KStandardItemListWidget::setOverlay(const QPixmap& overlay) @@ -480,12 +675,21 @@ void KStandardItemListWidget::dataChanged(const QHash& cur QSet dirtyRoles; if (roles.isEmpty()) { dirtyRoles = visibleRoles().toSet(); - dirtyRoles.insert("iconPixmap"); - dirtyRoles.insert("iconName"); } else { dirtyRoles = roles; } + // 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(); + const KUrl itemUrl = data().value("url").value(); + m_isCut = clipboard->isCut(itemUrl); + + // The icon-state might depend from other roles and hence is + // marked as dirty whenever a role has been changed + dirtyRoles.insert("iconPixmap"); + dirtyRoles.insert("iconName"); + QSetIterator it(dirtyRoles); while (it.hasNext()) { const QByteArray& role = it.next(); @@ -530,6 +734,7 @@ void KStandardItemListWidget::selectedChanged(bool selected) { Q_UNUSED(selected); updateAdditionalInfoTextColor(); + m_dirtyContent = true; } void KStandardItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous) @@ -539,15 +744,30 @@ void KStandardItemListWidget::siblingsInformationChanged(const QBitArray& curren m_dirtyLayout = true; } +int KStandardItemListWidget::selectionLength(const QString& text) const +{ + return text.length(); +} + void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(previous); - QGraphicsView* parent = scene()->views()[0]; - if (current.isEmpty() || !parent || current != "text") { + QGraphicsView* parent = scene()->views()[0]; + if (current.isEmpty() || !parent || current != "text") { if (m_roleEditor) { emit roleEditingCanceled(index(), current, data().value(current)); - m_roleEditor->deleteLater(); + + disconnect(m_roleEditor, SIGNAL(roleEditingCanceled(QByteArray,QVariant)), + this, SLOT(slotRoleEditingCanceled(QByteArray,QVariant))); + disconnect(m_roleEditor, SIGNAL(roleEditingFinished(QByteArray,QVariant)), + this, SLOT(slotRoleEditingFinished(QByteArray,QVariant))); + + if (m_oldRoleEditor) { + m_oldRoleEditor->deleteLater(); + } + m_oldRoleEditor = m_roleEditor; + m_roleEditor->hide(); m_roleEditor = 0; } return; @@ -558,8 +778,8 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const const TextInfo* textInfo = m_textInfo.value("text"); m_roleEditor = new KItemListRoleEditor(parent); - m_roleEditor->setIndex(index()); m_roleEditor->setRole(current); + m_roleEditor->setFont(styleOption().font); const QString text = data().value(current).toString(); m_roleEditor->setPlainText(text); @@ -567,25 +787,19 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const QTextOption textOption = textInfo->staticText.textOption(); m_roleEditor->document()->setDefaultTextOption(textOption); - // Select the text without MIME-type extension - int selectionLength = text.length(); + const int textSelectionLength = selectionLength(text); - const QString extension = KMimeType::extractKnownExtension(text); - if (!extension.isEmpty()) { - selectionLength -= extension.length() + 1; - } - - if (selectionLength > 0) { + if (textSelectionLength > 0) { QTextCursor cursor = m_roleEditor->textCursor(); cursor.movePosition(QTextCursor::StartOfBlock); - cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectionLength); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, textSelectionLength); m_roleEditor->setTextCursor(cursor); } - connect(m_roleEditor, SIGNAL(roleEditingCanceled(int,QByteArray,QVariant)), - this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant))); - connect(m_roleEditor, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)), - this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant))); + connect(m_roleEditor, SIGNAL(roleEditingCanceled(QByteArray,QVariant)), + this, SLOT(slotRoleEditingCanceled(QByteArray,QVariant))); + connect(m_roleEditor, SIGNAL(roleEditingFinished(QByteArray,QVariant)), + this, SLOT(slotRoleEditingFinished(QByteArray,QVariant))); // Adjust the geometry of the editor QRectF rect = roleEditingRect(current); @@ -646,23 +860,19 @@ void KStandardItemListWidget::slotCutItemsChanged() } } -void KStandardItemListWidget::slotRoleEditingCanceled(int index, - const QByteArray& role, - const QVariant& value) +void KStandardItemListWidget::slotRoleEditingCanceled(const QByteArray& role, + const QVariant& value) { - m_roleEditor->deleteLater(); - m_roleEditor = 0; - emit roleEditingCanceled(index, role, value); + closeRoleEditor(); + emit roleEditingCanceled(index(), role, value); setEditedRole(QByteArray()); } -void KStandardItemListWidget::slotRoleEditingFinished(int index, - const QByteArray& role, - const QVariant& value) +void KStandardItemListWidget::slotRoleEditingFinished(const QByteArray& role, + const QVariant& value) { - m_roleEditor->deleteLater(); - m_roleEditor = 0; - emit roleEditingFinished(index, role, value); + closeRoleEditor(); + emit roleEditingFinished(index(), role, value); setEditedRole(QByteArray()); } @@ -677,6 +887,8 @@ void KStandardItemListWidget::triggerCacheRefreshing() const QHash values = data(); m_isExpandable = m_supportsItemExpanding && values["isExpandable"].toBool(); m_isHidden = isHidden(); + m_customizedFont = customizedFont(styleOption().font); + m_customizedFontMetrics = QFontMetrics(m_customizedFont); updateExpansionArea(); updateTextsCache(); @@ -691,14 +903,14 @@ void KStandardItemListWidget::updateExpansionArea() { if (m_supportsItemExpanding) { const QHash values = data(); - Q_ASSERT(values.contains("expandedParentsCount")); const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); if (expandedParentsCount >= 0) { + const KItemListStyleOption& option = styleOption(); const qreal widgetHeight = size().height(); - const qreal inc = (widgetHeight - KIconLoader::SizeSmall) / 2; + const qreal inc = (widgetHeight - option.iconSize) / 2; const qreal x = expandedParentsCount * widgetHeight + inc; const qreal y = inc; - m_expansionArea = QRectF(x, y, KIconLoader::SizeSmall, KIconLoader::SizeSmall); + m_expansionArea = QRectF(x, y, option.iconSize, option.iconSize); return; } } @@ -739,35 +951,28 @@ void KStandardItemListWidget::updatePixmapCache() // use a generic icon as fallback iconName = QLatin1String("unknown"); } - m_pixmap = pixmapForIcon(iconName, maxIconHeight); + const QStringList overlays = values["iconOverlays"].toStringList(); + m_pixmap = pixmapForIcon(iconName, overlays, 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 maximum available size. KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight)); } - 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); + KIconEffect* effect = KIconLoader::global()->iconEffect(); + m_pixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); } if (m_isHidden) { - applyHiddenEffect(m_pixmap); + KIconEffect::semiTransparent(m_pixmap); + } + + if (m_layout == IconsLayout && isSelected()) { + const QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color(); + QImage image = m_pixmap.toImage(); + KIconEffect::colorize(image, color, 0.8f); + m_pixmap = QPixmap::fromImage(image); } } @@ -782,7 +987,7 @@ void KStandardItemListWidget::updatePixmapCache() 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(); + const qreal requiredTextHeight = textRowsCount * m_customizedFontMetrics.height(); scaledIconSize = (requiredTextHeight < maxIconHeight) ? widgetSize.height() - 2 * padding : maxIconHeight; } @@ -868,7 +1073,7 @@ void KStandardItemListWidget::updateTextsCache() const qreal availableWidth = (m_layout == DetailsLayout) ? columnWidth("rating") - columnPadding(option) - : m_textRect.width(); + : size().width(); if (ratingSize.width() > availableWidth) { ratingSize.rwidth() = availableWidth; } @@ -902,7 +1107,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() const qreal padding = option.padding; const qreal maxWidth = size().width() - 2 * padding; const qreal widgetHeight = size().height(); - const qreal lineSpacing = option.fontMetrics.lineSpacing(); + 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. @@ -915,10 +1120,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() qreal nameHeight = 0; QTextLine line; - const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0); - const int maxNameLines = (option.maxTextSize.height() / int(lineSpacing)) - additionalRolesCount; - - QTextLayout layout(nameTextInfo->staticText.text(), option.font); + QTextLayout layout(nameTextInfo->staticText.text(), m_customizedFont); layout.setTextOption(nameTextInfo->staticText.textOption()); layout.beginLayout(); int nameLineIndex = 0; @@ -928,18 +1130,21 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() nameHeight += line.height(); ++nameLineIndex; - if (nameLineIndex == maxNameLines) { + if (nameLineIndex == option.maxTextLines) { // The maximum number of textlines has been reached. If this is // the case provide an elided text if necessary. const int textLength = line.textStart() + line.textLength(); if (textLength < nameText.length()) { // Elide the last line of the text - QString lastTextLine = nameText.mid(line.textStart(), line.textLength()); - lastTextLine = option.fontMetrics.elidedText(lastTextLine, - Qt::ElideRight, - line.naturalTextWidth() - 1); + QString lastTextLine = nameText.mid(line.textStart()); + lastTextLine = m_customizedFontMetrics.elidedText(lastTextLine, + Qt::ElideRight, + maxWidth); const QString elidedText = nameText.left(line.textStart()) + lastTextLine; nameTextInfo->staticText.setText(elidedText); + + const qreal lastLineWidth = m_customizedFontMetrics.boundingRect(lastTextLine).width(); + nameWidth = qMax(nameWidth, lastLineWidth); } break; } @@ -947,6 +1152,7 @@ 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 - @@ -970,7 +1176,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() qreal requiredWidth = 0; - QTextLayout layout(text, option.font); + QTextLayout layout(text, m_customizedFont); QTextOption textOption; textOption.setWrapMode(QTextOption::NoWrap); layout.setTextOption(textOption); @@ -981,9 +1187,12 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() textLine.setLineWidth(maxWidth); requiredWidth = textLine.naturalTextWidth(); if (requiredWidth > maxWidth) { - const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth); + const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, maxWidth); textInfo->staticText.setText(elidedText); - requiredWidth = option.fontMetrics.width(elidedText); + requiredWidth = m_customizedFontMetrics.width(elidedText); + } else if (role == "rating") { + // Use the width of the rating pixmap, because the rating text is empty. + requiredWidth = m_rating.width(); } } layout.endLayout(); @@ -1011,7 +1220,7 @@ void KStandardItemListWidget::updateCompactLayoutTextCache() const KItemListStyleOption& option = styleOption(); const qreal widgetHeight = size().height(); - const qreal lineSpacing = option.fontMetrics.lineSpacing(); + 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; @@ -1024,10 +1233,10 @@ void KStandardItemListWidget::updateCompactLayoutTextCache() TextInfo* textInfo = m_textInfo.value(role); textInfo->staticText.setText(text); - qreal requiredWidth = option.fontMetrics.width(text); + qreal requiredWidth = m_customizedFontMetrics.width(text); if (requiredWidth > maxWidth) { requiredWidth = maxWidth; - const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth); + const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, maxWidth); textInfo->staticText.setText(elidedText); } @@ -1039,7 +1248,7 @@ void KStandardItemListWidget::updateCompactLayoutTextCache() y += lineSpacing; } - m_textRect = QRectF(x - option.padding, 0, maximumRequiredTextWidth + 2 * option.padding, widgetHeight); + m_textRect = QRectF(x - 2 * option.padding, 0, maximumRequiredTextWidth + 3 * option.padding, widgetHeight); } void KStandardItemListWidget::updateDetailsLayoutTextCache() @@ -1057,7 +1266,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() const qreal widgetHeight = size().height(); const int scaledIconSize = widgetHeight - 2 * option.padding; - const int fontHeight = option.fontMetrics.height(); + const int fontHeight = m_customizedFontMetrics.height(); const qreal columnWidthInc = columnPadding(option); qreal firstColumnInc = scaledIconSize; @@ -1074,7 +1283,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() 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); + qreal requiredWidth = m_customizedFontMetrics.width(text); const qreal roleWidth = columnWidth(role); qreal availableTextWidth = roleWidth - columnWidthInc; @@ -1084,8 +1293,8 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() } if (requiredWidth > availableTextWidth) { - text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth); - requiredWidth = option.fontMetrics.width(text); + text = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth); + requiredWidth = m_customizedFontMetrics.width(text); } TextInfo* textInfo = m_textInfo.value(role); @@ -1097,8 +1306,8 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache() 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() - 2 * option.padding, 0, + textWidth + 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 @@ -1154,6 +1363,7 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter) QRect siblingRect(x, 0, siblingSize, siblingSize); QStyleOption option; + option.palette.setColor(QPalette::Text, option.palette.color(normalTextColorRole())); bool isItemSibling = true; const QBitArray siblings = siblingsInformation(); @@ -1193,46 +1403,77 @@ QRectF KStandardItemListWidget::roleEditingRect(const QByteArray& role) const return rect; } -QPixmap KStandardItemListWidget::pixmapForIcon(const QString& name, int size) -{ - const KIcon icon(name); - - int requestedSize; - if (size <= KIconLoader::SizeSmall) { - requestedSize = KIconLoader::SizeSmall; - } else if (size <= KIconLoader::SizeSmallMedium) { - requestedSize = KIconLoader::SizeSmallMedium; - } else if (size <= KIconLoader::SizeMedium) { - requestedSize = KIconLoader::SizeMedium; - } else if (size <= KIconLoader::SizeLarge) { - requestedSize = KIconLoader::SizeLarge; - } else if (size <= KIconLoader::SizeHuge) { - requestedSize = KIconLoader::SizeHuge; - } else if (size <= KIconLoader::SizeEnormous) { - requestedSize = KIconLoader::SizeEnormous; - } else if (size <= KIconLoader::SizeEnormous * 2) { - requestedSize = KIconLoader::SizeEnormous * 2; - } else { - requestedSize = size; +void KStandardItemListWidget::closeRoleEditor() +{ + disconnect(m_roleEditor, SIGNAL(roleEditingCanceled(QByteArray,QVariant)), + this, SLOT(slotRoleEditingCanceled(QByteArray,QVariant))); + disconnect(m_roleEditor, SIGNAL(roleEditingFinished(QByteArray,QVariant)), + this, SLOT(slotRoleEditingFinished(QByteArray,QVariant))); + + if (m_roleEditor->hasFocus()) { + // If the editing was not ended by a FocusOut event, we have + // to transfer the keyboard focus back to the KItemListContainer. + scene()->views()[0]->parentWidget()->setFocus(); } - QPixmap pixmap = icon.pixmap(requestedSize, requestedSize); - if (requestedSize != size) { - KPixmapModifier::scale(pixmap, QSize(size, size)); + if (m_oldRoleEditor) { + m_oldRoleEditor->deleteLater(); } - - return pixmap; + m_oldRoleEditor = m_roleEditor; + m_roleEditor->hide(); + m_roleEditor = 0; } -void KStandardItemListWidget::applyCutEffect(QPixmap& pixmap) +QPixmap KStandardItemListWidget::pixmapForIcon(const QString& name, const QStringList& overlays, int size) { - KIconEffect* effect = KIconLoader::global()->iconEffect(); - pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); -} + const QString key = "KStandardItemListWidget:" % name % ":" % overlays.join(":") % ":" % QString::number(size); + QPixmap pixmap; + + if (!QPixmapCache::find(key, pixmap)) { + const KIcon icon(name); + + int requestedSize; + if (size <= KIconLoader::SizeSmall) { + requestedSize = KIconLoader::SizeSmall; + } else if (size <= KIconLoader::SizeSmallMedium) { + requestedSize = KIconLoader::SizeSmallMedium; + } else if (size <= KIconLoader::SizeMedium) { + requestedSize = KIconLoader::SizeMedium; + } else if (size <= KIconLoader::SizeLarge) { + requestedSize = KIconLoader::SizeLarge; + } else if (size <= KIconLoader::SizeHuge) { + requestedSize = KIconLoader::SizeHuge; + } else if (size <= KIconLoader::SizeEnormous) { + requestedSize = KIconLoader::SizeEnormous; + } else if (size <= KIconLoader::SizeEnormous * 2) { + requestedSize = KIconLoader::SizeEnormous * 2; + } else { + requestedSize = size; + } -void KStandardItemListWidget::applyHiddenEffect(QPixmap& pixmap) -{ - KIconEffect::semiTransparent(pixmap); + pixmap = icon.pixmap(requestedSize, requestedSize); + if (requestedSize != 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. + 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, pixmap, KIconLoader::Desktop); + break; + } + } + + QPixmapCache::insert(key, pixmap); + } + + return pixmap; } QSizeF KStandardItemListWidget::preferredRatingSize(const KItemListStyleOption& option)