Skip to content

Commit

Permalink
Merge pull request #13394 from ronso0/hotcue-swap-gui
Browse files Browse the repository at this point in the history
feat: allow swapping hotcues
  • Loading branch information
Swiftb0y authored Jan 14, 2025
2 parents 1803e02 + e1c1ffb commit 61178b6
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 15 deletions.
1 change: 1 addition & 0 deletions res/skins/LateNight/controls/button_hotcue.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Hotcue><Variable name="number"/></Hotcue>
<NumberStates>3</NumberStates>
<DimBrightThreshold><Variable name="DimBrightThresholdHotcueBtn"/></DimBrightThreshold>
<DndRectMargin><Variable name="DndRectMarginHotcueBtn"/></DndRectMargin>
<State>
<Number>0</Number>
<Unpressed scalemode="STRETCH">skins:LateNight/<Variable name="BtnScheme"/>/buttons/btn_<Variable name="BtnType"/>_square.svg</Unpressed>
Expand Down
4 changes: 4 additions & 0 deletions res/skins/LateNight/skin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@
<SetVariable name="DimBrightThresholdOverview">127</SetVariable>
<SetVariable name="DimBrightThresholdWaveform">105</SetVariable>
<SetVariable name="DimBrightThresholdHotcueBtn">105</SetVariable>
<!-- When dragging a hotcue button in order to swap hotcues, the entire
widget rect is used for the dnd cursor but the margin is not considered.
'1' cuts off the colored overlap. -->
<SetVariable name="DndRectMarginHotcueBtn">1</SetVariable>

<SetVariable name="AxesColor">#999</SetVariable>
<SetVariable name="BeatColor">#999</SetVariable>
Expand Down
36 changes: 36 additions & 0 deletions src/engine/controls/cuecontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,11 @@ void CueControl::connectControls() {
this,
&CueControl::hotcueClear,
Qt::DirectConnection);
connect(pControl,
&HotcueControl::hotcueSwap,
this,
&CueControl::hotcueSwap,
Qt::DirectConnection);
}
}

Expand Down Expand Up @@ -1218,6 +1223,26 @@ void CueControl::hotcueClear(HotcueControl* pControl, double value) {
setHotcueFocusIndex(Cue::kNoHotCue);
}

void CueControl::hotcueSwap(HotcueControl* pControl, double v) {
// 1-based GUI/human index to 0-based internal index
int newCuenum = static_cast<int>(v) - 1;
if (newCuenum < mixxx::kFirstHotCueIndex || newCuenum >= m_iNumHotCues) {
return;
}

auto lock = lockMutex(&m_trackMutex);
if (!m_pLoadedTrack) {
return;
}

CuePointer pCue = pControl->getCue();
if (!pCue) {
return;
}

m_pLoadedTrack->swapHotcues(pCue->getHotCue(), newCuenum);
}

void CueControl::hotcuePositionChanged(
HotcueControl* pControl, double value) {
auto lock = lockMutex(&m_trackMutex);
Expand Down Expand Up @@ -2585,6 +2610,13 @@ HotcueControl::HotcueControl(const QString& group, int hotcueIndex)
&HotcueControl::slotHotcueClear,
Qt::DirectConnection);

m_hotcueSwap = std::make_unique<ControlPushButton>(keyForControl(QStringLiteral("swap")));
connect(m_hotcueSwap.get(),
&ControlObject::valueChanged,
this,
&HotcueControl::slotHotcueSwap,
Qt::DirectConnection);

m_previewingType.setValue(mixxx::CueType::Invalid);
m_previewingPosition.setValue(mixxx::audio::kInvalidFramePos);
}
Expand Down Expand Up @@ -2643,6 +2675,10 @@ void HotcueControl::slotHotcueClear(double v) {
emit hotcueClear(this, v);
}

void HotcueControl::slotHotcueSwap(double v) {
emit hotcueSwap(this, v);
}

void HotcueControl::slotHotcuePositionChanged(double newPosition) {
emit hotcuePositionChanged(this, newPosition);
}
Expand Down
4 changes: 4 additions & 0 deletions src/engine/controls/cuecontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class HotcueControl : public QObject {
void slotHotcueActivateLoop(double v);
void slotHotcueActivatePreview(double v);
void slotHotcueClear(double v);
void slotHotcueSwap(double v);
void slotHotcueEndPositionChanged(double newPosition);
void slotHotcuePositionChanged(double newPosition);
void slotHotcueColorChangeRequest(double newColor);
Expand All @@ -150,6 +151,7 @@ class HotcueControl : public QObject {
void hotcueActivate(HotcueControl* pHotcue, double v, HotcueSetMode mode);
void hotcueActivatePreview(HotcueControl* pHotcue, double v);
void hotcueClear(HotcueControl* pHotcue, double v);
void hotcueSwap(HotcueControl* pHotcue, double v);
void hotcuePositionChanged(HotcueControl* pHotcue, double newPosition);
void hotcueEndPositionChanged(HotcueControl* pHotcue, double newEndPosition);
void hotcuePlay(double v);
Expand Down Expand Up @@ -181,6 +183,7 @@ class HotcueControl : public QObject {
std::unique_ptr<ControlPushButton> m_hotcueActivateLoop;
std::unique_ptr<ControlPushButton> m_hotcueActivatePreview;
std::unique_ptr<ControlPushButton> m_hotcueClear;
std::unique_ptr<ControlPushButton> m_hotcueSwap;

ControlValueAtomic<mixxx::CueType> m_previewingType;
ControlValueAtomic<mixxx::audio::FramePos> m_previewingPosition;
Expand Down Expand Up @@ -228,6 +231,7 @@ class CueControl : public EngineControl {
void hotcueActivatePreview(HotcueControl* pControl, double v);
void updateCurrentlyPreviewingIndex(int hotcueIndex);
void hotcueClear(HotcueControl* pControl, double v);
void hotcueSwap(HotcueControl* pHotcue, double v);
void hotcuePositionChanged(HotcueControl* pControl, double newPosition);
void hotcueEndPositionChanged(HotcueControl* pControl, double newEndPosition);

Expand Down
7 changes: 6 additions & 1 deletion src/skin/legacy/tooltips.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,12 @@ void Tooltips::addStandardTooltips() {
<< QString("%1 + %2: %3")
.arg(rightClick,
shift,
tr("Delete selected hotcue."));
tr("Delete selected hotcue."))
<< tr("Drag this button onto another Hotcue button to move it "
"there (change its index). If the other hotcue is set, "
"the two are swapped.")
<< tr("Dragging with Shift key pressed will not start previewing "
"the hotcue");

// Status displays and toggle buttons
add("toggle_recording")
Expand Down
11 changes: 11 additions & 0 deletions src/track/cue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,17 @@ mixxx::audio::FrameDiff_t Cue::getLengthFrames() const {
return m_endPosition - m_startPosition;
}

void Cue::setHotCue(int n) {
VERIFY_OR_DEBUG_ASSERT(n >= mixxx::kFirstHotCueIndex) {
return;
}
const auto lock = lockMutex(&m_mutex);
if (m_iHotCue == n) {
return;
}
m_iHotCue = n;
}

int Cue::getHotCue() const {
const auto lock = lockMutex(&m_mutex);
return m_iHotCue;
Expand Down
3 changes: 2 additions & 1 deletion src/track/cue.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class Cue : public QObject {

mixxx::audio::FrameDiff_t getLengthFrames() const;

void setHotCue(int n);
int getHotCue() const;

QString getLabel() const;
Expand Down Expand Up @@ -104,7 +105,7 @@ class Cue : public QObject {
mixxx::CueType m_type;
mixxx::audio::FramePos m_startPosition;
mixxx::audio::FramePos m_endPosition;
const int m_iHotCue;
int m_iHotCue;
QString m_label;
mixxx::RgbColor m_color;

Expand Down
39 changes: 39 additions & 0 deletions src/track/track.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,21 @@ CuePointer Track::findCueById(DbId id) const {
return CuePointer();
}

CuePointer Track::findHotcueByIndex(int idx) const {
auto locked = lockMutex(&m_qMutex);
auto cueIt = std::find_if(
m_cuePoints.begin(),
m_cuePoints.end(),
[idx](const CuePointer& pCue) {
return pCue && pCue->getHotCue() == idx;
});
if (cueIt != m_cuePoints.end()) {
return *cueIt;
} else {
return {};
}
}

void Track::removeCue(const CuePointer& pCue) {
if (!pCue) {
return;
Expand Down Expand Up @@ -1103,6 +1118,30 @@ void Track::removeCuesOfType(mixxx::CueType type) {
}
}

void Track::swapHotcues(int a, int b) {
VERIFY_OR_DEBUG_ASSERT(a != b) {
qWarning() << "Track::swapHotcues rejected," << a << "==" << b;
return;
}
VERIFY_OR_DEBUG_ASSERT(a != Cue::kNoHotCue || b != Cue::kNoHotCue) {
qWarning() << "Track::swapHotcues rejected, both a and b are kNoHotCue";
return;
}
auto locked = lockMutex(&m_qMutex);
CuePointer pCueA = findHotcueByIndex(a);
CuePointer pCueB = findHotcueByIndex(b);
if (!pCueA && !pCueB) {
return;
}
if (pCueA) {
pCueA->setHotCue(b);
}
if (pCueB) {
pCueB->setHotCue(a);
}
emit cuesUpdated();
}

void Track::setCuePoints(const QList<CuePointer>& cuePoints) {
// While this method could be called from any thread,
// associated Cue objects should always live on the
Expand Down
3 changes: 2 additions & 1 deletion src/track/track.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,14 +322,15 @@ class Track : public QObject {
}
CuePointer findCueByType(mixxx::CueType type) const; // NOTE: Cannot be used for hotcues.
CuePointer findCueById(DbId id) const;
CuePointer findHotcueByIndex(int idx) const;
void removeCue(const CuePointer& pCue);
void removeCuesOfType(mixxx::CueType);
QList<CuePointer> getCuePoints() const {
const QMutexLocker lock(&m_qMutex);
// lock thread-unsafe copy constructors of QList
return m_cuePoints;
}

void swapHotcues(int a, int b);
void setCuePoints(const QList<CuePointer>& cuePoints);

#ifdef __STEM__
Expand Down
12 changes: 12 additions & 0 deletions src/util/db/dbid.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ class DbId {
return debug << dbId.m_value;
}

friend QDataStream& operator<<(QDataStream& out, const DbId& dbId) {
// explicit cast as recommended by Qt docs
return out << static_cast<quint32>(dbId.m_value);
}

friend QDataStream& operator>>(QDataStream& in, DbId& dbId) {
quint32 v;
in >> v;
dbId.m_value = v;
return in;
}

friend qhash_seed_t qHash(
const DbId& dbId,
qhash_seed_t seed = 0) {
Expand Down
Loading

0 comments on commit 61178b6

Please sign in to comment.