diff --git a/data/themes/classic/edit_merge.png b/data/themes/classic/edit_merge.png new file mode 100644 index 00000000000..3e8742b86ef Binary files /dev/null and b/data/themes/classic/edit_merge.png differ diff --git a/data/themes/default/edit_merge.png b/data/themes/default/edit_merge.png new file mode 100644 index 00000000000..3e8742b86ef Binary files /dev/null and b/data/themes/default/edit_merge.png differ diff --git a/include/Pattern.h b/include/Pattern.h index 0bc1f61f37a..0a73376c024 100644 --- a/include/Pattern.h +++ b/include/Pattern.h @@ -176,6 +176,7 @@ class PatternView : public TrackContentObjectView void setMutedNoteBorderColor(QColor const & color) { m_mutedNoteBorderColor = color; } public slots: + Pattern* getPattern(); void update() override; diff --git a/include/TrackContentObjectView.h b/include/TrackContentObjectView.h index 0f99af96479..e9461c5dd50 100644 --- a/include/TrackContentObjectView.h +++ b/include/TrackContentObjectView.h @@ -111,6 +111,10 @@ class TrackContentObjectView : public selectableObject, public ModelView // some metadata to be written to the clipboard. static void remove( QVector tcovs ); static void toggleMute( QVector tcovs ); + static void mergeTCOs(QVector tcovs); + + // Returns true if selection can be merged and false if not + static bool canMergeSelection(QVector tcovs); QColor getColorForDisplay( QColor ); @@ -129,7 +133,8 @@ public slots: Cut, Copy, Paste, - Mute + Mute, + Merge }; virtual void constructContextMenu( QMenu * ) diff --git a/src/gui/TrackContentObjectView.cpp b/src/gui/TrackContentObjectView.cpp index 0cd34bdd117..6aa78a1dd6e 100644 --- a/src/gui/TrackContentObjectView.cpp +++ b/src/gui/TrackContentObjectView.cpp @@ -24,6 +24,8 @@ #include "TrackContentObjectView.h" +#include + #include #include #include @@ -33,9 +35,14 @@ #include "ColorChooser.h" #include "ComboBoxModel.h" #include "DataFile.h" +#include "Engine.h" #include "embed.h" #include "GuiApplication.h" +#include "InstrumentTrack.h" +#include "Note.h" +#include "Pattern.h" #include "SampleTrack.h" +#include "Song.h" #include "SongEditor.h" #include "StringPairDrag.h" #include "TextFloat.h" @@ -937,9 +944,11 @@ void TrackContentObjectView::mouseReleaseEvent( QMouseEvent * me ) */ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) { + QVector selectedTCOs = getClickedTCOs(); + // Depending on whether we right-clicked a selection or an individual TCO we will have // different labels for the actions. - bool individualTCO = getClickedTCOs().size() <= 1; + bool individualTCO = selectedTCOs.size() <= 1; if( cme->modifiers() ) { @@ -965,6 +974,15 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) ? tr("Cut") : tr("Cut selection"), [this](){ contextMenuAction( Cut ); } ); + + if (canMergeSelection(selectedTCOs)) + { + contextMenu.addAction( + embed::getIconPixmap("edit_merge"), + tr("Merge Selection"), + [this]() { contextMenuAction(Merge); } + ); + } } contextMenu.addAction( @@ -1023,6 +1041,9 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) case Mute: toggleMute( active ); break; + case Merge: + mergeTCOs(active); + break; } } @@ -1106,6 +1127,100 @@ void TrackContentObjectView::toggleMute( QVector tcovs } } +bool TrackContentObjectView::canMergeSelection(QVector tcovs) +{ + // Can't merge a single TCO + if (tcovs.size() < 2) { return false; } + + // We check if the owner of the first TCO is an Instrument Track + bool isInstrumentTrack = dynamic_cast(tcovs.at(0)->getTrackView()); + + // Then we create a set with all the TCOs owners + std::set ownerTracks; + for (auto tcov: tcovs) { ownerTracks.insert(tcov->getTrackView()); } + + // Can merge if there's only one owner track and it's an Instrument Track + return isInstrumentTrack && ownerTracks.size() == 1; +} + +void TrackContentObjectView::mergeTCOs(QVector tcovs) +{ + // Get the track that we are merging TCOs in + InstrumentTrack* track = + dynamic_cast(tcovs.at(0)->getTrackView()->getTrack()); + + if (!track) + { + qWarning("Warning: Couldn't retrieve InstrumentTrack in mergeTCOs()"); + return; + } + + // For Undo/Redo + track->addJournalCheckPoint(); + track->saveJournallingState(false); + + // Find the earliest position of all the selected TCOVs + const auto earliestTCOV = std::min_element(tcovs.constBegin(), tcovs.constEnd(), + [](TrackContentObjectView* a, TrackContentObjectView* b) + { + return a->getTrackContentObject()->startPosition() < + b->getTrackContentObject()->startPosition(); + } + ); + + const TimePos earliestPos = (*earliestTCOV)->getTrackContentObject()->startPosition(); + + // Create a pattern where all notes will be added + Pattern* newPattern = dynamic_cast(track->createTCO(earliestPos)); + if (!newPattern) + { + qWarning("Warning: Failed to convert TCO to Pattern on mergeTCOs"); + return; + } + + newPattern->saveJournallingState(false); + + // Add the notes and remove the TCOs that are being merged + for (auto tcov: tcovs) + { + // Convert TCOV to PatternView + PatternView* pView = dynamic_cast(tcov); + + if (!pView) + { + qWarning("Warning: Non-pattern TCO on InstrumentTrack"); + continue; + } + + NoteVector currentTCONotes = pView->getPattern()->notes(); + TimePos pViewPos = pView->getPattern()->startPosition(); + + for (Note* note: currentTCONotes) + { + Note* newNote = newPattern->addNote(*note, false); + TimePos originalNotePos = newNote->pos(); + newNote->setPos(originalNotePos + (pViewPos - earliestPos)); + } + + // We disable the journalling system before removing, so the + // removal doesn't get added to the undo/redo history + tcov->getTrackContentObject()->saveJournallingState(false); + // No need to check for nullptr because we check while building the tcovs QVector + tcov->remove(); + } + + // Update length since we might have moved notes beyond the end of the pattern length + newPattern->updateLength(); + // Rearrange notes because we might have moved them + newPattern->rearrangeAllNotes(); + // Restore journalling states now that the operation is finished + newPattern->restoreJournallingState(); + track->restoreJournallingState(); + // Update song + Engine::getSong()->setModified(); + gui->songEditor()->update(); +} + diff --git a/src/tracks/Pattern.cpp b/src/tracks/Pattern.cpp index ded78ffa8c6..659a1919c19 100644 --- a/src/tracks/Pattern.cpp +++ b/src/tracks/Pattern.cpp @@ -627,6 +627,17 @@ PatternView::PatternView( Pattern* pattern, TrackView* parent ) : setStyle( QApplication::style() ); } + + + +Pattern* PatternView::getPattern() +{ + return m_pat; +} + + + + void PatternView::update() { ToolTip::add(this, m_pat->name());