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.
  • Loading branch information
blackears committed Mar 20, 2020
1 parent 3d49d5c commit 08857b7
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 21 deletions.
215 changes: 196 additions & 19 deletions mscore/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 @@ -561,16 +563,23 @@ 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("Copy notes"));
connect(act, SIGNAL(triggered(bool)), this, SLOT(copyNotes()));
popup.addAction(act);

act = new QAction(tr("Paste notes here"));
connect(act, SIGNAL(triggered(bool)), this, SLOT(pasteNotesAtCursor()));
popup.addAction(act);

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

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

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

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

showPopupMenu(event->globalPos());
}

Expand Down Expand Up @@ -674,11 +685,11 @@ 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();
Expand All @@ -691,7 +702,7 @@ void PianoView::mouseReleaseEvent(QMouseEvent* event)
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 @@ -764,7 +775,7 @@ void PianoView::mouseReleaseEvent(QMouseEvent* event)
if (roundedTick != startTick) {
ChordRest* cr0;
ChordRest* cr1;
cutChordRest(e, track, roundedTick, cr0, cr1);
cutChordRest(e, track, rt, cr0, cr1);
}
score->endCmd();
}
Expand All @@ -779,6 +790,47 @@ void PianoView::mouseReleaseEvent(QMouseEvent* event)
}


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

void PianoView::addNote(Fraction startTick, Fraction frac, int pitch, int track)
{
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
score->startCmd();

ChordRest* cr0;
ChordRest* cr1;

//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->isChord() && cr0->ticks() == frac) {
Chord* ch = toChord(cr0);
score->addNote(ch, nv);
}
else {
score->setNoteRest(cr0->segment(), track, nv, frac);
}
}

score->endCmd();
}

}

//---------------------------------------------------------
// mouseMoveEvent
Expand Down Expand Up @@ -858,10 +910,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 @@ -885,10 +937,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 @@ -898,9 +949,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 @@ -1242,4 +1293,130 @@ void PianoView::setSubdiv(int value)
}
}

//---------------------------------------------------------
// 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[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("firstTickN", QString::number(firstTick.numerator()));
xml.writeAttribute("firstTickD", 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 tieLen = Fraction(note->playTicks(), 1) - ticks;
Fraction len = ticks + tieLen;

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

int voice = note->voice();

xml.writeStartElement("note");
xml.writeAttribute("startTickN", QString::number(startTick.numerator()));
xml.writeAttribute("startTickD", QString::number(startTick.denominator()));
xml.writeAttribute("tickLenN", QString::number(len.numerator()));
xml.writeAttribute("tickLenD", 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;

int pickTick = pixelXToTick(popupMenuPos.x());
int subbeats = _tuplet * (1 << _subdiv);
int subbeatTicks = MScore::division / subbeats;
int roundedTick = (pickTick / subbeatTicks) * subbeatTicks;

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("firstTickN").toString().toInt();
int d = xml.attributes().value("firstTickD").toString().toInt();
firstTick = Fraction(n, d);
}
if (xml.name().toString() == "note") {
int sn = xml.attributes().value("startTickN").toString().toInt();
int sd = xml.attributes().value("startTickD").toString().toInt();
Fraction startTick = Fraction(sn, sd);

int tn = xml.attributes().value("tickLenN").toString().toInt();
int td = xml.attributes().value("tickLenD").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 + Fraction(roundedTick, 1), tickLen, pitch, track);
}
}
}
}

score->endCmd();
}

}
8 changes: 6 additions & 2 deletions mscore/pianoview.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class PianoView : public QGraphicsView {
bool dragStarted;
QPointF mouseDownPos;
QPointF lastMousePos;
QPointF popupMenuPos;
DragStyle dragStyle;
int lastDragPitch;
bool inProgressUndoEvent;
Expand All @@ -110,8 +111,9 @@ class PianoView : public QGraphicsView {
void updateBoundingSize();
void clearNoteData();
void selectNotes(int startTick, int endTick, int lowPitch, int highPitch, NoteSelectType selType);
void showPopupMenu(const QPoint& pos);
bool cutChordRest(ChordRest* e, int track, int cutTick, ChordRest*& cr0, ChordRest*& cr1);
void showPopupMenu(const QPoint& posGlobal);
bool cutChordRest(ChordRest* e, int track, Fraction cutTick, ChordRest*& cr0, ChordRest*& cr1);
void addNote(Fraction startTick, Fraction frac, int pitch, int track);

QAction* getAction(const char* id);

Expand Down Expand Up @@ -140,6 +142,8 @@ class PianoView : public QGraphicsView {
void setTuplet(int);
void setSubdiv(int);
void setBarPattern(int);
void copyNotes();
void pasteNotesAtCursor();

public:
PianoView();
Expand Down

0 comments on commit 08857b7

Please sign in to comment.