Skip to content

Commit

Permalink
fix #278988 Copy and paste notes in Piano Roll Editor
Browse files Browse the repository at this point in the history
Can now copy and paste notes in piano roll editor using the right click menu.

Allowing cut and paste of nodes in piano roll editor.


Allowing cut and paste of nodes in piano roll editor.


Cleaning code.

Using Fraction::fromTicks to convert ticks.

Changing variable names.

Updating cut/paste to use Fraction

Adding cut shortcut.

Undo now reverts all notes from paste.

Cleaning up code
  • Loading branch information
blackears committed Apr 22, 2020
1 parent c1e7364 commit 1213bb0
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 26 deletions.
12 changes: 12 additions & 0 deletions libmscore/note.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,18 @@ int Note::playTicks() const
return (note->chord()->tick() + note->chord()->actualTicks() - stick).ticks();
}

//---------------------------------------------------------
// playTicksFraction
/// Return total tick len of tied notes
//---------------------------------------------------------

Fraction Note::playTicksFraction() const
{
Fraction stick = firstTiedNote()->chord()->tick();
const Note* note = lastTiedNote();
return note->chord()->tick() + note->chord()->actualTicks() - stick;
}

//---------------------------------------------------------
// addSpanner
//---------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions libmscore/note.h
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ class Note final : public Element {
void setTrack(int val) override;

int playTicks() const;
Fraction playTicksFraction() const;

qreal headWidth() const;
qreal headHeight() const;
Expand Down
256 changes: 232 additions & 24 deletions mscore/pianoroll/pianoview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ namespace Ms {

extern MuseScore* mscore;

static const QString PIANO_NOTE_MIME_TYPE = "application/musescore/pianorollnotes";

static const qreal MIN_DRAG_DIST_SQ = 9;

const BarPattern PianoView::barPatterns[] = {
Expand Down Expand Up @@ -589,16 +591,27 @@ void PianoView::wheelEvent(QWheelEvent* event)
// showPopupMenu
//---------------------------------------------------------

void PianoView::showPopupMenu(const QPoint& pos)
void PianoView::showPopupMenu(const QPoint& posGlobal)
{
QMenu popup(this);
// popup.addAction(getAction("cut"));
// popup.addAction(getAction("copy"));
popup.addAction(getAction("paste"));
// popup.addAction(getAction("swap"));

QAction* act;

act = new QAction(tr("Cut notes"));
connect(act, &QAction::triggered, this, &PianoView::cutNotes);
popup.addAction(act);

act = new QAction(tr("Copy notes"));
connect(act, &QAction::triggered, this, &PianoView::copyNotes);
popup.addAction(act);

act = new QAction(tr("Paste notes here"));
connect(act, &QAction::triggered, this, &PianoView::pasteNotesAtCursor);
popup.addAction(act);

popup.addAction(getAction("delete"));

popup.exec(pos);
popup.exec(posGlobal);
}

//---------------------------------------------------------
Expand All @@ -607,6 +620,8 @@ void PianoView::showPopupMenu(const QPoint& pos)

void PianoView::contextMenuEvent(QContextMenuEvent *event)
{
_popupMenuPos = mapToScene(event->pos());

showPopupMenu(event->globalPos());
}

Expand Down Expand Up @@ -702,24 +717,24 @@ void PianoView::mouseReleaseEvent(QMouseEvent* event)

NoteVal nv(pickPitch);

Fraction t = Fraction::fromTicks(roundedTick);
Segment* seg = score->tick2segment(t);
Fraction rt = Fraction::fromTicks(roundedTick);
Segment* seg = score->tick2segment(rt);
score->expandVoice(seg, track);

ChordRest* e = score->findCR(t, track);
ChordRest* e = score->findCR(rt, track);
if (e && !e->tuplet() && _tuplet == 1) {
//Ignore tuplets
score->startCmd();

ChordRest* cr0;
ChordRest* cr1;
ChordRest* cr0 = nullptr;
ChordRest* cr1 = nullptr;
Fraction frac = is.duration().fraction();

//Default to quarter note if faction is invalid
if (!frac.isValid() || frac.isZero())
frac.set(1, 4);

if (cutChordRest(e, track, roundedTick, cr0, cr1)) {
if (cutChordRest(e, track, rt, cr0, cr1)) {
score->setNoteRest(cr1->segment(), track, nv, frac);
}
else {
Expand Down Expand Up @@ -790,9 +805,9 @@ void PianoView::mouseReleaseEvent(QMouseEvent* event)
int startTick = e->tick().ticks();

if (roundedTick != startTick) {
ChordRest* cr0;
ChordRest* cr1;
cutChordRest(e, track, roundedTick, cr0, cr1);
ChordRest* cr0 = nullptr;
ChordRest* cr1 = nullptr;
cutChordRest(e, track, rt, cr0, cr1);
}
score->endCmd();
}
Expand All @@ -807,6 +822,49 @@ void PianoView::mouseReleaseEvent(QMouseEvent* event)
}


//---------------------------------------------------------
// addNote
//---------------------------------------------------------

void PianoView::addNote(Fraction startTick, Fraction frac, int pitch, int track, bool command)
{
NoteVal nv(pitch);

Score* score = _staff->score();
Segment* seg = score->tick2segment(startTick);
score->expandVoice(seg, track);

ChordRest* e = score->findCR(startTick, track);
if (e && !e->tuplet() && _tuplet == 1) {
//Ignore tuplets
if (command)
score->startCmd();

ChordRest* cr0 = nullptr;
ChordRest* cr1 = nullptr;

//Default to quarter note if faction is invalid
if (!frac.isValid() || frac.isZero())
frac.set(1, 4);

if (cutChordRest(e, track, startTick, cr0, cr1)) {
score->setNoteRest(cr1->segment(), track, nv, frac);
}
else if (cr0) {
if (cr0->isChord() && cr0->ticks() == frac) {
Chord* ch = toChord(cr0);
score->addNote(ch, nv);
}
else {
score->setNoteRest(cr0->segment(), track, nv, frac);
}
}

if (command)
score->endCmd();
}

}

//---------------------------------------------------------
// mouseMoveEvent
Expand Down Expand Up @@ -886,10 +944,10 @@ void PianoView::mouseMoveEvent(QMouseEvent* event)
// cutChordRest
//---------------------------------------------------------

bool PianoView::cutChordRest(ChordRest* e, int track, int cutTick, ChordRest*& cr0, ChordRest*& cr1)
bool PianoView::cutChordRest(ChordRest* e, int track, Fraction cutTick, ChordRest*& cr0, ChordRest*& cr1)
{
int startTick = e->segment()->tick().ticks();
int tcks = e->ticks().ticks();
Fraction startTick = e->segment()->tick();
Fraction tcks = e->ticks();
if (cutTick <= startTick || cutTick > startTick + tcks) {
cr0 = e;
cr1 = 0;
Expand All @@ -913,10 +971,9 @@ bool PianoView::cutChordRest(ChordRest* e, int track, int cutTick, ChordRest*& c
NoteVal nv(-1);

Score* score = _staff->score();
score->setNoteRest(e->segment(), track, nv, Fraction::fromTicks(cutTick) - e->tick());
ChordRest *nextCR = score->findCR(Fraction::fromTicks(cutTick), track);
score->setNoteRest(e->segment(), track, nv, cutTick - e->tick());
ChordRest *nextCR = score->findCR(cutTick, track);

// nextCR->segment()->setTick(cutTick);
Chord* ch0 = 0;

if (nextCR->isChord()) {
Expand All @@ -926,9 +983,9 @@ bool PianoView::cutChordRest(ChordRest* e, int track, int cutTick, ChordRest*& c
for (Note* n: ch1->notes()) {
NoteVal nx = n->noteVal();
if (!ch0) {
ChordRest* cr = score->findCR(Fraction::fromTicks(startTick), track);
ChordRest* cr = score->findCR(startTick, track);
score->setNoteRest(cr->segment(), track, nx, cr->ticks());
ch0 = toChord(score->findCR(Fraction::fromTicks(startTick), track));
ch0 = toChord(score->findCR(startTick, track));
}
else {
score->addNote(ch0, nx);
Expand Down Expand Up @@ -1189,7 +1246,7 @@ QList<PianoItem*> PianoView::getSelectedItems()
{
QList<PianoItem*> list;
for (int i = 0; i < _noteList.size(); ++i) {
if (_noteList[i]->note()->selected())
if (_noteList.at(i)->note()->selected())
list.append(_noteList[i]);
}
return list;
Expand Down Expand Up @@ -1280,4 +1337,155 @@ void PianoView::setSubdiv(int value)
}
}

//---------------------------------------------------------
// cutNotes
//---------------------------------------------------------

void PianoView::cutNotes()
{
copyNotes();

Score* score = _staff->score();
score->startCmd();

score->cmdDeleteSelection();

score->endCmd();
}

//---------------------------------------------------------
// copyNotes
//---------------------------------------------------------

void PianoView::copyNotes()
{
//Custom note copy routine based on Selection::staffMimeData()
Fraction firstTick;
bool init = false;
for (int i = 0; i < _noteList.size(); ++i) {
if (_noteList[i]->note()->selected()) {
Note* note = _noteList.at(i)->note();
Fraction startTick = note->chord()->tick();

if (!init || firstTick > startTick) {
firstTick = startTick;
init = true;
}
}
}

//No valid notes
if (!init)
return;

QString xmlStrn;
QXmlStreamWriter xml(&xmlStrn);
xml.setAutoFormatting(true);
xml.writeStartDocument();

xml.writeStartElement("notes");
xml.writeAttribute("firstN", QString::number(firstTick.numerator()));
xml.writeAttribute("firstD", QString::number(firstTick.denominator()));

//bundle notes into XML file & send to clipboard.
//This is only affects pianoview and is not part of the regular copy/paste process
for (int i = 0; i < _noteList.size(); ++i) {
if (_noteList[i]->note()->selected()) {
Note* note = _noteList[i]->note();

Chord* chord = note->chord();

Fraction ticks = chord->ticks();
Fraction len = note->playTicksFraction();

Fraction startTick = note->chord()->tick();
int pitch = note->pitch();

int voice = note->voice();

xml.writeStartElement("note");
xml.writeAttribute("startN", QString::number(startTick.numerator()));
xml.writeAttribute("startD", QString::number(startTick.denominator()));
xml.writeAttribute("lenN", QString::number(len.numerator()));
xml.writeAttribute("lenD", QString::number(len.denominator()));
xml.writeAttribute("pitch", QString::number(pitch));
xml.writeAttribute("voice", QString::number(voice));
xml.writeEndElement();
}
}

xml.writeEndElement();
xml.writeEndDocument();

QMimeData* mimeData = new QMimeData;
mimeData->setData(PIANO_NOTE_MIME_TYPE, xmlStrn.toUtf8());
QApplication::clipboard()->setMimeData(mimeData);
}

//---------------------------------------------------------
// pasteNotesAtCursor
//---------------------------------------------------------

void PianoView::pasteNotesAtCursor()
{
//ScoreView::normalPaste();
const QMimeData* ms = QApplication::clipboard()->mimeData();
if (!ms)
return;

Score* _score = _staff->score();
Pos barPos(_score->tempomap(), _score->sigmap(), pixelXToTick(_popupMenuPos.x()), TType::TICKS);

int beatsInBar = barPos.timesig().timesig().numerator();
int pickTick = barPos.tick();
Fraction pickFrac = Fraction::fromTicks(pickTick);

//Number of smaller pieces the beat is divided into
int subbeats = _tuplet * (1 << _subdiv);
int divisions = beatsInBar * subbeats;

//Round down to nearest division
int numDiv = (int)floor((pickFrac.numerator() * divisions / (double)pickFrac.denominator()));
Fraction pasteStartTick(numDiv, divisions);

Score* score = _staff->score();
score->startCmd();

if (ms->hasFormat(PIANO_NOTE_MIME_TYPE)) {
//Decode our XML format and recreate the notes
QByteArray data = ms->data(PIANO_NOTE_MIME_TYPE);
QXmlStreamReader xml(data);
Fraction firstTick;

while (!xml.atEnd()) {
QXmlStreamReader::TokenType tt = xml.readNext();
if (tt == QXmlStreamReader::StartElement){
if (xml.name().toString() == "notes") {
int n = xml.attributes().value("firstN").toString().toInt();
int d = xml.attributes().value("firstD").toString().toInt();
firstTick = Fraction(n, d);
}
if (xml.name().toString() == "note") {
int sn = xml.attributes().value("startN").toString().toInt();
int sd = xml.attributes().value("startD").toString().toInt();
Fraction startTick = Fraction(sn, sd);

int tn = xml.attributes().value("lenN").toString().toInt();
int td = xml.attributes().value("lenD").toString().toInt();
Fraction tickLen = Fraction(tn, td);

int pitch = xml.attributes().value("pitch").toString().toInt();
int voice = xml.attributes().value("voice").toString().toInt();

int track = _staff->idx() * VOICES + voice;

addNote(startTick - firstTick + pasteStartTick, tickLen, pitch, track, false);
}
}
}
}

score->endCmd();
}

}
Loading

0 comments on commit 1213bb0

Please sign in to comment.