Tue, 06 Mar 2018 23:29:40 +0200
Implemented row moving in the model and replaced swapping with it
CMakeLists.txt | file | annotate | diff | comparison | revisions | |
src/basics.h | file | annotate | diff | comparison | revisions | |
src/editHistory.cpp | file | annotate | diff | comparison | revisions | |
src/editHistory.h | file | annotate | diff | comparison | revisions | |
src/generics/migrate.h | file | annotate | diff | comparison | revisions | |
src/generics/range.h | file | annotate | diff | comparison | revisions | |
src/lddocument.cpp | file | annotate | diff | comparison | revisions | |
src/main.h | file | annotate | diff | comparison | revisions | |
src/model.cpp | file | annotate | diff | comparison | revisions | |
src/model.h | file | annotate | diff | comparison | revisions | |
src/toolsets/movetoolset.cpp | file | annotate | diff | comparison | revisions |
--- a/CMakeLists.txt Mon Mar 05 23:59:47 2018 +0200 +++ b/CMakeLists.txt Tue Mar 06 23:29:40 2018 +0200 @@ -138,6 +138,8 @@ src/editmodes/magicWandMode.h src/editmodes/rectangleMode.h src/editmodes/selectMode.h + src/generics/migrate.h + src/generics/range.h src/generics/reverse.h src/geometry/linesegment.h src/linetypes/comment.h
--- a/src/basics.h Mon Mar 05 23:59:47 2018 +0200 +++ b/src/basics.h Tue Mar 06 23:29:40 2018 +0200 @@ -329,6 +329,15 @@ return x / qAbs(x); } +template<> +inline int sign(int x) +{ + if (x == 0) + return 0; + else + return x / qAbs(x); +} + /* * Returns the maximum of a single parameter (the parameter itself). */
--- a/src/editHistory.cpp Mon Mar 05 23:59:47 2018 +0200 +++ b/src/editHistory.cpp Tue Mar 06 23:29:40 2018 +0200 @@ -188,19 +188,31 @@ parent()->document()->setObjectAt(row, newState); } -SwapHistoryEntry::SwapHistoryEntry (const QModelIndex& index_1, const QModelIndex& index_2, EditHistory* parent) : +MoveHistoryEntry::MoveHistoryEntry(int top, int bottom, int destination, EditHistory* parent) : AbstractHistoryEntry {parent}, - row_1 (index_1.row()), - row_2 (index_2.row()) {} + top {top}, + bottom {bottom}, + destination {destination} {} - -void SwapHistoryEntry::undo() +void MoveHistoryEntry::undo() { - Model* model = parent()->document(); - model->swapObjects(model->index(row_1), model->index(row_2)); + bool downwards = (destination < top); + parent()->document()->moveRows( + {}, + downwards ? (destination) : (destination - (bottom - top) - 1), + bottom - top + 1, + {}, + downwards ? (bottom + 1) : top + ); } -void SwapHistoryEntry::redo() +void MoveHistoryEntry::redo() { - undo(); + parent()->document()->moveRows( + {}, + top, + bottom - top + 1, + {}, + destination + ); }
--- a/src/editHistory.h Mon Mar 05 23:59:47 2018 +0200 +++ b/src/editHistory.h Tue Mar 06 23:29:40 2018 +0200 @@ -116,14 +116,15 @@ Serializer::Archive newState; }; -class SwapHistoryEntry : public AbstractHistoryEntry +class MoveHistoryEntry : public AbstractHistoryEntry { public: - SwapHistoryEntry (const QModelIndex& index_1, const QModelIndex& index_2, EditHistory* parent); + MoveHistoryEntry(int top, int bottom, int destination, EditHistory* parent); void undo() override; void redo() override; private: - int row_1; - int row_2; + int top; + int bottom; + int destination; };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/generics/migrate.h Tue Mar 06 23:29:40 2018 +0200 @@ -0,0 +1,45 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2017 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <algorithm> +#include "range.h" + +template<typename T> +void migrate(T& vector, int first, int last, int destination) +{ + if (destination < first) + { + int n = first - destination; + for (int i : range(first, first + 1, last)) + { + for (int step : range(i - 1, i - 2, i - n)) + std::swap(vector[step + 1], vector[step]); + } + } + else if (destination > last) + { + int n = destination - last - 1; + + for (int i : range(last, last - 1, first)) + { + for (int step : range(i + 1, i + 2, i + n)) + std::swap(vector[step - 1], vector[step]); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/generics/range.h Tue Mar 06 23:29:40 2018 +0200 @@ -0,0 +1,161 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2017 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +/* + * Range + * + * This class models a range of values (by default integers but anything with a + * total order qualifies) The value type must be constructible with 0 and 1 for + * the sake of default values. + * + * The range may be iterated, in which case the first value yielded will be the + * lower bound. Then, the iterator's value is incremented by a certain step + * value, yielding the next value. This is continued until the iterator would + * yield a value larger than upper bound. + * + * A range can be constructed in a number of ways: + * - Range<>() + * default-construction, lower and upper bounds will be set to 0. + * - Range<>(first, end) + * the range contains the values [first, end - 1]. + * Iteration of this range yields: first, first + 1, ..., end - 1. + * - Range<>(first, second, end) + * the range contains the values [first, end - 1]. + * Iteration of this range yields: + * - first + * - first + (second - first) + * - first + 2*(second - first) + * - end - 1 (assuming that (end - 1) can be represented with + * (first + n*(second - first)) with some n + * + * The function range() is provided to avoid the angle brackets for the common + * use case where T = int. + */ +template<typename T = int> +class Range +{ + struct Iterator + { + const T baseValue; + const T stepValue; + int stepCount = 0; + + Iterator() : + baseValue {0}, + stepValue {1}, + stepCount {0} {} + + Iterator(T value, T stepValue, T stepCount) : + baseValue {value}, + stepValue {stepValue}, + stepCount {stepCount} {} + + T operator*() const + { + return baseValue + stepCount * stepValue; + } + + bool operator!=(const Iterator& other) const + { + return stepCount != other.stepCount; + } + + Iterator& operator++() + { + stepCount += 1; + return *this; + } + + Iterator& operator--() + { + stepCount -= 1; + return *this; + } + }; + +public: + Range() : + beginValue {0}, + endValue {0}, + step {1} {} + + Range(T first, T second, T last) : + beginValue {first}, + endValue {last + (second - first)}, + step {second - first} {} + + Iterator begin() const + { + return {beginValue, step, 0}; + } + + bool contains(T value) const + { + return value >= beginValue and value < endValue; + } + + Iterator end() const + { + return {beginValue, step, (endValue - beginValue) / step}; + } + + bool overlaps(const Range<T>& other) const + { + return contains(other.beginValue) or contains(other.endValue - 1); + } + + bool operator==(const Range<T>& other) const + { + return beginValue == other.beginValue + and endValue == other.endValue + and step == other.step; + } + + bool operator!=(Range<T> const& other) const + { + return not operator==(other); + } + +private: + T beginValue; + T endValue; + T step; +}; + +/* + * Returns a range from [first, second, ..., last] + */ +template<typename T = int> +Range<T> range(T first, T second, T last) +{ + return {first, second, last}; +} + +/* + * Returns a range from [first, first + 1, ..., end - 1] + */ +/* +template<typename T = int> +Range<T> range(T end) +{ + return range<T>(0, 1, end - 1); +} +*/
--- a/src/lddocument.cpp Mon Mar 05 23:59:47 2018 +0200 +++ b/src/lddocument.cpp Tue Mar 06 23:29:40 2018 +0200 @@ -44,10 +44,10 @@ connect( this, - &Model::objectsSwapped, - [&](const QModelIndex& index_1, const QModelIndex& index_2) + &Model::rowsMoved, + [&](const QModelIndex&, int start, int end, const QModelIndex&, int row) { - history()->add<SwapHistoryEntry>(index_1, index_2); + history()->add<MoveHistoryEntry>(start, end, row); } ); }
--- a/src/main.h Mon Mar 05 23:59:47 2018 +0200 +++ b/src/main.h Tue Mar 06 23:29:40 2018 +0200 @@ -32,5 +32,6 @@ #include "version.h" #include "format.h" #include "configuration.h" +#include "generics/range.h" extern Configuration* config;
--- a/src/model.cpp Mon Mar 05 23:59:47 2018 +0200 +++ b/src/model.cpp Tue Mar 06 23:29:40 2018 +0200 @@ -19,6 +19,7 @@ #include "model.h" #include "linetypes/modelobject.h" #include "documentmanager.h" +#include "generics/migrate.h" #include "linetypes/comment.h" #include "linetypes/conditionaledge.h" #include "linetypes/edgeline.h" @@ -79,17 +80,21 @@ installObject(row, object); } -/* - * Swaps one object with another, assuming they both are in this model. - */ -bool Model::swapObjects(const QModelIndex& index_1, const QModelIndex& index_2) -{ - if (index_1.isValid() and index_2.isValid() and index_1 != index_2) - { - qSwap(_objects[index_1.row()], _objects[index_2.row()]); - emit objectsSwapped(index_1, index_2); - emit dataChanged(index_1, index_1); - emit dataChanged(index_2, index_2); +bool Model::moveRows( + const QModelIndex&, + int sourceRow, + int count, + const QModelIndex&, + int destinationRow +) { + int sourceRowLast = sourceRow + count - 1; + + if (range(0, 1, size() - count).contains(sourceRow) + and range(0, 1, size()).contains(destinationRow) + ) { + beginMoveRows({}, sourceRow, sourceRowLast, {}, destinationRow); + migrate(_objects, sourceRow, sourceRowLast, destinationRow); + endMoveRows(); return true; } else
--- a/src/model.h Mon Mar 05 23:59:47 2018 +0200 +++ b/src/model.h Tue Mar 06 23:29:40 2018 +0200 @@ -89,7 +89,6 @@ void insertCopy(int position, LDObject* object); void insertFromArchive(int row, Serializer::Archive& archive); - bool swapObjects(const QModelIndex& index_1, const QModelIndex& index_2); bool setObjectAt(int idx, Serializer::Archive& archive); template<typename T, typename... Args> T* emplace(Args&& ...args); template<typename T, typename... Args> T* emplaceAt(int position, Args&& ...args); @@ -116,6 +115,14 @@ LDObject* lookup(const QModelIndex& index) const; QModelIndex indexFromId(qint32 id) const; + bool moveRows( + const QModelIndex& sourceParent, + int sourceRow, + int count, + const QModelIndex& destinationParent, + int destinationChild + ) override; + int rowCount(const QModelIndex& parent) const override; QVariant data(const QModelIndex& index, int role) const override; @@ -123,7 +130,6 @@ void objectAdded(const QModelIndex& object); void aboutToRemoveObject(const QModelIndex& index); void objectModified(LDObject* object); - void objectsSwapped(const QModelIndex& index_1, const QModelIndex& index_2); protected: template<typename T, typename... Args> T* constructObject(Args&& ...args);
--- a/src/toolsets/movetoolset.cpp Mon Mar 05 23:59:47 2018 +0200 +++ b/src/toolsets/movetoolset.cpp Tue Mar 06 23:29:40 2018 +0200 @@ -30,33 +30,15 @@ void MoveToolset::moveSelection (bool up) { - // TODO: order these! - QVector<LDObject*> objs = selectedObjects().toList().toVector(); - - if (objs.isEmpty()) - return; - - // If we move down, we need to iterate the array in reverse order. - int start = up ? 0 : (countof(objs) - 1); - int end = up ? countof(objs) : -1; - int increment = up ? 1 : -1; - - for (int i = start; i != end; i += increment) + for (const QItemSelectionRange& selectionRange : m_window->currentSelectionModel()->selection()) { - LDObject* obj = objs[i]; - - QModelIndex index = currentDocument()->indexOf(obj); - int target = index.row() + (up ? -1 : 1); - - if ((up and index.row() == 0) or (not up and index.row() == countof(currentDocument()->objects()) - 1)) - { - // One of the objects hit the extrema. If this happens, this should be the first - // object to be iterated on. Thus, nothing has changed yet and it's safe to just - // abort the entire operation. - return; - } - - currentDocument()->swapObjects(index, currentDocument()->index(target)); + currentDocument()->moveRows( + {}, + selectionRange.top(), + selectionRange.height(), + {}, + up ? (selectionRange.top() - 1) : (selectionRange.bottom() + 2) + ); } }