diff --git a/res/skins/Deere/style.qss b/res/skins/Deere/style.qss index e3c3905d7f6..e7867d676f4 100644 --- a/res/skins/Deere/style.qss +++ b/res/skins/Deere/style.qss @@ -2382,10 +2382,26 @@ WEffectChainPresetSelector::indicator:unchecked:selected, outline: none; } -#CueDeleteButton:hover { +#CueSavedLoopButton { +/* tall button, about the same height as cue number + label edit box */ + width: 28px; + height: 46px; + /* make the icon slightly larger than default 16px */ + qproperty-iconSize: 20px; + qproperty-icon: url(skin:/../Deere/icon/ic_loop_48px.svg); + background-color: #3B3B3B; + border-radius: 2px; + outline: none; +} + +#CueDeleteButton:hover, #CueSavedLoopButton:hover { background-color: #4B4B4B; } +#CueSavedLoopButton:checked { + background-color: #006596; +} + WRateRange { font-size: 10px; } diff --git a/res/skins/LateNight/style_classic.qss b/res/skins/LateNight/style_classic.qss index 7b5fc9293f9..1cf56a6e748 100644 --- a/res/skins/LateNight/style_classic.qss +++ b/res/skins/LateNight/style_classic.qss @@ -1424,7 +1424,8 @@ QPushButton#pushButtonAutoDJ:enabled:!checked, WPushButton#GuiToggleButton[displayValue="0"], #RecFeedback[displayValue="0"], WPushButton#BroadcastButton[displayValue="0"], -WPushButton#SkinSettingsToggle[displayValue="0"] { +WPushButton#SkinSettingsToggle[displayValue="0"], +WCueMenuPopup QPushButton { background-color: #262626; } @@ -2042,9 +2043,8 @@ QPushButton#pushButtonRepeatPlaylist:!checked { } /* widgets in cue popup menu */ -#CueDeleteButton { /* - padding: 3px 6px; */ - qproperty-icon: url(skins:LateNight/classic/buttons/btn__delete.svg); +#CueDeleteButton, +#CueSavedLoopButton { /* color buttons are 42x24 px. To get the final size for the Delete button consider border width. It's a tall button, about the same height as cue number + label edit box */ @@ -2052,10 +2052,17 @@ QPushButton#pushButtonRepeatPlaylist:!checked { height: 42px; border-width: 2px; border-image: url(skins:LateNight/classic/buttons/btn_embedded_library.svg) 2 2 2 2; - /* make the icon slightly larger than default 16px */ - qproperty-iconSize: 20px; - /* has no effect - padding: 0px; */ +} +#CueDeleteButton { + image: url(skins:LateNight/classic/buttons/btn__delete.svg); +} +#CueSavedLoopButton { + image: url(skins:LateNight/classic/buttons/btn__loop.svg); +} +#CueSavedLoopButton:pressed, +#CueSavedLoopButton:checked { + background-color: #db0000; + outline: none; } #CueLabelEdit { diff --git a/res/skins/LateNight/style_palemoon.qss b/res/skins/LateNight/style_palemoon.qss index 8aafa1b6bbe..d1459a64c2a 100644 --- a/res/skins/LateNight/style_palemoon.qss +++ b/res/skins/LateNight/style_palemoon.qss @@ -1459,7 +1459,8 @@ WPushButton#FxAssignButton1[displayValue="0"], WEffectSelector:!editable, #fadeModeCombobox:!editable, #LibraryFeatureControls QPushButton:enabled, -#CueDeleteButton { +#CueDeleteButton, +#CueSavedLoopButton { outline: none; border-width: 2px; border-image: url(skins:LateNight/palemoon/buttons/btn_embedded_library.svg) 2 2 2 2; @@ -1488,7 +1489,9 @@ WEffectSelector:!editable, QPushButton#pushButtonRecording:checked, WEffectSelector:!editable:on, #fadeModeCombobox:!editable:on, - #CueDeleteButton[pressed="true"] { + #CueDeleteButton[pressed="true"], + #CueSavedLoopButton[pressed="true"], + #CueSavedLoopButton:checked { border-width: 2px; border-image: url(skins:LateNight/palemoon/buttons/btn_embedded_library_active.svg) 2 2 2 2; } @@ -1636,6 +1639,7 @@ WBeatSpinBox::down-button { /* bright buttons in dimmed containers #BeatgridControls WPushButton[displayValue="0"], */ #CueDeleteButton, + #CueSavedLoopButton, #SplitCue[displayValue="0"], #PlayPreview[displayValue="0"], /* library controls */ @@ -1689,7 +1693,9 @@ WPushButton#LoopOut[pressed="true"], #BeatjumpControls WPushButton[value="1"], #RateControls WPushButton[value="1"], #BeatgridControls WPushButton[pressed="true"]/*, -#CueDeleteButton[pressed="true"]*/ { +#CueDeleteButton[pressed="true"], +#CueSavedLoopButton[pressed="true"], +#CueSavedLoopButton:checked*/ { background-color: #7d350d; } @@ -2498,20 +2504,32 @@ QPushButton#pushButtonRepeatPlaylist:!checked { /* AutoDJ button icons */ /* widgets in cue popup menu */ -WCueMenuPopup #CueDeleteButton { - qproperty-icon: url(skins:LateNight/palemoon/buttons/btn__delete.svg); +WCueMenuPopup #CueDeleteButton, +WCueMenuPopup #CueSavedLoopButton { width: 24px; height: 42px; - /* make the icon slightly larger than default 16px */ - qproperty-iconSize: 20px; } +WCueMenuPopup #CueDeleteButton { + /* Note: we can't use qproperty-icon here because it's evauated only once + and therefore won't style the checked state */ + image: url(skins:LateNight/palemoon/buttons/btn__delete.svg); +} + WCueMenuPopup #CueDeleteButton:pressed { + background-color: #b24c12; + outline: none; + image: url(skins:LateNight/palemoon/buttons/btn__delete_active.svg); + } -WCueMenuPopup #CueDeleteButton:pressed { +WCueMenuPopup #CueSavedLoopButton { + image: url(skins:LateNight/palemoon/buttons/btn__loop.svg); +} +WCueMenuPopup #CueSavedLoopButton:pressed, +WCueMenuPopup #CueSavedLoopButton:checked { background-color: #b24c12; outline: none; - /* not applied: */ - qproperty-icon: url(skins:LateNight/palemoon/buttons/btn__delete_active.svg); + image: url(skins:LateNight/palemoon/buttons/btn__loop_active.svg); } + WCueMenuPopup #CueLabelEdit { /*border: 1px solid #c2b3a5;*/ border-radius: 0px; diff --git a/res/skins/Shade/style.qss b/res/skins/Shade/style.qss index b397f95aa4c..0df56fc9b79 100644 --- a/res/skins/Shade/style.qss +++ b/res/skins/Shade/style.qss @@ -430,6 +430,23 @@ WCueMenuPopup QLabel { qproperty-iconSize: 20px; outline: none; } +#CueSavedLoopButton { + /* set any border to allow styles for other properties as well */ + border: 1px solid #060613; +/* To get the final size for the Delete button consider border width. + tall button, about the same height as cue number + label edit box */ + width: 24px; + height: 43px; + padding: 0px; + background-color: #aab2b7; + qproperty-icon: url(skin:/btn/btn_hotcue_loop.png); + /* make the icon slightly larger than default 16px */ + qproperty-iconSize: 20px; + outline: none; +} +#CueSavedLoopButton:pressed, #CueSavedLoopButton:checked { + background-color: #F90562; +} #CueLabelEdit { border: 1px solid #060613; border-radius: 0px; diff --git a/res/skins/Shade/style_dark.qss b/res/skins/Shade/style_dark.qss index c09903a48aa..7850b8c420f 100644 --- a/res/skins/Shade/style_dark.qss +++ b/res/skins/Shade/style_dark.qss @@ -150,6 +150,9 @@ WEffectSelector::indicator:unchecked:selected, #CueDeleteButton { background-color: #8a8a8a; } +#CueSavedLoopButton:pressed, #CueSavedLoopButton:checked { + background-color: #b79d00; +} #CueLabelEdit { background-color: #3F3041; } diff --git a/res/skins/Shade/style_summer_sunset.qss b/res/skins/Shade/style_summer_sunset.qss index e8a8da9b386..2a0fc64da0c 100644 --- a/res/skins/Shade/style_summer_sunset.qss +++ b/res/skins/Shade/style_summer_sunset.qss @@ -145,6 +145,12 @@ WEffectSelector::indicator:unchecked:selected, #CueDeleteButton { background-color: #cdbb5d; } +#CueSavedLoopButton { + background-color: #cdbb5d; +} +#CueSavedLoopButton:pressed, #CueSavedLoopButton:checked { + background-color: #54fd05; +} #CueLabelEdit { background-color: #706633; } diff --git a/res/skins/Tango/buttons/btn_loop_beatjump_on_black.svg b/res/skins/Tango/buttons/btn_loop_beatjump_on_black.svg new file mode 100644 index 00000000000..5dcd9f37f64 --- /dev/null +++ b/res/skins/Tango/buttons/btn_loop_beatjump_on_black.svg @@ -0,0 +1 @@ + diff --git a/res/skins/Tango/style.qss b/res/skins/Tango/style.qss index e28bc900121..edd7fb5e433 100644 --- a/res/skins/Tango/style.qss +++ b/res/skins/Tango/style.qss @@ -2465,7 +2465,8 @@ WTrackMenu QMenu QCheckBox::indicator { } /* widgets in cue popup menu */ -#CueDeleteButton { +#CueDeleteButton, +#CueSavedLoopButton { /* color buttons are 42x24 px. To get the final size for the Delete button consider border width. tall button, about the same height as cue number + label edit box */ @@ -2474,10 +2475,20 @@ WTrackMenu QMenu QCheckBox::indicator { border: 1px solid #666; background-color: #333; border-radius: 2px; - qproperty-icon: url(skin:/../Tango/buttons/btn_delete.svg); - /* make the icon slightly larger than default 16px */ - qproperty-iconSize: 20px; -} + } + #CueDeleteButton { + /* Note: we can't use qproperty-icon here because it's evauated only once + and therefore won't style the checked state */ + image: url(skin:/../Tango/buttons/btn_delete.svg); + } + #CueSavedLoopButton:!checked { + image: url(skin:/../Tango/buttons/btn_loop_beatjump_off.svg); + } + #CueSavedLoopButton:checked { + border: 1px solid #666; + background-color: #ff7b00; + image: url(skin:/../Tango/buttons/btn_loop_beatjump_on_black.svg); + } #CueLabelEdit { border: 1px solid #ff6600; border-radius: 0px; diff --git a/res/skins/default.qss b/res/skins/default.qss index 97ef209c250..8fa087d7883 100644 --- a/res/skins/default.qss +++ b/res/skins/default.qss @@ -34,7 +34,7 @@ QPushButton#LibraryBPMButton:!checked { /* Add default icons for cue menu buttons */ WCueMenuPopup #CueDeleteButton { - qproperty-icon: url(:/images/ic_delete.svg); + image: url(:/images/ic_delete.svg); } WColorPicker QPushButton[noColor="true"] { diff --git a/src/control/pollingcontrolproxy.h b/src/control/pollingcontrolproxy.h index db59a66ed8f..011500c3900 100644 --- a/src/control/pollingcontrolproxy.h +++ b/src/control/pollingcontrolproxy.h @@ -56,6 +56,11 @@ class PollingControlProxy { return m_pControl->defaultValue(); } + /// Return the key of the underlying control + const ConfigKey& getKey() const { + return m_pControl->getKey(); + } + /// Sets the control value to v. Thread safe, non-blocking. void set(double v) { m_pControl->set(v, nullptr); diff --git a/src/waveform/renderers/waveformmark.cpp b/src/waveform/renderers/waveformmark.cpp index 33b0d95bc16..610c6d1aee4 100644 --- a/src/waveform/renderers/waveformmark.cpp +++ b/src/waveform/renderers/waveformmark.cpp @@ -107,9 +107,11 @@ WaveformMark::WaveformMark(const QString& group, m_showUntilNext{} { QString positionControl; QString endPositionControl; + QString typeControl; if (hotCue != Cue::kNoHotCue) { positionControl = "hotcue_" + QString::number(hotCue + 1) + "_position"; endPositionControl = "hotcue_" + QString::number(hotCue + 1) + "_endposition"; + typeControl = "hotcue_" + QString::number(hotCue + 1) + "_type"; m_showUntilNext = true; } else { positionControl = context.selectString(node, "Control"); @@ -121,6 +123,7 @@ WaveformMark::WaveformMark(const QString& group, } if (!endPositionControl.isEmpty()) { m_pEndPositionCO = std::make_unique(group, endPositionControl); + m_pTypeCO = std::make_unique(group, typeControl); } QString visibilityControl = context.selectString(node, "VisibilityControl"); diff --git a/src/waveform/renderers/waveformmark.h b/src/waveform/renderers/waveformmark.h index b604f5eb83e..32ebc0ab036 100644 --- a/src/waveform/renderers/waveformmark.h +++ b/src/waveform/renderers/waveformmark.h @@ -63,10 +63,17 @@ class WaveformMark { return m_pPositionCO->get(); } double getSampleEndPosition() const { - if (m_pEndPositionCO) { - return m_pEndPositionCO->get(); + if (!m_pEndPositionCO || + // A hotcue may have an end position although it isn't a saved + // loop anymore. This happens when the user changes the cue + // type. However, we persist the end position if the user wants + // to restore the cue to a saved loop + (m_pTypeCO && + static_cast(m_pTypeCO->get()) != + mixxx::CueType::Loop)) { + return Cue::kNoPosition; } - return Cue::kNoPosition; + return m_pEndPositionCO->get(); } QString getItem() const { return m_pPositionCO->getKey().item; @@ -163,6 +170,7 @@ class WaveformMark { private: std::unique_ptr m_pPositionCO; std::unique_ptr m_pEndPositionCO; + std::unique_ptr m_pTypeCO; std::unique_ptr m_pVisibleCO; std::unique_ptr m_pGraphics; diff --git a/src/widget/wcolorpicker.cpp b/src/widget/wcolorpicker.cpp index 129d18cdc8a..ebcf2262338 100644 --- a/src/widget/wcolorpicker.cpp +++ b/src/widget/wcolorpicker.cpp @@ -46,9 +46,6 @@ WColorPicker::WColorPicker(Options options, const ColorPalette& palette, QWidget QGridLayout* pLayout = new QGridLayout(); pLayout->setContentsMargins(0, 0, 0, 0); - pLayout->setSizeConstraint(QLayout::SetFixedSize); - setSizePolicy(QSizePolicy()); - // Unfortunately, not all styles supported by Qt support setting a // background color for QPushButtons (see // https://bugreports.qt.io/browse/QTBUG-11089). For example, when using diff --git a/src/widget/wcuemenupopup.cpp b/src/widget/wcuemenupopup.cpp index 022ce3f22b4..6f8f1cab5ee 100644 --- a/src/widget/wcuemenupopup.cpp +++ b/src/widget/wcuemenupopup.cpp @@ -3,56 +3,94 @@ #include #include +#include "control/controlobject.h" #include "moc_wcuemenupopup.cpp" #include "track/track.h" +void CueTypePushButton::mousePressEvent(QMouseEvent* e) { + if (e->type() == QEvent::MouseButtonPress && e->button() == Qt::RightButton) { + emit rightClicked(); + return; + } + QPushButton::mousePressEvent(e); +} + WCueMenuPopup::WCueMenuPopup(UserSettingsPointer pConfig, QWidget* parent) : QWidget(parent), - m_colorPaletteSettings(ColorPaletteSettings(pConfig)) { + m_colorPaletteSettings(ColorPaletteSettings(pConfig)), + m_pBeatLoopSize(ControlFlag::AllowMissingOrInvalid), + m_pPlayPos(ControlFlag::AllowMissingOrInvalid), + m_pTrackSample(ControlFlag::AllowMissingOrInvalid), + m_pQuantizeEnabled(ControlFlag::AllowMissingOrInvalid) { QWidget::hide(); setWindowFlags(Qt::Popup); setAttribute(Qt::WA_StyledBackground); setObjectName("WCueMenuPopup"); - m_pCueNumber = new QLabel(this); + m_pCueNumber = std::make_unique(this); m_pCueNumber->setToolTip(tr("Cue number")); m_pCueNumber->setObjectName("CueNumberLabel"); m_pCueNumber->setAlignment(Qt::AlignLeft); - m_pCuePosition = new QLabel(this); + m_pCuePosition = std::make_unique(this); m_pCuePosition->setToolTip(tr("Cue position")); m_pCuePosition->setObjectName("CuePositionLabel"); m_pCuePosition->setAlignment(Qt::AlignRight); - m_pEditLabel = new QLineEdit(this); + m_pEditLabel = std::make_unique(this); m_pEditLabel->setToolTip(tr("Edit cue label")); m_pEditLabel->setObjectName("CueLabelEdit"); m_pEditLabel->setPlaceholderText(tr("Label...")); - connect(m_pEditLabel, &QLineEdit::textEdited, this, &WCueMenuPopup::slotEditLabel); - connect(m_pEditLabel, &QLineEdit::returnPressed, this, &WCueMenuPopup::hide); + connect(m_pEditLabel.get(), &QLineEdit::textEdited, this, &WCueMenuPopup::slotEditLabel); + connect(m_pEditLabel.get(), &QLineEdit::returnPressed, this, &WCueMenuPopup::hide); - m_pColorPicker = new WColorPicker(WColorPicker::Option::NoOptions, m_colorPaletteSettings.getHotcueColorPalette(), this); + m_pColorPicker = + std::make_unique(WColorPicker::Option::NoOptions, + m_colorPaletteSettings.getHotcueColorPalette(), + this); m_pColorPicker->setObjectName("CueColorPicker"); - connect(m_pColorPicker, &WColorPicker::colorPicked, this, &WCueMenuPopup::slotChangeCueColor); + connect(m_pColorPicker.get(), + &WColorPicker::colorPicked, + this, + &WCueMenuPopup::slotChangeCueColor); - m_pDeleteCue = new QPushButton("", this); + m_pDeleteCue = std::make_unique("", this); m_pDeleteCue->setToolTip(tr("Delete this cue")); m_pDeleteCue->setObjectName("CueDeleteButton"); - connect(m_pDeleteCue, &QPushButton::clicked, this, &WCueMenuPopup::slotDeleteCue); + connect(m_pDeleteCue.get(), &QPushButton::clicked, this, &WCueMenuPopup::slotDeleteCue); + + m_pSavedLoopCue = std::make_unique(this); + m_pSavedLoopCue->setToolTip( + tr("Toggle this cue type between normal cue and saved loop") + + "\n\n" + + tr("Left-click: Use the old size or the current beatloop size as the loop size") + + "\n" + + tr("Right-click: Use the current play position as loop end if it is after the cue")); + m_pSavedLoopCue->setObjectName("CueSavedLoopButton"); + m_pSavedLoopCue->setCheckable(true); + connect(m_pSavedLoopCue.get(), + &CueTypePushButton::clicked, + this, + &WCueMenuPopup::slotSavedLoopCueAuto); + connect(m_pSavedLoopCue.get(), + &CueTypePushButton::rightClicked, + this, + &WCueMenuPopup::slotSavedLoopCueManual); QHBoxLayout* pLabelLayout = new QHBoxLayout(); - pLabelLayout->addWidget(m_pCueNumber); + pLabelLayout->addWidget(m_pCueNumber.get()); pLabelLayout->addStretch(1); - pLabelLayout->addWidget(m_pCuePosition); + pLabelLayout->addWidget(m_pCuePosition.get()); QVBoxLayout* pLeftLayout = new QVBoxLayout(); pLeftLayout->addLayout(pLabelLayout); - pLeftLayout->addWidget(m_pEditLabel); - pLeftLayout->addWidget(m_pColorPicker); + pLeftLayout->addWidget(m_pEditLabel.get()); + pLeftLayout->addWidget(m_pColorPicker.get()); QVBoxLayout* pRightLayout = new QVBoxLayout(); - pRightLayout->addWidget(m_pDeleteCue); + pRightLayout->addWidget(m_pDeleteCue.get()); pRightLayout->addStretch(1); + pRightLayout->addWidget(m_pSavedLoopCue.get()); QHBoxLayout* pMainLayout = new QHBoxLayout(); pMainLayout->addLayout(pLeftLayout); @@ -65,11 +103,35 @@ WCueMenuPopup::WCueMenuPopup(UserSettingsPointer pConfig, QWidget* parent) layout()->activate(); } -void WCueMenuPopup::setTrackAndCue(TrackPointer pTrack, const CuePointer& pCue) { - if (pTrack && pCue) { - m_pTrack = pTrack; - m_pCue = pCue; +void WCueMenuPopup::setTrackCueGroup( + TrackPointer pTrack, const CuePointer& pCue, const QString& group) { + if (!pTrack || !pCue) { + return; + } + + m_pTrack = pTrack; + m_pCue = pCue; + + if (m_pBeatLoopSize.getKey().group != group) { + m_pBeatLoopSize = PollingControlProxy(group, "beatloop_size"); + } + + if (m_pPlayPos.getKey().group != group) { + m_pPlayPos = PollingControlProxy(group, "playposition"); + } + + if (m_pTrackSample.getKey().group != group) { + m_pTrackSample = PollingControlProxy(group, "track_samples"); + } + + if (m_pQuantizeEnabled.getKey().group != group) { + m_pQuantizeEnabled = PollingControlProxy(group, "quantize"); + } + slotUpdate(); +} +void WCueMenuPopup::slotUpdate() { + if (m_pTrack && m_pCue) { int hotcueNumber = m_pCue->getHotCue(); QString hotcueNumberText = ""; if (hotcueNumber != Cue::kNoHotCue) { @@ -83,7 +145,7 @@ void WCueMenuPopup::setTrackAndCue(TrackPointer pTrack, const CuePointer& pCue) if (pos.startPosition.isValid()) { double startPositionSeconds = pos.startPosition.value() / m_pTrack->getSampleRate(); positionText = mixxx::Duration::formatTime(startPositionSeconds, mixxx::Duration::Precision::CENTISECONDS); - if (pos.endPosition.isValid()) { + if (pos.endPosition.isValid() && m_pCue->getType() != mixxx::CueType::HotCue) { double endPositionSeconds = pos.endPosition.value() / m_pTrack->getSampleRate(); positionText = QString("%1 - %2").arg( positionText, @@ -95,6 +157,7 @@ void WCueMenuPopup::setTrackAndCue(TrackPointer pTrack, const CuePointer& pCue) m_pEditLabel->setText(m_pCue->getLabel()); m_pColorPicker->setSelectedColor(m_pCue->getColor()); + m_pSavedLoopCue->setChecked(m_pCue->getType() == mixxx::CueType::Loop); } else { m_pTrack.reset(); m_pCue.reset(); @@ -135,7 +198,76 @@ void WCueMenuPopup::slotDeleteCue() { hide(); } +void WCueMenuPopup::slotSavedLoopCueAuto() { + VERIFY_OR_DEBUG_ASSERT(m_pCue != nullptr) { + return; + } + VERIFY_OR_DEBUG_ASSERT(m_pTrack != nullptr) { + return; + } + VERIFY_OR_DEBUG_ASSERT(m_pBeatLoopSize.valid()) { + return; + } + if (m_pCue->getType() == mixxx::CueType::Loop) { + m_pCue->setType(mixxx::CueType::HotCue); + } else { + auto cueStartEnd = m_pCue->getStartAndEndPosition(); + if (!cueStartEnd.endPosition.isValid() || + cueStartEnd.endPosition <= cueStartEnd.startPosition) { + double beatloopSize = m_pBeatLoopSize.get(); + const mixxx::BeatsPointer pBeats = m_pTrack->getBeats(); + if (beatloopSize <= 0 || !pBeats) { + return; + } + auto position = pBeats->findNBeatsFromPosition( + cueStartEnd.startPosition, beatloopSize); + if (position <= m_pCue->getPosition()) { + return; + } + m_pCue->setEndPosition(position); + } + m_pCue->setType(mixxx::CueType::Loop); + } + slotUpdate(); +} + +void WCueMenuPopup::slotSavedLoopCueManual() { + VERIFY_OR_DEBUG_ASSERT(m_pCue != nullptr) { + return; + } + VERIFY_OR_DEBUG_ASSERT(m_pTrack != nullptr) { + return; + } + VERIFY_OR_DEBUG_ASSERT(m_pBeatLoopSize.valid()) { + return; + } + const mixxx::BeatsPointer pBeats = m_pTrack->getBeats(); + auto position = mixxx::audio::FramePos::fromEngineSamplePos( + m_pPlayPos.get() * m_pTrackSample.get()); + if (m_pQuantizeEnabled.toBool() && pBeats) { + mixxx::audio::FramePos nextBeatPosition, prevBeatPosition; + pBeats->findPrevNextBeats(position, &prevBeatPosition, &nextBeatPosition, false); + position = (nextBeatPosition - position > position - prevBeatPosition) + ? prevBeatPosition + : nextBeatPosition; + } + if (position <= m_pCue->getPosition()) { + return; + } + m_pCue->setEndPosition(position); + m_pCue->setType(mixxx::CueType::Loop); + slotUpdate(); +} + void WCueMenuPopup::closeEvent(QCloseEvent* event) { + if (m_pTrack && m_pCue) { + // Check if this is a hotcue, and -if yes- if it has an end position + // from previous loop cue or temporary state. + // If yes, remove it. + if (m_pCue->getType() == mixxx::CueType::HotCue && m_pCue->getEndPosition().isValid()) { + m_pCue->setEndPosition(mixxx::audio::FramePos()); + } + } emit aboutToHide(); QWidget::closeEvent(event); } diff --git a/src/widget/wcuemenupopup.h b/src/widget/wcuemenupopup.h index 9bfbbeb3f4d..4f20d171ea5 100644 --- a/src/widget/wcuemenupopup.h +++ b/src/widget/wcuemenupopup.h @@ -2,28 +2,39 @@ #include #include +#include #include +#include "control/pollingcontrolproxy.h" #include "preferences/colorpalettesettings.h" #include "track/cue.h" #include "track/track_decl.h" #include "util/widgethelper.h" #include "widget/wcolorpicker.h" +class ControlProxy; + +// Custom PushButton which emit a custom signal when right-clicked +class CueTypePushButton : public QPushButton { + Q_OBJECT + public: + explicit CueTypePushButton(QWidget* parent = 0) + : QPushButton(parent) { + } + + protected: + void mousePressEvent(QMouseEvent* e) override; + + signals: + void rightClicked(); +}; + class WCueMenuPopup : public QWidget { Q_OBJECT public: WCueMenuPopup(UserSettingsPointer pConfig, QWidget* parent = nullptr); - ~WCueMenuPopup() { - delete m_pCueNumber; - delete m_pCuePosition; - delete m_pEditLabel; - delete m_pColorPicker; - delete m_pDeleteCue; - } - - void setTrackAndCue(TrackPointer pTrack, const CuePointer& pCue); + void setTrackCueGroup(TrackPointer pTrack, const CuePointer& pCue, const QString& group); void setColorPalette(const ColorPalette& palette) { if (m_pColorPicker != nullptr) { @@ -52,18 +63,34 @@ class WCueMenuPopup : public QWidget { private slots: void slotEditLabel(); void slotDeleteCue(); + void slotUpdate(); + /// This slot is called when the saved loop button is being left pressed, + /// which effectively toggle the cue loop between standard cue and saved + /// loop. If the cue was never a saved loop, it will use the current + /// beatloop size to define the saved loop size. If it was previously a + /// saved loop, it will use the previously known loop size. + void slotSavedLoopCueAuto(); + /// This slot is called when the saved loop button is being left pressed, + /// which effectively makes the cue a saved loop and use the current play + /// position as loop end + void slotSavedLoopCueManual(); void slotChangeCueColor(mixxx::RgbColor::optional_t color); private: ColorPaletteSettings m_colorPaletteSettings; + PollingControlProxy m_pBeatLoopSize; + PollingControlProxy m_pPlayPos; + PollingControlProxy m_pTrackSample; + PollingControlProxy m_pQuantizeEnabled; CuePointer m_pCue; TrackPointer m_pTrack; - QLabel* m_pCueNumber; - QLabel* m_pCuePosition; - QLineEdit* m_pEditLabel; - WColorPicker* m_pColorPicker; - QPushButton* m_pDeleteCue; + std::unique_ptr m_pCueNumber; + std::unique_ptr m_pCuePosition; + std::unique_ptr m_pEditLabel; + std::unique_ptr m_pColorPicker; + std::unique_ptr m_pDeleteCue; + std::unique_ptr m_pSavedLoopCue; protected: void closeEvent(QCloseEvent* event) override; diff --git a/src/widget/whotcuebutton.cpp b/src/widget/whotcuebutton.cpp index 903305e1b50..0e91f1812ad 100644 --- a/src/widget/whotcuebutton.cpp +++ b/src/widget/whotcuebutton.cpp @@ -121,7 +121,7 @@ void WHotcueButton::mousePressEvent(QMouseEvent* e) { pTrack->removeCue(pHotCue); return; } - m_pCueMenuPopup->setTrackAndCue(pTrack, pHotCue); + m_pCueMenuPopup->setTrackCueGroup(pTrack, pHotCue, m_group); // use the bottom left corner as starting point for popup m_pCueMenuPopup->popup(mapToGlobal(QPoint(0, height()))); } diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index 14cd0614686..794db86a9f4 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -548,7 +548,7 @@ void WOverview::mousePressEvent(QMouseEvent* e) { m_pCurrentTrack->removeCue(pHoveredCue); return; } else { - m_pCueMenuPopup->setTrackAndCue(m_pCurrentTrack, pHoveredCue); + m_pCueMenuPopup->setTrackCueGroup(m_pCurrentTrack, pHoveredCue, m_group); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) m_pCueMenuPopup->popup(e->globalPosition().toPoint()); #else diff --git a/src/widget/wwaveformviewer.cpp b/src/widget/wwaveformviewer.cpp index 8cd120760f8..bbce61ccc73 100644 --- a/src/widget/wwaveformviewer.cpp +++ b/src/widget/wwaveformviewer.cpp @@ -102,7 +102,7 @@ void WWaveformViewer::mousePressEvent(QMouseEvent* event) { if (!isPlaying() && m_pHoveredMark) { auto cueAtClickPos = getCuePointerFromCueMark(m_pHoveredMark); if (cueAtClickPos) { - m_pCueMenuPopup->setTrackAndCue(currentTrack, cueAtClickPos); + m_pCueMenuPopup->setTrackCueGroup(currentTrack, cueAtClickPos, m_group); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) m_pCueMenuPopup->popup(event->globalPosition().toPoint()); #else