diff options
Diffstat (limited to 'src/quick/util/qquickchangeset.cpp')
-rw-r--r-- | src/quick/util/qquickchangeset.cpp | 565 |
1 files changed, 356 insertions, 209 deletions
diff --git a/src/quick/util/qquickchangeset.cpp b/src/quick/util/qquickchangeset.cpp index c335408c02..23fa432367 100644 --- a/src/quick/util/qquickchangeset.cpp +++ b/src/quick/util/qquickchangeset.cpp @@ -43,120 +43,159 @@ QT_BEGIN_NAMESPACE + +/*! + \class QQuickChangeSet + \brief The QQuickChangeSet class stores an ordered list of notifications about + changes to a linear data set. + \internal + + QQuickChangeSet can be used to record a series of notications about items in an indexed list + being inserted, removed, moved, and changed. Notifications in the set are re-ordered so that + all notifications of a single type are grouped together and sorted in order of ascending index, + with remove notifications preceding all others, followed by insert notification, and then + change notifications. + + Moves in a change set are represented by a remove notification paired with an insert + notification by way of a shared unique moveId. Re-ordering may result in one or both of the + paired notifications being divided, when this happens the offset member of the notification + will indicate the relative offset of the divided notification from the beginning of the + original. +*/ + +/*! + Constructs an empty change set. +*/ + QQuickChangeSet::QQuickChangeSet() - : m_moveCounter(0) - , m_difference(0) + : m_difference(0) { } +/*! + Constructs a copy of a \a changeSet. +*/ + QQuickChangeSet::QQuickChangeSet(const QQuickChangeSet &changeSet) : m_removes(changeSet.m_removes) , m_inserts(changeSet.m_inserts) , m_changes(changeSet.m_changes) - , m_moveCounter(changeSet.m_moveCounter) - , m_difference(0) + , m_difference(changeSet.m_difference) { } +/*! + Destroys a change set. +*/ + QQuickChangeSet::~QQuickChangeSet() { } +/*! + Assigns the value of a \a changeSet to another. +*/ + QQuickChangeSet &QQuickChangeSet::operator =(const QQuickChangeSet &changeSet) { m_removes = changeSet.m_removes; m_inserts = changeSet.m_inserts; m_changes = changeSet.m_changes; - m_moveCounter = changeSet.m_moveCounter; m_difference = changeSet.m_difference; return *this; } +/*! + Appends a notification that \a count items were inserted at \a index. +*/ + void QQuickChangeSet::insert(int index, int count) { - applyInsertions(QVector<Insert>() << Insert(index, count)); + insert(QVector<Insert>() << Insert(index, count)); } +/*! + Appends a notification that \a count items were removed at \a index. +*/ + void QQuickChangeSet::remove(int index, int count) { - QVector<Insert> i; - applyRemovals(QVector<Remove>() << Remove(index, count), i); + QVector<Remove> removes; + removes.append(Remove(index, count)); + remove(&removes, 0); } -void QQuickChangeSet::move(int from, int to, int count) +/*! + Appends a notification that \a count items were moved \a from one index \a to another. + + The \a moveId must be unique across the lifetime of the change set and any related + change sets. +*/ + +void QQuickChangeSet::move(int from, int to, int count, int moveId) { - apply(QVector<Remove>() << Remove(from, count, -2), QVector<Insert>() << Insert(to, count, -2)); + QVector<Remove> removes; + removes.append(Remove(from, count, moveId)); + QVector<Insert> inserts; + inserts.append(Insert(to, count, moveId)); + remove(&removes, &inserts); + insert(inserts); } +/*! + Appends a notification that \a count items were changed at \a index. +*/ + void QQuickChangeSet::change(int index, int count) { - applyChanges(QVector<Change>() << Change(index, count)); + QVector<Change> changes; + changes.append(Change(index, count)); + change(changes); } -void QQuickChangeSet::apply(const QQuickChangeSet &changeSet) -{ - apply(changeSet.m_removes, changeSet.m_inserts, changeSet.m_changes); -} +/*! + Applies the changes in a \a changeSet to another. +*/ -void QQuickChangeSet::apply(const QVector<Remove> &removals) +void QQuickChangeSet::apply(const QQuickChangeSet &changeSet) { - QVector<Remove> r = removals; - QVector<Insert> i; - applyRemovals(r, i); + QVector<Remove> r = changeSet.m_removes; + QVector<Insert> i = changeSet.m_inserts; + QVector<Change> c = changeSet.m_changes; + remove(&r, &i); + insert(i); + change(c); } -void QQuickChangeSet::apply(const QVector<Insert> &insertions) -{ - QVector<Insert> i = insertions; - applyInsertions(i); -} +/*! + Applies a list of \a removes to a change set. -void QQuickChangeSet::apply(const QVector<Change> &changes) -{ - QVector<Change> c = changes; - applyChanges(c); -} + If a remove contains a moveId then any intersecting insert in the set will replace the + corresponding intersection in the optional \a inserts list. +*/ -void QQuickChangeSet::apply(const QVector<Remove> &removals, const QVector<Insert> &insertions, const QVector<Change> &changes) +void QQuickChangeSet::remove(const QVector<Remove> &removes, QVector<Insert> *inserts) { - QVector<Remove> r = removals; - QVector<Insert> i = insertions; - QVector<Change> c = changes; - applyRemovals(r, i); - applyInsertions(i); - applyChanges(c); + QVector<Remove> r = removes; + remove(&r, inserts); } -void QQuickChangeSet::applyRemovals(QVector<Remove> &removals, QVector<Insert> &insertions) +void QQuickChangeSet::remove(QVector<Remove> *removes, QVector<Insert> *inserts) { int removeCount = 0; int insertCount = 0; QVector<Insert>::iterator insert = m_inserts.begin(); QVector<Change>::iterator change = m_changes.begin(); - QVector<Remove>::iterator rit = removals.begin(); - for (; rit != removals.end(); ++rit) { + QVector<Remove>::iterator rit = removes->begin(); + for (; rit != removes->end(); ++rit) { int index = rit->index + removeCount; int count = rit->count; - QVector<Insert>::iterator iit = insertions.begin(); - for (; rit->moveId != -1 && iit != insertions.end() && iit->moveId != rit->moveId; ++iit) {} - - for (QVector<Remove>::iterator nrit = rit + 1; nrit != removals.end(); nrit = rit + 1) { - if (nrit->index != rit->index || (rit->moveId == -1) != (nrit->moveId == -1)) - break; - if (nrit->moveId != -1) { - QVector<Insert>::iterator niit = iit + 1; - if (niit->moveId != nrit->moveId || niit->index != iit->index + iit->count) - break; - niit->index = iit->index; - niit->count += iit->count; - iit = insertions.erase(iit); - } - nrit->count += rit->count; - rit = removals.erase(rit); - } - - for (; change != m_changes.end() && change->end() < rit->index; ++change) {} + // Decrement the accumulated remove count from the indexes of any changes prior to the + // current remove. + for (; change != m_changes.end() && change->end() < rit->index; ++change) + change->index -= removeCount; + // Remove any portion of a change notification that intersects the current remove. for (; change != m_changes.end() && change->index > rit->end(); ++change) { change->count -= qMin(change->end(), rit->end()) - qMax(change->index, rit->index); if (change->count == 0) { @@ -165,164 +204,185 @@ void QQuickChangeSet::applyRemovals(QVector<Remove> &removals, QVector<Insert> & change->index = rit->index; } } + + // Decrement the accumulated remove count from the indexes of any inserts prior to the + // current remove. for (; insert != m_inserts.end() && insert->end() <= index; ++insert) { insertCount += insert->count; insert->index -= removeCount; } - for (; insert != m_inserts.end() && insert->index < index + count; ++insert) { - const int offset = insert->index - index; - const int difference = qMin(insert->end(), index + count) - qMax(insert->index, index); - const int moveId = rit->moveId != -1 ? m_moveCounter++ : -1; - if (insert->moveId != -1) { - QVector<Remove>::iterator remove = m_removes.begin(); - for (; remove != m_removes.end() && remove->moveId != insert->moveId; ++remove) {} - Q_ASSERT(remove != m_removes.end()); - const int offset = index - insert->index; - if (rit->moveId != -1 && offset < 0) { - const int moveId = m_moveCounter++; - iit = insertions.insert(iit, Insert(iit->index, -offset, moveId)); - ++iit; - iit->index += -offset; - iit->count -= -offset; - rit = removals.insert(rit, Remove(rit->index, -offset, moveId)); - ++rit; - rit->count -= -offset; - } - if (offset > 0) { - const int moveId = m_moveCounter++; - insert = m_inserts.insert(insert, Insert(insert->index, offset, moveId)); - ++insert; - insert->index += offset; - insert->count -= offset; - remove = m_removes.insert(remove, Remove(remove->index, offset, moveId)); - ++remove; - remove->count -= offset; - rit->index -= offset; - index += offset; - count -= offset; - } + rit->index -= insertCount; - if (remove->count == difference) { - remove->moveId = moveId; - } else { - remove = m_removes.insert(remove, Remove(remove->index, difference, moveId)); - ++remove; - remove->count -= difference; - } - } else if (rit->moveId != -1 && offset > 0) { - const int moveId = m_moveCounter++; - iit = insertions.insert(iit, Insert(iit->index, offset, moveId)); - ++iit; - iit->index += offset; - iit->count -= offset; - rit = removals.insert(rit, Remove(rit->index, offset, moveId)); + // Remove any portion of a insert notification that intersects the current remove. + while (insert != m_inserts.end() && insert->index < index + count) { + int offset = index - insert->index; + const int difference = qMin(insert->end(), index + count) - qMax(insert->index, index); + + // If part of the remove or insert that precedes the intersection has a moveId create + // a new delta for that portion and subtract the size of that delta from the current + // one. + if (offset < 0 && rit->moveId != -1) { + rit = removes->insert(rit, Remove( + rit->index, -offset, rit->moveId, rit->offset)); ++rit; - rit->count -= offset; - index += offset; - count -= offset; + rit->count -= -offset; + rit->offset += -offset; + index += -offset; + count -= -offset; + removeCount += -offset; + offset = 0; + } else if (offset > 0 && insert->moveId != -1) { + insert = m_inserts.insert(insert, Insert( + insert->index - removeCount, offset, insert->moveId, insert->offset)); + ++insert; + insert->index += offset; + insert->count -= offset; + insert->offset += offset; + rit->index -= offset; + insertCount += offset; } - if (rit->moveId != -1 && difference > 0) { - iit = insertions.insert(iit, Insert( - iit->index, difference, insert->moveId != -1 ? moveId : -1)); - ++iit; - iit->index += difference; - iit->count -= difference; + // If the current remove has a move id, find any inserts with the same move id and + // replace the corresponding sections with the insert removed from the change set. + if (rit->moveId != -1 && difference > 0 && inserts) { + for (QVector<Insert>::iterator iit = inserts->begin(); iit != inserts->end(); ++iit) { + if (iit->moveId != rit->moveId + || rit->offset > iit->offset + iit->count + || iit->offset > rit->offset + difference) { + continue; + } + // If the intersecting insert starts before the replacement one create + // a new insert for the portion prior to the replacement insert. + const int overlapOffset = rit->offset - iit->offset; + if (overlapOffset > 0) { + iit = inserts->insert(iit, Insert( + iit->index, overlapOffset, iit->moveId, iit->offset)); + ++iit; + iit->index += overlapOffset; + iit->count -= overlapOffset; + iit->offset += overlapOffset; + } + if (iit->offset >= rit->offset + && iit->offset + iit->count <= rit->offset + difference) { + // If the replacement insert completely encapsulates the existing + // one just change the moveId. + iit->moveId = insert->moveId; + iit->offset = insert->offset + qMax(0, -overlapOffset); + } else { + // Create a new insertion before the intersecting one with the number of intersecting + // items and remove that number from that insert. + const int count + = qMin(iit->offset + iit->count, rit->offset + difference) + - qMax(iit->offset, rit->offset); + iit = inserts->insert(iit, Insert( + iit->index, + count, + insert->moveId, + insert->offset + qMax(0, -overlapOffset))); + ++iit; + iit->index += count; + iit->count -= count; + iit->offset += count; + } + } } + // Subtract the number of intersecting items from the current remove and insert. insert->count -= difference; + insert->offset += difference; rit->count -= difference; + rit->offset += difference; + + index += difference; + count -= difference; + removeCount += difference; + if (insert->count == 0) { insert = m_inserts.erase(insert); - --insert; - } else if (index <= insert->index) { - insert->index = rit->index; + } else if (rit->count == -offset || rit->count == 0) { + insert->index += difference; + break; + } else if (offset <= 0) { + insert->index = index - removeCount; + insertCount += insert->count; + ++insert; } else { + insert->index -= removeCount - difference; rit->index -= insert->count; + insertCount += insert->count; + ++insert; } - index += difference; - count -= difference; - removeCount += difference; } - rit->index -= insertCount; removeCount += rit->count; - - if (rit->count == 0) { - if (rit->moveId != -1 && iit->count == 0) - insertions.erase(iit); - rit = removals.erase(rit); - --rit; - } else if (rit->moveId != -1) { - const int moveId = m_moveCounter++; - rit->moveId = moveId; - iit->moveId = moveId; - } } - for (; change != m_changes.end(); ++change) - change->index -= removeCount; for (; insert != m_inserts.end(); ++insert) insert->index -= removeCount; removeCount = 0; QVector<Remove>::iterator remove = m_removes.begin(); - for (rit = removals.begin(); rit != removals.end(); ++rit) { - QVector<Insert>::iterator iit = insertions.begin(); + for (rit = removes->begin(); rit != removes->end(); ++rit) { + if (rit->count == 0) + continue; + // Accumulate consecutive removes into a single delta before attempting to apply. + for (QVector<Remove>::iterator next = rit + 1; next != removes->end() + && next->index == rit->index + && next->moveId == -1 + && rit->moveId == -1; ++next) { + next->count += rit->count; + rit = next; + } int index = rit->index + removeCount; - for (; rit->moveId != -1 && iit != insertions.end() && iit->moveId != rit->moveId; ++iit) {} + // Decrement the accumulated remove count from the indexes of any inserts prior to the + // current remove. for (; remove != m_removes.end() && index > remove->index; ++remove) remove->index -= removeCount; - while (remove != m_removes.end() && index + rit->count > remove->index) { + while (remove != m_removes.end() && index + rit->count >= remove->index) { int count = 0; - const int offset = remove->index - index - removeCount; + const int offset = remove->index - index; QVector<Remove>::iterator rend = remove; for (; rend != m_removes.end() && rit->moveId == -1 && rend->moveId == -1 - && rit->index + rit->count >= rend->index; ++rend) { + && index + rit->count >= rend->index; ++rend) { count += rend->count; } if (remove != rend) { - const int difference = rend == m_removes.end() || rit->index + rit->count < rend->index - removeCount - ? rit->count - : offset; + // Accumulate all existing non-move removes that are encapsulated by or immediately + // follow the current remove into it. + int difference = 0; + if (rend == m_removes.end()) + difference = rit->count; + else if (rit->index + rit->count < rend->index - removeCount) + difference = rit->count; + else if (rend->moveId != -1) { + difference = rend->index - removeCount - rit->index; + index += difference; + } else if (offset > 0) + difference = offset; count += difference; - index += difference; rit->count -= difference; removeCount += difference; remove->index = rit->index; remove->count = count; remove = m_removes.erase(++remove, rend); - } else if (rit->moveId != -1) { - if (offset > 0) { - const int moveId = m_moveCounter++; - iit = insertions.insert(iit, Insert(iit->index, offset, moveId)); - ++iit; - iit->index += offset; - iit->count -= offset; - remove = m_removes.insert(remove, Remove(rit->index, offset, moveId)); - ++remove; - rit->count -= offset; - removeCount += offset; - } - remove->index = rit->index; - index += offset; - - ++remove; } else { + // Insert a remove for the portion of the unmergable current remove prior to the + // point of intersection. if (offset > 0) { - remove = m_removes.insert(remove, Remove(rit->index, offset)); + remove = m_removes.insert(remove, Remove( + rit->index, offset, rit->moveId, rit->offset)); ++remove; rit->count -= offset; + rit->offset += offset; removeCount += offset; + index += offset; } remove->index = rit->index; - index += offset; ++remove; } - index += count; } if (rit->count > 0) { @@ -336,78 +396,125 @@ void QQuickChangeSet::applyRemovals(QVector<Remove> &removals, QVector<Insert> & m_difference -= removeCount; } -void QQuickChangeSet::applyInsertions(QVector<Insert> &insertions) +/*! + Applies a list of \a inserts to a change set. +*/ + +void QQuickChangeSet::insert(const QVector<Insert> &inserts) { int insertCount = 0; QVector<Insert>::iterator insert = m_inserts.begin(); QVector<Change>::iterator change = m_changes.begin(); - for (QVector<Insert>::iterator iit = insertions.begin(); iit != insertions.end(); ++iit) { + for (QVector<Insert>::const_iterator iit = inserts.begin(); iit != inserts.end(); ++iit) { + if (iit->count == 0) + continue; int index = iit->index - insertCount; - int count = iit->count; + + Insert current = *iit; + // Accumulate consecutive inserts into a single delta before attempting to insert. + for (QVector<Insert>::const_iterator next = iit + 1; next != inserts.end() + && next->index == iit->index + iit->count + && next->moveId == -1 + && iit->moveId == -1; ++next) { + current.count += next->count; + iit = next; + } + + // Increment the index of any changes before the current insert by the accumlated insert + // count. for (; change != m_changes.end() && change->index >= index; ++change) change->index += insertCount; - if (change != m_changes.end() && change->index < index + count) { + // If the current insert index is in the middle of a change split it in two at that + // point and increment the index of the latter half. + if (change != m_changes.end() && change->index < index + iit->count) { int offset = index - change->index; change = m_changes.insert(change, Change(change->index + insertCount, offset)); ++change; - change->index += count + offset; + change->index += iit->count + offset; change->count -= offset; } - for (; insert != m_inserts.end() && iit->index > insert->index + insert->count; ++insert) + + // Increment the index of any inserts before the current insert by the accumlated insert + // count. + for (; insert != m_inserts.end() && index > insert->index + insert->count; ++insert) insert->index += insertCount; if (insert == m_inserts.end()) { - insert = m_inserts.insert(insert, *iit); + insert = m_inserts.insert(insert, current); ++insert; - insertCount += iit->count; } else { const int offset = index - insert->index; - if (offset < 0 || (offset == 0 && (iit->moveId != -1 || insert->moveId != -1))) { - insert = m_inserts.insert(insert, *iit); + + if (offset < 0) { + // If the current insert is before an existing insert and not adjacent just insert + // it into the list. + insert = m_inserts.insert(insert, current); ++insert; } else if (iit->moveId == -1 && insert->moveId == -1) { - insert->index -= iit->count; - insert->count += iit->count; + // If neither the current nor existing insert has a moveId add the current insert + // to the existing one. + if (offset < insert->count) { + insert->index -= current.count; + insert->count += current.count; + } else { + insert->index += insertCount; + insert->count += current.count; + ++insert; + } } else if (offset < insert->count) { - const int moveId = insert->moveId != -1 ? m_moveCounter++ : -1; - insert = m_inserts.insert(insert, Insert(insert->index + insertCount, offset, moveId)); - ++insert; - insert->index += offset; - insert->count -= offset; - insert = m_inserts.insert(insert, *iit); - ++insert; - - if (insert->moveId != -1) { - QVector<Remove>::iterator remove = m_removes.begin(); - for (; remove != m_removes.end() && remove->moveId != insert->moveId; ++remove) {} - Q_ASSERT(remove != m_removes.end()); - if (remove->count == offset) { - remove->moveId = moveId; - } else { - remove = m_removes.insert(remove, Remove(remove->index, offset, moveId)); - ++remove; - remove->count -= offset; - } + // If either insert has a moveId then split the existing insert and insert the + // current one in the middle. + if (offset > 0) { + insert = m_inserts.insert(insert, Insert( + insert->index + insertCount, offset, insert->moveId, insert->offset)); + ++insert; + insert->index += offset; + insert->count -= offset; + insert->offset += offset; } + insert = m_inserts.insert(insert, current); + ++insert; } else { + insert->index += insertCount; ++insert; - insert = m_inserts.insert(insert, *iit); + insert = m_inserts.insert(insert, current); ++insert; } - insertCount += iit->count; } + insertCount += current.count; } - for (; change != m_changes.end(); ++change) - change->index += insertCount; for (; insert != m_inserts.end(); ++insert) insert->index += insertCount; m_difference += insertCount; } -void QQuickChangeSet::applyChanges(QVector<Change> &changes) +/*! + Applies a combined list of \a removes and \a inserts to a change set. This is equivalent + calling \l remove() followed by \l insert() with the same lists. +*/ + +void QQuickChangeSet::move(const QVector<Remove> &removes, const QVector<Insert> &inserts) +{ + QVector<Remove> r = removes; + QVector<Insert> i = inserts; + remove(&r, &i); + insert(i); +} + +/*! + Applies a list of \a changes to a change set. +*/ + +void QQuickChangeSet::change(const QVector<Change> &changes) +{ + QVector<Change> c = changes; + change(&c); +} + +void QQuickChangeSet::change(QVector<Change> *changes) { QVector<Insert>::iterator insert = m_inserts.begin(); QVector<Change>::iterator change = m_changes.begin(); - for (QVector<Change>::iterator cit = changes.begin(); cit != changes.end(); ++cit) { + for (QVector<Change>::iterator cit = changes->begin(); cit != changes->end(); ++cit) { for (; insert != m_inserts.end() && insert->end() < cit->index; ++insert) {} for (; insert != m_inserts.end() && insert->index < cit->end(); ++insert) { const int offset = insert->index - cit->index; @@ -416,7 +523,7 @@ void QQuickChangeSet::applyChanges(QVector<Change> &changes) cit->index = insert->index + insert->count; cit->count = count; } else { - cit = changes.insert(++cit, Change(insert->index + insert->count, count)); + cit = changes->insert(++cit, Change(insert->index + insert->count, count)); --cit; cit->count = offset; } @@ -436,14 +543,14 @@ void QQuickChangeSet::applyChanges(QVector<Change> &changes) if (cit->index + cit->count > change->index + change->count) { change->count = cit->index + cit->count - change->index; - QVector<Change>::iterator rbegin = change; - QVector<Change>::iterator rend = ++rbegin; - for (; rend != m_changes.end() && rend->index <= change->index + change->count; ++rend) { - if (rend->index + rend->count > change->index + change->count) - change->count = rend->index + rend->count - change->index; + QVector<Change>::iterator cbegin = change; + QVector<Change>::iterator cend = ++cbegin; + for (; cend != m_changes.end() && cend->index <= change->index + change->count; ++cend) { + if (cend->index + cend->count > change->index + change->count) + change->count = cend->index + cend->count - change->index; } - if (rbegin != rend) { - change = m_changes.erase(rbegin, rend); + if (cbegin != cend) { + change = m_changes.erase(cbegin, cend); --change; } } @@ -451,6 +558,10 @@ void QQuickChangeSet::applyChanges(QVector<Change> &changes) } } +/*! + Prints the contents of a change \a set to the \a debug stream. +*/ + QDebug operator <<(QDebug debug, const QQuickChangeSet &set) { debug.nospace() << "QQuickChangeSet("; @@ -460,16 +571,52 @@ QDebug operator <<(QDebug debug, const QQuickChangeSet &set) return debug.nospace() << ')'; } +/*! + Prints a \a remove to the \a debug stream. +*/ + QDebug operator <<(QDebug debug, const QQuickChangeSet::Remove &remove) { - return (debug.nospace() << "Remove(" << remove.index << ',' << remove.count << ',' << remove.moveId << ')').space(); + if (remove.moveId == -1) { + return (debug.nospace() + << "Remove(" << remove.index + << ',' << remove.count + << ')').space(); + } else { + return (debug.nospace() + << "Remove(" << remove.index + << ',' << remove.count + << ',' << remove.moveId + << ',' << remove.offset + << ')').space(); + } } +/*! + Prints an \a insert to the \a debug stream. +*/ + QDebug operator <<(QDebug debug, const QQuickChangeSet::Insert &insert) { - return (debug.nospace() << "Insert(" << insert.index << ',' << insert.count << ',' << insert.moveId << ')').space(); + if (insert.moveId == -1) { + return (debug.nospace() + << "Insert(" << insert.index + << ',' << insert.count + << ')').space(); + } else { + return (debug.nospace() + << "Insert(" << insert.index + << ',' << insert.count + << ',' << insert.moveId + << ',' << insert.offset + << ')').space(); + } } +/*! + Prints a \a change to the \a debug stream. +*/ + QDebug operator <<(QDebug debug, const QQuickChangeSet::Change &change) { return (debug.nospace() << "Change(" << change.index << ',' << change.count << ')').space(); |