Skip to content

CueControl: reject hotcue position writes when unset - #16787

Open
gary-gdbsystems wants to merge 2 commits into
mixxxdj:mainfrom
gary-gdbsystems:hotcue-position-readonly
Open

CueControl: reject hotcue position writes when unset#16787
gary-gdbsystems wants to merge 2 commits into
mixxxdj:mainfrom
gary-gdbsystems:hotcue-position-readonly

Conversation

@gary-gdbsystems

@gary-gdbsystems gary-gdbsystems commented Jul 29, 2026

Copy link
Copy Markdown

Summary

Fixes #10409.

hotcue_X_position was a plain writable ControlObject, so a script or controller could write to it even when no hotcue was set. The underlying cue never moved — CueControl::hotcuePositionChanged() already rejects a null cue — but the raw set() still stored a bogus value in the control, which was then reported back on read.

Approach

Route external writes through connectValueChangeRequest, mirroring the existing hotcue_X_color pattern:

  • The new slotHotcuePositionChangeRequest() rejects the request unless a hotcue is set (guards on the m_pCue pointer).
  • Otherwise it delegates to the existing validated move path (CueControl::hotcuePositionChanged), so moving an existing hotcue still works; the resulting cue change syncs the control back to the cue's actual (validated) position via loadCuesFromTrack().
  • Because the control is now confirm-required, the engine-side sync in HotcueControl::setPosition() uses setAndConfirm().

This supersedes the earlier stalled attempt in #13446. It intentionally does not call setReadOnly() (which, combined with a request handler, would log a spurious is read-only. Ignoring warning on every legitimate hotcue move) and guards on m_pCue rather than the control value (the old !m_hotcuePosition->get() check misfires at position 0).

Changes

  • src/engine/controls/cuecontrol.cpp — wire the change-request handler, add slotHotcuePositionChangeRequest(), use setAndConfirm() in setPosition().
  • src/engine/controls/cuecontrol.h — slot declaration.
  • src/test/hotcuecontrol_test.cpp — two tests: a write is rejected when no hotcue is set; a write moves a set hotcue.

Testing

  • HotcueControlTest (32 tests) and all cue/loop suites (113 tests) pass.
  • clang-format, clang-tidy, and the other pre-commit hooks are clean.

The hotcue_X_position control was a plain writable ControlObject, so a
script or controller could write to it even when no hotcue was set. The
underlying cue never moved (CueControl::hotcuePositionChanged rejects a
null cue), but the raw set() still left a bogus value in the control,
which was then reported back on read.

Route external writes through connectValueChangeRequest, mirroring the
existing hotcue_X_color pattern. The new handler rejects the request
unless a hotcue is set, otherwise it delegates to the existing validated
move path; the resulting cue change syncs the control back to the cue's
actual position. Because the control is now confirm-required, engine-side
updates use setAndConfirm().

Note: unlike the earlier approach in mixxxdj#13446, this does not call
setReadOnly() (which would log a spurious "is read-only" warning on every
legitimate move) and guards on the m_pCue pointer rather than the control
value (which misfires at position 0).

Resolves the request in mixxxdj#10409.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ronso0 ronso0 added the AI The issue or PR makes heavy use of AI, which requires extra attention and effort to review label Jul 29, 2026

@cr7pt0gr4ph7 cr7pt0gr4ph7 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core logic looks good, but should also be extended to cover hotcue end position.

Comment thread src/engine/controls/cuecontrol.cpp Outdated
Comment on lines +2928 to +2931
// Use setAndConfirm() because m_hotcuePosition is confirm-required: a plain
// set() would be intercepted as a change request instead of updating the
// engine-authoritative value.
m_hotcuePosition->setAndConfirm(position.toEngineSamplePosMaybeInvalid());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verbose comment can be removed:

Suggested change
// Use setAndConfirm() because m_hotcuePosition is confirm-required: a plain
// set() would be intercepted as a change request instead of updating the
// engine-authoritative value.
m_hotcuePosition->setAndConfirm(position.toEngineSamplePosMaybeInvalid());
m_hotcuePosition->setAndConfirm(position.toEngineSamplePosMaybeInvalid());

Comment thread src/engine/controls/cuecontrol.cpp Outdated
Comment on lines +2641 to +2644
// The position may only be moved by dragging an existing hotcue, so route
// write requests through a handler that rejects them unless the hotcue is
// set. This also makes the control confirm-required, so engine-side updates
// must use setAndConfirm() (see HotcueControl::setPosition()).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verbose comment can be removed:

Suggested change
// The position may only be moved by dragging an existing hotcue, so route
// write requests through a handler that rejects them unless the hotcue is
// set. This also makes the control confirm-required, so engine-side updates
// must use setAndConfirm() (see HotcueControl::setPosition()).

Comment on lines 2650 to 2656
m_hotcueEndPosition = std::make_unique<ControlObject>(
keyForControl(QStringLiteral("endposition")));
connect(m_hotcueEndPosition.get(),
&ControlObject::valueChanged,
this,
&HotcueControl::slotHotcueEndPositionChanged,
Qt::DirectConnection);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hotcue end position should also reject writes when the hotcue is not set:

Suggested change
m_hotcueEndPosition = std::make_unique<ControlObject>(
keyForControl(QStringLiteral("endposition")));
connect(m_hotcueEndPosition.get(),
&ControlObject::valueChanged,
this,
&HotcueControl::slotHotcueEndPositionChanged,
Qt::DirectConnection);
m_hotcuePosition->connectValueChangeRequest(
this,
&HotcueControl::slotHotcueEndPositionChangeRequest,
Qt::DirectConnection);

Comment thread src/engine/controls/cuecontrol.cpp Outdated
Comment on lines +2849 to +2850
// Reject the change if no hotcue is set: the position can only be moved by
// dragging an existing hotcue, not to create one (see issue #10409).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overly verbose comment can be reduced to:

Suggested change
// Reject the change if no hotcue is set: the position can only be moved by
// dragging an existing hotcue, not to create one (see issue #10409).
// Reject the change if no hotcue is set

}

void HotcueControl::slotHotcueEndPositionChanged(double newEndPosition) {
emit hotcueEndPositionChanged(this, newEndPosition);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hotcue end position should also reject writes if the hotcue is not set (and maybe also if the current hotcue type doesn't support an end position?):

Suggested change
emit hotcueEndPositionChanged(this, newEndPosition);
// Reject the change if no hotcue is set
if (!m_pCue) {
return;
}
emit hotcueEndPositionChanged(this, newEndPosition);

@@ -138,6 +138,7 @@ class HotcueControl : public QObject {
void slotHotcueSwap(double v);
void slotHotcueEndPositionChanged(double newPosition);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hotcue end position should also reject change requests:

Suggested change
void slotHotcueEndPositionChanged(double newPosition);
void slotHotcueEndPositionChanged(double newPosition);
void slotHotcueEndPositionChangeRequest(double newPosition);

.isValid());
}

TEST_F(HotcueControlTest, PositionChangeRequestRejectedWhenUnset) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be extended to also test that the hotcue end position rejects changes when not set.

Comment thread src/engine/controls/cuecontrol.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
m_hotcueEndPosition->setAndConfirm(endPosition.toEngineSamplePosMaybeInvalid());

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.
@gary-gdbsystems

Copy link
Copy Markdown
Author

Thanks. Extended the reject-when-unset guard to hotcue_endposition (same as position — rejects when no hotcue is set, and setEndPosition() now uses setAndConfirm()), and trimmed the comments.

Only saved loops have an end position, so I can also reject endposition writes for non-loop hotcues in a follow-up if preferred. I kept a one-line note explaining the setAndConfirm(); can remove it if you'd rather.

@cr7pt0gr4ph7

Copy link
Copy Markdown
Contributor

Only saved loops have an end position, so I can also reject endposition writes for non-loop hotcues in a follow-up if preferred. I kept a one-line note explaining the setAndConfirm(); can remove it if you'd rather.

Just for completeness: Jump hotcues also have an endposition, which is used to store the position that is being jumped to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI The issue or PR makes heavy use of AI, which requires extra attention and effort to review code quality engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

make 'hotcue_X_position' read-only and reject if hotcue doesn't exist

3 participants