Skip to content

Commit

Permalink
Adds feature to merge Instrument Track patterns (LMMS#5700)
Browse files Browse the repository at this point in the history
  • Loading branch information
IanCaio authored Dec 12, 2020
1 parent 87fd019 commit 848538f
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 2 deletions.
Binary file added data/themes/classic/edit_merge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/themes/default/edit_merge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions include/Pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class PatternView : public TrackContentObjectView
void setMutedNoteBorderColor(QColor const & color) { m_mutedNoteBorderColor = color; }

public slots:
Pattern* getPattern();
void update() override;


Expand Down
7 changes: 6 additions & 1 deletion include/TrackContentObjectView.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ class TrackContentObjectView : public selectableObject, public ModelView
// some metadata to be written to the clipboard.
static void remove( QVector<TrackContentObjectView *> tcovs );
static void toggleMute( QVector<TrackContentObjectView *> tcovs );
static void mergeTCOs(QVector<TrackContentObjectView*> tcovs);

// Returns true if selection can be merged and false if not
static bool canMergeSelection(QVector<TrackContentObjectView*> tcovs);

QColor getColorForDisplay( QColor );

Expand All @@ -129,7 +133,8 @@ public slots:
Cut,
Copy,
Paste,
Mute
Mute,
Merge
};

virtual void constructContextMenu( QMenu * )
Expand Down
117 changes: 116 additions & 1 deletion src/gui/TrackContentObjectView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

#include "TrackContentObjectView.h"

#include <set>

#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
Expand All @@ -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"
Expand Down Expand Up @@ -937,9 +944,11 @@ void TrackContentObjectView::mouseReleaseEvent( QMouseEvent * me )
*/
void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme )
{
QVector<TrackContentObjectView*> 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() )
{
Expand All @@ -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(
Expand Down Expand Up @@ -1023,6 +1041,9 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action )
case Mute:
toggleMute( active );
break;
case Merge:
mergeTCOs(active);
break;
}
}

Expand Down Expand Up @@ -1106,6 +1127,100 @@ void TrackContentObjectView::toggleMute( QVector<TrackContentObjectView *> tcovs
}
}

bool TrackContentObjectView::canMergeSelection(QVector<TrackContentObjectView*> 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<InstrumentTrackView*>(tcovs.at(0)->getTrackView());

// Then we create a set with all the TCOs owners
std::set<TrackView*> 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<TrackContentObjectView*> tcovs)
{
// Get the track that we are merging TCOs in
InstrumentTrack* track =
dynamic_cast<InstrumentTrack*>(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<Pattern*>(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<PatternView*>(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();
}




Expand Down
11 changes: 11 additions & 0 deletions src/tracks/Pattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down

0 comments on commit 848538f

Please sign in to comment.