Expand or collapse all selected folders on Key Right/Left

CCBUG: 196772
This commit is contained in:
Méven Car 2023-05-26 16:10:38 +00:00
parent a1c5c5cf81
commit c17e6c8d2b
4 changed files with 352 additions and 16 deletions

View 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;

View 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);

View 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

View file

@ -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"