Commit 34aaaa96 authored by Glen Ditchfield's avatar Glen Ditchfield 🐛
Browse files

Find all items that must shift when a new item is placed

When an item is inserted in a cell, items it overlaps must shift over, but
those items might overlap others which must also shift even though they do
not overlap the initial item.  Bug 64603 gives an example.

BUG: 64603
FIXED-IN: 5.16.3
parent b6da43a8
Pipeline #50496 canceled with stage
......@@ -11,3 +11,5 @@ target_link_libraries(noteeditdialogtest
KF5::AkonadiNotes
KF5::CalendarSupport
)
ecm_add_test(placeitemtest LINK_LIBRARIES Qt5::Test KF5::CalendarSupport)
/*
* SPDX-FileCopyrightText: 2021 Glen Ditchfield <GJDitchfield@acm.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
*/
#include <QList>
#include <QTest>
#include "cellitem.h"
class PlaceItemTest : public QObject
{
Q_OBJECT
public:
private Q_SLOTS:
void soleItemHasNoOverlaps();
void itemDoesNotOverlapItself();
void twoItemsShareTheirCell();
void variousOverlapPositions_data();
void variousOverlapPositions();
void fillLeftGap();
void fillCenterGap();
void transitiveOverlap();
};
using namespace CalendarSupport;
// Skeletal item class, not unlike EventViews::AgendaItem. Instances represent
// a range of cell indexes from startAt up to but not including endBefore.
struct TestItem: public CellItem
{
const QString name;
const int startAt, endBefore;
TestItem(const char *n, int s, int e)
: name(QString::fromLatin1(n))
, startAt(s)
, endBefore(e)
{
}
TestItem(const char *n, int s, int e, int subCell, int subCells)
: name(QString::fromLatin1(n))
, startAt(s)
, endBefore(e)
{
setSubCell(subCell);
setSubCells(subCells);
}
bool overlaps(CellItem *o) const override
{
auto other = static_cast<TestItem *>(o);
return !(other->endBefore <= startAt || other->startAt >= endBefore);
}
QString label() const override
{
return name;
}
};
void PlaceItemTest::soleItemHasNoOverlaps()
{
auto item = std::make_unique<TestItem>("i", 1, 3);
const QList<CellItem *> cells;
auto overlappers = CellItem::placeItem(cells, item.get());
QCOMPARE(overlappers.size(), 0);
QCOMPARE(item->subCell(), 0);
QCOMPARE(item->subCells(), 1);
}
void PlaceItemTest::itemDoesNotOverlapItself()
{
auto item = std::make_unique<TestItem>("i", 1, 3);
const QList<CellItem *> cells({item.get()});
auto overlappers = CellItem::placeItem(cells, item.get());
QCOMPARE(overlappers.size(), 0);
QCOMPARE(item->subCell(), 0);
QCOMPARE(item->subCells(), 1);
}
void PlaceItemTest::twoItemsShareTheirCell()
{
auto oldItem = std::make_unique<TestItem>("i1", 1, 2, 0, 1);
const QList<CellItem *> cells({oldItem.get()});
auto newItem = std::make_unique<TestItem>("i2", 1, 2);
auto overlappers = CellItem::placeItem(cells, newItem.get());
QCOMPARE(overlappers.size(), 2);
QCOMPARE(oldItem->subCells(), 2);
QCOMPARE(newItem->subCells(), 2);
QVERIFY(oldItem->subCell() != newItem->subCell());
QVERIFY(oldItem->subCell() < oldItem->subCells());
QVERIFY(newItem->subCell() < newItem->subCells());
}
void PlaceItemTest::variousOverlapPositions_data()
{
QTest::addColumn<int>("startAt");
QTest::addColumn<bool>("shouldOverlap");
QTest::newRow("before top") << 0 << false;
QTest::newRow("overlaps top") << 1 << true;
QTest::newRow("at top") << 2 << true;
QTest::newRow("inside") << 3 << true;
QTest::newRow("at bottom") << 4 << true;
QTest::newRow("overlaps bottom") << 5 << true;
QTest::newRow("after bottom") << 6 << false;
}
// Evaluate placement of a new item at various positions relative to an existing
// item that covers many cells.
void PlaceItemTest::variousOverlapPositions()
{
QFETCH(int, startAt);
QFETCH(bool, shouldOverlap);
auto oldItem = std::make_unique<TestItem>("old", 2, 6, 0, 1);
const QList<CellItem *> cells({oldItem.get()});
auto newItem = std::make_unique<TestItem>("new", startAt, startAt + 2);
auto overlappers = CellItem::placeItem(cells, newItem.get());
QCOMPARE(overlappers.size(), shouldOverlap ? 2 : 0);
QVERIFY(!shouldOverlap || oldItem->subCell() != newItem->subCell());
QCOMPARE(oldItem->subCells(), shouldOverlap ? 2 : 1);
QCOMPARE(newItem->subCells(), shouldOverlap ? 2 : 1);
}
// |item1| |item1|
// |_____||item2| --> |_____||item2|
// |_____| | new ||_____|
// |_____|
void PlaceItemTest::fillLeftGap()
{
auto item1 = std::make_unique<TestItem>("item1", 0, 2, 0, 2);
auto item2 = std::make_unique<TestItem>("item2", 1, 3, 1, 2);
const QList<CellItem *> cells({item1.get(), item2.get()});
auto newItem = std::make_unique<TestItem>("new", 2, 4);
auto overlappers = CellItem::placeItem(cells, newItem.get());
QCOMPARE(newItem->subCell(), item1->subCell());
QCOMPARE(newItem->subCells(), 2);
}
// |item1| |item1|
// |item2||_____||item3| --> |item2||_____||item3|
// |_____| |_____| |_____|| new ||_____|
// |_____|
void PlaceItemTest::fillCenterGap()
{
auto item1 = std::make_unique<TestItem>("item1", 0, 2, 1, 3);
auto item2 = std::make_unique<TestItem>("item2", 1, 3, 0, 3);
auto item3 = std::make_unique<TestItem>("item3", 1, 3, 2, 3);
const QList<CellItem *> cells({item1.get(), item2.get(), item3.get()});
auto newItem= std::make_unique<TestItem>("new", 2, 4);
auto overlappers = CellItem::placeItem(cells, newItem.get());
QCOMPARE(newItem->subCell(), item1->subCell());
QCOMPARE(newItem->subCells(), 3);
}
// Items that do not overlap placeItem may also need adjustment.
// See https://bugs.kde.org/show_bug.cgi?id=64603
// |item1 | |item1|
// |________||item3 | --> |_____||item3|
// |item2 ||________| |item2||_____|| new |
// |________| |_____| |_____|
void PlaceItemTest::transitiveOverlap()
{
auto item1 = std::make_unique<TestItem>("item1", 0, 2, 0, 2);
auto item2 = std::make_unique<TestItem>("item2", 2, 4, 0, 2);
auto item3 = std::make_unique<TestItem>("item3", 1, 3, 1, 2);
const QList<CellItem *> cells({item1.get(), item2.get(), item3.get()});
auto newItem= std::make_unique<TestItem>("new", 2, 4);
auto overlappers = CellItem::placeItem(cells, newItem.get());
QCOMPARE(newItem->subCells(), 3);
QCOMPARE(item1->subCells(), 3);
QCOMPARE(item2->subCells(), 3);
QCOMPARE(item3->subCells(), 3);
}
QTEST_MAIN(PlaceItemTest)
#include "placeitemtest.moc"
......@@ -4,6 +4,8 @@
SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
*/
#include <QSet>
#include "cellitem.h"
#include "calendarsupport_debug.h"
......@@ -38,63 +40,51 @@ QString CellItem::label() const
QList<CellItem *> CellItem::placeItem(const QList<CellItem *> &cells, CellItem *placeItem)
{
QList<CellItem *> conflictItems;
int maxSubCells = 0;
QMultiHash<int, CellItem *> subCellDict;
// Find all items which are in same cell
QList<CellItem *>::ConstIterator it;
QList<CellItem *>::ConstIterator end(cells.constEnd());
for (it = cells.constBegin(); it != end; ++it) {
CellItem *item = *it;
if (item == placeItem) {
continue;
}
if (item->overlaps(placeItem)) {
qCDebug(CALENDARSUPPORT_LOG) << " Overlaps:" << item->label();
conflictItems.append(item);
if (item->subCells() > maxSubCells) {
maxSubCells = item->subCells();
QSet<int> subCellsInUse;
// Find all items that overlap placeItem, the items that overlaps them, and so on.
QList<CellItem *> overlappingItems {placeItem};
for (int i = 0; i < overlappingItems.count(); i++) {
const auto checkItem = overlappingItems.at(i);
for (const auto item : cells) {
if (item->overlaps(checkItem) && !overlappingItems.contains(item)) {
qCDebug(CALENDARSUPPORT_LOG) << item->label() << "overlaps" << checkItem->label();
overlappingItems.append(item);
if (item->subCell() >= maxSubCells) {
maxSubCells = item->subCells();
}
if (checkItem == placeItem) {
subCellsInUse.insert(item->subCell());
}
}
subCellDict.insert(item->subCell(), item);
}
}
if (!conflictItems.empty()) {
// Look for unused sub cell and insert item
if (overlappingItems.count() > 1) {
// Look for an unused subcell in placeItem's cells. If all are used,
// all overlapping items have to squeeze over.
int i;
for (i = 0; i < maxSubCells; ++i) {
qCDebug(CALENDARSUPPORT_LOG) << " Trying subcell" << i;
if (!subCellDict.contains(i)) {
qCDebug(CALENDARSUPPORT_LOG) << " Use subcell" << i;
placeItem->setSubCell(i);
if (!subCellsInUse.contains(i)) {
break;
}
}
placeItem->setSubCell(i);
if (i == maxSubCells) {
qCDebug(CALENDARSUPPORT_LOG) << " New subcell" << i;
placeItem->setSubCell(maxSubCells);
maxSubCells++; // add new item to number of sub cells
maxSubCells += 1;
for (auto item : overlappingItems) {
item->setSubCells(maxSubCells);
}
}
qCDebug(CALENDARSUPPORT_LOG) << " Sub cells:" << maxSubCells;
// Write results to item to be placed
conflictItems.append(placeItem);
placeItem->setSubCells(maxSubCells);
QList<CellItem *>::iterator it;
QList<CellItem *>::iterator end(conflictItems.end());
for (it = conflictItems.begin(); it != end; ++it) {
(*it)->setSubCells(maxSubCells);
}
// Todo: Adapt subCells of items conflicting with conflicting items
qCDebug(CALENDARSUPPORT_LOG) << "use subcell" << i << "of" << maxSubCells;
} else {
// Nothing overlapped placeItem, so:
overlappingItems.clear();
placeItem->setSubCell(0);
placeItem->setSubCells(1);
}
return conflictItems;
return overlappingItems;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment