96c03195e3
Layout optimizations
2628 lines
79 KiB
C++
2628 lines
79 KiB
C++
//=============================================================================
|
|
// 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);
|
|
}
|
|
|
|
}
|