diff --git a/data/themes/default/cut_overlaps.png b/data/themes/default/cut_overlaps.png new file mode 100644 index 000000000..64b044ea9 Binary files /dev/null and b/data/themes/default/cut_overlaps.png differ diff --git a/data/themes/default/fill.png b/data/themes/default/fill.png new file mode 100644 index 000000000..145667350 Binary files /dev/null and b/data/themes/default/fill.png differ diff --git a/data/themes/default/max_length.png b/data/themes/default/max_length.png new file mode 100644 index 000000000..387bb7934 Binary files /dev/null and b/data/themes/default/max_length.png differ diff --git a/data/themes/default/min_length.png b/data/themes/default/min_length.png new file mode 100644 index 000000000..7dc67bdb0 Binary files /dev/null and b/data/themes/default/min_length.png differ diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 77d372bcc..ea243e541 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -211,6 +211,8 @@ protected slots: void clearGhostPattern(); void glueNotes(); + void fitNoteLengths(bool fill); + void constrainNoteLengths(bool constrainMax); signals: diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 76732cd8c..1d8ad8bfd 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -718,6 +718,86 @@ void PianoRoll::glueNotes() } } +void PianoRoll::fitNoteLengths(bool fill) +{ + if (!hasValidPattern()) { return; } + m_pattern->addJournalCheckPoint(); + + // Reference notes + NoteVector refNotes = m_pattern->notes(); + std::sort(refNotes.begin(), refNotes.end(), Note::lessThan); + + // Notes to edit + NoteVector notes = getSelectedNotes(); + if (notes.empty()) + { + notes = refNotes; + } + else if (!fill) + { + std::sort(notes.begin(), notes.end(), Note::lessThan); + } + if (fill) + { + std::sort(notes.begin(), notes.end(), [](Note* n1, Note* n2) { return n1->endPos() < n2->endPos(); }); + } + + int length; + NoteVector::iterator ref = refNotes.begin(); + for (Note* note : notes) + { + // Fast forward to next reference note + while (ref != refNotes.end() && (fill ? (*ref)->pos() < note->endPos() : (*ref)->pos() <= note->pos())) + { + ref++; + } + if (ref == refNotes.end()) + { + if (!fill) { break; } + // Last notes stretch to end of last bar + length = notes.last()->endPos().nextFullBar() * TimePos::ticksPerBar() - note->pos(); + } + else + { + length = (*ref)->pos() - note->pos(); + } + if (fill ? note->length() < length : note->length() > length) + { + note->setLength(length); + } + } + + update(); + gui->songEditor()->update(); + Engine::getSong()->setModified(); +} + + +void PianoRoll::constrainNoteLengths(bool constrainMax) +{ + if (!hasValidPattern()) { return; } + m_pattern->addJournalCheckPoint(); + + NoteVector notes = getSelectedNotes(); + if (notes.empty()) + { + notes = m_pattern->notes(); + } + + TimePos bound = m_lenOfNewNotes; // will be length of last note + for (Note* note : notes) + { + if (constrainMax ? note->length() > bound : note->length() < bound) + { + note->setLength(bound); + } + } + + update(); + gui->songEditor()->update(); + Engine::getSong()->setModified(); +} + void PianoRoll::loadMarkedSemiTones(const QDomElement & de) { @@ -4603,25 +4683,40 @@ PianoRollWindow::PianoRollWindow() : m_editor->m_timeLine->addToolButtons( timeLineToolBar ); // -- Note modifier tools - // Toolbar QToolButton * noteToolsButton = new QToolButton(m_toolBar); noteToolsButton->setIcon(embed::getIconPixmap("tool")); noteToolsButton->setPopupMode(QToolButton::InstantPopup); - // Glue QAction * glueAction = new QAction(embed::getIconPixmap("glue"), tr("Glue"), noteToolsButton); connect(glueAction, SIGNAL(triggered()), m_editor, SLOT(glueNotes())); glueAction->setShortcut( Qt::SHIFT | Qt::Key_G ); - // Knife QAction * knifeAction = new QAction(embed::getIconPixmap("edit_knife"), tr("Knife"), noteToolsButton); connect(knifeAction, &QAction::triggered, m_editor, &PianoRoll::setKnifeAction); knifeAction->setShortcut( Qt::SHIFT | Qt::Key_K ); + + QAction* fillAction = new QAction(embed::getIconPixmap("fill"), tr("Fill"), noteToolsButton); + connect(fillAction, &QAction::triggered, [this](){ m_editor->fitNoteLengths(true); }); + fillAction->setShortcut(Qt::SHIFT | Qt::Key_F); + + QAction* cutOverlapsAction = new QAction(embed::getIconPixmap("cut_overlaps"), tr("Cut overlaps"), noteToolsButton); + connect(cutOverlapsAction, &QAction::triggered, [this](){ m_editor->fitNoteLengths(false); }); + cutOverlapsAction->setShortcut(Qt::SHIFT | Qt::Key_C); + + QAction* minLengthAction = new QAction(embed::getIconPixmap("min_length"), tr("Min length as last"), noteToolsButton); + connect(minLengthAction, &QAction::triggered, [this](){ m_editor->constrainNoteLengths(false); }); + + QAction* maxLengthAction = new QAction(embed::getIconPixmap("max_length"), tr("Max length as last"), noteToolsButton); + connect(maxLengthAction, &QAction::triggered, [this](){ m_editor->constrainNoteLengths(true); }); noteToolsButton->addAction(glueAction); noteToolsButton->addAction(knifeAction); + noteToolsButton->addAction(fillAction); + noteToolsButton->addAction(cutOverlapsAction); + noteToolsButton->addAction(minLengthAction); + noteToolsButton->addAction(maxLengthAction); notesActionsToolBar->addWidget(noteToolsButton);