]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/private/kitemlistviewlayouter.cpp
KItemListViewLayouter: handle grid view layouts in RtL properly
[dolphin.git] / src / kitemviews / private / kitemlistviewlayouter.cpp
index 04325c7d0bc3c74bb8fa21b3d82bc2a19c7439b3..4c22b4dbcbac26714888e4fed1adb6c82acf0fb3 100644 (file)
@@ -1,28 +1,16 @@
-/***************************************************************************
- *   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
 
@@ -35,7 +23,7 @@ KItemListViewLayouter::KItemListViewLayouter(KItemListSizeHintResolver* sizeHint
     m_itemSize(128, 128),
     m_itemMargin(),
     m_headerHeight(0),
-    m_model(0),
+    m_model(nullptr),
     m_sizeHintResolver(sizeHintResolver),
     m_scrollOffset(0),
     m_maximumScrollOffset(0),
@@ -358,170 +346,177 @@ void KItemListViewLayouter::markAsDirty()
 
 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()
@@ -621,4 +616,3 @@ qreal KItemListViewLayouter::minimumGroupHeaderWidth() const
     return 100;
 }
 
-#include "kitemlistviewlayouter.moc"