Implemented row moving in the model and replaced swapping with it

Tue, 06 Mar 2018 23:29:40 +0200

author
Santeri Piippo
date
Tue, 06 Mar 2018 23:29:40 +0200
changeset 1273
900f1dfae46b
parent 1272
bf0ac547b934
child 1274
b6ee4ae3f421

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)
+		);
 	}
 }
 

mercurial