From: Peter Penz Date: Mon, 6 Feb 2012 22:26:20 +0000 (+0100) Subject: Group header improvements X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/commitdiff_plain/5369ee6819a26d9d3f106521c525c03f81a943ce Group header improvements - Use a simpler background and colors as suggested by Martin Zilz. This is just an early draft, I need to post some comparison screenshots to Martin for review. - Fixes of some layout issues that have been revealed because of adjusting the group header heights. - More clever animation/no-animation detection when doing listview-property changes in parallel. --- diff --git a/src/kitemviews/kfileitemlistgroupheader.cpp b/src/kitemviews/kfileitemlistgroupheader.cpp index 06d410f28..237a974c2 100644 --- a/src/kitemviews/kfileitemlistgroupheader.cpp +++ b/src/kitemviews/kfileitemlistgroupheader.cpp @@ -41,10 +41,9 @@ void KFileItemListGroupHeader::paint(QPainter* painter, const QStyleOptionGraphi { KItemListGroupHeader::paint(painter, option, widget); - painter->setPen(styleOption().palette.text().color()); + painter->setPen(roleColor()); painter->setFont(m_font); - const int margin = styleOption().margin; - painter->drawStaticText(margin * 2, margin, m_text); + painter->drawStaticText(roleBounds().topLeft(), m_text); } void KFileItemListGroupHeader::dataChanged(const QVariant& current, const QVariant& previous) @@ -63,8 +62,7 @@ void KFileItemListGroupHeader::resizeEvent(QGraphicsSceneResizeEvent* event) void KFileItemListGroupHeader::updateText() { const qreal width = size().width() - 4 * styleOption().margin; - m_font = font(); - m_font.setBold(true); + m_font = font(); // TODO: Most probably the font size will be slightly shrinked in future QFontMetricsF fontMetrics(m_font); const QString text = fontMetrics.elidedText(data().toString(), Qt::ElideRight, width); diff --git a/src/kitemviews/kitemlistgroupheader.cpp b/src/kitemviews/kitemlistgroupheader.cpp index a63ad8d09..8eff39901 100644 --- a/src/kitemviews/kitemlistgroupheader.cpp +++ b/src/kitemviews/kitemlistgroupheader.cpp @@ -36,15 +36,13 @@ KItemListGroupHeader::KItemListGroupHeader(QGraphicsWidget* parent) : m_data(), m_styleOption(), m_scrollOrientation(Qt::Vertical), - m_leftBorderCache(0), - m_rightBorderCache(0), - m_outlineColor() + m_roleColor(), + m_roleBounds() { } KItemListGroupHeader::~KItemListGroupHeader() { - deleteCache(); } void KItemListGroupHeader::setRole(const QByteArray& role) @@ -97,8 +95,6 @@ void KItemListGroupHeader::setScrollOrientation(Qt::Orientation orientation) m_scrollOrientation = orientation; if (orientation == Qt::Vertical) { m_dirtyCache = true; - } else { - deleteCache(); } scrollOrientationChanged(orientation, previous); } @@ -114,24 +110,25 @@ void KItemListGroupHeader::paint(QPainter* painter, const QStyleOptionGraphicsIt Q_UNUSED(option); Q_UNUSED(widget); - if (m_scrollOrientation == Qt::Horizontal) { - Q_ASSERT(!m_leftBorderCache); - Q_ASSERT(!m_rightBorderCache); - return; - } - if (m_dirtyCache) { updateCache(); } - const int leftBorderX = m_leftBorderCache->width() + 1; - const int rightBorderX = size().width() - m_rightBorderCache->width() - 2; + if (m_scrollOrientation != Qt::Horizontal) { + painter->setPen(m_roleColor); + const qreal y = m_roleBounds.y() - m_styleOption.margin; + painter->drawLine(0, y, size().width() - 1, y); + } +} - painter->setPen(m_outlineColor); - painter->drawLine(leftBorderX, 1, rightBorderX, 1); +QRectF KItemListGroupHeader::roleBounds() const +{ + return m_roleBounds; +} - painter->drawPixmap(1, 1, *m_leftBorderCache); - painter->drawPixmap(rightBorderX, 1, *m_rightBorderCache); +QColor KItemListGroupHeader::roleColor() const +{ + return m_roleColor; } void KItemListGroupHeader::roleChanged(const QByteArray& current, const QByteArray& previous) @@ -170,82 +167,26 @@ void KItemListGroupHeader::updateCache() { Q_ASSERT(m_dirtyCache); - deleteCache(); - - const int length = qMax(int(size().height() - 1), 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); - - // 1. Draw top horizontal line - painter.drawLine(0, 0, length - 3, 0); - - // 2. Draw vertical line with gradient - const int right = length - 1; - 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); - } + m_roleColor = QColor((c1.red() * p1 + c2.red() * p2) / 100, + (c1.green() * p1 + c2.green() * p2) / 100, + (c1.blue() * p1 + c2.blue() * p2) / 100); - m_dirtyCache = false; -} + const int margin = m_styleOption.margin; + const QFontMetrics fontMetrics(m_styleOption.font); + const qreal roleHeight = fontMetrics.height(); -void KItemListGroupHeader::deleteCache() -{ - delete m_leftBorderCache; - m_leftBorderCache = 0; + m_roleBounds = QRectF(margin, + size().height() - roleHeight - margin, + size().width() - 2 * margin, + roleHeight); - delete m_rightBorderCache; - m_rightBorderCache = 0; + m_dirtyCache = false; } #include "kitemlistgroupheader.moc" diff --git a/src/kitemviews/kitemlistgroupheader.h b/src/kitemviews/kitemlistgroupheader.h index 8f556afc5..5ddf20896 100644 --- a/src/kitemviews/kitemlistgroupheader.h +++ b/src/kitemviews/kitemlistgroupheader.h @@ -30,6 +30,13 @@ class KItemListView; +/** + * @brief Base class for group headers. + * + * Draws a default header background. Derived classes must reimplement + * the method paint() and draw the role within the given roleBounds() with + * the color roleColor(). + */ class LIBDOLPHINPRIVATE_EXPORT KItemListGroupHeader : public QGraphicsWidget { Q_OBJECT @@ -58,6 +65,12 @@ public: virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); protected: + /** @return Bounding rectangle where the role should be drawn into. */ + QRectF roleBounds() const; + + /** @return Primary color that should be used for drawing the role. */ + QColor roleColor() const; + /** * Is called after the role has been changed and allows the derived class * to react on this change. @@ -87,7 +100,6 @@ protected: private: void updateCache(); - void deleteCache(); private: bool m_dirtyCache; @@ -96,9 +108,8 @@ private: KItemListStyleOption m_styleOption; Qt::Orientation m_scrollOrientation; - QPixmap* m_leftBorderCache; - QPixmap* m_rightBorderCache; - QColor m_outlineColor; + QColor m_roleColor; + QRectF m_roleBounds; }; #endif diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 52d857705..a2252b918 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -55,6 +55,7 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_enabledSelectionToggles(false), m_grouped(false), m_activeTransactions(0), + m_endTransactionAnimationHint(Animation), m_itemSize(), m_controller(0), m_model(0), @@ -125,6 +126,8 @@ void KItemListView::setScrollOrientation(Qt::Orientation orientation) it.next(); it.value()->setScrollOrientation(orientation); } + updateGroupHeaderHeight(); + } doLayout(NoAnimation); @@ -331,14 +334,19 @@ void KItemListView::setStyleOption(const KItemListStyleOption& option) const KItemListStyleOption previousOption = m_styleOption; m_styleOption = option; + if (m_grouped) { + updateGroupHeaderHeight(); + } + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); it.value()->setStyleOption(option); } - m_sizeHintResolver->clearCache(); + m_sizeHintResolver->clearCache(); doLayout(Animation); + onStyleOptionChanged(option, previousOption); } @@ -548,7 +556,8 @@ void KItemListView::endTransaction() if (m_activeTransactions == 0) { onTransactionEnd(); - doLayout(NoAnimation); + doLayout(m_endTransactionAnimationHint); + m_endTransactionAnimationHint = Animation; } } @@ -557,7 +566,6 @@ bool KItemListView::isTransactionActive() const return m_activeTransactions > 0; } - void KItemListView::setHeaderShown(bool show) { @@ -893,7 +901,7 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) // geometry update if necessary. const int activeTransactions = m_activeTransactions; m_activeTransactions = 0; - doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count); + doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count); m_activeTransactions = activeTransactions; } } @@ -978,12 +986,7 @@ void KItemListView::slotGroupedSortingChanged(bool current) m_layouter->markAsDirty(); if (m_grouped) { - // Apply the height of the header to the layouter - const qreal groupHeaderHeight = m_styleOption.fontMetrics.height() + - m_styleOption.margin * 2; - m_layouter->setGroupHeaderHeight(groupHeaderHeight); - - updateVisibleGroupHeaders(); + updateGroupHeaderHeight(); } else { // Clear all visible headers QMutableHashIterator it (m_visibleGroups); @@ -1287,7 +1290,16 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha m_layoutTimer->stop(); } - if (!m_model || m_model->count() < 0 || m_activeTransactions > 0) { + if (m_activeTransactions > 0) { + if (hint == NoAnimation) { + // As soon as at least one property change should be done without animation, + // the whole transaction will be marked as not animated. + m_endTransactionAnimationHint = NoAnimation; + } + return; + } + + if (!m_model || m_model->count() < 0) { return; } @@ -1608,13 +1620,13 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) return; } - KItemListGroupHeader* header = m_visibleGroups.value(widget); - if (!header) { - header = m_groupHeaderCreator->create(this); - header->setParentItem(widget); - m_visibleGroups.insert(widget, header); + KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); + if (!groupHeader) { + groupHeader = m_groupHeaderCreator->create(this); + groupHeader->setParentItem(widget); + m_visibleGroups.insert(widget, groupHeader); } - Q_ASSERT(header->parentItem() == widget); + Q_ASSERT(groupHeader->parentItem() == widget); // Determine the shown data for the header by doing a binary // search in the groups-list @@ -1630,18 +1642,18 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) } } while (groups.at(mid).first != index && min <= max); - header->setData(groups.at(mid).second); - header->setRole(model()->sortRole()); - header->setStyleOption(m_styleOption); - header->setScrollOrientation(scrollOrientation()); + groupHeader->setData(groups.at(mid).second); + groupHeader->setRole(model()->sortRole()); + groupHeader->setStyleOption(m_styleOption); + groupHeader->setScrollOrientation(scrollOrientation()); - header->show(); + groupHeader->show(); } void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) { - KItemListGroupHeader* header = m_visibleGroups.value(widget); - Q_ASSERT(header); + KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); + Q_ASSERT(groupHeader); const int index = widget->index(); const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index); @@ -1651,8 +1663,8 @@ void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) // group header position to the relative position. const QPointF groupHeaderPos(groupHeaderRect.x() - itemRect.x(), - groupHeaderRect.height()); - header->setPos(groupHeaderPos); - header->resize(groupHeaderRect.size()); + groupHeader->setPos(groupHeaderPos); + groupHeader->resize(groupHeaderRect.size()); } void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) @@ -1893,6 +1905,16 @@ bool KItemListView::scrollBarRequired(const QSizeF& size) const : maxOffset > size.width(); } +void KItemListView::updateGroupHeaderHeight() +{ + qreal groupHeaderHeight = m_styleOption.fontMetrics.height(); + groupHeaderHeight += (scrollOrientation() == Qt::Vertical) + ? m_styleOption.margin * 4 : m_styleOption.margin * 2; + m_layouter->setGroupHeaderHeight(groupHeaderHeight); + + updateVisibleGroupHeaders(); +} + int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) { int inc = 0; diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index e675df221..50d8908e5 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -202,8 +202,20 @@ public: */ void scrollToItem(int index); + /** + * If several properties of KItemListView are changed synchronously, it is + * recommended to encapsulate the calls between beginTransaction() and endTransaction(). + * This prevents unnecessary and expensive layout-calculations. + */ void beginTransaction(); + + /** + * Counterpart to beginTransaction(). The layout changes will only be animated if + * all property changes between beginTransaction() and endTransaction() support + * animations. + */ void endTransaction(); + bool isTransactionActive() const; /** @@ -468,6 +480,12 @@ private: */ bool scrollBarRequired(const QSizeF& size) const; + /** + * Applies the height of the group header to the layouter. The height + * depends on the used scroll orientation. + */ + void updateGroupHeaderHeight(); + /** * Helper function for triggerAutoScrolling(). * @param pos Logical position of the mouse relative to the range. @@ -484,6 +502,7 @@ private: bool m_enabledSelectionToggles; bool m_grouped; int m_activeTransactions; // Counter for beginTransaction()/endTransaction() + LayoutAnimationHint m_endTransactionAnimationHint; QSizeF m_itemSize; KItemListController* m_controller; diff --git a/src/kitemviews/kitemlistviewlayouter.cpp b/src/kitemviews/kitemlistviewlayouter.cpp index 60822adc7..4e7a91087 100644 --- a/src/kitemviews/kitemlistviewlayouter.cpp +++ b/src/kitemviews/kitemlistviewlayouter.cpp @@ -221,6 +221,8 @@ QRectF KItemListViewLayouter::itemRect(int index) const QRectF KItemListViewLayouter::groupHeaderRect(int index) const { + const_cast(this)->doLayout(); + const QRectF firstItemRect = itemRect(index); QPointF pos = firstItemRect.topLeft(); if (pos.isNull()) { diff --git a/src/views/dolphinitemlistcontainer.cpp b/src/views/dolphinitemlistcontainer.cpp index 4f7a0e06b..65f69d963 100644 --- a/src/views/dolphinitemlistcontainer.cpp +++ b/src/views/dolphinitemlistcontainer.cpp @@ -242,8 +242,10 @@ void DolphinItemListContainer::updateGridSize() // Apply the calculated values styleOption.margin = innerMargin; styleOption.iconSize = iconSize; + m_fileItemListView->beginTransaction(); m_fileItemListView->setStyleOption(styleOption); m_fileItemListView->setItemSize(QSizeF(itemWidth, itemHeight)); + m_fileItemListView->endTransaction(); } void DolphinItemListContainer::updateFont()