//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2002-2016 Werner Schweer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

/**
 \file
 Implementation of undo functions.

 The undo system requires calling startUndo() when starting a GUI command
 and calling endUndo() when ending the command. All changes to a score
 in response to a GUI command must be undoable/redoable by executing
 a sequence of low-level undo operations. This sequence is built by the code
 handling the command, by calling one or more undoOp()'s
 between startUndo() and endUndo().
*/

#include "log.h"
#include "undo.h"
#include "element.h"
#include "note.h"
#include "score.h"
#include "segment.h"
#include "measure.h"
#include "system.h"
#include "select.h"
#include "input.h"
#include "slur.h"
#include "tie.h"
#include "clef.h"
#include "staff.h"
#include "chord.h"
#include "sig.h"
#include "key.h"
#include "barline.h"
#include "volta.h"
#include "tuplet.h"
#include "harmony.h"
#include "pitchspelling.h"
#include "part.h"
#include "beam.h"
#include "dynamic.h"
#include "page.h"
#include "keysig.h"
#include "image.h"
#include "hairpin.h"
#include "rest.h"
#include "bend.h"
#include "tremolobar.h"
#include "articulation.h"
#include "noteevent.h"
#include "slur.h"
#include "tempotext.h"
#include "instrchange.h"
#include "box.h"
#include "stafftype.h"
#include "accidental.h"
#include "layoutbreak.h"
#include "spanner.h"
#include "sequencer.h"
#include "breath.h"
#include "fingering.h"
#include "rehearsalmark.h"
#include "excerpt.h"
#include "stafftext.h"
#include "chordline.h"
#include "tremolo.h"
#include "sym.h"
#include "utils.h"
#include "glissando.h"
#include "stafflines.h"
#include "bracket.h"
#include "fret.h"
#include "textedit.h"

namespace Ms {
extern Measure* tick2measure(int tick);

//---------------------------------------------------------
//   updateNoteLines
//    compute line position of noteheads after
//    clef change
//---------------------------------------------------------

void updateNoteLines(Segment* segment, int track)
{
    Staff* staff = segment->score()->staff(track / VOICES);
    if (staff->isDrumStaff(segment->tick()) || staff->isTabStaff(segment->tick())) {
        return;
    }
    for (Segment* s = segment->next1(); s; s = s->next1()) {
        if ((s->segmentType() & (SegmentType::Clef | SegmentType::HeaderClef)) && s->element(track)
            && !s->element(track)->generated()) {
            break;
        }
        if (!s->isChordRestType()) {
            continue;
        }
        for (int t = track; t < track + VOICES; ++t) {
            Element* e = s->element(t);
            if (e && e->isChord()) {
                Chord* chord = toChord(e);
                for (Note* n : chord->notes()) {
                    n->updateLine();
                }
                chord->sortNotes();
                for (Chord* gc : chord->graceNotes()) {
                    for (Note* gn : gc->notes()) {
                        gn->updateLine();
                    }
                    gc->sortNotes();
                }
            }
        }
    }
}

//---------------------------------------------------------
//   UndoCommand
//---------------------------------------------------------

UndoCommand::~UndoCommand()
{
    for (auto c : qAsConst(childList)) {
        delete c;
    }
}

//---------------------------------------------------------
//   UndoCommand::cleanup
//---------------------------------------------------------

void UndoCommand::cleanup(bool undo)
{
    for (auto c : childList) {
        c->cleanup(undo);
    }
}

//---------------------------------------------------------
//   undo
//---------------------------------------------------------

void UndoCommand::undo(EditData* ed)
{
    int n = childList.size();
    for (int i = n - 1; i >= 0; --i) {
        qCDebug(undoRedo) << "<" << childList[i]->name() << ">";
        childList[i]->undo(ed);
    }
    flip(ed);
}

//---------------------------------------------------------
//   redo
//---------------------------------------------------------

void UndoCommand::redo(EditData* ed)
{
    int n = childList.size();
    for (int i = 0; i < n; ++i) {
        qCDebug(undoRedo) << "<" << childList[i]->name() << ">";
        childList[i]->redo(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
//---------------------------------------------------------

void UndoCommand::unwind()
{
    while (!childList.empty()) {
        UndoCommand* c = childList.takeLast();
        qDebug("unwind <%s>", c->name());
        c->undo(0);
        delete c;
    }
}

//---------------------------------------------------------
//   UndoStack
//---------------------------------------------------------

UndoStack::UndoStack()
{
    curCmd   = 0;
    curIdx   = 0;
    cleanState = 0;
    stateList.push_back(cleanState);
    nextState = 1;
}

//---------------------------------------------------------
//   UndoStack
//---------------------------------------------------------

UndoStack::~UndoStack()
{
    int idx = 0;
    for (auto c : list) {
        c->cleanup(idx++ < curIdx);
    }
    qDeleteAll(list);
}

//---------------------------------------------------------
//   beginMacro
//---------------------------------------------------------

void UndoStack::beginMacro(Score* score)
{
    if (curCmd) {
        qWarning("already active");
        return;
    }
    curCmd = new UndoMacro(score);
}

//---------------------------------------------------------
//   push
//---------------------------------------------------------

void UndoStack::push(UndoCommand* cmd, EditData* ed)
{
    if (!curCmd) {
        // this can happen for layout() outside of a command (load)
        if (!ScoreLoad::loading()) {
            qDebug("no active command, UndoStack");
        }

        cmd->redo(ed);
        delete cmd;
        return;
    }
#ifndef QT_NO_DEBUG
    if (!strcmp(cmd->name(), "ChangeProperty")) {
        ChangeProperty* cp = static_cast<ChangeProperty*>(cmd);
        qCDebug(undoRedo, "<%s> id %d %s", cmd->name(), int(cp->getId()), propertyName(cp->getId()));
    } else {
        qCDebug(undoRedo, "<%s>", cmd->name());
    }
#endif
    curCmd->appendChild(cmd);
    cmd->redo(ed);
}

//---------------------------------------------------------
//   push1
//---------------------------------------------------------

void UndoStack::push1(UndoCommand* cmd)
{
    if (!curCmd) {
        if (!ScoreLoad::loading()) {
            qWarning("no active command, UndoStack %p", this);
        }
        return;
    }
    curCmd->appendChild(cmd);
}

//---------------------------------------------------------
//   remove
//---------------------------------------------------------

void UndoStack::remove(int idx)
{
    Q_ASSERT(idx <= curIdx);
    Q_ASSERT(curIdx >= 0);
    // remove redo stack
    while (list.size() > curIdx) {
        UndoCommand* cmd = list.takeLast();
        stateList.pop_back();
        cmd->cleanup(false);      // delete elements for which UndoCommand() holds ownership
        delete cmd;
//            --curIdx;
    }
    while (list.size() > idx) {
        UndoCommand* cmd = list.takeLast();
        stateList.pop_back();
        cmd->cleanup(true);
        delete cmd;
    }
    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
//---------------------------------------------------------

void UndoStack::pop()
{
    if (!curCmd) {
        if (!ScoreLoad::loading()) {
            qWarning("no active command");
        }
        return;
    }
    UndoCommand* cmd = curCmd->removeChild();
    cmd->undo(0);
}

//---------------------------------------------------------
//   rollback
//---------------------------------------------------------

void UndoStack::rollback()
{
    qDebug("==");
    Q_ASSERT(curCmd == 0);
    Q_ASSERT(curIdx > 0);
    int idx = curIdx - 1;
    list[idx]->unwind();
    remove(idx);
}

//---------------------------------------------------------
//   endMacro
//---------------------------------------------------------

void UndoStack::endMacro(bool rollback)
{
    if (curCmd == 0) {
        qWarning("not active");
        return;
    }
    if (rollback) {
        delete curCmd;
    } else {
        // remove redo stack
        while (list.size() > curIdx) {
            UndoCommand* cmd = list.takeLast();
            stateList.pop_back();
            cmd->cleanup(false);        // delete elements for which UndoCommand() holds ownership
            delete cmd;
        }
        list.append(curCmd);
        stateList.push_back(nextState++);
        ++curIdx;
    }
    curCmd = 0;
}

//---------------------------------------------------------
//   reopen
//---------------------------------------------------------

void UndoStack::reopen()
{
    qDebug("curIdx %d size %d", curIdx, list.size());
    Q_ASSERT(curCmd == 0);
    Q_ASSERT(curIdx > 0);
    --curIdx;
    curCmd = list.takeAt(curIdx);
    stateList.erase(stateList.begin() + curIdx);
    for (auto i : curCmd->commands()) {
        qDebug("   <%s>", i->name());
    }
}

//---------------------------------------------------------
//   setClean
//---------------------------------------------------------

void UndoStack::setClean()
{
    cleanState = state();
}

//---------------------------------------------------------
//   undo
//---------------------------------------------------------

void UndoStack::undo(EditData* ed)
{
    qCDebug(undoRedo) << "===";
    // Are we currently editing text?
    if (ed && ed->element && ed->element->isTextBase()) {
        TextEditData* ted = static_cast<TextEditData*>(ed->getData(ed->element));
        if (ted && ted->startUndoIdx == curIdx) {
            // No edits to undo, so do nothing
            return;
        }
    }
    if (curIdx) {
        --curIdx;
        Q_ASSERT(curIdx >= 0);
        list[curIdx]->undo(ed);
    }
}

//---------------------------------------------------------
//   redo
//---------------------------------------------------------

void UndoStack::redo(EditData* ed)
{
    qCDebug(undoRedo) << "===";
    if (canRedo()) {
        list[curIdx++]->redo(ed);
    }
}

//---------------------------------------------------------
//   UndoMacro
//---------------------------------------------------------

void UndoMacro::fillSelectionInfo(SelectionInfo& info, const Selection& sel)
{
    info.staffStart = info.staffEnd = -1;
    info.elements.clear();

    if (sel.isList()) {
        for (Element* e : sel.elements()) {
            if (e->isNote() || e->isChordRest() || (e->isTextBase() && !e->isInstrumentName()) || e->isFretDiagram()) {
                info.elements.push_back(e);
            } else {
                // don't remember selection we are unable to restore
                info.elements.clear();
                return;
            }
        }
    } else if (sel.isRange()) {
        info.staffStart = sel.staffStart();
        info.staffEnd = sel.staffEnd();
        info.tickStart = sel.tickStart();
        info.tickEnd = sel.tickEnd();
    }
}

void UndoMacro::applySelectionInfo(const SelectionInfo& info, Selection& sel)
{
    if (!info.elements.empty()) {
        for (Element* e : info.elements) {
            sel.add(e);
        }
    } else if (info.staffStart != -1) {
        sel.setRangeTicks(info.tickStart, info.tickEnd, info.staffStart, info.staffEnd);
    }
}

UndoMacro::UndoMacro(Score* s) :
    undoInputState(s->inputState()), score(s)
{
    fillSelectionInfo(undoSelectionInfo, s->selection());
}

void UndoMacro::undo(EditData* ed)
{
    redoInputState = score->inputState();
    fillSelectionInfo(redoSelectionInfo, score->selection());
    score->deselectAll();

    // Undo for child commands.
    UndoCommand::undo(ed);

    score->setInputState(undoInputState);
    if (undoSelectionInfo.isValid()) {
        score->deselectAll();
        applySelectionInfo(undoSelectionInfo, score->selection());
    }
}

void UndoMacro::redo(EditData* ed)
{
    undoInputState = score->inputState();
    fillSelectionInfo(undoSelectionInfo, score->selection());
    score->deselectAll();

    // Redo for child commands.
    UndoCommand::redo(ed);

    score->setInputState(redoInputState);
    if (redoSelectionInfo.isValid()) {
        score->deselectAll();
        applySelectionInfo(redoSelectionInfo, score->selection());
    }
}

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

//---------------------------------------------------------
//   CloneVoice
//---------------------------------------------------------

CloneVoice::CloneVoice(Segment* _sf, const Fraction& _lTick, Segment* _d, int _strack, int _dtrack, int _otrack,
                       bool _linked)
{
    sf      = _sf;            // first source segment
    lTick   = _lTick;         // last tick to clone
    d       = _d;             // first destination segment
    strack  = _strack;
    dtrack  = _dtrack;
    otrack  = _otrack;        // old source track if -1 delete voice in strack after copy
    linked  = _linked;        // if true  add elements in destination segment only
                              // if false add elements in every linked staff
}

void CloneVoice::undo(EditData*)
{
    Score* s = d->score();
    Fraction ticks = d->tick() + lTick - sf->tick();
    int sTrack = otrack == -1 ? dtrack : otrack;   // use the correct source / destination if deleting the source
    int dTrack = otrack == -1 ? strack : dtrack;

    // Clear destination voice (in case of not linked and otrack = -1 we would delete our source
    if (otrack != -1 && linked) {
        for (Segment* seg = d; seg && seg->tick() < ticks; seg = seg->next1()) {
            Element* el = seg->element(dTrack);
            if (el && el->isChordRest()) {
                el->unlink();
                seg->setElement(dTrack, 0);
            }
        }
    }

    if (otrack == -1 && !linked) {
        // On the first run get going the undo redo action for adding/deleting elements and slurs
        if (first) {
            s->cloneVoice(sTrack, dTrack, sf, ticks, linked);
            auto spanners = s->spannerMap().findOverlapping(sf->tick().ticks(), lTick.ticks());
            for (auto i = spanners.begin(); i < spanners.end(); i++) {
                Spanner* sp = i->value;
                if (sp->isSlur() && (sp->track() == sTrack || sp->track2() == sTrack)) {
                    s->undoRemoveElement(sp);
                }
            }
            for (Segment* seg = d; seg && seg->tick() < ticks; seg = seg->next1()) {
                Element* el = seg->element(sTrack);
                if (el && el->isChordRest()) {
                    s->undoRemoveElement(el);
                }
            }
        }
        // Set rests if first voice in a staff
        if (!(sTrack % VOICES)) {
            s->setRest(d->tick(), sTrack, ticks, false, 0);
        }
    } else {
        s->cloneVoice(sTrack, dTrack, sf, ticks, linked);
        if (!linked && !(dTrack % VOICES)) {
            s->setRest(d->tick(), dTrack, ticks, false, 0);
        }
    }

    first = false;
}

void CloneVoice::redo(EditData*)
{
    Score* s = d->score();
    Fraction ticks = d->tick() + lTick - sf->tick();

    // Clear destination voice (in case of not linked and otrack = -1 we would delete our source
    if (otrack != -1 && linked) {
        for (Segment* seg = d; seg && seg->tick() < ticks; seg = seg->next1()) {
            Element* el = seg->element(dtrack);
            if (el && el->isChordRest()) {
                el->unlink();
                seg->setElement(dtrack, 0);
            }
        }
    }

    if (otrack == -1 && !linked) {
        // On the first run get going the undo redo action for adding/deleting elements and slurs
        if (first) {
            s->cloneVoice(strack, dtrack, sf, ticks, linked);
            auto spanners = s->spannerMap().findOverlapping(sf->tick().ticks(), lTick.ticks());
            for (auto i = spanners.begin(); i < spanners.end(); i++) {
                Spanner* sp = i->value;
                if (sp->isSlur() && (sp->track() == strack || sp->track2() == strack)) {
                    s->undoRemoveElement(sp);
                }
            }
            for (Segment* seg = d; seg && seg->tick() < ticks; seg = seg->next1()) {
                Element* el = seg->element(strack);
                if (el && el->isChordRest()) {
                    s->undoRemoveElement(el);
                }
            }
        }
        // Set rests if first voice in a staff
        if (!(strack % VOICES)) {
            s->setRest(d->tick(), strack, ticks, false, 0);
        }
    } else {
        s->cloneVoice(strack, dtrack, sf, ticks, linked, first);
    }
}

//---------------------------------------------------------
//   AddElement
//---------------------------------------------------------

AddElement::AddElement(Element* e)
{
    element = e;
}

//---------------------------------------------------------
//   AddElement::cleanup
//---------------------------------------------------------

void AddElement::cleanup(bool undo)
{
    if (!undo) {
        delete element;
        element = 0;
    }
}

//---------------------------------------------------------
//   undoRemoveTuplet
//---------------------------------------------------------

static void undoRemoveTuplet(DurationElement* cr)
{
    if (cr->tuplet()) {
        cr->tuplet()->remove(cr);
        if (cr->tuplet()->elements().empty()) {
            undoRemoveTuplet(cr->tuplet());
        }
    }
}

//---------------------------------------------------------
//   undoAddTuplet
//---------------------------------------------------------

static void undoAddTuplet(DurationElement* cr)
{
    if (cr->tuplet()) {
        cr->tuplet()->add(cr);
        if (cr->tuplet()->elements().size() == 1) {
            undoAddTuplet(cr->tuplet());
        }
    }
}

//---------------------------------------------------------
//   endUndoRedo
//---------------------------------------------------------

void AddElement::endUndoRedo(bool isUndo) const
{
    if (element->isChordRest()) {
        if (isUndo) {
            undoRemoveTuplet(toChordRest(element));
        } else {
            undoAddTuplet(toChordRest(element));
        }
    } else if (element->isClef()) {
        element->triggerLayout();
        element->score()->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
    } else if (element->isKeySig()) {
        element->triggerLayout();
        element->score()->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
    }
}

//---------------------------------------------------------
//   undo
//---------------------------------------------------------

void AddElement::undo(EditData*)
{
    if (!element->isTuplet()) {
        element->score()->removeElement(element);
    }
    endUndoRedo(true);
}

//---------------------------------------------------------
//   redo
//---------------------------------------------------------

void AddElement::redo(EditData*)
{
    if (!element->isTuplet()) {
        element->score()->addElement(element);
    }
    endUndoRedo(false);
}

//---------------------------------------------------------
//   name
//---------------------------------------------------------

const char* AddElement::name() const
{
    static char buffer[64];
    if (element->isTextBase()) {
        snprintf(buffer, 64, "Add:    %s <%s> %p", element->name(),
                 qPrintable(toTextBase(element)->plainText()), element);
    } else if (element->isSegment()) {
        snprintf(buffer, 64, "Add:    <%s-%s> %p", element->name(), toSegment(element)->subTypeName(), element);
    } else {
        snprintf(buffer, 64, "Add:    <%s> %p", element->name(), element);
    }
    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
//---------------------------------------------------------

static void removeNote(const Note* note)
{
    Score* score = note->score();
    if (note->tieFor() && note->tieFor()->endNote()) {
        score->undo(new RemoveElement(note->tieFor()));
    }
    if (note->tieBack()) {
        score->undo(new RemoveElement(note->tieBack()));
    }
    for (Spanner* s : note->spannerBack()) {
        score->undo(new RemoveElement(s));
    }
    for (Spanner* s : note->spannerFor()) {
        score->undo(new RemoveElement(s));
    }
}

//---------------------------------------------------------
//   RemoveElement
//---------------------------------------------------------

RemoveElement::RemoveElement(Element* e)
{
    element = e;

    Score* score = element->score();
    if (element->isChordRest()) {
        ChordRest* cr = toChordRest(element);
        if (cr->tuplet() && cr->tuplet()->elements().size() <= 1) {
            score->undo(new RemoveElement(cr->tuplet()));
        }
        if (e->isChord()) {
            Chord* chord = toChord(e);
            // remove tremolo between 2 notes
            if (chord->tremolo()) {
                Tremolo* tremolo = chord->tremolo();
                if (tremolo->twoNotes()) {
                    score->undo(new RemoveElement(tremolo));
                }
            }
            for (const Note* note : chord->notes()) {
                removeNote(note);
            }
        }
    } else if (element->isNote()) {
        // Removing an individual note within a chord
        const Note* note = toNote(element);
        removeNote(note);
    }
}

//---------------------------------------------------------
//   RemoveElement::cleanup
//---------------------------------------------------------

void RemoveElement::cleanup(bool undo)
{
    if (undo) {
        delete element;
        element = 0;
    }
}

//---------------------------------------------------------
//   undo
//---------------------------------------------------------

void RemoveElement::undo(EditData*)
{
    if (!element->isTuplet()) {
        element->score()->addElement(element);
    }
    if (element->isChordRest()) {
        if (element->isChord()) {
            Chord* chord = toChord(element);
            for (Note* note : chord->notes()) {
                note->connectTiedNotes();
            }
        }
        undoAddTuplet(toChordRest(element));
    } else if (element->isClef()) {
        element->score()->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
    } else if (element->isKeySig()) {
        element->score()->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
    }
}

//---------------------------------------------------------
//   redo
//---------------------------------------------------------

void RemoveElement::redo(EditData*)
{
    if (!element->isTuplet()) {
        element->score()->removeElement(element);
    }
    if (element->isChordRest()) {
        undoRemoveTuplet(toChordRest(element));
        if (element->isChord()) {
            Chord* chord = toChord(element);
            for (Note* note : chord->notes()) {
                note->disconnectTiedNotes();
            }
        }
    } else if (element->isClef()) {
        element->score()->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
    } else if (element->isKeySig()) {
        element->score()->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
    }
}

//---------------------------------------------------------
//   name
//---------------------------------------------------------

const char* RemoveElement::name() const
{
    static char buffer[64];
    if (element->isTextBase()) {
        snprintf(buffer, 64, "Remove: %s <%s> %p", element->name(),
                 qPrintable(toTextBase(element)->plainText()), element);
    } else if (element->isSegment()) {
        snprintf(buffer, 64, "Remove: <%s-%s> %p", element->name(), toSegment(element)->subTypeName(), element);
    } else {
        snprintf(buffer, 64, "Remove: %s %p", element->name(), element);
    }
    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
//---------------------------------------------------------

InsertPart::InsertPart(Part* p, int i)
{
    part = p;
    idx  = i;
}

void InsertPart::undo(EditData*)
{
    part->score()->removePart(part);
}

void InsertPart::redo(EditData*)
{
    part->score()->insertPart(part, idx);
}

//---------------------------------------------------------
//   RemovePart
//---------------------------------------------------------

RemovePart::RemovePart(Part* p, int i)
{
    part = p;
    idx  = i;
}

void RemovePart::undo(EditData*)
{
    part->score()->insertPart(part, idx);
}

void RemovePart::redo(EditData*)
{
    part->score()->removePart(part);
}

//---------------------------------------------------------
//   InsertStaff
//---------------------------------------------------------

InsertStaff::InsertStaff(Staff* p, int _ridx)
{
    staff = p;
    ridx  = _ridx;
}

void InsertStaff::undo(EditData*)
{
    staff->score()->removeStaff(staff);
}

void InsertStaff::redo(EditData*)
{
    staff->score()->insertStaff(staff, ridx);
}

//---------------------------------------------------------
//   RemoveStaff
//---------------------------------------------------------

RemoveStaff::RemoveStaff(Staff* p)
{
    staff = p;
    ridx  = staff->rstaff();
}

void RemoveStaff::undo(EditData*)
{
    staff->score()->insertStaff(staff, ridx);
}

void RemoveStaff::redo(EditData*)
{
    staff->score()->removeStaff(staff);
}

//---------------------------------------------------------
//   InsertMStaff
//---------------------------------------------------------

InsertMStaff::InsertMStaff(Measure* m, MStaff* ms, int i)
{
    measure = m;
    mstaff  = ms;
    idx     = i;
}

void InsertMStaff::undo(EditData*)
{
    measure->removeMStaff(mstaff, idx);
}

void InsertMStaff::redo(EditData*)
{
    measure->insertMStaff(mstaff, idx);
}

//---------------------------------------------------------
//   RemoveMStaff
//---------------------------------------------------------

RemoveMStaff::RemoveMStaff(Measure* m, MStaff* ms, int i)
{
    measure = m;
    mstaff  = ms;
    idx     = i;
}

void RemoveMStaff::undo(EditData*)
{
    measure->insertMStaff(mstaff, idx);
}

void RemoveMStaff::redo(EditData*)
{
    measure->removeMStaff(mstaff, idx);
}

//---------------------------------------------------------
//   SortStaves
//---------------------------------------------------------

SortStaves::SortStaves(Score* s, QList<int> l)
{
    score = s;

    for (int i=0; i < l.size(); i++) {
        rlist.append(l.indexOf(i));
    }
    list  = l;
}

void SortStaves::redo(EditData*)
{
    score->sortStaves(list);
}

void SortStaves::undo(EditData*)
{
    score->sortStaves(rlist);
}

//---------------------------------------------------------
//   ChangePitch
//---------------------------------------------------------

ChangePitch::ChangePitch(Note* _note, int _pitch, int _tpc1, int _tpc2)
{
    note  = _note;
    pitch = _pitch;
    tpc1  = _tpc1;
    tpc2  = _tpc2;
}

void ChangePitch::flip(EditData*)
{
    int f_pitch = note->pitch();
    int f_tpc1  = note->tpc1();
    int f_tpc2  = note->tpc2();
    // do not change unless necessary
    if (f_pitch == pitch && f_tpc1 == tpc1 && f_tpc2 == tpc2) {
        return;
    }

    note->setPitch(pitch, tpc1, tpc2);
    pitch = f_pitch;
    tpc1  = f_tpc1;
    tpc2  = f_tpc2;

    note->triggerLayout();
}

//---------------------------------------------------------
//   ChangeFretting
//
//    To use with tablatures to force a specific note fretting;
//    Pitch, string and fret must be changed all together; otherwise,
//    if they are not consistent among themselves, the refretting algorithm may re-assign
//    fret and string numbers for (potentially) all the notes of all the chords of a segment.
//---------------------------------------------------------

ChangeFretting::ChangeFretting(Note* _note, int _pitch, int _string, int _fret, int _tpc1, int _tpc2)
{
    note  = _note;
    pitch = _pitch;
    string= _string;
    fret  = _fret;
    tpc1  = _tpc1;
    tpc2  = _tpc2;
}

void ChangeFretting::flip(EditData*)
{
    int f_pitch = note->pitch();
    int f_string= note->string();
    int f_fret  = note->fret();
    int f_tpc1  = note->tpc1();
    int f_tpc2  = note->tpc2();
    // do not change unless necessary
    if (f_pitch == pitch && f_string == string && f_fret == fret && f_tpc1 == tpc1 && f_tpc2 == tpc2) {
        return;
    }

    note->setPitch(pitch, tpc1, tpc2);
    note->setString(string);
    note->setFret(fret);
    pitch = f_pitch;
    string= f_string;
    fret  = f_fret;
    tpc1  = f_tpc1;
    tpc2  = f_tpc2;
    note->triggerLayout();
}

//---------------------------------------------------------
//   ChangeElement
//---------------------------------------------------------

ChangeElement::ChangeElement(Element* oe, Element* ne)
{
    oldElement = oe;
    newElement = ne;
}

void ChangeElement::flip(EditData*)
{
    const LinkedElements* links = oldElement->links();
    if (links) {
        newElement->linkTo(oldElement);
        oldElement->unlink();
    }

    Score* score = oldElement->score();
    if (!score->selection().isRange()) {
        if (oldElement->selected()) {
            score->deselect(oldElement);
        }
        if (newElement->selected()) {
            score->select(newElement, SelectType::ADD);
        }
    }
    if (oldElement->parent() == 0) {
        score->removeElement(oldElement);
        score->addElement(newElement);
    } else {
        oldElement->parent()->change(oldElement, newElement);
    }

    if (newElement->isKeySig()) {
        KeySig* ks = toKeySig(newElement);
        if (!ks->generated()) {
            ks->staff()->setKey(ks->tick(), ks->keySigEvent());
        }
    } else if (newElement->isDynamic()) {
        newElement->score()->addLayoutFlags(LayoutFlag::FIX_PITCH_VELO);
    } else if (newElement->isTempoText()) {
        TempoText* t = toTempoText(oldElement);
        score->setTempo(t->segment(), t->tempo());
    }
//      if (newElement->isSegmentFlag()) {
    if (newElement->isSpannerSegment()) {
        SpannerSegment* os = toSpannerSegment(oldElement);
        SpannerSegment* ns = toSpannerSegment(newElement);
        if (os->system()) {
            os->system()->remove(os);
        }
        if (ns->system()) {
            ns->system()->add(ns);
        }
    }
    qSwap(oldElement, newElement);
    oldElement->triggerLayout();
    newElement->triggerLayout();
    // score->setLayoutAll();
}

//---------------------------------------------------------
//   InsertStaves
//---------------------------------------------------------

InsertStaves::InsertStaves(Measure* m, int _a, int _b)
{
    measure = m;
    a       = _a;
    b       = _b;
}

void InsertStaves::undo(EditData*)
{
    measure->removeStaves(a, b);
}

void InsertStaves::redo(EditData*)
{
    measure->insertStaves(a, b);
}

//---------------------------------------------------------
//   RemoveStaves
//---------------------------------------------------------

RemoveStaves::RemoveStaves(Measure* m, int _a, int _b)
{
    measure = m;
    a       = _a;
    b       = _b;
}

void RemoveStaves::undo(EditData*)
{
    measure->insertStaves(a, b);
}

void RemoveStaves::redo(EditData*)
{
    measure->removeStaves(a, b);
}

//---------------------------------------------------------
//   ChangeKeySig
//---------------------------------------------------------

ChangeKeySig::ChangeKeySig(KeySig* k, KeySigEvent newKeySig, bool sc, bool addEvtToStaff) :
    keysig(k), ks(newKeySig), showCourtesy(sc), evtInStaff(addEvtToStaff)
{}

void ChangeKeySig::flip(EditData*)
{
    Segment* segment = keysig->segment();
    Fraction tick = segment->tick();
    Staff* staff = keysig->staff();

    const bool curEvtInStaff = (staff->currentKeyTick(tick) == tick);
    KeySigEvent curKey = keysig->keySigEvent();
    const bool curShowCourtesy = keysig->showCourtesy();

    keysig->setKeySigEvent(ks);
    keysig->setShowCourtesy(showCourtesy);

    // Add/remove the corresponding key events, if appropriate.
    if (evtInStaff) {
        staff->setKey(tick, ks);     // replace
    } else if (curEvtInStaff) {
        staff->removeKey(tick);     // if nothing to add instead, just remove.
    }
    // If no keysig event corresponds to the key signature then this keysig
    // is probably generated. Otherwise it is probably added manually.
    // Set segment flags according to this, layout will change it if needed.
    segment->setEnabled(evtInStaff);
    segment->setHeader(!evtInStaff && segment->rtick() == Fraction(0,1));

    showCourtesy = curShowCourtesy;
    ks           = curKey;
    evtInStaff   = curEvtInStaff;
    keysig->triggerLayout();
    keysig->score()->setLayout(keysig->staff()->nextKeyTick(tick), keysig->staffIdx());
}

//---------------------------------------------------------
//   ChangeMeasureLen
//---------------------------------------------------------

ChangeMeasureLen::ChangeMeasureLen(Measure* m, Fraction l)
{
    measure     = m;
    len         = l;
}

void ChangeMeasureLen::flip(EditData*)
{
    Fraction oLen = measure->ticks();

    //
    // move EndBarLine and TimeSigAnnounce
    // to end of measure:
    //

    std::list<Segment*> sl;
    for (Segment* s = measure->first(); s; s = s->next()) {
        if (!s->isEndBarLineType() && !s->isTimeSigAnnounceType()) {
            continue;
        }
        s->setRtick(len);
        sl.push_back(s);
        measure->remove(s);
    }
    measure->setTicks(len);
    measure->score()->fixTicks();
    len = oLen;
}

//---------------------------------------------------------
//   TransposeHarmony
//---------------------------------------------------------

TransposeHarmony::TransposeHarmony(Harmony* h, int rtpc, int btpc)
{
    harmony = h;
    rootTpc = rtpc;
    baseTpc = btpc;
}

void TransposeHarmony::flip(EditData*)
{
    harmony->realizedHarmony().setDirty(true);   //harmony should be re-realized after transposition
    int baseTpc1 = harmony->baseTpc();
    int rootTpc1 = harmony->rootTpc();
    harmony->setBaseTpc(baseTpc);
    harmony->setRootTpc(rootTpc);
    harmony->setXmlText(harmony->harmonyName());
    harmony->render();
    rootTpc = rootTpc1;
    baseTpc = baseTpc1;
}

//---------------------------------------------------------
//   ExchangeVoice
//---------------------------------------------------------

ExchangeVoice::ExchangeVoice(Measure* m, int _val1, int _val2, int _staff)
{
    measure = m;
    val1    = _val1;
    val2    = _val2;
    staff   = _staff;
}

void ExchangeVoice::undo(EditData*)
{
    measure->exchangeVoice(val2, val1, staff);
    measure->checkMultiVoices(staff);
}

void ExchangeVoice::redo(EditData*)
{
    measure->exchangeVoice(val1, val2, staff);
}

//---------------------------------------------------------
//   ChangeInstrumentShort
//---------------------------------------------------------

ChangeInstrumentShort::ChangeInstrumentShort(const Fraction& _tick, Part* p, QList<StaffName> t)
{
    tick = _tick;
    part = p;
    text = t;
}

void ChangeInstrumentShort::flip(EditData*)
{
    QList<StaffName> s = part->shortNames(tick);
    part->setShortNames(text, tick);
    text = s;
    part->score()->setLayoutAll();
}

//---------------------------------------------------------
//   ChangeInstrumentLong
//---------------------------------------------------------

ChangeInstrumentLong::ChangeInstrumentLong(const Fraction& _tick, Part* p, QList<StaffName> t)
{
    tick = _tick;
    part = p;
    text = t;
}

void ChangeInstrumentLong::flip(EditData*)
{
    QList<StaffName> s = part->longNames(tick);
    part->setLongNames(text, tick);
    text = s;
    part->score()->setLayoutAll();
}

//---------------------------------------------------------
//   EditText::undo
//---------------------------------------------------------

void EditText::undo(EditData*)
{
/*      if (!text->styled()) {
            for (int i = 0; i < undoLevel; ++i)
                  text->undo();
            }
      */
    undoRedo();
}

//---------------------------------------------------------
//   EditText::redo
//---------------------------------------------------------

void EditText::redo(EditData*)
{
/*
      if (!text->styled()) {
            for (int i = 0; i < undoLevel; ++i)
                  text->redo();
            }
      */
    undoRedo();
}

//---------------------------------------------------------
//   EditText::undoRedo
//---------------------------------------------------------

void EditText::undoRedo()
{
    QString s = text->xmlText();
    text->setXmlText(oldText);
    oldText = s;
    text->triggerLayout();
}

//---------------------------------------------------------
//   ChangePatch
//---------------------------------------------------------

void ChangePatch::flip(EditData*)
{
    MidiPatch op;
    op.prog          = channel->program();
    op.bank          = channel->bank();
    op.synti         = channel->synti();

    channel->setProgram(patch.prog);
    channel->setBank(patch.bank);
    channel->setSynti(patch.synti);

    patch            = op;

    if (MScore::seq == 0) {
        qWarning("no seq");
        return;
    }

    NPlayEvent event;
    event.setType(ME_CONTROLLER);
    event.setChannel(channel->channel());

    int hbank = (channel->bank() >> 7) & 0x7f;
    int lbank = channel->bank() & 0x7f;

    event.setController(CTRL_HBANK);
    event.setValue(hbank);
    MScore::seq->sendEvent(event);

    event.setController(CTRL_LBANK);
    event.setValue(lbank);
    MScore::seq->sendEvent(event);

    event.setController(CTRL_PROGRAM);
    event.setValue(channel->program());

    score->setInstrumentsChanged(true);

    MScore::seq->sendEvent(event);
}

//---------------------------------------------------------
//   SetUserBankController
//---------------------------------------------------------

void SetUserBankController::flip(EditData*)
{
    bool oldVal = channel->userBankController();
    channel->setUserBankController(val);
    val = oldVal;
}

//---------------------------------------------------------
//   ChangeStaff
//---------------------------------------------------------

ChangeStaff::ChangeStaff(Staff* _staff,  bool _invisible, ClefTypeList _clefType,
                         qreal _userDist, Staff::HideMode _hideMode, bool _showIfEmpty, bool _cutaway, bool hide)
{
    staff       = _staff;
    invisible   = _invisible;
    clefType    = _clefType;
    userDist    = _userDist;
    hideMode    = _hideMode;
    showIfEmpty = _showIfEmpty;
    cutaway     = _cutaway;
    hideSystemBarLine = hide;
}

//---------------------------------------------------------
//   flip
//---------------------------------------------------------

void ChangeStaff::flip(EditData*)
{
    bool invisibleChanged = staff->invisible() != invisible;
    ClefTypeList oldClefType = staff->defaultClefType();
    bool oldInvisible   = staff->invisible();
    qreal oldUserDist   = staff->userDist();
    Staff::HideMode oldHideMode    = staff->hideWhenEmpty();
    bool oldShowIfEmpty = staff->showIfEmpty();
    bool oldCutaway     = staff->cutaway();
    bool hide           = staff->hideSystemBarLine();

    staff->setInvisible(invisible);
    staff->setDefaultClefType(clefType);
    staff->setUserDist(userDist);
    staff->setHideWhenEmpty(hideMode);
    staff->setShowIfEmpty(showIfEmpty);
    staff->setCutaway(cutaway);
    staff->setHideSystemBarLine(hideSystemBarLine);

    invisible   = oldInvisible;
    clefType    = oldClefType;
    userDist    = oldUserDist;
    hideMode    = oldHideMode;
    showIfEmpty = oldShowIfEmpty;
    cutaway     = oldCutaway;
    hideSystemBarLine = hide;

    Score* score = staff->score();
    if (invisibleChanged) {
        int staffIdx = staff->idx();
        for (Measure* m = score->firstMeasure(); m; m = m->nextMeasure()) {
            m->staffLines(staffIdx)->setVisible(!staff->invisible());
        }
    }
    staff->triggerLayout();
    staff->masterScore()->rebuildMidiMapping();
    staff->score()->setPlaylistDirty();
}

//---------------------------------------------------------
//   ChangeStaffType::flip
//---------------------------------------------------------

void ChangeStaffType::flip(EditData*)
{
    StaffType st = *staff->staffType(Fraction(0,1));        // TODO

    staff->setStaffType(Fraction(0,1), staffType);

    staffType = st;

    staff->triggerLayout();
}

//---------------------------------------------------------
//   ChangePart
//---------------------------------------------------------

ChangePart::ChangePart(Part* _part, Instrument* i, const QString& s)
{
    instrument = i;
    part       = _part;
    partName   = s;
}

//---------------------------------------------------------
//   flip
//---------------------------------------------------------

void ChangePart::flip(EditData*)
{
    Instrument* oi = part->instrument();
    QString s      = part->partName();
    part->setInstrument(instrument);
    part->setPartName(partName);

    part->updateHarmonyChannels(false);

    Score* score = part->score();
    score->masterScore()->rebuildMidiMapping();
    score->setInstrumentsChanged(true);
    score->setPlaylistDirty();

    // check if notes need to be updated
    // true if changing into or away from TAB or from one TAB type to another

    score->setLayoutAll();

    partName   = s;
    instrument = oi;
}

//---------------------------------------------------------
//   ChangeStyle
//---------------------------------------------------------

ChangeStyle::ChangeStyle(Score* s, const MStyle& st) :
    score(s), style(st)
{
}

//---------------------------------------------------------
//   flip
//---------------------------------------------------------

void ChangeStyle::flip(EditData*)
{
    MStyle tmp = score->style();

    if (score->styleV(Sid::concertPitch) != style.value(Sid::concertPitch)) {
        score->cmdConcertPitchChanged(style.value(Sid::concertPitch).toBool(), true);
    }
    if (score->styleV(Sid::MusicalSymbolFont) != style.value(Sid::MusicalSymbolFont)) {
        score->setScoreFont(ScoreFont::fontFactory(style.value(Sid::MusicalSymbolFont).toString()));
    }
    score->setStyle(style);
    score->styleChanged();
    style = tmp;
}

//---------------------------------------------------------
//   ChangeStyleVal::flip
//---------------------------------------------------------

void ChangeStyleVal::flip(EditData*)
{
    QVariant v = score->styleV(idx);
    if (v != value) {
        score->style().set(idx, value);
        switch (idx) {
        case Sid::chordExtensionMag:
        case Sid::chordExtensionAdjust:
        case Sid::chordModifierMag:
        case Sid::chordModifierAdjust:
        case Sid::chordDescriptionFile: {
            score->style().chordList()->unload();
            qreal emag = score->styleD(Sid::chordExtensionMag);
            qreal eadjust = score->styleD(Sid::chordExtensionAdjust);
            qreal mmag = score->styleD(Sid::chordModifierMag);
            qreal madjust = score->styleD(Sid::chordModifierAdjust);
            score->style().chordList()->configureAutoAdjust(emag, eadjust, mmag, madjust);
            if (score->styleB(Sid::chordsXmlFile)) {
                score->style().chordList()->read("chords.xml");
            }
            score->style().chordList()->read(score->styleSt(Sid::chordDescriptionFile));
        }
        break;
        default:
            break;
        }
        score->styleChanged();
    }
    value = v;
}

//---------------------------------------------------------
//   ChangePageNumberOffset::flip
//---------------------------------------------------------

void ChangePageNumberOffset::flip(EditData*)
{
    int po = score->pageNumberOffset();

    score->setPageNumberOffset(pageOffset);
    score->setLayoutAll();

    pageOffset = po;
}

//---------------------------------------------------------
//   ChangeChordStaffMove
//---------------------------------------------------------

ChangeChordStaffMove::ChangeChordStaffMove(ChordRest* cr, int v) :
    chordRest(cr), staffMove(v)
{
}

void ChangeChordStaffMove::flip(EditData*)
{
    int v = chordRest->staffMove();
    for (ScoreElement* e : chordRest->linkList()) {
        ChordRest* cr = toChordRest(e);
        cr->setStaffMove(staffMove);
        cr->triggerLayout();
    }
    staffMove = v;
}

//---------------------------------------------------------
//   ChangeVelocity
//---------------------------------------------------------

ChangeVelocity::ChangeVelocity(Note* n, Note::ValueType t, int o) :
    note(n), veloType(t), veloOffset(o)
{
}

void ChangeVelocity::flip(EditData*)
{
    Note::ValueType t = note->veloType();
    int o       = note->veloOffset();
    note->setVeloType(veloType);
    note->setVeloOffset(veloOffset);
    veloType   = t;
    veloOffset = o;
}

//---------------------------------------------------------
//   ChangeMStaffProperties
//---------------------------------------------------------

ChangeMStaffProperties::ChangeMStaffProperties(Measure* m, int i, bool v, bool s) :
    measure(m), staffIdx(i), visible(v), stemless(s)
{
}

//---------------------------------------------------------
//   flip
//---------------------------------------------------------

void ChangeMStaffProperties::flip(EditData*)
{
    bool v = measure->visible(staffIdx);
    bool s = measure->stemless(staffIdx);
    measure->setStaffVisible(staffIdx, visible);
    measure->setStaffStemless(staffIdx, stemless);
    visible    = v;
    stemless = s;
}

//---------------------------------------------------------
//   getCourtesyClefs
//    remember clefs at the end of previous measure
//---------------------------------------------------------

std::vector<Clef*> InsertRemoveMeasures::getCourtesyClefs(Measure* m)
{
    Score* score = m->score();
    std::vector<Clef*> startClefs;
    if (m->prev() && m->prev()->isMeasure()) {
        Measure* prevMeasure = toMeasure(m->prev());
        const Segment* clefSeg = prevMeasure->findSegmentR(SegmentType::Clef | SegmentType::HeaderClef,
                                                           prevMeasure->ticks());
        if (clefSeg) {
            for (int st = 0; st < score->nstaves(); ++st) {
                Element* clef = clefSeg->element(staff2track(st));
                if (clef && clef->isClef()) {
                    startClefs.push_back(toClef(clef));
                }
            }
        }
    }
    return startClefs;
}

//---------------------------------------------------------
//   insertMeasures
//---------------------------------------------------------

void InsertRemoveMeasures::insertMeasures()
{
    Score* score = fm->score();
    QList<Clef*> clefs;
    std::vector<Clef*> prevMeasureClefs;
    QList<KeySig*> keys;
    Segment* fs = 0;
    Segment* ls = 0;
    if (fm->isMeasure()) {
        score->setPlaylistDirty();
        fs = toMeasure(fm)->first();
        ls = toMeasure(lm)->last();
        for (Segment* s = fs; s && s != ls; s = s->next1()) {
            if (!s->enabled()
                || !(s->segmentType() & (SegmentType::Clef | SegmentType::HeaderClef | SegmentType::KeySig))) {
                continue;
            }
            for (int track = 0; track < score->ntracks(); track += VOICES) {
                Element* e = s->element(track);
                if (!e || e->generated()) {
                    continue;
                }
                if (e->isClef()) {
                    clefs.append(toClef(e));
                } else if (e->isKeySig()) {
                    keys.append(toKeySig(e));
                }
            }
        }
        prevMeasureClefs = getCourtesyClefs(toMeasure(fm));
    }
    score->measures()->insert(fm, lm);

    if (fm->isMeasure()) {
        score->fixTicks();
        score->insertTime(fm->tick(), lm->endTick() - fm->tick());

        // move ownership of Instrument back to part
        for (Segment* s = fs; s && s != ls; s = s->next1()) {
            for (Element* e : s->annotations()) {
                if (e->isInstrumentChange()) {
                    e->part()->setInstrument(toInstrumentChange(e)->instrument(), s->tick());
                }
            }
        }
        for (Clef* clef : prevMeasureClefs) {
            clef->staff()->setClef(clef);
        }
        for (Clef* clef : clefs) {
            clef->staff()->setClef(clef);
        }
        for (KeySig* key : keys) {
            key->staff()->setKey(key->segment()->tick(), key->keySigEvent());
        }
    }

    score->setLayoutAll();

    //
    // connect ties
    //

    if (!fm->isMeasure() || !fm->prevMeasure()) {
        return;
    }
    Measure* m = fm->prevMeasure();
    for (Segment* seg = m->first(); seg; seg = seg->next()) {
        for (int track = 0; track < score->ntracks(); ++track) {
            Element* e = seg->element(track);
            if (e == 0 || !e->isChord()) {
                continue;
            }
            Chord* chord = toChord(e);
            foreach (Note* n, chord->notes()) {
                Tie* tie = n->tieFor();
                if (!tie) {
                    continue;
                }
                if (!tie->endNote() || tie->endNote()->chord()->segment()->measure() != m) {
                    Note* nn = searchTieNote(n);
                    if (nn) {
                        tie->setEndNote(nn);
                        nn->setTieBack(tie);
                    }
                }
            }
        }
    }
}

//---------------------------------------------------------
//   removeMeasures
//---------------------------------------------------------

void InsertRemoveMeasures::removeMeasures()
{
    Score* score = fm->score();

    Fraction tick1 = fm->tick();
    Fraction tick2 = lm->endTick();

    QList<System*> systemList;
    for (MeasureBase* mb = lm;; mb = mb->prev()) {
        System* system = mb->system();
        if (system) {
            if (!systemList.contains(system)) {
                systemList.push_back(system);
            }
            system->removeMeasure(mb);
        }
        if (mb == fm) {
            break;
        }
    }
    score->measures()->remove(fm, lm);

    score->fixTicks();
    if (fm->isMeasure()) {
        score->setPlaylistDirty();

        // check if there is a clef at the end of last measure
        // remove clef from staff cleflist

        if (lm->isMeasure()) {
            Measure* m = toMeasure(lm);
            Segment* s = m->findSegment(SegmentType::Clef, tick2);
            if (s) {
                for (Element* e : s->elist()) {
                    Clef* clef = toClef(e);
                    if (clef) {
                        score->staff(clef->staffIdx())->removeClef(clef);
                    }
                }
            }
        }

        // remember clefs at the end of previous measure
        const auto clefs = getCourtesyClefs(toMeasure(fm));

        if (score->firstMeasure()) {
            score->insertTime(tick1, -(tick2 - tick1));
        }

        // Restore clefs that were backed up. Events for them could be lost
        // as a result of the recent insertTime() call.
        for (Clef* clef : clefs) {
            clef->staff()->setClef(clef);
        }

        for (Spanner* sp : score->unmanagedSpanners()) {
            if ((sp->tick() >= tick1 && sp->tick() < tick2) || (sp->tick2() >= tick1 && sp->tick2() < tick2)) {
                sp->removeUnmanaged();
            }
        }
        score->connectTies(true);       // ??
    }

    // remove empty systems

    for (System* s : systemList) {
        if (s->measures().empty()) {
            Page* page = s->page();
            if (page) {
                // erase system from page
                QList<System*>& sl = page->systems();
                auto i = std::find(sl.begin(), sl.end(), s);
                if (i != sl.end()) {
                    sl.erase(i);
                }
                // erase system from score
                auto k = std::find(score->systems().begin(), score->systems().end(), s);
                if (k != score->systems().end()) {
                    score->systems().erase(k);
                }
                // finally delete system
                score->deleteLater(s);
            }
        }
    }

    score->setLayoutAll();
}

//---------------------------------------------------------
//   AddExcerpt::undo
//---------------------------------------------------------

void AddExcerpt::undo(EditData*)
{
    excerpt->oscore()->removeExcerpt(excerpt);
}

//---------------------------------------------------------
//   AddExcerpt::redo
//---------------------------------------------------------

void AddExcerpt::redo(EditData*)
{
    excerpt->oscore()->addExcerpt(excerpt);
}

//---------------------------------------------------------
//   RemoveExcerpt::undo()
//---------------------------------------------------------

void RemoveExcerpt::undo(EditData*)
{
    excerpt->oscore()->addExcerpt(excerpt);
}

//---------------------------------------------------------
//   RemoveExcerpt::redo()
//---------------------------------------------------------

void RemoveExcerpt::redo(EditData*)
{
    excerpt->oscore()->removeExcerpt(excerpt);
}

//---------------------------------------------------------
//   SwapExcerpt::flip
//---------------------------------------------------------

void SwapExcerpt::flip(EditData*)
{
    score->excerpts().swap(pos1, pos2);
    score->setExcerptsChanged(true);
}

//---------------------------------------------------------
//   ChangeExcerptTitle::flip
//---------------------------------------------------------

void ChangeExcerptTitle::flip(EditData*)
{
    QString s = title;
    title = excerpt->title();
    excerpt->setTitle(s);
    excerpt->oscore()->setExcerptsChanged(true);
}

//---------------------------------------------------------
//   flip
//---------------------------------------------------------

void ChangeBend::flip(EditData*)
{
    QList<PitchValue> pv = bend->points();
    bend->score()->addRefresh(bend->canvasBoundingRect());
    bend->setPoints(points);
    points = pv;
    bend->layout();
    bend->score()->addRefresh(bend->canvasBoundingRect());
}

//---------------------------------------------------------
//   flip
//---------------------------------------------------------

void ChangeTremoloBar::flip(EditData*)
{
    QList<PitchValue> pv = bend->points();
    bend->setPoints(points);
    points = pv;
}

//---------------------------------------------------------
//   ChangeNoteEvents::flip
//---------------------------------------------------------

void ChangeNoteEvents::flip(EditData*)
{
/*TODO:      QList<NoteEvent*> e = chord->playEvents();
      chord->setPlayEvents(events);
      events = e;
      */
}

//---------------------------------------------------------
//   ChangeNoteEventList::flip
//---------------------------------------------------------

void ChangeNoteEventList::flip(EditData*)
{
    note->score()->setPlaylistDirty();
    // Get copy of current list.
    NoteEventList nel = note->playEvents();
    // Replace current copy with new list.
    note->setPlayEvents(newEvents);
    // Save copy of replaced list.
    newEvents = nel;
    // Get a copy of the current playEventType.
    PlayEventType petval = note->chord()->playEventType();
    // Replace current setting with new setting.
    note->chord()->setPlayEventType(newPetype);
    // Save copy of old setting.
    newPetype = petval;
}

//---------------------------------------------------------
//   ChangeNoteEventList::flip
//---------------------------------------------------------

void ChangeChordPlayEventType::flip(EditData*)
{
    chord->score()->setPlaylistDirty();
    // Flips data between NoteEventList's.
    size_t n = chord->notes().size();
    for (size_t i = 0; i < n; ++i) {
        Note* note = chord->notes()[i];
        note->playEvents().swap(events[int(i)]);
    }
    // Flips PlayEventType between chord and undo.
    PlayEventType curPetype = chord->playEventType();
    chord->setPlayEventType(petype);
    petype = curPetype;
}

//---------------------------------------------------------
//   ChangeInstrument::flip
//---------------------------------------------------------

void ChangeInstrument::flip(EditData*)
{
    Part* part = is->staff()->part();
    Fraction tickStart = is->segment()->tick();
    Instrument* oi = is->instrument();    //new Instrument(*is->instrument());

    // set instrument in both part and instrument change element
    is->setInstrument(instrument);        //*instrument
    part->setInstrument(instrument, tickStart);

    // update score
    is->masterScore()->rebuildMidiMapping();
    is->masterScore()->updateChannel();
    is->score()->setInstrumentsChanged(true);
    is->triggerLayoutAll();

    // remember original instrument
    instrument = oi;
}

//---------------------------------------------------------
//   flip
//---------------------------------------------------------

void SwapCR::flip(EditData*)
{
    Segment* s1 = cr1->segment();
    Segment* s2 = cr2->segment();
    int track = cr1->track();

    Element* cr = s1->element(track);
    s1->setElement(track, s2->element(track));
    s2->setElement(track, cr);
    cr1->score()->setLayout(s1->tick(), cr1->staffIdx(), cr1);
    cr1->score()->setLayout(s2->tick(), cr1->staffIdx(), cr1);
}

//---------------------------------------------------------
//   ChangeClefType
//---------------------------------------------------------

ChangeClefType::ChangeClefType(Clef* c, ClefType cl, ClefType tc)
{
    clef            = c;
    concertClef     = cl;
    transposingClef = tc;
}

//---------------------------------------------------------
//   ChangeClefType::flip
//---------------------------------------------------------

void ChangeClefType::flip(EditData*)
{
    ClefType ocl = clef->concertClef();
    ClefType otc = clef->transposingClef();

    clef->setConcertClef(concertClef);
    clef->setTransposingClef(transposingClef);

    clef->staff()->setClef(clef);
    Segment* segment = clef->segment();
    updateNoteLines(segment, clef->track());
    clef->triggerLayoutAll();        // TODO: reduce layout to clef range

    concertClef     = ocl;
    transposingClef = otc;
    // layout the clef to align the currentClefType with the actual one immediately
    clef->layout();
}

//---------------------------------------------------------
//   flip
//---------------------------------------------------------
#if 0 // MoveStaff is commented out in mscore/instrwidget.cpp, not used anywhere else
void MoveStaff::flip(EditData*)
{
    Part* oldPart = staff->part();
    int idx = staff->rstaff();
    oldPart->removeStaff(staff);
    part->insertStaff(staff, rstaff);
    part = oldPart;
    rstaff = idx;
    staff->score()->setLayoutAll();
}

#endif

//---------------------------------------------------------
//   ChangeProperty::flip
//---------------------------------------------------------

void ChangeProperty::flip(EditData*)
{
    qCDebug(undoRedo) << element->name() << int(id) << "(" << propertyName(id) << ")" << element->getProperty(id)
                      << "->" << property;

    QVariant v       = element->getProperty(id);
    PropertyFlags ps = element->propertyFlags(id);

    element->setProperty(id, property);
    element->setPropertyFlags(id, flags);
    property = v;
    flags = ps;
}

//---------------------------------------------------------
//   ChangeBracketProperty::flip
//---------------------------------------------------------

void ChangeBracketProperty::flip(EditData* ed)
{
    element = staff->brackets()[level];
    ChangeProperty::flip(ed);
    level = toBracketItem(element)->column();
}

//---------------------------------------------------------
//   ChangeMetaText::flip
//---------------------------------------------------------

void ChangeMetaText::flip(EditData*)
{
    QString s = score->metaTag(id);
    score->setMetaTag(id, text);
    text = s;
}

//---------------------------------------------------------
//   ChangeSynthesizerState::flip
//---------------------------------------------------------

void ChangeSynthesizerState::flip(EditData*)
{
    std::swap(state, score->_synthesizerState);
}

void AddBracket::redo(EditData*)
{
    staff->setBracketType(level, type);
    staff->setBracketSpan(level, span);
    staff->triggerLayout();
}

void AddBracket::undo(EditData*)
{
    staff->setBracketType(level, BracketType::NO_BRACKET);
    staff->triggerLayout();
}

void RemoveBracket::redo(EditData*)
{
    staff->setBracketType(level, BracketType::NO_BRACKET);
    staff->triggerLayout();
}

void RemoveBracket::undo(EditData*)
{
    staff->setBracketType(level, type);
    staff->setBracketSpan(level, span);
    staff->triggerLayout();
}

//---------------------------------------------------------
//   ChangeSpannerElements
//---------------------------------------------------------

void ChangeSpannerElements::flip(EditData*)
{
    Element* oldStartElement   = spanner->startElement();
    Element* oldEndElement     = spanner->endElement();
    if (spanner->anchor() == Spanner::Anchor::NOTE) {
        // be sure new spanner elements are of the right type
        if (!startElement->isNote() || !endElement->isNote()) {
            return;
        }
        Note* oldStartNote = toNote(oldStartElement);
        Note* oldEndNote = toNote(oldEndElement);
        Note* newStartNote = toNote(startElement);
        Note* newEndNote = toNote(endElement);
        // update spanner's start and end notes
        if (newStartNote && newEndNote) {
            spanner->setNoteSpan(newStartNote, newEndNote);
            if (spanner->isTie()) {
                Tie* tie = toTie(spanner);
                oldStartNote->setTieFor(nullptr);
                oldEndNote->setTieBack(nullptr);
                newStartNote->setTieFor(tie);
                newEndNote->setTieBack(tie);
            } else {
                oldStartNote->removeSpannerFor(spanner);
                oldEndNote->removeSpannerBack(spanner);
                newStartNote->addSpannerFor(spanner);
                newEndNote->addSpannerBack(spanner);
                if (spanner->isGlissando()) {
                    oldEndNote->chord()->updateEndsGlissando();
                }
            }
        }
    } else {
        spanner->setStartElement(startElement);
        spanner->setEndElement(endElement);
    }
    startElement = oldStartElement;
    endElement   = oldEndElement;
    spanner->triggerLayout();
}

//---------------------------------------------------------
//   ChangeParent
//---------------------------------------------------------

void ChangeParent::flip(EditData*)
{
    Element* p = element->parent();
    int si = element->staffIdx();
    p->remove(element);
    element->setParent(parent);
    element->setTrack(staffIdx * VOICES);
    parent->add(element);
    staffIdx = si;
    parent = p;
}

//---------------------------------------------------------
//   ChangeMMRest
//---------------------------------------------------------

void ChangeMMRest::flip(EditData*)
{
    Measure* mmr = m->mmRest();
    m->setMMRest(mmrest);
    mmrest = mmr;
}

//---------------------------------------------------------
//   InsertTime
//---------------------------------------------------------

void InsertTime::redo(EditData*)
{
    score->insertTime(tick, len);
}

void InsertTime::undo(EditData*)
{
    score->insertTime(tick, -len);
}

//---------------------------------------------------------
//   InsertTimeUnmanagedSpanner::flip
//---------------------------------------------------------

void InsertTimeUnmanagedSpanner::flip(EditData*)
{
    for (Score* s : score->scoreList()) {
        const auto unmanagedSpanners(s->unmanagedSpanners());
        for (Spanner* sp : unmanagedSpanners) {
            sp->insertTimeUnmanaged(tick, len);
        }
    }
    len = -len;
}

//---------------------------------------------------------
//   ChangeNoteEvent::flip
//---------------------------------------------------------

void ChangeNoteEvent::flip(EditData*)
{
    note->score()->setPlaylistDirty();
    NoteEvent e = *oldEvent;
    *oldEvent   = newEvent;
    newEvent    = e;
    // Get a copy of the current playEventType.
    PlayEventType petval = note->chord()->playEventType();
    // Replace current setting with new setting.
    note->chord()->setPlayEventType(newPetype);
    // Save copy of old setting.
    newPetype = petval;
}

//---------------------------------------------------------
//   LinkUnlink
//---------------------------------------------------------

LinkUnlink::~LinkUnlink()
{
    if (le && mustDelete) {
        Q_ASSERT(le->size() <= 1);
        delete le;
    }
}

void LinkUnlink::link()
{
    if (le->size() == 1) {
        le->front()->setLinks(le);
    }
    mustDelete = false;
    le->append(e);
    e->setLinks(le);
}

void LinkUnlink::unlink()
{
    Q_ASSERT(le->contains(e));
    le->removeOne(e);
    if (le->size() == 1) {
        le->front()->setLinks(0);
        mustDelete = true;
    }

    e->setLinks(0);
}

//---------------------------------------------------------
//   Link
//    link e1 to e2
//---------------------------------------------------------

Link::Link(ScoreElement* e1, ScoreElement* e2)
{
    Q_ASSERT(e1->links() == 0);
    le = e2->links();
    if (!le) {
        if (e1->isStaff()) {
            le = new LinkedElements(e1->score(), -1);
        } else {
            le = new LinkedElements(e1->score());
        }
        le->push_back(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
//---------------------------------------------------------

Unlink::Unlink(ScoreElement* _e)
{
    e  = _e;
    le = e->links();
    Q_ASSERT(le);
}

//---------------------------------------------------------
//   ChangeStartEndSpanner::flip
//---------------------------------------------------------

void ChangeStartEndSpanner::flip(EditData*)
{
    Element* s = spanner->startElement();
    Element* e = spanner->endElement();
    spanner->setStartElement(start);
    spanner->setEndElement(end);
    start = s;
    end   = e;
}

//---------------------------------------------------------
//   ChangeMetaTags::flip
//---------------------------------------------------------

void ChangeMetaTags::flip(EditData*)
{
    QMap<QString,QString> t = score->metaTags();
    score->setMetaTags(metaTags);
    metaTags = t;
}

//---------------------------------------------------------
//   ChangeDrumset::flip
//---------------------------------------------------------

void ChangeDrumset::flip(EditData*)
{
    Drumset d = *instrument->drumset();
    instrument->setDrumset(&drumset);
    drumset = d;
}

//---------------------------------------------------------
//   FretDot
//---------------------------------------------------------

void FretDot::redo(EditData*)
{
    undoData = FretUndoData(diagram);

    diagram->setDot(string, fret, add, dtype);
    diagram->triggerLayout();
}

void FretDot::undo(EditData*)
{
    undoData.updateDiagram();
    diagram->triggerLayout();
}

//---------------------------------------------------------
//   FretMarker
//---------------------------------------------------------

void FretMarker::redo(EditData*)
{
    undoData = FretUndoData(diagram);

    diagram->setMarker(string, mtype);
    diagram->triggerLayout();
}

void FretMarker::undo(EditData*)
{
    undoData.updateDiagram();
    diagram->triggerLayout();
}

//---------------------------------------------------------
//   FretBarre
//---------------------------------------------------------

void FretBarre::redo(EditData*)
{
    undoData = FretUndoData(diagram);

    diagram->setBarre(string, fret, add);
    diagram->triggerLayout();
}

void FretBarre::undo(EditData*)
{
    undoData.updateDiagram();
    diagram->triggerLayout();
}

//---------------------------------------------------------
//   FretClear
//---------------------------------------------------------

void FretClear::redo(EditData*)
{
    undoData = FretUndoData(diagram);

    diagram->clear();
    diagram->triggerLayout();
}

void FretClear::undo(EditData*)
{
    undoData.updateDiagram();
    diagram->triggerLayout();
}

//---------------------------------------------------------
//   MoveTremolo
//---------------------------------------------------------

void MoveTremolo::redo(EditData*)
{
    // Find new tremolo chords
    Measure* m1 = score->tick2measure(chord1Tick);
    Measure* m2 = score->tick2measure(chord2Tick);
    IF_ASSERT_FAILED(m1 && m2) {
        return;
    }
    Chord* c1 = m1->findChord(chord1Tick, track);
    Chord* c2 = m2->findChord(chord2Tick, track);
    IF_ASSERT_FAILED(c1 && c2) {
        return;
    }

    // Remember the old tremolo chords
    oldC1 = trem->chord1();
    oldC2 = trem->chord2();

    // Move tremolo away from old chords
    trem->chord1()->setTremolo(nullptr);
    trem->chord2()->setTremolo(nullptr);

    // Delete old tremolo on c1 and c2, if present
    if (c1->tremolo() && (c1->tremolo() != trem)) {
        if (c2->tremolo() == c1->tremolo()) {
            c2->tremolo()->setChords(c1,c2);
        } else {
            c1->tremolo()->setChords(c1,nullptr);
        }
        Tremolo* oldTremolo  = c1->tremolo();
        c1->setTremolo(nullptr);
        delete oldTremolo;
    }
    if (c2->tremolo() && (c2->tremolo() != trem)) {
        c2->tremolo()->setChords(nullptr,c2);
        Tremolo* oldTremolo  = c2->tremolo();
        c2->setTremolo(nullptr);
        delete oldTremolo;
    }

    // Move tremolo to new chords
    c1->setTremolo(trem);
    c2->setTremolo(trem);
    trem->setChords(c1, c2);
    trem->setParent(c1);

    // Tremolo would cross barline, so remove it
    if (m1 != m2) {
        score->undoRemoveElement(trem);
        return;
    }
    // One of the notes crosses a barline, so remove the tremolo
    if (c1->ticks() != c2->ticks()) {
        score->undoRemoveElement(trem);
    }
}

void MoveTremolo::undo(EditData*)
{
    // Move tremolo to old position
    trem->chord1()->setTremolo(nullptr);
    trem->chord2()->setTremolo(nullptr);
    oldC1->setTremolo(trem);
    oldC2->setTremolo(trem);
    trem->setChords(oldC1, oldC2);
    trem->setParent(oldC1);
}
}
