1 /*
2  *    Copyright 2012, 2013 Thomas Schöps
3  *    Copyright 2014 Kai Pastor
4  *
5  *    This file is part of OpenOrienteering.
6  *
7  *    OpenOrienteering is free software: you can redistribute it and/or modify
8  *    it under the terms of the GNU General Public License as published by
9  *    the Free Software Foundation, either version 3 of the License, or
10  *    (at your option) any later version.
11  *
12  *    OpenOrienteering is distributed in the hope that it will be useful,
13  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *    GNU General Public License for more details.
16  *
17  *    You should have received a copy of the GNU General Public License
18  *    along with OpenOrienteering.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 
22 #include "undo.h"
23 
24 #include <vector>
25 
26 #include <QXmlStreamReader>
27 
28 #include "object_undo.h"
29 #include "map_part_undo.h"
30 #include "util/xml_stream_util.h"
31 
32 
33 namespace literal
34 {
35 	const QLatin1String step("step");
36 	const QLatin1String steps("steps");
37 }
38 
39 
40 
41 namespace OpenOrienteering {
42 
43 // ### UndoStep ###
44 
45 // static
getUndoStepForType(Type type,Map * map)46 UndoStep* UndoStep::getUndoStepForType(Type type, Map* map)
47 {
48 	switch (type)
49 	{
50 	case CombinedUndoStepType:
51 		return new CombinedUndoStep(map);
52 
53 	case ReplaceObjectsUndoStepType:
54 		return new ReplaceObjectsUndoStep(map);
55 
56 	case DeleteObjectsUndoStepType:
57 		return new DeleteObjectsUndoStep(map);
58 
59 	case AddObjectsUndoStepType:
60 		return new AddObjectsUndoStep(map);
61 
62 	case SwitchSymbolUndoStepType:
63 		return new SwitchSymbolUndoStep(map);
64 
65 	case SwitchDashesUndoStepType:
66 		return new SwitchDashesUndoStep(map);
67 
68 	case ValidNoOpUndoStepType:
69 		return new NoOpUndoStep(map, true);
70 
71 	case ObjectTagsUndoStepType:
72 		return new ObjectTagsUndoStep(map);
73 
74 	case SwitchPartUndoStepType:
75 		return new SwitchPartUndoStep(map);
76 
77 	case MapPartUndoStepType:
78 		return new MapPartUndoStep(map);
79 
80 	default:
81 		qWarning("Undefined undo step type");
82 		Q_FALLTHROUGH();
83 	case SwitchPartUndoStepTypeV0:
84 		return new NoOpUndoStep(map, false);
85 	}
86 }
87 
88 
UndoStep(Type type,Map * map)89 UndoStep::UndoStep(Type type, Map* map)
90 : type(type)
91 , map(map)
92 {
93 	; // nothing else
94 }
95 
~UndoStep()96 UndoStep::~UndoStep()
97 {
98 	; // nothing
99 }
100 
isValid() const101 bool UndoStep::isValid() const
102 {
103 	return true;
104 }
105 
getModifiedParts(PartSet & out) const106 bool UndoStep::getModifiedParts(PartSet& out) const
107 {
108 	Q_UNUSED(out);
109 	return false;
110 }
111 
getModifiedObjects(int,ObjectSet &) const112 void UndoStep::getModifiedObjects(int, ObjectSet&) const
113 {
114 	; // nothing
115 }
116 
117 // static
load(QXmlStreamReader & xml,Map * map,SymbolDictionary & symbol_dict)118 UndoStep* UndoStep::load(QXmlStreamReader& xml, Map* map, SymbolDictionary& symbol_dict)
119 {
120 	Q_ASSERT(xml.name() == QLatin1String("step"));
121 
122 	int type = xml.attributes().value(QLatin1String("type")).toInt();
123 	UndoStep* step = UndoStep::getUndoStepForType((Type)type, map);
124 	while (xml.readNextStartElement())
125 		step->loadImpl(xml, symbol_dict);
126 
127 	return step;
128 }
129 
save(QXmlStreamWriter & xml) const130 void UndoStep::save(QXmlStreamWriter& xml) const
131 {
132 	XmlElementWriter element(xml, QLatin1String("step"));
133 	element.writeAttribute(QLatin1String("type"), type);
134 	saveImpl(xml);
135 }
136 
saveImpl(QXmlStreamWriter & xml) const137 void UndoStep::saveImpl(QXmlStreamWriter& xml) const
138 {
139 	Q_UNUSED(xml);
140 	; // nothing yet
141 }
142 
loadImpl(QXmlStreamReader & xml,SymbolDictionary & symbol_dict)143 void UndoStep::loadImpl(QXmlStreamReader& xml, SymbolDictionary &symbol_dict)
144 {
145 	Q_UNUSED(symbol_dict);
146 
147 	xml.skipCurrentElement(); // unknown
148 }
149 
150 
151 
152 // ### CombinedUndoStep ###
153 
CombinedUndoStep(Map * map)154 CombinedUndoStep::CombinedUndoStep(Map* map)
155 : UndoStep(CombinedUndoStepType, map)
156 {
157 	; // nothing else
158 }
159 
~CombinedUndoStep()160 CombinedUndoStep::~CombinedUndoStep()
161 {
162 	for (auto* step : steps)
163 		delete step;
164 }
165 
isValid() const166 bool CombinedUndoStep::isValid() const
167 {
168 	return std::all_of(begin(steps), end(steps), [](const auto& step) {
169 		return step->isValid();
170 	});
171 }
172 
undo()173 UndoStep* CombinedUndoStep::undo()
174 {
175 	CombinedUndoStep* undo_step = new CombinedUndoStep(map);
176 	undo_step->steps.reserve(steps.size());
177 	std::for_each(steps.rbegin(), steps.rend(), [undo_step](auto step) {
178 		undo_step->push(step->undo());
179 	});
180 	return undo_step;
181 }
182 
getModifiedParts(PartSet & out) const183 bool CombinedUndoStep::getModifiedParts(PartSet &out) const
184 {
185 	for (const auto* step : steps)
186 	{
187 		step->getModifiedParts(out);
188 	}
189 	return !out.empty();
190 }
191 
getModifiedObjects(int part_index,ObjectSet & out) const192 void CombinedUndoStep::getModifiedObjects(int part_index, ObjectSet &out) const
193 {
194 	for (const auto* step : steps)
195 	{
196 		step->getModifiedObjects(part_index, out);
197 	}
198 }
199 
200 
201 
saveImpl(QXmlStreamWriter & xml) const202 void CombinedUndoStep::saveImpl(QXmlStreamWriter& xml) const
203 {
204 	UndoStep::saveImpl(xml);
205 
206 	// From Mapper 0.6, use the same XML element and order as UndoManager.
207 	// (A barrier element prevents older versions from loading this element.)
208 	XmlElementWriter steps_element(xml, literal::steps);
209 	steps_element.writeAttribute(XmlStreamLiteral::count, steps.size());
210 	for (const auto* step : steps)
211 		step->save(xml);
212 }
213 
loadImpl(QXmlStreamReader & xml,SymbolDictionary & symbol_dict)214 void CombinedUndoStep::loadImpl(QXmlStreamReader& xml, SymbolDictionary& symbol_dict)
215 {
216 	if (xml.name() == QLatin1String("substeps"))
217 	{
218 		// Mapper before 0.6
219 		// @todo Remove in a future version
220 		int size = xml.attributes().value(QLatin1String("count")).toInt();
221 		steps.reserve(qMin(size, 10)); // 10 is not a limit
222 		while (xml.readNextStartElement())
223 		{
224 			if (xml.name() == literal::step)
225 				steps.insert(steps.begin(), UndoStep::load(xml, map, symbol_dict));
226 			else
227 				xml.skipCurrentElement(); // unknown
228 		}
229 	}
230 	else if (xml.name() == literal::steps)
231 	{
232 		// From Mapper 0.6, use the same XML element and order as UndoManager.
233 		XmlElementReader steps_element(xml);
234 		std::size_t size = steps_element.attribute<std::size_t>(XmlStreamLiteral::count);
235 		steps.reserve(qMin(size, (std::size_t)50)); // 50 is not a limit
236 		while (xml.readNextStartElement())
237 		{
238 			if (xml.name() == literal::step)
239 				steps.push_back(UndoStep::load(xml, map, symbol_dict));
240 			else
241 				xml.skipCurrentElement(); // unknown
242 		}
243 	}
244 	else
245 		UndoStep::loadImpl(xml, symbol_dict);
246 }
247 
248 
249 
250 // ### InvalidUndoStep ###
251 
NoOpUndoStep(Map * map,bool valid)252 NoOpUndoStep::NoOpUndoStep(Map *map, bool valid)
253 : UndoStep(valid ? ValidNoOpUndoStepType : InvalidUndoStepType, map)
254 , valid(valid)
255 {
256 	// nothing else
257 }
258 
~NoOpUndoStep()259 NoOpUndoStep::~NoOpUndoStep()
260 {
261 	// nothing
262 }
263 
isValid() const264 bool NoOpUndoStep::isValid() const
265 {
266 	return valid;
267 }
268 
undo()269 UndoStep* NoOpUndoStep::undo()
270 {
271 	if (!valid)
272 		qWarning("InvalidUndoStep::undo() must not be called");
273 
274 	return new NoOpUndoStep(map, true);
275 }
276 
277 
278 }  // namespace OpenOrienteering
279 
280