diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp deleted file mode 100644 index b007455c861..00000000000 --- a/src/engine/controls/cuecontrol.cpp +++ /dev/null @@ -1,2737 +0,0 @@ -#include "engine/controls/cuecontrol.h" - -#include "control/controlindicator.h" -#include "control/controlobject.h" -#include "control/controlpushbutton.h" -#include "engine/enginebuffer.h" -#include "moc_cuecontrol.cpp" -#include "preferences/colorpalettesettings.h" -#include "track/track.h" -#include "util/color/predefinedcolorpalettes.h" -#include "vinylcontrol/defs_vinylcontrol.h" - -namespace { - -// TODO: Convert these doubles to a standard enum -// and convert elseif logic to switch statements -constexpr double CUE_MODE_MIXXX = 0.0; -constexpr double CUE_MODE_PIONEER = 1.0; -constexpr double CUE_MODE_DENON = 2.0; -constexpr double CUE_MODE_NUMARK = 3.0; -constexpr double CUE_MODE_MIXXX_NO_BLINK = 4.0; -constexpr double CUE_MODE_CUP = 5.0; - -/// This is the position of a fresh loaded tack without any seek -constexpr int kNoHotCueNumber = 0; -/// Used for a common tracking of the previewing Hotcue in m_currentlyPreviewingIndex -constexpr int kMainCueIndex = NUM_HOT_CUES; - -// Helper function to convert control values (i.e. doubles) into RgbColor -// instances (or nullopt if value < 0). This happens by using the integer -// component as RGB color codes (e.g. 0xFF0000). -inline mixxx::RgbColor::optional_t doubleToRgbColor(double value) { - if (value < 0) { - return std::nullopt; - } - auto colorCode = static_cast(value); - if (value != mixxx::RgbColor::validateCode(colorCode)) { - return std::nullopt; - } - return mixxx::RgbColor::optional(colorCode); -} - -/// Convert hot cue index to 1-based number -/// -/// Works independent of if the hot cue index is either 0-based -/// or 1..n-based. -inline int hotcueIndexToHotcueNumber(int hotcueIndex) { - if (hotcueIndex >= mixxx::kFirstHotCueIndex) { - DEBUG_ASSERT(hotcueIndex != Cue::kNoHotCue); - return (hotcueIndex - mixxx::kFirstHotCueIndex) + 1; // to 1-based numbering - } else { - DEBUG_ASSERT(hotcueIndex == Cue::kNoHotCue); - return kNoHotCueNumber; - } -} - -/// Convert 1-based hot cue number to hot cue index. -/// -/// Works independent of if the hot cue index is either 0-based -/// or 1..n-based. -inline int hotcueNumberToHotcueIndex(int hotcueNumber) { - if (hotcueNumber >= 1) { - DEBUG_ASSERT(hotcueNumber != kNoHotCueNumber); - return mixxx::kFirstHotCueIndex + (hotcueNumber - 1); // from 1-based numbering - } else { - DEBUG_ASSERT(hotcueNumber == kNoHotCueNumber); - return Cue::kNoHotCue; - } -} - -void appendCueHint(gsl::not_null pHintList, - const mixxx::audio::FramePos& frame, - Hint::Type type) { - if (frame.isValid()) { - const Hint cueHint = { - /*.frame =*/static_cast(frame.toLowerFrameBoundary().value()), - /*.frameCount =*/Hint::kFrameCountForward, - /*.type =*/type}; - pHintList->append(cueHint); - } -} - -void appendCueHint(gsl::not_null pHintList, const double playPos, Hint::Type type) { - const auto frame = mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(playPos); - appendCueHint(pHintList, frame, type); -} - -} // namespace - -CueControl::CueControl(const QString& group, - UserSettingsPointer pConfig) - : EngineControl(group, pConfig), - m_pConfig(pConfig), - m_colorPaletteSettings(ColorPaletteSettings(pConfig)), - m_currentlyPreviewingIndex(Cue::kNoHotCue), - m_pPlay(ControlObject::getControl(ConfigKey(group, "play"))), - m_pStopButton(ControlObject::getControl(ConfigKey(group, "stop"))), - m_bypassCueSetByPlay(false), - m_iNumHotCues(NUM_HOT_CUES), - m_pCurrentSavedLoopControl(nullptr), - m_trackMutex(QT_RECURSIVE_MUTEX_INIT) { - // To silence a compiler warning about CUE_MODE_PIONEER. - Q_UNUSED(CUE_MODE_PIONEER); - - createControls(); - connectControls(); - - m_pTrackSamples = ControlObject::getControl(ConfigKey(group, "track_samples")); - - m_pQuantizeEnabled = ControlObject::getControl(ConfigKey(group, "quantize")); - connect(m_pQuantizeEnabled, &ControlObject::valueChanged, - this, &CueControl::quantizeChanged, - Qt::DirectConnection); - - m_pClosestBeat = ControlObject::getControl(ConfigKey(group, "beat_closest")); - m_pLoopStartPosition = make_parented(group, "loop_start_position", this); - m_pLoopEndPosition = make_parented(group, "loop_end_position", this); - m_pLoopEnabled = make_parented(group, "loop_enabled", this); - m_pBeatLoopActivate = make_parented(group, "beatloop_activate", this); - m_pBeatLoopSize = make_parented(group, "beatloop_size", this); - - m_pCuePoint = new ControlObject(ConfigKey(group, "cue_point")); - m_pCuePoint->set(Cue::kNoPosition); - - m_pCueMode = new ControlObject(ConfigKey(group, "cue_mode")); - - m_pPassthrough = make_parented(group, "passthrough", this); - m_pPassthrough->connectValueChanged(this, - &CueControl::passthroughChanged, - Qt::DirectConnection); -} - -CueControl::~CueControl() { - delete m_pCuePoint; - delete m_pCueMode; - qDeleteAll(m_hotcueControls); -} - -void CueControl::createControls() { - m_pCueSet = std::make_unique(ConfigKey(m_group, "cue_set")); - m_pCueSet->setButtonMode(mixxx::control::ButtonMode::Trigger); - m_pCueClear = std::make_unique(ConfigKey(m_group, "cue_clear")); - m_pCueClear->setButtonMode(mixxx::control::ButtonMode::Trigger); - m_pCueGoto = std::make_unique(ConfigKey(m_group, "cue_goto")); - m_pCueGotoAndPlay = std::make_unique(ConfigKey(m_group, "cue_gotoandplay")); - m_pCuePlay = std::make_unique(ConfigKey(m_group, "cue_play")); - m_pCueGotoAndStop = std::make_unique(ConfigKey(m_group, "cue_gotoandstop")); - m_pCuePreview = std::make_unique(ConfigKey(m_group, "cue_preview")); - m_pCueCDJ = std::make_unique(ConfigKey(m_group, "cue_cdj")); - m_pCueDefault = std::make_unique(ConfigKey(m_group, "cue_default")); - m_pPlayStutter = std::make_unique(ConfigKey(m_group, "play_stutter")); - - m_pPlayLatched = std::make_unique(ConfigKey(m_group, "play_latched")); - m_pPlayLatched->setReadOnly(); - - m_pCueIndicator = std::make_unique(ConfigKey(m_group, "cue_indicator")); - m_pPlayIndicator = std::make_unique(ConfigKey(m_group, "play_indicator")); - - m_pIntroStartPosition = std::make_unique( - ConfigKey(m_group, "intro_start_position")); - m_pIntroStartPosition->set(Cue::kNoPosition); - m_pIntroStartEnabled = std::make_unique( - ConfigKey(m_group, "intro_start_enabled")); - m_pIntroStartEnabled->setReadOnly(); - m_pIntroStartSet = std::make_unique(ConfigKey(m_group, "intro_start_set")); - m_pIntroStartClear = std::make_unique( - ConfigKey(m_group, "intro_start_clear")); - m_pIntroStartActivate = std::make_unique( - ConfigKey(m_group, "intro_start_activate")); - m_pIntroEndPosition = std::make_unique(ConfigKey(m_group, "intro_end_position")); - m_pIntroEndPosition->set(Cue::kNoPosition); - m_pIntroEndEnabled = std::make_unique(ConfigKey(m_group, "intro_end_enabled")); - m_pIntroEndEnabled->setReadOnly(); - m_pIntroEndSet = std::make_unique(ConfigKey(m_group, "intro_end_set")); - m_pIntroEndClear = std::make_unique(ConfigKey(m_group, "intro_end_clear")); - m_pIntroEndActivate = std::make_unique( - ConfigKey(m_group, "intro_end_activate")); - - m_pOutroStartPosition = std::make_unique( - ConfigKey(m_group, "outro_start_position")); - m_pOutroStartPosition->set(Cue::kNoPosition); - m_pOutroStartEnabled = std::make_unique( - ConfigKey(m_group, "outro_start_enabled")); - m_pOutroStartEnabled->setReadOnly(); - m_pOutroStartSet = std::make_unique(ConfigKey(m_group, "outro_start_set")); - m_pOutroStartClear = std::make_unique( - ConfigKey(m_group, "outro_start_clear")); - m_pOutroStartActivate = std::make_unique( - ConfigKey(m_group, "outro_start_activate")); - m_pOutroEndPosition = std::make_unique(ConfigKey(m_group, "outro_end_position")); - m_pOutroEndPosition->set(Cue::kNoPosition); - m_pOutroEndEnabled = std::make_unique(ConfigKey(m_group, "outro_end_enabled")); - m_pOutroEndEnabled->setReadOnly(); - m_pOutroEndSet = std::make_unique(ConfigKey(m_group, "outro_end_set")); - m_pOutroEndClear = std::make_unique(ConfigKey(m_group, "outro_end_clear")); - m_pOutroEndActivate = std::make_unique( - ConfigKey(m_group, "outro_end_activate")); - - m_pVinylControlEnabled = std::make_unique(m_group, "vinylcontrol_enabled"); - m_pVinylControlMode = std::make_unique(m_group, "vinylcontrol_mode"); - - m_pHotcueFocus = std::make_unique(ConfigKey(m_group, "hotcue_focus")); - setHotcueFocusIndex(Cue::kNoHotCue); - m_pHotcueFocusColorPrev = std::make_unique( - ConfigKey(m_group, "hotcue_focus_color_prev")); - m_pHotcueFocusColorNext = std::make_unique( - ConfigKey(m_group, "hotcue_focus_color_next")); - - // Create hotcue controls - for (int i = 0; i < m_iNumHotCues; ++i) { - HotcueControl* pControl = new HotcueControl(m_group, i); - m_hotcueControls.append(pControl); - } -} - -void CueControl::connectControls() { - // Main Cue controls - connect(m_pCueSet.get(), - &ControlObject::valueChanged, - this, - &CueControl::cueSet, - Qt::DirectConnection); - connect(m_pCueClear.get(), - &ControlObject::valueChanged, - this, - &CueControl::cueClear, - Qt::DirectConnection); - connect(m_pCueGoto.get(), - &ControlObject::valueChanged, - this, - &CueControl::cueGoto, - Qt::DirectConnection); - connect(m_pCueGotoAndPlay.get(), - &ControlObject::valueChanged, - this, - &CueControl::cueGotoAndPlay, - Qt::DirectConnection); - connect(m_pCuePlay.get(), - &ControlObject::valueChanged, - this, - &CueControl::cuePlay, - Qt::DirectConnection); - connect(m_pCueGotoAndStop.get(), - &ControlObject::valueChanged, - this, - &CueControl::cueGotoAndStop, - Qt::DirectConnection); - connect(m_pCuePreview.get(), - &ControlObject::valueChanged, - this, - &CueControl::cuePreview, - Qt::DirectConnection); - connect(m_pCueCDJ.get(), - &ControlObject::valueChanged, - this, - &CueControl::cueCDJ, - Qt::DirectConnection); - connect(m_pCueDefault.get(), - &ControlObject::valueChanged, - this, - &CueControl::cueDefault, - Qt::DirectConnection); - connect(m_pPlayStutter.get(), - &ControlObject::valueChanged, - this, - &CueControl::playStutter, - Qt::DirectConnection); - - connect(m_pIntroStartSet.get(), - &ControlObject::valueChanged, - this, - &CueControl::introStartSet, - Qt::DirectConnection); - connect(m_pIntroStartClear.get(), - &ControlObject::valueChanged, - this, - &CueControl::introStartClear, - Qt::DirectConnection); - connect(m_pIntroStartActivate.get(), - &ControlObject::valueChanged, - this, - &CueControl::introStartActivate, - Qt::DirectConnection); - connect(m_pIntroEndSet.get(), - &ControlObject::valueChanged, - this, - &CueControl::introEndSet, - Qt::DirectConnection); - connect(m_pIntroEndClear.get(), - &ControlObject::valueChanged, - this, - &CueControl::introEndClear, - Qt::DirectConnection); - connect(m_pIntroEndActivate.get(), - &ControlObject::valueChanged, - this, - &CueControl::introEndActivate, - Qt::DirectConnection); - - connect(m_pOutroStartSet.get(), - &ControlObject::valueChanged, - this, - &CueControl::outroStartSet, - Qt::DirectConnection); - connect(m_pOutroStartClear.get(), - &ControlObject::valueChanged, - this, - &CueControl::outroStartClear, - Qt::DirectConnection); - connect(m_pOutroStartActivate.get(), - &ControlObject::valueChanged, - this, - &CueControl::outroStartActivate, - Qt::DirectConnection); - connect(m_pOutroEndSet.get(), - &ControlObject::valueChanged, - this, - &CueControl::outroEndSet, - Qt::DirectConnection); - connect(m_pOutroEndClear.get(), - &ControlObject::valueChanged, - this, - &CueControl::outroEndClear, - Qt::DirectConnection); - connect(m_pOutroEndActivate.get(), - &ControlObject::valueChanged, - this, - &CueControl::outroEndActivate, - Qt::DirectConnection); - - connect(m_pHotcueFocusColorPrev.get(), - &ControlObject::valueChanged, - this, - &CueControl::hotcueFocusColorPrev, - Qt::DirectConnection); - connect(m_pHotcueFocusColorNext.get(), - &ControlObject::valueChanged, - this, - &CueControl::hotcueFocusColorNext, - Qt::DirectConnection); - - // Hotcue controls - for (const auto& pControl : std::as_const(m_hotcueControls)) { - connect(pControl, &HotcueControl::hotcuePositionChanged, - this, &CueControl::hotcuePositionChanged, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueEndPositionChanged, - this, - &CueControl::hotcueEndPositionChanged, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueSet, - this, - &CueControl::hotcueSet, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueGoto, - this, - &CueControl::hotcueGoto, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueGotoAndPlay, - this, - &CueControl::hotcueGotoAndPlay, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueGotoAndStop, - this, - &CueControl::hotcueGotoAndStop, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueGotoAndLoop, - this, - &CueControl::hotcueGotoAndLoop, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueCueLoop, - this, - &CueControl::hotcueCueLoop, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueActivate, - this, - &CueControl::hotcueActivate, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueActivatePreview, - this, - &CueControl::hotcueActivatePreview, - Qt::DirectConnection); - connect(pControl, - &HotcueControl::hotcueClear, - this, - &CueControl::hotcueClear, - Qt::DirectConnection); - } -} - -void CueControl::disconnectControls() { - disconnect(m_pCueSet.get(), nullptr, this, nullptr); - disconnect(m_pCueClear.get(), nullptr, this, nullptr); - disconnect(m_pCueGoto.get(), nullptr, this, nullptr); - disconnect(m_pCueGotoAndPlay.get(), nullptr, this, nullptr); - disconnect(m_pCuePlay.get(), nullptr, this, nullptr); - disconnect(m_pCueGotoAndStop.get(), nullptr, this, nullptr); - disconnect(m_pCuePreview.get(), nullptr, this, nullptr); - disconnect(m_pCueCDJ.get(), nullptr, this, nullptr); - disconnect(m_pCueDefault.get(), nullptr, this, nullptr); - disconnect(m_pPlayStutter.get(), nullptr, this, nullptr); - - disconnect(m_pIntroStartSet.get(), nullptr, this, nullptr); - disconnect(m_pIntroStartClear.get(), nullptr, this, nullptr); - disconnect(m_pIntroStartActivate.get(), nullptr, this, nullptr); - disconnect(m_pIntroEndSet.get(), nullptr, this, nullptr); - disconnect(m_pIntroEndClear.get(), nullptr, this, nullptr); - disconnect(m_pIntroEndActivate.get(), nullptr, this, nullptr); - - disconnect(m_pOutroStartSet.get(), nullptr, this, nullptr); - disconnect(m_pOutroStartClear.get(), nullptr, this, nullptr); - disconnect(m_pOutroStartActivate.get(), nullptr, this, nullptr); - disconnect(m_pOutroEndSet.get(), nullptr, this, nullptr); - disconnect(m_pOutroEndClear.get(), nullptr, this, nullptr); - disconnect(m_pOutroEndActivate.get(), nullptr, this, nullptr); - - disconnect(m_pHotcueFocusColorPrev.get(), nullptr, this, nullptr); - disconnect(m_pHotcueFocusColorNext.get(), nullptr, this, nullptr); - - for (const auto& pControl : std::as_const(m_hotcueControls)) { - disconnect(pControl, nullptr, this, nullptr); - } -} - -void CueControl::passthroughChanged(double enabled) { - if (enabled > 0) { - // If passthrough was enabled seeking and playing is prohibited, and the - // waveform and overview are blocked. - // Disconnect all cue controls to prevent cue changes without UI feedback. - disconnectControls(); - } else { - // Reconnect all controls when deck returns to regular mode. - connectControls(); - } -} - -void CueControl::attachCue(const CuePointer& pCue, HotcueControl* pControl) { - VERIFY_OR_DEBUG_ASSERT(pControl) { - return; - } - detachCue(pControl); - connect(pCue.get(), - &Cue::updated, - this, - &CueControl::cueUpdated, - Qt::DirectConnection); - - pControl->setCue(pCue); -} - -void CueControl::detachCue(HotcueControl* pControl) { - VERIFY_OR_DEBUG_ASSERT(pControl) { - return; - } - - CuePointer pCue = pControl->getCue(); - if (!pCue) { - return; - } - - disconnect(pCue.get(), nullptr, this, nullptr); - m_pCurrentSavedLoopControl.testAndSetRelease(pControl, nullptr); - pControl->resetCue(); -} - -// This is called from the EngineWokerThread and ends with the initial seek -// via seekOnLoad(). There is the theoretical and pending issue of a delayed control -// command intended for the old track that might be performed instead. -void CueControl::trackLoaded(TrackPointer pNewTrack) { - auto lock = lockMutex(&m_trackMutex); - if (m_pLoadedTrack) { - disconnect(m_pLoadedTrack.get(), nullptr, this, nullptr); - - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - - for (const auto& pControl : std::as_const(m_hotcueControls)) { - detachCue(pControl); - } - - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - m_pCuePoint->set(Cue::kNoPosition); - m_pIntroStartPosition->set(Cue::kNoPosition); - m_pIntroStartEnabled->forceSet(0.0); - m_pIntroEndPosition->set(Cue::kNoPosition); - m_pIntroEndEnabled->forceSet(0.0); - m_pOutroStartPosition->set(Cue::kNoPosition); - m_pOutroStartEnabled->forceSet(0.0); - m_pOutroEndPosition->set(Cue::kNoPosition); - m_pOutroEndEnabled->forceSet(0.0); - m_n60dBSoundStartPosition.setValue(Cue::kNoPosition); - setHotcueFocusIndex(Cue::kNoHotCue); - m_pLoadedTrack.reset(); - m_usedSeekOnLoadPosition.setValue(mixxx::audio::kStartFramePos); - } - - if (!pNewTrack) { - return; - } - m_pLoadedTrack = pNewTrack; - - connect(m_pLoadedTrack.get(), - &Track::analyzed, - this, - &CueControl::trackAnalyzed, - Qt::DirectConnection); - - connect(m_pLoadedTrack.get(), - &Track::cuesUpdated, - this, - &CueControl::trackCuesUpdated, - Qt::DirectConnection); - - connect(m_pLoadedTrack.get(), - &Track::loopRemove, - this, - &CueControl::loopRemove); - - lock.unlock(); - - // Use pNewTrack from now, because m_pLoadedTrack might have been reset - // immediately after leaving the locking scope! - - // Update COs with cues from track. - loadCuesFromTrack(); - - // Seek track according to SeekOnLoadMode. - SeekOnLoadMode seekOnLoadMode = getSeekOnLoadPreference(); - - switch (seekOnLoadMode) { - case SeekOnLoadMode::Beginning: - // This allows users to load tracks and have the needle-drop be maintained. - if (!(m_pVinylControlEnabled->toBool() && - m_pVinylControlMode->get() == MIXXX_VCMODE_ABSOLUTE)) { - seekOnLoad(mixxx::audio::kStartFramePos); - } - return; - case SeekOnLoadMode::FirstSound: { - CuePointer pN60dBSound = - pNewTrack->findCueByType(mixxx::CueType::N60dBSound); - mixxx::audio::FramePos n60dBSoundPosition; - if (pN60dBSound) { - n60dBSoundPosition = pN60dBSound->getPosition(); - } - if (n60dBSoundPosition.isValid()) { - seekOnLoad(n60dBSoundPosition); - } else { - seekOnLoad(mixxx::audio::kStartFramePos); - } - break; - } - case SeekOnLoadMode::MainCue: { - // Take main cue position from CO instead of cue point list because - // value in CO will be quantized if quantization is enabled - // while value in cue point list will never be quantized. - // This prevents jumps when track analysis finishes while quantization is enabled. - const auto mainCuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (mainCuePosition.isValid()) { - seekOnLoad(mainCuePosition); - return; - } - break; - } - case SeekOnLoadMode::FirstHotcue: { - mixxx::audio::FramePos firstHotcuePosition; - HotcueControl* pControl = m_hotcueControls.value(0, nullptr); - if (pControl) { - firstHotcuePosition = pControl->getPosition(); - if (firstHotcuePosition.isValid()) { - seekOnLoad(firstHotcuePosition); - return; - } - } - break; - } - case SeekOnLoadMode::IntroStart: { - const auto introStartPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - if (introStartPosition.isValid()) { - seekOnLoad(introStartPosition); - return; - } - break; - } - default: - DEBUG_ASSERT(!"Unknown enum value"); - break; - } - seekOnLoad(mixxx::audio::kStartFramePos); -} - -void CueControl::seekOnLoad(mixxx::audio::FramePos seekOnLoadPosition) { - DEBUG_ASSERT(seekOnLoadPosition.isValid()); - seekExact(seekOnLoadPosition); - m_usedSeekOnLoadPosition.setValue(seekOnLoadPosition); -} - -void CueControl::cueUpdated() { - //auto lock = lockMutex(&m_mutex); - // We should get a trackCuesUpdated call anyway, so do nothing. -} - -void CueControl::loadCuesFromTrack() { - auto lock = lockMutex(&m_trackMutex); - if (!m_pLoadedTrack) { - return; - } - - QSet active_hotcues; - CuePointer pMainCue; - CuePointer pIntroCue; - CuePointer pOutroCue; - - const QList cues = m_pLoadedTrack->getCuePoints(); - for (const auto& pCue : cues) { - switch (pCue->getType()) { - case mixxx::CueType::MainCue: - DEBUG_ASSERT(!pMainCue); // There should be only one MainCue cue - pMainCue = pCue; - break; - case mixxx::CueType::Intro: - DEBUG_ASSERT(!pIntroCue); // There should be only one Intro cue - pIntroCue = pCue; - break; - case mixxx::CueType::Outro: - DEBUG_ASSERT(!pOutroCue); // There should be only one Outro cue - pOutroCue = pCue; - break; - case mixxx::CueType::HotCue: - case mixxx::CueType::Loop: { - if (pCue->getHotCue() == Cue::kNoHotCue) { - continue; - } - - int hotcue = pCue->getHotCue(); - HotcueControl* pControl = m_hotcueControls.value(hotcue, nullptr); - - // Cue's hotcue doesn't have a hotcue control. - if (pControl == nullptr) { - continue; - } - - CuePointer pOldCue(pControl->getCue()); - - // If the old hotcue is different than this one. - if (pOldCue != pCue) { - // old cue is detached if required - attachCue(pCue, pControl); - } else { - // If the old hotcue is the same, then we only need to update - Cue::StartAndEndPositions pos = pCue->getStartAndEndPosition(); - pControl->setPosition(pos.startPosition); - pControl->setEndPosition(pos.endPosition); - pControl->setColor(pCue->getColor()); - pControl->setType(pCue->getType()); - } - // Add the hotcue to the list of active hotcues - active_hotcues.insert(hotcue); - break; - } - case mixxx::CueType::N60dBSound: { - Cue::StartAndEndPositions pos = pCue->getStartAndEndPosition(); - m_n60dBSoundStartPosition.setValue(pos.startPosition.toEngineSamplePos()); - break; - } - case mixxx::CueType::Beat: - case mixxx::CueType::Jump: - case mixxx::CueType::Invalid: - default: - break; - } - } - - // Detach all hotcues that are no longer present - for (int hotCueIndex = 0; hotCueIndex < m_iNumHotCues; ++hotCueIndex) { - if (!active_hotcues.contains(hotCueIndex)) { - HotcueControl* pControl = m_hotcueControls.at(hotCueIndex); - detachCue(pControl); - } - } - - if (pIntroCue) { - const auto startPosition = quantizeCuePoint(pIntroCue->getPosition()); - const auto endPosition = quantizeCuePoint(pIntroCue->getEndPosition()); - - m_pIntroStartPosition->set(startPosition.toEngineSamplePosMaybeInvalid()); - m_pIntroStartEnabled->forceSet(startPosition.isValid()); - m_pIntroEndPosition->set(endPosition.toEngineSamplePosMaybeInvalid()); - m_pIntroEndEnabled->forceSet(endPosition.isValid()); - } else { - m_pIntroStartPosition->set(Cue::kNoPosition); - m_pIntroStartEnabled->forceSet(0.0); - m_pIntroEndPosition->set(Cue::kNoPosition); - m_pIntroEndEnabled->forceSet(0.0); - } - - if (pOutroCue) { - const auto startPosition = quantizeCuePoint(pOutroCue->getPosition()); - const auto endPosition = quantizeCuePoint(pOutroCue->getEndPosition()); - - m_pOutroStartPosition->set(startPosition.toEngineSamplePosMaybeInvalid()); - m_pOutroStartEnabled->forceSet(startPosition.isValid()); - m_pOutroEndPosition->set(endPosition.toEngineSamplePosMaybeInvalid()); - m_pOutroEndEnabled->forceSet(endPosition.isValid()); - } else { - m_pOutroStartPosition->set(Cue::kNoPosition); - m_pOutroStartEnabled->forceSet(0.0); - m_pOutroEndPosition->set(Cue::kNoPosition); - m_pOutroEndEnabled->forceSet(0.0); - } - - // Because of legacy, we store the main cue point twice and need to - // sync both values. - // The mixxx::CueType::MainCue from getCuePoints() has the priority - mixxx::audio::FramePos mainCuePosition; - if (pMainCue) { - mainCuePosition = pMainCue->getPosition(); - // adjust the track cue accordingly - m_pLoadedTrack->setMainCuePosition(mainCuePosition); - } else { - // If no load cue point is stored, read from track - // Note: This is mixxx::audio::kStartFramePos for new tracks - // and always a valid position. - mainCuePosition = m_pLoadedTrack->getMainCuePosition(); - // A main cue point only needs to be added if the position - // differs from the default position. - if (mainCuePosition.isValid() && - mainCuePosition != mixxx::audio::kStartFramePos) { - qInfo() - << "Adding missing main cue point at" - << mainCuePosition - << "for track" - << m_pLoadedTrack->getLocation(); - m_pLoadedTrack->createAndAddCue( - mixxx::CueType::MainCue, - Cue::kNoHotCue, - mainCuePosition, - mixxx::audio::kInvalidFramePos); - } - } - - DEBUG_ASSERT(mainCuePosition.isValid()); - const auto quantizedMainCuePosition = quantizeCuePoint(mainCuePosition); - m_pCuePoint->set(quantizedMainCuePosition.toEngineSamplePosMaybeInvalid()); -} - -void CueControl::trackAnalyzed() { - if (frameInfo().currentPosition != m_usedSeekOnLoadPosition.getValue()) { - // the track is already manual cued, don't re-cue - return; - } - - // Make track follow the updated cues. - SeekOnLoadMode seekOnLoadMode = getSeekOnLoadPreference(); - - switch (seekOnLoadMode) { - case SeekOnLoadMode::MainCue: { - const auto position = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (position.isValid()) { - seekOnLoad(position); - } - break; - } - case SeekOnLoadMode::IntroStart: { - const auto position = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - if (position.isValid()) { - seekOnLoad(position); - } - break; - } - default: - // nothing to do here - break; - } -} - -void CueControl::trackCuesUpdated() { - loadCuesFromTrack(); -} - -void CueControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) { - Q_UNUSED(pBeats); - loadCuesFromTrack(); -} - -void CueControl::quantizeChanged(double v) { - Q_UNUSED(v); - - // check if we were at the cue point before - bool wasTrackAtCue = getTrackAt() == TrackAt::Cue; - bool wasTrackAtIntro = isTrackAtIntroCue(); - - loadCuesFromTrack(); - - // if we are playing (no matter what reason for) do not seek - if (m_pPlay->toBool()) { - return; - } - - // Retrieve new cue pos and follow - const auto cuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (wasTrackAtCue && cuePosition.isValid()) { - seekExact(cuePosition); - } - // Retrieve new intro start pos and follow - const auto introPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - if (wasTrackAtIntro && introPosition.isValid()) { - seekExact(introPosition); - } -} - -mixxx::RgbColor CueControl::colorFromConfig(const ConfigKey& configKey) { - auto hotcueColorPalette = - m_colorPaletteSettings.getHotcueColorPalette(); - int colorIndex = m_pConfig->getValue(configKey, -1); - if (colorIndex < 0 || colorIndex >= hotcueColorPalette.size()) { - return hotcueColorPalette.defaultColor(); - } - return hotcueColorPalette.at(colorIndex); -}; - -void CueControl::hotcueSet(HotcueControl* pControl, double value, HotcueSetMode mode) { - //qDebug() << "CueControl::hotcueSet" << value; - - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - if (!m_pLoadedTrack) { - return; - } - - // Note: the cue is just detached from the hotcue control - // It remains in the database for later use - // TODO: find a rule, that allows us to delete the cue as well - // https://github.com/mixxxdj/mixxx/issues/8740 - hotcueClear(pControl, value); - - mixxx::audio::FramePos cueStartPosition; - mixxx::audio::FramePos cueEndPosition; - mixxx::CueType cueType = mixxx::CueType::Invalid; - - bool loopEnabled = m_pLoopEnabled->toBool(); - if (mode == HotcueSetMode::Auto) { - if (loopEnabled) { - // Don't create a hotcue at loop start if there is one already. - // This allows to set a hotuce inside an active, saved loop with - // 'hotcue_X_activate'. - auto* pSavedLoopControl = m_pCurrentSavedLoopControl.loadAcquire(); - if (pSavedLoopControl && - pSavedLoopControl->getPosition() == - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pLoopStartPosition->get()) && - pSavedLoopControl->getEndPosition() == - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pLoopEndPosition->get())) { - mode = HotcueSetMode::Cue; - } else { - mode = HotcueSetMode::Loop; - } - } else { - mode = HotcueSetMode::Cue; - } - } - - switch (mode) { - case HotcueSetMode::Cue: { - // If no loop is enabled, just store regular jump cue - cueStartPosition = getQuantizedCurrentPosition(); - cueType = mixxx::CueType::HotCue; - break; - } - case HotcueSetMode::Loop: { - if (loopEnabled) { - // If a loop is enabled, save the current loop - cueStartPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pLoopStartPosition->get()); - cueEndPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pLoopEndPosition->get()); - } else { - // If no loop is enabled, save a loop starting from the current - // position and with the current beatloop size - cueStartPosition = getQuantizedCurrentPosition(); - double beatloopSize = m_pBeatLoopSize->get(); - const mixxx::BeatsPointer pBeats = m_pLoadedTrack->getBeats(); - if (beatloopSize <= 0 || !pBeats) { - return; - } - if (cueStartPosition.isValid()) { - cueEndPosition = pBeats->findNBeatsFromPosition(cueStartPosition, beatloopSize); - } - } - cueType = mixxx::CueType::Loop; - break; - } - default: - DEBUG_ASSERT(!"Invalid HotcueSetMode"); - return; - } - - VERIFY_OR_DEBUG_ASSERT(cueType != mixxx::CueType::Invalid) { - return; - } - - // Abort if no position has been found. - VERIFY_OR_DEBUG_ASSERT(cueStartPosition.isValid() && - (cueType != mixxx::CueType::Loop || cueEndPosition.isValid())) { - return; - } - - int hotcueIndex = pControl->getHotcueIndex(); - - mixxx::RgbColor color = mixxx::PredefinedColorPalettes::kDefaultCueColor; - if (cueType == mixxx::CueType::Loop) { - ConfigKey autoLoopColorsKey("[Controls]", "auto_loop_colors"); - if (getConfig()->getValue(autoLoopColorsKey, false)) { - color = m_colorPaletteSettings.getHotcueColorPalette().colorForHotcueIndex(hotcueIndex); - } else { - color = colorFromConfig(ConfigKey("[Controls]", "LoopDefaultColorIndex")); - } - } else { - ConfigKey autoHotcueColorsKey("[Controls]", "auto_hotcue_colors"); - if (getConfig()->getValue(autoHotcueColorsKey, false)) { - color = m_colorPaletteSettings.getHotcueColorPalette().colorForHotcueIndex(hotcueIndex); - } else { - color = colorFromConfig(ConfigKey("[Controls]", "HotcueDefaultColorIndex")); - } - } - - CuePointer pCue = m_pLoadedTrack->createAndAddCue( - cueType, - hotcueIndex, - cueStartPosition, - cueEndPosition, - color); - - // TODO(XXX) deal with spurious signals - attachCue(pCue, pControl); - - if (cueType == mixxx::CueType::Loop) { - setCurrentSavedLoopControlAndActivate(pControl); - } - - // If quantize is enabled and we are not playing, jump to the cue point - // since it's not necessarily where we currently are. TODO(XXX) is this - // potentially invalid for vinyl control? - bool playing = m_pPlay->toBool(); - if (!playing && m_pQuantizeEnabled->toBool()) { - lock.unlock(); // prevent deadlock. - // Enginebuffer will quantize more exactly than we can. - seekAbs(cueStartPosition); - } -} - -void CueControl::hotcueGoto(HotcueControl* pControl, double value) { - if (value <= 0) { - return; - } - const mixxx::audio::FramePos position = pControl->getPosition(); - if (position.isValid()) { - seekAbs(position); - } -} - -void CueControl::hotcueGotoAndStop(HotcueControl* pControl, double value) { - if (value <= 0) { - return; - } - - const mixxx::audio::FramePos position = pControl->getPosition(); - if (!position.isValid()) { - return; - } - - if (m_currentlyPreviewingIndex == Cue::kNoHotCue) { - m_pPlay->set(0.0); - seekExact(position); - } else { - // this becomes a play latch command if we are previewing - m_pPlay->set(0.0); - } -} - -void CueControl::hotcueGotoAndPlay(HotcueControl* pControl, double value) { - if (value <= 0) { - return; - } - const mixxx::audio::FramePos position = pControl->getPosition(); - if (position.isValid()) { - seekAbs(position); - // End previewing to not jump back if a sticking finger on a cue - // button is released (just in case) - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - if (!m_pPlay->toBool()) { - // don't move the cue point to the hot cue point in DENON mode - m_bypassCueSetByPlay = true; - m_pPlay->set(1.0); - } - } - - setHotcueFocusIndex(pControl->getHotcueIndex()); -} - -void CueControl::hotcueGotoAndLoop(HotcueControl* pControl, double value) { - if (value == 0) { - return; - } - CuePointer pCue = pControl->getCue(); - if (!pCue) { - return; - } - - const mixxx::audio::FramePos startPosition = pCue->getPosition(); - if (!startPosition.isValid()) { - return; - } - - if (pCue->getType() == mixxx::CueType::Loop) { - seekAbs(startPosition); - setCurrentSavedLoopControlAndActivate(pControl); - } else if (pCue->getType() == mixxx::CueType::HotCue) { - seekAbs(startPosition); - setBeatLoop(startPosition, true); - } else { - return; - } - - // End previewing to not jump back if a sticking finger on a cue - // button is released (just in case) - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - if (!m_pPlay->toBool()) { - // don't move the cue point to the hot cue point in DENON mode - m_bypassCueSetByPlay = true; - m_pPlay->set(1.0); - } - - setHotcueFocusIndex(pControl->getHotcueIndex()); -} - -void CueControl::hotcueCueLoop(HotcueControl* pControl, double value) { - if (value == 0) { - return; - } - - CuePointer pCue = pControl->getCue(); - - if (!pCue || !pCue->getPosition().isValid()) { - hotcueSet(pControl, value, HotcueSetMode::Cue); - pCue = pControl->getCue(); - VERIFY_OR_DEBUG_ASSERT(pCue && pCue->getPosition().isValid()) { - return; - } - } - - switch (pCue->getType()) { - case mixxx::CueType::Loop: { - // The hotcue_X_cueloop CO was invoked for a saved loop, set it as - // active the first time this happens and toggle the loop_enabled state - // on subsequent invocations. - if (m_pCurrentSavedLoopControl != pControl) { - setCurrentSavedLoopControlAndActivate(pControl); - } else { - bool loopActive = pControl->getStatus() == HotcueControl::Status::Active; - Cue::StartAndEndPositions pos = pCue->getStartAndEndPosition(); - setLoop(pos.startPosition, pos.endPosition, !loopActive); - } - } break; - case mixxx::CueType::HotCue: { - // The hotcue_X_cueloop CO was invoked for a hotcue. In that case, - // create a beatloop starting at the hotcue position. This is useful for - // mapping the CUE LOOP mode labeled on some controllers. - setCurrentSavedLoopControlAndActivate(nullptr); - const mixxx::audio::FramePos startPosition = pCue->getPosition(); - const bool loopActive = m_pLoopEnabled->toBool() && - startPosition == - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pLoopStartPosition->get()); - setBeatLoop(startPosition, !loopActive); - break; - } - default: - return; - } - - setHotcueFocusIndex(pControl->getHotcueIndex()); -} - -void CueControl::hotcueActivate(HotcueControl* pControl, double value, HotcueSetMode mode) { - //qDebug() << "CueControl::hotcueActivate" << value; - - CuePointer pCue = pControl->getCue(); - if (value > 0) { - // pressed - if (pCue && pCue->getPosition().isValid() && - pCue->getType() != mixxx::CueType::Invalid) { - if (m_pPlay->toBool() && m_currentlyPreviewingIndex == Cue::kNoHotCue) { - // playing by Play button - switch (pCue->getType()) { - case mixxx::CueType::HotCue: - hotcueGoto(pControl, value); - break; - case mixxx::CueType::Loop: - if (m_pCurrentSavedLoopControl != pControl) { - setCurrentSavedLoopControlAndActivate(pControl); - } else { - bool loopActive = pControl->getStatus() == - HotcueControl::Status::Active; - Cue::StartAndEndPositions pos = pCue->getStartAndEndPosition(); - setLoop(pos.startPosition, pos.endPosition, !loopActive); - } - break; - default: - DEBUG_ASSERT(!"Invalid CueType!"); - } - } else { - // pressed during pause or preview - hotcueActivatePreview(pControl, value); - } - } else { - // pressed a not existing cue - hotcueSet(pControl, value, mode); - } - } else { - // released - hotcueActivatePreview(pControl, value); - } - - setHotcueFocusIndex(pControl->getHotcueIndex()); -} - -void CueControl::hotcueActivatePreview(HotcueControl* pControl, double value) { - CuePointer pCue = pControl->getCue(); - int index = pControl->getHotcueIndex(); - if (value > 0) { - if (m_currentlyPreviewingIndex != index) { - pControl->cachePreviewingStartState(); - const mixxx::audio::FramePos position = pControl->getPreviewingPosition(); - mixxx::CueType type = pControl->getPreviewingType(); - if (type != mixxx::CueType::Invalid && position.isValid()) { - updateCurrentlyPreviewingIndex(index); - m_bypassCueSetByPlay = true; - if (type == mixxx::CueType::Loop) { - setCurrentSavedLoopControlAndActivate(pControl); - } else if (pControl->getStatus() == HotcueControl::Status::Set) { - pControl->setStatus(HotcueControl::Status::Active); - } - seekAbs(position); - m_pPlay->set(1.0); - } - } - } else if (m_currentlyPreviewingIndex == index) { - // This is a release of a previewing hotcue - const mixxx::audio::FramePos position = pControl->getPreviewingPosition(); - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - m_pPlay->set(0.0); - if (position.isValid()) { - seekExact(position); - } - } - - setHotcueFocusIndex(pControl->getHotcueIndex()); -} - -void CueControl::updateCurrentlyPreviewingIndex(int hotcueIndex) { - int oldPreviewingIndex = m_currentlyPreviewingIndex.fetchAndStoreRelease(hotcueIndex); - if (oldPreviewingIndex >= 0 && oldPreviewingIndex < m_iNumHotCues) { - // We where already in previewing state, clean up .. - HotcueControl* pLastControl = m_hotcueControls.at(oldPreviewingIndex); - mixxx::CueType lastType = pLastControl->getPreviewingType(); - if (lastType == mixxx::CueType::Loop) { - m_pLoopEnabled->set(0); - } - CuePointer pLastCue(pLastControl->getCue()); - if (pLastCue && pLastCue->getType() != mixxx::CueType::Invalid) { - pLastControl->setStatus(HotcueControl::Status::Set); - } - } -} - -void CueControl::hotcueClear(HotcueControl* pControl, double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - if (!m_pLoadedTrack) { - return; - } - - CuePointer pCue = pControl->getCue(); - if (!pCue) { - return; - } - detachCue(pControl); - m_pLoadedTrack->removeCue(pCue); - setHotcueFocusIndex(Cue::kNoHotCue); -} - -void CueControl::hotcuePositionChanged( - HotcueControl* pControl, double value) { - auto lock = lockMutex(&m_trackMutex); - if (!m_pLoadedTrack) { - return; - } - - CuePointer pCue = pControl->getCue(); - if (!pCue) { - return; - } - - const auto newPosition = mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(value); - // Setting the position to Cue::kNoPosition is the same as calling hotcue_x_clear - if (!newPosition.isValid()) { - detachCue(pControl); - return; - } - - // TODO: Remove this check if we support positions < 0 - const auto trackEndPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pTrackSamples->get()); - if (newPosition <= mixxx::audio::kStartFramePos || - (trackEndPosition.isValid() && newPosition >= trackEndPosition)) { - return; - } - - if (pCue->getType() == mixxx::CueType::Loop && newPosition >= pCue->getEndPosition()) { - return; - } - pCue->setStartPosition(newPosition); -} - -void CueControl::hotcueEndPositionChanged( - HotcueControl* pControl, double value) { - CuePointer pCue = pControl->getCue(); - if (!pCue) { - return; - } - - const auto newEndPosition = mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(value); - - // Setting the end position of a loop cue to Cue::kNoPosition converts - // it into a regular jump cue - if (pCue->getType() == mixxx::CueType::Loop && !newEndPosition.isValid()) { - pCue->setType(mixxx::CueType::HotCue); - pCue->setEndPosition(mixxx::audio::kInvalidFramePos); - } else { - const mixxx::audio::FramePos startPosition = pCue->getPosition(); - if (startPosition.isValid() && newEndPosition > startPosition) { - pCue->setEndPosition(newEndPosition); - } - } -} - -void CueControl::hintReader(gsl::not_null pHintList) { - appendCueHint(pHintList, m_pCuePoint->get(), Hint::Type::MainCue); - - // this is called from the engine thread - // it is no locking required, because m_hotcueControl is filled during the - // constructor and getPosition()->get() is a ControlObject - for (const auto& pControl : std::as_const(m_hotcueControls)) { - appendCueHint(pHintList, pControl->getPosition(), Hint::Type::HotCue); - } - - appendCueHint(pHintList, m_n60dBSoundStartPosition.getValue(), Hint::Type::FirstSound); - appendCueHint(pHintList, m_pIntroStartPosition->get(), Hint::Type::IntroStart); - appendCueHint(pHintList, m_pIntroEndPosition->get(), Hint::Type::IntroEnd); - appendCueHint(pHintList, m_pOutroStartPosition->get(), Hint::Type::OutroStart); -} - -// Moves the cue point to current position or to closest beat in case -// quantize is enabled -void CueControl::cueSet(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - const mixxx::audio::FramePos position = getQuantizedCurrentPosition(); - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Store cue point in loaded track - // The m_pCuePoint CO is set via loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - pLoadedTrack->setMainCuePosition(position); - } -} - -void CueControl::cueClear(double value) { - if (value <= 0) { - return; - } - - // the m_pCuePoint CO is set via loadCuesFromTrack() - // no locking required - TrackPointer pLoadedTrack = m_pLoadedTrack; - if (pLoadedTrack) { - pLoadedTrack->setMainCuePosition(mixxx::audio::kStartFramePos); - } -} - -void CueControl::cueGoto(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - // Seek to cue point - const auto mainCuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - - // Note: We do not mess with play here, we continue playing or previewing. - - // Need to unlock before emitting any signals to prevent deadlock. - lock.unlock(); - - if (mainCuePosition.isValid()) { - seekAbs(mainCuePosition); - } -} - -void CueControl::cueGotoAndPlay(double value) { - if (value <= 0) { - return; - } - - cueGoto(value); - auto lock = lockMutex(&m_trackMutex); - // Start playing if not already - - // End previewing to not jump back if a sticking finger on a cue - // button is released (just in case) - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - if (!m_pPlay->toBool()) { - // don't move the cue point to the hot cue point in DENON mode - m_bypassCueSetByPlay = true; - m_pPlay->set(1.0); - } -} - -void CueControl::cueGotoAndStop(double value) { - if (value <= 0) { - return; - } - - if (m_currentlyPreviewingIndex == Cue::kNoHotCue) { - m_pPlay->set(0.0); - const auto mainCuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (mainCuePosition.isValid()) { - seekExact(mainCuePosition); - } - } else { - // this becomes a play latch command if we are previewing - m_pPlay->set(0.0); - } -} - -void CueControl::cuePreview(double value) { - const auto mainCuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (!mainCuePosition.isValid()) { - return; - } - - if (value > 0) { - if (m_currentlyPreviewingIndex == kMainCueIndex) { - return; - } - - updateCurrentlyPreviewingIndex(kMainCueIndex); - seekAbs(mainCuePosition); - m_pPlay->set(1.0); - } else if (m_currentlyPreviewingIndex == kMainCueIndex) { - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - m_pPlay->set(0.0); - seekExact(mainCuePosition); - } -} - -void CueControl::cueCDJ(double value) { - // This is how Pioneer cue buttons work: - // If pressed while freely playing (i.e. playing and platter NOT being touched), stop playback and go to cue. - // If pressed while NOT freely playing (i.e. stopped or playing but platter IS being touched), set new cue point. - // If pressed while stopped and at cue, play while pressed. - // If play is pressed while holding cue, the deck is now playing. (Handled in playFromCuePreview().) - - const auto freely_playing = - m_pPlay->toBool() && !getEngineBuffer()->getScratching(); - TrackAt trackAt = getTrackAt(); - - const auto mainCuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (!mainCuePosition.isValid()) { - return; - } - - if (value > 0) { - if (m_currentlyPreviewingIndex == kMainCueIndex) { - // already previewing, do nothing - return; - } else if (m_currentlyPreviewingIndex != Cue::kNoHotCue) { - // we are already previewing by hotcues - // just jump to cue point and continue previewing - updateCurrentlyPreviewingIndex(kMainCueIndex); - seekAbs(mainCuePosition); - } else if (freely_playing || trackAt == TrackAt::End) { - // Jump to cue when playing or when at end position - m_pPlay->set(0.0); - seekAbs(mainCuePosition); - } else if (trackAt == TrackAt::Cue) { - // paused at cue point - updateCurrentlyPreviewingIndex(kMainCueIndex); - m_pPlay->set(1.0); - } else { - // Paused not at cue point and not at end position - cueSet(value); - - // If quantize is enabled, jump to the cue point since it's not - // necessarily where we currently are - if (m_pQuantizeEnabled->toBool()) { - // We need to re-get the cue point since it changed. - const auto newCuePosition = mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (newCuePosition.isValid()) { - // Enginebuffer will quantize more exactly than we can. - seekAbs(newCuePosition); - } - } - } - } else if (m_currentlyPreviewingIndex == kMainCueIndex) { - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - m_pPlay->set(0.0); - // Need to unlock before emitting any signals to prevent deadlock. - seekExact(mainCuePosition); - } - - // indicator may flash because the delayed adoption of seekAbs - // Correct the Indicator set via play - if (m_pLoadedTrack && !freely_playing) { - m_pCueIndicator->setBlinkValue(ControlIndicator::ON); - } else { - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - } -} - -void CueControl::cueDenon(double value) { - // This is how Denon DN-S 3700 cue buttons work: - // If pressed go to cue and stop. - // If pressed while stopped and at cue, play while pressed. - // Cue Point is moved by play from pause - - bool playing = (m_pPlay->toBool()); - TrackAt trackAt = getTrackAt(); - - const auto mainCuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (!mainCuePosition.isValid()) { - return; - } - - if (value > 0) { - if (m_currentlyPreviewingIndex == kMainCueIndex) { - // already previewing, do nothing - return; - } else if (m_currentlyPreviewingIndex != Cue::kNoHotCue) { - // we are already previewing by hotcues - // just jump to cue point and continue previewing - updateCurrentlyPreviewingIndex(kMainCueIndex); - seekAbs(mainCuePosition); - } else if (!playing && trackAt == TrackAt::Cue) { - // paused at cue point - updateCurrentlyPreviewingIndex(kMainCueIndex); - m_pPlay->set(1.0); - } else { - m_pPlay->set(0.0); - seekExact(mainCuePosition); - } - } else if (m_currentlyPreviewingIndex == kMainCueIndex) { - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - m_pPlay->set(0.0); - seekExact(mainCuePosition); - } -} - -void CueControl::cuePlay(double value) { - // This is how CUP button works: - // If freely playing (i.e. playing and platter NOT being touched), press to go to cue and stop. - // If not freely playing (i.e. stopped or platter IS being touched), press to go to cue and stop. - // On release, start playing from cue point. - - const auto freely_playing = - m_pPlay->toBool() && !getEngineBuffer()->getScratching(); - TrackAt trackAt = getTrackAt(); - - const auto mainCuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (!mainCuePosition.isValid()) { - return; - } - - // pressed - if (value > 0) { - if (freely_playing) { - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - m_pPlay->set(0.0); - seekAbs(mainCuePosition); - } else if (trackAt == TrackAt::ElseWhere) { - // Pause not at cue point and not at end position - cueSet(value); - // Just in case. - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - m_pPlay->set(0.0); - // If quantize is enabled, jump to the cue point since it's not - // necessarily where we currently are - if (m_pQuantizeEnabled->toBool()) { - const auto newCuePosition = mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (newCuePosition.isValid()) { - // Enginebuffer will quantize more exactly than we can. - seekAbs(newCuePosition); - } - } - } - } else if (trackAt == TrackAt::Cue) { - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - m_pPlay->set(1.0); - } -} - -void CueControl::cueDefault(double v) { - double cueMode = m_pCueMode->get(); - // Decide which cue implementation to call based on the user preference - if (cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) { - cueDenon(v); - } else if (cueMode == CUE_MODE_CUP) { - cuePlay(v); - } else { - // The modes CUE_MODE_PIONEER and CUE_MODE_MIXXX are similar - // are handled inside cueCDJ(v) - // default to Pioneer mode - cueCDJ(v); - } -} - -void CueControl::pause(double v) { - auto lock = lockMutex(&m_trackMutex); - //qDebug() << "CueControl::pause()" << v; - if (v > 0.0) { - m_pPlay->set(0.0); - } -} - -void CueControl::playStutter(double v) { - auto lock = lockMutex(&m_trackMutex); - //qDebug() << "playStutter" << v; - if (v > 0.0) { - if (m_pPlay->toBool()) { - if (m_currentlyPreviewingIndex != Cue::kNoHotCue) { - // latch playing - updateCurrentlyPreviewingIndex(Cue::kNoHotCue); - } else { - // Stutter - cueGoto(1.0); - } - } else { - m_pPlay->set(1.0); - } - } -} - -void CueControl::introStartSet(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - - const mixxx::audio::FramePos position = getQuantizedCurrentPosition(); - if (!position.isValid()) { - return; - } - - // Make sure user is not trying to place intro start cue on or after - // other intro/outro cues. - const auto introEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroEndPosition->get()); - const auto outroStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroStartPosition->get()); - const auto outroEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroEndPosition->get()); - if (introEnd.isValid() && position >= introEnd) { - qWarning() - << "Trying to place intro start cue on or after intro end cue."; - return; - } - if (outroStart.isValid() && position >= outroStart) { - qWarning() << "Trying to place intro start cue on or after outro start " - "cue."; - return; - } - if (outroEnd.isValid() && position >= outroEnd) { - qWarning() - << "Trying to place intro start cue on or after outro end cue."; - return; - } - - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Update Track's cue. - // CO's are updated in loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Intro); - if (!pCue) { - pCue = pLoadedTrack->createAndAddCue( - mixxx::CueType::Intro, - Cue::kNoHotCue, - position, - introEnd); - } else { - pCue->setStartAndEndPosition(position, introEnd); - } - } -} - -void CueControl::introStartClear(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - const auto introEndPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroEndPosition->get()); - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Update Track's cue. - // CO's are updated in loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Intro); - if (introEndPosition.isValid()) { - pCue->setStartPosition(mixxx::audio::kInvalidFramePos); - pCue->setEndPosition(introEndPosition); - } else if (pCue) { - pLoadedTrack->removeCue(pCue); - } - } -} - -void CueControl::introStartActivate(double value) { - if (value <= 0) { - return; - } - - const auto introStartPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - if (introStartPosition.isValid()) { - seekAbs(introStartPosition); - } else { - introStartSet(1.0); - } -} - -void CueControl::introEndSet(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - - const mixxx::audio::FramePos position = getQuantizedCurrentPosition(); - if (!position.isValid()) { - return; - } - - // Make sure user is not trying to place intro end cue on or before - // intro start cue, or on or after outro start/end cue. - const auto introStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - const auto outroStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroStartPosition->get()); - const auto outroEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroEndPosition->get()); - if (introStart.isValid() && position <= introStart) { - qWarning() << "Trying to place intro end cue on or before intro start " - "cue."; - return; - } - if (outroStart.isValid() && position >= outroStart) { - qWarning() - << "Trying to place intro end cue on or after outro start cue."; - return; - } - if (outroEnd.isValid() && position >= outroEnd) { - qWarning() - << "Trying to place intro end cue on or after outro end cue."; - return; - } - - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Update Track's cue. - // CO's are updated in loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Intro); - if (!pCue) { - pCue = pLoadedTrack->createAndAddCue( - mixxx::CueType::Intro, - Cue::kNoHotCue, - introStart, - position); - } else { - pCue->setStartAndEndPosition(introStart, position); - } - } -} - -void CueControl::introEndClear(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - const auto introStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Update Track's cue. - // CO's are updated in loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Intro); - if (introStart.isValid()) { - pCue->setStartPosition(introStart); - pCue->setEndPosition(mixxx::audio::kInvalidFramePos); - } else if (pCue) { - pLoadedTrack->removeCue(pCue); - } - } -} - -void CueControl::introEndActivate(double value) { - if (value == 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - const auto introEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroEndPosition->get()); - lock.unlock(); - - if (introEnd.isValid()) { - seekAbs(introEnd); - } else { - introEndSet(1.0); - } -} - -void CueControl::outroStartSet(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - - const mixxx::audio::FramePos position = getQuantizedCurrentPosition(); - if (!position.isValid()) { - return; - } - - // Make sure user is not trying to place outro start cue on or before - // intro end cue or on or after outro end cue. - const auto introStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - const auto introEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroEndPosition->get()); - const auto outroEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroEndPosition->get()); - if (introStart.isValid() && position <= introStart) { - qWarning() << "Trying to place outro start cue on or before intro " - "start cue."; - return; - } - if (introEnd.isValid() && position <= introEnd) { - qWarning() << "Trying to place outro start cue on or before intro end " - "cue."; - return; - } - if (outroEnd.isValid() && position >= outroEnd) { - qWarning() - << "Trying to place outro start cue on or after outro end cue."; - return; - } - - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Update Track's cue. - // CO's are updated in loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Outro); - if (!pCue) { - pCue = pLoadedTrack->createAndAddCue( - mixxx::CueType::Outro, - Cue::kNoHotCue, - position, - outroEnd); - } else { - pCue->setStartAndEndPosition(position, outroEnd); - } - } -} - -void CueControl::outroStartClear(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - const auto outroEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroEndPosition->get()); - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Update Track's cue. - // CO's are updated in loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Outro); - if (outroEnd.isValid()) { - pCue->setStartPosition(mixxx::audio::kInvalidFramePos); - pCue->setEndPosition(outroEnd); - } else if (pCue) { - pLoadedTrack->removeCue(pCue); - } - } -} - -void CueControl::outroStartActivate(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - const auto outroStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroStartPosition->get()); - lock.unlock(); - - if (outroStart.isValid()) { - seekAbs(outroStart); - } else { - outroStartSet(1.0); - } -} - -void CueControl::outroEndSet(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - - const mixxx::audio::FramePos position = getQuantizedCurrentPosition(); - if (!position.isValid()) { - return; - } - - // Make sure user is not trying to place outro end cue on or before - // other intro/outro cues. - const auto introStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - const auto introEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroEndPosition->get()); - const auto outroStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroStartPosition->get()); - if (introStart.isValid() && position <= introStart) { - qWarning() << "Trying to place outro end cue on or before intro start " - "cue."; - return; - } - if (introEnd.isValid() && position <= introEnd) { - qWarning() - << "Trying to place outro end cue on or before intro end cue."; - return; - } - if (outroStart.isValid() && position <= outroStart) { - qWarning() << "Trying to place outro end cue on or before outro start " - "cue."; - return; - } - - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Update Track's cue. - // CO's are updated in loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Outro); - if (!pCue) { - pCue = pLoadedTrack->createAndAddCue( - mixxx::CueType::Outro, - Cue::kNoHotCue, - outroStart, - position); - } else { - pCue->setStartAndEndPosition(outroStart, position); - } - } -} - -void CueControl::outroEndClear(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - const auto outroStart = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroStartPosition->get()); - TrackPointer pLoadedTrack = m_pLoadedTrack; - lock.unlock(); - - // Update Track's cue. - // CO's are updated in loadCuesFromTrack() - // this can be done outside the locking scope - if (pLoadedTrack) { - CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Outro); - if (outroStart.isValid()) { - pCue->setStartPosition(outroStart); - pCue->setEndPosition(mixxx::audio::kInvalidFramePos); - } else if (pCue) { - pLoadedTrack->removeCue(pCue); - } - } -} - -void CueControl::outroEndActivate(double value) { - if (value <= 0) { - return; - } - - auto lock = lockMutex(&m_trackMutex); - const auto outroEnd = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pOutroEndPosition->get()); - lock.unlock(); - - if (outroEnd.isValid()) { - seekAbs(outroEnd); - } else { - outroEndSet(1.0); - } -} - -// This is also called from the engine thread. No locking allowed. -bool CueControl::updateIndicatorsAndModifyPlay( - bool newPlay, bool oldPlay, bool playPossible) { - //qDebug() << "updateIndicatorsAndModifyPlay" << newPlay << playPossible - // << m_iCurrentlyPreviewingHotcues << m_bPreviewing; - CueMode cueMode = static_cast(static_cast(m_pCueMode->get())); - if ((cueMode == CueMode::Denon || cueMode == CueMode::Numark) && - newPlay && !oldPlay && playPossible && - !m_bypassCueSetByPlay) { - // in Denon mode each play from pause moves the cue point - // if not previewing - cueSet(1.0); - } - m_bypassCueSetByPlay = false; - - // when previewing, "play" was set by cue button, a following toggle request - // (play = 0.0) is used for latching play. - bool previewing = false; - if (m_currentlyPreviewingIndex != Cue::kNoHotCue) { - if (!newPlay && oldPlay) { - // play latch request: stop previewing and go into normal play mode. - int oldPreviewingIndex = - m_currentlyPreviewingIndex.fetchAndStoreRelease( - Cue::kNoHotCue); - if (oldPreviewingIndex >= 0 && oldPreviewingIndex < m_iNumHotCues) { - HotcueControl* pLastControl = m_hotcueControls.at(oldPreviewingIndex); - mixxx::CueType lastType = pLastControl->getPreviewingType(); - if (lastType != mixxx::CueType::Loop) { - CuePointer pLastCue(pLastControl->getCue()); - if (pLastCue && pLastCue->getType() != mixxx::CueType::Invalid) { - pLastControl->setStatus(HotcueControl::Status::Set); - } - } - } - newPlay = true; - m_pPlayLatched->forceSet(1.0); - } else { - previewing = true; - m_pPlayLatched->forceSet(0.0); - } - } - - TrackAt trackAt = getTrackAt(); - - if (!playPossible) { - // play not possible - newPlay = false; - m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); - m_pStopButton->set(0.0); - m_pPlayLatched->forceSet(0.0); - } else if (newPlay && !previewing) { - // Play: Indicates a latched Play - m_pPlayIndicator->setBlinkValue(ControlIndicator::ON); - m_pStopButton->set(0.0); - m_pPlayLatched->forceSet(1.0); - } else { - // Pause: - m_pStopButton->set(1.0); - m_pPlayLatched->forceSet(0.0); - if (cueMode == CueMode::Denon) { - if (trackAt == TrackAt::Cue || previewing) { - m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); - } else { - // Flashing indicates that a following play would move cue point - m_pPlayIndicator->setBlinkValue( - ControlIndicator::RATIO1TO1_500MS); - } - } else if (cueMode == CueMode::Mixxx || - cueMode == CueMode::MixxxNoBlinking || - cueMode == CueMode::Numark) { - m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); - } else { - // Flashing indicates that play is possible in Pioneer mode - m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); - } - } - - if (cueMode != CueMode::Denon && cueMode != CueMode::Numark) { - if (m_pCuePoint->get() != Cue::kNoPosition) { - if (newPlay == 0.0 && trackAt == TrackAt::ElseWhere) { - if (cueMode == CueMode::Mixxx) { - // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point - m_pCueIndicator->setBlinkValue( - ControlIndicator::RATIO1TO1_500MS); - } else if (cueMode == CueMode::MixxxNoBlinking) { - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - } else { - // in Pioneer mode Cue Button is flashing fast if CUE will move Cue point - m_pCueIndicator->setBlinkValue( - ControlIndicator::RATIO1TO1_250MS); - } - } else { - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - } - } else { - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - } - } - m_pPlayStutter->set(newPlay ? 1.0 : 0.0); - - return newPlay; -} - -// called from the engine thread -void CueControl::updateIndicators() { - // No need for mutex lock because we are only touching COs. - double cueMode = m_pCueMode->get(); - TrackAt trackAt = getTrackAt(); - - if (cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) { - // Cue button is only lit at cue point - bool playing = m_pPlay->toBool(); - if (trackAt == TrackAt::Cue) { - // at cue point - if (!playing) { - m_pCueIndicator->setBlinkValue(ControlIndicator::ON); - m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); - } - } else { - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - if (!playing) { - if (trackAt != TrackAt::End && cueMode != CUE_MODE_NUMARK) { - // Play will move cue point - m_pPlayIndicator->setBlinkValue( - ControlIndicator::RATIO1TO1_500MS); - } else { - // At track end - m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); - } - } - } - } else { - // Here we have CUE_MODE_PIONEER or CUE_MODE_MIXXX - // default to Pioneer mode - if (m_currentlyPreviewingIndex != kMainCueIndex) { - const auto freely_playing = - m_pPlay->toBool() && !getEngineBuffer()->getScratching(); - if (!freely_playing) { - switch (trackAt) { - case TrackAt::ElseWhere: - if (cueMode == CUE_MODE_MIXXX) { - // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point - m_pCueIndicator->setBlinkValue( - ControlIndicator::RATIO1TO1_500MS); - } else if (cueMode == CUE_MODE_MIXXX_NO_BLINK) { - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - } else { - // in Pioneer mode Cue Button is flashing fast if CUE will move Cue point - m_pCueIndicator->setBlinkValue( - ControlIndicator::RATIO1TO1_250MS); - } - break; - case TrackAt::End: - // At track end - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - break; - case TrackAt::Cue: - // Next Press is preview - m_pCueIndicator->setBlinkValue(ControlIndicator::ON); - break; - } - } else { - // Cue indicator should be off when freely playing - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - } - } else { - // Preview - m_pCueIndicator->setBlinkValue(ControlIndicator::ON); - } - } -} - -void CueControl::resetIndicators() { - m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); - m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); -} - -CueControl::TrackAt CueControl::getTrackAt() const { - FrameInfo info = frameInfo(); - // Note: current can be in the padded silence after the track end > total. - if (info.trackEndPosition.isValid() && info.currentPosition >= info.trackEndPosition) { - return TrackAt::End; - } - const auto mainCuePosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pCuePoint->get()); - if (mainCuePosition.isValid() && fabs(info.currentPosition - mainCuePosition) < 0.5) { - return TrackAt::Cue; - } - return TrackAt::ElseWhere; -} - -mixxx::audio::FramePos CueControl::getQuantizedCurrentPosition() { - FrameInfo info = frameInfo(); - - // Note: currentPos can be past the end of the track, in the padded - // silence of the last buffer. This position might be not reachable in - // a future runs, depending on the buffering. - - // Don't quantize if quantization is disabled. - if (!m_pQuantizeEnabled->toBool()) { - return info.currentPosition; - } - - const auto closestBeat = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pClosestBeat->get()); - // Note: closestBeat can be an interpolated beat past the end of the track, - // which cannot be reached. - if (closestBeat.isValid() && info.trackEndPosition.isValid() && - closestBeat <= info.trackEndPosition) { - return closestBeat; - } - - return info.currentPosition; -} - -mixxx::audio::FramePos CueControl::quantizeCuePoint(mixxx::audio::FramePos position) { - // Don't quantize unset cues. - if (!position.isValid()) { - return mixxx::audio::kInvalidFramePos; - } - - // We need to use m_pTrackSamples here because FrameInfo is set later by - // the engine and not during EngineBuffer::slotTrackLoaded. - const auto trackEndPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pTrackSamples->get()); - - VERIFY_OR_DEBUG_ASSERT(trackEndPosition.isValid()) { - return mixxx::audio::kInvalidFramePos; - } - - // Don't quantize when quantization is disabled. - if (!m_pQuantizeEnabled->toBool()) { - return position; - } - - if (position > trackEndPosition) { - // This can happen if the track length has changed or the cue was set in the - // the padded silence after the track. - position = trackEndPosition; - } - - const mixxx::BeatsPointer pBeats = m_pLoadedTrack->getBeats(); - if (!pBeats) { - return position; - } - - const auto quantizedPosition = pBeats->findClosestBeat(position); - // The closest beat can be an unreachable interpolated beat past the end of - // the track. - if (quantizedPosition.isValid() && quantizedPosition <= trackEndPosition) { - return quantizedPosition; - } - - return position; -} - -bool CueControl::isTrackAtIntroCue() { - const auto introStartPosition = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pIntroStartPosition->get()); - return introStartPosition.isValid() && - (fabs(frameInfo().currentPosition - introStartPosition) < 0.5); -} - -SeekOnLoadMode CueControl::getSeekOnLoadPreference() { - return getConfig()->getValue(ConfigKey("[Controls]", "CueRecall"), SeekOnLoadMode::IntroStart); -} - -void CueControl::hotcueFocusColorPrev(double value) { - if (value <= 0) { - return; - } - - int hotcueIndex = getHotcueFocusIndex(); - if (hotcueIndex < 0 || hotcueIndex >= m_hotcueControls.size()) { - return; - } - - HotcueControl* pControl = m_hotcueControls.at(hotcueIndex); - if (!pControl) { - return; - } - - CuePointer pCue = pControl->getCue(); - if (!pCue) { - return; - } - - mixxx::RgbColor::optional_t color = pCue->getColor(); - if (!color) { - return; - } - - ColorPalette colorPalette = m_colorPaletteSettings.getHotcueColorPalette(); - pCue->setColor(colorPalette.previousColor(*color)); -} - -void CueControl::hotcueFocusColorNext(double value) { - if (value <= 0) { - return; - } - - int hotcueIndex = getHotcueFocusIndex(); - if (hotcueIndex < 0 || hotcueIndex >= m_hotcueControls.size()) { - return; - } - - HotcueControl* pControl = m_hotcueControls.at(hotcueIndex); - if (!pControl) { - return; - } - - CuePointer pCue = pControl->getCue(); - if (!pCue) { - return; - } - - mixxx::RgbColor::optional_t color = pCue->getColor(); - if (!color) { - return; - } - - ColorPalette colorPalette = m_colorPaletteSettings.getHotcueColorPalette(); - pCue->setColor(colorPalette.nextColor(*color)); -} - -void CueControl::setCurrentSavedLoopControlAndActivate(HotcueControl* pControl) { - HotcueControl* pOldSavedLoopControl = m_pCurrentSavedLoopControl.fetchAndStoreAcquire(nullptr); - if (pOldSavedLoopControl && pOldSavedLoopControl != pControl) { - // Disable previous saved loop - DEBUG_ASSERT(pOldSavedLoopControl->getStatus() != HotcueControl::Status::Empty); - pOldSavedLoopControl->setStatus(HotcueControl::Status::Set); - } - - if (!pControl) { - return; - } - CuePointer pCue = pControl->getCue(); - VERIFY_OR_DEBUG_ASSERT(pCue) { - return; - } - - mixxx::CueType type = pCue->getType(); - Cue::StartAndEndPositions pos = pCue->getStartAndEndPosition(); - - VERIFY_OR_DEBUG_ASSERT( - type == mixxx::CueType::Loop && - pos.startPosition.isValid() && - pos.endPosition.isValid()) { - return; - } - - // Set new control as active - setLoop(pos.startPosition, pos.endPosition, true); - pControl->setStatus(HotcueControl::Status::Active); - m_pCurrentSavedLoopControl.storeRelease(pControl); -} - -void CueControl::slotLoopReset() { - setCurrentSavedLoopControlAndActivate(nullptr); -} - -void CueControl::slotLoopEnabledChanged(bool enabled) { - HotcueControl* pSavedLoopControl = m_pCurrentSavedLoopControl; - if (!pSavedLoopControl) { - return; - } - - DEBUG_ASSERT(pSavedLoopControl->getStatus() != HotcueControl::Status::Empty); - DEBUG_ASSERT(pSavedLoopControl->getCue() && - pSavedLoopControl->getCue()->getPosition() == - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pLoopStartPosition->get())); - DEBUG_ASSERT(pSavedLoopControl->getCue() && - pSavedLoopControl->getCue()->getEndPosition() == - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - m_pLoopEndPosition->get())); - - if (enabled) { - pSavedLoopControl->setStatus(HotcueControl::Status::Active); - } else { - pSavedLoopControl->setStatus(HotcueControl::Status::Set); - } -} - -void CueControl::slotLoopUpdated(mixxx::audio::FramePos startPosition, - mixxx::audio::FramePos endPosition) { - HotcueControl* pSavedLoopControl = m_pCurrentSavedLoopControl; - if (!pSavedLoopControl) { - return; - } - - if (pSavedLoopControl->getStatus() != HotcueControl::Status::Active) { - slotLoopReset(); - return; - } - - CuePointer pCue = pSavedLoopControl->getCue(); - if (!pCue) { - // this can happen if the cue is deleted while this slot is cued - return; - } - - VERIFY_OR_DEBUG_ASSERT(pCue->getType() == mixxx::CueType::Loop) { - setCurrentSavedLoopControlAndActivate(nullptr); - return; - } - - VERIFY_OR_DEBUG_ASSERT(startPosition.isValid() && endPosition.isValid() && - startPosition < endPosition) { - return; - } - - DEBUG_ASSERT(pSavedLoopControl->getStatus() == HotcueControl::Status::Active); - pCue->setStartPosition(startPosition); - pCue->setEndPosition(endPosition); - DEBUG_ASSERT(pSavedLoopControl->getStatus() == HotcueControl::Status::Active); -} - -void CueControl::setHotcueFocusIndex(int hotcueIndex) { - m_pHotcueFocus->set(hotcueIndexToHotcueNumber(hotcueIndex)); -} - -int CueControl::getHotcueFocusIndex() const { - return hotcueNumberToHotcueIndex(static_cast(m_pHotcueFocus->get())); -} - -ConfigKey HotcueControl::keyForControl(const QString& name) { - ConfigKey key; - key.group = m_group; - // Add one to hotcue so that we don't have a hotcue_0 - key.item = QStringLiteral("hotcue_") + - QString::number(hotcueIndexToHotcueNumber(m_hotcueIndex)) + - QChar('_') + name; - return key; -} - -HotcueControl::HotcueControl(const QString& group, int hotcueIndex) - : m_group(group), - m_hotcueIndex(hotcueIndex), - m_pCue(nullptr) { - m_hotcuePosition = std::make_unique(keyForControl(QStringLiteral("position"))); - connect(m_hotcuePosition.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcuePositionChanged, - Qt::DirectConnection); - m_hotcuePosition->set(Cue::kNoPosition); - - m_hotcueEndPosition = std::make_unique( - keyForControl(QStringLiteral("endposition"))); - connect(m_hotcueEndPosition.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueEndPositionChanged, - Qt::DirectConnection); - m_hotcueEndPosition->set(Cue::kNoPosition); - - m_pHotcueStatus = std::make_unique(keyForControl(QStringLiteral("status"))); - m_pHotcueStatus->setReadOnly(); - - // Add an alias for the legacy hotcue_X_enabled CO - m_pHotcueStatus->addAlias(keyForControl(QStringLiteral("enabled"))); - - m_hotcueType = std::make_unique(keyForControl(QStringLiteral("type"))); - m_hotcueType->setReadOnly(); - - // The rgba value of the color assigned to this color. - m_hotcueColor = std::make_unique(keyForControl(QStringLiteral("color"))); - m_hotcueColor->connectValueChangeRequest( - this, - &HotcueControl::slotHotcueColorChangeRequest, - Qt::DirectConnection); - - m_hotcueSet = std::make_unique(keyForControl(QStringLiteral("set"))); - connect(m_hotcueSet.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueSet, - Qt::DirectConnection); - - m_hotcueSetCue = std::make_unique(keyForControl(QStringLiteral("setcue"))); - connect(m_hotcueSetCue.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueSetCue, - Qt::DirectConnection); - - m_hotcueSetLoop = std::make_unique(keyForControl(QStringLiteral("setloop"))); - connect(m_hotcueSetLoop.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueSetLoop, - Qt::DirectConnection); - - m_hotcueGoto = std::make_unique(keyForControl(QStringLiteral("goto"))); - connect(m_hotcueGoto.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueGoto, - Qt::DirectConnection); - - m_hotcueGotoAndPlay = std::make_unique( - keyForControl(QStringLiteral("gotoandplay"))); - connect(m_hotcueGotoAndPlay.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueGotoAndPlay, - Qt::DirectConnection); - - m_hotcueGotoAndStop = std::make_unique( - keyForControl(QStringLiteral("gotoandstop"))); - connect(m_hotcueGotoAndStop.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueGotoAndStop, - Qt::DirectConnection); - - m_hotcueGotoAndLoop = std::make_unique( - keyForControl(QStringLiteral("gotoandloop"))); - connect(m_hotcueGotoAndLoop.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueGotoAndLoop, - Qt::DirectConnection); - - // Enable/disable the loop associated with this hotcue (either a saved loop - // or a beatloop from the hotcue position if this is a regular hotcue). - m_hotcueCueLoop = std::make_unique(keyForControl(QStringLiteral("cueloop"))); - connect(m_hotcueCueLoop.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueCueLoop, - Qt::DirectConnection); - - m_hotcueActivate = std::make_unique( - keyForControl(QStringLiteral("activate"))); - connect(m_hotcueActivate.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueActivate, - Qt::DirectConnection); - - m_hotcueActivateCue = std::make_unique( - keyForControl(QStringLiteral("activatecue"))); - connect(m_hotcueActivateCue.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueActivateCue, - Qt::DirectConnection); - - m_hotcueActivateLoop = std::make_unique( - keyForControl(QStringLiteral("activateloop"))); - connect(m_hotcueActivateLoop.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueActivateLoop, - Qt::DirectConnection); - - m_hotcueActivatePreview = std::make_unique( - keyForControl(QStringLiteral("activate_preview"))); - connect(m_hotcueActivatePreview.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueActivatePreview, - Qt::DirectConnection); - - m_hotcueClear = std::make_unique(keyForControl(QStringLiteral("clear"))); - connect(m_hotcueClear.get(), - &ControlObject::valueChanged, - this, - &HotcueControl::slotHotcueClear, - Qt::DirectConnection); - - m_previewingType.setValue(mixxx::CueType::Invalid); - m_previewingPosition.setValue(mixxx::audio::kInvalidFramePos); -} - -HotcueControl::~HotcueControl() = default; - -void HotcueControl::slotHotcueSet(double v) { - emit hotcueSet(this, v, HotcueSetMode::Auto); -} - -void HotcueControl::slotHotcueSetCue(double v) { - emit hotcueSet(this, v, HotcueSetMode::Cue); -} - -void HotcueControl::slotHotcueSetLoop(double v) { - emit hotcueSet(this, v, HotcueSetMode::Loop); -} - -void HotcueControl::slotHotcueGoto(double v) { - emit hotcueGoto(this, v); -} - -void HotcueControl::slotHotcueGotoAndPlay(double v) { - emit hotcueGotoAndPlay(this, v); -} - -void HotcueControl::slotHotcueGotoAndStop(double v) { - emit hotcueGotoAndStop(this, v); -} - -void HotcueControl::slotHotcueGotoAndLoop(double v) { - emit hotcueGotoAndLoop(this, v); -} - -void HotcueControl::slotHotcueCueLoop(double v) { - emit hotcueCueLoop(this, v); -} - -void HotcueControl::slotHotcueActivate(double v) { - emit hotcueActivate(this, v, HotcueSetMode::Auto); -} - -void HotcueControl::slotHotcueActivateCue(double v) { - emit hotcueActivate(this, v, HotcueSetMode::Cue); -} - -void HotcueControl::slotHotcueActivateLoop(double v) { - emit hotcueActivate(this, v, HotcueSetMode::Loop); -} - -void HotcueControl::slotHotcueActivatePreview(double v) { - emit hotcueActivatePreview(this, v); -} - -void HotcueControl::slotHotcueClear(double v) { - emit hotcueClear(this, v); -} - -void HotcueControl::slotHotcuePositionChanged(double newPosition) { - emit hotcuePositionChanged(this, newPosition); -} - -void HotcueControl::slotHotcueEndPositionChanged(double newEndPosition) { - emit hotcueEndPositionChanged(this, newEndPosition); -} - -void HotcueControl::slotHotcueColorChangeRequest(double newColor) { - if (newColor < 0 || newColor > 0xFFFFFF) { - qWarning() << "slotHotcueColorChangeRequest got invalid value:" << newColor; - return; - } - // qDebug() << "HotcueControl::slotHotcueColorChangeRequest" << newColor; - if (!m_pCue) { - return; - } - - mixxx::RgbColor::optional_t color = doubleToRgbColor(newColor); - VERIFY_OR_DEBUG_ASSERT(color) { - return; - } - - m_pCue->setColor(*color); - m_hotcueColor->setAndConfirm(newColor); -} - -mixxx::audio::FramePos HotcueControl::getPosition() const { - return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_hotcuePosition->get()); -} - -mixxx::audio::FramePos HotcueControl::getEndPosition() const { - return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(m_hotcueEndPosition->get()); -} - -void HotcueControl::setCue(const CuePointer& pCue) { - DEBUG_ASSERT(!m_pCue); - Cue::StartAndEndPositions pos = pCue->getStartAndEndPosition(); - setPosition(pos.startPosition); - setEndPosition(pos.endPosition); - // qDebug() << "HotcueControl::setCue"; - setColor(pCue->getColor()); - setStatus((pCue->getType() == mixxx::CueType::Invalid) - ? HotcueControl::Status::Empty - : HotcueControl::Status::Set); - setType(pCue->getType()); - // set pCue only if all other data is in place - // because we have a null check for valid data else where in the code - m_pCue = pCue; -} -mixxx::RgbColor::optional_t HotcueControl::getColor() const { - return doubleToRgbColor(m_hotcueColor->get()); -} - -void HotcueControl::setColor(mixxx::RgbColor::optional_t newColor) { - // qDebug() << "HotcueControl::setColor()" << newColor; - if (newColor) { - m_hotcueColor->setAndConfirm(*newColor); - } -} - -void HotcueControl::resetCue() { - // clear pCue first because we have a null check for valid data else where - // in the code - m_pCue.reset(); - setPosition(mixxx::audio::kInvalidFramePos); - setEndPosition(mixxx::audio::kInvalidFramePos); - setType(mixxx::CueType::Invalid); - setStatus(Status::Empty); -} - -void HotcueControl::setPosition(mixxx::audio::FramePos position) { - m_hotcuePosition->set(position.toEngineSamplePosMaybeInvalid()); -} - -void HotcueControl::setEndPosition(mixxx::audio::FramePos endPosition) { - m_hotcueEndPosition->set(endPosition.toEngineSamplePosMaybeInvalid()); -} - -void HotcueControl::setType(mixxx::CueType type) { - m_hotcueType->forceSet(static_cast(type)); -} - -void HotcueControl::setStatus(HotcueControl::Status status) { - m_pHotcueStatus->forceSet(static_cast(status)); -} - -HotcueControl::Status HotcueControl::getStatus() const { - // Cast to int before casting to the int-based enum class because MSVC will - // throw a hissy fit otherwise. - return static_cast(static_cast(m_pHotcueStatus->get())); -} diff --git a/src/library/dao/cuedao.cpp b/src/library/dao/cuedao.cpp deleted file mode 100644 index 5be8c94ef24..00000000000 --- a/src/library/dao/cuedao.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#include "library/dao/cuedao.h" - -#include -#include -#include - -#include "engine/engine.h" -#include "library/queryutil.h" -#include "util/assert.h" -#include "util/color/rgbcolor.h" -#include "util/db/fwdsqlquery.h" -#include "util/logger.h" - -namespace { - -const mixxx::Logger kLogger = mixxx::Logger("CueDAO"); - -/// Wrap a `QString` label in a `QVariant`. The label column is not nullable, -/// so this function also makes sure that the label an empty string, not null. -inline const QVariant labelToQVariant(const QString& label) { - if (label.isNull()) { - return QLatin1String(""); // null -> empty - } else { - return label; - } -} - -/// Empty labels are read as null strings -inline QString labelFromQVariant(const QVariant& value) { - const auto label = value.toString(); - if (label.isEmpty()) { - return QString(); // empty -> null - } else { - return label; - } -} - -CuePointer cueFromRow(const QSqlRecord& row) { - const auto id = DbId(row.value(row.indexOf("id"))); - TrackId trackId(row.value(row.indexOf("track_id"))); - auto type = static_cast(row.value(row.indexOf("type")).toInt()); - const auto position = - mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( - row.value(row.indexOf("position")).toDouble()); - double lengthFrames = row.value(row.indexOf("length")).toDouble() / - mixxx::kEngineChannelOutputCount; - int hotcue = row.value(row.indexOf("hotcue")).toInt(); - QString label = labelFromQVariant(row.value(row.indexOf("label"))); - mixxx::RgbColor::optional_t color = mixxx::RgbColor::fromQVariant(row.value(row.indexOf("color"))); - VERIFY_OR_DEBUG_ASSERT(color) { - return CuePointer(); - } - if (type == mixxx::CueType::Loop && lengthFrames == 0.0) { - // These entries are likely added via issue #11283 - qWarning() << "Discard loop cue" << hotcue << "found in database with length of 0"; - return CuePointer(); - } - if (type == mixxx::CueType::HotCue && - position == mixxx::audio::FramePos(0) && - *color == mixxx::RgbColor(0)) { - // These entries are likely added via issue #11283 - qWarning() << "Discard black hot cue" << hotcue << "found in database at position 0"; - return CuePointer(); - } - CuePointer pCue(new Cue(id, - type, - position, - lengthFrames, - hotcue, - label, - *color)); - return pCue; -} - -} // namespace - -QList CueDAO::getCuesForTrack(TrackId trackId) const { - //qDebug() << "CueDAO::getCuesForTrack" << QThread::currentThread() << m_database.connectionName(); - QList cues; - - FwdSqlQuery query( - m_database, - QStringLiteral("SELECT * FROM " CUE_TABLE " WHERE track_id=:id")); - DEBUG_ASSERT( - query.isPrepared() && - !query.hasError()); - query.bindValue(":id", trackId); - if (!query.execPrepared()) { - kLogger.warning() - << "Failed to load cues of track" - << trackId; - DEBUG_ASSERT(!"failed query"); - return cues; - } - QMap hotCuesByNumber; - while (query.next()) { - CuePointer pCue = cueFromRow(query.record()); - if (!pCue) { - continue; - } - int hotCueNumber = pCue->getHotCue(); - if (hotCueNumber != Cue::kNoHotCue) { - const auto pDuplicateCue = hotCuesByNumber.take(hotCueNumber); - if (pDuplicateCue) { - kLogger.warning() - << "Dropping hot cue" - << pDuplicateCue->getId() - << "with duplicate number" - << hotCueNumber; - cues.removeOne(pDuplicateCue); - } - hotCuesByNumber.insert(hotCueNumber, pCue); - } - cues.push_back(pCue); - } - return cues; -} - -bool CueDAO::deleteCuesForTrack(TrackId trackId) const { - qDebug() << "CueDAO::deleteCuesForTrack" << QThread::currentThread() << m_database.connectionName(); - QSqlQuery query(m_database); - query.prepare(QStringLiteral("DELETE FROM " CUE_TABLE " WHERE track_id=:track_id")); - query.bindValue(":track_id", trackId.toVariant()); - if (query.exec()) { - return true; - } else { - LOG_FAILED_QUERY(query); - } - return false; -} - -bool CueDAO::deleteCuesForTracks(const QList& trackIds) const { - qDebug() << "CueDAO::deleteCuesForTracks" << QThread::currentThread() << m_database.connectionName(); - - QStringList idList; - for (const auto& trackId: trackIds) { - idList << trackId.toString(); - } - - QSqlQuery query(m_database); - query.prepare(QStringLiteral("DELETE FROM " CUE_TABLE " WHERE track_id in (%1)") - .arg(idList.join(","))); - if (query.exec()) { - return true; - } else { - LOG_FAILED_QUERY(query); - } - return false; -} - -bool CueDAO::saveCue(TrackId trackId, Cue* cue) const { - //qDebug() << "CueDAO::saveCue" << QThread::currentThread() << m_database.connectionName(); - VERIFY_OR_DEBUG_ASSERT(cue) { - return false; - } - - // Prepare query - QSqlQuery query(m_database); - if (cue->getId().isValid()) { - // Update cue - query.prepare(QStringLiteral("UPDATE " CUE_TABLE " SET " - "track_id=:track_id," - "type=:type," - "position=:position," - "length=:length," - "hotcue=:hotcue," - "label=:label," - "color=:color" - " WHERE id=:id")); - query.bindValue(":id", cue->getId().toVariant()); - } else { - // New cue - query.prepare( - QStringLiteral("INSERT INTO " CUE_TABLE - " (track_id, type, position, length, hotcue, " - "label, color) VALUES (:track_id, :type, " - ":position, :length, :hotcue, :label, :color)")); - } - - // Bind values and execute query - query.bindValue(":track_id", trackId.toVariant()); - query.bindValue(":type", static_cast(cue->getType())); - query.bindValue(":position", cue->getPosition().toEngineSamplePosMaybeInvalid()); - query.bindValue(":length", cue->getLengthFrames() * mixxx::kEngineChannelOutputCount); - query.bindValue(":hotcue", cue->getHotCue()); - query.bindValue(":label", labelToQVariant(cue->getLabel())); - query.bindValue(":color", mixxx::RgbColor::toQVariant(cue->getColor())); - if (!query.exec()) { - LOG_FAILED_QUERY(query); - return false; - } - - if (!cue->getId().isValid()) { - // New cue - const auto newId = DbId(query.lastInsertId()); - DEBUG_ASSERT(newId.isValid()); - cue->setId(newId); - } - DEBUG_ASSERT(cue->getId().isValid()); - cue->setDirty(false); - return true; -} - -void CueDAO::saveTrackCues( - TrackId trackId, - const QList& cueList) const { - DEBUG_ASSERT(trackId.isValid()); - QStringList cueIds; - cueIds.reserve(cueList.size()); - for (const auto& pCue : cueList) { - // New cues (without an id) must always be marked as dirty - DEBUG_ASSERT(pCue->getId().isValid() || pCue->isDirty()); - // Update or save cue - if (pCue->isDirty()) { - saveCue(trackId, pCue.get()); - } - // After saving each cue must have a valid id - VERIFY_OR_DEBUG_ASSERT(pCue->getId().isValid()) { - continue; - } - cueIds.append(pCue->getId().toString()); - } - - // Delete orphaned cues - FwdSqlQuery query( - m_database, - QStringLiteral("DELETE FROM " CUE_TABLE " WHERE track_id=:track_id AND id NOT IN (%1)") - .arg(cueIds.join(QChar(',')))); - DEBUG_ASSERT( - query.isPrepared() && - !query.hasError()); - query.bindValue(":track_id", trackId); - if (!query.execPrepared()) { - kLogger.warning() - << "Failed to delete orphaned cues of track" - << trackId; - DEBUG_ASSERT(!"failed query"); - return; - } - if (query.numRowsAffected() > 0) { - kLogger.debug() - << "Deleted" - << query.numRowsAffected() - << "orphaned cue(s) of track" - << trackId; - } -} diff --git a/src/library/library.cpp b/src/library/library.cpp index 91a5acf8f5a..eac4cb9d535 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -117,7 +117,6 @@ Library::Library( &Library::exportCrate, // signal-to-signal Qt::DirectConnection); #endif - m_pBrowseFeature = new BrowseFeature( this, m_pConfig, pRecordingManager); connect(m_pBrowseFeature, diff --git a/src/library/trackset/crate/cratefeature.cpp b/src/library/trackset/crate/cratefeature.cpp index 4de07c8b02d..2fe68cde8a9 100644 --- a/src/library/trackset/crate/cratefeature.cpp +++ b/src/library/trackset/crate/cratefeature.cpp @@ -27,8 +27,12 @@ #include "widget/wlibrarysidebar.h" #include "widget/wlibrarytextbrowser.h" +const bool sDebug = false; + namespace { +// constexpr int kInvalidCrateId = -1; + QString formatLabel( const CrateSummary& crateSummary) { return QStringLiteral("%1 (%2) %3") @@ -47,10 +51,10 @@ using namespace mixxx::library::prefs; CrateFeature::CrateFeature(Library* pLibrary, UserSettingsPointer pConfig) - : BaseTrackSetFeature(pLibrary, pConfig, "CRATEHOME", QStringLiteral("crates")), + : BaseTrackSetFeature(pLibrary, pConfig, "CRATESHOME", QStringLiteral("crates")), m_lockedCrateIcon(":/images/library/ic_library_locked_tracklist.svg"), m_pTrackCollection(pLibrary->trackCollectionManager()->internalCollection()), - m_crateTableModel(this, pLibrary->trackCollectionManager()) { + m_crateTableModel(this, pLibrary->trackCollectionManager(), pConfig) { initActions(); // construct child model @@ -205,9 +209,9 @@ QString CrateFeature::formatRootViewHtml() const { html.append(QStringLiteral("

%1

").arg(cratesSummary)); html.append(QStringLiteral("

%1

").arg(cratesSummary2)); html.append(QStringLiteral("

%1

").arg(cratesSummary3)); - //Colorize links in lighter blue, instead of QT default dark blue. - //Links are still different from regular text, but readable on dark/light backgrounds. - //https://github.com/mixxxdj/mixxx/issues/9103 + // Colorize links in lighter blue, instead of QT default dark blue. + // Links are still different from regular text, but readable on dark/light backgrounds. + // https://github.com/mixxxdj/mixxx/issues/9103 html.append( QStringLiteral("%1") .arg(createCrateLink)); @@ -232,7 +236,7 @@ void CrateFeature::updateTreeItemForCrateSummary( DEBUG_ASSERT(CrateId(pTreeItem->getData()) == crateSummary.getId()); } // Update mutable properties - pTreeItem->setLabel(formatLabel(crateSummary)); + // pTreeItem->setLabel(formatLabel(crateSummary)); pTreeItem->setIcon(crateSummary.isLocked() ? m_lockedCrateIcon : QIcon()); } @@ -298,7 +302,7 @@ void CrateFeature::activate() { BaseTrackSetFeature::activate(); } -void CrateFeature::activateChild(const QModelIndex& index) { +void CrateFeature::oldactivateChild(const QModelIndex& index) { qDebug() << " CrateFeature::activateChild()" << index; CrateId crateId(crateIdFromIndex(index)); VERIFY_OR_DEBUG_ASSERT(crateId.isValid()) { @@ -308,6 +312,7 @@ void CrateFeature::activateChild(const QModelIndex& index) { m_lastRightClickedIndex = QModelIndex(); m_prevSiblingCrate = CrateId(); emit saveModelState(); + // QList groupedCrates = m_crateTableModel.getGroupedCrates(); m_crateTableModel.selectCrate(crateId); emit showTrackModel(&m_crateTableModel); emit enableCoverArtDisplay(true); @@ -315,6 +320,7 @@ void CrateFeature::activateChild(const QModelIndex& index) { bool CrateFeature::activateCrate(CrateId crateId) { qDebug() << "CrateFeature::activateCrate()" << crateId; + qDebug() << "EVE EVE EVE EVE EVE CrateFeature::activateCrate()" << crateId; VERIFY_OR_DEBUG_ASSERT(crateId.isValid()) { return false; } @@ -372,7 +378,7 @@ void CrateFeature::onRightClick(const QPoint& globalPos) { void CrateFeature::onRightClickChild( const QPoint& globalPos, const QModelIndex& index) { - //Save the model index so we can get it in the action slots... + // Save the model index so we can get it in the action slots... m_lastRightClickedIndex = index; CrateId crateId(crateIdFromIndex(index)); if (!crateId.isValid()) { @@ -546,7 +552,7 @@ void CrateFeature::slotAutoDjTrackSourceChanged() { } } -QModelIndex CrateFeature::rebuildChildModel(CrateId selectedCrateId) { +QModelIndex CrateFeature::oldrebuildChildModel(CrateId selectedCrateId) { qDebug() << "CrateFeature::rebuildChildModel()" << selectedCrateId; m_lastRightClickedIndex = QModelIndex(); @@ -585,7 +591,7 @@ QModelIndex CrateFeature::rebuildChildModel(CrateId selectedCrateId) { } } -void CrateFeature::updateChildModel(const QSet& updatedCrateIds) { +void CrateFeature::oldupdateChildModel(const QSet& updatedCrateIds) { const CrateStorage& crateStorage = m_pTrackCollection->crates(); for (const CrateId& crateId : updatedCrateIds) { QModelIndex index = indexFromCrateId(crateId); @@ -638,7 +644,7 @@ QModelIndex CrateFeature::indexFromCrateId(CrateId crateId) const { } void CrateFeature::slotImportPlaylist() { - //qDebug() << "slotImportPlaylist() row:" ; //<< m_lastRightClickedIndex.data(); + // qDebug() << "slotImportPlaylist() row:" ; //<< m_lastRightClickedIndex.data(); QString playlistFile = getPlaylistFile(); if (playlistFile.isEmpty()) { @@ -685,7 +691,8 @@ void CrateFeature::slotImportPlaylistFile(const QString& playlistFile, CrateId c // Create a temporary table model since the main one might have another // crate selected which is not the crate that received the right-click. std::unique_ptr pCrateTableModel = - std::make_unique(this, m_pLibrary->trackCollectionManager()); + std::make_unique( + this, m_pLibrary->trackCollectionManager(), m_pConfig); pCrateTableModel->selectCrate(crateId); pCrateTableModel->select(); pCrateTableModel->addTracks(QModelIndex(), locations); @@ -810,7 +817,8 @@ void CrateFeature::slotExportPlaylist() { // Create list of files of the crate // Create a new table model since the main one might have an active search. std::unique_ptr pCrateTableModel = - std::make_unique(this, m_pLibrary->trackCollectionManager()); + std::make_unique( + this, m_pLibrary->trackCollectionManager(), m_pConfig); pCrateTableModel->selectCrate(crateId); pCrateTableModel->select(); @@ -840,7 +848,8 @@ void CrateFeature::slotExportTrackFiles() { } // Create a new table model since the main one might have an active search. std::unique_ptr pCrateTableModel = - std::make_unique(this, m_pLibrary->trackCollectionManager()); + std::make_unique( + this, m_pLibrary->trackCollectionManager(), m_pConfig); pCrateTableModel->selectCrate(crateId); pCrateTableModel->select(); @@ -880,6 +889,7 @@ void CrateFeature::storePrevSiblingCrateId(CrateId crateId) { void CrateFeature::slotCrateTableChanged(CrateId crateId) { Q_UNUSED(crateId); + // QList groupedCrates = m_crateTableModel.getGroupedCrates(); if (isChildIndexSelectedInSidebar(m_lastClickedIndex)) { // If the previously selected crate was loaded to the tracks table and // selected in the sidebar try to activate that or a sibling @@ -932,17 +942,39 @@ void CrateFeature::slotTrackSelected(TrackId trackId) { } } - // Set all crates the track is in bold (or if there is no track selected, - // clear all the bolding). + // Recursive lambda to set bold for crates and groups + auto setBoldForItemsRecursive = + [&](TreeItem* pItem, + const auto& setBoldForItemsRecursiveRef) -> bool { + if (!pItem) { + return false; + } + + bool isBold = false; + + // Check if the current item is a crate and contains the selected track + if (!pItem->children().isEmpty()) { + // Recursively check child items + for (TreeItem* pChild : pItem->children()) { + isBold |= setBoldForItemsRecursiveRef(pChild, setBoldForItemsRecursiveRef); + } + } else { + // Check crates directly + isBold = m_selectedTrackId.isValid() && + std::binary_search( + sortedTrackCrates.begin(), + sortedTrackCrates.end(), + CrateId(pItem->getData())); + } + + // Set bold status for the current item + pItem->setBold(isBold); + return isBold; + }; + + // Start the recursion from the root for (TreeItem* pTreeItem : pRootItem->children()) { - DEBUG_ASSERT(pTreeItem != nullptr); - bool crateContainsSelectedTrack = - m_selectedTrackId.isValid() && - std::binary_search( - sortedTrackCrates.begin(), - sortedTrackCrates.end(), - CrateId(pTreeItem->getData())); - pTreeItem->setBold(crateContainsSelectedTrack); + setBoldForItemsRecursive(pTreeItem, setBoldForItemsRecursive); } m_pSidebarModel->triggerRepaint(); @@ -951,3 +983,491 @@ void CrateFeature::slotTrackSelected(TrackId trackId) { void CrateFeature::slotResetSelectedTrack() { slotTrackSelected(TrackId{}); } + +QModelIndex CrateFeature::rebuildChildModel(CrateId selectedCrateId) { + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] -> rebuildChildModel()" << selectedCrateId; + } + + QModelIndex previouslySelectedIndex = m_lastRightClickedIndex; + + // remember open/close state of group + QMap groupExpandedStates; + for (int row = 0; row < m_pSidebarModel->rowCount(); ++row) { + QModelIndex groupIndex = m_pSidebarModel->index(row, 0); + if (groupIndex.isValid()) { + TreeItem* pGroupItem = m_pSidebarModel->getItem(groupIndex); + if (pGroupItem) { + const QString& groupName = pGroupItem->getLabel(); + groupExpandedStates[groupName] = m_pSidebarWidget->isExpanded(groupIndex); + if (sDebug) { + qDebug() << "[CrateFeature] Saved open/close state " + "for group:" + << groupName << "->" + << groupExpandedStates[groupName]; + } + } + } + } + + m_lastRightClickedIndex = QModelIndex(); + TreeItem* pRootItem = m_pSidebarModel->getRootItem(); + VERIFY_OR_DEBUG_ASSERT(pRootItem != nullptr) { + return QModelIndex(); + } + m_pSidebarModel->removeRows(0, pRootItem->childRows()); + + QList groupedCrates = m_crateTableModel.getGroupedCrates(); + const QString& delimiter = m_pConfig->getValue( + ConfigKey("[Library]", "GroupedCratesVarLengthMask")); + + if (m_pConfig->getValue(ConfigKey("[Library]", "GroupedCratesLength")) == 0) { + // Fixed prefix length + QMap groupCounts; + for (const QVariantMap& crateData : groupedCrates) { + const QString& groupName = crateData["group_name"].toString(); + groupCounts[groupName]++; + } + + QMap groupItems; + std::vector> modelRows; + // int selectedRow = -1; + + for (const QVariantMap& crateData : groupedCrates) { + const QString& groupName = crateData["group_name"].toString(); + CrateId crateId(crateData["crate_id"]); + // const QString& crateName = crateData["crate_name"].toString(); + + CrateSummary crateSummary; + if (!m_pTrackCollection->crates().readCrateSummaryById(crateId, &crateSummary)) { + qWarning() << "[GROUPEDCRATESFEATURE] -> Failed to fetch summary " + "for crate ID:" + << crateId; + continue; + } + + const QString& crateSummaryName = formatLabel(crateSummary); + + if (groupCounts[groupName] > 1) { + TreeItem* pGroupItem = groupItems.value(groupName, nullptr); + if (!pGroupItem) { + auto newGroup = std::make_unique(groupName, kInvalidCrateId); + pGroupItem = newGroup.get(); + groupItems.insert(groupName, pGroupItem); + modelRows.push_back(std::move(newGroup)); + } + + const QString& displayCrateName = + crateSummaryName.mid(groupName.length()).trimmed(); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] -> crateSummaryName - " + "displayCrateName = " + << crateSummaryName << " - " << displayCrateName; + } + + TreeItem* pChildItem = pGroupItem->appendChild( + displayCrateName, crateId.toVariant().toInt()); + pChildItem->setFullPath(groupName + delimiter + displayCrateName); + updateTreeItemForCrateSummary(pChildItem, crateSummary); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Added CrateId to group:" + << crateId << "Group:" << groupName; + } + // if (selectedCrateId == crateId) { + // selectedRow = static_cast(modelRows.size()) - 1; + // } + } else { + auto newCrate = std::make_unique( + crateSummaryName, crateId.toVariant().toInt()); + newCrate->setFullPath(crateSummaryName); + updateTreeItemForCrateSummary(newCrate.get(), crateSummary); + + // if (selectedCrateId == crateId) { + // selectedRow = static_cast(modelRows.size()); + //} + modelRows.push_back(std::move(newCrate)); + } + } + + m_pSidebarModel->insertTreeItemRows(std::move(modelRows), 0); + slotTrackSelected(m_selectedTrackId); + + } else { + // variable group prefix length with mask + QMap> topLevelGroups; + for (const QVariantMap& crateData : groupedCrates) { + const QString& groupName = crateData["group_name"].toString(); + const QString& topGroup = groupName.section(delimiter, 0, 0); + topLevelGroups[topGroup].append(crateData); + } + + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Top-level groups:"; + for (auto it = topLevelGroups.constBegin(); it != topLevelGroups.constEnd(); ++it) { + qDebug() << "Group:" << it.key() << "-> Crates:" << it.value().size(); + } + } + // lambda function to build tree + std::function&, TreeItem*)> + buildTreeStructure; + buildTreeStructure = [&](const QString& currentPath, + const QList& crates, + TreeItem* pParentItem) { + QMap> subgroupedCrates; + + for (const QVariantMap& crateData : crates) { + const QString& groupName = crateData["group_name"].toString(); + + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Processing crate with " + "groupName:" + << groupName << "currentPath:" << currentPath; + } + + if (!groupName.startsWith(currentPath)) { + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Skipping crate. " + "Group name does not match path:" + << groupName << "Current path:" << currentPath; + } + continue; + } + + const QString& remainingPath = groupName.mid(currentPath.length()); + int delimiterPos = remainingPath.indexOf(delimiter); + + if (delimiterPos >= 0) { + const QString& subgroupName = remainingPath.left(delimiterPos); + subgroupedCrates[subgroupName].append(crateData); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Added crate to " + "subgroup:" + << subgroupName + << "Remaining path:" << remainingPath; + } + } else { + CrateId crateId(crateData["crate_id"]); + CrateSummary crateSummary; + if (!m_pTrackCollection->crates().readCrateSummaryById( + crateId, &crateSummary)) { + qWarning() << "[GROUPEDCRATESFEATURE] Failed to fetch " + "summary for crate ID:" + << crateId; + continue; + } + + const QString& displayCrateName = + formatLabel(crateSummary).mid(currentPath.length()); + + TreeItem* pChildItem = pParentItem->appendChild( + displayCrateName.trimmed(), crateId.toVariant()); + pChildItem->setFullPath(currentPath + delimiter + displayCrateName); + updateTreeItemForCrateSummary(pChildItem, crateSummary); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Added crate to " + "parent:" + << displayCrateName + << "Parent:" << pParentItem->getLabel(); + } + } + } + + for (auto it = subgroupedCrates.constBegin(); it != subgroupedCrates.constEnd(); ++it) { + const QString& subgroupName = it.key(); + const QList& subgroupCrates = it.value(); + if (!subgroupCrates.isEmpty()) { + if (subgroupCrates.size() > 1) { + // subgroup has > 1 crate -> create subgroup + auto pNewSubgroup = std::make_unique( + subgroupName, kInvalidCrateId); + TreeItem* pSubgroupItem = pNewSubgroup.get(); + pParentItem->insertChild(pParentItem->childCount(), + std::move(pNewSubgroup)); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Created subgroup:" << subgroupName + << "Parent:" << pParentItem->getLabel(); + } + + // loop into the subgroup + buildTreeStructure( + currentPath + subgroupName + delimiter, + subgroupCrates, + pSubgroupItem); + } else { + // only one crate -> directly under the parent, NO subgroup + const QVariantMap& crateData = subgroupCrates.first(); + CrateId crateId(crateData["crate_id"]); + CrateSummary crateSummary; + if (!m_pTrackCollection->crates().readCrateSummaryById( + crateId, &crateSummary)) { + qWarning() << "[GROUPEDCRATESFEATURE] Failed to " + "fetch summary for crate ID:" + << crateId; + continue; + } + + const QString& displayCrateName = + formatLabel(crateSummary) + .mid(currentPath.length()); + + TreeItem* pChildItem = pParentItem->appendChild( + displayCrateName.trimmed(), crateId.toVariant()); + pChildItem->setFullPath(currentPath + delimiter + displayCrateName); + updateTreeItemForCrateSummary(pChildItem, crateSummary); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Added single crate to parent:" + << displayCrateName + << "Parent:" << pParentItem->getLabel(); + } + } + } + } + }; + // building rootlevel groups + for (auto it = topLevelGroups.constBegin(); it != topLevelGroups.constEnd(); ++it) { + const QString& groupName = it.key(); + const QList& crates = it.value(); + + if (crates.size() > 1) { + auto pNewGroup = std::make_unique(groupName, kInvalidCrateId); + TreeItem* pGroupItem = pNewGroup.get(); + pRootItem->insertChild(pRootItem->childCount(), std::move(pNewGroup)); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Created top-level group:" << groupName; + } + + buildTreeStructure(groupName + delimiter, crates, pGroupItem); + } else { + const QVariantMap& crateData = crates.first(); + CrateId crateId(crateData["crate_id"]); + CrateSummary crateSummary; + + if (!m_pTrackCollection->crates().readCrateSummaryById(crateId, &crateSummary)) { + qWarning() << "[GROUPEDCRATESFEATURE] Failed to fetch " + "summary for crate ID:" + << crateId; + continue; + } + + const QString& displayCrateName = formatLabel(crateSummary); + TreeItem* pChildItem = pRootItem->appendChild( + displayCrateName.trimmed(), crateId.toVariant()); + updateTreeItemForCrateSummary(pChildItem, crateSummary); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Added crate to root:" << displayCrateName; + } + } + } + } + // store open/close state of groups + for (int row = 0; row < m_pSidebarModel->rowCount(); ++row) { + QModelIndex groupIndex = m_pSidebarModel->index(row, 0); + if (groupIndex.isValid()) { + TreeItem* pGroupItem = m_pSidebarModel->getItem(groupIndex); + if (pGroupItem) { + const QString& groupName = pGroupItem->getLabel(); + if (groupExpandedStates.contains(groupName)) { + m_pSidebarWidget->setExpanded(groupIndex, groupExpandedStates[groupName]); + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] Restored expanded " + "state for group:" + << groupName << "->" + << groupExpandedStates[groupName]; + } + } + } + } + } + + if (previouslySelectedIndex.isValid()) { + return previouslySelectedIndex; + } + + return QModelIndex(); +} + +void CrateFeature::updateChildModel(const QSet& updatedCrateIds) { + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] -> updateChildModel() -> Updating " + "crates" + << updatedCrateIds; + } + + QModelIndex previouslySelectedIndex = m_lastRightClickedIndex; + + // Fetch grouped crates using CrateTableModel + QList groupedCrates = m_crateTableModel.getGroupedCrates(); + + QMap> groupedCratesMap; + for (const QVariantMap& crateData : groupedCrates) { + groupedCratesMap[crateData["group_name"].toString()].append(crateData); + } + + // const QString& delimiter = m_pConfig->getValue(ConfigKey("[Library]", + // "GroupedCratesVarLengthMask")); + + // Update full paths recursively for all items starting from the root + updateFullPathRecursive(m_pSidebarModel->getRootItem(), QString()); + + // Update or rebuild items + for (const CrateId& crateId : updatedCrateIds) { + // Find the updated crate in groupedCrates + auto updatedGroup = std::find_if( + groupedCrates.begin(), + groupedCrates.end(), + [&crateId](const QVariantMap& crateData) { + return crateData["crate_id"].toInt() == crateId.toVariant().toInt(); + }); + + if (updatedGroup != groupedCrates.end()) { + // const QString& groupName = (*updatedGroup)["group_name"].toString(); + QModelIndex index = indexFromCrateId(crateId); + if (index.isValid()) { + // Update the existing item + TreeItem* pItem = m_pSidebarModel->getItem(index); + VERIFY_OR_DEBUG_ASSERT(pItem != nullptr) { + continue; + } + pItem->setData((*updatedGroup)["crate_name"].toString()); + updateTreeItemForCrateSummary(pItem, CrateSummary(crateId)); + + // Update fullPath for the entire tree under this item + updateFullPathRecursive(m_pSidebarModel->getRootItem(), QString()); + + m_pSidebarModel->triggerRepaint(index); + } else { + // Rebuild the group if the crate is missing + rebuildChildModel(crateId); + } + } + } + if (previouslySelectedIndex.isValid()) { + m_lastRightClickedIndex = previouslySelectedIndex; + } +} + +void CrateFeature::activateChild(const QModelIndex& index) { + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] -> activateChild() -> index" << index; + } + + CrateId crateId(crateIdFromIndex(index)); + + if (crateId.toString() == "-1") { + // Group activated + if (sDebug) { + qDebug() << "[CrateFeature] -> activateChild() -> Group activated"; + } + const QString& fullPath = fullPathFromIndex(index); + if (fullPath.isEmpty()) { + qWarning() << "[GROUPEDCRATESFEATURE] -> activateChild() -> Group " + "activated: No valid full path for index: " + << index; + return; + } + if (sDebug) { + qDebug() << "[CrateFeature] -> activateChild() -> Group " + "activated -> fullPath:" + << fullPath; + } + + m_lastClickedIndex = index; + m_lastRightClickedIndex = QModelIndex(); + m_prevSiblingCrate = CrateId(); + emit saveModelState(); + emit disableSearch(); + emit enableCoverArtDisplay(false); + + m_crateTableModel.selectCrateGroup(fullPath); + emit featureSelect(this, m_lastClickedIndex); + emit showTrackModel(&m_crateTableModel); + } else { + // Crate activated + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] -> activateChild() -> Child " + "crate activated -> crateId: " + << crateId; + } + VERIFY_OR_DEBUG_ASSERT(crateId.isValid()) { + return; + } + m_lastClickedIndex = index; + m_lastRightClickedIndex = QModelIndex(); + m_prevSiblingCrate = CrateId(); + emit saveModelState(); + m_crateTableModel.selectCrate(crateId); + emit showTrackModel(&m_crateTableModel); + emit enableCoverArtDisplay(true); + } +} + +QString CrateFeature::fullPathFromIndex(const QModelIndex& index) const { + const QString& delimiter = m_pConfig->getValue( + ConfigKey("[Library]", "GroupedCratesVarLengthMask")); + if (!index.isValid()) { + return QString(); + } + + TreeItem* pItem = m_pSidebarModel->getItem(index); + if (!pItem) { + return QString(); + } + + QString fullPath; + TreeItem* currentItem = pItem; + while (currentItem) { + if (!fullPath.isEmpty()) { + // Prepend delimiter + fullPath.prepend(delimiter); + } + // Prepend current item's label + fullPath.prepend(currentItem->getLabel()); + currentItem = currentItem->parent(); + } + + // remove the last prepended delimiter (we don't know the depth of the tree) + // if another root level crate exists that is not in the group + // (beginning of the name equal to root level groop name), + // the member tracks would be added to the group, + // with added delimiter only tracks in group member crates are added + fullPath = fullPath.mid(delimiter.length()).append(delimiter); + return fullPath; +} + +QString CrateFeature::groupNameFromIndex(const QModelIndex& index) const { + if (!index.isValid()) { + // If index Invalid -> return an empty string + return QString(); + } + + TreeItem* pItem = m_pSidebarModel->getItem(index); + if (!pItem) { + // ig no item found for this index -> return an empty string + return QString(); + } + // if index & label found -> return label + return pItem->getLabel(); +} + +void CrateFeature::updateFullPathRecursive(TreeItem* pItem, const QString& parentPath) { + const QString& delimiter = m_pConfig->getValue( + ConfigKey("[Library]", "GroupedCratesVarLengthMask")); + if (!pItem) { + return; + } + + QString currentFullPath = parentPath.isEmpty() + ? pItem->getLabel() + : parentPath + delimiter + pItem->getLabel(); + pItem->setFullPath(currentFullPath); + + if (sDebug) { + qDebug() << "[GROUPEDCRATESFEATURE] -> Updated full path for item: " << pItem->getLabel() + << " FullPath: " << currentFullPath; + } + + for (TreeItem* pChild : pItem->children()) { + updateFullPathRecursive(pChild, currentFullPath); + } +} diff --git a/src/library/trackset/crate/cratefeature.h b/src/library/trackset/crate/cratefeature.h index 0289a6e2060..c54eddd8688 100644 --- a/src/library/trackset/crate/cratefeature.h +++ b/src/library/trackset/crate/cratefeature.h @@ -40,10 +40,14 @@ class CrateFeature : public BaseTrackSetFeature { void bindSidebarWidget(WLibrarySidebar* pSidebarWidget) override; TreeItemModel* sidebarModel() const override; + QString fullPathFromIndex(const QModelIndex& index) const; + void reviewGroupCrateIds(const QString& fullPath, const QList& crateIds); public slots: void activate() override; void activateChild(const QModelIndex& index) override; + void oldactivateChild(const QModelIndex& index); + // void oldactivateChild(const QModelIndex& index) override; void onRightClick(const QPoint& globalPos) override; void onRightClickChild(const QPoint& globalPos, const QModelIndex& index) override; void slotCreateCrate(); @@ -77,6 +81,7 @@ class CrateFeature : public BaseTrackSetFeature { void slotUpdateCrateLabels(const QSet& updatedCrateIds); private: + QMap> groupCrateIds; void initActions(); void connectLibrary(Library* pLibrary); void connectTrackCollection(); @@ -90,7 +95,9 @@ class CrateFeature : public BaseTrackSetFeature { const CrateSummary& crateSummary) const; QModelIndex rebuildChildModel(CrateId selectedCrateId = CrateId()); + QModelIndex oldrebuildChildModel(CrateId selectedCrateId = CrateId()); void updateChildModel(const QSet& updatedCrateIds); + void oldupdateChildModel(const QSet& updatedCrateIds); CrateId crateIdFromIndex(const QModelIndex& index) const; QModelIndex indexFromCrateId(CrateId crateId) const; @@ -132,4 +139,6 @@ class CrateFeature : public BaseTrackSetFeature { parented_ptr m_pAnalyzeCrateAction; QPointer m_pSidebarWidget; + QString groupNameFromIndex(const QModelIndex& index) const; + void updateFullPathRecursive(TreeItem* pItem, const QString& parentPath); }; diff --git a/src/library/trackset/crate/cratetablemodel.cpp b/src/library/trackset/crate/cratetablemodel.cpp index 076c5448816..b5ff9a68b0b 100644 --- a/src/library/trackset/crate/cratetablemodel.cpp +++ b/src/library/trackset/crate/cratetablemodel.cpp @@ -12,21 +12,153 @@ namespace { +const bool sDebug = false; const QString kModelName = QStringLiteral("crate"); } // anonymous namespace CrateTableModel::CrateTableModel( QObject* pParent, - TrackCollectionManager* pTrackCollectionManager) + TrackCollectionManager* pTrackCollectionManager, + UserSettingsPointer pConfig) : TrackSetTableModel( pParent, pTrackCollectionManager, - "mixxx.db.model.crate") { + "mixxx.db.model.crate"), + m_pConfig(pConfig) { +} + +QList CrateTableModel::getGroupedCrates() { + if (sDebug) { + qDebug() << "[GROUPEDCRATESTABLEMODEL] Generating grouped crates list."; + } + + QList groupedCrates; + + QSqlQuery query(m_database); + // QString queryString; + + if (sDebug) { + qDebug() << "[GROUPEDCRATESTABLEMODEL] configvalue GroupedCratesLength = " + << m_pConfig->getValue(ConfigKey("[Library]", "GroupedCratesLength")); + qDebug() << "[GROUPEDCRATESTABLEMODEL] configvalue GroupedCratesFixedLength = " + << m_pConfig->getValue(ConfigKey("[Library]", "GroupedCratesFixedLength"), 0); + qDebug() << "[GROUPEDCRATESTABLEMODEL] configvalue GroupedCratesVarLengthMask = " + << m_pConfig->getValue(ConfigKey("[Library]", "GroupedCratesVarLengthMask")); + } + + // fixed prefix length + if (m_pConfig->getValue(ConfigKey("[Library]", "GroupedCratesLength")) == 0) { + QString queryString = + QStringLiteral( + "SELECT DISTINCT " + " SUBSTR(name, 1, %1) AS group_name, " + " id AS crate_id, " + " name AS crate_name " + "FROM crates " + "WHERE show = 1 " + "ORDER BY LOWER(name)") + .arg(m_pConfig->getValue( + ConfigKey("[Library]", + "GroupedCratesFixedLength"), + 0)); + if (sDebug) { + qDebug() << "[GROUPEDCRATESTABLEMODEL] queryString: " << queryString; + } + if (!query.exec(queryString)) { + qWarning() << "[GROUPEDCRATESTABLEMODEL] Failed to execute grouped " + "crates query:" + << query.lastError(); + return groupedCrates; + } + + while (query.next()) { + QVariantMap crateData; + // QString groupName = query.value("group_name").toString().trimmed(); + QString groupName = query.value("group_name").toString(); + crateData["group_name"] = groupName; + crateData["crate_id"] = query.value("crate_id"); + crateData["crate_name"] = query.value("crate_name"); + groupedCrates.append(crateData); + if (sDebug) { + qDebug() << "[GROUPEDCRATESTABLEMODEL] Grouped crates -> crate " + "added: " + << crateData; + } + } + } else { + // Variable prefix length with mask, multiple occurrences -> multilevel subgroups + QString queryString = QStringLiteral( + "SELECT DISTINCT " + " id AS crate_id, " + " name AS crate_name " + "FROM crates " + "WHERE show = 1 " + "ORDER BY LOWER(name)"); + if (sDebug) { + qDebug() << "[GROUPEDCRATESTABLEMODEL] queryString: " << queryString; + } + if (!query.exec(queryString)) { + qWarning() << "[GROUPEDCRATESTABLEMODEL] Failed to execute grouped " + "crates query:" + << query.lastError(); + return groupedCrates; + } + const QString& searchDelimit = m_pConfig->getValue( + ConfigKey("[Library]", "GroupedCratesVarLengthMask")); + + while (query.next()) { + QString crateName = query.value("crate_name").toString(); + if (crateName.contains(searchDelimit)) { + // QStringList groupHierarchy = crateName.split(searchDelimit, Qt::SkipEmptyParts); + QStringList groupHierarchy = crateName.split(searchDelimit); + QString currentGroup; + + for (int i = 0; i < groupHierarchy.size(); ++i) { + // currentGroup += (i > 0 ? searchDelimit : "") + groupHierarchy[i].trimmed(); + currentGroup += (i > 0 ? searchDelimit : "") + groupHierarchy[i]; + + QVariantMap crateData; + crateData["group_name"] = currentGroup; + crateData["crate_id"] = query.value("crate_id"); + crateData["crate_name"] = crateName; + + // Add only the full crate record for the last level + if (i == groupHierarchy.size() - 1) { + groupedCrates.append(crateData); + } + + if (sDebug) { + qDebug() << "[GROUPEDCRATESTABLEMODEL] Grouped crates -> crate added: " + << crateData; + } + } + } else { + // No delimiter in crate name -> root-level + QVariantMap crateData; + // crateData["group_name"] = crateName.trimmed(); + crateData["group_name"] = crateName; + crateData["crate_id"] = query.value("crate_id"); + crateData["crate_name"] = crateName; + groupedCrates.append(crateData); + + if (sDebug) { + qDebug() << "[GROUPEDCRATESTABLEMODEL] Grouped crates -> " + "crate added at root level: " + << crateData; + } + } + } + } + if (sDebug) { + qDebug() << "[GROUPEDCRATESTABLEMODEL] Grouped crates list generated " + "with" + << groupedCrates.size() << "entries."; + } + return groupedCrates; } void CrateTableModel::selectCrate(CrateId crateId) { - //qDebug() << "CrateTableModel::setCrate()" << crateId; if (crateId == m_selectedCrate) { qDebug() << "Already focused on crate " << crateId; return; @@ -81,6 +213,58 @@ void CrateTableModel::selectCrate(CrateId crateId) { setDefaultSort(fieldIndex("artist"), Qt::AscendingOrder); } +void CrateTableModel::selectCrateGroup(const QString& groupName) { + if (sDebug) { + qDebug() << "[CrateTableModel] -> selectCrateGroup() -> Searching for " + "tracks in groups starting with:" + << groupName; + } + const QString& checkStamp = QDateTime::currentDateTime().toString("hhmmss"); + + const QString& tableName = QStringLiteral("crate_%1").arg(checkStamp); + + QStringList columns; + columns << LIBRARYTABLE_ID + << "'' AS " + LIBRARYTABLE_PREVIEW + << LIBRARYTABLE_COVERART_DIGEST + " AS " + LIBRARYTABLE_COVERART; + + QString queryString = + QString("CREATE TEMPORARY VIEW IF NOT EXISTS %1 AS " + "SELECT %2 FROM %3 " + "WHERE library.id IN(SELECT crate_tracks.track_id from " + "crate_tracks " + "WHERE crate_tracks.crate_id IN(SELECT crates.id from " + "crates WHERE crates.name LIKE '%4%')) " + "AND %5=0") + .arg(tableName, + columns.join(","), + LIBRARY_TABLE, + groupName, + LIBRARYTABLE_MIXXXDELETED); + + if (sDebug) { + qDebug() << "[CrateTableModel] -> Generated SQL Query:" << queryString; + } + + // Execute the query + FwdSqlQuery(m_database, queryString).execPrepared(); + columns[0] = LIBRARYTABLE_ID; + columns[1] = LIBRARYTABLE_PREVIEW; + columns[2] = LIBRARYTABLE_COVERART; + + // Update the table and view + setTable(tableName, + LIBRARYTABLE_ID, + columns, + m_pTrackCollectionManager->internalCollection()->getTrackSource()); + setDefaultSort(fieldIndex("artist"), Qt::AscendingOrder); + + if (sDebug) { + qDebug() << "[CrateTableModel] -> Group table successfully created " + "with provided Crate IDs."; + } +} + bool CrateTableModel::addTrack(const QModelIndex& index, const QString& location) { Q_UNUSED(index); diff --git a/src/library/trackset/crate/cratetablemodel.h b/src/library/trackset/crate/cratetablemodel.h index 6e977b4a1d8..eaddd8d6b9c 100644 --- a/src/library/trackset/crate/cratetablemodel.h +++ b/src/library/trackset/crate/cratetablemodel.h @@ -3,11 +3,16 @@ #include "library/trackset/crate/crateid.h" #include "library/trackset/tracksettablemodel.h" +constexpr int kInvalidCrateId = -1; + class CrateTableModel final : public TrackSetTableModel { Q_OBJECT public: - CrateTableModel(QObject* parent, TrackCollectionManager* pTrackCollectionManager); + CrateTableModel( + QObject* parent, + TrackCollectionManager* pTrackCollectionManager, + UserSettingsPointer pConfig); ~CrateTableModel() final = default; void selectCrate(CrateId crateId = CrateId()); @@ -15,6 +20,8 @@ class CrateTableModel final : public TrackSetTableModel { return m_selectedCrate; } + void selectCrateGroup(const QString& groupName); + QList getGroupedCrates(); bool addTrack(const QModelIndex& index, const QString& location); void removeTracks(const QModelIndexList& indices) final; @@ -30,4 +37,5 @@ class CrateTableModel final : public TrackSetTableModel { private: CrateId m_selectedCrate; QHash m_searchTexts; + UserSettingsPointer m_pConfig; }; diff --git a/src/library/treeitem.cpp b/src/library/treeitem.cpp index a6bd742005a..653cab7bb88 100644 --- a/src/library/treeitem.cpp +++ b/src/library/treeitem.cpp @@ -110,3 +110,14 @@ void TreeItem::removeChildren(int row, int count) { qDeleteAll(m_children.constBegin() + row, m_children.constBegin() + (row + count)); constErase(&m_children, m_children.constBegin() + row, m_children.constBegin() + (row + count)); } + +int TreeItem::getRow() const { + if (m_pParent) { + return m_pParent->m_children.indexOf(const_cast(this)); + } + return kInvalidRow; // If this is a root item or parent is missing +} + +int TreeItem::childCount() const { + return m_children.size(); +} diff --git a/src/library/treeitem.h b/src/library/treeitem.h index df80de26b26..92cfadd7cd3 100644 --- a/src/library/treeitem.h +++ b/src/library/treeitem.h @@ -38,6 +38,19 @@ class TreeItem final { ~TreeItem(); + ///////////////////////////////////////////////////////////////////////// + // Full Path Accessors + ///////////////////////////////////////////////////////////////////////// + + // Set the full path + void setFullPath(const QString& fullPath) { + m_fullPath = fullPath; + } + + // Get the full path + QString fullPath() const { + return m_fullPath; + } ///////////////////////////////////////////////////////////////////////// // Feature @@ -68,6 +81,10 @@ class TreeItem final { // or kInvalidRow if this is a root item without a parent. int parentRow() const; + // Eve + int getRow() const; + int childCount() const; + // EVE ///////////////////////////////////////////////////////////////////////// // Children @@ -147,4 +164,5 @@ class TreeItem final { QVariant m_data; QIcon m_icon; bool m_bold; + QString m_fullPath; }; diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index d25f6376c46..4ee89b02cbf 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -271,6 +271,8 @@ void DlgPrefLibrary::slotResetToDefaults() { checkBox_show_itunes->setChecked(true); checkBox_show_traktor->setChecked(true); checkBox_show_rekordbox->setChecked(true); + + checkBox_grouped_crates_enable->setChecked(false); } void DlgPrefLibrary::slotUpdate() { @@ -340,6 +342,29 @@ void DlgPrefLibrary::slotUpdate() { break; } + checkBox_grouped_crates_enable->setChecked(m_pConfig->getValue( + ConfigKey("[Library]", "GroupedCratesEnabled"), true)); + int GroupedCratesLength = m_pConfig->getValue( + ConfigKey("[Library]", "GroupedCratesLength")); + + if (GroupedCratesLength == 0) { + radioButton_grouped_crates_fixed_length->setChecked(true); + } else if (GroupedCratesLength == 1) { + radioButton_grouped_crates_var_mask->setChecked(true); + } + spinBox_grouped_crates_fixed_length->setValue(m_pConfig->getValue( + ConfigKey("[Library]", "GroupedCratesFixedLength"))); + spinBox_grouped_crates_fixed_length->setToolTip( + tr("Select the number of characters at the beginning of your " + "cratenames representing the group")); + lineEdit_grouped_crates_var_mask->setText(m_pConfig->getValue( + ConfigKey("[Library]", "GroupedCratesVarLengthMask"))); + lineEdit_grouped_crates_var_mask->setToolTip( + tr("Enter the mask you want to use between the groupname(s) and " + "the cratename.") + + "\n" + + tr("Don't use spaces around the delimiter (or around the mask), " + "these can break the detection process.")); bool editMetadataSelectedClick = m_pConfig->getValue( kEditMetadataSelectedClickConfigKey, kEditMetadataSelectedClickDefault); @@ -585,6 +610,21 @@ void DlgPrefLibrary::slotApply() { ConfigValue(rowHeight)); } + m_pConfig->set(ConfigKey("[Library]", "GroupedCratesEnabled"), + ConfigValue((int)checkBox_grouped_crates_enable->isChecked())); + + if (radioButton_grouped_crates_fixed_length->isChecked()) { + m_pConfig->set(ConfigKey("[Library]", "GroupedCratesLength"), + ConfigValue(0)); + } else if (radioButton_grouped_crates_var_mask->isChecked()) { + m_pConfig->set(ConfigKey("[Library]", "GroupedCratesLength"), + ConfigValue(1)); + } + m_pConfig->set(ConfigKey("[Library]", "GroupedCratesFixedLength"), + ConfigValue(spinBox_grouped_crates_fixed_length->value())); + m_pConfig->set(ConfigKey("[Library]", "GroupedCratesVarLengthMask"), + ConfigValue(lineEdit_grouped_crates_var_mask->text())); + BaseTrackTableModel::setApplyPlayedTrackColor( checkbox_played_track_color->isChecked()); m_pConfig->set( diff --git a/src/preferences/dialog/dlgpreflibrarydlg.ui b/src/preferences/dialog/dlgpreflibrarydlg.ui index cb943bc4342..39ee03f49c6 100644 --- a/src/preferences/dialog/dlgpreflibrarydlg.ui +++ b/src/preferences/dialog/dlgpreflibrarydlg.ui @@ -14,14 +14,12 @@ Library Preferences - Music Directories - @@ -38,7 +36,6 @@ - @@ -52,7 +49,6 @@ - @@ -63,7 +59,6 @@ - @@ -77,7 +72,6 @@ - @@ -88,11 +82,9 @@ - - @@ -115,7 +107,6 @@ - @@ -132,7 +123,6 @@ - @@ -143,7 +133,6 @@ - @@ -154,14 +143,12 @@ - Track Table View - @@ -169,18 +156,16 @@ - Track Double-Click Action: - Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - @@ -212,14 +197,13 @@ - Library Row Height: - Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -239,14 +223,13 @@ - Library Font: - Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -264,21 +247,19 @@ - BPM display precision: - Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - @@ -286,25 +267,22 @@ - - - + Track Search - Search-as-you-type timeout: - Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -315,7 +293,6 @@ - @@ -323,7 +300,6 @@ - @@ -331,14 +307,13 @@ - Percentage of pitch slider range for 'fuzzy' BPM search: - Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter comboBox_search_bpm_fuzzy_range @@ -358,25 +333,22 @@ - - - + Session History - Track duplicate distance - Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true @@ -399,7 +371,6 @@ - @@ -409,7 +380,7 @@ Delete history playlist with less than N tracks - Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -426,11 +397,96 @@ - - - + + + + + + 0 + 100 + + + + Grouped Crates + + + + + 10 + 20 + 300 + 20 + + + + Enable Grouped Crates (Mixxx restart needed) + + + + + + 50 + 40 + 400 + 20 + + + + + 400 + 0 + + + + Fixed length: 1 level of grouping: Group = #Characters + + + + + + 50 + 70 + 400 + 20 + + + + + 400 + 0 + + + + Variable length, multilevel: grouping: Groups are delimited with a mask: + + + + + + 460 + 40 + 42 + 22 + + + + 20 + + + + + + 460 + 70 + 113 + 20 + + + + + @@ -523,7 +579,6 @@ - @@ -593,7 +648,6 @@ - diff --git a/src/preferences/dialog/ui_dlgpreflibrarydlg.h b/src/preferences/dialog/ui_dlgpreflibrarydlg.h new file mode 100644 index 00000000000..677197f0ceb --- /dev/null +++ b/src/preferences/dialog/ui_dlgpreflibrarydlg.h @@ -0,0 +1,839 @@ +/******************************************************************************** +** Form generated from reading UI file 'dlgpreflibrarydlg.ui' +** +** Created by: Qt User Interface Compiler version 6.5.3 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_DLGPREFLIBRARYDLG_H +#define UI_DLGPREFLIBRARYDLG_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_DlgPrefLibraryDlg { + public: + QVBoxLayout* verticalLayout; + QGroupBox* groupBox_MusicDirectories; + QGridLayout* gridLayout_music_directories; + QListView* dirList; + QPushButton* pushButton_add_dir; + QPushButton* pushButton_relocate_dir; + QPushButton* pushButton_remove_dir; + QCheckBox* checkBox_library_scan; + QGroupBox* groupBox_AudioFileFormats; + QGridLayout* gridLayout_audio_formats; + QLabel* builtInFormats; + QGroupBox* groupBox_AudioFileTags; + QGridLayout* gridLayout_metadata; + QCheckBox* checkBox_sync_track_metadata; + QCheckBox* checkBox_serato_metadata_export; + QCheckBox* checkBox_use_relative_path; + QGroupBox* groupBox_TrackTableView; + QGridLayout* gridLayout_track_table_view; + QCheckBox* checkBox_edit_metadata_selected_clicked; + QLabel* label_doubeClickAction; + QRadioButton* radioButton_dbclick_deck; + QRadioButton* radioButton_dbclick_bottom; + QRadioButton* radioButton_dbclick_top; + QRadioButton* radioButton_dbclick_ignore; + QLabel* label_rowHeight; + QSpinBox* spinBox_row_height; + QLabel* label_libraryFont; + QLineEdit* lineEdit_library_font; + QToolButton* btn_library_font; + QLabel* label_bpm_precision; + QSpinBox* spinbox_bpm_precision; + QCheckBox* checkbox_played_track_color; + QGroupBox* groupBox_Search; + QGridLayout* gridLayout_search; + QLabel* label_searchDebouncingTimeout; + QSpinBox* spinBox_search_debouncing_timeout; + QCheckBox* checkBox_enable_search_completions; + QCheckBox* checkBox_enable_search_history_shortcuts; + QLabel* label_searchBpmFuzzyRange; + QComboBox* comboBox_search_bpm_fuzzy_range; + QLabel* label_searchBpmFuzzyRangeInfo; + QGroupBox* groupBox_History; + QGridLayout* gridLayout_history; + QLabel* label_history_track_duplicate_distance; + QSpinBox* spinbox_history_track_duplicate_distance; + QLabel* label_history_cleanup; + QSpinBox* spinbox_history_min_tracks_to_keep; + QGroupBox* groupBox_GroupedCrates; + QCheckBox* checkBox_grouped_crates_enable; + QRadioButton* radioButton_grouped_crates_fixed_length; + QRadioButton* radioButton_grouped_crates_var_mask; + QSpinBox* spinBox_grouped_crates_fixed_length; + QLineEdit* lineEdit_grouped_crates_var_mask; + QGroupBox* groupBox_external_libraries; + QGridLayout* gridLayout_external_libraries; + QCheckBox* checkBox_show_rhythmbox; + QCheckBox* checkBox_show_banshee; + QCheckBox* checkBox_show_itunes; + QCheckBox* checkBox_show_traktor; + QCheckBox* checkBox_show_rekordbox; + QCheckBox* checkBox_show_serato; + QLabel* label_4; + QLabel* label_11; + QGroupBox* groupBox_cover_art_fetcher; + QGridLayout* gridLayout_cover_art_fetcher; + QLabel* label_12; + QLabel* label_13; + QRadioButton* radioButton_cover_art_fetcher_highest; + QRadioButton* radioButton_cover_art_fetcher_high; + QRadioButton* radioButton_cover_art_fetcher_medium; + QRadioButton* radioButton_cover_art_fetcher_lowest; + QGroupBox* groupBox_settingsDir; + QVBoxLayout* vlayout_settingsDir; + QLabel* label_settingsDir; + QLabel* label_settingsManualLink; + QLabel* label_settingsWarning; + QPushButton* pushButton_open_settings_dir; + QSpacerItem* verticalSpacer; + + void setupUi(QWidget* DlgPrefLibraryDlg) { + if (DlgPrefLibraryDlg->objectName().isEmpty()) + DlgPrefLibraryDlg->setObjectName("DlgPrefLibraryDlg"); + DlgPrefLibraryDlg->resize(661, 1522); + DlgPrefLibraryDlg->setWindowTitle(QString::fromUtf8("Library Preferences")); + verticalLayout = new QVBoxLayout(DlgPrefLibraryDlg); + verticalLayout->setSpacing(6); + verticalLayout->setContentsMargins(11, 11, 11, 11); + verticalLayout->setObjectName("verticalLayout"); + groupBox_MusicDirectories = new QGroupBox(DlgPrefLibraryDlg); + groupBox_MusicDirectories->setObjectName("groupBox_MusicDirectories"); + gridLayout_music_directories = new QGridLayout(groupBox_MusicDirectories); + gridLayout_music_directories->setSpacing(6); + gridLayout_music_directories->setContentsMargins(11, 11, 11, 11); + gridLayout_music_directories->setObjectName("gridLayout_music_directories"); + dirList = new QListView(groupBox_MusicDirectories); + dirList->setObjectName("dirList"); + dirList->setEditTriggers(QAbstractItemView::NoEditTriggers); + dirList->setIconSize(QSize(16, 16)); + dirList->setUniformItemSizes(true); + + gridLayout_music_directories->addWidget(dirList, 0, 0, 3, 1); + + pushButton_add_dir = new QPushButton(groupBox_MusicDirectories); + pushButton_add_dir->setObjectName("pushButton_add_dir"); + QFont font; + pushButton_add_dir->setFont(font); + + gridLayout_music_directories->addWidget(pushButton_add_dir, 0, 1, 1, 1); + + pushButton_relocate_dir = new QPushButton(groupBox_MusicDirectories); + pushButton_relocate_dir->setObjectName("pushButton_relocate_dir"); + + gridLayout_music_directories->addWidget(pushButton_relocate_dir, 1, 1, 1, 1); + + pushButton_remove_dir = new QPushButton(groupBox_MusicDirectories); + pushButton_remove_dir->setObjectName("pushButton_remove_dir"); + pushButton_remove_dir->setFont(font); + + gridLayout_music_directories->addWidget(pushButton_remove_dir, 2, 1, 1, 1); + + checkBox_library_scan = new QCheckBox(groupBox_MusicDirectories); + checkBox_library_scan->setObjectName("checkBox_library_scan"); + checkBox_library_scan->setChecked(true); + + gridLayout_music_directories->addWidget(checkBox_library_scan, 3, 0, 1, 2); + + verticalLayout->addWidget(groupBox_MusicDirectories); + + groupBox_AudioFileFormats = new QGroupBox(DlgPrefLibraryDlg); + groupBox_AudioFileFormats->setObjectName("groupBox_AudioFileFormats"); + groupBox_AudioFileFormats->setFlat(false); + gridLayout_audio_formats = new QGridLayout(groupBox_AudioFileFormats); + gridLayout_audio_formats->setSpacing(6); + gridLayout_audio_formats->setContentsMargins(11, 11, 11, 11); + gridLayout_audio_formats->setObjectName("gridLayout_audio_formats"); + builtInFormats = new QLabel(groupBox_AudioFileFormats); + builtInFormats->setObjectName("builtInFormats"); + builtInFormats->setWordWrap(true); + + gridLayout_audio_formats->addWidget(builtInFormats, 0, 0, 1, 1); + + verticalLayout->addWidget(groupBox_AudioFileFormats); + + groupBox_AudioFileTags = new QGroupBox(DlgPrefLibraryDlg); + groupBox_AudioFileTags->setObjectName("groupBox_AudioFileTags"); + gridLayout_metadata = new QGridLayout(groupBox_AudioFileTags); + gridLayout_metadata->setSpacing(6); + gridLayout_metadata->setContentsMargins(11, 11, 11, 11); + gridLayout_metadata->setObjectName("gridLayout_metadata"); + checkBox_sync_track_metadata = new QCheckBox(groupBox_AudioFileTags); + checkBox_sync_track_metadata->setObjectName("checkBox_sync_track_metadata"); + + gridLayout_metadata->addWidget(checkBox_sync_track_metadata, 0, 0, 1, 2); + + checkBox_serato_metadata_export = new QCheckBox(groupBox_AudioFileTags); + checkBox_serato_metadata_export->setObjectName("checkBox_serato_metadata_export"); + + gridLayout_metadata->addWidget(checkBox_serato_metadata_export, 1, 0, 1, 1); + + checkBox_use_relative_path = new QCheckBox(groupBox_AudioFileTags); + checkBox_use_relative_path->setObjectName("checkBox_use_relative_path"); + + gridLayout_metadata->addWidget(checkBox_use_relative_path, 2, 0, 1, 2); + + verticalLayout->addWidget(groupBox_AudioFileTags); + + groupBox_TrackTableView = new QGroupBox(DlgPrefLibraryDlg); + groupBox_TrackTableView->setObjectName("groupBox_TrackTableView"); + gridLayout_track_table_view = new QGridLayout(groupBox_TrackTableView); + gridLayout_track_table_view->setSpacing(6); + gridLayout_track_table_view->setContentsMargins(11, 11, 11, 11); + gridLayout_track_table_view->setObjectName("gridLayout_track_table_view"); + checkBox_edit_metadata_selected_clicked = new QCheckBox(groupBox_TrackTableView); + checkBox_edit_metadata_selected_clicked->setObjectName( + "checkBox_edit_metadata_selected_clicked"); + + gridLayout_track_table_view->addWidget(checkBox_edit_metadata_selected_clicked, 1, 0, 1, 3); + + label_doubeClickAction = new QLabel(groupBox_TrackTableView); + label_doubeClickAction->setObjectName("label_doubeClickAction"); + label_doubeClickAction->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + + gridLayout_track_table_view->addWidget(label_doubeClickAction, 2, 0, 1, 3); + + radioButton_dbclick_deck = new QRadioButton(groupBox_TrackTableView); + radioButton_dbclick_deck->setObjectName("radioButton_dbclick_deck"); + radioButton_dbclick_deck->setChecked(true); + + gridLayout_track_table_view->addWidget(radioButton_dbclick_deck, 3, 0, 1, 3); + + radioButton_dbclick_bottom = new QRadioButton(groupBox_TrackTableView); + radioButton_dbclick_bottom->setObjectName("radioButton_dbclick_bottom"); + + gridLayout_track_table_view->addWidget(radioButton_dbclick_bottom, 4, 0, 1, 3); + + radioButton_dbclick_top = new QRadioButton(groupBox_TrackTableView); + radioButton_dbclick_top->setObjectName("radioButton_dbclick_top"); + + gridLayout_track_table_view->addWidget(radioButton_dbclick_top, 5, 0, 1, 3); + + radioButton_dbclick_ignore = new QRadioButton(groupBox_TrackTableView); + radioButton_dbclick_ignore->setObjectName("radioButton_dbclick_ignore"); + + gridLayout_track_table_view->addWidget(radioButton_dbclick_ignore, 6, 0, 1, 3); + + label_rowHeight = new QLabel(groupBox_TrackTableView); + label_rowHeight->setObjectName("label_rowHeight"); + label_rowHeight->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + + gridLayout_track_table_view->addWidget(label_rowHeight, 7, 0, 1, 1); + + spinBox_row_height = new QSpinBox(groupBox_TrackTableView); + spinBox_row_height->setObjectName("spinBox_row_height"); + spinBox_row_height->setMinimum(5); + spinBox_row_height->setMaximum(100); + spinBox_row_height->setValue(20); + + gridLayout_track_table_view->addWidget(spinBox_row_height, 7, 1, 1, 2); + + label_libraryFont = new QLabel(groupBox_TrackTableView); + label_libraryFont->setObjectName("label_libraryFont"); + label_libraryFont->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + + gridLayout_track_table_view->addWidget(label_libraryFont, 8, 0, 1, 1); + + lineEdit_library_font = new QLineEdit(groupBox_TrackTableView); + lineEdit_library_font->setObjectName("lineEdit_library_font"); + lineEdit_library_font->setReadOnly(true); + + gridLayout_track_table_view->addWidget(lineEdit_library_font, 8, 1, 1, 1); + + btn_library_font = new QToolButton(groupBox_TrackTableView); + btn_library_font->setObjectName("btn_library_font"); + + gridLayout_track_table_view->addWidget(btn_library_font, 8, 2, 1, 1); + + label_bpm_precision = new QLabel(groupBox_TrackTableView); + label_bpm_precision->setObjectName("label_bpm_precision"); + label_bpm_precision->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + + gridLayout_track_table_view->addWidget(label_bpm_precision, 9, 0, 1, 1); + + spinbox_bpm_precision = new QSpinBox(groupBox_TrackTableView); + spinbox_bpm_precision->setObjectName("spinbox_bpm_precision"); + + gridLayout_track_table_view->addWidget(spinbox_bpm_precision, 9, 1, 1, 2); + + checkbox_played_track_color = new QCheckBox(groupBox_TrackTableView); + checkbox_played_track_color->setObjectName("checkbox_played_track_color"); + + gridLayout_track_table_view->addWidget(checkbox_played_track_color, 10, 0, 1, 3); + + verticalLayout->addWidget(groupBox_TrackTableView); + + groupBox_Search = new QGroupBox(DlgPrefLibraryDlg); + groupBox_Search->setObjectName("groupBox_Search"); + gridLayout_search = new QGridLayout(groupBox_Search); + gridLayout_search->setSpacing(6); + gridLayout_search->setContentsMargins(11, 11, 11, 11); + gridLayout_search->setObjectName("gridLayout_search"); + label_searchDebouncingTimeout = new QLabel(groupBox_Search); + label_searchDebouncingTimeout->setObjectName("label_searchDebouncingTimeout"); + label_searchDebouncingTimeout->setAlignment( + Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + + gridLayout_search->addWidget(label_searchDebouncingTimeout, 0, 0, 1, 1); + + spinBox_search_debouncing_timeout = new QSpinBox(groupBox_Search); + spinBox_search_debouncing_timeout->setObjectName("spinBox_search_debouncing_timeout"); + + gridLayout_search->addWidget(spinBox_search_debouncing_timeout, 0, 1, 1, 2); + + checkBox_enable_search_completions = new QCheckBox(groupBox_Search); + checkBox_enable_search_completions->setObjectName("checkBox_enable_search_completions"); + + gridLayout_search->addWidget(checkBox_enable_search_completions, 1, 0, 1, 3); + + checkBox_enable_search_history_shortcuts = new QCheckBox(groupBox_Search); + checkBox_enable_search_history_shortcuts->setObjectName( + "checkBox_enable_search_history_shortcuts"); + + gridLayout_search->addWidget(checkBox_enable_search_history_shortcuts, 2, 0, 1, 3); + + label_searchBpmFuzzyRange = new QLabel(groupBox_Search); + label_searchBpmFuzzyRange->setObjectName("label_searchBpmFuzzyRange"); + label_searchBpmFuzzyRange->setAlignment( + Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + + gridLayout_search->addWidget(label_searchBpmFuzzyRange, 3, 0, 1, 1); + + comboBox_search_bpm_fuzzy_range = new QComboBox(groupBox_Search); + comboBox_search_bpm_fuzzy_range->setObjectName("comboBox_search_bpm_fuzzy_range"); + + gridLayout_search->addWidget(comboBox_search_bpm_fuzzy_range, 3, 1, 1, 2); + + label_searchBpmFuzzyRangeInfo = new QLabel(groupBox_Search); + label_searchBpmFuzzyRangeInfo->setObjectName("label_searchBpmFuzzyRangeInfo"); + label_searchBpmFuzzyRangeInfo->setWordWrap(true); + + gridLayout_search->addWidget(label_searchBpmFuzzyRangeInfo, 4, 0, 1, 3); + + verticalLayout->addWidget(groupBox_Search); + + groupBox_History = new QGroupBox(DlgPrefLibraryDlg); + groupBox_History->setObjectName("groupBox_History"); + gridLayout_history = new QGridLayout(groupBox_History); + gridLayout_history->setSpacing(6); + gridLayout_history->setContentsMargins(11, 11, 11, 11); + gridLayout_history->setObjectName("gridLayout_history"); + label_history_track_duplicate_distance = new QLabel(groupBox_History); + label_history_track_duplicate_distance->setObjectName( + "label_history_track_duplicate_distance"); + label_history_track_duplicate_distance->setAlignment( + Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + label_history_track_duplicate_distance->setWordWrap(true); + + gridLayout_history->addWidget(label_history_track_duplicate_distance, 1, 0, 1, 1); + + spinbox_history_track_duplicate_distance = new QSpinBox(groupBox_History); + spinbox_history_track_duplicate_distance->setObjectName( + "spinbox_history_track_duplicate_distance"); + spinbox_history_track_duplicate_distance->setMinimum(0); + spinbox_history_track_duplicate_distance->setMaximum(99); + spinbox_history_track_duplicate_distance->setValue(1); + + gridLayout_history->addWidget(spinbox_history_track_duplicate_distance, 1, 1, 1, 2); + + label_history_cleanup = new QLabel(groupBox_History); + label_history_cleanup->setObjectName("label_history_cleanup"); + label_history_cleanup->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + + gridLayout_history->addWidget(label_history_cleanup, 2, 0, 1, 1); + + spinbox_history_min_tracks_to_keep = new QSpinBox(groupBox_History); + spinbox_history_min_tracks_to_keep->setObjectName("spinbox_history_min_tracks_to_keep"); + spinbox_history_min_tracks_to_keep->setMinimum(1); + spinbox_history_min_tracks_to_keep->setMaximum(99); + spinbox_history_min_tracks_to_keep->setValue(1); + + gridLayout_history->addWidget(spinbox_history_min_tracks_to_keep, 2, 1, 1, 2); + + verticalLayout->addWidget(groupBox_History); + + groupBox_GroupedCrates = new QGroupBox(DlgPrefLibraryDlg); + groupBox_GroupedCrates->setObjectName("groupBox_GroupedCrates"); + groupBox_GroupedCrates->setMinimumSize(QSize(0, 100)); + checkBox_grouped_crates_enable = new QCheckBox(groupBox_GroupedCrates); + checkBox_grouped_crates_enable->setObjectName("checkBox_grouped_crates_enable"); + checkBox_grouped_crates_enable->setGeometry(QRect(10, 20, 300, 20)); + radioButton_grouped_crates_fixed_length = new QRadioButton(groupBox_GroupedCrates); + radioButton_grouped_crates_fixed_length->setObjectName( + "radioButton_grouped_crates_fixed_length"); + radioButton_grouped_crates_fixed_length->setGeometry(QRect(50, 40, 400, 20)); + radioButton_grouped_crates_fixed_length->setMinimumSize(QSize(400, 0)); + radioButton_grouped_crates_var_mask = new QRadioButton(groupBox_GroupedCrates); + radioButton_grouped_crates_var_mask->setObjectName("radioButton_grouped_crates_var_mask"); + radioButton_grouped_crates_var_mask->setGeometry(QRect(50, 70, 400, 20)); + radioButton_grouped_crates_var_mask->setMinimumSize(QSize(400, 0)); + spinBox_grouped_crates_fixed_length = new QSpinBox(groupBox_GroupedCrates); + spinBox_grouped_crates_fixed_length->setObjectName("spinBox_grouped_crates_fixed_length"); + spinBox_grouped_crates_fixed_length->setGeometry(QRect(460, 40, 42, 22)); + spinBox_grouped_crates_fixed_length->setMaximum(20); + lineEdit_grouped_crates_var_mask = new QLineEdit(groupBox_GroupedCrates); + lineEdit_grouped_crates_var_mask->setObjectName("lineEdit_grouped_crates_var_mask"); + lineEdit_grouped_crates_var_mask->setGeometry(QRect(460, 70, 113, 20)); + + verticalLayout->addWidget(groupBox_GroupedCrates); + + groupBox_external_libraries = new QGroupBox(DlgPrefLibraryDlg); + groupBox_external_libraries->setObjectName("groupBox_external_libraries"); + gridLayout_external_libraries = new QGridLayout(groupBox_external_libraries); + gridLayout_external_libraries->setSpacing(6); + gridLayout_external_libraries->setContentsMargins(11, 11, 11, 11); + gridLayout_external_libraries->setObjectName("gridLayout_external_libraries"); + checkBox_show_rhythmbox = new QCheckBox(groupBox_external_libraries); + checkBox_show_rhythmbox->setObjectName("checkBox_show_rhythmbox"); + checkBox_show_rhythmbox->setChecked(true); + + gridLayout_external_libraries->addWidget(checkBox_show_rhythmbox, 0, 0, 1, 1); + + checkBox_show_banshee = new QCheckBox(groupBox_external_libraries); + checkBox_show_banshee->setObjectName("checkBox_show_banshee"); + checkBox_show_banshee->setChecked(true); + + gridLayout_external_libraries->addWidget(checkBox_show_banshee, 1, 0, 1, 1); + + checkBox_show_itunes = new QCheckBox(groupBox_external_libraries); + checkBox_show_itunes->setObjectName("checkBox_show_itunes"); + checkBox_show_itunes->setChecked(true); + + gridLayout_external_libraries->addWidget(checkBox_show_itunes, 2, 0, 1, 1); + + checkBox_show_traktor = new QCheckBox(groupBox_external_libraries); + checkBox_show_traktor->setObjectName("checkBox_show_traktor"); + checkBox_show_traktor->setChecked(true); + + gridLayout_external_libraries->addWidget(checkBox_show_traktor, 3, 0, 1, 1); + + checkBox_show_rekordbox = new QCheckBox(groupBox_external_libraries); + checkBox_show_rekordbox->setObjectName("checkBox_show_rekordbox"); + checkBox_show_rekordbox->setChecked(true); + + gridLayout_external_libraries->addWidget(checkBox_show_rekordbox, 4, 0, 1, 1); + + checkBox_show_serato = new QCheckBox(groupBox_external_libraries); + checkBox_show_serato->setObjectName("checkBox_show_serato"); + checkBox_show_serato->setChecked(true); + + gridLayout_external_libraries->addWidget(checkBox_show_serato, 5, 0, 1, 1); + + label_4 = new QLabel(groupBox_external_libraries); + label_4->setObjectName("label_4"); + label_4->setWordWrap(true); + label_4->setContentsMargins(-1, 10, -1, -1); + + gridLayout_external_libraries->addWidget(label_4, 6, 0, 1, 1); + + label_11 = new QLabel(groupBox_external_libraries); + label_11->setObjectName("label_11"); + label_11->setWordWrap(true); + + gridLayout_external_libraries->addWidget(label_11, 7, 0, 1, 1); + + verticalLayout->addWidget(groupBox_external_libraries); + + groupBox_cover_art_fetcher = new QGroupBox(DlgPrefLibraryDlg); + groupBox_cover_art_fetcher->setObjectName("groupBox_cover_art_fetcher"); + gridLayout_cover_art_fetcher = new QGridLayout(groupBox_cover_art_fetcher); + gridLayout_cover_art_fetcher->setSpacing(6); + gridLayout_cover_art_fetcher->setContentsMargins(11, 11, 11, 11); + gridLayout_cover_art_fetcher->setObjectName("gridLayout_cover_art_fetcher"); + label_12 = new QLabel(groupBox_cover_art_fetcher); + label_12->setObjectName("label_12"); + label_12->setWordWrap(true); + + gridLayout_cover_art_fetcher->addWidget(label_12, 0, 0, 1, 1); + + label_13 = new QLabel(groupBox_cover_art_fetcher); + label_13->setObjectName("label_13"); + label_13->setWordWrap(true); + + gridLayout_cover_art_fetcher->addWidget(label_13, 1, 0, 1, 1); + + radioButton_cover_art_fetcher_highest = new QRadioButton(groupBox_cover_art_fetcher); + radioButton_cover_art_fetcher_highest->setObjectName( + "radioButton_cover_art_fetcher_highest"); + radioButton_cover_art_fetcher_highest->setChecked(false); + + gridLayout_cover_art_fetcher->addWidget(radioButton_cover_art_fetcher_highest, 2, 0, 1, 1); + + radioButton_cover_art_fetcher_high = new QRadioButton(groupBox_cover_art_fetcher); + radioButton_cover_art_fetcher_high->setObjectName("radioButton_cover_art_fetcher_high"); + radioButton_cover_art_fetcher_high->setChecked(false); + + gridLayout_cover_art_fetcher->addWidget(radioButton_cover_art_fetcher_high, 3, 0, 1, 1); + + radioButton_cover_art_fetcher_medium = new QRadioButton(groupBox_cover_art_fetcher); + radioButton_cover_art_fetcher_medium->setObjectName("radioButton_cover_art_fetcher_medium"); + radioButton_cover_art_fetcher_medium->setChecked(false); + + gridLayout_cover_art_fetcher->addWidget(radioButton_cover_art_fetcher_medium, 4, 0, 1, 1); + + radioButton_cover_art_fetcher_lowest = new QRadioButton(groupBox_cover_art_fetcher); + radioButton_cover_art_fetcher_lowest->setObjectName("radioButton_cover_art_fetcher_lowest"); + radioButton_cover_art_fetcher_lowest->setChecked(false); + + gridLayout_cover_art_fetcher->addWidget(radioButton_cover_art_fetcher_lowest, 5, 0, 1, 1); + + verticalLayout->addWidget(groupBox_cover_art_fetcher); + + groupBox_settingsDir = new QGroupBox(DlgPrefLibraryDlg); + groupBox_settingsDir->setObjectName("groupBox_settingsDir"); + vlayout_settingsDir = new QVBoxLayout(groupBox_settingsDir); + vlayout_settingsDir->setSpacing(6); + vlayout_settingsDir->setContentsMargins(11, 11, 11, 11); + vlayout_settingsDir->setObjectName("vlayout_settingsDir"); + label_settingsDir = new QLabel(groupBox_settingsDir); + label_settingsDir->setObjectName("label_settingsDir"); + label_settingsDir->setWordWrap(true); + + vlayout_settingsDir->addWidget(label_settingsDir); + + label_settingsManualLink = new QLabel(groupBox_settingsDir); + label_settingsManualLink->setObjectName("label_settingsManualLink"); + label_settingsManualLink->setWordWrap(true); + label_settingsManualLink->setOpenExternalLinks(true); + + vlayout_settingsDir->addWidget(label_settingsManualLink); + + label_settingsWarning = new QLabel(groupBox_settingsDir); + label_settingsWarning->setObjectName("label_settingsWarning"); + label_settingsWarning->setWordWrap(true); + + vlayout_settingsDir->addWidget(label_settingsWarning); + + pushButton_open_settings_dir = new QPushButton(groupBox_settingsDir); + pushButton_open_settings_dir->setObjectName("pushButton_open_settings_dir"); + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth( + pushButton_open_settings_dir->sizePolicy().hasHeightForWidth()); + pushButton_open_settings_dir->setSizePolicy(sizePolicy); + + vlayout_settingsDir->addWidget(pushButton_open_settings_dir); + + verticalLayout->addWidget(groupBox_settingsDir); + + verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + + verticalLayout->addItem(verticalSpacer); + +#if QT_CONFIG(shortcut) + label_searchBpmFuzzyRange->setBuddy(comboBox_search_bpm_fuzzy_range); +#endif // QT_CONFIG(shortcut) + QWidget::setTabOrder(dirList, pushButton_add_dir); + QWidget::setTabOrder(pushButton_add_dir, pushButton_relocate_dir); + QWidget::setTabOrder(pushButton_relocate_dir, pushButton_remove_dir); + QWidget::setTabOrder(pushButton_remove_dir, checkBox_library_scan); + QWidget::setTabOrder(checkBox_library_scan, checkBox_sync_track_metadata); + QWidget::setTabOrder(checkBox_sync_track_metadata, checkBox_serato_metadata_export); + QWidget::setTabOrder(checkBox_serato_metadata_export, + checkBox_edit_metadata_selected_clicked); + QWidget::setTabOrder(checkBox_edit_metadata_selected_clicked, radioButton_dbclick_deck); + QWidget::setTabOrder(radioButton_dbclick_deck, radioButton_dbclick_bottom); + QWidget::setTabOrder(radioButton_dbclick_bottom, radioButton_dbclick_top); + QWidget::setTabOrder(radioButton_dbclick_top, radioButton_dbclick_ignore); + QWidget::setTabOrder(radioButton_dbclick_ignore, spinbox_bpm_precision); + QWidget::setTabOrder(spinbox_bpm_precision, checkbox_played_track_color); + QWidget::setTabOrder(checkbox_played_track_color, spinbox_history_track_duplicate_distance); + QWidget::setTabOrder(spinbox_history_track_duplicate_distance, + spinbox_history_min_tracks_to_keep); + QWidget::setTabOrder(spinbox_history_min_tracks_to_keep, checkBox_use_relative_path); + QWidget::setTabOrder(checkBox_use_relative_path, spinBox_row_height); + QWidget::setTabOrder(spinBox_row_height, btn_library_font); + QWidget::setTabOrder(btn_library_font, spinBox_search_debouncing_timeout); + QWidget::setTabOrder(spinBox_search_debouncing_timeout, checkBox_enable_search_completions); + QWidget::setTabOrder(checkBox_enable_search_completions, + checkBox_enable_search_history_shortcuts); + QWidget::setTabOrder(checkBox_enable_search_history_shortcuts, + comboBox_search_bpm_fuzzy_range); + QWidget::setTabOrder(comboBox_search_bpm_fuzzy_range, checkBox_show_rhythmbox); + QWidget::setTabOrder(checkBox_show_rhythmbox, checkBox_show_banshee); + QWidget::setTabOrder(checkBox_show_banshee, checkBox_show_itunes); + QWidget::setTabOrder(checkBox_show_itunes, checkBox_show_traktor); + QWidget::setTabOrder(checkBox_show_traktor, checkBox_show_rekordbox); + QWidget::setTabOrder(checkBox_show_rekordbox, checkBox_show_serato); + QWidget::setTabOrder(checkBox_show_serato, radioButton_cover_art_fetcher_highest); + QWidget::setTabOrder(radioButton_cover_art_fetcher_highest, + radioButton_cover_art_fetcher_high); + QWidget::setTabOrder(radioButton_cover_art_fetcher_high, + radioButton_cover_art_fetcher_medium); + QWidget::setTabOrder(radioButton_cover_art_fetcher_medium, + radioButton_cover_art_fetcher_lowest); + QWidget::setTabOrder(radioButton_cover_art_fetcher_lowest, pushButton_open_settings_dir); + + retranslateUi(DlgPrefLibraryDlg); + + QMetaObject::connectSlotsByName(DlgPrefLibraryDlg); + } // setupUi + + void retranslateUi(QWidget* DlgPrefLibraryDlg) { + groupBox_MusicDirectories->setTitle(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Music Directories", nullptr)); +#if QT_CONFIG(tooltip) + pushButton_add_dir->setToolTip(QCoreApplication::translate( + "DlgPrefLibraryDlg", + "Add a directory where your music is stored. Mixxx will watch " + "this directory and its subdirectories for new tracks.", + nullptr)); +#endif // QT_CONFIG(tooltip) + pushButton_add_dir->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Add", nullptr)); +#if QT_CONFIG(tooltip) + pushButton_relocate_dir->setToolTip( + QCoreApplication::translate("DlgPrefLibraryDlg", + "If an existing music directory is moved, Mixxx " + "doesn't know where to find the audio files in it. " + "Choose Relink to select the music directory in its " + "new location.
This will re-establish the links " + "to the audio files in the Mixxx library.", + nullptr)); +#endif // QT_CONFIG(tooltip) + pushButton_relocate_dir->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Relink", nullptr)); +#if QT_CONFIG(tooltip) + pushButton_remove_dir->setToolTip( + QCoreApplication::translate("DlgPrefLibraryDlg", + "If removed, Mixxx will no longer watch this directory " + "and its subdirectories for new tracks.", + nullptr)); +#endif // QT_CONFIG(tooltip) + pushButton_remove_dir->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Remove", nullptr)); + checkBox_library_scan->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Rescan directories on start-up", + nullptr)); + groupBox_AudioFileFormats->setTitle(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Audio File Formats", nullptr)); + builtInFormats->setText(QString()); + groupBox_AudioFileTags->setTitle( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Track Metadata Synchronization / Playlists", + nullptr)); +#if QT_CONFIG(tooltip) + checkBox_sync_track_metadata->setToolTip( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Automatically write modified track metadata from the " + "library into file tags and reimport metadata from " + "updated file tags into the library", + nullptr)); +#endif // QT_CONFIG(tooltip) + checkBox_sync_track_metadata->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Synchronize library track metadata from/to file tags", + nullptr)); +#if QT_CONFIG(tooltip) + checkBox_serato_metadata_export->setToolTip(QCoreApplication::translate( + "DlgPrefLibraryDlg", + "Keeps track color, beat grid, bpm lock, cue points, and loops " + "synchronized with SERATO_MARKERS/MARKERS2 file " + "tags.

WARNING: Enabling this option also enables the " + "reimport of Serato metadata after files have been modified " + "outside of Mixxx. On reimport existing metadata in Mixxx is " + "replaced with the metadata found in file tags. Custom " + "metadata not included in file tags like loop colors is lost.", + nullptr)); +#endif // QT_CONFIG(tooltip) + checkBox_serato_metadata_export->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Synchronize Serato track metadata from/to file tags " + "(experimental)", + nullptr)); + checkBox_use_relative_path->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Use relative paths for playlist export if possible", + nullptr)); + groupBox_TrackTableView->setTitle(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Track Table View", nullptr)); + checkBox_edit_metadata_selected_clicked->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Edit metadata after clicking selected track", + nullptr)); + label_doubeClickAction->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Track Double-Click Action:", nullptr)); + radioButton_dbclick_deck->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Load track to next available deck", + nullptr)); + radioButton_dbclick_bottom->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Add track to Auto DJ queue (bottom)", + nullptr)); + radioButton_dbclick_top->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Add track to Auto DJ queue (top)", + nullptr)); + radioButton_dbclick_ignore->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Ignore", nullptr)); + label_rowHeight->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Library Row Height:", nullptr)); + spinBox_row_height->setSuffix(QCoreApplication::translate( + "DlgPrefLibraryDlg", " px", nullptr)); + label_libraryFont->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Library Font:", nullptr)); + btn_library_font->setText(QCoreApplication::translate("DlgPrefLibraryDlg", "...", nullptr)); + label_bpm_precision->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "BPM display precision:", nullptr)); + checkbox_played_track_color->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Grey out played tracks", nullptr)); + groupBox_Search->setTitle(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Track Search", nullptr)); + label_searchDebouncingTimeout->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Search-as-you-type timeout:", nullptr)); + spinBox_search_debouncing_timeout->setSuffix( + QCoreApplication::translate( + "DlgPrefLibraryDlg", " ms", nullptr)); + checkBox_enable_search_completions->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Enable search completions", nullptr)); + checkBox_enable_search_history_shortcuts->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Enable search history keyboard shortcuts", + nullptr)); + label_searchBpmFuzzyRange->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", + "Percentage of pitch slider range for 'fuzzy' BPM search:", + nullptr)); + label_searchBpmFuzzyRangeInfo->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "This range will be used for the 'fuzzy' BPM search " + "(~bpm:) via the search box, as well as for BPM search " + "in Track context menu > Search related Tracks", + nullptr)); + groupBox_History->setTitle(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Session History", nullptr)); + label_history_track_duplicate_distance->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Track duplicate distance", + nullptr)); +#if QT_CONFIG(tooltip) + spinbox_history_track_duplicate_distance->setToolTip( + QCoreApplication::translate("DlgPrefLibraryDlg", + "When playing a track again log it to the session " + "history only if more than N other tracks have been " + "played in the meantime", + nullptr)); +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(tooltip) + label_history_cleanup->setToolTip( + QCoreApplication::translate("DlgPrefLibraryDlg", + "History playlist with less than N tracks will be " + "deleted

Note: the cleanup will be performed " + "during startup and shutdown of Mixxx.", + nullptr)); +#endif // QT_CONFIG(tooltip) + label_history_cleanup->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Delete history playlist with less than N tracks", + nullptr)); + groupBox_GroupedCrates->setTitle(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Grouped Crates", nullptr)); + checkBox_grouped_crates_enable->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Enable Grouped Crates (Mixxx restart needed)", + nullptr)); + radioButton_grouped_crates_fixed_length->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Fixed length: 1 level of grouping: Group = " + "#Characters", + nullptr)); + radioButton_grouped_crates_var_mask->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Variable length, multilevel: grouping: Groups are " + "delimited with a mask: ", + nullptr)); + groupBox_external_libraries->setTitle(QCoreApplication::translate( + "DlgPrefLibraryDlg", "External Libraries", nullptr)); + checkBox_show_rhythmbox->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Show Rhythmbox Library", nullptr)); + checkBox_show_banshee->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Show Banshee Library", nullptr)); + checkBox_show_itunes->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Show iTunes Library", nullptr)); + checkBox_show_traktor->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Show Traktor Library", nullptr)); + checkBox_show_rekordbox->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Show Rekordbox Library", nullptr)); + checkBox_show_serato->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Show Serato Library", nullptr)); + label_4->setText(QCoreApplication::translate("DlgPrefLibraryDlg", + "All external libraries shown are write protected.", + nullptr)); + label_11->setText(QCoreApplication::translate("DlgPrefLibraryDlg", + "You will need to restart Mixxx for these settings to take " + "effect.", + nullptr)); + groupBox_cover_art_fetcher->setTitle( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Preferred Cover Art Fetcher Resolution", + nullptr)); + label_12->setText(QCoreApplication::translate("DlgPrefLibraryDlg", + "Fetch cover art from coverartarchive.com by using Import " + "Metadata From Musicbrainz.", + nullptr)); + label_13->setText(QCoreApplication::translate("DlgPrefLibraryDlg", + "Note: \">1200 px\" can fetch up to very large cover arts.", + nullptr)); + radioButton_cover_art_fetcher_highest->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + ">1200 px (if available)", + nullptr)); + radioButton_cover_art_fetcher_high->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "1200 px (if available)", nullptr)); + radioButton_cover_art_fetcher_medium->setText( + QCoreApplication::translate( + "DlgPrefLibraryDlg", "500 px", nullptr)); + radioButton_cover_art_fetcher_lowest->setText( + QCoreApplication::translate( + "DlgPrefLibraryDlg", "250 px", nullptr)); + groupBox_settingsDir->setTitle(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Settings Directory", nullptr)); + label_settingsDir->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", + "The Mixxx settings directory contains the library database, " + "various configuration files, log files, track analysis data, " + "as well as custom controller mappings.", + nullptr)); + label_settingsManualLink->setText(QString()); + label_settingsWarning->setText( + QCoreApplication::translate("DlgPrefLibraryDlg", + "Edit those files only if you know what you are doing " + "and only while Mixxx is not running.", + nullptr)); + pushButton_open_settings_dir->setText(QCoreApplication::translate( + "DlgPrefLibraryDlg", "Open Mixxx Settings Folder", nullptr)); + (void)DlgPrefLibraryDlg; + } // retranslateUi +}; + +namespace Ui { +class DlgPrefLibraryDlg : public Ui_DlgPrefLibraryDlg {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_DLGPREFLIBRARYDLG_H diff --git a/src/track/cue.h b/src/track/cue.h index c0474e76ae9..ee9b17c3cf0 100644 --- a/src/track/cue.h +++ b/src/track/cue.h @@ -73,9 +73,7 @@ class Cue : public QObject { void shiftPositionFrames(mixxx::audio::FrameDiff_t frameOffset); mixxx::audio::FrameDiff_t getLengthFrames() const; - int getHotCue() const; - QString getLabel() const; void setLabel(const QString& label);