From 54b5a283765f168575f2eaeff95c93dbeca3d63d Mon Sep 17 00:00:00 2001 From: Peter Penz Date: Mon, 24 Oct 2011 00:22:03 +0200 Subject: [PATCH] Make group-headers less ugly --- src/kitemviews/kfileitemlistgroupheader.cpp | 37 ++++- src/kitemviews/kfileitemlistgroupheader.h | 14 +- src/kitemviews/kfileitemmodel.cpp | 7 + src/kitemviews/kitemlistgroupheader.cpp | 160 ++++++++++++++++++-- src/kitemviews/kitemlistgroupheader.h | 47 +++++- src/kitemviews/kitemlistview.cpp | 2 + src/kitemviews/kitemlistviewlayouter.cpp | 19 +++ 7 files changed, 271 insertions(+), 15 deletions(-) diff --git a/src/kitemviews/kfileitemlistgroupheader.cpp b/src/kitemviews/kfileitemlistgroupheader.cpp index 969bd925b..06d410f28 100644 --- a/src/kitemviews/kfileitemlistgroupheader.cpp +++ b/src/kitemviews/kfileitemlistgroupheader.cpp @@ -25,8 +25,12 @@ #include KFileItemListGroupHeader::KFileItemListGroupHeader(QGraphicsWidget* parent) : - KItemListGroupHeader(parent) + KItemListGroupHeader(parent), + m_font(), + m_text() { + m_text.setTextFormat(Qt::PlainText); + m_text.setPerformanceHint(QStaticText::AggressiveCaching); } KFileItemListGroupHeader::~KFileItemListGroupHeader() @@ -36,8 +40,35 @@ KFileItemListGroupHeader::~KFileItemListGroupHeader() void KFileItemListGroupHeader::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { KItemListGroupHeader::paint(painter, option, widget); - // TODO: Use dataChanged() hook to prepare a cached property - painter->drawText(QRectF(0, 0, size().width(), size().height()), data().toString()); + + painter->setPen(styleOption().palette.text().color()); + painter->setFont(m_font); + const int margin = styleOption().margin; + painter->drawStaticText(margin * 2, margin, m_text); +} + +void KFileItemListGroupHeader::dataChanged(const QVariant& current, const QVariant& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + updateText(); +} + +void KFileItemListGroupHeader::resizeEvent(QGraphicsSceneResizeEvent* event) +{ + QGraphicsWidget::resizeEvent(event); + updateText(); +} + +void KFileItemListGroupHeader::updateText() +{ + const qreal width = size().width() - 4 * styleOption().margin; + m_font = font(); + m_font.setBold(true); + + QFontMetricsF fontMetrics(m_font); + const QString text = fontMetrics.elidedText(data().toString(), Qt::ElideRight, width); + m_text.setText(text); } #include "kfileitemlistgroupheader.moc" diff --git a/src/kitemviews/kfileitemlistgroupheader.h b/src/kitemviews/kfileitemlistgroupheader.h index 3c23f13d4..52d9a64f1 100644 --- a/src/kitemviews/kfileitemlistgroupheader.h +++ b/src/kitemviews/kfileitemlistgroupheader.h @@ -24,6 +24,8 @@ #include +#include + class LIBDOLPHINPRIVATE_EXPORT KFileItemListGroupHeader : public KItemListGroupHeader { Q_OBJECT @@ -32,8 +34,18 @@ public: KFileItemListGroupHeader(QGraphicsWidget* parent = 0); virtual ~KFileItemListGroupHeader(); - /** @reimp */ virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); + +protected: + virtual void dataChanged(const QVariant& current, const QVariant& previous); + virtual void resizeEvent(QGraphicsSceneResizeEvent* event); + +private: + void updateText(); + +private: + QFont m_font; + QStaticText m_text; }; #endif diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index da2126aa3..5ccca5a94 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1168,6 +1168,13 @@ QList > KFileItemModel::nameRoleGroups() const QChar firstChar; bool isLetter = false; for (int i = 0; i <= maxIndex; ++i) { + if (m_requestRole[ExpansionLevelRole] && m_data.at(i).value("expansionLevel").toInt() > 0) { + // KItemListView would be capable to show sub-groups in groups but + // in typical usecases this results in visual clutter, hence we + // just ignore sub-groups. + continue; + } + const QString name = m_data.at(i).value("name").toString(); // Use the first character of the name as group indication diff --git a/src/kitemviews/kitemlistgroupheader.cpp b/src/kitemviews/kitemlistgroupheader.cpp index 5aac02c82..7413a7d25 100644 --- a/src/kitemviews/kitemlistgroupheader.cpp +++ b/src/kitemviews/kitemlistgroupheader.cpp @@ -24,19 +24,28 @@ #include "kitemlistview.h" +#include #include - +#include #include KItemListGroupHeader::KItemListGroupHeader(QGraphicsWidget* parent) : QGraphicsWidget(parent, 0), + m_dirtyCache(true), m_role(), - m_data() + m_data(), + m_styleOption(), + m_scrollOrientation(Qt::Vertical), + m_leftBorderCache(0), + m_rightBorderCache(0), + m_outlineColor() { } KItemListGroupHeader::~KItemListGroupHeader() { + delete m_leftBorderCache; + delete m_rightBorderCache; } void KItemListGroupHeader::setRole(const QByteArray& role) @@ -69,22 +78,51 @@ QVariant KItemListGroupHeader::data() const return m_data; } -QSizeF KItemListGroupHeader::sizeHint(Qt::SizeHint which, const QSizeF& constraint) const +void KItemListGroupHeader::setStyleOption(const KItemListStyleOption& option) +{ + const KItemListStyleOption previous = m_styleOption; + m_styleOption = option; + m_dirtyCache = true; + styleOptionChanged(option, previous); +} + +const KItemListStyleOption& KItemListGroupHeader::styleOption() const +{ + return m_styleOption; +} + +void KItemListGroupHeader::setScrollOrientation(Qt::Orientation orientation) +{ + if (m_scrollOrientation != orientation) { + const Qt::Orientation previous = m_scrollOrientation; + m_scrollOrientation = orientation; + m_dirtyCache = true; + scrollOrientationChanged(orientation, previous); + } +} + +Qt::Orientation KItemListGroupHeader::scrollOrientation() const { - Q_UNUSED(which); - Q_UNUSED(constraint); - return QSizeF(); + return m_scrollOrientation; } void KItemListGroupHeader::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + if (m_dirtyCache) { + updateCache(); + } + Q_UNUSED(option); Q_UNUSED(widget); - painter->setPen(Qt::darkGreen); - painter->setBrush(QColor(0, 255, 0, 50)); - painter->drawRect(rect()); - //painter->drawText(rect(), QString::number(m_index)); + const int leftBorderX = m_leftBorderCache->width() + 1; + const int rightBorderX = size().width() - m_rightBorderCache->width() - 2; + + painter->setPen(m_outlineColor); + painter->drawLine(leftBorderX, 1, rightBorderX, 1); + + painter->drawPixmap(1, 1, *m_leftBorderCache); + painter->drawPixmap(rightBorderX, 1, *m_rightBorderCache); } void KItemListGroupHeader::roleChanged(const QByteArray& current, const QByteArray& previous) @@ -99,4 +137,106 @@ void KItemListGroupHeader::dataChanged(const QVariant& current, const QVariant& Q_UNUSED(previous); } +void KItemListGroupHeader::styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); +} + +void KItemListGroupHeader::scrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); +} + +void KItemListGroupHeader::resizeEvent(QGraphicsSceneResizeEvent* event) +{ + QGraphicsWidget::resizeEvent(event); + if (event->oldSize().height() != event->newSize().height()) { + m_dirtyCache = true; + } +} + +void KItemListGroupHeader::updateCache() +{ + Q_ASSERT(m_dirtyCache); + + delete m_leftBorderCache; + delete m_rightBorderCache; + + const int length = size().height() - 1; + m_leftBorderCache = new QPixmap(length, length); + m_leftBorderCache->fill(Qt::transparent); + + m_rightBorderCache = new QPixmap(length, length); + m_rightBorderCache->fill(Qt::transparent); + + // Calculate the outline color. No alphablending is used for + // performance reasons. + const QColor c1 = m_styleOption.palette.text().color(); + const QColor c2 = m_styleOption.palette.background().color(); + const int p1 = 35; + const int p2 = 100 - p1; + m_outlineColor = QColor((c1.red() * p1 + c2.red() * p2) / 100, + (c1.green() * p1 + c2.green() * p2) / 100, + (c1.blue() * p1 + c2.blue() * p2) / 100); + + // The drawing code is based on the code of DolphinCategoryDrawer from Dolphin 1.7 + // Copyright (C) 2007 Rafael Fernández López + { + // Cache the left border as pixmap + QPainter painter(m_leftBorderCache); + painter.setPen(m_outlineColor); + + // 1. Draw top horizontal line + painter.drawLine(3, 0, length, 0); + + // 2. Draw vertical line with gradient + const QPoint start(0, 3); + QLinearGradient gradient(start, QPoint(0, length)); + gradient.setColorAt(0, m_outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter.fillRect(QRect(start, QSize(1, length - start.y())), gradient); + + // 3. Draw arc + painter.setRenderHint(QPainter::Antialiasing); + QRectF arc(QPointF(0, 0), QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter.drawArc(arc, 1440, 1440); + } + + { + // Cache the right border as pixmap + QPainter painter(m_rightBorderCache); + painter.setPen(m_outlineColor); + + const int right = length - 1; + if (m_scrollOrientation == Qt::Vertical) { + // 1. Draw top horizontal line + painter.drawLine(0, 0, length - 3, 0); + + // 2. Draw vertical line with gradient + const QPoint start(right, 3); + QLinearGradient gradient(start, QPoint(right, length)); + gradient.setColorAt(0, m_outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter.fillRect(QRect(start, QSize(1, length - start.y())), gradient); + + // 3. Draw arc + painter.setRenderHint(QPainter::Antialiasing); + QRectF arc(QPointF(length - 5, 0), QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter.drawArc(arc, 0, 1440); + } else { + // Draw a horizontal gradiented line + QLinearGradient gradient(QPoint(0, 0), QPoint(length, 0)); + gradient.setColorAt(0, m_outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter.fillRect(QRect(QPoint(0, 0), QSize(length, 1)), gradient); + } + } + + m_dirtyCache = false; +} + #include "kitemlistgroupheader.moc" diff --git a/src/kitemviews/kitemlistgroupheader.h b/src/kitemviews/kitemlistgroupheader.h index 081607eef..20a58cc5b 100644 --- a/src/kitemviews/kitemlistgroupheader.h +++ b/src/kitemviews/kitemlistgroupheader.h @@ -22,6 +22,8 @@ #include +#include + #include #include #include @@ -42,17 +44,60 @@ public: void setData(const QVariant& data); QVariant data() const; - virtual QSizeF sizeHint(Qt::SizeHint which = Qt::PreferredSize, const QSizeF& constraint = QSizeF()) const; + void setStyleOption(const KItemListStyleOption& option); + const KItemListStyleOption& styleOption() const; + + /** + * Sets the scroll orientation that is used by the KItemListView. + * This allows the group header to use a modified look dependent + * on the orientation. + */ + void setScrollOrientation(Qt::Orientation orientation); + Qt::Orientation scrollOrientation() const; + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); protected: + /** + * Is called after the role has been changed and allows the derived class + * to react on this change. + */ virtual void roleChanged(const QByteArray& current, const QByteArray& previous); + + /** + * Is called after the role has been changed and allows the derived class + * to react on this change. + */ virtual void dataChanged(const QVariant& current, const QVariant& previous); + /** + * Is called after the style option has been changed and allows the derived class + * to react on this change. + */ + virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous); + + /** + * Is called after the scroll orientation has been changed and allows the derived class + * to react on this change. + */ + virtual void scrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous); + + /** @reimp */ + virtual void resizeEvent(QGraphicsSceneResizeEvent* event); + +private: + void updateCache(); + private: + bool m_dirtyCache; QByteArray m_role; QVariant m_data; + KItemListStyleOption m_styleOption; + Qt::Orientation m_scrollOrientation; + QPixmap* m_leftBorderCache; + QPixmap* m_rightBorderCache; + QColor m_outlineColor; }; #endif diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 05a2d30c1..2c88160fe 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -1478,6 +1478,8 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) header->setData(groups.at(mid).second); header->setRole(model()->sortRole()); + header->setStyleOption(m_styleOption); + header->setScrollOrientation(scrollOrientation()); header->show(); } diff --git a/src/kitemviews/kitemlistviewlayouter.cpp b/src/kitemviews/kitemlistviewlayouter.cpp index 6bd9a6e27..9b807aaf5 100644 --- a/src/kitemviews/kitemlistviewlayouter.cpp +++ b/src/kitemviews/kitemlistviewlayouter.cpp @@ -355,6 +355,25 @@ void KItemListViewLayouter::doLayout() m_itemRects.append(bounds); } + if (grouped && horizontalScrolling) { + // When grouping is enabled in the horizontal mode, the header alignment + // looks like this: + // Header-1 Header-2 Header-3 + // Item 1 Item 4 Item 7 + // Item 2 Item 5 Item 8 + // Item 3 Item 6 Item 9 + // In this case 'requiredItemHeight' represents the column-width. We don't + // check the content of the header in the layouter to determine the required + // width, hence assure that at least a minimal width of 15 characters is given + // (in average a character requires the halve width of the font height). + // + // TODO: Let the group headers provide a minimum width and respect this width here + const qreal minimumGroupHeaderWidth = m_groupHeaderHeight * 15 / 2; + if (requiredItemHeight < minimumGroupHeaderWidth) { + requiredItemHeight = minimumGroupHeaderWidth; + } + } + maxItemHeight = qMax(maxItemHeight, requiredItemHeight); x += m_columnWidth; ++index; -- 2.47.3