Skip to content

Commit

Permalink
Merge pull request #5570 from dmitrio95/295544-endedit-text-undoes-ch…
Browse files Browse the repository at this point in the history
…anges

fix #295544: fix reverting non-textual changes made in text editing mode
  • Loading branch information
dmitrio95 committed Feb 20, 2020
2 parents 5732889 + 5b2853b commit a00781f
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 34 deletions.
69 changes: 41 additions & 28 deletions libmscore/textedit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,48 +72,61 @@ void TextBase::endEdit(EditData& ed)
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
const QString actualText = xmlText();
UndoStack* undo = score()->undoStack();
while (undo->getCurIdx() > ted->startUndoIdx)
undo->undo(&ed);

// replace all undo/redo records collected during text editing with
// one property change

using Filter = UndoCommand::Filter;
undo->mergeCommands(ted->startUndoIdx);
undo->last()->filterChildren(Filter::TextEdit, this);

bool newlyAdded = false;

if (ted->oldXmlText.isEmpty()) {
UndoStack* us = score()->undoStack();
UndoCommand* ucmd = us->last();
if (ucmd) {
const QList<UndoCommand*>& cl = ucmd->commands();
const UndoCommand* cmd = cl.back();
if (strncmp(cmd->name(), "Add:", 4) == 0) {
const AddElement* ae = static_cast<const AddElement*>(cmd);
if (ae->getElement() == this) {
if (actualText.isEmpty()) {
// we just created this empty text, rollback that operation
us->rollback();
score()->update();
ed.element = 0;
}
else {
us->reopen();
// combine undo records of text creation with text editing
undoChangeProperty(Pid::TEXT, actualText);
layout1();
score()->endCmd();
}
return;
}
}
UndoCommand* ucmd = us->prev();
if (ucmd && ucmd->hasFilteredChildren(Filter::AddElement, this)) {
// We have just added this element to a score.
// Combine undo records of text creation with text editing.
newlyAdded = true;
us->mergeCommands(ted->startUndoIdx - 1);
}
}

if (actualText.isEmpty()) {
qDebug("actual text is empty");
score()->startCmd();

undo->reopen();
score()->undoRemoveElement(this);
ed.element = 0;
score()->endCmd();

static const std::vector<Filter> filters {
Filter::AddElementLinked,
Filter::RemoveElementLinked,
Filter::ChangePropertyLinked,
Filter::Link,
};

if (newlyAdded && !undo->current()->hasUnfilteredChildren(filters, this)) {
for (Filter f : filters)
undo->current()->filterChildren(f, this);

score()->endCmd();

// Ensure that unnecessary text elements will be cleaned up
MasterScore* ms = score()->masterScore();
for (ScoreElement* se : linkList())
ms->deleteLater(se);
}
else {
score()->endCmd();
}

return;
}
score()->startCmd();

setXmlText(ted->oldXmlText); // reset text to value before editing
undo->reopen();
undoChangeProperty(Pid::TEXT, actualText); // change property to set text to actual value again
// this also changes text of linked elements
layout1();
Expand Down
22 changes: 16 additions & 6 deletions libmscore/textedit.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,30 @@ struct TextEditData : public ElementEditData {
};

//---------------------------------------------------------
// ChangeText
// TextEditUndoCommand
//---------------------------------------------------------

class ChangeText : public UndoCommand {
class TextEditUndoCommand : public UndoCommand {
protected:
TextCursor c;
public:
TextEditUndoCommand(const TextCursor& tc) : c(tc) {}
bool isFiltered(UndoCommand::Filter f, const Element* target) const override { return f == UndoCommand::Filter::TextEdit && c.text() == target; }
};

//---------------------------------------------------------
// ChangeText
//---------------------------------------------------------

class ChangeText : public TextEditUndoCommand {
QString s;

protected:
void insertText(EditData*);
void removeText(EditData*);

public:
ChangeText(const TextCursor* tc, const QString& t) : c(*tc), s(t) {}
ChangeText(const TextCursor* tc, const QString& t) : TextEditUndoCommand(*tc), s(t) {}
virtual void undo(EditData*) override = 0;
virtual void redo(EditData*) override = 0;
const TextCursor& cursor() const { return c; }
Expand Down Expand Up @@ -85,14 +96,13 @@ class RemoveText : public ChangeText {
// SplitJoinText
//---------------------------------------------------------

class SplitJoinText : public UndoCommand {
class SplitJoinText : public TextEditUndoCommand {
protected:
TextCursor c;
virtual void split(EditData*);
virtual void join(EditData*);

public:
SplitJoinText(const TextCursor* tc) : c(*tc) {}
SplitJoinText(const TextCursor* tc) : TextEditUndoCommand(*tc) {}
};

//---------------------------------------------------------
Expand Down
137 changes: 137 additions & 0 deletions libmscore/undo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,68 @@ void UndoCommand::redo(EditData* ed)
flip(ed);
}

//---------------------------------------------------------
// appendChildren
/// Append children of \p other into this UndoCommand.
/// Ownership over child commands of \p other is
/// transferred to this UndoCommand.
//---------------------------------------------------------

void UndoCommand::appendChildren(UndoCommand* other)
{
childList.append(other->childList);
other->childList.clear();
}

//---------------------------------------------------------
// hasFilteredChildren
//---------------------------------------------------------

bool UndoCommand::hasFilteredChildren(UndoCommand::Filter f, const Element* target) const
{
for (UndoCommand* cmd : childList) {
if (cmd->isFiltered(f, target))
return true;
}
return false;
}

//---------------------------------------------------------
// hasUnfilteredChildren
//---------------------------------------------------------

bool UndoCommand::hasUnfilteredChildren(const std::vector<UndoCommand::Filter>& filters, const Element* target) const
{
for (UndoCommand* cmd : childList) {
bool filtered = false;
for (UndoCommand::Filter f : filters) {
if (cmd->isFiltered(f, target)) {
filtered = true;
break;
}
}
if (!filtered)
return true;
}
return false;
}

//---------------------------------------------------------
// filterChildren
//---------------------------------------------------------

void UndoCommand::filterChildren(UndoCommand::Filter f, Element* target)
{
QList<UndoCommand*> acceptedList;
for (UndoCommand* cmd : childList) {
if (cmd->isFiltered(f, target))
delete cmd;
else
acceptedList.push_back(cmd);
}
childList = std::move(acceptedList);
}

//---------------------------------------------------------
// unwind
//---------------------------------------------------------
Expand Down Expand Up @@ -283,6 +345,24 @@ void UndoStack::remove(int idx)
curIdx = idx;
}

//---------------------------------------------------------
// mergeCommands
//---------------------------------------------------------

void UndoStack::mergeCommands(int startIdx)
{
Q_ASSERT(startIdx <= curIdx);

if (startIdx >= list.size())
return;

UndoMacro* startMacro = list[startIdx];

for (int idx = startIdx + 1; idx < curIdx; ++idx)
startMacro->append(std::move(*list[idx]));
remove(startIdx + 1); // TODO: remove from startIdx to curIdx only
}

//---------------------------------------------------------
// pop
//---------------------------------------------------------
Expand Down Expand Up @@ -474,6 +554,15 @@ void UndoMacro::redo(EditData* ed)
}
}

void UndoMacro::append(UndoMacro&& other)
{
appendChildren(&other);
if (score == other.score) {
redoInputState = std::move(other.redoInputState);
redoSelectionInfo = std::move(other.redoSelectionInfo);
}
}

//---------------------------------------------------------
// CloneVoice
//---------------------------------------------------------
Expand Down Expand Up @@ -685,6 +774,24 @@ const char* AddElement::name() const
return buffer;
}

//---------------------------------------------------------
// AddElement::isFiltered
//---------------------------------------------------------

bool AddElement::isFiltered(UndoCommand::Filter f, const Element* target) const
{
using Filter = UndoCommand::Filter;
switch (f) {
case Filter::AddElement:
return target == element;
case Filter::AddElementLinked:
return target->linkList().contains(element);
default:
break;
}
return false;
}

//---------------------------------------------------------
// removeNote
// Helper function for RemoveElement class
Expand Down Expand Up @@ -813,6 +920,24 @@ const char* RemoveElement::name() const
return buffer;
}

//---------------------------------------------------------
// RemoveElement::isFiltered
//---------------------------------------------------------

bool RemoveElement::isFiltered(UndoCommand::Filter f, const Element* target) const
{
using Filter = UndoCommand::Filter;
switch (f) {
case Filter::RemoveElement:
return target == element;
case Filter::RemoveElementLinked:
return target->linkList().contains(element);
default:
break;
}
return false;
}

//---------------------------------------------------------
// InsertPart
//---------------------------------------------------------
Expand Down Expand Up @@ -2294,6 +2419,18 @@ Link::Link(ScoreElement* e1, ScoreElement* e2)
e = e1;
}

//---------------------------------------------------------
// Link::isFiltered
//---------------------------------------------------------

bool Link::isFiltered(UndoCommand::Filter f, const Element* target) const
{
using Filter = UndoCommand::Filter;
if (f == Filter::Link)
return e == target || le->contains(const_cast<Element*>(target));
return false;
}

//---------------------------------------------------------
// Unlink
//---------------------------------------------------------
Expand Down
Loading

0 comments on commit a00781f

Please sign in to comment.