diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp index 328ca718b62..99e6cb3aa76 100644 --- a/src/engine/controls/bpmcontrol.cpp +++ b/src/engine/controls/bpmcontrol.cpp @@ -642,10 +642,7 @@ double BpmControl::getNearestPositionInPhase( return dThisPosition; } - double dOtherLength = ControlObject::getControl( - ConfigKey(pOtherEngineBuffer->getGroup(), "track_samples"))->get(); - double dOtherEnginePlayPos = pOtherEngineBuffer->getVisualPlayPos(); - double dOtherPosition = dOtherLength * dOtherEnginePlayPos; + double dOtherPosition = pOtherEngineBuffer->getExactPlayPos(); if (!BpmControl::getBeatContext(otherBeats, dOtherPosition, NULL, NULL, NULL, &dOtherBeatFraction)) { diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index e16385ca0d1..e2e4a017c85 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -409,6 +409,10 @@ void EngineBuffer::requestSyncMode(SyncMode mode) { } } +void EngineBuffer::requestClonePosition(EngineChannel* pChannel) { + m_pChannelToCloneFrom.store(pChannel); +} + void EngineBuffer::readToCrossfadeBuffer(const int iBufferSize) { if (!m_bCrossfadeReady) { // Read buffer, as if there where no parameter change @@ -420,6 +424,10 @@ void EngineBuffer::readToCrossfadeBuffer(const int iBufferSize) { } } +void EngineBuffer::seekCloneBuffer(EngineBuffer* pOtherBuffer) { + doSeekPlayPos(pOtherBuffer->getExactPlayPos(), SEEK_EXACT); +} + // WARNING: This method is not thread safe and must not be called from outside // the engine callback! void EngineBuffer::setNewPlaypos(double newpos, bool adjustingPhase) { @@ -1104,6 +1112,12 @@ void EngineBuffer::processSyncRequests() { } void EngineBuffer::processSeek(bool paused) { + // Check if we are cloning another channel before doing any seeking. + EngineChannel* pChannel = m_pChannelToCloneFrom.fetchAndStoreRelaxed(NULL); + if (pChannel) { + seekCloneBuffer(pChannel->getEngineBuffer()); + } + // We need to read position just after reading seekType, to ensure that we // read the matching position to seek_typ or a position from a new (second) // seek just queued from another thread @@ -1281,6 +1295,10 @@ void EngineBuffer::slotEjectTrack(double v) { } } +double EngineBuffer::getExactPlayPos() { + return getVisualPlayPos() * getTrackSamples(); +} + double EngineBuffer::getVisualPlayPos() { return m_visualPlayPos->getEnginePlayPos(); } diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index 4b0a4f4f16f..95c8e43de90 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -132,6 +132,7 @@ class EngineBuffer : public EngineObject { void requestSyncPhase(); void requestEnableSync(bool enabled); void requestSyncMode(SyncMode mode); + void requestClonePosition(EngineChannel* pChannel); // The process methods all run in the audio callback. void process(CSAMPLE* pOut, const int iBufferSize); @@ -142,6 +143,7 @@ class EngineBuffer : public EngineObject { bool isTrackLoaded(); TrackPointer getLoadedTrack() const; + double getExactPlayPos(); double getVisualPlayPos(); double getTrackSamples(); @@ -224,6 +226,9 @@ class EngineBuffer : public EngineObject { // to prevent pops. void readToCrossfadeBuffer(const int iBufferSize); + // Copy the play position from the given buffer + void seekCloneBuffer(EngineBuffer* pOtherBuffer); + // Reset buffer playpos and set file playpos. void setNewPlaypos(double playpos, bool adjustingPhase); @@ -380,6 +385,7 @@ class EngineBuffer : public EngineObject { QAtomicInt m_iEnableSyncQueued; QAtomicInt m_iSyncModeQueued; ControlValueAtomic m_queuedSeekPosition; + QAtomicPointer m_pChannelToCloneFrom; // Is true if the previous buffer was silent due to pausing QAtomicInt m_iTrackLoading; diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index ca27cb08dfc..ac419f0400d 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -2,6 +2,7 @@ #include "mixer/basetrackplayer.h" #include "mixer/playerinfo.h" +#include "mixer/playermanager.h" #include "control/controlobject.h" #include "control/controlpotmeter.h" @@ -37,7 +38,8 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent, m_pConfig(pConfig), m_pEngineMaster(pMixingEngine), m_pLoadedTrack(), - m_replaygainPending(false) { + m_replaygainPending(false), + m_pChannelToCloneFrom(nullptr) { ChannelHandleAndGroup channelGroup = pMixingEngine->registerChannelGroup(group); m_pChannel = new EngineDeck(channelGroup, pConfig, pMixingEngine, @@ -77,6 +79,13 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent, m_pDuration = std::make_unique( ConfigKey(getGroup(), "duration")); + // Deck cloning + m_pCloneFromDeck = std::make_unique( + ConfigKey(getGroup(), "CloneFromDeck"), + false); + connect(m_pCloneFromDeck.get(), &ControlObject::valueChanged, + this, &BaseTrackPlayerImpl::slotCloneFromDeck); + // Waveform controls // This acts somewhat like a ControlPotmeter, but the normal _up/_down methods // do not work properly with this CO. @@ -109,6 +118,8 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent, m_pPlay->connectValueChanged(this, &BaseTrackPlayerImpl::slotPlayToggled); pVisualsManager->addDeck(group); + + m_cloneTimer.start(); } BaseTrackPlayerImpl::~BaseTrackPlayerImpl() { @@ -167,21 +178,31 @@ void BaseTrackPlayerImpl::loadTrack(TrackPointer pTrack) { // The loop in and out points must be set here and not in slotTrackLoaded // so LoopingControl::trackLoaded can access them. - const QList trackCues(m_pLoadedTrack->getCuePoints()); - QListIterator it(trackCues); - while (it.hasNext()) { - CuePointer pCue(it.next()); - if (pCue->getType() == Cue::LOOP) { - double loopStart = pCue->getPosition(); - double loopEnd = loopStart + pCue->getLength(); - if (loopStart != kNoTrigger && loopEnd != kNoTrigger && loopStart <= loopEnd) { - m_pLoopInPoint->set(loopStart); - m_pLoopOutPoint->set(loopEnd); - break; + if (!m_pChannelToCloneFrom) { + const QList trackCues(m_pLoadedTrack->getCuePoints()); + QListIterator it(trackCues); + while (it.hasNext()) { + CuePointer pCue(it.next()); + if (pCue->getType() == Cue::LOOP) { + double loopStart = pCue->getPosition(); + double loopEnd = loopStart + pCue->getLength(); + if (loopStart != kNoTrigger && loopEnd != kNoTrigger && loopStart <= loopEnd) { + m_pLoopInPoint->set(loopStart); + m_pLoopOutPoint->set(loopEnd); + break; + } } } + } else { + // copy loop in and out points from other deck because any new loops + // won't be saved yet + m_pLoopInPoint->set(ControlObject::get( + ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_start_position"))); + m_pLoopOutPoint->set(ControlObject::get( + ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_end_position"))); } + connectLoadedTrack(); } @@ -244,6 +265,18 @@ void BaseTrackPlayerImpl::disconnectLoadedTrack() { } void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) { + mixxx::Duration elapsed = m_cloneTimer.restart(); + if (elapsed < mixxx::Duration::fromSeconds(0.5)) { + // load pressed twice quickly, clone instead of loading + EngineChannel* pChannel = m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel); + slotCloneChannel(pChannel); + } + else { + slotLoadTrackInternal(pNewTrack, bPlay); + } +} + +void BaseTrackPlayerImpl::slotLoadTrackInternal(TrackPointer pNewTrack, bool bPlay) { qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup(); // Before loading the track, ensure we have access. This uses lazy // evaluation to make sure track isn't NULL before we dereference it. @@ -278,6 +311,7 @@ void BaseTrackPlayerImpl::slotLoadFailed(TrackPointer pTrack, QString reason) { } else { qDebug() << "Failed to load track (NULL track object)" << reason; } + m_pChannelToCloneFrom = nullptr; // Alert user. QMessageBox::warning(NULL, tr("Couldn't load track."), reason); } @@ -342,22 +376,50 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack, ConfigKey("[Mixer Profile]", "GainAutoReset"), false)) { m_pPreGain->set(1.0); } - int reset = m_pConfig->getValue( - ConfigKey("[Controls]", "SpeedAutoReset"), RESET_PITCH); - if (reset == RESET_SPEED || reset == RESET_PITCH_AND_SPEED) { - // Avoid resetting speed if master sync is enabled and other decks with sync enabled - // are playing, as this would change the speed of already playing decks. - if (!m_pEngineMaster->getEngineSync()->otherSyncedPlaying(getGroup())) { - if (m_pRateSlider != NULL) { - m_pRateSlider->set(0.0); + + if (!m_pChannelToCloneFrom) { + int reset = m_pConfig->getValue( + ConfigKey("[Controls]", "SpeedAutoReset"), RESET_PITCH); + if (reset == RESET_SPEED || reset == RESET_PITCH_AND_SPEED) { + // Avoid resetting speed if master sync is enabled and other decks with sync enabled + // are playing, as this would change the speed of already playing decks. + if (!m_pEngineMaster->getEngineSync()->otherSyncedPlaying(getGroup())) { + if (m_pRateSlider != NULL) { + m_pRateSlider->set(0.0); + } } } - } - if (reset == RESET_PITCH || reset == RESET_PITCH_AND_SPEED) { - if (m_pPitchAdjust != NULL) { - m_pPitchAdjust->set(0.0); + if (reset == RESET_PITCH || reset == RESET_PITCH_AND_SPEED) { + if (m_pPitchAdjust != NULL) { + m_pPitchAdjust->set(0.0); + } + } + } else { + // perform a clone of the given channel + + // copy rate + if (m_pRateSlider != nullptr) { + m_pRateSlider->set(ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "rate"))); + } + + // copy pitch + if (m_pPitchAdjust != nullptr) { + m_pPitchAdjust->set(ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "pitch_adjust"))); + } + + // copy play state + ControlObject::set(ConfigKey(getGroup(), "play"), + ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "play"))); + + // copy the play position + m_pChannel->getEngineBuffer()->requestClonePosition(m_pChannelToCloneFrom); + + // copy the loop state + if (ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_enabled")) == 1.0) { + ControlObject::set(ConfigKey(getGroup(), "reloop_toggle"), 1.0); } } + emit(newTrackLoaded(m_pLoadedTrack)); } else { // this is the result from an outdated load or unload signal @@ -366,6 +428,8 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack, qDebug() << "stray BaseTrackPlayerImpl::slotTrackLoaded()"; } + m_pChannelToCloneFrom = nullptr; + // Update the PlayerInfo class that is used in EngineBroadcast to replace // the metadata of a stream PlayerInfo::instance().setTrackInfo(getGroup(), m_pLoadedTrack); @@ -375,6 +439,44 @@ TrackPointer BaseTrackPlayerImpl::getLoadedTrack() const { return m_pLoadedTrack; } +void BaseTrackPlayerImpl::slotCloneDeck(const QString& group) { + EngineChannel* pChannel = m_pEngineMaster->getChannel(group); + if (!pChannel) { + return; + } + + slotCloneChannel(pChannel); +} + +void BaseTrackPlayerImpl::slotCloneFromDeck(double d) { + int deck = std::lround(d); + if (deck < 1) { + slotCloneChannel(m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel)); + } else { + slotCloneDeck(PlayerManager::groupForDeck(deck-1)); + } +} + +void BaseTrackPlayerImpl::slotCloneChannel(EngineChannel* pChannel) { + // don't clone from ourselves + if (pChannel == m_pChannel) { + return; + } + + m_pChannelToCloneFrom = pChannel; + if (!m_pChannelToCloneFrom) { + return; + } + + TrackPointer pTrack = m_pChannelToCloneFrom->getEngineBuffer()->getLoadedTrack(); + if (!pTrack) { + m_pChannelToCloneFrom = nullptr; + return; + } + + slotLoadTrackInternal(pTrack, false); +} + void BaseTrackPlayerImpl::slotSetReplayGain(mixxx::ReplayGain replayGain) { // Do not change replay gain when track is playing because // this may lead to an unexpected volume change diff --git a/src/mixer/basetrackplayer.h b/src/mixer/basetrackplayer.h index 97442816f43..61e33a4fd96 100644 --- a/src/mixer/basetrackplayer.h +++ b/src/mixer/basetrackplayer.h @@ -11,6 +11,7 @@ #include "mixer/baseplayer.h" #include "track/track.h" #include "util/memory.h" +#include "util/performancetimer.h" class EngineMaster; class ControlObject; @@ -38,6 +39,8 @@ class BaseTrackPlayer : public BasePlayer { public slots: virtual void slotLoadTrack(TrackPointer pTrack, bool bPlay = false) = 0; + virtual void slotCloneChannel(EngineChannel* pChannel) = 0; + virtual void slotCloneDeck(const QString& group) = 0; signals: void newTrackLoaded(TrackPointer pLoadedTrack); @@ -74,12 +77,16 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { public slots: void slotLoadTrack(TrackPointer track, bool bPlay) final; + void slotCloneChannel(EngineChannel* pChannel) final; + void slotCloneDeck(const QString& group) final; void slotTrackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack); void slotLoadFailed(TrackPointer pTrack, QString reason); void slotSetReplayGain(mixxx::ReplayGain replayGain); void slotPlayToggled(double); private slots: + void slotLoadTrackInternal(TrackPointer pNewTrack, bool bPlay); + void slotCloneFromDeck(double deck); void slotPassthroughEnabled(double v); void slotVinylControlEnabled(double v); void slotWaveformZoomValueChangeRequest(double pressed); @@ -101,6 +108,11 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { TrackPointer m_pLoadedTrack; EngineDeck* m_pChannel; bool m_replaygainPending; + EngineChannel* m_pChannelToCloneFrom; + PerformanceTimer m_cloneTimer; + + // Deck clone control + std::unique_ptr m_pCloneFromDeck; // Waveform display related controls std::unique_ptr m_pWaveformZoom; diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index aa05ba33412..28bf0812684 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -556,6 +556,17 @@ Auxiliary* PlayerManager::getAuxiliary(unsigned int auxiliary) const { return m_auxiliaries[auxiliary - 1]; } +void PlayerManager::slotCloneDeck(QString source_group, QString target_group) { + BaseTrackPlayer* pPlayer = getPlayer(target_group); + + if (pPlayer == nullptr) { + qWarning() << "Invalid group argument " << target_group << " to slotCloneDeck."; + return; + } + + pPlayer->slotCloneDeck(source_group); +} + void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, QString group, bool play) { // Do not lock mutex in this method unless it is changed to access // PlayerManager state. diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h index 3fc42e375a4..a80731c041f 100644 --- a/src/mixer/playermanager.h +++ b/src/mixer/playermanager.h @@ -182,6 +182,7 @@ class PlayerManager : public QObject, public PlayerManagerInterface { // Slots for loading tracks into a Player, which is either a Sampler or a Deck void slotLoadTrackToPlayer(TrackPointer pTrack, QString group, bool play = false); void slotLoadToPlayer(QString location, QString group); + void slotCloneDeck(QString source_group, QString target_group); // Slots for loading tracks to decks void slotLoadTrackIntoNextAvailableDeck(TrackPointer pTrack); diff --git a/src/skin/legacyskinparser.cpp b/src/skin/legacyskinparser.cpp index 8b370016270..00f80e31c86 100644 --- a/src/skin/legacyskinparser.cpp +++ b/src/skin/legacyskinparser.cpp @@ -937,6 +937,8 @@ QWidget* LegacySkinParser::parseOverview(const QDomElement& node) { connect(overviewWidget, SIGNAL(trackDropped(QString, QString)), m_pPlayerManager, SLOT(slotLoadToPlayer(QString, QString))); + connect(overviewWidget, &WOverview::cloneDeck, + m_pPlayerManager, &PlayerManager::slotCloneDeck); commonWidgetSetup(node, overviewWidget); overviewWidget->setup(node, *m_pContext); @@ -987,6 +989,8 @@ QWidget* LegacySkinParser::parseVisual(const QDomElement& node) { connect(viewer, SIGNAL(trackDropped(QString, QString)), m_pPlayerManager, SLOT(slotLoadToPlayer(QString, QString))); + connect(viewer, &WWaveformViewer::cloneDeck, + m_pPlayerManager, &PlayerManager::slotCloneDeck); // if any already loaded viewer->slotTrackLoaded(pPlayer->getLoadedTrack()); @@ -1012,6 +1016,8 @@ QWidget* LegacySkinParser::parseText(const QDomElement& node) { p, SLOT(slotLoadingTrack(TrackPointer, TrackPointer))); connect(p, SIGNAL(trackDropped(QString,QString)), m_pPlayerManager, SLOT(slotLoadToPlayer(QString,QString))); + connect(p, &WTrackText::cloneDeck, + m_pPlayerManager, &PlayerManager::slotCloneDeck); TrackPointer pTrack = pPlayer->getLoadedTrack(); if (pTrack) { @@ -1039,6 +1045,8 @@ QWidget* LegacySkinParser::parseTrackProperty(const QDomElement& node) { p, SLOT(slotLoadingTrack(TrackPointer, TrackPointer))); connect(p, SIGNAL(trackDropped(QString,QString)), m_pPlayerManager, SLOT(slotLoadToPlayer(QString,QString))); + connect(p, &WTrackProperty::cloneDeck, + m_pPlayerManager, &PlayerManager::slotCloneDeck); TrackPointer pTrack = pPlayer->getLoadedTrack(); if (pTrack) { @@ -1189,6 +1197,8 @@ QWidget* LegacySkinParser::parseSpinny(const QDomElement& node) { spinny, SLOT(swap())); connect(spinny, SIGNAL(trackDropped(QString, QString)), m_pPlayerManager, SLOT(slotLoadToPlayer(QString, QString))); + connect(spinny, &WSpinny::cloneDeck, + m_pPlayerManager, &PlayerManager::slotCloneDeck); spinny->setup(node, *m_pContext); spinny->installEventFilter(m_pKeyboard); @@ -1243,6 +1253,8 @@ QWidget* LegacySkinParser::parseCoverArt(const QDomElement& node) { } else if (pPlayer != nullptr) { connect(pCoverArt, SIGNAL(trackDropped(QString, QString)), m_pPlayerManager, SLOT(slotLoadToPlayer(QString, QString))); + connect(pCoverArt, &WCoverArt::cloneDeck, + m_pPlayerManager, &PlayerManager::slotCloneDeck); } return pCoverArt; diff --git a/src/test/autodjprocessor_test.cpp b/src/test/autodjprocessor_test.cpp index 4cb27d531da..c731a6959fc 100644 --- a/src/test/autodjprocessor_test.cpp +++ b/src/test/autodjprocessor_test.cpp @@ -77,6 +77,9 @@ class FakeDeck : public BaseTrackPlayer { play.set(bPlay); } + MOCK_METHOD1(slotCloneChannel, void(EngineChannel* pChannel)); + MOCK_METHOD1(slotCloneDeck, void(const QString& group)); + TrackPointer loadedTrack; ControlLinPotmeter playposition; ControlPushButton play; diff --git a/src/util/dnd.h b/src/util/dnd.h index 9b08fc8138a..f143a949d83 100644 --- a/src/util/dnd.h +++ b/src/util/dnd.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include "library/parsercsv.h" #include "util/sandbox.h" #include "mixer/playermanager.h" +#include "widget/trackdroptarget.h" class DragAndDropHelper { public: @@ -96,6 +98,29 @@ class DragAndDropHelper { "AllowTrackLoadToPlayingDeck")).toInt(); } + static bool allowDeckCloneAttempt(const QDropEvent& event, + const QString& group) { + // only allow clones to decks + if (!PlayerManager::isDeckGroup(group, nullptr)) { + return false; + } + + // forbid clone if shift is pressed + if (event.keyboardModifiers().testFlag(Qt::ShiftModifier)) { + return false; + } + + if (!event.mimeData()->hasText() || + // prevent cloning to ourself + event.mimeData()->text() == group || + // only allow clone from decks + !PlayerManager::isDeckGroup(event.mimeData()->text(), nullptr)) { + return false; + } + + return true; + } + static bool dragEnterAccept(const QMimeData& mimeData, const QString& sourceIdentifier, bool firstOnly, @@ -141,6 +166,37 @@ class DragAndDropHelper { return QUrl::fromLocalFile(trackLocation); } + static void handleTrackDragEnterEvent(QDragEnterEvent* event, const QString& group, + UserSettingsPointer pConfig) { + if (allowLoadToPlayer(group, pConfig) && + dragEnterAccept(*event->mimeData(), group, + true, false)) { + event->acceptProposedAction(); + } else { + event->ignore(); + } + } + + static void handleTrackDropEvent(QDropEvent* event, TrackDropTarget& target, + const QString& group, UserSettingsPointer pConfig) { + if (allowLoadToPlayer(group, pConfig)) { + if (allowDeckCloneAttempt(*event, group)) { + event->accept(); + emit(target.cloneDeck(event->mimeData()->text(), group)); + return; + } else { + QList files = dropEventFiles( + *event->mimeData(), group, true, false); + if (!files.isEmpty()) { + event->accept(); + emit(target.trackDropped(files.at(0).absoluteFilePath(), group)); + return; + } + } + } + event->ignore(); + } + private: static QDrag* dragUrls(const QList& locationUrls, QWidget* pDragSource, QString sourceIdentifier) { diff --git a/src/widget/trackdroptarget.h b/src/widget/trackdroptarget.h new file mode 100644 index 00000000000..f0822314c84 --- /dev/null +++ b/src/widget/trackdroptarget.h @@ -0,0 +1,13 @@ + +#ifndef TRACKDROPTARGET_H +#define TRACKDROPTARGET_H + +#include + +class TrackDropTarget { +signals: + virtual void trackDropped(QString filename, QString group) = 0; + virtual void cloneDeck(QString source_group, QString target_group) = 0; +}; + +#endif // TRACKDROPTARGET diff --git a/src/widget/wcoverart.cpp b/src/widget/wcoverart.cpp index baa7d2cfe4d..dfdab514733 100644 --- a/src/widget/wcoverart.cpp +++ b/src/widget/wcoverart.cpp @@ -249,11 +249,8 @@ void WCoverArt::mouseMoveEvent(QMouseEvent* event) { void WCoverArt::dragEnterEvent(QDragEnterEvent* event) { // If group is empty then we are a library cover art widget and we don't // accept track drops. - if (!m_group.isEmpty() && - DragAndDropHelper::allowLoadToPlayer(m_group, m_pConfig) && - DragAndDropHelper::dragEnterAccept(*event->mimeData(), m_group, - true, false)) { - event->acceptProposedAction(); + if (!m_group.isEmpty()) { + DragAndDropHelper::handleTrackDragEnterEvent(event, m_group, m_pConfig); } else { event->ignore(); } @@ -262,15 +259,9 @@ void WCoverArt::dragEnterEvent(QDragEnterEvent* event) { void WCoverArt::dropEvent(QDropEvent *event) { // If group is empty then we are a library cover art widget and we don't // accept track drops. - if (!m_group.isEmpty() && - DragAndDropHelper::allowLoadToPlayer(m_group, m_pConfig)) { - QList files = DragAndDropHelper::dropEventFiles( - *event->mimeData(), m_group, true, false); - if (!files.isEmpty()) { - event->accept(); - emit(trackDropped(files.at(0).absoluteFilePath(), m_group)); - return; - } + if (!m_group.isEmpty()) { + DragAndDropHelper::handleTrackDropEvent(event, *this, m_group, m_pConfig); + } else { + event->ignore(); } - event->ignore(); } diff --git a/src/widget/wcoverart.h b/src/widget/wcoverart.h index e4cdf2f91c0..1c71a365da5 100644 --- a/src/widget/wcoverart.h +++ b/src/widget/wcoverart.h @@ -11,12 +11,13 @@ #include "track/track.h" #include "library/coverartcache.h" #include "skin/skincontext.h" +#include "widget/trackdroptarget.h" #include "widget/wbasewidget.h" #include "widget/wcoverartmenu.h" class DlgCoverArtFullSize; -class WCoverArt : public QWidget, public WBaseWidget { +class WCoverArt : public QWidget, public WBaseWidget, public TrackDropTarget { Q_OBJECT public: WCoverArt(QWidget* parent, UserSettingsPointer pConfig, @@ -33,6 +34,7 @@ class WCoverArt : public QWidget, public WBaseWidget { signals: void trackDropped(QString filename, QString group); + void cloneDeck(QString source_group, QString target_group); private slots: void slotCoverFound(const QObject* pRequestor, diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index 3bf5adf3138..aa6375c81d9 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -60,7 +60,6 @@ WOverview::WOverview( m_endOfTrackControl->connectValueChanged(this, &WOverview::onEndOfTrackChange); m_trackSamplesControl = new ControlProxy(m_group, "track_samples", this); - m_playControl = new ControlProxy(m_group, "play", this); setAcceptDrops(true); connect(pPlayerManager, &PlayerManager::trackAnalyzerProgress, @@ -586,27 +585,9 @@ void WOverview::resizeEvent(QResizeEvent * /*unused*/) { } void WOverview::dragEnterEvent(QDragEnterEvent* event) { - if (DragAndDropHelper::allowLoadToPlayer(m_group, - m_playControl->get() > 0.0, - m_pConfig) && - DragAndDropHelper::dragEnterAccept(*event->mimeData(), m_group, - true, false)) { - event->acceptProposedAction(); - } else { - event->ignore(); - } + DragAndDropHelper::handleTrackDragEnterEvent(event, m_group, m_pConfig); } void WOverview::dropEvent(QDropEvent* event) { - if (DragAndDropHelper::allowLoadToPlayer(m_group, m_playControl->get() > 0.0, - m_pConfig)) { - QList files = DragAndDropHelper::dropEventFiles( - *event->mimeData(), m_group, true, false); - if (!files.isEmpty()) { - event->accept(); - emit(trackDropped(files.at(0).absoluteFilePath(), m_group)); - return; - } - } - event->ignore(); + DragAndDropHelper::handleTrackDropEvent(event, *this, m_group, m_pConfig); } diff --git a/src/widget/woverview.h b/src/widget/woverview.h index 625a5bbf264..d13bc759a7a 100644 --- a/src/widget/woverview.h +++ b/src/widget/woverview.h @@ -19,6 +19,7 @@ #include #include "track/track.h" +#include "widget/trackdroptarget.h" #include "widget/wwidget.h" #include "analyzer/analyzerprogress.h" @@ -29,7 +30,7 @@ class PlayerManager; -class WOverview : public WWidget { +class WOverview : public WWidget, public TrackDropTarget { Q_OBJECT public: void setup(const QDomNode& node, const SkinContext& context); @@ -42,6 +43,7 @@ class WOverview : public WWidget { signals: void trackDropped(QString filename, QString group); + void cloneDeck(QString source_group, QString target_group); protected: WOverview( @@ -107,7 +109,6 @@ class WOverview : public WWidget { ControlProxy* m_endOfTrackControl; bool m_endOfTrack; ControlProxy* m_trackSamplesControl; - ControlProxy* m_playControl; // Current active track TrackPointer m_pCurrentTrack; diff --git a/src/widget/wspinny.cpp b/src/widget/wspinny.cpp index 06090d26315..b067fdc6f62 100644 --- a/src/widget/wspinny.cpp +++ b/src/widget/wspinny.cpp @@ -27,7 +27,6 @@ WSpinny::WSpinny(QWidget* parent, const QString& group, WBaseWidget(this), m_group(group), m_pConfig(pConfig), - m_pPlay(nullptr), m_pPlayPos(nullptr), m_pVisualPlayPos(nullptr), m_pTrackSamples(nullptr), @@ -182,8 +181,6 @@ void WSpinny::setup(const QDomNode& node, const SkinContext& context) { m_qImage.fill(qRgba(0,0,0,0)); #endif - m_pPlay = new ControlProxy( - m_group, "play", this); m_pPlayPos = new ControlProxy( m_group, "playposition", this); m_pVisualPlayPos = VisualPlayPosition::getVisualPlayPosition(m_group); @@ -675,26 +672,9 @@ bool WSpinny::event(QEvent* pEvent) { } void WSpinny::dragEnterEvent(QDragEnterEvent* event) { - if (DragAndDropHelper::allowLoadToPlayer(m_group, m_pPlay->get() > 0.0, - m_pConfig) && - DragAndDropHelper::dragEnterAccept(*event->mimeData(), m_group, - true, false)) { - event->acceptProposedAction(); - } else { - event->ignore(); - } + DragAndDropHelper::handleTrackDragEnterEvent(event, m_group, m_pConfig); } void WSpinny::dropEvent(QDropEvent * event) { - if (DragAndDropHelper::allowLoadToPlayer(m_group, m_pPlay->get() > 0.0, - m_pConfig)) { - QList files = DragAndDropHelper::dropEventFiles( - *event->mimeData(), m_group, true, false); - if (!files.isEmpty()) { - event->accept(); - emit(trackDropped(files.at(0).absoluteFilePath(), m_group)); - return; - } - } - event->ignore(); + DragAndDropHelper::handleTrackDropEvent(event, *this, m_group, m_pConfig); } diff --git a/src/widget/wspinny.h b/src/widget/wspinny.h index ef520c534a5..ada8d92a2f0 100644 --- a/src/widget/wspinny.h +++ b/src/widget/wspinny.h @@ -13,6 +13,7 @@ #include "skin/skincontext.h" #include "track/track.h" #include "vinylcontrol/vinylsignalquality.h" +#include "widget/trackdroptarget.h" #include "widget/wbasewidget.h" #include "widget/wcoverartmenu.h" #include "widget/wwidget.h" @@ -21,7 +22,8 @@ class ControlProxy; class VisualPlayPosition; class VinylControlManager; -class WSpinny : public QGLWidget, public WBaseWidget, public VinylSignalQualityListener { +class WSpinny : public QGLWidget, public WBaseWidget, public VinylSignalQualityListener, + public TrackDropTarget { Q_OBJECT public: WSpinny(QWidget* parent, const QString& group, @@ -56,6 +58,7 @@ class WSpinny : public QGLWidget, public WBaseWidget, public VinylSignalQualityL signals: void trackDropped(QString filename, QString group); + void cloneDeck(QString source_group, QString target_group); protected: //QWidget: @@ -82,7 +85,6 @@ class WSpinny : public QGLWidget, public WBaseWidget, public VinylSignalQualityL QImage m_fgImageScaled; std::shared_ptr m_pGhostImage; QImage m_ghostImageScaled; - ControlProxy* m_pPlay; ControlProxy* m_pPlayPos; QSharedPointer m_pVisualPlayPos; ControlProxy* m_pTrackSamples; diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 6f83a4561b2..97bf6226d73 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -56,24 +56,9 @@ void WTrackProperty::mouseMoveEvent(QMouseEvent *event) { } void WTrackProperty::dragEnterEvent(QDragEnterEvent *event) { - if (DragAndDropHelper::allowLoadToPlayer(m_pGroup, m_pConfig) && - DragAndDropHelper::dragEnterAccept(*event->mimeData(), m_pGroup, - true, false)) { - event->acceptProposedAction(); - } else { - event->ignore(); - } + DragAndDropHelper::handleTrackDragEnterEvent(event, m_pGroup, m_pConfig); } void WTrackProperty::dropEvent(QDropEvent *event) { - if (DragAndDropHelper::allowLoadToPlayer(m_pGroup, m_pConfig)) { - QList files = DragAndDropHelper::dropEventFiles( - *event->mimeData(), m_pGroup, true, false); - if (!files.isEmpty()) { - event->accept(); - emit(trackDropped(files.at(0).absoluteFilePath(), m_pGroup)); - return; - } - } - event->ignore(); + DragAndDropHelper::handleTrackDropEvent(event, *this, m_pGroup, m_pConfig); } diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index c50c8b9177f..baa4084c5d3 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -8,9 +8,10 @@ #include "preferences/usersettings.h" #include "skin/skincontext.h" #include "track/track.h" +#include "widget/trackdroptarget.h" #include "widget/wlabel.h" -class WTrackProperty : public WLabel { +class WTrackProperty : public WLabel, public TrackDropTarget { Q_OBJECT public: WTrackProperty(const char* group, UserSettingsPointer pConfig, QWidget* pParent); @@ -19,6 +20,7 @@ class WTrackProperty : public WLabel { signals: void trackDropped(QString filename, QString group); + void cloneDeck(QString source_group, QString target_group); public slots: void slotTrackLoaded(TrackPointer track); diff --git a/src/widget/wtracktext.cpp b/src/widget/wtracktext.cpp index 5c843229cdf..b27c9eb23ab 100644 --- a/src/widget/wtracktext.cpp +++ b/src/widget/wtracktext.cpp @@ -45,24 +45,9 @@ void WTrackText::mouseMoveEvent(QMouseEvent *event) { } void WTrackText::dragEnterEvent(QDragEnterEvent *event) { - if (DragAndDropHelper::allowLoadToPlayer(m_pGroup, m_pConfig) && - DragAndDropHelper::dragEnterAccept(*event->mimeData(), m_pGroup, - true, false)) { - event->acceptProposedAction(); - } else { - event->ignore(); - } + DragAndDropHelper::handleTrackDragEnterEvent(event, m_pGroup, m_pConfig); } void WTrackText::dropEvent(QDropEvent *event) { - if (DragAndDropHelper::allowLoadToPlayer(m_pGroup, m_pConfig)) { - QList files = DragAndDropHelper::dropEventFiles( - *event->mimeData(), m_pGroup, true, false); - if (!files.isEmpty()) { - event->accept(); - emit(trackDropped(files.at(0).absoluteFilePath(), m_pGroup)); - return; - } - } - event->ignore(); + DragAndDropHelper::handleTrackDropEvent(event, *this, m_pGroup, m_pConfig); } diff --git a/src/widget/wtracktext.h b/src/widget/wtracktext.h index 2cd0977b31b..5837cecbc06 100644 --- a/src/widget/wtracktext.h +++ b/src/widget/wtracktext.h @@ -7,15 +7,17 @@ #include "preferences/usersettings.h" #include "track/track.h" +#include "widget/trackdroptarget.h" #include "widget/wlabel.h" -class WTrackText : public WLabel { +class WTrackText : public WLabel, public TrackDropTarget { Q_OBJECT public: WTrackText(const char* group, UserSettingsPointer pConfig, QWidget *pParent); signals: void trackDropped(QString fileName, QString group); + void cloneDeck(QString source_group, QString target_group); public slots: void slotTrackLoaded(TrackPointer track); diff --git a/src/widget/wwaveformviewer.cpp b/src/widget/wwaveformviewer.cpp index 50748254757..072a7008b26 100644 --- a/src/widget/wwaveformviewer.cpp +++ b/src/widget/wwaveformviewer.cpp @@ -150,26 +150,11 @@ void WWaveformViewer::wheelEvent(QWheelEvent *event) { } void WWaveformViewer::dragEnterEvent(QDragEnterEvent* event) { - if (DragAndDropHelper::allowLoadToPlayer(m_pGroup, m_pConfig) && - DragAndDropHelper::dragEnterAccept(*event->mimeData(), m_pGroup, - true, false)) { - event->acceptProposedAction(); - } else { - event->ignore(); - } + DragAndDropHelper::handleTrackDragEnterEvent(event, m_pGroup, m_pConfig); } void WWaveformViewer::dropEvent(QDropEvent* event) { - if (DragAndDropHelper::allowLoadToPlayer(m_pGroup, m_pConfig)) { - QList files = DragAndDropHelper::dropEventFiles( - *event->mimeData(), m_pGroup, true, false); - if (!files.isEmpty()) { - event->accept(); - emit(trackDropped(files.at(0).absoluteFilePath(), m_pGroup)); - return; - } - } - event->ignore(); + DragAndDropHelper::handleTrackDropEvent(event, *this, m_pGroup, m_pConfig); } void WWaveformViewer::slotTrackLoaded(TrackPointer track) { diff --git a/src/widget/wwaveformviewer.h b/src/widget/wwaveformviewer.h index 7d090080bea..941705f6e74 100644 --- a/src/widget/wwaveformviewer.h +++ b/src/widget/wwaveformviewer.h @@ -9,6 +9,7 @@ #include #include "track/track.h" +#include "widget/trackdroptarget.h" #include "widget/wwidget.h" #include "skin/skincontext.h" @@ -16,7 +17,7 @@ class ControlProxy; class WaveformWidgetAbstract; class ControlPotmeter; -class WWaveformViewer : public WWidget { +class WWaveformViewer : public WWidget, public TrackDropTarget { Q_OBJECT public: WWaveformViewer(const char *group, UserSettingsPointer pConfig, QWidget *parent=nullptr); @@ -34,6 +35,7 @@ class WWaveformViewer : public WWidget { signals: void trackDropped(QString filename, QString group); + void cloneDeck(QString source_group, QString target_group); public slots: void slotTrackLoaded(TrackPointer track);