X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/f9bcd0a47cbdf0806c35a82856efdbe06279fb82..e29e1cda15973969164d7b7fa805544189a5e172:/src/kitemviews/kitemlistviewlayouter.cpp diff --git a/src/kitemviews/kitemlistviewlayouter.cpp b/src/kitemviews/kitemlistviewlayouter.cpp index 78688c941..2d02b6725 100644 --- a/src/kitemviews/kitemlistviewlayouter.cpp +++ b/src/kitemviews/kitemlistviewlayouter.cpp @@ -24,35 +24,31 @@ #include -#define KITEMLISTVIEWLAYOUTER_DEBUG - -namespace { - // TODO - const int HeaderHeight = 50; -}; +// #define KITEMLISTVIEWLAYOUTER_DEBUG 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), + m_itemMargin(), m_headerHeight(0), m_model(0), m_sizeHintResolver(0), - m_offset(0), - m_maximumOffset(0), + m_scrollOffset(0), + m_maximumScrollOffset(0), + m_itemOffset(0), + 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() { } @@ -99,6 +95,19 @@ QSizeF KItemListViewLayouter::itemSize() const return m_itemSize; } +void KItemListViewLayouter::setItemMargin(const QSizeF& margin) +{ + if (m_itemMargin != margin) { + m_itemMargin = margin; + m_dirty = true; + } +} + +QSizeF KItemListViewLayouter::itemMargin() const +{ + return m_itemMargin; +} + void KItemListViewLayouter::setHeaderHeight(qreal height) { if (m_headerHeight != height) { @@ -112,17 +121,55 @@ qreal KItemListViewLayouter::headerHeight() const return m_headerHeight; } -void KItemListViewLayouter::setOffset(qreal offset) +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) { + m_scrollOffset = offset; + m_visibleIndexesDirty = true; + } +} + +qreal KItemListViewLayouter::scrollOffset() const +{ + return m_scrollOffset; +} + +qreal KItemListViewLayouter::maximumScrollOffset() const +{ + const_cast(this)->doLayout(); + return m_maximumScrollOffset; +} + +void KItemListViewLayouter::setItemOffset(qreal offset) { - if (m_offset != offset) { - m_offset = offset; + if (m_itemOffset != offset) { + m_itemOffset = offset; m_visibleIndexesDirty = true; } } -qreal KItemListViewLayouter::offset() const +qreal KItemListViewLayouter::itemOffset() const { - return m_offset; + return m_itemOffset; +} + +qreal KItemListViewLayouter::maximumItemOffset() const +{ + const_cast(this)->doLayout(); + return m_maximumItemOffset; } void KItemListViewLayouter::setModel(const KItemModelBase* model) @@ -151,12 +198,6 @@ const KItemListSizeHintResolver* KItemListViewLayouter::sizeHintResolver() const return m_sizeHintResolver; } -qreal KItemListViewLayouter::maximumOffset() const -{ - const_cast(this)->doLayout(); - return m_maximumOffset; -} - int KItemListViewLayouter::firstVisibleIndex() const { const_cast(this)->doLayout(); @@ -169,31 +210,51 @@ int KItemListViewLayouter::lastVisibleIndex() const return m_lastVisibleIndex; } -QRectF KItemListViewLayouter::itemBoundingRect(int index) const +QRectF KItemListViewLayouter::itemRect(int index) const { const_cast(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_offset; + pos.rx() -= m_scrollOffset; bounds.moveTo(pos); return bounds; } - QRectF bounds = m_itemBoundingRects[index]; - QPointF pos = bounds.topLeft(); - pos.ry() -= m_offset; - bounds.moveTo(pos); + QRectF bounds = m_itemRects[index]; + bounds.moveTo(bounds.topLeft() - QPointF(m_itemOffset, m_scrollOffset)); return bounds; } +QRectF KItemListViewLayouter::groupHeaderRect(int index) const +{ + const_cast(this)->doLayout(); + + 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(minimumGroupHeaderWidth(), m_groupHeaderHeight); + } + return QRectF(pos, size); +} + int KItemListViewLayouter::maximumVisibleItems() const { const_cast(this)->doLayout(); @@ -208,14 +269,10 @@ int KItemListViewLayouter::maximumVisibleItems() const return rows * m_columnCount; } -int KItemListViewLayouter::itemsPerOffset() const -{ - return m_columnCount; -} - bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const { - return m_groupIndexes.contains(itemIndex); + const_cast(this)->doLayout(); + return m_groupItemIndexes.contains(itemIndex); } void KItemListViewLayouter::markAsDirty() @@ -233,27 +290,44 @@ void KItemListViewLayouter::doLayout() m_visibleIndexesDirty = true; QSizeF itemSize = m_itemSize; + QSizeF itemMargin = m_itemMargin; QSizeF size = m_size; + + const bool grouped = createGroupHeaders(); const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal); if (horizontalScrolling) { + // Flip everything so that the layout logically can work like having + // a vertical scrolling itemSize.setWidth(m_itemSize.height()); itemSize.setHeight(m_itemSize.width()); + itemMargin.setWidth(m_itemMargin.height()); + itemMargin.setHeight(m_itemMargin.width()); size.setWidth(m_size.height()); size.setHeight(m_size.width()); + + if (grouped) { + // In the horizontal scrolling case all groups are aligned + // at the top, which decreases the available height. For the + // flipped data this means that the width must be decreased. + size.rwidth() -= m_groupHeaderHeight; + } } - m_columnWidth = itemSize.width(); - m_columnCount = qMax(1, int(size.width() / m_columnWidth)); - m_xPosInc = 0; + m_columnWidth = itemSize.width() + itemMargin.width(); + const qreal widthForColumns = size.width() - itemMargin.width(); + m_columnCount = qMax(1, int(widthForColumns / m_columnWidth)); + m_xPosInc = itemMargin.width(); const int itemCount = m_model->count(); - if (itemCount > m_columnCount) { + if (itemCount > m_columnCount && m_columnWidth >= 32) { // Apply the unused width equally to each column const qreal unusedWidth = size.width() - m_columnCount * m_columnWidth; - const qreal columnInc = unusedWidth / (m_columnCount + 1); - m_columnWidth += columnInc; - m_xPosInc += columnInc; + if (unusedWidth > 0) { + const qreal columnInc = unusedWidth / (m_columnCount + 1); + m_columnWidth += columnInc; + m_xPosInc += columnInc; + } } int rowCount = itemCount / m_columnCount; @@ -261,9 +335,9 @@ void KItemListViewLayouter::doLayout() ++rowCount; } - m_itemBoundingRects.reserve(itemCount); + m_itemRects.reserve(itemCount); - qreal y = m_headerHeight; + qreal y = m_headerHeight + itemMargin.height(); int rowIndex = 0; int index = 0; @@ -271,6 +345,23 @@ void KItemListViewLayouter::doLayout() 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(); @@ -283,40 +374,67 @@ 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); + } + + 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 headerWidth = minimumGroupHeaderWidth(); + if (requiredItemHeight < headerWidth) { + requiredItemHeight = headerWidth; + } } 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; + y += maxItemHeight + itemMargin.height(); ++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()); } - m_maximumOffset = (itemCount > 0) ? m_itemBoundingRects.last().bottom() : 0; + if (itemCount > 0) { + // Calculate the maximum y-range of the last row for m_maximumScrollOffset + m_maximumScrollOffset = m_itemRects.last().bottom(); + const qreal rowY = m_itemRects.last().y(); - m_grouped = !m_model->groupRole().isEmpty(); - /*if (m_grouped) { - createGroupHeaders(); - - const int lastGroupItemCount = m_model->count() - m_groups.last().firstItemIndex; - m_maximumOffset = m_groups.last().y + (lastGroupItemCount / m_columnCount) * m_rowHeight; - if (lastGroupItemCount % m_columnCount != 0) { - m_maximumOffset += m_rowHeight; + int index = m_itemRects.count() - 2; + while (index >= 0 && m_itemRects.at(index).bottom() >= rowY) { + m_maximumScrollOffset = qMax(m_maximumScrollOffset, m_itemRects.at(index).bottom()); + --index; } - } else {*/ - // m_maximumOffset = m_minimumRowHeight * rowCount; - //} + + m_maximumItemOffset = m_columnCount * m_columnWidth; + } else { + m_maximumScrollOffset = 0; + m_maximumItemOffset = 0; + } #ifdef KITEMLISTVIEWLAYOUTER_DEBUG kDebug() << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed(); @@ -324,11 +442,7 @@ void KItemListViewLayouter::doLayout() m_dirty = false; } - if (m_grouped) { - updateGroupedVisibleIndexes(); - } else { - updateVisibleIndexes(); - } + updateVisibleIndexes(); } void KItemListViewLayouter::updateVisibleIndexes() @@ -337,7 +451,6 @@ void KItemListViewLayouter::updateVisibleIndexes() return; } - Q_ASSERT(!m_grouped); Q_ASSERT(!m_dirty); if (m_model->count() <= 0) { @@ -347,146 +460,86 @@ 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_offset / 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; - } + // Calculate the first visible index that is fully visible + int min = 0; + int max = maxIndex; + int mid = 0; + do { + mid = (min + max) / 2; + if (m_itemRects[mid].top() < m_scrollOffset) { + min = mid + 1; + } else { + max = mid - 1; + } + } while (min <= max); + + if (mid > 0) { + // Include the row before the first fully visible index, as it might + // be partly visible + if (m_itemRects[mid].top() >= m_scrollOffset) { + --mid; + Q_ASSERT(m_itemRects[mid].top() < m_scrollOffset); + } - while (prevRowIndex >= 0 && m_itemBoundingRects[prevRowIndex].bottom() >= m_offset) { - 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_offset + 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; + const qreal rowTop = m_itemRects[mid].top(); + while (mid > 0 && m_itemRects[mid - 1].top() == rowTop) { + --mid; + } } - m_lastVisibleIndex += m_columnCount - 1; // move it to the last column - m_lastVisibleIndex = qBound(0, m_lastVisibleIndex, maxIndex); - - m_visibleIndexesDirty = false; -} + m_firstVisibleIndex = mid; -void KItemListViewLayouter::updateGroupedVisibleIndexes() -{ - if (!m_visibleIndexesDirty) { - return; + // 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; } - Q_ASSERT(m_grouped); - Q_ASSERT(!m_dirty); + 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); - if (m_model->count() <= 0) { - m_firstVisibleIndex = -1; - m_lastVisibleIndex = -1; - m_visibleIndexesDirty = false; - return; + while (mid > 0 && m_itemRects[mid].y() > bottom) { + --mid; } + m_lastVisibleIndex = mid; - // 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_offset) { - groupIndex = i - 1; - break; - } - } + m_visibleIndexesDirty = false; +} - // Calculate the first visible index - qreal groupY = m_groups[groupIndex].y; - m_firstVisibleIndex = m_groups[groupIndex].firstItemIndex; - const int invisibleRowCount = int(m_offset - 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; - } +bool KItemListViewLayouter::createGroupHeaders() +{ + if (!m_model->groupedSorting()) { + return false; } - m_firstVisibleGroupIndex = groupIndex; - - const int maxIndex = m_model->count() - 1; - m_firstVisibleIndex = qBound(0, m_firstVisibleIndex, maxIndex); + m_groupItemIndexes.clear(); - // Calculate the last visible index: Find group where the last visible item is shown. - const qreal visibleBottom = m_offset + m_size.height(); // TODO: respect Qt::Horizontal alignment - while ((groupIndex < lastGroupIndex) && (m_groups[groupIndex + 1].y < visibleBottom)) { - ++groupIndex; + const QList > groups = m_model->groups(); + if (groups.isEmpty()) { + return false; } - groupY = m_groups[groupIndex].y; - m_lastVisibleIndex = m_groups[groupIndex].firstItemIndex; - const int availableHeight = static_cast(visibleBottom - groupY); - int visibleRowCount = availableHeight / int(m_itemSize.height()); - if (availableHeight % int(m_itemSize.height()) != 0) { - ++visibleRowCount; + for (int i = 0; i < groups.count(); ++i) { + const int firstItemIndex = groups.at(i).first; + m_groupItemIndexes.insert(firstItemIndex); } - 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; - } - } - //Q_ASSERT(m_lastVisibleIndex < m_model->count()); - m_lastVisibleIndex = qBound(0, m_lastVisibleIndex, maxIndex); - m_visibleIndexesDirty = false; + return true; } -void KItemListViewLayouter::createGroupHeaders() +qreal KItemListViewLayouter::minimumGroupHeaderWidth() const { - m_groups.clear(); - m_groupIndexes.clear(); - - // TODO: - QList numbers; - numbers << 0 << 5 << 6 << 13 << 20 << 25 << 30 << 35 << 50; - - qreal y = 0; - for (int i = 0; i < numbers.count(); ++i) { - if (i > 0) { - const int previousGroupItemCount = numbers[i] - 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 = numbers[i]; - itemGroup.y = y; - - m_groups.append(itemGroup); - m_groupIndexes.insert(itemGroup.firstItemIndex); - } + return m_groupHeaderHeight * 15 / 2; } #include "kitemlistviewlayouter_p.moc"