-/***************************************************************************
- * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
- * *
- * 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 <peter.penz19@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
#include "kitemlistviewlayouter.h"
-
-#include <kitemviews/kitemmodelbase.h>
+#include "dolphindebug.h"
#include "kitemlistsizehintresolver.h"
+#include "kitemviews/kitemmodelbase.h"
-#include <KDebug>
+#include <QGuiApplication>
+#include <QScopeGuard>
// #define KITEMLISTVIEWLAYOUTER_DEBUG
m_itemSize(128, 128),
m_itemMargin(),
m_headerHeight(0),
- m_model(0),
+ m_model(nullptr),
m_sizeHintResolver(sizeHintResolver),
m_scrollOffset(0),
m_maximumScrollOffset(0),
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();
+
+ 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;
- }
+ 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;
}
+ }
- 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 = QGuiApplication::isRightToLeft() ? 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;
+ }
- for (int column = 0; column < m_columnCount; ++column) {
- m_columnOffsets[column] = currentOffset;
- currentOffset += m_columnWidth;
- }
+ if (QGuiApplication::isLeftToRight()) for (int column = 0; column < m_columnCount; ++column) {
+ m_columnOffsets[column] = currentOffset;
+ currentOffset += m_columnWidth;
+ }
+ else for (int column = 0; column < m_columnCount; ++column) {
+ m_columnOffsets[column] = currentOffset - m_columnWidth;
+ 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;
}
- updateVisibleIndexes();
+ if (itemCount > 0) {
+ m_maximumScrollOffset = y;
+ m_maximumItemOffset = m_columnCount * m_columnWidth;
+ } else {
+ m_maximumScrollOffset = 0;
+ m_maximumItemOffset = 0;
+ }
+
+#ifdef KITEMLISTVIEWLAYOUTER_DEBUG
+ qCDebug(DolphinDebug) << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed();
+#endif
+ m_dirty = false;
}
void KItemListViewLayouter::updateVisibleIndexes()