Skip to content

Commit ec69bf7

Browse files
author
Gary
committed
CueControl: also reject hotcue endposition writes when unset
Address review feedback on the hotcue-position guard: apply the same confirm-required + reject-when-unset treatment to hotcue_endposition, so writing it with no hotcue set no longer stores a bogus value. Route endposition writes through slotHotcueEndPositionChangeRequest (rejects when m_pCue is null), and use setAndConfirm() in setEndPosition() since the control is now confirm-required. Also trim the verbose comments on the position path per review.
1 parent 4d99688 commit ec69bf7

3 files changed

Lines changed: 85 additions & 13 deletions

File tree

src/engine/controls/cuecontrol.cpp

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,10 +2638,6 @@ HotcueControl::HotcueControl(const QString& group, int hotcueIndex)
26382638
&HotcueControl::slotHotcuePositionChanged,
26392639
Qt::DirectConnection);
26402640
m_hotcuePosition->set(Cue::kNoPosition);
2641-
// The position may only be moved by dragging an existing hotcue, so route
2642-
// write requests through a handler that rejects them unless the hotcue is
2643-
// set. This also makes the control confirm-required, so engine-side updates
2644-
// must use setAndConfirm() (see HotcueControl::setPosition()).
26452641
m_hotcuePosition->connectValueChangeRequest(
26462642
this,
26472643
&HotcueControl::slotHotcuePositionChangeRequest,
@@ -2655,6 +2651,10 @@ HotcueControl::HotcueControl(const QString& group, int hotcueIndex)
26552651
&HotcueControl::slotHotcueEndPositionChanged,
26562652
Qt::DirectConnection);
26572653
m_hotcueEndPosition->set(Cue::kNoPosition);
2654+
m_hotcueEndPosition->connectValueChangeRequest(
2655+
this,
2656+
&HotcueControl::slotHotcueEndPositionChangeRequest,
2657+
Qt::DirectConnection);
26582658

26592659
m_pHotcueStatus = std::make_unique<ControlObject>(keyForControl(QStringLiteral("status")));
26602660
m_pHotcueStatus->setReadOnly();
@@ -2846,21 +2846,25 @@ void HotcueControl::slotHotcuePositionChanged(double newPosition) {
28462846
}
28472847

28482848
void HotcueControl::slotHotcuePositionChangeRequest(double newPosition) {
2849-
// Reject the change if no hotcue is set: the position can only be moved by
2850-
// dragging an existing hotcue, not to create one (see issue #10409).
2849+
// Reject the change if no hotcue is set
28512850
if (!m_pCue) {
28522851
return;
28532852
}
2854-
// Delegate to CueControl::hotcuePositionChanged(), which validates the
2855-
// position and moves the cue. The resulting cue change syncs the position
2856-
// control back to the confirmed value via loadCuesFromTrack().
28572853
emit hotcuePositionChanged(this, newPosition);
28582854
}
28592855

28602856
void HotcueControl::slotHotcueEndPositionChanged(double newEndPosition) {
28612857
emit hotcueEndPositionChanged(this, newEndPosition);
28622858
}
28632859

2860+
void HotcueControl::slotHotcueEndPositionChangeRequest(double newEndPosition) {
2861+
// Reject the change if no hotcue is set
2862+
if (!m_pCue) {
2863+
return;
2864+
}
2865+
emit hotcueEndPositionChanged(this, newEndPosition);
2866+
}
2867+
28642868
void HotcueControl::slotHotcueColorChangeRequest(double newColor) {
28652869
if (newColor < 0 || newColor > 0xFFFFFF) {
28662870
qWarning() << "slotHotcueColorChangeRequest got invalid value:" << newColor;
@@ -2925,14 +2929,13 @@ void HotcueControl::resetCue() {
29252929
}
29262930

29272931
void HotcueControl::setPosition(mixxx::audio::FramePos position) {
2928-
// Use setAndConfirm() because m_hotcuePosition is confirm-required: a plain
2929-
// set() would be intercepted as a change request instead of updating the
2930-
// engine-authoritative value.
2932+
// setAndConfirm() because m_hotcuePosition is confirm-required.
29312933
m_hotcuePosition->setAndConfirm(position.toEngineSamplePosMaybeInvalid());
29322934
}
29332935

29342936
void HotcueControl::setEndPosition(mixxx::audio::FramePos endPosition) {
2935-
m_hotcueEndPosition->set(endPosition.toEngineSamplePosMaybeInvalid());
2937+
// setAndConfirm() because m_hotcueEndPosition is confirm-required.
2938+
m_hotcueEndPosition->setAndConfirm(endPosition.toEngineSamplePosMaybeInvalid());
29362939
}
29372940

29382941
mixxx::CueType HotcueControl::getType() const {

src/engine/controls/cuecontrol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ class HotcueControl : public QObject {
139139
void slotHotcueEndPositionChanged(double newPosition);
140140
void slotHotcuePositionChanged(double newPosition);
141141
void slotHotcuePositionChangeRequest(double newPosition);
142+
void slotHotcueEndPositionChangeRequest(double newEndPosition);
142143
void slotHotcueColorChangeRequest(double newColor);
143144

144145
signals:

src/test/hotcuecontrol_test.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,74 @@ TEST_F(HotcueControlTest, PositionChangeRequestRejectedWhenUnset) {
281281
m_pHotcue1Status->get());
282282
}
283283

284+
TEST_F(HotcueControlTest, EndPositionChangeRequestRejectedWhenUnset) {
285+
createAndLoadFakeTrack();
286+
287+
// With no hotcue set the end position control must reject write requests and
288+
// stay unset instead of storing a bogus value (issue #10409).
289+
EXPECT_FALSE(mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
290+
m_pHotcue1EndPosition->get())
291+
.isValid());
292+
293+
m_pHotcue1EndPosition->set(mixxx::audio::FramePos(200).toEngineSamplePos());
294+
ProcessBuffer();
295+
296+
EXPECT_FALSE(mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
297+
m_pHotcue1EndPosition->get())
298+
.isValid());
299+
EXPECT_DOUBLE_EQ(static_cast<double>(HotcueControl::Status::Empty),
300+
m_pHotcue1Status->get());
301+
}
302+
303+
TEST_F(HotcueControlTest, EndPositionChangeRequestMovesSetLoop) {
304+
createAndLoadFakeTrack();
305+
306+
// Create a saved loop hotcue at [100, 200].
307+
constexpr mixxx::audio::FramePos loopStartPosition(100);
308+
constexpr mixxx::audio::FramePos loopEndPosition(200);
309+
m_pChannel1->getEngineBuffer()->setLoop(
310+
loopStartPosition, loopEndPosition, true);
311+
m_pHotcue1Set->set(1);
312+
m_pHotcue1Set->set(0);
313+
EXPECT_FRAMEPOS_EQ_CONTROL(loopEndPosition, m_pHotcue1EndPosition);
314+
315+
// Writing the end position control of a set loop hotcue must still move the
316+
// loop end (the confirm-required guard only rejects writes when unset).
317+
constexpr mixxx::audio::FramePos newEndPosition(300);
318+
m_pHotcue1EndPosition->set(newEndPosition.toEngineSamplePos());
319+
ProcessBuffer();
320+
321+
EXPECT_FRAMEPOS_EQ_CONTROL(newEndPosition, m_pHotcue1EndPosition);
322+
}
323+
324+
TEST_F(HotcueControlTest, EndPositionChangeRequestClearsSetLoop) {
325+
createAndLoadFakeTrack();
326+
327+
// Create a saved loop hotcue at [100, 200].
328+
constexpr mixxx::audio::FramePos loopStartPosition(100);
329+
constexpr mixxx::audio::FramePos loopEndPosition(200);
330+
m_pChannel1->getEngineBuffer()->setLoop(
331+
loopStartPosition, loopEndPosition, true);
332+
m_pHotcue1Set->set(1);
333+
m_pHotcue1Set->set(0);
334+
EXPECT_FRAMEPOS_EQ_CONTROL(loopEndPosition, m_pHotcue1EndPosition);
335+
336+
// Writing Cue::kNoPosition to the end position of a set loop hotcue clears
337+
// the end and converts the loop back into a plain hotcue. This still routes
338+
// through the confirm-required handler because the hotcue is set.
339+
ControlProxy hotcueType(m_sGroup1, QStringLiteral("hotcue_1_type"));
340+
m_pHotcue1EndPosition->set(Cue::kNoPosition);
341+
ProcessBuffer();
342+
343+
EXPECT_FALSE(mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
344+
m_pHotcue1EndPosition->get())
345+
.isValid());
346+
EXPECT_DOUBLE_EQ(static_cast<double>(mixxx::CueType::HotCue),
347+
hotcueType.get());
348+
// The hotcue itself remains at its start position.
349+
EXPECT_FRAMEPOS_EQ_CONTROL(loopStartPosition, m_pHotcue1Position);
350+
}
351+
284352
TEST_F(HotcueControlTest, PositionChangeRequestMovesSetHotcue) {
285353
createAndLoadFakeTrack();
286354

0 commit comments

Comments
 (0)