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