QItemSelectionModel: use range-based for loops
Use range-based for loops and standard algorithms instead custom loops within QItemSelection - contains() - isSelected() - isRowSelected() - isColumnSelected() - rowIntersectsSelection() - columnIntersectsSelection() Adjust the testcases to cover some branches inside isRow/ColumnSelected. Rearrange some checks to check index.parent() as last because its not a cheap operation. Task-number: QTBUG-113137 Change-Id: I399f8a34b96abbb75309993c2acc447689ddfffa Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
parent
13b4075093
commit
bf1a2ba8ef
@ -256,7 +256,7 @@ static void rowLengthsFromRange(const QItemSelectionRange &range, QList<std::pai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isSelectableAndEnabled(Qt::ItemFlags flags)
|
static inline bool isSelectableAndEnabled(Qt::ItemFlags flags)
|
||||||
{
|
{
|
||||||
return flags.testFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
return flags.testFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||||
}
|
}
|
||||||
@ -416,12 +416,8 @@ void QItemSelection::select(const QModelIndex &topLeft, const QModelIndex &botto
|
|||||||
|
|
||||||
bool QItemSelection::contains(const QModelIndex &index) const
|
bool QItemSelection::contains(const QModelIndex &index) const
|
||||||
{
|
{
|
||||||
if (isSelectableAndEnabled(index.flags())) {
|
if (isSelectableAndEnabled(index.flags()))
|
||||||
QList<QItemSelectionRange>::const_iterator it = begin();
|
return std::any_of(begin(), end(), [&](const auto &range) { return range.contains(index); });
|
||||||
for (; it != end(); ++it)
|
|
||||||
if ((*it).contains(index))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1429,15 +1425,10 @@ bool QItemSelectionModel::isSelected(const QModelIndex &index) const
|
|||||||
if (d->model != index.model() || !index.isValid())
|
if (d->model != index.model() || !index.isValid())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool selected = false;
|
|
||||||
// search model ranges
|
// search model ranges
|
||||||
QList<QItemSelectionRange>::const_iterator it = d->ranges.begin();
|
const auto containsIndex = [&](const auto &range)
|
||||||
for (; it != d->ranges.end(); ++it) {
|
{ return range.isValid() && range.contains(index); };
|
||||||
if ((*it).isValid() && (*it).contains(index)) {
|
bool selected = std::any_of(d->ranges.begin(), d->ranges.end(), containsIndex);
|
||||||
selected = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check currentSelection
|
// check currentSelection
|
||||||
if (d->currentSelection.size()) {
|
if (d->currentSelection.size()) {
|
||||||
@ -1475,25 +1466,32 @@ bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) cons
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// return false if row exist in currentSelection (Deselect)
|
// return false if row exist in currentSelection (Deselect)
|
||||||
if (d->currentCommand & Deselect && d->currentSelection.size()) {
|
if (d->currentCommand & Deselect) {
|
||||||
for (int i=0; i<d->currentSelection.size(); ++i) {
|
const auto matchesRow = [&](const auto &selection)
|
||||||
if (d->currentSelection.at(i).parent() == parent &&
|
{
|
||||||
row >= d->currentSelection.at(i).top() &&
|
return row >= selection.top() &&
|
||||||
row <= d->currentSelection.at(i).bottom())
|
row <= selection.bottom() &&
|
||||||
|
parent == selection.parent();
|
||||||
|
};
|
||||||
|
if (std::any_of(d->currentSelection.cbegin(), d->currentSelection.cend(), matchesRow))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// return false if ranges in both currentSelection and ranges
|
// return false if ranges in both currentSelection and ranges
|
||||||
// intersect and have the same row contained
|
// intersect and have the same row contained
|
||||||
if (d->currentCommand & Toggle && d->currentSelection.size()) {
|
if (d->currentCommand & Toggle) {
|
||||||
for (int i=0; i<d->currentSelection.size(); ++i)
|
for (const auto &selection : d->currentSelection) {
|
||||||
if (d->currentSelection.at(i).top() <= row &&
|
if (row >= selection.top() && row <= selection.bottom()) {
|
||||||
d->currentSelection.at(i).bottom() >= row)
|
const auto selectionAndRangeIntersect = [&](const auto &range)
|
||||||
for (int j=0; j<d->ranges.size(); ++j)
|
{
|
||||||
if (d->ranges.at(j).top() <= row && d->ranges.at(j).bottom() >= row
|
return row >= range.top() &&
|
||||||
&& d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid())
|
row <= range.bottom() &&
|
||||||
|
selection.intersected(range).isValid();
|
||||||
|
};
|
||||||
|
if (std::any_of(d->ranges.cbegin(), d->ranges.cend(), selectionAndRangeIntersect))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto isSelectable = [&](int row, int column) {
|
auto isSelectable = [&](int row, int column) {
|
||||||
return isSelectableAndEnabled(d->model->index(row, column, parent).flags());
|
return isSelectableAndEnabled(d->model->index(row, column, parent).flags());
|
||||||
@ -1501,29 +1499,28 @@ bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) cons
|
|||||||
|
|
||||||
const int colCount = d->model->columnCount(parent);
|
const int colCount = d->model->columnCount(parent);
|
||||||
int unselectable = 0;
|
int unselectable = 0;
|
||||||
// add ranges and currentSelection and check through them all
|
// check ranges and currentSelection
|
||||||
QList<QItemSelectionRange>::const_iterator it;
|
|
||||||
QList<QItemSelectionRange> joined = d->ranges;
|
|
||||||
if (d->currentSelection.size())
|
|
||||||
joined += d->currentSelection;
|
|
||||||
for (int column = 0; column < colCount; ++column) {
|
for (int column = 0; column < colCount; ++column) {
|
||||||
if (!isSelectable(row, column)) {
|
if (!isSelectable(row, column)) {
|
||||||
++unselectable;
|
++unselectable;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const auto doCheck = [&](const auto &listToCheck)
|
||||||
for (it = joined.constBegin(); it != joined.constEnd(); ++it) {
|
{
|
||||||
if ((*it).contains(row, column, parent)) {
|
for (const auto &curSel : listToCheck) {
|
||||||
for (int i = column; i <= (*it).right(); ++i) {
|
if (curSel.contains(row, column, parent)) {
|
||||||
|
const auto right = curSel.right();
|
||||||
|
for (int i = column + 1; i <= right; ++i) {
|
||||||
if (!isSelectable(row, i))
|
if (!isSelectable(row, i))
|
||||||
++unselectable;
|
++unselectable;
|
||||||
}
|
}
|
||||||
|
column = qMax(column, right);
|
||||||
column = qMax(column, (*it).right());
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (it == joined.constEnd())
|
return false;
|
||||||
|
};
|
||||||
|
if (!doCheck(d->ranges) && !doCheck(d->currentSelection))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return unselectable < colCount;
|
return unselectable < colCount;
|
||||||
@ -1549,58 +1546,61 @@ bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// return false if column exist in currentSelection (Deselect)
|
// return false if column exist in currentSelection (Deselect)
|
||||||
if (d->currentCommand & Deselect && d->currentSelection.size()) {
|
if (d->currentCommand & Deselect) {
|
||||||
for (int i = 0; i < d->currentSelection.size(); ++i) {
|
const auto matchesColumn = [&](const auto &selection)
|
||||||
if (d->currentSelection.at(i).parent() == parent &&
|
{
|
||||||
column >= d->currentSelection.at(i).left() &&
|
return column >= selection.left() &&
|
||||||
column <= d->currentSelection.at(i).right())
|
column <= selection.right() &&
|
||||||
|
parent == selection.parent();
|
||||||
|
};
|
||||||
|
if (std::any_of(d->currentSelection.cbegin(), d->currentSelection.cend(), matchesColumn))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// return false if ranges in both currentSelection and the selection model
|
// return false if ranges in both currentSelection and the selection model
|
||||||
// intersect and have the same column contained
|
// intersect and have the same column contained
|
||||||
if (d->currentCommand & Toggle && d->currentSelection.size()) {
|
if (d->currentCommand & Toggle) {
|
||||||
for (int i = 0; i < d->currentSelection.size(); ++i) {
|
for (const auto &selection : d->currentSelection) {
|
||||||
if (d->currentSelection.at(i).left() <= column &&
|
if (column >= selection.left() && column <= selection.right()) {
|
||||||
d->currentSelection.at(i).right() >= column) {
|
const auto selectionAndRangeIntersect = [&](const auto &range)
|
||||||
for (int j = 0; j < d->ranges.size(); ++j) {
|
{
|
||||||
if (d->ranges.at(j).left() <= column && d->ranges.at(j).right() >= column
|
return column >= range.left() &&
|
||||||
&& d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) {
|
column <= range.right() &&
|
||||||
|
selection.intersected(range).isValid();
|
||||||
|
};
|
||||||
|
if (std::any_of(d->ranges.cbegin(), d->ranges.cend(), selectionAndRangeIntersect))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto isSelectable = [&](int row, int column) {
|
auto isSelectable = [&](int row, int column) {
|
||||||
return isSelectableAndEnabled(d->model->index(row, column, parent).flags());
|
return isSelectableAndEnabled(d->model->index(row, column, parent).flags());
|
||||||
};
|
};
|
||||||
|
|
||||||
const int rowCount = d->model->rowCount(parent);
|
const int rowCount = d->model->rowCount(parent);
|
||||||
int unselectable = 0;
|
int unselectable = 0;
|
||||||
|
// check ranges and currentSelection
|
||||||
// add ranges and currentSelection and check through them all
|
|
||||||
QList<QItemSelectionRange>::const_iterator it;
|
|
||||||
QList<QItemSelectionRange> joined = d->ranges;
|
|
||||||
if (d->currentSelection.size())
|
|
||||||
joined += d->currentSelection;
|
|
||||||
for (int row = 0; row < rowCount; ++row) {
|
for (int row = 0; row < rowCount; ++row) {
|
||||||
if (!isSelectable(row, column)) {
|
if (!isSelectable(row, column)) {
|
||||||
++unselectable;
|
++unselectable;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (it = joined.constBegin(); it != joined.constEnd(); ++it) {
|
const auto doCheck = [&](const auto &listToCheck)
|
||||||
if ((*it).contains(row, column, parent)) {
|
{
|
||||||
for (int i = row; i <= (*it).bottom(); ++i) {
|
for (const auto &curSel : listToCheck) {
|
||||||
if (!isSelectable(i, column)) {
|
if (curSel.contains(row, column, parent)) {
|
||||||
|
const auto bottom = curSel.bottom();
|
||||||
|
for (int i = row + 1; i <= bottom; ++i) {
|
||||||
|
if (!isSelectable(i, column))
|
||||||
++unselectable;
|
++unselectable;
|
||||||
}
|
}
|
||||||
}
|
row = qMax(row, bottom);
|
||||||
row = qMax(row, (*it).bottom());
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (it == joined.constEnd())
|
return false;
|
||||||
|
};
|
||||||
|
if (!doCheck(d->ranges) && !doCheck(d->currentSelection))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return unselectable < rowCount;
|
return unselectable < rowCount;
|
||||||
@ -1623,14 +1623,15 @@ bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &par
|
|||||||
|
|
||||||
QItemSelection sel = d->ranges;
|
QItemSelection sel = d->ranges;
|
||||||
sel.merge(d->currentSelection, d->currentCommand);
|
sel.merge(d->currentSelection, d->currentCommand);
|
||||||
for (const QItemSelectionRange &range : std::as_const(sel)) {
|
if (sel.isEmpty() || sel.constFirst().parent() != parent)
|
||||||
if (range.parent() != parent)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
for (const QItemSelectionRange &range : std::as_const(sel)) {
|
||||||
int top = range.top();
|
int top = range.top();
|
||||||
int bottom = range.bottom();
|
int bottom = range.bottom();
|
||||||
|
if (top <= row && bottom >= row) {
|
||||||
int left = range.left();
|
int left = range.left();
|
||||||
int right = range.right();
|
int right = range.right();
|
||||||
if (top <= row && bottom >= row) {
|
|
||||||
for (int j = left; j <= right; j++) {
|
for (int j = left; j <= right; j++) {
|
||||||
if (isSelectableAndEnabled(d->model->index(row, j, parent).flags()))
|
if (isSelectableAndEnabled(d->model->index(row, j, parent).flags()))
|
||||||
return true;
|
return true;
|
||||||
@ -1658,14 +1659,15 @@ bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelInde
|
|||||||
|
|
||||||
QItemSelection sel = d->ranges;
|
QItemSelection sel = d->ranges;
|
||||||
sel.merge(d->currentSelection, d->currentCommand);
|
sel.merge(d->currentSelection, d->currentCommand);
|
||||||
for (const QItemSelectionRange &range : std::as_const(sel)) {
|
if (sel.isEmpty() || sel.constFirst().parent() != parent)
|
||||||
if (range.parent() != parent)
|
|
||||||
return false;
|
return false;
|
||||||
int top = range.top();
|
|
||||||
int bottom = range.bottom();
|
for (const QItemSelectionRange &range : std::as_const(sel)) {
|
||||||
int left = range.left();
|
int left = range.left();
|
||||||
int right = range.right();
|
int right = range.right();
|
||||||
if (left <= column && right >= column) {
|
if (left <= column && right >= column) {
|
||||||
|
int top = range.top();
|
||||||
|
int bottom = range.bottom();
|
||||||
for (int j = top; j <= bottom; j++) {
|
for (int j = top; j <= bottom; j++) {
|
||||||
if (isSelectableAndEnabled(d->model->index(j, column, parent).flags()))
|
if (isSelectableAndEnabled(d->model->index(j, column, parent).flags()))
|
||||||
return true;
|
return true;
|
||||||
@ -1683,7 +1685,7 @@ bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelInde
|
|||||||
In contrast to selection.isEmpty(), this takes into account
|
In contrast to selection.isEmpty(), this takes into account
|
||||||
whether items are enabled and whether they are selectable.
|
whether items are enabled and whether they are selectable.
|
||||||
*/
|
*/
|
||||||
static bool selectionIsEmpty(const QItemSelection &selection)
|
static inline bool selectionIsEmpty(const QItemSelection &selection)
|
||||||
{
|
{
|
||||||
return std::all_of(selection.begin(), selection.end(),
|
return std::all_of(selection.begin(), selection.end(),
|
||||||
[](const QItemSelectionRange &r) { return r.isEmpty(); });
|
[](const QItemSelectionRange &r) { return r.isEmpty(); });
|
||||||
|
@ -42,16 +42,14 @@ public:
|
|||||||
|
|
||||||
inline bool contains(const QModelIndex &index) const
|
inline bool contains(const QModelIndex &index) const
|
||||||
{
|
{
|
||||||
return (parent() == index.parent()
|
return contains(index.row(), index.column(), index.parent());
|
||||||
&& tl.row() <= index.row() && tl.column() <= index.column()
|
|
||||||
&& br.row() >= index.row() && br.column() >= index.column());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool contains(int row, int column, const QModelIndex &parentIndex) const
|
inline bool contains(int row, int column, const QModelIndex &parentIndex) const
|
||||||
{
|
{
|
||||||
return (parent() == parentIndex
|
return (tl.row() <= row && tl.column() <= column &&
|
||||||
&& tl.row() <= row && tl.column() <= column
|
br.row() >= row && br.column() >= column &&
|
||||||
&& br.row() >= row && br.column() >= column);
|
parent() == parentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool intersects(const QItemSelectionRange &other) const;
|
bool intersects(const QItemSelectionRange &other) const;
|
||||||
|
@ -56,6 +56,7 @@ private slots:
|
|||||||
void merge_data();
|
void merge_data();
|
||||||
void merge();
|
void merge();
|
||||||
void isRowSelected();
|
void isRowSelected();
|
||||||
|
void isColumnSelected();
|
||||||
void childrenDeselectionSignal();
|
void childrenDeselectionSignal();
|
||||||
#if QT_CONFIG(proxymodel)
|
#if QT_CONFIG(proxymodel)
|
||||||
void layoutChangedWithAllSelected1();
|
void layoutChangedWithAllSelected1();
|
||||||
@ -1970,6 +1971,11 @@ void tst_QItemSelectionModel::rowIntersectsSelection1()
|
|||||||
selectionModel.select(index, QItemSelectionModel::Toggle);
|
selectionModel.select(index, QItemSelectionModel::Toggle);
|
||||||
QVERIFY(!selectionModel.rowIntersectsSelection(0, QModelIndex()));
|
QVERIFY(!selectionModel.rowIntersectsSelection(0, QModelIndex()));
|
||||||
QVERIFY(!selectionModel.columnIntersectsSelection(0, QModelIndex()));
|
QVERIFY(!selectionModel.columnIntersectsSelection(0, QModelIndex()));
|
||||||
|
|
||||||
|
QStandardItemModel model2;
|
||||||
|
model2.setItem(0, 0, new QStandardItem("foo"));
|
||||||
|
QVERIFY(!selectionModel.rowIntersectsSelection(0, model2.index(0, 0, QModelIndex())));
|
||||||
|
QVERIFY(!selectionModel.columnIntersectsSelection(0, model2.index(0, 0, QModelIndex())));
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QItemSelectionModel::rowIntersectsSelection2()
|
void tst_QItemSelectionModel::rowIntersectsSelection2()
|
||||||
@ -2206,7 +2212,47 @@ void tst_QItemSelectionModel::isRowSelected()
|
|||||||
QItemSelectionModel sel(&model);
|
QItemSelectionModel sel(&model);
|
||||||
sel.select(QItemSelection(model.index(0, 0), model.index(0, 1)), QItemSelectionModel::Select);
|
sel.select(QItemSelection(model.index(0, 0), model.index(0, 1)), QItemSelectionModel::Select);
|
||||||
QCOMPARE(sel.selectedIndexes().size(), 1);
|
QCOMPARE(sel.selectedIndexes().size(), 1);
|
||||||
QVERIFY(sel.isRowSelected(0, QModelIndex()));
|
QVERIFY(sel.isRowSelected(0));
|
||||||
|
QVERIFY(!sel.isRowSelected(1));
|
||||||
|
|
||||||
|
// check Toggle branch in isRowSelected()
|
||||||
|
sel.select(QItemSelection(model.index(0, 0), model.index(0, 1)), QItemSelectionModel::Toggle);
|
||||||
|
QVERIFY(!sel.isRowSelected(0));
|
||||||
|
QVERIFY(!sel.isRowSelected(1));
|
||||||
|
|
||||||
|
sel.select(QItemSelection(model.index(0, 0), model.index(0, 1)), QItemSelectionModel::Toggle);
|
||||||
|
QVERIFY(sel.isRowSelected(0));
|
||||||
|
QVERIFY(!sel.isRowSelected(1));
|
||||||
|
|
||||||
|
// check Deselect branch in isRowSelected()
|
||||||
|
sel.select(QItemSelection(model.index(0, 0), model.index(0, 1)), QItemSelectionModel::Deselect);
|
||||||
|
QVERIFY(!sel.isRowSelected(0));
|
||||||
|
QVERIFY(!sel.isRowSelected(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QItemSelectionModel::isColumnSelected()
|
||||||
|
{
|
||||||
|
QStandardItemModel model(2, 2);
|
||||||
|
model.setData(model.index(0, 0), 0, Qt::UserRole - 1);
|
||||||
|
QItemSelectionModel sel(&model);
|
||||||
|
sel.select(QItemSelection(model.index(0, 0), model.index(1, 0)), QItemSelectionModel::Select);
|
||||||
|
QCOMPARE(sel.selectedIndexes().size(), 1);
|
||||||
|
QVERIFY(sel.isColumnSelected(0));
|
||||||
|
QVERIFY(!sel.isColumnSelected(1));
|
||||||
|
|
||||||
|
// check Toggle branch in isColumnSelected()
|
||||||
|
sel.select(QItemSelection(model.index(0, 0), model.index(1, 0)), QItemSelectionModel::Toggle);
|
||||||
|
QVERIFY(!sel.isColumnSelected(0));
|
||||||
|
QVERIFY(!sel.isColumnSelected(1));
|
||||||
|
|
||||||
|
sel.select(QItemSelection(model.index(0, 0), model.index(1, 0)), QItemSelectionModel::Toggle);
|
||||||
|
QVERIFY(sel.isColumnSelected(0));
|
||||||
|
QVERIFY(!sel.isColumnSelected(1));
|
||||||
|
|
||||||
|
// check Deselect branch in isColumnSelected()
|
||||||
|
sel.select(QItemSelection(model.index(0, 0), model.index(1, 0)), QItemSelectionModel::Deselect);
|
||||||
|
QVERIFY(!sel.isColumnSelected(0));
|
||||||
|
QVERIFY(!sel.isColumnSelected(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QItemSelectionModel::childrenDeselectionSignal()
|
void tst_QItemSelectionModel::childrenDeselectionSignal()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user