From fab8cf60704c264c2f70404c4a085b1935374c86 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Wed, 29 Jul 2020 00:53:28 -0300 Subject: [PATCH 01/20] Starts implementing the feature The idea of this branch is to allow actions triggered through the context menu of a TCO on the song editor to affect all the selected TCOs. With this commit, only the "Mute/unmute" action affects all selected TCOs, while the others retain their old behavior (only affect the TCO that owns the context menu). For that, a method was created that processes all actions (the triggered action is parsed as a parameter to the method). In the case of the "Mute" action, it checks if the song editor has selected TCOs, and if it does it mutes/unmutes all of them. --- include/Track.h | 11 ++++++++++ src/core/Track.cpp | 52 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/include/Track.h b/include/Track.h index ff0e3ef88bf..5a9aeefe530 100644 --- a/include/Track.h +++ b/include/Track.h @@ -299,6 +299,15 @@ protected slots: ToggleSelected } ; + enum ContextMenuAction + { + Remove, + Cut, + Copy, + Paste, + Mute + }; + static TextFloat * s_textFloat; TrackContentObject * m_tco; @@ -334,6 +343,8 @@ protected slots: } void setInitialOffsets(); + void contextMenuAction( ContextMenuAction action ); + bool mouseMovedDistance( QMouseEvent * me, int distance ); MidiTime draggedTCOPos( QMouseEvent * me ); } ; diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 734cb4e12d9..519871d8370 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1126,24 +1126,66 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) { contextMenu.addAction( embed::getIconPixmap( "cancel" ), tr( "Delete (middle mousebutton)" ), - this, SLOT( remove() ) ); + [this](){ contextMenuAction( Remove ); } ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "edit_cut" ), - tr( "Cut" ), this, SLOT( cut() ) ); + tr( "Cut" ), [this](){ contextMenuAction( Cut ); } ); } contextMenu.addAction( embed::getIconPixmap( "edit_copy" ), - tr( "Copy" ), m_tco, SLOT( copy() ) ); + tr( "Copy" ), [this](){ contextMenuAction( Copy ); } ); contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), - tr( "Paste" ), m_tco, SLOT( paste() ) ); + tr( "Paste" ), [this](){ contextMenuAction( Paste ); } ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "muted" ), tr( "Mute/unmute (<%1> + middle click)" ).arg(UI_CTRL_KEY), - m_tco, SLOT( toggleMute() ) ); + [this](){ contextMenuAction( Mute ); } ); constructContextMenu( &contextMenu ); contextMenu.exec( QCursor::pos() ); } +// This method processes the actions from the context menu of the TCO View. +// We use this method so we can check if there are more selected TCOs to apply +// some of the actions to all of them. +void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) +{ + // TODO: Make it possible to remove, cut, copy and paste multiple selected TCOs through the context menu + switch( action ) + { + case Remove: + remove(); + break; + case Cut: + cut(); + break; + case Copy: + getTrackContentObject()->copy(); + break; + case Paste: + getTrackContentObject()->paste(); + break; + case Mute: + // Checks if there are other selected TCOs and if so mutes them as well + QVector so = gui->songEditor()->m_editor->selectedObjects(); + + if( so.size() > 0 ){ + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView *tcov = + dynamic_cast( *it ); + + tcov->getTrackContentObject()->toggleMute(); + } + } + else + { + getTrackContentObject()->toggleMute(); + } + break; + } +} + From 865172b2e96a4a55fc50fac47a6f124ba46037d7 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Wed, 29 Jul 2020 01:06:52 -0300 Subject: [PATCH 02/20] Allows selected TCOs to be removed too Now the "Remove" action from the context menu will remove all selected TCOs if there are any. --- src/core/Track.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 519871d8370..826a0a0e08b 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1149,11 +1149,28 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) // some of the actions to all of them. void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) { - // TODO: Make it possible to remove, cut, copy and paste multiple selected TCOs through the context menu + // List of selected TCOs + QVector so = gui->songEditor()->m_editor->selectedObjects(); + + // TODO: Make it possible to cut, copy and paste multiple selected TCOs through the context menu switch( action ) { case Remove: - remove(); + // Checks if there are other selected TCOs and if so removes them as well + if( so.size() > 0 ){ + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView *tcov = + dynamic_cast( *it ); + + tcov->remove(); + } + } + else + { + remove(); + } break; case Cut: cut(); @@ -1166,8 +1183,6 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) break; case Mute: // Checks if there are other selected TCOs and if so mutes them as well - QVector so = gui->songEditor()->m_editor->selectedObjects(); - if( so.size() > 0 ){ for( QVector::iterator it = so.begin(); it != so.end(); ++it ) From 182fa10eabeb6abf737490266301050b85517379 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Wed, 29 Jul 2020 12:02:25 -0300 Subject: [PATCH 03/20] Starts implementing selected TCO cut and copy Now, when multiple TCOs are selected, the context menu actions Cut and Copy will write a DataFile to the clipboard containing the TCO information, so it can later be used to paste it. The Paste action now checks if there's data in the QApplication clipboard. If there is, it will later paste the TCOs (for now it just prints the data with qWarning). If there's not, it uses the regular TCO paste method that uses the internal LMMS clipboard. Because it now have to decide between the QApplication clipboard and the LMMS internal clipboard, the Clipboard::copy() method now clears anything in the QApplication clipboard, making it empty so LMMS can know it should use the internal clipboard instead in that situation. Adds safety checks for the selected TCO views. --- src/core/Clipboard.cpp | 7 ++++ src/core/Track.cpp | 84 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/core/Clipboard.cpp b/src/core/Clipboard.cpp index 0c4b972865b..9b1191cdc0d 100644 --- a/src/core/Clipboard.cpp +++ b/src/core/Clipboard.cpp @@ -22,6 +22,9 @@ * */ +#include +#include + #include "Clipboard.h" #include "JournallingObject.h" @@ -35,6 +38,10 @@ void Clipboard::copy( JournallingObject * _obj ) QDomElement parent = doc.createElement( "Clipboard" ); _obj->saveState( doc, parent ); content[_obj->nodeName()] = parent.firstChild().toElement(); + + // Clear the QApplication clipboard, so we don't have any conflicts when LMMS has to + // decide between the QApplication clipboard and the internal clipboard data + QApplication::clipboard()->clear( QClipboard::Clipboard ); } diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 826a0a0e08b..0b2308931c6 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -45,6 +45,8 @@ #include #include #include +#include +#include #include "AutomationPattern.h" @@ -1152,7 +1154,7 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) // List of selected TCOs QVector so = gui->songEditor()->m_editor->selectedObjects(); - // TODO: Make it possible to cut, copy and paste multiple selected TCOs through the context menu + // TODO: Make it possible to paste multiple selected TCOs through the context menu switch( action ) { case Remove: @@ -1164,7 +1166,10 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) TrackContentObjectView *tcov = dynamic_cast( *it ); - tcov->remove(); + if( tcov != NULL ) + { + tcov->remove(); + } } } else @@ -1173,13 +1178,77 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) } break; case Cut: - cut(); + if( so.size() > 0 ){ + // List of TCOs to be copied + QVector tcoViews; + + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView *tcov = dynamic_cast( *it ); + if( tcov != NULL ) + { + tcoViews.push_back( tcov ); + tcov->remove(); // We are cutting now + } + } + + // Write the TCOs to a DataFile for copying + DataFile dataFile = createTCODataFiles( tcoViews ); + + // Copy it to the clipboard + QMimeData *tco_content = new QMimeData; + tco_content->setData( Clipboard::mimeType(), dataFile.toString().toUtf8() ); + QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); + } + else + { + cut(); + } break; case Copy: - getTrackContentObject()->copy(); + if( so.size() > 0 ){ + // List of TCOs to be copied + QVector tcoViews; + + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView *tcov = dynamic_cast( *it ); + if( tcov != NULL ) + { + tcoViews.push_back( tcov ); + } + } + + // Write the TCOs to a DataFile for copying + DataFile dataFile = createTCODataFiles( tcoViews ); + + // Copy it to the clipboard + QMimeData *tco_content = new QMimeData; + tco_content->setData( Clipboard::mimeType(), dataFile.toString().toUtf8() ); + QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); + } + else + { + getTrackContentObject()->copy(); + } break; case Paste: - getTrackContentObject()->paste(); + // NOTE: Because we give preference to the QApplication clipboard over the LMMS Clipboard class, we need to + // clear the QApplication Clipboard during the LMMS Clipboard copy operations (Clipboard::copy does that) + + // If we have TCO data on the clipboard paste it. If not, do our regular TCO paste. + if( QApplication::clipboard()->mimeData( QClipboard::Clipboard )->hasFormat( Clipboard::mimeType() ) ) + { + // TODO: Implement the actual paste operation + qWarning("Context Menu Paste - Data:"); + qWarning() << QApplication::clipboard()->mimeData( QClipboard::Clipboard )->data( Clipboard::mimeType() ); + } + else + { + getTrackContentObject()->paste(); + } break; case Mute: // Checks if there are other selected TCOs and if so mutes them as well @@ -1190,7 +1259,10 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) TrackContentObjectView *tcov = dynamic_cast( *it ); - tcov->getTrackContentObject()->toggleMute(); + if( tcov != NULL ) + { + tcov->getTrackContentObject()->toggleMute(); + } } } else From 0e40683eaa612d126cb78ab034287200ae823317 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 31 Jul 2020 15:35:23 -0300 Subject: [PATCH 04/20] Overloads TCW paste selection methods This commit is a step towards implementing the paste feature of the TCO context menu. It overloads the TrackContentWidget::canPasteSelection and TrackContentWidget::pasteSelection methods so they can be called without having a QDropEvent (only the QMimeData with the copied TCOs information). The overloaded canPasteSelection(MidiTime, QMimeData, bool) method required a third argument which says whether pasting over the same bar should be allowed or not, because it shouldn't when the QDropEvent comes from the same application but should when it doesn't. That is defined in the canPasteSelection(MidiTime, QDropEvent) method. Also, the pasteSelection(MidiTime, QDropEvent) isn't optimal, since it calls canPasteSelection twice (more details in the comments, but it's basically because the two canPasteSelection methods can return different values depending on the origin of QDropEvent). This could later be fixed by calling canPasteSelection before pasteSelection and removing it from inside the method altogether. Next step is to add the "tco_" key to the mimeData on the Copy/Cut operations and implementing the paste operation using those methods. --- include/Track.h | 2 ++ src/core/Track.cpp | 50 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/include/Track.h b/include/Track.h index 5a9aeefe530..25f194ecc2c 100644 --- a/include/Track.h +++ b/include/Track.h @@ -381,7 +381,9 @@ class TrackContentWidget : public QWidget, public JournallingObject } bool canPasteSelection( MidiTime tcoPos, const QDropEvent *de ); + bool canPasteSelection( MidiTime tcoPos, const QMimeData *md, bool allowSameBar = false ); bool pasteSelection( MidiTime tcoPos, QDropEvent * de ); + bool pasteSelection( MidiTime tcoPos, const QMimeData * md ); MidiTime endPosition( const MidiTime & posStart ); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 0b2308931c6..7a549b925fa 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1648,9 +1648,24 @@ bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QDropEvent* d { const QMimeData * mimeData = de->mimeData(); + // Overloaded method to make it possible to call this method without a Drag&Drop event + // If the source of the DropEvent is the current instance of LMMS we don't allow pasting in the same bar + // If the source of the DropEvent is another instance of LMMS we allow it + if( de->source() ) + { + return canPasteSelection( tcoPos, mimeData ); + } + else + { + return canPasteSelection( tcoPos, mimeData, true ); + } +} + +bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QMimeData* md , bool allowSameBar ) +{ Track * t = getTrack(); - QString type = StringPairDrag::decodeMimeKey( mimeData ); - QString value = StringPairDrag::decodeMimeValue( mimeData ); + QString type = StringPairDrag::decodeMimeKey( md ); + QString value = StringPairDrag::decodeMimeValue( md ); // We can only paste into tracks of the same type if( type != ( "tco_" + QString::number( t->type() ) ) || @@ -1678,10 +1693,13 @@ bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QDropEvent* d // Don't paste if we're on the same bar auto sourceTrackContainerId = metadata.attributeNode( "trackContainerId" ).value().toUInt(); - if( de->source() && sourceTrackContainerId == t->trackContainer()->id() && - tcoPos == grabbedTCOBar && currentTrackIndex == initialTrackIndex ) + if( !allowSameBar ) { - return false; + if( sourceTrackContainerId == t->trackContainer()->id() && + tcoPos == grabbedTCOBar && currentTrackIndex == initialTrackIndex ) + { + return false; + } } // Extract the tco data @@ -1720,13 +1738,31 @@ bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QDropEvent* d */ bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de ) { + const QMimeData * mimeData = de->mimeData(); + + // TODO: If we use this method, we end up calling canPasteSelection twice: Once with a QDropEevnt + // and another time with a QMimeData. That's because both can have different results depending + // on the source of the QDropEvent (because of that we can't call it only on the other method). + // Maybe later we should remove the canPasteSelection from inside this method and change the code + // that uses it to call it before. if( canPasteSelection( tcoPos, de ) == false ) { return false; } - QString type = StringPairDrag::decodeKey( de ); - QString value = StringPairDrag::decodeValue( de ); + // Overloaded method so we can call it without a Drag&Drop event + return pasteSelection( tcoPos, mimeData ); +} + +bool TrackContentWidget::pasteSelection( MidiTime tcoPos, const QMimeData * md ) +{ + if( canPasteSelection( tcoPos, md ) == false ) + { + return false; + } + + QString type = StringPairDrag::decodeMimeKey( md ); + QString value = StringPairDrag::decodeMimeValue( md ); getTrack()->addJournalCheckPoint(); From c14e6adb8dd74dae9ff3ad328b730fbbde43ac87 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 31 Jul 2020 16:27:01 -0300 Subject: [PATCH 05/20] Adds the TCO type to the clipboard Adds the key with the TCO type ("tco_" + type number + ":" + TCO Data XML) to the clipboard, so it can be later used by the canPasteSelection and pasteSelection methods. --- src/core/Track.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 7a549b925fa..de12d2a049a 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1196,9 +1196,12 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) // Write the TCOs to a DataFile for copying DataFile dataFile = createTCODataFiles( tcoViews ); + // Add the TCO type as a key to the final string + QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); + // Copy it to the clipboard QMimeData *tco_content = new QMimeData; - tco_content->setData( Clipboard::mimeType(), dataFile.toString().toUtf8() ); + tco_content->setData( Clipboard::mimeType(), finalString.toUtf8() ); QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); } else @@ -1224,9 +1227,12 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) // Write the TCOs to a DataFile for copying DataFile dataFile = createTCODataFiles( tcoViews ); + // Add the TCO type as a key to the final string + QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); + // Copy it to the clipboard QMimeData *tco_content = new QMimeData; - tco_content->setData( Clipboard::mimeType(), dataFile.toString().toUtf8() ); + tco_content->setData( Clipboard::mimeType(), finalString.toUtf8() ); QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); } else From 09d7298c7db9a4a565fe09b1dcbebb4c475723d0 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 31 Jul 2020 16:56:26 -0300 Subject: [PATCH 06/20] Apply changes to "src/tracks/SampleTrack.cpp" Change the SampleTCOView::contextMenuEvent() method so it behaves the same way as the TrackContentObjectView::contextMenuEvent() method. For that, I had to change the ContextMenuAction enum and the contextMenuAction methods to be protected instead of private, so SampleTCOView can access it. --- include/Track.h | 21 ++++++++++----------- src/tracks/SampleTrack.cpp | 10 +++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/include/Track.h b/include/Track.h index 25f194ecc2c..cac8658db9c 100644 --- a/include/Track.h +++ b/include/Track.h @@ -257,11 +257,21 @@ public slots: void update() override; protected: + enum ContextMenuAction + { + Remove, + Cut, + Copy, + Paste, + Mute + }; + virtual void constructContextMenu( QMenu * ) { } void contextMenuEvent( QContextMenuEvent * cme ) override; + void contextMenuAction( ContextMenuAction action ); void dragEnterEvent( QDragEnterEvent * dee ) override; void dropEvent( QDropEvent * de ) override; void leaveEvent( QEvent * e ) override; @@ -299,15 +309,6 @@ protected slots: ToggleSelected } ; - enum ContextMenuAction - { - Remove, - Cut, - Copy, - Paste, - Mute - }; - static TextFloat * s_textFloat; TrackContentObject * m_tco; @@ -343,8 +344,6 @@ protected slots: } void setInitialOffsets(); - void contextMenuAction( ContextMenuAction action ); - bool mouseMovedDistance( QMouseEvent * me, int distance ); MidiTime draggedTCOPos( QMouseEvent * me ); } ; diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 86e1861c691..accd603476e 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -353,19 +353,19 @@ void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) { contextMenu.addAction( embed::getIconPixmap( "cancel" ), tr( "Delete (middle mousebutton)" ), - this, SLOT( remove() ) ); + [this](){ contextMenuAction( Remove ); } ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "edit_cut" ), - tr( "Cut" ), this, SLOT( cut() ) ); + tr( "Cut" ), [this](){ contextMenuAction( Cut ); } ); } contextMenu.addAction( embed::getIconPixmap( "edit_copy" ), - tr( "Copy" ), m_tco, SLOT( copy() ) ); + tr( "Copy" ), [this](){ contextMenuAction( Copy ); } ); contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), - tr( "Paste" ), m_tco, SLOT( paste() ) ); + tr( "Paste" ), [this](){ contextMenuAction( Paste ); } ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "muted" ), tr( "Mute/unmute (<%1> + middle click)" ).arg(UI_CTRL_KEY), - m_tco, SLOT( toggleMute() ) ); + [this](){ contextMenuAction( Mute ); } ); /*contextMenu.addAction( embed::getIconPixmap( "record" ), tr( "Set/clear record" ), m_tco, SLOT( toggleRecord() ) );*/ From c0688c37ab0cd19df4a3accbd227d94793f724e1 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 31 Jul 2020 17:56:09 -0300 Subject: [PATCH 07/20] Implement the paste action Now that the canPasteSelection and pasteSelection methods were overloaded, it was possible to implement the paste action using the same logic as the Drag&Drop copy/paste. Other small changes: - Removes the TCO views AFTER creating the TCO data file, instead of before (which probably resulted in an empty data file). - Uses the StringPairDrag::mimeType() instead of Clipboard::mimeType() since that's the one the methods from StringPairDrag.cpp recognizes. --- src/core/Track.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index de12d2a049a..d8270267ac0 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1154,7 +1154,6 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) // List of selected TCOs QVector so = gui->songEditor()->m_editor->selectedObjects(); - // TODO: Make it possible to paste multiple selected TCOs through the context menu switch( action ) { case Remove: @@ -1189,19 +1188,25 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) if( tcov != NULL ) { tcoViews.push_back( tcov ); - tcov->remove(); // We are cutting now } } // Write the TCOs to a DataFile for copying DataFile dataFile = createTCODataFiles( tcoViews ); + // Now that the dataFile is created we can delete the tracks, since we are cutting + for( QVector::iterator it = tcoViews.begin(); + it != tcoViews.end(); ++it ) + { + ( *it )->remove(); + } + // Add the TCO type as a key to the final string QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); // Copy it to the clipboard QMimeData *tco_content = new QMimeData; - tco_content->setData( Clipboard::mimeType(), finalString.toUtf8() ); + tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); } else @@ -1232,7 +1237,7 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) // Copy it to the clipboard QMimeData *tco_content = new QMimeData; - tco_content->setData( Clipboard::mimeType(), finalString.toUtf8() ); + tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); } else @@ -1245,11 +1250,19 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) // clear the QApplication Clipboard during the LMMS Clipboard copy operations (Clipboard::copy does that) // If we have TCO data on the clipboard paste it. If not, do our regular TCO paste. - if( QApplication::clipboard()->mimeData( QClipboard::Clipboard )->hasFormat( Clipboard::mimeType() ) ) + if( QApplication::clipboard()->mimeData( QClipboard::Clipboard )->hasFormat( StringPairDrag::mimeType() ) ) { - // TODO: Implement the actual paste operation - qWarning("Context Menu Paste - Data:"); - qWarning() << QApplication::clipboard()->mimeData( QClipboard::Clipboard )->data( Clipboard::mimeType() ); + // Paste the selection on the MidiTime of the selected Track + const QMimeData *md = QApplication::clipboard()->mimeData( QClipboard::Clipboard ); + MidiTime tcoPos = MidiTime( m_tco->startPosition() ); + + TrackContentWidget *tcw = getTrackView()->getTrackContentWidget(); + + if( tcw->pasteSelection( tcoPos, md ) == true ) + { + // If we succeed on the paste we delete the TCO we pasted on + remove(); + } } else { From 5c0d6de6f8bf62b6179fe9f08bb0939e4f8a35e4 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 31 Jul 2020 18:01:37 -0300 Subject: [PATCH 08/20] Removes QDebug header Forgot to remove the QDebug header on the last commit. --- src/core/Track.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index d8270267ac0..b573513cbb4 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -46,7 +46,6 @@ #include #include #include -#include #include "AutomationPattern.h" From 76df232c829559f4c45c2192b9155ee2f75d8eb5 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 31 Jul 2020 19:51:50 -0300 Subject: [PATCH 09/20] Creates a Context Menu on the TCW for "paste" Now it's possible to paste a selection of copied TCOs anywhere in the TCW, as long as the rules to paste are met (same rules as Drag&Drop copy/paste). If the rules are not met the "Paste" menu action shows but is disabled. --- include/Track.h | 7 +++++++ src/core/Track.cpp | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/include/Track.h b/include/Track.h index cac8658db9c..2d91d0bcc41 100644 --- a/include/Track.h +++ b/include/Track.h @@ -403,6 +403,13 @@ public slots: void changePosition( const MidiTime & newPos = MidiTime( -1 ) ); protected: + enum ContextMenuAction + { + Paste + }; + + void contextMenuEvent( QContextMenuEvent * cme ) override; + void contextMenuAction( QContextMenuEvent * cme, ContextMenuAction action ); void dragEnterEvent( QDragEnterEvent * dee ) override; void dropEvent( QDropEvent * de ) override; void mousePressEvent( QMouseEvent * me ) override; diff --git a/src/core/Track.cpp b/src/core/Track.cpp index b573513cbb4..971c94289b2 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1972,6 +1972,43 @@ MidiTime TrackContentWidget::endPosition( const MidiTime & posStart ) return posStart + static_cast( w * MidiTime::ticksPerBar() / ppb ); } +void TrackContentWidget::contextMenuEvent( QContextMenuEvent * cme ) +{ + if( cme->modifiers() ) + { + return; + } + + // If we don't have TCO data in the clipboard there's no need to create this menu + // since "paste" is the only action at the moment. + const QMimeData *md = QApplication::clipboard()->mimeData( QClipboard::Clipboard ); + if( !md->hasFormat( StringPairDrag::mimeType() ) ) + { + return; + } + + QMenu contextMenu( this ); + QAction *pasteA = contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), + tr( "Paste" ), [this, cme](){ contextMenuAction( cme, Paste ); } ); + // If we can't paste in the current TCW for some reason, disable the action so the user knows + pasteA->setEnabled( canPasteSelection( getPosition( cme->x() ), md ) ? true : false ); + + contextMenu.exec( QCursor::pos() ); +} + +void TrackContentWidget::contextMenuAction( QContextMenuEvent * cme, ContextMenuAction action ) +{ + switch( action ) + { + case Paste: + // Paste the selection on the MidiTime of the context menu click + const QMimeData *md = QApplication::clipboard()->mimeData( QClipboard::Clipboard ); + MidiTime tcoPos = getPosition( cme->x() ); + + pasteSelection( tcoPos, md ); + break; + } +} From f8cb3f59b42182e36ba0ecfb025d0be84fd8119e Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 31 Jul 2020 20:49:25 -0300 Subject: [PATCH 10/20] Small code refactoring Saving a few lines of code. --- src/core/Track.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 971c94289b2..ff4540fb037 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1669,14 +1669,9 @@ bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QDropEvent* d // Overloaded method to make it possible to call this method without a Drag&Drop event // If the source of the DropEvent is the current instance of LMMS we don't allow pasting in the same bar // If the source of the DropEvent is another instance of LMMS we allow it - if( de->source() ) - { - return canPasteSelection( tcoPos, mimeData ); - } - else - { - return canPasteSelection( tcoPos, mimeData, true ); - } + return de->source() + ? canPasteSelection( tcoPos, mimeData ) + : canPasteSelection( tcoPos, mimeData, true ); } bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QMimeData* md , bool allowSameBar ) @@ -1709,15 +1704,12 @@ bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QMimeData* md const TrackContainer::TrackList tracks = t->trackContainer()->tracks(); const int currentTrackIndex = tracks.indexOf( t ); - // Don't paste if we're on the same bar + // Don't paste if we're on the same bar and allowSameBar is false auto sourceTrackContainerId = metadata.attributeNode( "trackContainerId" ).value().toUInt(); - if( !allowSameBar ) + if( !allowSameBar && sourceTrackContainerId == t->trackContainer()->id() && + tcoPos == grabbedTCOBar && currentTrackIndex == initialTrackIndex ) { - if( sourceTrackContainerId == t->trackContainer()->id() && - tcoPos == grabbedTCOBar && currentTrackIndex == initialTrackIndex ) - { - return false; - } + return false; } // Extract the tco data @@ -2001,7 +1993,7 @@ void TrackContentWidget::contextMenuAction( QContextMenuEvent * cme, ContextMenu switch( action ) { case Paste: - // Paste the selection on the MidiTime of the context menu click + // Paste the selection on the MidiTime of the context menu event const QMimeData *md = QApplication::clipboard()->mimeData( QClipboard::Clipboard ); MidiTime tcoPos = getPosition( cme->x() ); From 2cf3f71ed58f4c58db623d4059fb88e9e73429bf Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 31 Jul 2020 23:01:36 -0300 Subject: [PATCH 11/20] Avoids double call to canPasteSelection This commit adds a third parameter to the pasteSelection overloaded method, which will define whether we whould skip the canPasteSelection check. The only situation where we will want to skip it is if we are calling pasteSelection from inside the other pasteSelection method, which will already have checked it. Because of that the default value is false. Organizes comments as well. --- include/Track.h | 2 +- src/core/Track.cpp | 23 ++++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/include/Track.h b/include/Track.h index 2d91d0bcc41..7749651f47d 100644 --- a/include/Track.h +++ b/include/Track.h @@ -382,7 +382,7 @@ class TrackContentWidget : public QWidget, public JournallingObject bool canPasteSelection( MidiTime tcoPos, const QDropEvent *de ); bool canPasteSelection( MidiTime tcoPos, const QMimeData *md, bool allowSameBar = false ); bool pasteSelection( MidiTime tcoPos, QDropEvent * de ); - bool pasteSelection( MidiTime tcoPos, const QMimeData * md ); + bool pasteSelection( MidiTime tcoPos, const QMimeData * md, bool skipSafetyCheck = false ); MidiTime endPosition( const MidiTime & posStart ); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index ff4540fb037..478672c1116 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1146,8 +1146,6 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) } // This method processes the actions from the context menu of the TCO View. -// We use this method so we can check if there are more selected TCOs to apply -// some of the actions to all of them. void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) { // List of selected TCOs @@ -1176,6 +1174,7 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) } break; case Cut: + // Checks if there are other selected TCOs and if so cut them as well if( so.size() > 0 ){ // List of TCOs to be copied QVector tcoViews; @@ -1214,6 +1213,7 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) } break; case Copy: + // Checks if there are other selected TCOs and if so copy them as well if( so.size() > 0 ){ // List of TCOs to be copied QVector tcoViews; @@ -1666,14 +1666,14 @@ bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QDropEvent* d { const QMimeData * mimeData = de->mimeData(); - // Overloaded method to make it possible to call this method without a Drag&Drop event // If the source of the DropEvent is the current instance of LMMS we don't allow pasting in the same bar - // If the source of the DropEvent is another instance of LMMS we allow it + // if it's another instance of LMMS we allow it return de->source() ? canPasteSelection( tcoPos, mimeData ) : canPasteSelection( tcoPos, mimeData, true ); } +// Overloaded method to make it possible to call this method without a Drag&Drop event bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QMimeData* md , bool allowSameBar ) { Track * t = getTrack(); @@ -1750,23 +1750,20 @@ bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de ) { const QMimeData * mimeData = de->mimeData(); - // TODO: If we use this method, we end up calling canPasteSelection twice: Once with a QDropEevnt - // and another time with a QMimeData. That's because both can have different results depending - // on the source of the QDropEvent (because of that we can't call it only on the other method). - // Maybe later we should remove the canPasteSelection from inside this method and change the code - // that uses it to call it before. if( canPasteSelection( tcoPos, de ) == false ) { return false; } - // Overloaded method so we can call it without a Drag&Drop event - return pasteSelection( tcoPos, mimeData ); + // We set skipSafetyCheck to true because we already called canPasteSelection + return pasteSelection( tcoPos, mimeData, true ); } -bool TrackContentWidget::pasteSelection( MidiTime tcoPos, const QMimeData * md ) +// Overloaded method so we can call it without a Drag&Drop event +bool TrackContentWidget::pasteSelection( MidiTime tcoPos, const QMimeData * md, bool skipSafetyCheck ) { - if( canPasteSelection( tcoPos, md ) == false ) + // When canPasteSelection was already called before, skipSafetyCheck will skip this + if( !skipSafetyCheck && canPasteSelection( tcoPos, md ) == false ) { return false; } From 28e2a18fe069e9153ed252d87d0092771f2f3528 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Sat, 1 Aug 2020 13:38:39 -0300 Subject: [PATCH 12/20] Separates methods for the actions on selections Now the remove, copy, cut, paste and toggle mute actions have a separate method for applying them to selections, which are then called from the contextMenuAction method. That made the code more organized and the contextMenuAction method smaller. Also, the mouse shortcuts for muting and removing (CTRL+middle button, middle button, CTRL+right button) now apply the action on selections as well. --- include/Track.h | 7 ++ src/core/Track.cpp | 252 ++++++++++++++++++++++++++++----------------- 2 files changed, 164 insertions(+), 95 deletions(-) diff --git a/include/Track.h b/include/Track.h index 7749651f47d..6d9b81ed80d 100644 --- a/include/Track.h +++ b/include/Track.h @@ -250,6 +250,13 @@ class TrackContentObjectView : public selectableObject, public ModelView bool needsUpdate(); void setNeedsUpdate( bool b ); + // Methods to remove, copy, cut, paste and mute selections of TCO views + void removeSelection( QVector so ); + void copySelection( QVector so ); + void cutSelection( QVector so ); + void pasteSelection(); // TODO: Rethink naming? + void toggleMuteSelection( QVector so ); + public slots: virtual bool close(); void cut(); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 478672c1116..dcd9fe95a6c 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -735,6 +735,9 @@ void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & pai */ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { + // List of selected TCOs + QVector so = gui->songEditor()->m_editor->selectedObjects(); + setInitialPos( me->pos() ); setInitialOffsets(); if( !fixedTCOs() && me->button() == Qt::LeftButton ) @@ -827,22 +830,50 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { if( me->modifiers() & Qt::ControlModifier ) { - m_tco->toggleMute(); + if( so.size() > 0 ) + { + toggleMuteSelection( so ); + } + else + { + m_tco->toggleMute(); + } } else if( me->modifiers() & Qt::ShiftModifier && !fixedTCOs() ) { - remove(); + if( so.size() > 0 ) + { + removeSelection( so ); + } + else + { + remove(); + } } } else if( me->button() == Qt::MidButton ) { if( me->modifiers() & Qt::ControlModifier ) { - m_tco->toggleMute(); + if( so.size() > 0 ) + { + toggleMuteSelection( so ); + } + else + { + m_tco->toggleMute(); + } } else if( !fixedTCOs() ) { - remove(); + if( so.size() > 0 ) + { + removeSelection( so ); + } + else + { + remove(); + } } } } @@ -1154,19 +1185,9 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) switch( action ) { case Remove: - // Checks if there are other selected TCOs and if so removes them as well - if( so.size() > 0 ){ - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView *tcov = - dynamic_cast( *it ); - - if( tcov != NULL ) - { - tcov->remove(); - } - } + if( so.size() > 0) + { + removeSelection( so ); } else { @@ -1175,37 +1196,9 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) break; case Cut: // Checks if there are other selected TCOs and if so cut them as well - if( so.size() > 0 ){ - // List of TCOs to be copied - QVector tcoViews; - - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView *tcov = dynamic_cast( *it ); - if( tcov != NULL ) - { - tcoViews.push_back( tcov ); - } - } - - // Write the TCOs to a DataFile for copying - DataFile dataFile = createTCODataFiles( tcoViews ); - - // Now that the dataFile is created we can delete the tracks, since we are cutting - for( QVector::iterator it = tcoViews.begin(); - it != tcoViews.end(); ++it ) - { - ( *it )->remove(); - } - - // Add the TCO type as a key to the final string - QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); - - // Copy it to the clipboard - QMimeData *tco_content = new QMimeData; - tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); - QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); + if( so.size() > 0 ) + { + cutSelection( so ); } else { @@ -1214,30 +1207,9 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) break; case Copy: // Checks if there are other selected TCOs and if so copy them as well - if( so.size() > 0 ){ - // List of TCOs to be copied - QVector tcoViews; - - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView *tcov = dynamic_cast( *it ); - if( tcov != NULL ) - { - tcoViews.push_back( tcov ); - } - } - - // Write the TCOs to a DataFile for copying - DataFile dataFile = createTCODataFiles( tcoViews ); - - // Add the TCO type as a key to the final string - QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); - - // Copy it to the clipboard - QMimeData *tco_content = new QMimeData; - tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); - QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); + if( so.size() > 0 ) + { + copySelection( so ); } else { @@ -1251,17 +1223,7 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) // If we have TCO data on the clipboard paste it. If not, do our regular TCO paste. if( QApplication::clipboard()->mimeData( QClipboard::Clipboard )->hasFormat( StringPairDrag::mimeType() ) ) { - // Paste the selection on the MidiTime of the selected Track - const QMimeData *md = QApplication::clipboard()->mimeData( QClipboard::Clipboard ); - MidiTime tcoPos = MidiTime( m_tco->startPosition() ); - - TrackContentWidget *tcw = getTrackView()->getTrackContentWidget(); - - if( tcw->pasteSelection( tcoPos, md ) == true ) - { - // If we succeed on the paste we delete the TCO we pasted on - remove(); - } + pasteSelection(); } else { @@ -1270,18 +1232,9 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) break; case Mute: // Checks if there are other selected TCOs and if so mutes them as well - if( so.size() > 0 ){ - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView *tcov = - dynamic_cast( *it ); - - if( tcov != NULL ) - { - tcov->getTrackContentObject()->toggleMute(); - } - } + if( so.size() > 0 ) + { + toggleMuteSelection( so ); } else { @@ -1291,6 +1244,115 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) } } +void TrackContentObjectView::removeSelection( QVector so ) +{ + // Checks if there are other selected TCOs and if so removes them as well + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView *tcov = + dynamic_cast( *it ); + + if( tcov != NULL ) + { + tcov->remove(); + } + } +} + +void TrackContentObjectView::copySelection( QVector so ) +{ + // List of TCOs to be copied + QVector tcoViews; + + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView *tcov = dynamic_cast( *it ); + if( tcov != NULL ) + { + tcoViews.push_back( tcov ); + } + } + + // Write the TCOs to a DataFile for copying + DataFile dataFile = createTCODataFiles( tcoViews ); + + // Add the TCO type as a key to the final string + QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); + + // Copy it to the clipboard + QMimeData *tco_content = new QMimeData; + tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); + QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); +} + +void TrackContentObjectView::cutSelection( QVector so ) +{ + // List of TCOs to be copied + QVector tcoViews; + + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView *tcov = dynamic_cast( *it ); + if( tcov != NULL ) + { + tcoViews.push_back( tcov ); + } + } + + // Write the TCOs to a DataFile for copying + DataFile dataFile = createTCODataFiles( tcoViews ); + + // TODO: Replace with removeSelection( so )? + // Now that the dataFile is created we can delete the tracks, since we are cutting + for( QVector::iterator it = tcoViews.begin(); + it != tcoViews.end(); ++it ) + { + ( *it )->remove(); + } + + // Add the TCO type as a key to the final string + QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); + + // Copy it to the clipboard + QMimeData *tco_content = new QMimeData; + tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); + QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); +} + +// TODO: Is it a good name for the method or can it be mistaken with TrackContentWidget::pasteSelection()? +void TrackContentObjectView::pasteSelection() +{ + // Paste the selection on the MidiTime of the selected Track + const QMimeData *md = QApplication::clipboard()->mimeData( QClipboard::Clipboard ); + MidiTime tcoPos = MidiTime( m_tco->startPosition() ); + + TrackContentWidget *tcw = getTrackView()->getTrackContentWidget(); + + if( tcw->pasteSelection( tcoPos, md ) == true ) + { + // If we succeed on the paste we delete the TCO we pasted on + remove(); + } +} + +void TrackContentObjectView::toggleMuteSelection( QVector so ) +{ + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView *tcov = + dynamic_cast( *it ); + + if( tcov != NULL ) + { + tcov->getTrackContentObject()->toggleMute(); + } + } +} + From 33516eca5d84c4b5d73669f19930ab5b09e28aa3 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Sat, 1 Aug 2020 21:07:38 -0300 Subject: [PATCH 13/20] Fixes small bug and reorganize code Fixes a small bug where using a mouse shortcut or choosing an action on a TCO that is not selected while other TCOs were selected would result in the selection being affected. Also reorganizes the code so the conditional for choosing between the selection action and the individual action stays inside the method. --- src/core/Track.cpp | 216 ++++++++++++++++++++------------------------- 1 file changed, 94 insertions(+), 122 deletions(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index dcd9fe95a6c..57f4d178ae3 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -830,50 +830,22 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { if( me->modifiers() & Qt::ControlModifier ) { - if( so.size() > 0 ) - { - toggleMuteSelection( so ); - } - else - { - m_tco->toggleMute(); - } + toggleMuteSelection( so ); } else if( me->modifiers() & Qt::ShiftModifier && !fixedTCOs() ) { - if( so.size() > 0 ) - { - removeSelection( so ); - } - else - { - remove(); - } + removeSelection( so ); } } else if( me->button() == Qt::MidButton ) { if( me->modifiers() & Qt::ControlModifier ) { - if( so.size() > 0 ) - { - toggleMuteSelection( so ); - } - else - { - m_tco->toggleMute(); - } + toggleMuteSelection( so ); } else if( !fixedTCOs() ) { - if( so.size() > 0 ) - { - removeSelection( so ); - } - else - { - remove(); - } + removeSelection( so ); } } } @@ -1185,36 +1157,13 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) switch( action ) { case Remove: - if( so.size() > 0) - { - removeSelection( so ); - } - else - { - remove(); - } + removeSelection( so ); break; case Cut: - // Checks if there are other selected TCOs and if so cut them as well - if( so.size() > 0 ) - { - cutSelection( so ); - } - else - { - cut(); - } + cutSelection( so ); break; case Copy: - // Checks if there are other selected TCOs and if so copy them as well - if( so.size() > 0 ) - { - copySelection( so ); - } - else - { - getTrackContentObject()->copy(); - } + copySelection( so ); break; case Paste: // NOTE: Because we give preference to the QApplication clipboard over the LMMS Clipboard class, we need to @@ -1231,15 +1180,7 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) } break; case Mute: - // Checks if there are other selected TCOs and if so mutes them as well - if( so.size() > 0 ) - { - toggleMuteSelection( so ); - } - else - { - getTrackContentObject()->toggleMute(); - } + toggleMuteSelection( so ); break; } } @@ -1247,79 +1188,102 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) void TrackContentObjectView::removeSelection( QVector so ) { // Checks if there are other selected TCOs and if so removes them as well - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) + if( so.size() > 0 && isSelected() ) { - TrackContentObjectView *tcov = - dynamic_cast( *it ); - - if( tcov != NULL ) + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) { - tcov->remove(); + TrackContentObjectView *tcov = + dynamic_cast( *it ); + + if( tcov != NULL ) + { + tcov->remove(); + } } } + else + { + remove(); + } } void TrackContentObjectView::copySelection( QVector so ) { - // List of TCOs to be copied - QVector tcoViews; - - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) + // Checks if there are other selected TCOs and if so copy them as well + if( so.size() > 0 && isSelected() ) { - TrackContentObjectView *tcov = dynamic_cast( *it ); - if( tcov != NULL ) + // List of TCOs to be copied + QVector tcoViews; + + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) { - tcoViews.push_back( tcov ); + TrackContentObjectView *tcov = dynamic_cast( *it ); + if( tcov != NULL ) + { + tcoViews.push_back( tcov ); + } } - } - // Write the TCOs to a DataFile for copying - DataFile dataFile = createTCODataFiles( tcoViews ); + // Write the TCOs to a DataFile for copying + DataFile dataFile = createTCODataFiles( tcoViews ); - // Add the TCO type as a key to the final string - QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); + // Add the TCO type as a key to the final string + QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); - // Copy it to the clipboard - QMimeData *tco_content = new QMimeData; - tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); - QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); + // Copy it to the clipboard + QMimeData *tco_content = new QMimeData; + tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); + QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); + } + else + { + getTrackContentObject()->copy(); + } } void TrackContentObjectView::cutSelection( QVector so ) { - // List of TCOs to be copied - QVector tcoViews; - - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) + // Checks if there are other selected TCOs and if so cut them as well + if( so.size() > 0 && isSelected() ) { - TrackContentObjectView *tcov = dynamic_cast( *it ); - if( tcov != NULL ) + // List of TCOs to be copied + QVector tcoViews; + + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) { - tcoViews.push_back( tcov ); + TrackContentObjectView *tcov = dynamic_cast( *it ); + if( tcov != NULL ) + { + tcoViews.push_back( tcov ); + } } - } - // Write the TCOs to a DataFile for copying - DataFile dataFile = createTCODataFiles( tcoViews ); + // Write the TCOs to a DataFile for copying + DataFile dataFile = createTCODataFiles( tcoViews ); - // TODO: Replace with removeSelection( so )? - // Now that the dataFile is created we can delete the tracks, since we are cutting - for( QVector::iterator it = tcoViews.begin(); - it != tcoViews.end(); ++it ) - { - ( *it )->remove(); - } + // TODO: Replace with removeSelection( so )? + // Now that the dataFile is created we can delete the tracks, since we are cutting + for( QVector::iterator it = tcoViews.begin(); + it != tcoViews.end(); ++it ) + { + ( *it )->remove(); + } - // Add the TCO type as a key to the final string - QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); + // Add the TCO type as a key to the final string + QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); - // Copy it to the clipboard - QMimeData *tco_content = new QMimeData; - tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); - QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); + // Copy it to the clipboard + QMimeData *tco_content = new QMimeData; + tco_content->setData( StringPairDrag::mimeType(), finalString.toUtf8() ); + QApplication::clipboard()->setMimeData( tco_content, QClipboard::Clipboard ); + } + else + { + cut(); + } } // TODO: Is it a good name for the method or can it be mistaken with TrackContentWidget::pasteSelection()? @@ -1340,17 +1304,25 @@ void TrackContentObjectView::pasteSelection() void TrackContentObjectView::toggleMuteSelection( QVector so ) { - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) + // Checks if there are other selected TCOs and if so mutes them as well + if( so.size() > 0 && isSelected() ) { - TrackContentObjectView *tcov = - dynamic_cast( *it ); - - if( tcov != NULL ) + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) { - tcov->getTrackContentObject()->toggleMute(); + TrackContentObjectView *tcov = + dynamic_cast( *it ); + + if( tcov != NULL ) + { + tcov->getTrackContentObject()->toggleMute(); + } } } + else + { + getTrackContentObject()->toggleMute(); + } } From 511a2db59a17ae06f0ae7d3a4461228b6738e9c1 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Sat, 1 Aug 2020 22:35:36 -0300 Subject: [PATCH 14/20] Move logic to the action methods Since the methods that called the action+Selection() methods didn't need the list of selectableObjects, I moved it to the inside of the action+Selection() methods and removed the parameter from them. --- include/Track.h | 10 +++++----- src/core/Track.cpp | 48 ++++++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/include/Track.h b/include/Track.h index 6d9b81ed80d..966bf615ead 100644 --- a/include/Track.h +++ b/include/Track.h @@ -250,12 +250,12 @@ class TrackContentObjectView : public selectableObject, public ModelView bool needsUpdate(); void setNeedsUpdate( bool b ); - // Methods to remove, copy, cut, paste and mute selections of TCO views - void removeSelection( QVector so ); - void copySelection( QVector so ); - void cutSelection( QVector so ); + // Methods to remove, copy, cut, paste and mute either selections of TCO views or individual TCOs + void removeSelection(); + void copySelection(); + void cutSelection(); void pasteSelection(); // TODO: Rethink naming? - void toggleMuteSelection( QVector so ); + void toggleMuteSelection(); public slots: virtual bool close(); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 57f4d178ae3..f04a5403488 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -735,8 +735,6 @@ void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & pai */ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { - // List of selected TCOs - QVector so = gui->songEditor()->m_editor->selectedObjects(); setInitialPos( me->pos() ); setInitialOffsets(); @@ -830,22 +828,22 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { if( me->modifiers() & Qt::ControlModifier ) { - toggleMuteSelection( so ); + toggleMuteSelection(); } else if( me->modifiers() & Qt::ShiftModifier && !fixedTCOs() ) { - removeSelection( so ); + removeSelection(); } } else if( me->button() == Qt::MidButton ) { if( me->modifiers() & Qt::ControlModifier ) { - toggleMuteSelection( so ); + toggleMuteSelection(); } else if( !fixedTCOs() ) { - removeSelection( so ); + removeSelection(); } } } @@ -1151,19 +1149,16 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) // This method processes the actions from the context menu of the TCO View. void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) { - // List of selected TCOs - QVector so = gui->songEditor()->m_editor->selectedObjects(); - switch( action ) { case Remove: - removeSelection( so ); + removeSelection(); break; case Cut: - cutSelection( so ); + cutSelection(); break; case Copy: - copySelection( so ); + copySelection(); break; case Paste: // NOTE: Because we give preference to the QApplication clipboard over the LMMS Clipboard class, we need to @@ -1180,13 +1175,16 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) } break; case Mute: - toggleMuteSelection( so ); + toggleMuteSelection(); break; } } -void TrackContentObjectView::removeSelection( QVector so ) +void TrackContentObjectView::removeSelection() { + // List of selected TCOs + QVector so = gui->songEditor()->m_editor->selectedObjects(); + // Checks if there are other selected TCOs and if so removes them as well if( so.size() > 0 && isSelected() ) { @@ -1208,8 +1206,11 @@ void TrackContentObjectView::removeSelection( QVector so ) } } -void TrackContentObjectView::copySelection( QVector so ) +void TrackContentObjectView::copySelection() { + // List of selected TCOs + QVector so = gui->songEditor()->m_editor->selectedObjects(); + // Checks if there are other selected TCOs and if so copy them as well if( so.size() > 0 && isSelected() ) { @@ -1243,8 +1244,11 @@ void TrackContentObjectView::copySelection( QVector so ) } } -void TrackContentObjectView::cutSelection( QVector so ) +void TrackContentObjectView::cutSelection() { + // List of selected TCOs + QVector so = gui->songEditor()->m_editor->selectedObjects(); + // Checks if there are other selected TCOs and if so cut them as well if( so.size() > 0 && isSelected() ) { @@ -1264,13 +1268,8 @@ void TrackContentObjectView::cutSelection( QVector so ) // Write the TCOs to a DataFile for copying DataFile dataFile = createTCODataFiles( tcoViews ); - // TODO: Replace with removeSelection( so )? // Now that the dataFile is created we can delete the tracks, since we are cutting - for( QVector::iterator it = tcoViews.begin(); - it != tcoViews.end(); ++it ) - { - ( *it )->remove(); - } + removeSelection(); // Add the TCO type as a key to the final string QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); @@ -1302,8 +1301,11 @@ void TrackContentObjectView::pasteSelection() } } -void TrackContentObjectView::toggleMuteSelection( QVector so ) +void TrackContentObjectView::toggleMuteSelection() { + // List of selected TCOs + QVector so = gui->songEditor()->m_editor->selectedObjects(); + // Checks if there are other selected TCOs and if so mutes them as well if( so.size() > 0 && isSelected() ) { From 716fe02374b9d981dbf7ac2e6d095f8214a91af3 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Sun, 2 Aug 2020 16:15:44 -0300 Subject: [PATCH 15/20] Changes logic structure and labels As suggested by Spekular, the logic structure was changed. Now, the mousePressEvent and contextMenuAction methods determine whether the action should happen to a selection of TCOs or an individual TCO. The list of TCOs to be affected populate a QVector list called "active", which is parsed to the action method that will apply the action to each object in the list. I changed the method names to removeActive, cutActive, copyActive and toggleMuteActive, since I believe that better describe the behavior. The paste method is still called pasteSelection for now, because the paste behavior isn't related to the active TCOs but to the content of the clipboard. The contextMenuEvent method on both src/core/Track.cpp and src/tracks/SampleTrack.cpp were changed so they also check if the right-clicked TCO is part of a selection or an individual TCO, and the labels for the actions are changed to better describe the behavior (i.e.: "Delete" for individual TCO and "Delete selection" for multiple TCOs). --- include/Track.h | 10 +- src/core/Track.cpp | 194 +++++++++++++++++++------------------ src/tracks/SampleTrack.cpp | 40 +++++++- 3 files changed, 138 insertions(+), 106 deletions(-) diff --git a/include/Track.h b/include/Track.h index 966bf615ead..13906f43948 100644 --- a/include/Track.h +++ b/include/Track.h @@ -250,12 +250,12 @@ class TrackContentObjectView : public selectableObject, public ModelView bool needsUpdate(); void setNeedsUpdate( bool b ); - // Methods to remove, copy, cut, paste and mute either selections of TCO views or individual TCOs - void removeSelection(); - void copySelection(); - void cutSelection(); + // Methods to remove, copy, cut, paste and mute a QVector of TCO views + void removeActive( QVector tcovs ); + void copyActive( QVector tcovs ); + void cutActive( QVector tcovs ); void pasteSelection(); // TODO: Rethink naming? - void toggleMuteSelection(); + void toggleMuteActive( QVector tcovs ); public slots: virtual bool close(); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index f04a5403488..df0fbc8a415 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -735,6 +735,24 @@ void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & pai */ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { + // Get a list of selected selectableObjects + QVector sos = gui->songEditor()->m_editor->selectedObjects(); + + // Convert to a list of selected TCOVs + QVector selection; + selection.reserve( sos.size() ); + for( auto so: sos ) + { + TrackContentObjectView *tcov = dynamic_cast ( so ); + if( tcov != nullptr ) + { + selection.append( tcov ); + } + } + + // If we clicked part of the selection, affect all selected clips. Otherwise affect the clip we clicked + QVector active = selection.contains( this ) ? selection : QVector( 1, this ); + // active will be later used for the removeActive, copyActive, cutActive or toggleMuteActive methods setInitialPos( me->pos() ); setInitialOffsets(); @@ -828,22 +846,22 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { if( me->modifiers() & Qt::ControlModifier ) { - toggleMuteSelection(); + toggleMuteActive( active ); } else if( me->modifiers() & Qt::ShiftModifier && !fixedTCOs() ) { - removeSelection(); + removeActive( active ); } } else if( me->button() == Qt::MidButton ) { if( me->modifiers() & Qt::ControlModifier ) { - toggleMuteSelection(); + toggleMuteActive( active ); } else if( !fixedTCOs() ) { - removeSelection(); + removeActive( active ); } } } @@ -1118,6 +1136,26 @@ void TrackContentObjectView::mouseReleaseEvent( QMouseEvent * me ) */ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) { + // Depending on whether we right-clicked a selection or an individual TCO we will have + // different labels for the actions. + // Get a list of selected selectableObjects + QVector sos = gui->songEditor()->m_editor->selectedObjects(); + + // Convert to a list of selected TCOVs + QVector selection; + selection.reserve( sos.size() ); + for( auto so: sos ) + { + TrackContentObjectView *tcov = dynamic_cast ( so ); + if( tcov != nullptr ) + { + selection.append( tcov ); + } + } + + // Individual TCO or selection being right-clicked? + bool individualTCO = selection.contains( this ) ? false : true; + if( cme->modifiers() ) { return; @@ -1127,20 +1165,30 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) if( fixedTCOs() == false ) { contextMenu.addAction( embed::getIconPixmap( "cancel" ), - tr( "Delete (middle mousebutton)" ), + tr( individualTCO + ? "Delete (middle mousebutton)" + : "Delete selection (middle mousebutton)" ), [this](){ contextMenuAction( Remove ); } ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "edit_cut" ), - tr( "Cut" ), [this](){ contextMenuAction( Cut ); } ); + tr( individualTCO + ? "Cut" + : "Cut selection" ), + [this](){ contextMenuAction( Cut ); } ); } contextMenu.addAction( embed::getIconPixmap( "edit_copy" ), - tr( "Copy" ), [this](){ contextMenuAction( Copy ); } ); + tr( individualTCO + ? "Copy" + : "Copy selection" ), + [this](){ contextMenuAction( Copy ); } ); contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), tr( "Paste" ), [this](){ contextMenuAction( Paste ); } ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "muted" ), - tr( "Mute/unmute (<%1> + middle click)" ).arg(UI_CTRL_KEY), - [this](){ contextMenuAction( Mute ); } ); + tr( individualTCO + ? "Mute/unmute (<%1> + middle click)" + : "Mute/unmute selection (<%1> + middle click)" ).arg(UI_CTRL_KEY), + [this](){ contextMenuAction( Mute ); } ); constructContextMenu( &contextMenu ); contextMenu.exec( QCursor::pos() ); @@ -1149,16 +1197,35 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) // This method processes the actions from the context menu of the TCO View. void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) { + // Get a list of selected selectableObjects + QVector sos = gui->songEditor()->m_editor->selectedObjects(); + + // Convert to a list of selected TCOVs + QVector selection; + selection.reserve( sos.size() ); + for( auto so: sos ) + { + TrackContentObjectView *tcov = dynamic_cast ( so ); + if( tcov != nullptr ) + { + selection.append( tcov ); + } + } + + // If we clicked part of the selection, affect all selected clips. Otherwise affect the clip we clicked + QVector active = selection.contains( this ) ? selection : QVector( 1, this ); + // active will be later used for the removeActive, copyActive, cutActive or toggleMuteActive methods + switch( action ) { case Remove: - removeSelection(); + removeActive( active ); break; case Cut: - cutSelection(); + cutActive( active ); break; case Copy: - copySelection(); + copyActive( active ); break; case Paste: // NOTE: Because we give preference to the QApplication clipboard over the LMMS Clipboard class, we need to @@ -1175,60 +1242,27 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) } break; case Mute: - toggleMuteSelection(); + toggleMuteActive( active ); break; } } -void TrackContentObjectView::removeSelection() +void TrackContentObjectView::removeActive( QVector tcovs ) { - // List of selected TCOs - QVector so = gui->songEditor()->m_editor->selectedObjects(); - - // Checks if there are other selected TCOs and if so removes them as well - if( so.size() > 0 && isSelected() ) + for( auto tcov: tcovs ) { - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView *tcov = - dynamic_cast( *it ); - - if( tcov != NULL ) - { - tcov->remove(); - } - } - } - else - { - remove(); + // No need to check if it's nullptr because we check when building the QVector + tcov->remove(); } } -void TrackContentObjectView::copySelection() +void TrackContentObjectView::copyActive( QVector tcovs ) { - // List of selected TCOs - QVector so = gui->songEditor()->m_editor->selectedObjects(); - // Checks if there are other selected TCOs and if so copy them as well - if( so.size() > 0 && isSelected() ) + if( tcovs.size() > 1 ) { - // List of TCOs to be copied - QVector tcoViews; - - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView *tcov = dynamic_cast( *it ); - if( tcov != NULL ) - { - tcoViews.push_back( tcov ); - } - } - // Write the TCOs to a DataFile for copying - DataFile dataFile = createTCODataFiles( tcoViews ); + DataFile dataFile = createTCODataFiles( tcovs ); // Add the TCO type as a key to the final string QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); @@ -1240,36 +1274,21 @@ void TrackContentObjectView::copySelection() } else { - getTrackContentObject()->copy(); + tcovs.at(0)->getTrackContentObject()->copy(); } } -void TrackContentObjectView::cutSelection() +void TrackContentObjectView::cutActive( QVector tcovs ) { - // List of selected TCOs - QVector so = gui->songEditor()->m_editor->selectedObjects(); - // Checks if there are other selected TCOs and if so cut them as well - if( so.size() > 0 && isSelected() ) + if( tcovs.size() > 1 ) { - // List of TCOs to be copied - QVector tcoViews; - - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView *tcov = dynamic_cast( *it ); - if( tcov != NULL ) - { - tcoViews.push_back( tcov ); - } - } - // Write the TCOs to a DataFile for copying - DataFile dataFile = createTCODataFiles( tcoViews ); + DataFile dataFile = createTCODataFiles( tcovs ); // Now that the dataFile is created we can delete the tracks, since we are cutting - removeSelection(); + // TODO: Is it safe to call tcov->remove(); on the current TCOV instance? + removeActive( tcovs ); // Add the TCO type as a key to the final string QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); @@ -1281,7 +1300,7 @@ void TrackContentObjectView::cutSelection() } else { - cut(); + tcovs.at(0)->cut(); } } @@ -1301,29 +1320,12 @@ void TrackContentObjectView::pasteSelection() } } -void TrackContentObjectView::toggleMuteSelection() +void TrackContentObjectView::toggleMuteActive( QVector tcovs ) { - // List of selected TCOs - QVector so = gui->songEditor()->m_editor->selectedObjects(); - - // Checks if there are other selected TCOs and if so mutes them as well - if( so.size() > 0 && isSelected() ) - { - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView *tcov = - dynamic_cast( *it ); - - if( tcov != NULL ) - { - tcov->getTrackContentObject()->toggleMute(); - } - } - } - else + for( auto tcov: tcovs ) { - getTrackContentObject()->toggleMute(); + // No need to check for nullptr because we check while building the tcovs QVector + tcov->getTrackContentObject()->toggleMute(); } } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index accd603476e..ea85102ad60 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -343,6 +343,26 @@ void SampleTCOView::updateSample() void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) { + // Depending on whether we right-clicked a selection or an individual TCO we will have + // different labels for the actions. + // Get a list of selected selectableObjects + QVector sos = gui->songEditor()->m_editor->selectedObjects(); + + // Convert to a list of selected TCOVs + QVector selection; + selection.reserve( sos.size() ); + for( auto so: sos ) + { + TrackContentObjectView *tcov = dynamic_cast ( so ); + if( tcov != nullptr ) + { + selection.append( tcov ); + } + } + + // Individual TCO or selection being right-clicked? + bool individualTCO = selection.contains( this ) ? false : true; + if( _cme->modifiers() ) { return; @@ -352,20 +372,30 @@ void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) if( fixedTCOs() == false ) { contextMenu.addAction( embed::getIconPixmap( "cancel" ), - tr( "Delete (middle mousebutton)" ), + tr( individualTCO + ? "Delete (middle mousebutton)" + : "Delete selection (middle mousebutton)" ), [this](){ contextMenuAction( Remove ); } ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "edit_cut" ), - tr( "Cut" ), [this](){ contextMenuAction( Cut ); } ); + tr( individualTCO + ? "Cut" + : "Cut selection" ), + [this](){ contextMenuAction( Cut ); } ); } contextMenu.addAction( embed::getIconPixmap( "edit_copy" ), - tr( "Copy" ), [this](){ contextMenuAction( Copy ); } ); + tr( individualTCO + ? "Copy" + : "Copy selection" ), + [this](){ contextMenuAction( Copy ); } ); contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), tr( "Paste" ), [this](){ contextMenuAction( Paste ); } ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "muted" ), - tr( "Mute/unmute (<%1> + middle click)" ).arg(UI_CTRL_KEY), - [this](){ contextMenuAction( Mute ); } ); + tr( individualTCO + ? "Mute/unmute (<%1> + middle click)" + : "Mute/unmute selection (<%1> + middle click)" ).arg(UI_CTRL_KEY), + [this](){ contextMenuAction( Mute ); } ); /*contextMenu.addAction( embed::getIconPixmap( "record" ), tr( "Set/clear record" ), m_tco, SLOT( toggleRecord() ) );*/ From 24beb4d4608ec630c822af831549288c973f46d5 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Sun, 2 Aug 2020 16:49:48 -0300 Subject: [PATCH 16/20] Make removeActive and toggleMuteActive static removeActive and toggleMuteActive methods are now static so they can be called from anywhere in the code since they don't require a TCO view instance to work. That makes it possible for them to be used in the future if any feature requires this type of action to be called from out of a TCO view instance. The same couldn't be done for the copyActive and cutActive methods because those require an instance of the TCO view to call them, since when copying to the clipboard some metadata is written using information from the object instance. --- include/Track.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/Track.h b/include/Track.h index 13906f43948..c7f372e3a60 100644 --- a/include/Track.h +++ b/include/Track.h @@ -251,11 +251,15 @@ class TrackContentObjectView : public selectableObject, public ModelView void setNeedsUpdate( bool b ); // Methods to remove, copy, cut, paste and mute a QVector of TCO views - void removeActive( QVector tcovs ); void copyActive( QVector tcovs ); void cutActive( QVector tcovs ); void pasteSelection(); // TODO: Rethink naming? - void toggleMuteActive( QVector tcovs ); + // removeActive and toggleMuteActive are static because they don't depend + // being called from a particular TCO view, but can be called anywhere as long + // as a valid TCO view list is given, while copy/cut require an instance for + // some metadata to be written to the clipboard. + static void removeActive( QVector tcovs ); + static void toggleMuteActive( QVector tcovs ); public slots: virtual bool close(); From 82deaa2aefb961c1b4f3e18c30b8d2aa39266315 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Sun, 2 Aug 2020 18:06:15 -0300 Subject: [PATCH 17/20] Renamed TCO View paste method I renamed the TCO View paste method from pasteSelection to just paste, since the TCO view doesn't currently have a paste method (only the TCO class). It's also a name that accurately describes the action: it will paste either a group of TCOVs or a single TCOV on the current TCOV. I also moved the logic for deciding between the multiple TCOV paste and single TCOV paste inside the paste method. --- include/Track.h | 2 +- src/core/Track.cpp | 41 ++++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/include/Track.h b/include/Track.h index c7f372e3a60..05333ec09a6 100644 --- a/include/Track.h +++ b/include/Track.h @@ -253,7 +253,7 @@ class TrackContentObjectView : public selectableObject, public ModelView // Methods to remove, copy, cut, paste and mute a QVector of TCO views void copyActive( QVector tcovs ); void cutActive( QVector tcovs ); - void pasteSelection(); // TODO: Rethink naming? + void paste(); // removeActive and toggleMuteActive are static because they don't depend // being called from a particular TCO view, but can be called anywhere as long // as a valid TCO view list is given, while copy/cut require an instance for diff --git a/src/core/Track.cpp b/src/core/Track.cpp index df0fbc8a415..f0b59aa40c3 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1228,18 +1228,7 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) copyActive( active ); break; case Paste: - // NOTE: Because we give preference to the QApplication clipboard over the LMMS Clipboard class, we need to - // clear the QApplication Clipboard during the LMMS Clipboard copy operations (Clipboard::copy does that) - - // If we have TCO data on the clipboard paste it. If not, do our regular TCO paste. - if( QApplication::clipboard()->mimeData( QClipboard::Clipboard )->hasFormat( StringPairDrag::mimeType() ) ) - { - pasteSelection(); - } - else - { - getTrackContentObject()->paste(); - } + paste(); break; case Mute: toggleMuteActive( active ); @@ -1304,19 +1293,29 @@ void TrackContentObjectView::cutActive( QVector tcovs } } -// TODO: Is it a good name for the method or can it be mistaken with TrackContentWidget::pasteSelection()? -void TrackContentObjectView::pasteSelection() +void TrackContentObjectView::paste() { - // Paste the selection on the MidiTime of the selected Track - const QMimeData *md = QApplication::clipboard()->mimeData( QClipboard::Clipboard ); - MidiTime tcoPos = MidiTime( m_tco->startPosition() ); + // NOTE: Because we give preference to the QApplication clipboard over the LMMS Clipboard class, we need to + // clear the QApplication Clipboard during the LMMS Clipboard copy operations (Clipboard::copy does that) - TrackContentWidget *tcw = getTrackView()->getTrackContentWidget(); + // If we have TCO data on the clipboard paste it. If not, do our regular TCO paste. + if( QApplication::clipboard()->mimeData( QClipboard::Clipboard )->hasFormat( StringPairDrag::mimeType() ) ) + { + // Paste the selection on the MidiTime of the selected Track + const QMimeData *md = QApplication::clipboard()->mimeData( QClipboard::Clipboard ); + MidiTime tcoPos = MidiTime( m_tco->startPosition() ); + + TrackContentWidget *tcw = getTrackView()->getTrackContentWidget(); - if( tcw->pasteSelection( tcoPos, md ) == true ) + if( tcw->pasteSelection( tcoPos, md ) == true ) + { + // If we succeed on the paste we delete the TCO we pasted on + remove(); + } + } + else { - // If we succeed on the paste we delete the TCO we pasted on - remove(); + getTrackContentObject()->paste(); } } From 3478638494c813f58d79f15e0de0f2941bf31615 Mon Sep 17 00:00:00 2001 From: IanCaio Date: Thu, 6 Aug 2020 21:55:34 -0300 Subject: [PATCH 18/20] Moves repeated code to a new method This commit adds another method to TrackContentObjectView called getClickedTCOs, which will return a QVector with the TCO views that are supposed to be affected by a context menu action (either the individual right-clicked TCO or the selection of TCOs). Code was updated on the other methods to make use of this new method. Method names for actions that affect multiple TCOs were changed. Instead of calling them copyActive, cutActive, toggleMuteActive and removeActive, they are just called copy, cut, toggleMute and remove, hence they are overloaded methods for those actions that affect multiple TCOs (their differenciation is the arguments list, which is a QVector list for those). --- include/Track.h | 13 +++-- src/core/Track.cpp | 105 ++++++++++++++----------------------- src/tracks/SampleTrack.cpp | 18 +------ 3 files changed, 49 insertions(+), 87 deletions(-) diff --git a/include/Track.h b/include/Track.h index 05333ec09a6..9362a838019 100644 --- a/include/Track.h +++ b/include/Track.h @@ -250,16 +250,19 @@ class TrackContentObjectView : public selectableObject, public ModelView bool needsUpdate(); void setNeedsUpdate( bool b ); + // Method to get a QVector of TCOs to be affected by a context menu action + QVector getClickedTCOs(); + // Methods to remove, copy, cut, paste and mute a QVector of TCO views - void copyActive( QVector tcovs ); - void cutActive( QVector tcovs ); + void copy( QVector tcovs ); + void cut( QVector tcovs ); void paste(); - // removeActive and toggleMuteActive are static because they don't depend + // remove and toggleMute are static because they don't depend // being called from a particular TCO view, but can be called anywhere as long // as a valid TCO view list is given, while copy/cut require an instance for // some metadata to be written to the clipboard. - static void removeActive( QVector tcovs ); - static void toggleMuteActive( QVector tcovs ); + static void remove( QVector tcovs ); + static void toggleMute( QVector tcovs ); public slots: virtual bool close(); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 95c9f859434..2ae1e4160ac 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -735,24 +735,8 @@ void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & pai */ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { - // Get a list of selected selectableObjects - QVector sos = gui->songEditor()->m_editor->selectedObjects(); - - // Convert to a list of selected TCOVs - QVector selection; - selection.reserve( sos.size() ); - for( auto so: sos ) - { - TrackContentObjectView *tcov = dynamic_cast ( so ); - if( tcov != nullptr ) - { - selection.append( tcov ); - } - } - - // If we clicked part of the selection, affect all selected clips. Otherwise affect the clip we clicked - QVector active = selection.contains( this ) ? selection : QVector( 1, this ); - // active will be later used for the removeActive, copyActive, cutActive or toggleMuteActive methods + QVector active = getClickedTCOs(); + // active will be later used for the remove, copy, cut or toggleMute methods setInitialPos( me->pos() ); setInitialOffsets(); @@ -846,22 +830,22 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { if( me->modifiers() & Qt::ControlModifier ) { - toggleMuteActive( active ); + toggleMute( active ); } else if( me->modifiers() & Qt::ShiftModifier && !fixedTCOs() ) { - removeActive( active ); + remove( active ); } } else if( me->button() == Qt::MidButton ) { if( me->modifiers() & Qt::ControlModifier ) { - toggleMuteActive( active ); + toggleMute( active ); } else if( !fixedTCOs() ) { - removeActive( active ); + remove( active ); } } } @@ -1138,23 +1122,7 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) { // Depending on whether we right-clicked a selection or an individual TCO we will have // different labels for the actions. - // Get a list of selected selectableObjects - QVector sos = gui->songEditor()->m_editor->selectedObjects(); - - // Convert to a list of selected TCOVs - QVector selection; - selection.reserve( sos.size() ); - for( auto so: sos ) - { - TrackContentObjectView *tcov = dynamic_cast ( so ); - if( tcov != nullptr ) - { - selection.append( tcov ); - } - } - - // Individual TCO or selection being right-clicked? - bool individualTCO = selection.contains( this ) ? false : true; + bool individualTCO = getClickedTCOs().size() <= 1; if( cme->modifiers() ) { @@ -1196,6 +1164,31 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) // This method processes the actions from the context menu of the TCO View. void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) +{ + QVector active = getClickedTCOs(); + // active will be later used for the remove, copy, cut or toggleMute methods + + switch( action ) + { + case Remove: + remove( active ); + break; + case Cut: + cut( active ); + break; + case Copy: + copy( active ); + break; + case Paste: + paste(); + break; + case Mute: + toggleMute( active ); + break; + } +} + +QVector TrackContentObjectView::getClickedTCOs() { // Get a list of selected selectableObjects QVector sos = gui->songEditor()->m_editor->selectedObjects(); @@ -1213,30 +1206,12 @@ void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) } // If we clicked part of the selection, affect all selected clips. Otherwise affect the clip we clicked - QVector active = selection.contains( this ) ? selection : QVector( 1, this ); - // active will be later used for the removeActive, copyActive, cutActive or toggleMuteActive methods - - switch( action ) - { - case Remove: - removeActive( active ); - break; - case Cut: - cutActive( active ); - break; - case Copy: - copyActive( active ); - break; - case Paste: - paste(); - break; - case Mute: - toggleMuteActive( active ); - break; - } + return selection.contains(this) + ? selection + : QVector( 1, this ); } -void TrackContentObjectView::removeActive( QVector tcovs ) +void TrackContentObjectView::remove( QVector tcovs ) { for( auto tcov: tcovs ) { @@ -1245,7 +1220,7 @@ void TrackContentObjectView::removeActive( QVector tco } } -void TrackContentObjectView::copyActive( QVector tcovs ) +void TrackContentObjectView::copy( QVector tcovs ) { // Checks if there are other selected TCOs and if so copy them as well if( tcovs.size() > 1 ) @@ -1267,7 +1242,7 @@ void TrackContentObjectView::copyActive( QVector tcovs } } -void TrackContentObjectView::cutActive( QVector tcovs ) +void TrackContentObjectView::cut( QVector tcovs ) { // Checks if there are other selected TCOs and if so cut them as well if( tcovs.size() > 1 ) @@ -1277,7 +1252,7 @@ void TrackContentObjectView::cutActive( QVector tcovs // Now that the dataFile is created we can delete the tracks, since we are cutting // TODO: Is it safe to call tcov->remove(); on the current TCOV instance? - removeActive( tcovs ); + remove( tcovs ); // Add the TCO type as a key to the final string QString finalString = QString( "tco_%1:%2" ).arg( m_tco->getTrack()->type() ).arg( dataFile.toString() ); @@ -1319,7 +1294,7 @@ void TrackContentObjectView::paste() } } -void TrackContentObjectView::toggleMuteActive( QVector tcovs ) +void TrackContentObjectView::toggleMute( QVector tcovs ) { for( auto tcov: tcovs ) { diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index ea85102ad60..1fdf0a4eb48 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -345,23 +345,7 @@ void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) { // Depending on whether we right-clicked a selection or an individual TCO we will have // different labels for the actions. - // Get a list of selected selectableObjects - QVector sos = gui->songEditor()->m_editor->selectedObjects(); - - // Convert to a list of selected TCOVs - QVector selection; - selection.reserve( sos.size() ); - for( auto so: sos ) - { - TrackContentObjectView *tcov = dynamic_cast ( so ); - if( tcov != nullptr ) - { - selection.append( tcov ); - } - } - - // Individual TCO or selection being right-clicked? - bool individualTCO = selection.contains( this ) ? false : true; + bool individualTCO = getClickedTCOs().size() <= 1; if( _cme->modifiers() ) { From 8fcb0b09c276d34a3e3b5cdb955e2974f945e74d Mon Sep 17 00:00:00 2001 From: IanCaio Date: Sat, 8 Aug 2020 16:46:50 -0300 Subject: [PATCH 19/20] Avoid unnecessary calls to getClickedTCOs() We use a ternary operator inside TrackContentObjectView::mousePressEvent to avoid unnecessary calls to getClickedTCOs when the list is not going to be used. The contextMenuEvent method on both Track.cpp and SampleTrack.cpp was reformated to use a more appropriate indentation and spacing between method calls. --- src/core/Track.cpp | 67 ++++++++++++++++++++++++-------------- src/tracks/SampleTrack.cpp | 58 ++++++++++++++++++++------------- 2 files changed, 78 insertions(+), 47 deletions(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 2ae1e4160ac..8912a1ec9f7 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -735,8 +735,11 @@ void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & pai */ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { - QVector active = getClickedTCOs(); - // active will be later used for the remove, copy, cut or toggleMute methods + // Right now, active is only used on right/mid clicks actions, so we use a ternary operator + // to avoid the overhead of calling getClickedTCOs when it's not used + auto active = me->button() == Qt::LeftButton + ? QVector() + : getClickedTCOs(); setInitialPos( me->pos() ); setInitialOffsets(); @@ -1130,33 +1133,47 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) } QMenu contextMenu( this ); + if( fixedTCOs() == false ) { - contextMenu.addAction( embed::getIconPixmap( "cancel" ), - tr( individualTCO - ? "Delete (middle mousebutton)" - : "Delete selection (middle mousebutton)" ), - [this](){ contextMenuAction( Remove ); } ); + contextMenu.addAction( + embed::getIconPixmap( "cancel" ), + tr( individualTCO + ? "Delete (middle mousebutton)" + : "Delete selection (middle mousebutton)" ), + [this](){ contextMenuAction( Remove ); } ); + contextMenu.addSeparator(); - contextMenu.addAction( embed::getIconPixmap( "edit_cut" ), - tr( individualTCO - ? "Cut" - : "Cut selection" ), - [this](){ contextMenuAction( Cut ); } ); - } - contextMenu.addAction( embed::getIconPixmap( "edit_copy" ), - tr( individualTCO - ? "Copy" - : "Copy selection" ), - [this](){ contextMenuAction( Copy ); } ); - contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), - tr( "Paste" ), [this](){ contextMenuAction( Paste ); } ); + + contextMenu.addAction( + embed::getIconPixmap( "edit_cut" ), + tr( individualTCO + ? "Cut" + : "Cut selection" ), + [this](){ contextMenuAction( Cut ); } ); + } + + contextMenu.addAction( + embed::getIconPixmap( "edit_copy" ), + tr( individualTCO + ? "Copy" + : "Copy selection" ), + [this](){ contextMenuAction( Copy ); } ); + + contextMenu.addAction( + embed::getIconPixmap( "edit_paste" ), + tr( "Paste" ), + [this](){ contextMenuAction( Paste ); } ); + contextMenu.addSeparator(); - contextMenu.addAction( embed::getIconPixmap( "muted" ), - tr( individualTCO - ? "Mute/unmute (<%1> + middle click)" - : "Mute/unmute selection (<%1> + middle click)" ).arg(UI_CTRL_KEY), - [this](){ contextMenuAction( Mute ); } ); + + contextMenu.addAction( + embed::getIconPixmap( "muted" ), + tr( individualTCO + ? "Mute/unmute (<%1> + middle click)" + : "Mute/unmute selection (<%1> + middle click)" ).arg(UI_CTRL_KEY), + [this](){ contextMenuAction( Mute ); } ); + constructContextMenu( &contextMenu ); contextMenu.exec( QCursor::pos() ); diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 1fdf0a4eb48..0183684a180 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -353,33 +353,47 @@ void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) } QMenu contextMenu( this ); + if( fixedTCOs() == false ) { - contextMenu.addAction( embed::getIconPixmap( "cancel" ), - tr( individualTCO - ? "Delete (middle mousebutton)" - : "Delete selection (middle mousebutton)" ), - [this](){ contextMenuAction( Remove ); } ); + contextMenu.addAction( + embed::getIconPixmap( "cancel" ), + tr( individualTCO + ? "Delete (middle mousebutton)" + : "Delete selection (middle mousebutton)" ), + [this](){ contextMenuAction( Remove ); } ); + contextMenu.addSeparator(); - contextMenu.addAction( embed::getIconPixmap( "edit_cut" ), - tr( individualTCO - ? "Cut" - : "Cut selection" ), - [this](){ contextMenuAction( Cut ); } ); + + contextMenu.addAction( + embed::getIconPixmap( "edit_cut" ), + tr( individualTCO + ? "Cut" + : "Cut selection" ), + [this](){ contextMenuAction( Cut ); } ); } - contextMenu.addAction( embed::getIconPixmap( "edit_copy" ), - tr( individualTCO - ? "Copy" - : "Copy selection" ), - [this](){ contextMenuAction( Copy ); } ); - contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), - tr( "Paste" ), [this](){ contextMenuAction( Paste ); } ); + + contextMenu.addAction( + embed::getIconPixmap( "edit_copy" ), + tr( individualTCO + ? "Copy" + : "Copy selection" ), + [this](){ contextMenuAction( Copy ); } ); + + contextMenu.addAction( + embed::getIconPixmap( "edit_paste" ), + tr( "Paste" ), + [this](){ contextMenuAction( Paste ); } ); + contextMenu.addSeparator(); - contextMenu.addAction( embed::getIconPixmap( "muted" ), - tr( individualTCO - ? "Mute/unmute (<%1> + middle click)" - : "Mute/unmute selection (<%1> + middle click)" ).arg(UI_CTRL_KEY), - [this](){ contextMenuAction( Mute ); } ); + + contextMenu.addAction( + embed::getIconPixmap( "muted" ), + tr( individualTCO + ? "Mute/unmute (<%1> + middle click)" + : "Mute/unmute selection (<%1> + middle click)" ).arg(UI_CTRL_KEY), + [this](){ contextMenuAction( Mute ); } ); + /*contextMenu.addAction( embed::getIconPixmap( "record" ), tr( "Set/clear record" ), m_tco, SLOT( toggleRecord() ) );*/ From 8f1d87462a108e0898be36c9e7e7d7073d7f3eab Mon Sep 17 00:00:00 2001 From: IanCaio Date: Sat, 8 Aug 2020 18:28:00 -0300 Subject: [PATCH 20/20] Fix indenting in a ternary operator assignment --- src/core/Track.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 8912a1ec9f7..f79a34a15c9 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -738,8 +738,8 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) // Right now, active is only used on right/mid clicks actions, so we use a ternary operator // to avoid the overhead of calling getClickedTCOs when it's not used auto active = me->button() == Qt::LeftButton - ? QVector() - : getClickedTCOs(); + ? QVector() + : getClickedTCOs(); setInitialPos( me->pos() ); setInitialOffsets();