diff --git a/res/images/cross_circle_red.svg b/res/images/cross_circle_red.svg
new file mode 100644
index 00000000000..300ea50059a
--- /dev/null
+++ b/res/images/cross_circle_red.svg
@@ -0,0 +1,5 @@
+
diff --git a/res/mixxx.qrc b/res/mixxx.qrc
index 8011fe39f94..257dc4cb8d6 100644
--- a/res/mixxx.qrc
+++ b/res/mixxx.qrc
@@ -2,7 +2,10 @@
../LICENSE
+
images/library/ic_library_drag_and_drop.svg
+
+ images/cross_circle_red.svg
images/library/ic_library_locked.svg
images/library/ic_library_unlocked.svg
diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp
index dd44cb74159..81a6c5a44b1 100644
--- a/src/widget/woverview.cpp
+++ b/src/widget/woverview.cpp
@@ -26,6 +26,12 @@
#include "widget/controlwidgetconnection.h"
#include "wskincolor.h"
+namespace {
+// Horizontal and vertical margin around the widget where we accept play pos dragging.
+constexpr int kDragOutsideLimitX = 100;
+constexpr int kDragOutsideLimitY = 50;
+} // anonymous namespace
+
WOverview::WOverview(
const QString& group,
PlayerManager* pPlayerManager,
@@ -50,6 +56,8 @@ WOverview::WOverview(
m_iPlayPos(0),
m_bTimeRulerActive(false),
m_orientation(Qt::Horizontal),
+ m_dragMarginH(kDragOutsideLimitX),
+ m_dragMarginV(kDragOutsideLimitY),
m_iLabelFontSize(10),
m_a(1.0),
m_b(0.0),
@@ -115,6 +123,12 @@ WOverview::WOverview(
this, &WOverview::onTrackAnalyzerProgress);
connect(m_pCueMenuPopup.get(), &WCueMenuPopup::aboutToHide, this, &WOverview::slotCueMenuPopupAboutToHide);
+
+ // Style the cursor we show when the cursor leaves the valid
+ // play pos dragging area.
+ QPixmap abortPixmap(32, 32);
+ abortPixmap.load(":/images/cross_circle_red.svg");
+ m_dragAbortCursor = QCursor(abortPixmap);
}
void WOverview::setup(const QDomNode& node, const SkinContext& context) {
@@ -226,6 +240,8 @@ void WOverview::setup(const QDomNode& node, const SkinContext& context) {
QString orientationString = context.selectString(node, "Orientation").toLower();
if (orientationString == "vertical") {
m_orientation = Qt::Vertical;
+ m_dragMarginH = kDragOutsideLimitY;
+ m_dragMarginV = kDragOutsideLimitX;
} else {
m_orientation = Qt::Horizontal;
}
@@ -417,11 +433,17 @@ void WOverview::onRateRatioChange(double v) {
void WOverview::onPassthroughChange(double v) {
m_bPassthroughEnabled = static_cast(v);
- if (!m_bPassthroughEnabled) {
+ if (m_bPassthroughEnabled) {
+ // Abort play position dragging
+ m_bLeftClickDragging = false;
+ m_bTimeRulerActive = false;
+ m_iPickupPos = m_iPlayPos;
+ unsetCursor();
+ } else {
slotWaveformSummaryUpdated();
}
- // Always call this to trigger a repaint even if not track is loaded
+ // Always call this to trigger a repaint even if no track is loaded
update();
}
@@ -487,18 +509,27 @@ void WOverview::receiveCuesUpdated() {
void WOverview::mouseMoveEvent(QMouseEvent* e) {
if (m_bLeftClickDragging) {
+ if (isPosInAllowedPosDragZone(e->pos())) {
+ m_bTimeRulerActive = true;
+ m_timeRulerPos = e->pos();
+ unsetCursor();
+ } else {
+ // Remove the time ruler to indicate dragging position is invalid,
+ // don't abort dragging!
+ m_iPickupPos = m_iPlayPos;
+ m_bTimeRulerActive = false;
+
+ setCursor(m_dragAbortCursor);
+ // Remember to restore cursor everywhere where we cancel dragging.
+ // Update immediately.
+ update();
+ return;
+ }
+
if (m_orientation == Qt::Horizontal) {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- m_iPickupPos = math_clamp(static_cast(e->position().x()), 0, width() - 1);
-#else
- m_iPickupPos = math_clamp(e->x(), 0, width() - 1);
-#endif
+ m_iPickupPos = math_clamp(e->pos().x(), 0, width() - 1);
} else {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- m_iPickupPos = math_clamp(static_cast(e->position().y()), 0, height() - 1);
-#else
- m_iPickupPos = math_clamp(e->y(), 0, height() - 1);
-#endif
+ m_iPickupPos = math_clamp(e->pos().y(), 0, height() - 1);
}
}
@@ -515,7 +546,7 @@ void WOverview::mouseMoveEvent(QMouseEvent* e) {
m_pHoveredMark = m_marks.findHoveredMark(e->pos(), m_orientation);
- //qDebug() << "WOverview::mouseMoveEvent" << e->pos() << m_iPos;
+ // qDebug() << "WOverview::mouseMoveEvent" << e->pos();
update();
}
@@ -523,16 +554,28 @@ void WOverview::mouseReleaseEvent(QMouseEvent* e) {
mouseMoveEvent(e);
if (m_bPassthroughEnabled) {
m_bLeftClickDragging = false;
+ // We may be dragging, and we may be outside the valid dragging area.
+ // If so, we've set the 'invalid drag' cursor. Restore the cursor now.
+ unsetCursor();
return;
}
//qDebug() << "WOverview::mouseReleaseEvent" << e->pos() << m_iPos << ">>" << dValue;
if (e->button() == Qt::LeftButton) {
if (m_bLeftClickDragging) {
- m_iPlayPos = m_iPickupPos;
- double dValue = positionToValue(m_iPickupPos);
- setControlParameterUp(dValue);
- m_bLeftClickDragging = false;
+ unsetCursor();
+ if (isPosInAllowedPosDragZone(e->pos())) {
+ m_iPlayPos = m_iPickupPos;
+ double dValue = positionToValue(m_iPickupPos);
+ setControlParameterUp(dValue);
+ m_bLeftClickDragging = false;
+ } else {
+ // Abort dragging if we are way outside the widget.
+ m_iPickupPos = m_iPlayPos;
+ m_bLeftClickDragging = false;
+ m_bTimeRulerActive = false;
+ return;
+ }
}
m_bTimeRulerActive = false;
} else if (e->button() == Qt::RightButton) {
@@ -547,6 +590,7 @@ void WOverview::mousePressEvent(QMouseEvent* e) {
mouseMoveEvent(e);
if (m_bPassthroughEnabled) {
m_bLeftClickDragging = false;
+ unsetCursor();
return;
}
double trackSamples = getTrackSamples();
@@ -555,17 +599,9 @@ void WOverview::mousePressEvent(QMouseEvent* e) {
}
if (e->button() == Qt::LeftButton) {
if (m_orientation == Qt::Horizontal) {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- m_iPickupPos = math_clamp(static_cast(e->position().x()), 0, width() - 1);
-#else
- m_iPickupPos = math_clamp(e->x(), 0, width() - 1);
-#endif
+ m_iPickupPos = math_clamp(e->pos().x(), 0, width() - 1);
} else {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- m_iPickupPos = math_clamp(static_cast(e->position().y()), 0, height() - 1);
-#else
- m_iPickupPos = math_clamp(e->y(), 0, height() - 1);
-#endif
+ m_iPickupPos = math_clamp(e->pos().y(), 0, height() - 1);
}
if (m_pHoveredMark != nullptr) {
@@ -584,6 +620,7 @@ void WOverview::mousePressEvent(QMouseEvent* e) {
m_iPickupPos = m_iPlayPos;
m_bLeftClickDragging = false;
m_bTimeRulerActive = false;
+ unsetCursor();
} else if (m_pHoveredMark == nullptr) {
m_bTimeRulerActive = true;
m_timeRulerPos = e->pos();
@@ -673,6 +710,7 @@ void WOverview::paintEvent(QPaintEvent* pEvent) {
if (m_bPassthroughEnabled) {
drawPassthroughOverlay(&painter);
m_pPassthroughLabel->show();
+ unsetCursor();
} else {
m_pPassthroughLabel->hide();
}
diff --git a/src/widget/woverview.h b/src/widget/woverview.h
index 242802d057f..a315bc35a6e 100644
--- a/src/widget/woverview.h
+++ b/src/widget/woverview.h
@@ -124,6 +124,15 @@ class WOverview : public WWidget, public TrackDropTarget {
return m_orientation == Qt::Horizontal ? height() : width();
}
+ inline bool isPosInAllowedPosDragZone(const QPoint pos) {
+ const QRect dragZone = rect().marginsAdded(QMargins(
+ m_dragMarginH,
+ m_dragMarginV,
+ m_dragMarginH,
+ m_dragMarginV));
+ return dragZone.contains(pos);
+ }
+
ConstWaveformPointer getWaveform() const {
return m_pWaveform;
}
@@ -164,6 +173,8 @@ class WOverview : public WWidget, public TrackDropTarget {
int m_iPlayPos;
bool m_bTimeRulerActive;
Qt::Orientation m_orientation;
+ int m_dragMarginH;
+ int m_dragMarginV;
int m_iLabelFontSize;
// Coefficient value-position linear transposition
@@ -211,10 +222,10 @@ class WOverview : public WWidget, public TrackDropTarget {
QColor m_lowColor;
int m_dimBrightThreshold;
parented_ptr m_pPassthroughLabel;
+ QCursor m_dragAbortCursor;
WaveformMarkSet m_marks;
std::vector m_markRanges;
WaveformMarkLabel m_cuePositionLabel;
WaveformMarkLabel m_cueTimeDistanceLabel;
-
};