]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kitemlistviewlayouter.cpp
Improve group-header layout
[dolphin.git] / src / kitemviews / kitemlistviewlayouter.cpp
index 30881abfdaf53d013566b47d0e9703e4b5f541f0..6bd9a6e27a1686331ee945d6b884c30d3bf1d13e 100644 (file)
 
 #define KITEMLISTVIEWLAYOUTER_DEBUG
 
-namespace {
-    // TODO
-    const int HeaderHeight = 50;
-};
-
 KItemListViewLayouter::KItemListViewLayouter(QObject* parent) :
     QObject(parent),
     m_dirty(true),
     m_visibleIndexesDirty(true),
-    m_grouped(false),
     m_scrollOrientation(Qt::Vertical),
     m_size(),
     m_itemSize(128, 128),
@@ -48,13 +42,12 @@ KItemListViewLayouter::KItemListViewLayouter(QObject* parent) :
     m_maximumItemOffset(0),
     m_firstVisibleIndex(-1),
     m_lastVisibleIndex(-1),
-    m_firstVisibleGroupIndex(-1),
     m_columnWidth(0),
     m_xPosInc(0),
     m_columnCount(0),
-    m_groups(),
-    m_groupIndexes(),
-    m_itemBoundingRects()
+    m_groupItemIndexes(),
+    m_groupHeaderHeight(0),
+    m_itemRects()
 {
 }
 
@@ -114,6 +107,19 @@ qreal KItemListViewLayouter::headerHeight() const
     return m_headerHeight;
 }
 
+void KItemListViewLayouter::setGroupHeaderHeight(qreal height)
+{
+    if (m_groupHeaderHeight != height) {
+        m_groupHeaderHeight = height;
+        m_dirty = true;
+    }
+}
+
+qreal KItemListViewLayouter::groupHeaderHeight() const
+{
+    return m_groupHeaderHeight;
+}
+
 void KItemListViewLayouter::setScrollOffset(qreal offset)
 {
     if (m_scrollOffset != offset) {
@@ -190,17 +196,17 @@ int KItemListViewLayouter::lastVisibleIndex() const
     return m_lastVisibleIndex;
 }
 
-QRectF KItemListViewLayouter::itemBoundingRect(int index) const
+QRectF KItemListViewLayouter::itemRect(int index) const
 {
     const_cast<KItemListViewLayouter*>(this)->doLayout();
-    if (index < 0 || index >= m_itemBoundingRects.count()) {
+    if (index < 0 || index >= m_itemRects.count()) {
         return QRectF();
     }
 
     if (m_scrollOrientation == Qt::Horizontal) {
         // Rotate the logical direction which is always vertical by 90°
         // to get the physical horizontal direction
-        const QRectF& b = m_itemBoundingRects[index];
+        const QRectF& b = m_itemRects[index];
         QRectF bounds(b.y(), b.x(), b.height(), b.width());
         QPointF pos = bounds.topLeft();
         pos.rx() -= m_scrollOffset;
@@ -208,11 +214,31 @@ QRectF KItemListViewLayouter::itemBoundingRect(int index) const
         return bounds;
     }
 
-    QRectF bounds = m_itemBoundingRects[index];
+    QRectF bounds = m_itemRects[index];
     bounds.moveTo(bounds.topLeft() - QPointF(m_itemOffset, m_scrollOffset));
     return bounds;
 }
 
+QRectF KItemListViewLayouter::groupHeaderRect(int index) const
+{
+    const QRectF firstItemRect = itemRect(index);
+    QPointF pos = firstItemRect.topLeft();
+    if (pos.isNull()) {
+        return QRectF();
+    }
+
+    pos.ry() -= m_groupHeaderHeight;
+
+    QSizeF size;
+    if (m_scrollOrientation == Qt::Vertical) {
+        pos.rx() = 0;
+        size = QSizeF(m_size.width(), m_groupHeaderHeight);
+    } else {
+        size = QSizeF(firstItemRect.width(), m_groupHeaderHeight);
+    }
+    return QRectF(pos, size);
+}
+
 int KItemListViewLayouter::maximumVisibleItems() const
 {
     const_cast<KItemListViewLayouter*>(this)->doLayout();
@@ -234,7 +260,7 @@ int KItemListViewLayouter::itemsPerOffset() const
 
 bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const
 {
-    return m_groupIndexes.contains(itemIndex);
+    return m_groupItemIndexes.contains(itemIndex);
 }
 
 void KItemListViewLayouter::markAsDirty()
@@ -282,16 +308,35 @@ void KItemListViewLayouter::doLayout()
             ++rowCount;
         }
 
-        m_itemBoundingRects.reserve(itemCount);
+        m_itemRects.reserve(itemCount);
 
         qreal y = m_headerHeight;
         int rowIndex = 0;
 
+        const bool grouped = createGroupHeaders();
+
         int index = 0;
         while (index < itemCount) {
             qreal x = m_xPosInc;
             qreal maxItemHeight = itemSize.height();
 
+            if (grouped) {
+                if (horizontalScrolling) {
+                    // All group headers will always be aligned on the top and not
+                    // flipped like the other properties
+                    x += m_groupHeaderHeight;
+                }
+
+                if (m_groupItemIndexes.contains(index)) {
+                    if (!horizontalScrolling) {
+                        // The item is the first item of a group.
+                        // Increase the y-position to provide space
+                        // for the group header.
+                        y += m_groupHeaderHeight;
+                    }
+                }
+            }
+
             int column = 0;
             while (index < itemCount && column < m_columnCount) {
                 qreal requiredItemHeight = itemSize.height();
@@ -304,56 +349,47 @@ void KItemListViewLayouter::doLayout()
                 }
 
                 const QRectF bounds(x, y, itemSize.width(), requiredItemHeight);
-                if (index < m_itemBoundingRects.count()) {
-                    m_itemBoundingRects[index] = bounds;
+                if (index < m_itemRects.count()) {
+                    m_itemRects[index] = bounds;
                 } else {
-                    m_itemBoundingRects.append(bounds);
+                    m_itemRects.append(bounds);
                 }
 
                 maxItemHeight = qMax(maxItemHeight, requiredItemHeight);
                 x += m_columnWidth;
                 ++index;
                 ++column;
+
+                if (grouped && m_groupItemIndexes.contains(index)) {
+                    // The item represents the first index of a group
+                    // and must aligned in the first column
+                    break;
+                }
             }
 
             y += maxItemHeight;
             ++rowIndex;
         }
-        if (m_itemBoundingRects.count() > itemCount) {
-            m_itemBoundingRects.erase(m_itemBoundingRects.begin() + itemCount,
-                                      m_itemBoundingRects.end());
+        if (m_itemRects.count() > itemCount) {
+            m_itemRects.erase(m_itemRects.begin() + itemCount,
+                                      m_itemRects.end());
         }
 
         if (itemCount > 0) {
-            m_maximumScrollOffset = m_itemBoundingRects.last().bottom();
+            m_maximumScrollOffset = m_itemRects.last().bottom();
             m_maximumItemOffset = m_columnCount * m_columnWidth;
         } else {
             m_maximumScrollOffset = 0;
             m_maximumItemOffset = 0;
         }
 
-        m_grouped = m_model->groupedSorting();
-        if (m_grouped) {
-            createGroupHeaders();
-
-            const int lastGroupItemCount = m_model->count() - m_groups.last().firstItemIndex;
-            m_maximumScrollOffset = m_groups.last().y + (lastGroupItemCount / m_columnCount) * itemSize.height();
-            if (lastGroupItemCount % m_columnCount != 0) {
-                m_maximumScrollOffset += itemSize.height();
-            }
-        }
-
 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
         kDebug() << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed();
 #endif
         m_dirty = false;
     }
 
-    if (m_grouped) {
-        updateGroupedVisibleIndexes();
-    } else {
-        updateVisibleIndexes();
-    }
+    updateVisibleIndexes();
 }
 
 void KItemListViewLayouter::updateVisibleIndexes()
@@ -362,7 +398,6 @@ void KItemListViewLayouter::updateVisibleIndexes()
         return;
     }
 
-    Q_ASSERT(!m_grouped);
     Q_ASSERT(!m_dirty);
 
     if (m_model->count() <= 0) {
@@ -372,145 +407,71 @@ void KItemListViewLayouter::updateVisibleIndexes()
         return;
     }
 
-    const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
-    const int minimumHeight =  horizontalScrolling ? m_itemSize.width()
-                                                   : m_itemSize.height();
-
-    // Calculate the first visible index:
-    // 1. Guess the index by using the minimum row height
     const int maxIndex = m_model->count() - 1;
-    m_firstVisibleIndex = int(m_scrollOffset / minimumHeight) * m_columnCount;
-
-    // 2. Decrease the index by checking the real row heights
-    int prevRowIndex = m_firstVisibleIndex - m_columnCount;
-    while (prevRowIndex > maxIndex) {
-        prevRowIndex -= m_columnCount;
-    }
 
-    const qreal top = m_scrollOffset + m_headerHeight;
-    while (prevRowIndex >= 0 && m_itemBoundingRects[prevRowIndex].bottom() >= top) {
-        m_firstVisibleIndex = prevRowIndex;
-        prevRowIndex -= m_columnCount;
-    }
-    m_firstVisibleIndex = qBound(0, m_firstVisibleIndex, maxIndex);
-
-    // Calculate the last visible index
-    const int visibleHeight = horizontalScrolling ? m_size.width() : m_size.height();
-    const qreal bottom = m_scrollOffset + visibleHeight;
-    m_lastVisibleIndex = m_firstVisibleIndex; // first visible row, first column
-    int nextRowIndex = m_lastVisibleIndex + m_columnCount;
-    while (nextRowIndex <= maxIndex && m_itemBoundingRects[nextRowIndex].y() <= bottom) {
-        m_lastVisibleIndex = nextRowIndex;
-        nextRowIndex += m_columnCount;
-    }
-    m_lastVisibleIndex += m_columnCount - 1; // move it to the last column
-    m_lastVisibleIndex = qBound(0, m_lastVisibleIndex, maxIndex);
-
-    m_visibleIndexesDirty = false;
-}
-
-void KItemListViewLayouter::updateGroupedVisibleIndexes()
-{
-    if (!m_visibleIndexesDirty) {
-        return;
-    }
-
-    Q_ASSERT(m_grouped);
-    Q_ASSERT(!m_dirty);
-
-    if (m_model->count() <= 0) {
-        m_firstVisibleIndex = -1;
-        m_lastVisibleIndex = -1;
-        m_visibleIndexesDirty = false;
-        return;
-    }
-
-    // Find the first visible group
-    const int lastGroupIndex = m_groups.count() - 1;
-    int groupIndex = lastGroupIndex;
-    for (int i = 1; i < m_groups.count(); ++i) {
-        if (m_groups[i].y >= m_scrollOffset) {
-            groupIndex = i - 1;
-            break;
+    // Calculate the first visible index that is (at least partly) visible
+    int min = 0;
+    int max = maxIndex;
+    int mid = 0;
+    do {
+        mid = (min + max) / 2;
+        if (m_itemRects[mid].bottom() < m_scrollOffset) {
+            min = mid + 1;
+        } else {
+            max = mid - 1;
         }
-    }
+    } while (min <= max);
 
-    // Calculate the first visible index
-    qreal groupY = m_groups[groupIndex].y;
-    m_firstVisibleIndex = m_groups[groupIndex].firstItemIndex;
-    const int invisibleRowCount = int(m_scrollOffset - groupY) / int(m_itemSize.height());
-    m_firstVisibleIndex += invisibleRowCount * m_columnCount;
-    if (groupIndex + 1 <= lastGroupIndex) {
-        // Check whether the calculated first visible index remains inside the current
-        // group. If this is not the case let the first element of the next group be the first
-        // visible index.
-        const int nextGroupIndex = m_groups[groupIndex + 1].firstItemIndex;
-        if (m_firstVisibleIndex > nextGroupIndex) {
-            m_firstVisibleIndex = nextGroupIndex;
-        }
+    while (mid < maxIndex && m_itemRects[mid].bottom() < m_scrollOffset) {
+        ++mid;
     }
+    m_firstVisibleIndex = mid;
 
-    m_firstVisibleGroupIndex = groupIndex;
-
-    const int maxIndex = m_model->count() - 1;
-    m_firstVisibleIndex = qBound(0, m_firstVisibleIndex, maxIndex);
-
-    // Calculate the last visible index: Find group where the last visible item is shown.
-    const qreal visibleBottom = m_scrollOffset + m_size.height(); // TODO: respect Qt::Horizontal alignment
-    while ((groupIndex < lastGroupIndex) && (m_groups[groupIndex + 1].y < visibleBottom)) {
-        ++groupIndex;
+    // Calculate the last visible index that is (at least partly) visible
+    const int visibleHeight = (m_scrollOrientation == Qt::Horizontal) ? m_size.width() : m_size.height();
+    qreal bottom = m_scrollOffset + visibleHeight;
+    if (m_model->groupedSorting()) {
+        bottom += m_groupHeaderHeight;
     }
 
-    groupY = m_groups[groupIndex].y;
-    m_lastVisibleIndex = m_groups[groupIndex].firstItemIndex;
-    const int availableHeight = static_cast<int>(visibleBottom - groupY);
-    int visibleRowCount = availableHeight / int(m_itemSize.height());
-    if (availableHeight % int(m_itemSize.height()) != 0) {
-        ++visibleRowCount;
-    }
-    m_lastVisibleIndex += visibleRowCount * m_columnCount - 1;
-
-    if (groupIndex + 1 <= lastGroupIndex) {
-        // Check whether the calculate last visible index remains inside the current group.
-        // If this is not the case let the last element of this group be the last visible index.
-        const int nextGroupIndex = m_groups[groupIndex + 1].firstItemIndex;
-        if (m_lastVisibleIndex >= nextGroupIndex) {
-            m_lastVisibleIndex = nextGroupIndex - 1;
+    min = m_firstVisibleIndex;
+    max = maxIndex;
+    do {
+        mid = (min + max) / 2;
+        if (m_itemRects[mid].y() <= bottom) {
+            min = mid + 1;
+        } else {
+            max = mid - 1;
         }
+    } while (min <= max);
+
+    while (mid > 0 && m_itemRects[mid].y() > bottom) {
+        --mid;
     }
-    m_lastVisibleIndex = qBound(0, m_lastVisibleIndex, maxIndex);
+    m_lastVisibleIndex = mid;
 
     m_visibleIndexesDirty = false;
 }
 
-void KItemListViewLayouter::createGroupHeaders()
+bool KItemListViewLayouter::createGroupHeaders()
 {
-    m_groups.clear();
-    m_groupIndexes.clear();
+    if (!m_model->groupedSorting()) {
+        return false;
+    }
+
+    m_groupItemIndexes.clear();
 
     const QList<QPair<int, QVariant> > groups = m_model->groups();
+    if (groups.isEmpty()) {
+        return false;
+    }
 
-    qreal y = 0;
     for (int i = 0; i < groups.count(); ++i) {
         const int firstItemIndex = groups.at(i).first;
-        if (i > 0) {
-            const int previousGroupItemCount = firstItemIndex - m_groups.last().firstItemIndex;
-            int previousGroupRowCount = previousGroupItemCount / m_columnCount;
-            if (previousGroupItemCount % m_columnCount != 0) {
-                ++previousGroupRowCount;
-            }
-            const qreal previousGroupHeight = previousGroupRowCount * m_itemSize.height();
-            y += previousGroupHeight;
-        }
-        y += HeaderHeight;
-
-        ItemGroup itemGroup;
-        itemGroup.firstItemIndex = firstItemIndex;
-        itemGroup.y = y;
-
-        m_groups.append(itemGroup);
-        m_groupIndexes.insert(firstItemIndex);
+        m_groupItemIndexes.insert(firstItemIndex);
     }
+
+    return true;
 }
 
 #include "kitemlistviewlayouter_p.moc"