Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : /**
24 : \file
25 : Implementation of undo functions.
26 :
27 : The undo system requires calling startUndo() when starting a GUI command
28 : and calling endUndo() when ending the command. All changes to a score
29 : in response to a GUI command must be undoable/redoable by executing
30 : a sequence of low-level undo operations. This sequence is built by the code
31 : handling the command, by calling one or more undoOp()'s
32 : between startUndo() and endUndo().
33 : */
34 :
35 : #include "undostack.h"
36 :
37 : #include "containers.h"
38 :
39 : #include "dom/box.h"
40 : #include "dom/select.h"
41 :
42 : #include "editing/editproperty.h"
43 : #include "editing/editstyle.h"
44 : #include "editing/textedit.h"
45 : #include "editing/transaction/undoablecommand.h"
46 :
47 : #include "log.h"
48 :
49 : #define LOG_UNDO() if constexpr (false) LOGD()
50 :
51 : using namespace mu::engraving;
52 :
53 : namespace mu::engraving {
54 : extern Measure* tick2measure(int tick);
55 :
56 : static const std::unordered_map<CommandType, CommandType> COMMAND_TYPE_INVERSION {
57 : { CommandType::AddElement, CommandType::RemoveElement },
58 : { CommandType::RemoveElement, CommandType::AddElement },
59 :
60 : { CommandType::AddBracket, CommandType::RemoveBracket },
61 : { CommandType::RemoveBracket, CommandType::AddBracket },
62 :
63 : { CommandType::AddExcerpt, CommandType::RemoveExcerpt },
64 : { CommandType::RemoveExcerpt, CommandType::AddExcerpt },
65 :
66 : { CommandType::AddSystemObjectStaff, CommandType::RemoveSystemObjectStaff },
67 : { CommandType::RemoveSystemObjectStaff, CommandType::AddSystemObjectStaff },
68 :
69 : { CommandType::InsertMeasures, CommandType::RemoveMeasures },
70 : { CommandType::RemoveMeasures, CommandType::InsertMeasures },
71 :
72 : { CommandType::InsertStaff, CommandType::RemoveStaff },
73 : { CommandType::RemoveStaff, CommandType::InsertStaff },
74 :
75 : { CommandType::InsertPart, CommandType::RemovePart },
76 : { CommandType::RemovePart, CommandType::InsertPart },
77 :
78 : { CommandType::Link, CommandType::Unlink },
79 : { CommandType::Unlink, CommandType::Link },
80 :
81 : { CommandType::AddNoteParenthesesInfo, CommandType::RemoveNoteParenthesesInfo },
82 : { CommandType::RemoveNoteParenthesesInfo, CommandType::AddNoteParenthesesInfo },
83 :
84 : { CommandType::ConnectSharedPart, CommandType::DisconnectSharedPart },
85 : { CommandType::DisconnectSharedPart, CommandType::ConnectSharedPart },
86 : };
87 :
88 : //---------------------------------------------------------
89 : // UndoStack
90 : //---------------------------------------------------------
91 :
92 2169 : UndoStack::UndoStack()
93 : {
94 2169 : m_activeTransaction = nullptr;
95 2169 : m_currentIndex = 0;
96 2169 : m_cleanState = 0;
97 2169 : m_states.push_back(m_cleanState);
98 2169 : m_nextState = 1;
99 2169 : }
100 :
101 1944 : UndoStack::~UndoStack()
102 : {
103 1944 : size_t idx = 0;
104 2545 : for (auto c : m_transactions) {
105 601 : c->cleanup(idx++ < m_currentIndex);
106 : }
107 1944 : muse::DeleteAll(m_transactions);
108 1944 : }
109 :
110 2540 : bool UndoStack::isLocked() const
111 : {
112 2540 : return m_isLocked;
113 : }
114 :
115 0 : void UndoStack::setLocked(bool locked)
116 : {
117 0 : m_isLocked = locked;
118 0 : }
119 :
120 1264 : void UndoStack::beginTransaction(Score* score, const TranslatableString& actionName)
121 : {
122 1264 : if (m_isLocked) {
123 0 : return;
124 : }
125 :
126 1264 : IF_ASSERT_FAILED(!m_activeTransaction) {
127 0 : LOGE() << "Transaction already in progress";
128 0 : return;
129 : }
130 :
131 1264 : m_activeTransaction = new UndoableTransaction(score, actionName);
132 : }
133 :
134 6 : void UndoStack::remove(size_t idx)
135 : {
136 6 : assert(idx <= m_currentIndex);
137 6 : assert(m_currentIndex != muse::nidx);
138 : // remove redo stack
139 12 : while (m_transactions.size() > m_currentIndex) {
140 0 : UndoableTransaction* transaction = muse::takeLast(m_transactions);
141 0 : m_states.pop_back();
142 0 : transaction->cleanup(false); // delete elements for which UndoableCommand() holds ownership
143 0 : delete transaction;
144 : // --curIdx;
145 : }
146 14 : while (m_transactions.size() > idx) {
147 2 : UndoableTransaction* transaction = muse::takeLast(m_transactions);
148 2 : m_states.pop_back();
149 2 : transaction->cleanup(true);
150 2 : delete transaction;
151 : }
152 6 : m_currentIndex = idx;
153 6 : }
154 :
155 5 : void UndoStack::mergeTransactions(size_t startIdx)
156 : {
157 5 : assert(startIdx <= m_currentIndex);
158 :
159 5 : if (startIdx >= m_transactions.size()) {
160 0 : return;
161 : }
162 :
163 5 : UndoableTransaction* startTransaction = m_transactions[startIdx];
164 :
165 7 : for (size_t idx = startIdx + 1; idx < m_currentIndex; ++idx) {
166 2 : startTransaction->append(std::move(*m_transactions[idx]));
167 : }
168 5 : remove(startIdx + 1); // TODO: remove from startIdx to curIdx only
169 : }
170 :
171 1266 : void UndoStack::endTransaction(bool rollback)
172 : {
173 1266 : if (m_isLocked) {
174 0 : return;
175 : }
176 :
177 1266 : IF_ASSERT_FAILED(m_activeTransaction) {
178 0 : LOGE() << "No transaction in progress";
179 0 : return;
180 : }
181 :
182 1266 : if (rollback) {
183 501 : delete m_activeTransaction;
184 : } else {
185 : // remove redo stack
186 1572 : while (m_transactions.size() > m_currentIndex) {
187 42 : UndoableTransaction* transaction = muse::takeLast(m_transactions);
188 42 : m_states.pop_back();
189 42 : transaction->cleanup(false); // delete elements for which UndoableCommand() holds ownership
190 42 : delete transaction;
191 : }
192 765 : m_transactions.push_back(m_activeTransaction);
193 765 : m_states.push_back(m_nextState++);
194 765 : ++m_currentIndex;
195 : }
196 1266 : m_activeTransaction = nullptr;
197 : }
198 :
199 3 : void UndoStack::reopen()
200 : {
201 3 : if (m_isLocked) {
202 0 : return;
203 : }
204 :
205 : LOG_UNDO() << "curIdx: " << m_currentIndex << ", size: " << m_transactions.size();
206 3 : assert(m_activeTransaction == nullptr);
207 3 : assert(m_currentIndex > 0);
208 3 : --m_currentIndex;
209 3 : m_activeTransaction = muse::takeAt(m_transactions, m_currentIndex);
210 3 : m_states.erase(m_states.begin() + m_currentIndex + 1);
211 3 : for (auto i : m_activeTransaction->commands()) {
212 : LOG_UNDO() << " " << i->name();
213 : }
214 : }
215 :
216 194 : void UndoStack::undo(EditData* ed)
217 : {
218 : LOG_UNDO() << "called";
219 : // Are we currently editing text?
220 194 : if (ed && ed->element && ed->element->isTextBase()) {
221 0 : TextEditData* ted = dynamic_cast<TextEditData*>(ed->getData(ed->element).get());
222 0 : if (ted && ted->startUndoIdx == m_currentIndex) {
223 : // No edits to undo, so do nothing
224 0 : return;
225 : }
226 : }
227 194 : if (m_currentIndex) {
228 194 : --m_currentIndex;
229 194 : assert(m_currentIndex < m_transactions.size());
230 194 : m_transactions[m_currentIndex]->undo();
231 : }
232 : }
233 :
234 36 : void UndoStack::redo()
235 : {
236 : LOG_UNDO() << "called";
237 36 : if (canRedo()) {
238 36 : m_transactions[m_currentIndex++]->redo();
239 : }
240 36 : }
241 :
242 : //---------------------------------------------------------
243 : // UndoableTransaction
244 : //---------------------------------------------------------
245 :
246 1264 : UndoableTransaction::UndoableTransaction(Score* s, const TranslatableString& actionName)
247 1264 : : m_undoInputState(s->inputState()), m_actionName(actionName), m_score(s)
248 : {
249 1264 : fillSelectionInfo(m_undoSelectionInfo, s->selection());
250 1264 : }
251 :
252 1146 : UndoableTransaction::~UndoableTransaction()
253 : {
254 18312 : for (UndoableCommand* command : m_commands) {
255 17166 : delete command;
256 : }
257 1146 : }
258 :
259 645 : void UndoableTransaction::cleanup(bool wasDone)
260 : {
261 17811 : for (UndoableCommand* command : m_commands) {
262 17166 : command->cleanup(wasDone);
263 : }
264 645 : }
265 :
266 2 : void UndoableTransaction::unwind(bool cleanUp)
267 : {
268 : // Undo everything first, without freeing: commands pushed later (e.g. AddElement for a part's linked
269 : // clone) may still be referenced by earlier commands' undo() (e.g. Link::undo()):
270 2 : std::vector<UndoableCommand*> undoneCommands;
271 2 : undoneCommands.reserve(m_commands.size());
272 5 : while (!m_commands.empty()) {
273 3 : UndoableCommand* command = muse::takeLast(m_commands);
274 : LOG_UNDO() << "unwind: " << command->name();
275 3 : command->undo();
276 3 : undoneCommands.push_back(command);
277 : }
278 : // Now we can free everything:
279 5 : for (UndoableCommand* command : undoneCommands) {
280 3 : if (cleanUp) {
281 3 : command->cleanup(false);
282 : }
283 3 : delete command;
284 : }
285 2 : }
286 :
287 2 : void UndoableTransaction::appendCommands(UndoableTransaction& other)
288 : {
289 2 : m_commands.insert(m_commands.end(), other.m_commands.cbegin(), other.m_commands.cend());
290 2 : other.m_commands.clear();
291 2 : }
292 :
293 0 : bool UndoableTransaction::hasCommandsMatchingFilter(UndoableCommandFilter f, const EngravingItem* target) const
294 : {
295 0 : for (UndoableCommand* command : m_commands) {
296 0 : if (command->matchesFilter(f, target)) {
297 0 : return true;
298 : }
299 : }
300 0 : return false;
301 : }
302 :
303 0 : bool UndoableTransaction::hasCommandsNotMatchingFilters(const std::vector<UndoableCommandFilter>& filters,
304 : const EngravingItem* target) const
305 : {
306 0 : for (UndoableCommand* command : m_commands) {
307 0 : bool filtered = false;
308 0 : for (UndoableCommandFilter f : filters) {
309 0 : if (command->matchesFilter(f, target)) {
310 0 : filtered = true;
311 0 : break;
312 : }
313 : }
314 0 : if (!filtered) {
315 0 : return true;
316 : }
317 : }
318 0 : return false;
319 : }
320 :
321 3 : void UndoableTransaction::removeCommandsMatchingFilter(UndoableCommandFilter f, EngravingItem* target)
322 : {
323 3 : std::vector<UndoableCommand*> acceptedList;
324 6 : for (UndoableCommand* command : m_commands) {
325 3 : if (command->matchesFilter(f, target)) {
326 3 : delete command;
327 : } else {
328 0 : acceptedList.push_back(command);
329 : }
330 : }
331 3 : m_commands = std::move(acceptedList);
332 3 : }
333 :
334 846 : bool UndoableTransaction::canRecordSelectedElement(const EngravingItem* e)
335 : {
336 846 : if (e->generated()) {
337 1 : return false;
338 : }
339 :
340 1468 : return e->isNote() || (e->isChordRest() && !e->isChord())
341 541 : || (e->isTextBase() && !e->isInstrumentName() && !e->isHammerOnPullOffText())
342 1468 : || e->isFretDiagram() || e->isSoundFlag();
343 : }
344 :
345 1494 : void UndoableTransaction::fillSelectionInfo(SelectionInfo& info, const Selection& sel)
346 : {
347 1494 : info.staffStart = info.staffEnd = muse::nidx;
348 1494 : info.startBox = info.endBox = nullptr;
349 1494 : info.elements.clear();
350 :
351 1494 : if (sel.isList()) {
352 1092 : for (EngravingItem* e : sel.elements()) {
353 846 : if (canRecordSelectedElement(e)) {
354 800 : info.elements.push_back(e);
355 : } else {
356 : // don't remember selection we are unable to restore
357 46 : info.elements.clear();
358 46 : return;
359 : }
360 : }
361 1202 : } else if (sel.isRange()) {
362 142 : info.staffStart = sel.staffStart();
363 142 : info.staffEnd = sel.staffEnd();
364 142 : info.tickStart = sel.tickStart();
365 142 : info.tickEnd = sel.tickEnd();
366 142 : info.startBox = sel.startBox();
367 142 : info.endBox = sel.endBox();
368 : }
369 : }
370 :
371 71 : void UndoableTransaction::applySelectionInfo(const SelectionInfo& info, Selection& sel)
372 : {
373 71 : if (!info.elements.empty()) {
374 108 : for (EngravingItem* e : info.elements) {
375 68 : sel.add(e);
376 : }
377 31 : } else if (info.staffStart != muse::nidx) {
378 31 : sel.setRangeTicks(info.tickStart, info.tickEnd, info.staffStart, info.staffEnd, info.startBox, info.endBox);
379 : }
380 71 : }
381 :
382 194 : void UndoableTransaction::undo()
383 : {
384 194 : m_redoInputState = m_score->inputState();
385 194 : fillSelectionInfo(m_redoSelectionInfo, m_score->selection());
386 194 : m_score->deselectAll();
387 :
388 3360 : for (auto it = m_commands.rbegin(); it != m_commands.rend(); ++it) {
389 : LOG_UNDO() << "<" << (*it)->name() << ">";
390 3166 : (*it)->undo();
391 : }
392 :
393 194 : m_score->setInputState(m_undoInputState);
394 194 : if (m_undoSelectionInfo.isValid()) {
395 52 : m_score->deselectAll();
396 52 : applySelectionInfo(m_undoSelectionInfo, m_score->selection());
397 : }
398 194 : }
399 :
400 36 : void UndoableTransaction::redo()
401 : {
402 36 : m_undoInputState = m_score->inputState();
403 36 : fillSelectionInfo(m_undoSelectionInfo, m_score->selection());
404 36 : m_score->deselectAll();
405 :
406 507 : for (UndoableCommand* command : m_commands) {
407 : LOG_UNDO() << "<" << command->name() << ">";
408 471 : command->redo();
409 : }
410 :
411 36 : m_score->setInputState(m_redoInputState);
412 36 : if (m_redoSelectionInfo.isValid()) {
413 19 : m_score->deselectAll();
414 19 : applySelectionInfo(m_redoSelectionInfo, m_score->selection());
415 : }
416 36 : }
417 :
418 2 : void UndoableTransaction::append(UndoableTransaction&& other)
419 : {
420 2 : appendCommands(other);
421 2 : if (m_score == other.m_score) {
422 2 : m_redoInputState = std::move(other.m_redoInputState);
423 2 : m_redoSelectionInfo = std::move(other.m_redoSelectionInfo);
424 : }
425 2 : }
426 :
427 0 : const InputState& UndoableTransaction::undoInputState() const
428 : {
429 0 : return m_undoInputState;
430 : }
431 :
432 0 : const InputState& UndoableTransaction::redoInputState() const
433 : {
434 0 : return m_redoInputState;
435 : }
436 :
437 0 : void UndoableTransaction::excludeElementFromSelectionInfo(EngravingItem* element)
438 : {
439 0 : if (m_undoSelectionInfo.isValid()) {
440 0 : muse::remove(m_undoSelectionInfo.elements, element);
441 : }
442 :
443 0 : if (m_redoSelectionInfo.isValid()) {
444 0 : muse::remove(m_redoSelectionInfo.elements, element);
445 : }
446 0 : }
447 :
448 0 : const UndoableTransaction::SelectionInfo& UndoableTransaction::undoSelectionInfo() const
449 : {
450 0 : return m_undoSelectionInfo;
451 : }
452 :
453 0 : const UndoableTransaction::SelectionInfo& UndoableTransaction::redoSelectionInfo() const
454 : {
455 0 : return m_redoSelectionInfo;
456 : }
457 :
458 1467 : UndoableTransaction::ChangesInfo UndoableTransaction::changesInfo(bool undo) const
459 : {
460 1467 : ChangesInfo result;
461 :
462 24813 : for (const UndoableCommand* command : commands()) {
463 23346 : CommandType type = command->type();
464 :
465 23346 : if (type == CommandType::ChangeProperty) {
466 2310 : auto changeProperty = static_cast<const ChangeProperty*>(command);
467 2310 : result.changedPropertyIdSet.insert(changeProperty->getId());
468 21036 : } else if (type == CommandType::ChangeStyleValues) {
469 56 : auto changeStyle = static_cast<const ChangeStyleValues*>(command);
470 113 : for (const auto& pair : changeStyle->values()) {
471 57 : result.changedStyleIdSet.insert(pair.first);
472 : }
473 20980 : } else if (type == CommandType::ChangeStyle) {
474 1 : auto changeStyle = static_cast<const ChangeStyle*>(command);
475 1 : const StyleIdSet styleIds = changeStyle->changedIds();
476 1 : result.changedStyleIdSet.insert(styleIds.cbegin(), styleIds.cend());
477 20980 : } else if (type == CommandType::TextEdit) {
478 9 : result.isTextEditing |= static_cast<const TextEditUndoableCommand*>(command)->cursor().editing();
479 : }
480 :
481 23346 : if (undo) {
482 1946 : auto it = COMMAND_TYPE_INVERSION.find(type);
483 1946 : if (it != COMMAND_TYPE_INVERSION.end()) {
484 1007 : type = it->second;
485 : }
486 : }
487 :
488 61296 : for (EngravingObject* object : command->objectItems()) {
489 37950 : if (!object) {
490 24 : continue;
491 : }
492 :
493 37926 : result.changedObjectTypes.insert(object->type());
494 37926 : result.changedObjects[object].insert(type);
495 23346 : }
496 : }
497 :
498 1467 : return result;
499 0 : }
500 :
501 0 : const TranslatableString& UndoableTransaction::actionName() const
502 : {
503 0 : return m_actionName;
504 : }
505 : }
|