]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Expand or collapse all selected folders on Key Right/Left
authorMéven Car <meven.car@kdemail.net>
Fri, 26 May 2023 16:10:38 +0000 (16:10 +0000)
committerMéven Car <meven.car@kdemail.net>
Fri, 26 May 2023 16:10:38 +0000 (16:10 +0000)
CCBUG: 196772

src/kitemviews/kitemlistcontroller.cpp
src/kitemviews/kitemset.h
src/tests/CMakeLists.txt
src/tests/kitemlistcontrollerexpandtest.cpp [new file with mode: 0644]

index 2e7d2f057d5fa61b0efe8b9ae32921d2dc78824c..74a631d8d884b12bf51e59bfa359b8012e1757d5 100644 (file)
@@ -237,21 +237,34 @@ bool KItemListController::keyPressEvent(QKeyEvent *event)
 {
     int index = m_selectionManager->currentItem();
     int key = event->key();
+    const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
 
     // Handle the expanding/collapsing of items
-    if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
-        if (key == Qt::Key_Right) {
-            if (m_model->setExpanded(index, true)) {
-                return true;
+    // expand / collapse all selected directories
+    if (m_view->supportsItemExpanding() && m_model->isExpandable(index) && (key == Qt::Key_Right || key == Qt::Key_Left)) {
+        const bool expandOrCollapse = key == Qt::Key_Right ? true : false;
+        bool shouldReturn = m_model->setExpanded(index, expandOrCollapse);
+
+        // edit in reverse to preserve index of the first handled items
+        const auto selectedItems = m_selectionManager->selectedItems();
+        for (auto it = selectedItems.rbegin(); it != selectedItems.rend(); ++it) {
+            shouldReturn |= m_model->setExpanded(*it, expandOrCollapse);
+            if (!shiftPressed) {
+                m_selectionManager->setSelected(*it);
             }
-        } else if (key == Qt::Key_Left) {
-            if (m_model->setExpanded(index, false)) {
-                return true;
+        }
+        if (shouldReturn) {
+            // update keyboard anchors
+            if (shiftPressed) {
+                m_keyboardAnchorIndex = selectedItems.count() > 0 ? qMin(index, selectedItems.last()) : index;
+                m_keyboardAnchorPos = keyboardAnchorPos(m_keyboardAnchorIndex);
             }
+
+            event->ignore();
+            return true;
         }
     }
 
-    const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
     const bool controlPressed = event->modifiers() & Qt::ControlModifier;
     const bool shiftOrControlPressed = shiftPressed || controlPressed;
     const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Up
@@ -327,11 +340,17 @@ bool KItemListController::keyPressEvent(QKeyEvent *event)
 
     case Qt::Key_Up:
         updateKeyboardAnchor();
+        if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
+            m_selectionManager->beginAnchoredSelection(index);
+        }
         index = previousRowIndex(index);
         break;
 
     case Qt::Key_Down:
         updateKeyboardAnchor();
+        if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
+            m_selectionManager->beginAnchoredSelection(index);
+        }
         index = nextRowIndex(index);
         break;
 
index fd73c0e02dd7847df413f6936261ce7afab8b88e..b8ab6864df48f254d879df34250fd49bcfa595b9 100644 (file)
@@ -50,7 +50,7 @@ public:
 
     class iterator
     {
-        iterator(const KItemRangeList::iterator &rangeIt, int offset)
+        iterator(const KItemRangeList::iterator &rangeIt, int offset = 0)
             : m_rangeIt(rangeIt)
             , m_offset(offset)
         {
@@ -135,7 +135,7 @@ public:
 
     class const_iterator
     {
-        const_iterator(KItemRangeList::const_iterator rangeIt, int offset)
+        const_iterator(KItemRangeList::const_iterator rangeIt, int offset = 0)
             : m_rangeIt(rangeIt)
             , m_offset(offset)
         {
@@ -223,6 +223,70 @@ public:
         friend class KItemSet;
     };
 
+    class const_reverse_iterator
+    {
+    public:
+        const_reverse_iterator(KItemSet::const_iterator rangeIt)
+            : m_current(rangeIt)
+        {
+        }
+
+        const_reverse_iterator(const KItemSet::const_reverse_iterator &other)
+            : m_current(other.base())
+        {
+        }
+
+        int operator*() const
+        {
+            // analog to std::prev
+            auto t = const_iterator(m_current);
+            --t;
+            return *t;
+        }
+
+        inline bool operator==(const const_reverse_iterator &other) const
+        {
+            return m_current == other.m_current;
+        }
+
+        bool operator!=(const const_reverse_iterator &other) const
+        {
+            return !(*this == other);
+        }
+
+        const_reverse_iterator &operator++()
+        {
+            --m_current;
+            return *this;
+        }
+        const_reverse_iterator operator++(int)
+        {
+            auto tmp = *this;
+            ++(*this);
+            return tmp;
+        }
+
+        const_reverse_iterator &operator--()
+        {
+            ++m_current;
+            return *this;
+        }
+        const_reverse_iterator operator--(int)
+        {
+            auto tmp = *this;
+            --(*this);
+            return tmp;
+        }
+
+        KItemSet::const_iterator base() const
+        {
+            return m_current;
+        }
+
+    private:
+        KItemSet::const_iterator m_current;
+    };
+
     iterator begin();
     const_iterator begin() const;
     const_iterator constBegin() const;
@@ -230,6 +294,9 @@ public:
     const_iterator end() const;
     const_iterator constEnd() const;
 
+    const_reverse_iterator rend() const;
+    const_reverse_iterator rbegin() const;
+
     int first() const;
     int last() const;
 
@@ -366,32 +433,32 @@ inline bool KItemSet::remove(int i)
 
 inline KItemSet::iterator KItemSet::begin()
 {
-    return iterator(m_itemRanges.begin(), 0);
+    return iterator(m_itemRanges.begin());
 }
 
 inline KItemSet::const_iterator KItemSet::begin() const
 {
-    return const_iterator(m_itemRanges.begin(), 0);
+    return const_iterator(m_itemRanges.begin());
 }
 
 inline KItemSet::const_iterator KItemSet::constBegin() const
 {
-    return const_iterator(m_itemRanges.constBegin(), 0);
+    return const_iterator(m_itemRanges.constBegin());
 }
 
 inline KItemSet::iterator KItemSet::end()
 {
-    return iterator(m_itemRanges.end(), 0);
+    return iterator(m_itemRanges.end());
 }
 
 inline KItemSet::const_iterator KItemSet::end() const
 {
-    return const_iterator(m_itemRanges.end(), 0);
+    return const_iterator(m_itemRanges.end());
 }
 
 inline KItemSet::const_iterator KItemSet::constEnd() const
 {
-    return const_iterator(m_itemRanges.constEnd(), 0);
+    return const_iterator(m_itemRanges.constEnd());
 }
 
 inline int KItemSet::first() const
@@ -405,6 +472,16 @@ inline int KItemSet::last() const
     return lastRange.index + lastRange.count - 1;
 }
 
+inline KItemSet::const_reverse_iterator KItemSet::rend() const
+{
+    return KItemSet::const_reverse_iterator(constBegin());
+}
+
+inline KItemSet::const_reverse_iterator KItemSet::rbegin() const
+{
+    return KItemSet::const_reverse_iterator(constEnd());
+}
+
 inline KItemSet &KItemSet::operator<<(int i)
 {
     insert(i);
index d4a7457cb55d5c4fcb8492197b189a01f9b40715..8e0c6f11909a688d5df44b25590be5ccce4cf811 100644 (file)
@@ -20,6 +20,11 @@ ecm_add_test(kitemlistcontrollertest.cpp testdir.cpp
 TEST_NAME kitemlistcontrollertest
 LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
+# KItemListControllerExpandTest
+ecm_add_test(kitemlistcontrollerexpandtest.cpp testdir.cpp
+TEST_NAME kitemlistcontrollerexpandtest
+LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
+
 # KFileItemListViewTest
 ecm_add_test(kfileitemlistviewtest.cpp testdir.cpp
 TEST_NAME kfileitemlistviewtest
diff --git a/src/tests/kitemlistcontrollerexpandtest.cpp b/src/tests/kitemlistcontrollerexpandtest.cpp
new file mode 100644 (file)
index 0000000..20bc0cc
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Méven Car <meven@kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "kitemviews/kfileitemlistview.h"
+#include "kitemviews/kfileitemmodel.h"
+#include "kitemviews/kitemlistcontainer.h"
+#include "kitemviews/kitemlistcontroller.h"
+#include "kitemviews/kitemlistselectionmanager.h"
+#include "testdir.h"
+
+#include <QSignalSpy>
+#include <QStandardPaths>
+#include <QTest>
+
+class KItemListControllerExpandTest : public QObject
+{
+    Q_OBJECT
+
+private Q_SLOTS:
+    void initTestCase();
+    void cleanupTestCase();
+
+    void init();
+    void cleanup();
+
+    void testDirExpand();
+
+private:
+    KFileItemListView *m_view;
+    KItemListController *m_controller;
+    KItemListSelectionManager *m_selectionManager;
+    KFileItemModel *m_model;
+    TestDir *m_testDir;
+    KItemListContainer *m_container;
+    QSignalSpy *m_spyDirectoryLoadingCompleted;
+};
+
+void KItemListControllerExpandTest::initTestCase()
+{
+    QStandardPaths::setTestModeEnabled(true);
+    qRegisterMetaType<KItemSet>("KItemSet");
+
+    m_testDir = new TestDir();
+    m_model = new KFileItemModel();
+    m_view = new KFileItemListView();
+    m_controller = new KItemListController(m_model, m_view, this);
+    m_container = new KItemListContainer(m_controller);
+    m_controller = m_container->controller();
+    m_controller->setSelectionBehavior(KItemListController::MultiSelection);
+    m_selectionManager = m_controller->selectionManager();
+
+    QStringList files;
+    files << "dir1/file1";
+    files << "dir1/file2";
+    files << "dir1/file3";
+    files << "dir1/file4";
+    files << "dir1/file5";
+
+    files << "dir2/file1";
+    files << "dir2/file2";
+    files << "dir2/file3";
+    files << "dir2/file4";
+    files << "dir2/file5";
+
+    files << "dir3/file1";
+    files << "dir3/file2";
+    files << "dir3/file3";
+    files << "dir3/file4";
+    files << "dir3/file5";
+
+    m_testDir->createFiles(files);
+    m_model->loadDirectory(m_testDir->url());
+    m_spyDirectoryLoadingCompleted = new QSignalSpy(m_model, &KFileItemModel::directoryLoadingCompleted);
+    QVERIFY(m_spyDirectoryLoadingCompleted->wait());
+
+    m_container->show();
+    QVERIFY(QTest::qWaitForWindowExposed(m_container));
+}
+void KItemListControllerExpandTest::cleanupTestCase()
+{
+    delete m_container;
+    m_container = nullptr;
+
+    delete m_testDir;
+    m_testDir = nullptr;
+}
+
+void KItemListControllerExpandTest::init()
+{
+    m_selectionManager->setCurrentItem(0);
+    QCOMPARE(m_selectionManager->currentItem(), 0);
+
+    m_selectionManager->clearSelection();
+    QVERIFY(!m_selectionManager->hasSelection());
+}
+
+void KItemListControllerExpandTest::cleanup()
+{
+}
+
+void KItemListControllerExpandTest::testDirExpand()
+{
+    m_view->setItemLayout(KFileItemListView::DetailsLayout);
+    QCOMPARE(m_view->itemLayout(), KFileItemListView::DetailsLayout);
+    m_view->setSupportsItemExpanding(true);
+
+    // intial state
+    QCOMPARE(m_spyDirectoryLoadingCompleted->count(), 1);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 0);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 0);
+
+    // extend first folder
+    QTest::keyClick(m_container, Qt::Key_Right);
+    QVERIFY(m_spyDirectoryLoadingCompleted->wait());
+    QCOMPARE(m_model->count(), 8);
+    QCOMPARE(m_selectionManager->currentItem(), 0);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 0);
+
+    // collapse folder
+    QTest::keyClick(m_container, Qt::Key_Left);
+
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 0);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 0);
+
+    // make the first folder selected
+    QTest::keyClick(m_container, Qt::Key_Down);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 1);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 1);
+
+    QTest::keyClick(m_container, Qt::Key_Up);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 0);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 1);
+
+    // expand the two first folders
+    QTest::keyClick(m_container, Qt::Key_Down, Qt::ShiftModifier);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 1);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 2);
+
+    // precondition
+    QCOMPARE(m_spyDirectoryLoadingCompleted->count(), 2);
+
+    // expand selected folders
+    QTest::keyClick(m_container, Qt::Key_Right);
+    QVERIFY(QTest::qWaitFor(
+        [this]() {
+            return m_spyDirectoryLoadingCompleted->count() == 3;
+        },
+        100));
+    QCOMPARE(m_model->count(), 8);
+    QCOMPARE(m_selectionManager->currentItem(), 6);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 2);
+
+    // collapse the folders
+    QTest::keyClick(m_container, Qt::Key_Left);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 1);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 2);
+
+    // select third folder
+    QTest::keyClick(m_container, Qt::Key_Down, Qt::ShiftModifier);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 2);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 3);
+
+    // precondition
+    QCOMPARE(m_spyDirectoryLoadingCompleted->count(), 3);
+
+    // expand the three folders
+    QTest::keyClick(m_container, Qt::Key_Right);
+
+    QVERIFY(QTest::qWaitFor(
+        [this]() {
+            return m_spyDirectoryLoadingCompleted->count() == 6;
+        },
+        100));
+
+    QCOMPARE(m_model->count(), 18);
+    QCOMPARE(m_selectionManager->currentItem(), 12);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 3);
+
+    // collapse the folders
+    QTest::keyClick(m_container, Qt::Key_Left);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 2);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 3);
+
+    // shift select the directories
+    QTest::keyClick(m_container, Qt::Key_Up);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 1);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 1);
+
+    QTest::keyClick(m_container, Qt::Key_Up);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 0);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 1);
+
+    QTest::keyClick(m_container, Qt::Key_Down, Qt::ShiftModifier);
+    QTest::keyClick(m_container, Qt::Key_Down, Qt::ShiftModifier);
+
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 2);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 3);
+
+    // expand the three folders with shift modifier
+    QTest::keyClick(m_container, Qt::Key_Right, Qt::ShiftModifier);
+
+    QVERIFY(QTest::qWaitFor(
+        [this]() {
+            return m_spyDirectoryLoadingCompleted->count() == 9;
+        },
+        100));
+
+    QCOMPARE(m_model->count(), 18);
+    QCOMPARE(m_selectionManager->currentItem(), 12);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 13);
+
+    // collapse the folders
+    QTest::keyClick(m_container, Qt::Key_Left);
+    QCOMPARE(m_model->count(), 3);
+    QCOMPARE(m_selectionManager->currentItem(), 2);
+    QCOMPARE(m_selectionManager->selectedItems().count(), 3);
+}
+
+QTEST_MAIN(KItemListControllerExpandTest)
+
+#include "kitemlistcontrollerexpandtest.moc"