-/***************************************************************************
- * Copyright (C) 2012 by Peter Penz <peter.penz19@gmail.com> *
- * *
- * This program is free software; you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation; either version 2 of the License, or *
- * (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program; if not, write to the *
- * Free Software Foundation, Inc., *
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
- ***************************************************************************/
+/*
+ * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
#include "kstandarditemlistwidget.h"
#include <KRatingPainter>
#include <KStringHandler>
+#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsSceneResizeEvent>
#include <QGraphicsView>
{
}
-void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
{
switch (static_cast<const KStandardItemListView*>(view)->itemLayout()) {
case KStandardItemListView::IconsLayout:
// 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);
+ width += fontMetrics.horizontalAdvance(text);
if (role == "text") {
if (view->supportsItemExpanding()) {
return baseFont;
}
-void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
{
const KItemListStyleOption& option = view->styleOption();
const QFont& normalFont = option.font;
textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
for (int index = 0; index < logicalHeightHints.count(); ++index) {
- if (logicalHeightHints.at(index) > 0.0) {
+ if (logicalHeightHints.at(index).first > 0.0) {
continue;
}
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.beginLayout();
QTextLine line;
int lineCount = 0;
+ bool isElided = false;
while ((line = layout.createLine()).isValid()) {
line.setLineWidth(maxWidth);
line.naturalTextWidth();
++lineCount;
if (lineCount == option.maxTextLines) {
+ isElided = layout.createLine().isValid();
break;
}
}
// Add one line for each additional information
textHeight += additionalRolesSpacing;
- logicalHeightHints[index] = textHeight + spacingAndIconHeight;
+ logicalHeightHints[index].first = textHeight + spacingAndIconHeight;
+ logicalHeightHints[index].second = isElided;
}
logicalWidthHint = itemWidth;
}
-void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
{
const KItemListStyleOption& option = view->styleOption();
const QFontMetrics& normalFontMetrics = option.fontMetrics;
const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font));
for (int index = 0; index < logicalHeightHints.count(); ++index) {
- if (logicalHeightHints.at(index) > 0.0) {
+ if (logicalHeightHints.at(index).first > 0.0) {
continue;
}
qreal maximumRequiredWidth = 0.0;
if (showOnlyTextRole) {
- maximumRequiredWidth = fontMetrics.width(itemText(index, view));
+ maximumRequiredWidth = fontMetrics.horizontalAdvance(itemText(index, view));
} else {
const QHash<QByteArray, QVariant>& values = view->model()->data(index);
- foreach (const QByteArray& role, visibleRoles) {
+ for (const QByteArray& role : visibleRoles) {
const QString& text = roleText(role, values);
- const qreal requiredWidth = fontMetrics.width(text);
+ const qreal requiredWidth = fontMetrics.horizontalAdvance(text);
maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth);
}
}
width = maxWidth;
}
- logicalHeightHints[index] = width;
+ logicalHeightHints[index].first = width;
}
logicalWidthHint = height;
}
-void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& 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);
+
+ float zoomLevel = 1;
+ if (option.iconSize >= KIconLoader::SizeEnormous) {
+ zoomLevel = 2;
+ } else if (option.iconSize >= KIconLoader::SizeHuge) {
+ zoomLevel = 1.8;
+ } else if (option.iconSize >= KIconLoader::SizeLarge) {
+ zoomLevel = 1.6;
+ } else if (option.iconSize >= KIconLoader::SizeMedium) {
+ zoomLevel = 1.4;
+ } else if (option.iconSize >= KIconLoader::SizeSmallMedium) {
+ zoomLevel = 1.2;
+ }
+
+ const qreal contentHeight = qMax<qreal>(option.iconSize, zoomLevel * option.fontMetrics.height());
+ logicalHeightHints.fill(std::make_pair(contentHeight + 2 * option.padding, false));
logicalWidthHint = -1.0;
}
KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) :
KItemListWidget(informant, parent),
+ m_textInfo(),
m_isCut(false),
m_isHidden(false),
m_customizedFont(),
m_scaledPixmapSize(),
m_iconRect(),
m_hoverPixmap(),
- m_textInfo(),
m_textRect(),
m_sortedVisibleRoles(),
m_expansionArea(),
}
const KItemListStyleOption& itemListStyleOption = styleOption();
- if (isHovered()) {
+ if (isHovered() && !m_pixmap.isNull()) {
if (hoverOpacity() < 1.0) {
/*
* Linear interpolation between m_pixmap and m_hoverPixmap.
} else {
drawPixmap(painter, m_hoverPixmap);
}
- } else {
+ } else if (!m_pixmap.isNull()) {
drawPixmap(painter, m_pixmap);
}
const KItemListStyleOption& option = styleOption();
if (option.extendedSelectionRegion) {
const QString text = textInfo->staticText.text();
- rect.setWidth(m_customizedFontMetrics.width(text) + 2 * option.padding);
+ rect.setWidth(m_customizedFontMetrics.horizontalAdvance(text) + 2 * option.padding);
}
return rect;
m_dirtyContent = true;
}
+void KStandardItemListWidget::invalidateIconCache()
+{
+ m_dirtyContent = true;
+ m_dirtyContentRoles.insert("iconPixmap");
+ m_dirtyContentRoles.insert("iconOverlays");
+}
+
void KStandardItemListWidget::refreshCache()
{
}
QSet<QByteArray> dirtyRoles;
if (roles.isEmpty()) {
- dirtyRoles = visibleRoles().toSet();
+ const auto visibleRoles = this->visibleRoles();
+ dirtyRoles = QSet<QByteArray>(visibleRoles.constBegin(), visibleRoles.constEnd());
} else {
dirtyRoles = roles;
}
QGraphicsView* parent = scene()->views()[0];
if (current.isEmpty() || !parent || current != "text") {
if (m_roleEditor) {
- emit roleEditingCanceled(index(), current, data().value(current));
+ Q_EMIT roleEditingCanceled(index(), current, data().value(current));
disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled,
this, &KStandardItemListWidget::slotRoleEditingCanceled);
m_roleEditor = new KItemListRoleEditor(parent);
m_roleEditor->setRole(current);
+ m_roleEditor->setAllowUpDownKeyChainEdit(m_layout != IconsLayout);
m_roleEditor->setFont(styleOption().font);
const QString text = data().value(current).toString();
const QVariant& value)
{
closeRoleEditor();
- emit roleEditingCanceled(index(), role, value);
+ Q_EMIT roleEditingCanceled(index(), role, value);
setEditedRole(QByteArray());
}
const QVariant& value)
{
closeRoleEditor();
- emit roleEditingFinished(index(), role, value);
+ Q_EMIT roleEditingFinished(index(), role, value);
setEditedRole(QByteArray());
}
}
if (updatePixmap) {
- m_pixmap = values["iconPixmap"].value<QPixmap>();
+ m_pixmap = QPixmap();
+
+ int sequenceIndex = hoverSequenceIndex();
+
+ if (values.contains("hoverSequencePixmaps")) {
+ // Use one of the hover sequence pixmaps instead of the default
+ // icon pixmap.
+
+ const QVector<QPixmap> pixmaps = values["hoverSequencePixmaps"].value<QVector<QPixmap>>();
+
+ if (values.contains("hoverSequenceWraparoundPoint")) {
+ const float wap = values["hoverSequenceWraparoundPoint"].toFloat();
+ if (wap >= 1.0f) {
+ sequenceIndex %= static_cast<int>(wap);
+ }
+ }
+
+ const int loadedIndex = qMax(qMin(sequenceIndex, pixmaps.size()-1), 0);
+
+ if (loadedIndex != 0) {
+ m_pixmap = pixmaps[loadedIndex];
+ }
+ }
+
+ if (m_pixmap.isNull()) {
+ m_pixmap = values["iconPixmap"].value<QPixmap>();
+ }
+
if (m_pixmap.isNull()) {
// Use the icon that fits to the MIME-type
QString iconName = values["iconName"].toString();
KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight) * qApp->devicePixelRatio());
}
+ if (m_pixmap.isNull()) {
+ m_hoverPixmap = QPixmap();
+ return;
+ }
+
if (m_isCut) {
KIconEffect* effect = KIconLoader::global()->iconEffect();
m_pixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
if (m_layout == IconsLayout && isSelected()) {
const QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color();
QImage image = m_pixmap.toImage();
+ if (image.isNull()) {
+ m_hoverPixmap = QPixmap();
+ return;
+ }
KIconEffect::colorize(image, color, 0.8f);
m_pixmap = QPixmap::fromImage(image);
}
if (extensionIndex != -1) {
// has file extension
const auto extensionLength = text.length() - extensionIndex;
- const auto extensionWidth = m_customizedFontMetrics.width(text.right(extensionLength));
+ const auto extensionWidth = m_customizedFontMetrics.horizontalAdvance(text.right(extensionLength));
if (elidingWidth > extensionWidth && extensionLength < 6 && (float(extensionWidth) / float(elidingWidth)) < 0.3) {
// if we have room to display the file extension and the extension is not too long
QString ret = m_customizedFontMetrics.elidedText(text.chopped(extensionLength),
Qt::ElideRight,
elidingWidth - extensionWidth);
- ret.append(text.right(extensionLength));
+ ret.append(text.rightRef(extensionLength));
return ret;
}
}
const QString elidedText = nameText.left(line.textStart()) + lastTextLine;
nameTextInfo->staticText.setText(elidedText);
- lastLineWidth = m_customizedFontMetrics.boundingRect(lastTextLine).width();
+ lastLineWidth = m_customizedFontMetrics.horizontalAdvance(lastTextLine);
// We do the text eliding in a loop with decreasing width (1 px / iteration)
// to avoid problems related to different width calculation code paths
// Calculate the position for each additional information
qreal y = nameTextInfo->pos.y() + nameHeight;
- foreach (const QByteArray& role, m_sortedVisibleRoles) {
+ for (const QByteArray& role : qAsConst(m_sortedVisibleRoles)) {
if (role == "text") {
continue;
}
if (requiredWidth > maxWidth) {
const QString elidedText = elideRightKeepExtension(text, maxWidth);
textInfo->staticText.setText(elidedText);
- requiredWidth = m_customizedFontMetrics.width(elidedText);
+ requiredWidth = m_customizedFontMetrics.horizontalAdvance(elidedText);
} else if (role == "rating") {
// Use the width of the rating pixmap, because the rating text is empty.
requiredWidth = m_rating.width();
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) {
+ for (const QByteArray& role : qAsConst(m_sortedVisibleRoles)) {
const QString text = roleText(role, values);
TextInfo* textInfo = m_textInfo.value(role);
textInfo->staticText.setText(text);
- qreal requiredWidth = m_customizedFontMetrics.width(text);
+ qreal requiredWidth = m_customizedFontMetrics.horizontalAdvance(text);
if (requiredWidth > maxWidth) {
requiredWidth = maxWidth;
const QString elidedText = elideRightKeepExtension(text, maxWidth);
y += lineSpacing;
}
- m_textRect = QRectF(x - 2 * option.padding, 0, maximumRequiredTextWidth + 3 * option.padding, widgetHeight);
+ m_textRect = QRectF(x - option.padding, 0, maximumRequiredTextWidth + 2 * option.padding, widgetHeight);
}
void KStandardItemListWidget::updateDetailsLayoutTextCache()
qreal x = firstColumnInc;
const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2);
- foreach (const QByteArray& role, m_sortedVisibleRoles) {
+ for (const QByteArray& role : qAsConst(m_sortedVisibleRoles)) {
QString text = roleText(role, values);
// Elide the text in case it does not fit into the available column-width
- qreal requiredWidth = m_customizedFontMetrics.width(text);
+ qreal requiredWidth = m_customizedFontMetrics.horizontalAdvance(text);
const qreal roleWidth = columnWidth(role);
qreal availableTextWidth = roleWidth - columnWidthInc;
if (requiredWidth > availableTextWidth) {
text = elideRightKeepExtension(text, availableTextWidth);
- requiredWidth = m_customizedFontMetrics.width(text);
+ requiredWidth = m_customizedFontMetrics.horizontalAdvance(text);
}
TextInfo* textInfo = m_textInfo.value(role);
const qreal textWidth = option.extendedSelectionRegion
? size().width() - textInfo->pos.x()
: requiredWidth + 2 * option.padding;
- m_textRect = QRectF(textInfo->pos.x() - 2 * option.padding, 0,
- textWidth + option.padding, size().height());
+ 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
const QString key = "KStandardItemListWidget:" % name % ":" % overlays.join(QLatin1Char(':')) % ":" % QString::number(size) % ":" % QString::number(mode);
QPixmap pixmap;
- if (!QPixmapCache::find(key, pixmap)) {
- const QIcon icon = QIcon::fromTheme(name, fallbackIcon);
+ if (!QPixmapCache::find(key, &pixmap)) {
+ QIcon icon = QIcon::fromTheme(name);
+ if (icon.isNull()) {
+ icon = QIcon(name);
+ }
+ if (icon.isNull()
+ || icon.pixmap(size / qApp->devicePixelRatio(), size / qApp->devicePixelRatio(), mode).isNull()) {
+ icon = fallbackIcon;
+ }
pixmap = icon.pixmap(size / qApp->devicePixelRatio(), size / qApp->devicePixelRatio(), mode);
if (pixmap.width() != size || pixmap.height() != size) {
// 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) {
+ for (const QString& overlay : overlays) {
if (!overlay.isEmpty()) {
int state = KIconLoader::DefaultState;