X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/32b51b7b92e16cfaf1121cbe80ca267fc8bb0718..a27443d904bc220615f2c4e8df74187b0c806d8a:/src/kitemviews/private/kitemlistviewlayouter.cpp diff --git a/src/kitemviews/private/kitemlistviewlayouter.cpp b/src/kitemviews/private/kitemlistviewlayouter.cpp index e2dcc62e7..3ed2343a8 100644 --- a/src/kitemviews/private/kitemlistviewlayouter.cpp +++ b/src/kitemviews/private/kitemlistviewlayouter.cpp @@ -1,57 +1,46 @@ -/*************************************************************************** - * Copyright (C) 2011 by Peter Penz * - * * - * 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: 2011 Peter Penz + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "kitemlistviewlayouter.h" - -#include +#include "dolphindebug.h" #include "kitemlistsizehintresolver.h" +#include "kitemviews/kitemmodelbase.h" -#include +#include +#include // #define KITEMLISTVIEWLAYOUTER_DEBUG -KItemListViewLayouter::KItemListViewLayouter(KItemListSizeHintResolver* sizeHintResolver, QObject* parent) : - QObject(parent), - m_dirty(true), - m_visibleIndexesDirty(true), - m_scrollOrientation(Qt::Vertical), - m_size(), - m_itemSize(128, 128), - m_itemMargin(), - m_headerHeight(0), - m_model(0), - m_sizeHintResolver(sizeHintResolver), - m_scrollOffset(0), - m_maximumScrollOffset(0), - m_itemOffset(0), - m_maximumItemOffset(0), - m_firstVisibleIndex(-1), - m_lastVisibleIndex(-1), - m_columnWidth(0), - m_xPosInc(0), - m_columnCount(0), - m_rowOffsets(), - m_columnOffsets(), - m_groupItemIndexes(), - m_groupHeaderHeight(0), - m_groupHeaderMargin(0), - m_itemInfos() +KItemListViewLayouter::KItemListViewLayouter(KItemListSizeHintResolver *sizeHintResolver, QObject *parent) + : QObject(parent) + , m_dirty(true) + , m_visibleIndexesDirty(true) + , m_scrollOrientation(Qt::Vertical) + , m_size() + , m_itemSize(128, 128) + , m_itemMargin() + , m_headerHeight(0) + , m_model(nullptr) + , m_sizeHintResolver(sizeHintResolver) + , m_scrollOffset(0) + , m_maximumScrollOffset(0) + , m_itemOffset(0) + , m_maximumItemOffset(0) + , m_firstVisibleIndex(-1) + , m_lastVisibleIndex(-1) + , m_columnWidth(0) + , m_xPosInc(0) + , m_columnCount(0) + , m_rowOffsets() + , m_columnOffsets() + , m_groupItemIndexes() + , m_groupHeaderHeight(0) + , m_groupHeaderMargin(0) + , m_itemInfos() + , m_statusBarOffset(0) { Q_ASSERT(m_sizeHintResolver); } @@ -73,7 +62,7 @@ Qt::Orientation KItemListViewLayouter::scrollOrientation() const return m_scrollOrientation; } -void KItemListViewLayouter::setSize(const QSizeF& size) +void KItemListViewLayouter::setSize(const QSizeF &size) { if (m_size != size) { if (m_scrollOrientation == Qt::Vertical) { @@ -94,7 +83,7 @@ QSizeF KItemListViewLayouter::size() const return m_size; } -void KItemListViewLayouter::setItemSize(const QSizeF& size) +void KItemListViewLayouter::setItemSize(const QSizeF &size) { if (m_itemSize != size) { m_itemSize = size; @@ -107,7 +96,7 @@ QSizeF KItemListViewLayouter::itemSize() const return m_itemSize; } -void KItemListViewLayouter::setItemMargin(const QSizeF& margin) +void KItemListViewLayouter::setItemMargin(const QSizeF &margin) { if (m_itemMargin != margin) { m_itemMargin = margin; @@ -174,7 +163,7 @@ qreal KItemListViewLayouter::scrollOffset() const qreal KItemListViewLayouter::maximumScrollOffset() const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); return m_maximumScrollOffset; } @@ -193,11 +182,11 @@ qreal KItemListViewLayouter::itemOffset() const qreal KItemListViewLayouter::maximumItemOffset() const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); return m_maximumItemOffset; } -void KItemListViewLayouter::setModel(const KItemModelBase* model) +void KItemListViewLayouter::setModel(const KItemModelBase *model) { if (m_model != model) { m_model = model; @@ -205,26 +194,26 @@ void KItemListViewLayouter::setModel(const KItemModelBase* model) } } -const KItemModelBase* KItemListViewLayouter::model() const +const KItemModelBase *KItemListViewLayouter::model() const { return m_model; } int KItemListViewLayouter::firstVisibleIndex() const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); return m_firstVisibleIndex; } int KItemListViewLayouter::lastVisibleIndex() const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); return m_lastVisibleIndex; } QRectF KItemListViewLayouter::itemRect(int index) const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); if (index < 0 || index >= m_itemInfos.count()) { return QRectF(); } @@ -238,8 +227,12 @@ QRectF KItemListViewLayouter::itemRect(int index) const // Rotate the logical direction which is always vertical by 90° // to get the physical horizontal direction QPointF pos(y, x); - pos.rx() -= m_scrollOffset; sizeHint.transpose(); + if (QGuiApplication::isRightToLeft()) { + pos.rx() = m_size.width() - 1 + m_scrollOffset - pos.x() - sizeHint.width(); + } else { + pos.rx() -= m_scrollOffset; + } return QRectF(pos, sizeHint); } @@ -254,7 +247,7 @@ QRectF KItemListViewLayouter::itemRect(int index) const QRectF KItemListViewLayouter::groupHeaderRect(int index) const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); const QRectF firstItemRect = itemRect(index); QPointF pos = firstItemRect.topLeft(); @@ -268,7 +261,6 @@ QRectF KItemListViewLayouter::groupHeaderRect(int index) const pos.ry() -= m_groupHeaderHeight; size = QSizeF(m_size.width(), m_groupHeaderHeight); } else { - pos.rx() -= m_itemMargin.width(); pos.ry() = 0; // Determine the maximum width used in the current column. As the @@ -283,9 +275,8 @@ QRectF KItemListViewLayouter::groupHeaderRect(int index) const break; } - const qreal itemWidth = (m_scrollOrientation == Qt::Vertical) - ? m_sizeHintResolver->sizeHint(index).width() - : m_sizeHintResolver->sizeHint(index).height(); + const qreal itemWidth = + (m_scrollOrientation == Qt::Vertical) ? m_sizeHintResolver->sizeHint(index).width() : m_sizeHintResolver->sizeHint(index).height(); if (itemWidth > headerWidth) { headerWidth = itemWidth; @@ -295,37 +286,40 @@ QRectF KItemListViewLayouter::groupHeaderRect(int index) const } size = QSizeF(headerWidth, m_size.height()); + + if (QGuiApplication::isRightToLeft()) { + pos.setX(firstItemRect.right() + m_itemMargin.width() - size.width()); + } else { + pos.rx() -= m_itemMargin.width(); + } } + return QRectF(pos, size); } int KItemListViewLayouter::itemColumn(int index) const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); if (index < 0 || index >= m_itemInfos.count()) { return -1; } - return (m_scrollOrientation == Qt::Vertical) - ? m_itemInfos[index].column - : m_itemInfos[index].row; + return (m_scrollOrientation == Qt::Vertical) ? m_itemInfos[index].column : m_itemInfos[index].row; } int KItemListViewLayouter::itemRow(int index) const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); if (index < 0 || index >= m_itemInfos.count()) { return -1; } - return (m_scrollOrientation == Qt::Vertical) - ? m_itemInfos[index].row - : m_itemInfos[index].column; + return (m_scrollOrientation == Qt::Vertical) ? m_itemInfos[index].row : m_itemInfos[index].column; } int KItemListViewLayouter::maximumVisibleItems() const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); const int height = static_cast(m_size.height()); const int rowHeight = static_cast(m_itemSize.height()); @@ -339,7 +333,7 @@ int KItemListViewLayouter::maximumVisibleItems() const bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const { - const_cast(this)->doLayout(); + const_cast(this)->doLayout(); return m_groupItemIndexes.contains(itemIndex); } @@ -348,180 +342,204 @@ void KItemListViewLayouter::markAsDirty() m_dirty = true; } +void KItemListViewLayouter::setStatusBarOffset(int offset) +{ + if (m_statusBarOffset != offset) { + m_statusBarOffset = offset; + } +} #ifndef QT_NO_DEBUG - bool KItemListViewLayouter::isDirty() - { - return m_dirty; - } +bool KItemListViewLayouter::isDirty() +{ + return m_dirty; +} #endif void KItemListViewLayouter::doLayout() { - if (m_dirty) { + // we always want to update visible indexes after performing a layout + auto qsg = qScopeGuard([this] { + updateVisibleIndexes(); + }); + + if (!m_dirty) { + return; + } + #ifdef KITEMLISTVIEWLAYOUTER_DEBUG - QElapsedTimer timer; - timer.start(); + QElapsedTimer timer; + timer.start(); #endif - 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.transpose(); - itemMargin.transpose(); - size.transpose(); - - 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_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.transpose(); + itemMargin.transpose(); + size.transpose(); + size.rwidth() -= m_statusBarOffset; + + 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() + 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 && m_columnWidth >= 32) { - // Apply the unused width equally to each column - const qreal unusedWidth = widthForColumns - m_columnCount * m_columnWidth; - if (unusedWidth > 0) { - const qreal columnInc = unusedWidth / (m_columnCount + 1); - m_columnWidth += columnInc; - m_xPosInc += columnInc; - } + const bool isRightToLeft = QGuiApplication::isRightToLeft(); + m_columnWidth = itemSize.width() + itemMargin.width(); + const qreal widthForColumns = std::max(size.width() - itemMargin.width(), m_columnWidth); + m_columnCount = qMax(1, int(widthForColumns / m_columnWidth)); + m_xPosInc = itemMargin.width(); + + const int itemCount = m_model->count(); + if (itemCount > m_columnCount && m_columnWidth >= 32) { + // Apply the unused width equally to each column + const qreal unusedWidth = widthForColumns - m_columnCount * m_columnWidth; + if (unusedWidth > 0) { + const qreal columnInc = unusedWidth / (m_columnCount + 1); + m_columnWidth += columnInc; + m_xPosInc += columnInc; } + } - m_itemInfos.resize(itemCount); + m_itemInfos.resize(itemCount); - // Calculate the offset of each column, i.e., the x-coordinate where the column starts. - m_columnOffsets.resize(m_columnCount); - qreal currentOffset = m_xPosInc; + // Calculate the offset of each column, i.e., the x-coordinate where the column starts. + m_columnOffsets.resize(m_columnCount); + qreal currentOffset = isRightToLeft && !horizontalScrolling ? widthForColumns : m_xPosInc; - if (grouped && horizontalScrolling) { - // All group headers will always be aligned on the top and not - // flipped like the other properties. - currentOffset += m_groupHeaderHeight; - } + if (grouped && horizontalScrolling) { + // All group headers will always be aligned on the top and not + // flipped like the other properties. + currentOffset += m_groupHeaderHeight; + } + if (isRightToLeft) { + for (int column = 0; column < m_columnCount; ++column) { + if (horizontalScrolling) { + m_columnOffsets[column] = currentOffset + column * m_columnWidth; + } else { + currentOffset -= m_columnWidth; + m_columnOffsets[column] = currentOffset; + } + } + } else { for (int column = 0; column < m_columnCount; ++column) { m_columnOffsets[column] = currentOffset; currentOffset += m_columnWidth; } + } - // Prepare the QVector which stores the y-coordinate for each new row. - int numberOfRows = (itemCount + m_columnCount - 1) / m_columnCount; - if (grouped && m_columnCount > 1) { - // In the worst case, a new row will be started for every group. - // We could calculate the exact number of rows now to prevent that we reserve - // too much memory, but the code required to do that might need much more - // memory than it would save in the average case. - numberOfRows += m_groupItemIndexes.count(); - } - m_rowOffsets.resize(numberOfRows); - - qreal y = m_headerHeight + itemMargin.height(); - int row = 0; - - int index = 0; - while (index < itemCount) { - qreal maxItemHeight = itemSize.height(); - - if (grouped) { - if (m_groupItemIndexes.contains(index)) { - // The item is the first item of a group. - // Increase the y-position to provide space - // for the group header. - if (index > 0) { - // Only add a margin if there has been added another - // group already before - y += m_groupHeaderMargin; - } else if (!horizontalScrolling) { - // The first group header should be aligned on top - y -= itemMargin.height(); - } - - if (!horizontalScrolling) { - y += m_groupHeaderHeight; - } + // Prepare the QVector which stores the y-coordinate for each new row. + int numberOfRows = (itemCount + m_columnCount - 1) / m_columnCount; + if (grouped && m_columnCount > 1) { + // In the worst case, a new row will be started for every group. + // We could calculate the exact number of rows now to prevent that we reserve + // too much memory, but the code required to do that might need much more + // memory than it would save in the average case. + numberOfRows += m_groupItemIndexes.count(); + } + m_rowOffsets.resize(numberOfRows); + + qreal y = m_headerHeight + itemMargin.height(); + int row = 0; + + int index = 0; + while (index < itemCount) { + qreal maxItemHeight = itemSize.height(); + + if (grouped) { + if (m_groupItemIndexes.contains(index)) { + // The item is the first item of a group. + // Increase the y-position to provide space + // for the group header. + if (index > 0) { + // Only add a margin if there has been added another + // group already before + y += m_groupHeaderMargin; + } else if (!horizontalScrolling) { + // The first group header should be aligned on top + y -= itemMargin.height(); } - } - - m_rowOffsets[row] = y; - int column = 0; - while (index < itemCount && column < m_columnCount) { - qreal requiredItemHeight = itemSize.height(); - const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index); - const qreal sizeHintHeight = sizeHint.height(); - if (sizeHintHeight > requiredItemHeight) { - requiredItemHeight = sizeHintHeight; + if (!horizontalScrolling) { + y += m_groupHeaderHeight; } + } + } - ItemInfo& itemInfo = m_itemInfos[index]; - itemInfo.column = column; - itemInfo.row = row; - - 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; - } - } + m_rowOffsets[row] = y; - maxItemHeight = qMax(maxItemHeight, requiredItemHeight); - ++index; - ++column; + int column = 0; + while (index < itemCount && column < m_columnCount) { + qreal requiredItemHeight = itemSize.height(); + const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index); + const qreal sizeHintHeight = sizeHint.height(); + if (sizeHintHeight > requiredItemHeight) { + requiredItemHeight = sizeHintHeight; + } - if (grouped && m_groupItemIndexes.contains(index)) { - // The item represents the first index of a group - // and must aligned in the first column - break; + ItemInfo &itemInfo = m_itemInfos[index]; + itemInfo.column = column; + itemInfo.row = row; + + 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; } } - y += maxItemHeight + itemMargin.height(); - ++row; - } + maxItemHeight = qMax(maxItemHeight, requiredItemHeight); + ++index; + ++column; - if (itemCount > 0) { - m_maximumScrollOffset = y; - m_maximumItemOffset = m_columnCount * m_columnWidth; - } else { - m_maximumScrollOffset = 0; - m_maximumItemOffset = 0; + if (grouped && m_groupItemIndexes.contains(index)) { + // The item represents the first index of a group + // and must aligned in the first column + break; + } } -#ifdef KITEMLISTVIEWLAYOUTER_DEBUG - kDebug() << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed(); -#endif - m_dirty = false; + y += maxItemHeight + itemMargin.height(); + ++row; + } + + if (itemCount > 0) { + m_maximumScrollOffset = y; + m_maximumItemOffset = m_columnCount * m_columnWidth; + } else { + m_maximumScrollOffset = 0; + m_maximumItemOffset = 0; } - updateVisibleIndexes(); +#ifdef KITEMLISTVIEWLAYOUTER_DEBUG + qCDebug(DolphinDebug) << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed(); +#endif + m_dirty = false; } void KItemListViewLayouter::updateVisibleIndexes() @@ -603,7 +621,7 @@ bool KItemListViewLayouter::createGroupHeaders() m_groupItemIndexes.clear(); - const QList > groups = m_model->groups(); + const QList> groups = m_model->groups(); if (groups.isEmpty()) { return false; } @@ -621,3 +639,4 @@ qreal KItemListViewLayouter::minimumGroupHeaderWidth() const return 100; } +#include "moc_kitemlistviewlayouter.cpp"