1 //
2 // C++ Implementation: woverview
3 //
4 // Description:
5 //
6 //
7 // Author: Tue Haste Andersen <haste@diku.dk>, (C) 2003
8 //
9 // Copyright: See COPYING file that comes with this distribution
10 //
11 //
12
13 #include "woverview.h"
14
15 #include <QBrush>
16 #include <QMimeData>
17 #include <QMouseEvent>
18 #include <QPaintEvent>
19 #include <QPainter>
20 #include <QUrl>
21 #include <QtDebug>
22
23 #include "analyzer/analyzerprogress.h"
24 #include "control/controlobject.h"
25 #include "control/controlproxy.h"
26 #include "engine/engine.h"
27 #include "mixer/playermanager.h"
28 #include "moc_woverview.cpp"
29 #include "preferences/colorpalettesettings.h"
30 #include "track/track.h"
31 #include "util/color/color.h"
32 #include "util/compatibility.h"
33 #include "util/dnd.h"
34 #include "util/duration.h"
35 #include "util/math.h"
36 #include "util/painterscope.h"
37 #include "util/timer.h"
38 #include "waveform/waveform.h"
39 #include "waveform/waveformwidgetfactory.h"
40 #include "widget/controlwidgetconnection.h"
41 #include "wskincolor.h"
42
WOverview(const QString & group,PlayerManager * pPlayerManager,UserSettingsPointer pConfig,QWidget * parent)43 WOverview::WOverview(
44 const QString& group,
45 PlayerManager* pPlayerManager,
46 UserSettingsPointer pConfig,
47 QWidget* parent)
48 : WWidget(parent),
49 m_actualCompletion(0),
50 m_pixmapDone(false),
51 m_waveformPeak(-1.0),
52 m_diffGain(0),
53 m_devicePixelRatio(1.0),
54 m_group(group),
55 m_pConfig(pConfig),
56 m_endOfTrack(false),
57 m_bPassthroughEnabled(false),
58 m_pCueMenuPopup(make_parented<WCueMenuPopup>(pConfig, this)),
59 m_bShowCueTimes(true),
60 m_iPosSeconds(0),
61 m_bLeftClickDragging(false),
62 m_iPickupPos(0),
63 m_iPlayPos(0),
64 m_pHoveredMark(nullptr),
65 m_bTimeRulerActive(false),
66 m_orientation(Qt::Horizontal),
67 m_iLabelFontSize(10),
68 m_a(1.0),
69 m_b(0.0),
70 m_analyzerProgress(kAnalyzerProgressUnknown),
71 m_trackLoaded(false),
72 m_scaleFactor(1.0) {
73 m_endOfTrackControl = new ControlProxy(
74 m_group, "end_of_track", this, ControlFlag::NoAssertIfMissing);
75 m_endOfTrackControl->connectValueChanged(this, &WOverview::onEndOfTrackChange);
76 m_pRateRatioControl = new ControlProxy(
77 m_group, "rate_ratio", this, ControlFlag::NoAssertIfMissing);
78 // Needed to recalculate range durations when rate slider is moved without the deck playing
79 m_pRateRatioControl->connectValueChanged(
80 this, &WOverview::onRateRatioChange);
81 m_trackSampleRateControl = new ControlProxy(
82 m_group, "track_samplerate", this, ControlFlag::NoAssertIfMissing);
83 m_trackSamplesControl = new ControlProxy(m_group, "track_samples", this);
84 m_playpositionControl = new ControlProxy(
85 m_group, "playposition", this, ControlFlag::NoAssertIfMissing);
86 m_pPassthroughControl =
87 new ControlProxy(m_group, "passthrough", this, ControlFlag::NoAssertIfMissing);
88 m_pPassthroughControl->connectValueChanged(this, &WOverview::onPassthroughChange);
89 m_bPassthroughEnabled = static_cast<bool>(m_pPassthroughControl->get());
90
91 setAcceptDrops(true);
92
93 setMouseTracking(true);
94
95 connect(pPlayerManager, &PlayerManager::trackAnalyzerProgress,
96 this, &WOverview::onTrackAnalyzerProgress);
97
98 connect(m_pCueMenuPopup.get(), &WCueMenuPopup::aboutToHide, this, &WOverview::slotCueMenuPopupAboutToHide);
99
100 m_pPassthroughLabel = new QLabel(this);
101 m_pPassthroughLabel->setObjectName("PassthroughLabel");
102 m_pPassthroughLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
103 // Shown on the overview waveform when vinyl passthrough is enabled
104 m_pPassthroughLabel->setText(tr("Passthrough"));
105 m_pPassthroughLabel->hide();
106 QVBoxLayout *pPassthroughLayout = new QVBoxLayout(this);
107 pPassthroughLayout->setContentsMargins(0,0,0,0);
108 pPassthroughLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
109 pPassthroughLayout->addWidget(m_pPassthroughLabel);
110 setLayout(pPassthroughLayout);
111 }
112
setup(const QDomNode & node,const SkinContext & context)113 void WOverview::setup(const QDomNode& node, const SkinContext& context) {
114 m_scaleFactor = context.getScaleFactor();
115 m_signalColors.setup(node, context);
116
117 m_backgroundColor = m_signalColors.getBgColor();
118 m_axesColor = m_signalColors.getAxesColor();
119 m_playPosColor = m_signalColors.getPlayPosColor();
120 m_passthroughOverlayColor = m_signalColors.getPassthroughOverlayColor();
121 m_playedOverlayColor = m_signalColors.getPlayedOverlayColor();
122 m_lowColor = m_signalColors.getLowColor();
123 m_dimBrightThreshold = m_signalColors.getDimBrightThreshold();
124
125 m_labelBackgroundColor = context.selectColor(node, "LabelBackgroundColor");
126 if (!m_labelBackgroundColor.isValid()) {
127 m_labelBackgroundColor = m_backgroundColor;
128 m_labelBackgroundColor.setAlpha(255 / 2); // 0 == fully transparent
129 }
130
131 m_labelTextColor = context.selectColor(node, "LabelTextColor");
132 if (!m_labelTextColor.isValid()) {
133 m_labelTextColor = Qt::white;
134 }
135
136 bool okay = false;
137 int labelFontSize = context.selectInt(node, "LabelFontSize", &okay);
138 if (okay) {
139 m_iLabelFontSize = labelFontSize;
140 }
141
142 // Clear the background pixmap, if it exists.
143 m_backgroundPixmap = QPixmap();
144 m_backgroundPixmapPath = context.selectString(node, "BgPixmap");
145 if (!m_backgroundPixmapPath.isEmpty()) {
146 m_backgroundPixmap = *WPixmapStore::getPixmapNoCache(
147 context.makeSkinPath(m_backgroundPixmapPath),
148 m_scaleFactor);
149 }
150
151 m_endOfTrackColor = QColor(200, 25, 20);
152 const QString endOfTrackColorName = context.selectString(node, "EndOfTrackColor");
153 if (!endOfTrackColorName.isNull()) {
154 m_endOfTrackColor.setNamedColor(endOfTrackColorName);
155 m_endOfTrackColor = WSkinColor::getCorrectColor(m_endOfTrackColor);
156 }
157
158 // setup hotcues and cue and loop(s)
159 m_marks.setup(m_group, node, context, m_signalColors);
160
161 ColorPaletteSettings colorPaletteSettings(m_pConfig);
162 auto colorPalette = colorPaletteSettings.getHotcueColorPalette();
163 m_pCueMenuPopup->setColorPalette(colorPalette);
164
165 for (const auto& pMark: m_marks) {
166 if (pMark->isValid()) {
167 pMark->connectSamplePositionChanged(this,
168 &WOverview::onMarkChanged);
169 }
170 if (pMark->hasVisible()) {
171 pMark->connectVisibleChanged(this,
172 &WOverview::onMarkChanged);
173 }
174 }
175
176 QDomNode child = node.firstChild();
177 while (!child.isNull()) {
178 if (child.nodeName() == "MarkRange") {
179 m_markRanges.push_back(WaveformMarkRange(m_group, child, context, m_signalColors));
180 WaveformMarkRange& markRange = m_markRanges.back();
181
182 if (markRange.m_markEnabledControl) {
183 markRange.m_markEnabledControl->connectValueChanged(
184 this, &WOverview::onMarkRangeChange);
185 }
186 if (markRange.m_markVisibleControl) {
187 markRange.m_markVisibleControl->connectValueChanged(
188 this, &WOverview::onMarkRangeChange);
189 }
190 if (markRange.m_markStartPointControl) {
191 markRange.m_markStartPointControl->connectValueChanged(
192 this, &WOverview::onMarkRangeChange);
193 }
194 if (markRange.m_markEndPointControl) {
195 markRange.m_markEndPointControl->connectValueChanged(
196 this, &WOverview::onMarkRangeChange);
197 }
198 }
199 child = child.nextSibling();
200 }
201
202 QString orientationString = context.selectString(node, "Orientation").toLower();
203 if (orientationString == "vertical") {
204 m_orientation = Qt::Vertical;
205 } else {
206 m_orientation = Qt::Horizontal;
207 }
208
209 m_bShowCueTimes = context.selectBool(node, "ShowCueTimes", true);
210
211 //qDebug() << "WOverview : m_marks" << m_marks.size();
212 //qDebug() << "WOverview : m_markRanges" << m_markRanges.size();
213 if (!m_connections.isEmpty()) {
214 ControlParameterWidgetConnection* defaultConnection = m_connections.at(0);
215 if (defaultConnection) {
216 if (defaultConnection->getEmitOption() &
217 ControlParameterWidgetConnection::EMIT_DEFAULT) {
218 // ON_PRESS means here value change on mouse move during press
219 defaultConnection->setEmitOption(
220 ControlParameterWidgetConnection::EMIT_ON_RELEASE);
221 }
222 }
223 }
224
225 setFocusPolicy(Qt::NoFocus);
226 }
227
onConnectedControlChanged(double dParameter,double dValue)228 void WOverview::onConnectedControlChanged(double dParameter, double dValue) {
229 // this is connected via skin to "playposition"
230 Q_UNUSED(dValue);
231
232 // Calculate handle position. Clamp the value within 0-1 because that's
233 // all we represent with this widget.
234 dParameter = math_clamp(dParameter, 0.0, 1.0);
235
236 bool redraw = false;
237 int oldPos = m_iPlayPos;
238 m_iPlayPos = valueToPosition(dParameter);
239 if (oldPos != m_iPlayPos) {
240 redraw = true;
241 }
242
243 if (!m_bLeftClickDragging) {
244 // if not dragged the pick-up moves with the play position
245 m_iPickupPos = m_iPlayPos;
246 }
247
248 // In case the user is hovering a cue point or holding right click, the
249 // calculated time between the playhead and cue/cursor should be updated at
250 // least once per second, regardless of m_iPos which depends on the length
251 // of the widget.
252 int oldPositionSeconds = m_iPosSeconds;
253 m_iPosSeconds = static_cast<int>(dParameter * m_trackSamplesControl->get());
254 if ((m_bTimeRulerActive || m_pHoveredMark != nullptr) && oldPositionSeconds != m_iPosSeconds) {
255 redraw = true;
256 }
257
258 if (redraw) {
259 update();
260 }
261 }
262
slotWaveformSummaryUpdated()263 void WOverview::slotWaveformSummaryUpdated() {
264 //qDebug() << "WOverview::slotWaveformSummaryUpdated()";
265
266 TrackPointer pTrack(m_pCurrentTrack);
267 if (!pTrack) {
268 return;
269 }
270 m_pWaveform = pTrack->getWaveformSummary();
271 if (m_pWaveform) {
272 // If the waveform is already complete, just draw it.
273 if (m_pWaveform->getCompletion() == m_pWaveform->getDataSize()) {
274 m_actualCompletion = 0;
275 if (drawNextPixmapPart()) {
276 update();
277 }
278 }
279 } else {
280 // Null waveform pointer means waveform was cleared.
281 m_waveformSourceImage = QImage();
282 m_analyzerProgress = kAnalyzerProgressUnknown;
283 m_actualCompletion = 0;
284 m_waveformPeak = -1.0;
285 m_pixmapDone = false;
286
287 update();
288 }
289 }
290
onTrackAnalyzerProgress(TrackId trackId,AnalyzerProgress analyzerProgress)291 void WOverview::onTrackAnalyzerProgress(TrackId trackId, AnalyzerProgress analyzerProgress) {
292 if (!m_pCurrentTrack || (m_pCurrentTrack->getId() != trackId)) {
293 return;
294 }
295
296 bool updateNeeded = drawNextPixmapPart();
297 if (updateNeeded || (m_analyzerProgress != analyzerProgress)) {
298 m_analyzerProgress = analyzerProgress;
299 update();
300 }
301 }
302
slotTrackLoaded(TrackPointer pTrack)303 void WOverview::slotTrackLoaded(TrackPointer pTrack) {
304 Q_UNUSED(pTrack); // only used in DEBUG_ASSERT
305 DEBUG_ASSERT(m_pCurrentTrack == pTrack);
306 m_trackLoaded = true;
307 if (m_pCurrentTrack) {
308 updateCues(m_pCurrentTrack->getCuePoints());
309 }
310 update();
311 }
312
slotLoadingTrack(TrackPointer pNewTrack,TrackPointer pOldTrack)313 void WOverview::slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack) {
314 Q_UNUSED(pOldTrack); // only used in DEBUG_ASSERT
315 //qDebug() << this << "WOverview::slotLoadingTrack" << pNewTrack.get() << pOldTrack.get();
316 DEBUG_ASSERT(m_pCurrentTrack == pOldTrack);
317 if (m_pCurrentTrack != nullptr) {
318 disconnect(m_pCurrentTrack.get(),
319 &Track::waveformSummaryUpdated,
320 this,
321 &WOverview::slotWaveformSummaryUpdated);
322 }
323
324 m_waveformSourceImage = QImage();
325 m_analyzerProgress = kAnalyzerProgressUnknown;
326 m_actualCompletion = 0;
327 m_waveformPeak = -1.0;
328 m_pixmapDone = false;
329 m_trackLoaded = false;
330 m_endOfTrack = false;
331
332 if (pNewTrack) {
333 m_pCurrentTrack = pNewTrack;
334 m_pWaveform = pNewTrack->getWaveformSummary();
335
336 connect(pNewTrack.get(),
337 &Track::waveformSummaryUpdated,
338 this,
339 &WOverview::slotWaveformSummaryUpdated);
340 slotWaveformSummaryUpdated();
341 connect(pNewTrack.get(), &Track::cuesUpdated, this, &WOverview::receiveCuesUpdated);
342 } else {
343 m_pCurrentTrack.reset();
344 m_pWaveform.clear();
345 }
346 update();
347 }
348
onEndOfTrackChange(double v)349 void WOverview::onEndOfTrackChange(double v) {
350 //qDebug() << "WOverview::onEndOfTrackChange()" << v;
351 m_endOfTrack = v > 0.0;
352 update();
353 }
354
onMarkChanged(double v)355 void WOverview::onMarkChanged(double v) {
356 Q_UNUSED(v);
357 //qDebug() << "WOverview::onMarkChanged()" << v;
358 if (m_pCurrentTrack) {
359 updateCues(m_pCurrentTrack->getCuePoints());
360 update();
361 }
362 }
363
onMarkRangeChange(double v)364 void WOverview::onMarkRangeChange(double v) {
365 Q_UNUSED(v);
366 //qDebug() << "WOverview::onMarkRangeChange()" << v;
367 update();
368 }
369
onRateRatioChange(double v)370 void WOverview::onRateRatioChange(double v) {
371 Q_UNUSED(v);
372 update();
373 }
374
onPassthroughChange(double v)375 void WOverview::onPassthroughChange(double v) {
376 m_bPassthroughEnabled = static_cast<bool>(v);
377
378 if (!m_bPassthroughEnabled) {
379 slotWaveformSummaryUpdated();
380 }
381
382 // Always call this to trigger a repaint even if not track is loaded
383 update();
384 }
385
updateCues(const QList<CuePointer> & loadedCues)386 void WOverview::updateCues(const QList<CuePointer> &loadedCues) {
387 m_marksToRender.clear();
388 for (const CuePointer& currentCue : loadedCues) {
389 const WaveformMarkPointer pMark = m_marks.getHotCueMark(currentCue->getHotCue());
390
391 if (pMark != nullptr && pMark->isValid() && pMark->isVisible()
392 && pMark->getSamplePosition() != Cue::kNoPosition) {
393 QColor newColor = mixxx::RgbColor::toQColor(currentCue->getColor());
394 if (newColor != pMark->fillColor() || newColor != pMark->m_textColor) {
395 pMark->setBaseColor(newColor, m_dimBrightThreshold);
396 }
397
398 int hotcueNumber = currentCue->getHotCue();
399 if (currentCue->getType() == mixxx::CueType::HotCue && hotcueNumber != Cue::kNoHotCue) {
400 // Prepend the hotcue number to hotcues' labels
401 QString newLabel = currentCue->getLabel();
402 if (newLabel.isEmpty()) {
403 newLabel = QString::number(hotcueNumber + 1);
404 } else {
405 newLabel = QString("%1: %2").arg(hotcueNumber + 1).arg(newLabel);
406 }
407
408 if (pMark->m_text != newLabel) {
409 pMark->m_text = newLabel;
410 }
411 }
412
413 m_marksToRender.append(pMark);
414 }
415 }
416
417 // The loop above only adds WaveformMarks for hotcues to m_marksToRender.
418 for (const auto& pMark : m_marks) {
419 if (!m_marksToRender.contains(pMark) && pMark->isValid() && pMark->getSamplePosition() != Cue::kNoPosition && pMark->isVisible()) {
420 m_marksToRender.append(pMark);
421 }
422 }
423 std::sort(m_marksToRender.begin(), m_marksToRender.end());
424 }
425
426 // connecting the tracks cuesUpdated and onMarkChanged is not possible
427 // due to the incompatible signatures. This is a "wrapper" workaround
receiveCuesUpdated()428 void WOverview::receiveCuesUpdated() {
429 onMarkChanged(0);
430 }
431
mouseMoveEvent(QMouseEvent * e)432 void WOverview::mouseMoveEvent(QMouseEvent* e) {
433 if (m_bLeftClickDragging) {
434 if (m_orientation == Qt::Horizontal) {
435 m_iPickupPos = math_clamp(e->x(), 0, width() - 1);
436 } else {
437 m_iPickupPos = math_clamp(e->y(), 0, height() - 1);
438 }
439 }
440
441 // Do not activate cue hovering while right click is held down and the
442 // button down event was not on a cue.
443 if (m_bTimeRulerActive) {
444 // Prevent showing times beyond the boundaries of the track when the
445 // cursor is dragged outside this widget before releasing right click.
446 m_timeRulerPos.setX(math_clamp(e->pos().x(), 0, width()));
447 m_timeRulerPos.setY(math_clamp(e->pos().y(), 0, height()));
448 update();
449 return;
450 }
451
452 m_pHoveredMark.clear();
453
454 // Non-hotcue marks (intro/outro cues, main cue, loop in/out) are sorted
455 // before hotcues in m_marksToRender so if there is a hotcue in the same
456 // location, the hotcue gets rendered on top. When right clicking, the
457 // the hotcue rendered on top must be assigned to m_pHoveredMark to show
458 // the CueMenuPopup. To accomplish this, m_marksToRender is iterated in
459 // reverse and the loop breaks as soon as m_pHoveredMark is set.
460 for (int i = m_marksToRender.size() - 1; i >= 0; --i) {
461 WaveformMarkPointer pMark = m_marksToRender.at(i);
462 if (pMark->contains(e->pos(), m_orientation)) {
463 m_pHoveredMark = pMark;
464 break;
465 }
466 }
467
468 //qDebug() << "WOverview::mouseMoveEvent" << e->pos() << m_iPos;
469 update();
470 }
471
mouseReleaseEvent(QMouseEvent * e)472 void WOverview::mouseReleaseEvent(QMouseEvent* e) {
473 mouseMoveEvent(e);
474 //qDebug() << "WOverview::mouseReleaseEvent" << e->pos() << m_iPos << ">>" << dValue;
475
476 if (e->button() == Qt::LeftButton) {
477 if (m_bLeftClickDragging) {
478 m_iPlayPos = m_iPickupPos;
479 double dValue = positionToValue(m_iPickupPos);
480 setControlParameterUp(dValue);
481 m_bLeftClickDragging = false;
482 }
483 m_bTimeRulerActive = false;
484 } else if (e->button() == Qt::RightButton) {
485 // Do not seek when releasing a right click. This is important to
486 // prevent accidental seeking when trying to right click a hotcue.
487 m_bTimeRulerActive = false;
488 }
489 }
490
mousePressEvent(QMouseEvent * e)491 void WOverview::mousePressEvent(QMouseEvent* e) {
492 //qDebug() << "WOverview::mousePressEvent" << e->pos();
493 mouseMoveEvent(e);
494 if (m_pCurrentTrack == nullptr) {
495 return;
496 }
497 if (e->button() == Qt::LeftButton) {
498 if (m_orientation == Qt::Horizontal) {
499 m_iPickupPos = math_clamp(e->x(), 0, width() - 1);
500 } else {
501 m_iPickupPos = math_clamp(e->y(), 0, height() - 1);
502 }
503
504 if (m_pHoveredMark != nullptr) {
505 double dValue = m_pHoveredMark->getSamplePosition() / m_trackSamplesControl->get();
506 m_iPickupPos = valueToPosition(dValue);
507 m_iPlayPos = m_iPickupPos;
508 setControlParameterUp(dValue);
509 m_bLeftClickDragging = false;
510 } else {
511 m_bLeftClickDragging = true;
512 m_bTimeRulerActive = true;
513 m_timeRulerPos = e->pos();
514 }
515 } else if (e->button() == Qt::RightButton) {
516 if (m_bLeftClickDragging) {
517 m_iPickupPos = m_iPlayPos;
518 m_bLeftClickDragging = false;
519 m_bTimeRulerActive = false;
520 } else if (m_pHoveredMark == nullptr) {
521 m_bTimeRulerActive = true;
522 m_timeRulerPos = e->pos();
523 } else if (m_pHoveredMark->getHotCue() != Cue::kNoHotCue) {
524 // Currently the only way WaveformMarks can be associated
525 // with their respective Cue objects is by using the hotcue
526 // number. If cues without assigned hotcue are drawn on
527 // WOverview in the future, another way to associate
528 // WaveformMarks with Cues will need to be implemented.
529 CuePointer pHoveredCue;
530 QList<CuePointer> cueList = m_pCurrentTrack->getCuePoints();
531 for (const auto& pCue : cueList) {
532 if (pCue->getHotCue() == m_pHoveredMark->getHotCue()) {
533 pHoveredCue = pCue;
534 break;
535 }
536 }
537 if (pHoveredCue != nullptr) {
538 if (e->modifiers().testFlag(Qt::ShiftModifier)) {
539 m_pCurrentTrack->removeCue(pHoveredCue);
540 return;
541 } else {
542 m_pCueMenuPopup->setTrackAndCue(m_pCurrentTrack, pHoveredCue);
543 m_pCueMenuPopup->popup(e->globalPos());
544 }
545 }
546 }
547 }
548 }
549
slotCueMenuPopupAboutToHide()550 void WOverview::slotCueMenuPopupAboutToHide() {
551 m_pHoveredMark.clear();
552 update();
553 }
554
leaveEvent(QEvent * pEvent)555 void WOverview::leaveEvent(QEvent* pEvent) {
556 Q_UNUSED(pEvent);
557 if (!m_pCueMenuPopup->isVisible()) {
558 m_pHoveredMark.clear();
559 }
560 m_bLeftClickDragging = false;
561 m_bTimeRulerActive = false;
562 update();
563 }
564
paintEvent(QPaintEvent * pEvent)565 void WOverview::paintEvent(QPaintEvent* pEvent) {
566 Q_UNUSED(pEvent);
567 ScopedTimer t("WOverview::paintEvent");
568
569 QPainter painter(this);
570 painter.fillRect(rect(), m_backgroundColor);
571
572 if (!m_backgroundPixmap.isNull()) {
573 painter.drawPixmap(rect(), m_backgroundPixmap);
574 }
575
576 if (m_pCurrentTrack) {
577 // Refer to util/ScopePainter.h to understand the semantics of
578 // ScopePainter.
579 drawEndOfTrackBackground(&painter);
580 drawAxis(&painter);
581 drawWaveformPixmap(&painter);
582 drawPlayedOverlay(&painter);
583 drawPlayPosition(&painter);
584 drawEndOfTrackFrame(&painter);
585 drawAnalyzerProgress(&painter);
586
587 double trackSamples = m_trackSamplesControl->get();
588 if (m_trackLoaded && trackSamples > 0) {
589 const float offset = 1.0f;
590 const auto gain = static_cast<CSAMPLE_GAIN>(length() - 2) /
591 static_cast<CSAMPLE_GAIN>(m_trackSamplesControl->get());
592
593 drawRangeMarks(&painter, offset, gain);
594 drawMarks(&painter, offset, gain);
595 drawPickupPosition(&painter);
596 drawTimeRuler(&painter);
597 drawMarkLabels(&painter, offset, gain);
598 }
599 }
600
601 if (m_bPassthroughEnabled) {
602 drawPassthroughOverlay(&painter);
603 m_pPassthroughLabel->show();
604 } else {
605 m_pPassthroughLabel->hide();
606 }
607 }
608
drawEndOfTrackBackground(QPainter * pPainter)609 void WOverview::drawEndOfTrackBackground(QPainter* pPainter) {
610 if (m_endOfTrack) {
611 PainterScope painterScope(pPainter);
612 pPainter->setOpacity(0.3);
613 pPainter->setBrush(m_endOfTrackColor);
614 pPainter->drawRect(rect().adjusted(1, 1, -2, -2));
615 }
616 }
617
drawAxis(QPainter * pPainter)618 void WOverview::drawAxis(QPainter* pPainter) {
619 PainterScope painterScope(pPainter);
620 pPainter->setPen(QPen(m_axesColor, m_scaleFactor));
621 if (m_orientation == Qt::Horizontal) {
622 pPainter->drawLine(0, height() / 2, width(), height() / 2);
623 } else {
624 pPainter->drawLine(width() / 2, 0, width() / 2, height());
625 }
626 }
627
drawWaveformPixmap(QPainter * pPainter)628 void WOverview::drawWaveformPixmap(QPainter* pPainter) {
629 WaveformWidgetFactory* widgetFactory = WaveformWidgetFactory::instance();
630 if (!m_waveformSourceImage.isNull()) {
631 PainterScope painterScope(pPainter);
632 float diffGain;
633 bool normalize = widgetFactory->isOverviewNormalized();
634 if (normalize && m_pixmapDone && m_waveformPeak > 1) {
635 diffGain = 255 - m_waveformPeak - 1;
636 } else {
637 const auto visualGain = static_cast<float>(
638 widgetFactory->getVisualGain(WaveformWidgetFactory::All));
639 diffGain = 255.0f - (255.0f / visualGain);
640 }
641
642 if (m_diffGain != diffGain || m_waveformImageScaled.isNull()) {
643 QRect sourceRect(0,
644 static_cast<int>(diffGain),
645 m_waveformSourceImage.width(),
646 m_waveformSourceImage.height() -
647 2 * static_cast<int>(diffGain));
648 QImage croppedImage = m_waveformSourceImage.copy(sourceRect);
649 if (m_orientation == Qt::Vertical) {
650 // Rotate pixmap
651 croppedImage = croppedImage.transformed(QTransform(0, 1, 1, 0, 0, 0));
652 }
653 m_waveformImageScaled = croppedImage.scaled(size() * m_devicePixelRatio,
654 Qt::IgnoreAspectRatio,
655 Qt::SmoothTransformation);
656 m_diffGain = diffGain;
657 }
658
659 pPainter->drawImage(rect(), m_waveformImageScaled);
660 }
661 }
662
drawPlayedOverlay(QPainter * pPainter)663 void WOverview::drawPlayedOverlay(QPainter* pPainter) {
664 // Overlay the played part of the overview-waveform with a skin defined color
665 if (!m_waveformSourceImage.isNull() && m_playedOverlayColor.alpha() > 0) {
666 if (m_orientation == Qt::Vertical) {
667 pPainter->fillRect(0,
668 0,
669 m_waveformImageScaled.width(),
670 m_iPlayPos,
671 m_playedOverlayColor);
672 } else {
673 pPainter->fillRect(0,
674 0,
675 m_iPlayPos,
676 m_waveformImageScaled.height(),
677 m_playedOverlayColor);
678 }
679 }
680 }
681
drawPlayPosition(QPainter * pPainter)682 void WOverview::drawPlayPosition(QPainter* pPainter) {
683 // When the position line is currently dragged with the left mouse button
684 // draw a thin line -without triangles or shadow- at the playposition.
685 // The new playposition is drawn by drawPickupPosition()
686 if (m_bLeftClickDragging) {
687 PainterScope painterScope(pPainter);
688 QLineF line;
689 if (m_orientation == Qt::Horizontal) {
690 line.setLine(m_iPlayPos, 0.0, m_iPlayPos, height());
691 } else {
692 line.setLine(0.0, m_iPlayPos, width(), m_iPlayPos);
693 }
694 pPainter->setPen(QPen(m_playPosColor, m_scaleFactor));
695 pPainter->setOpacity(0.5);
696 pPainter->drawLine(line);
697 }
698 }
699
drawEndOfTrackFrame(QPainter * pPainter)700 void WOverview::drawEndOfTrackFrame(QPainter* pPainter) {
701 if (m_endOfTrack) {
702 PainterScope painterScope(pPainter);
703 pPainter->setOpacity(0.8);
704 pPainter->setPen(QPen(QBrush(m_endOfTrackColor), 1.5 * m_scaleFactor));
705 pPainter->setBrush(QColor(0, 0, 0, 0));
706 pPainter->drawRect(rect().adjusted(0, 0, -1, -1));
707 }
708 }
709
drawAnalyzerProgress(QPainter * pPainter)710 void WOverview::drawAnalyzerProgress(QPainter* pPainter) {
711 if ((m_analyzerProgress >= kAnalyzerProgressNone) &&
712 (m_analyzerProgress < kAnalyzerProgressDone)) {
713 PainterScope painterScope(pPainter);
714 pPainter->setPen(QPen(m_playPosColor, 3 * m_scaleFactor));
715
716 if (m_analyzerProgress > kAnalyzerProgressNone) {
717 if (m_orientation == Qt::Horizontal) {
718 pPainter->drawLine(QLineF(width() * m_analyzerProgress,
719 height() / 2,
720 width(),
721 height() / 2));
722 } else {
723 pPainter->drawLine(QLineF(width() / 2,
724 height() * m_analyzerProgress,
725 width() / 2,
726 height()));
727 }
728 }
729
730 if (m_analyzerProgress <= kAnalyzerProgressHalf) { // remove text after progress by wf is recognizable
731 if (m_trackLoaded) {
732 //: Text on waveform overview when file is playable but no waveform is visible
733 paintText(tr("Ready to play, analyzing..."), pPainter);
734 } else {
735 //: Text on waveform overview when file is cached from source
736 paintText(tr("Loading track..."), pPainter);
737 }
738 } else if (m_analyzerProgress >= kAnalyzerProgressFinalizing) {
739 //: Text on waveform overview during finalizing of waveform analysis
740 paintText(tr("Finalizing..."), pPainter);
741 }
742 } else if (!m_trackLoaded) {
743 // This happens if the track samples are not loaded, but we have
744 // a cached track
745 //: Text on waveform overview when file is cached from source
746 paintText(tr("Loading track..."), pPainter);
747 }
748 }
749
drawRangeMarks(QPainter * pPainter,const float & offset,const float & gain)750 void WOverview::drawRangeMarks(QPainter* pPainter, const float& offset, const float& gain) {
751 for (auto&& markRange : m_markRanges) {
752 if (!markRange.active() || !markRange.visible()) {
753 continue;
754 }
755
756 // Active mark ranges by definition have starts/ends that are not
757 // disabled.
758 const qreal startValue = markRange.start();
759 const qreal endValue = markRange.end();
760
761 const qreal startPosition = offset + startValue * gain;
762 const qreal endPosition = offset + endValue * gain;
763
764 if (startPosition < 0.0 && endPosition < 0.0) {
765 continue;
766 }
767
768 PainterScope painterScope(pPainter);
769
770 if (markRange.enabled()) {
771 pPainter->setOpacity(markRange.m_enabledOpacity);
772 pPainter->setPen(markRange.m_activeColor);
773 pPainter->setBrush(markRange.m_activeColor);
774 } else {
775 pPainter->setOpacity(markRange.m_disabledOpacity);
776 pPainter->setPen(markRange.m_disabledColor);
777 pPainter->setBrush(markRange.m_disabledColor);
778 }
779
780 // let top and bottom of the rect out of the widget
781 if (m_orientation == Qt::Horizontal) {
782 pPainter->drawRect(QRectF(QPointF(startPosition, -2.0),
783 QPointF(endPosition, height() + 1.0)));
784 } else {
785 pPainter->drawRect(QRectF(QPointF(-2.0, startPosition),
786 QPointF(width() + 1.0, endPosition)));
787 }
788 }
789 }
790
drawMarks(QPainter * pPainter,const float offset,const float gain)791 void WOverview::drawMarks(QPainter* pPainter, const float offset, const float gain) {
792 QFont markerFont = pPainter->font();
793 markerFont.setPixelSize(static_cast<int>(m_iLabelFontSize * m_scaleFactor));
794 QFontMetricsF fontMetrics(markerFont);
795
796 // Text labels are rendered so they do not overlap with other WaveformMarks'
797 // labels. If the text would be too wide, it is elided. However, the user
798 // can hover the mouse cursor over a label to show the whole label text,
799 // temporarily hiding any following labels that would be drawn over it.
800 // This requires looping over the WaveformMarks twice and the marks must be
801 // sorted in the order they appear on the waveform.
802 // In the first loop, the lines are drawn and the text to render plus its
803 // location are calculated then stored in a WaveformMarkLabel. The text must
804 // be drawn in the second loop to prevent the lines of following
805 // WaveformMarks getting drawn over it. The second loop is in the separate
806 // drawMarkLabels function so it can be called after drawCurrentPosition so
807 // the view of labels is not obscured by the playhead.
808
809 bool markHovered = false;
810 for (int i = 0; i < m_marksToRender.size(); ++i) {
811 WaveformMarkPointer pMark = m_marksToRender.at(i);
812 PainterScope painterScope(pPainter);
813
814 const float markPosition = math_clamp(
815 offset + static_cast<float>(m_marksToRender.at(i)->getSamplePosition()) * gain,
816 0.0f,
817 static_cast<float>(width()));
818 pMark->m_linePosition = markPosition;
819
820 QLineF line;
821 QLineF bgLine;
822 if (m_orientation == Qt::Horizontal) {
823 line.setLine(markPosition, 0.0, markPosition, height());
824 bgLine.setLine(markPosition - 1.0, 0.0, markPosition - 1.0, height());
825 } else {
826 line.setLine(0.0, markPosition, width(), markPosition);
827 bgLine.setLine(0.0, markPosition - 1.0, width(), markPosition - 1.0);
828 }
829
830 pPainter->setPen(pMark->borderColor());
831 pPainter->drawLine(bgLine);
832
833 pPainter->setPen(pMark->fillColor());
834 pPainter->drawLine(line);
835
836 if (!pMark->m_text.isEmpty()) {
837 Qt::Alignment halign = pMark->m_align & Qt::AlignHorizontal_Mask;
838 Qt::Alignment valign = pMark->m_align & Qt::AlignVertical_Mask;
839
840 QString text = pMark->m_text;
841
842 // Only allow the text to overlap the following mark if the mouse is
843 // hovering over it. Elide it if it would render over the next
844 // label, but do not elide it if the next mark's label is not at the
845 // same vertical position.
846 if (pMark != m_pHoveredMark && i < m_marksToRender.size() - 1) {
847 float nextMarkPosition = -1.0f;
848 for (int m = i + 1; m < m_marksToRender.size() - 1; ++m) {
849 WaveformMarkPointer otherMark = m_marksToRender.at(m);
850 bool otherAtSameHeight = valign == (otherMark->m_align & Qt::AlignVertical_Mask);
851 // Hotcues always show at least their number.
852 bool otherHasLabel = !otherMark->m_text.isEmpty() || otherMark->getHotCue() != Cue::kNoHotCue;
853 if (otherAtSameHeight && otherHasLabel) {
854 nextMarkPosition = offset +
855 static_cast<float>(
856 otherMark->getSamplePosition()) *
857 gain;
858 break;
859 }
860 }
861 if (nextMarkPosition != -1.0) {
862 text = fontMetrics.elidedText(text, Qt::ElideRight, nextMarkPosition - markPosition - 5);
863 }
864 }
865 // Sometimes QFontMetrics::elidedText turns the QString into just an
866 // ellipsis character, so always show at least the hotcue number if
867 // the label does not fit.
868 if ((text.isEmpty() || text == "…") && pMark->getHotCue() != Cue::kNoHotCue) {
869 text = QString::number(pMark->getHotCue() + 1);
870 }
871
872 QRectF textRect = fontMetrics.boundingRect(text);
873 QPointF textPoint;
874 if (m_orientation == Qt::Horizontal) {
875 if (halign == Qt::AlignLeft) {
876 textPoint.setX(markPosition - textRect.width() - 5.5);
877 } else if (halign == Qt::AlignHCenter) {
878 textPoint.setX(markPosition - textRect.width() / 2);
879 } else { // AlignRight
880 textPoint.setX(markPosition + 1.5);
881 }
882
883 if (valign == Qt::AlignTop) {
884 textPoint.setY(fontMetrics.height());
885 } else if (valign == Qt::AlignVCenter) {
886 textPoint.setY((textRect.height() + height()) / 2);
887 } else { // AlignBottom
888 textPoint.setY(float(height()) - 0.5f);
889 }
890 } else { // Vertical
891 if (halign == Qt::AlignLeft) {
892 textPoint.setX(1.0f);
893 } else if (halign == Qt::AlignHCenter) {
894 textPoint.setX((width() - textRect.width()) / 2);
895 } else { // AlignRight
896 textPoint.setX(width() - textRect.width());
897 }
898
899 if (valign == Qt::AlignTop) {
900 textPoint.setY(markPosition - 1.0f);
901 } else if (valign == Qt::AlignVCenter) {
902 textPoint.setY(markPosition + textRect.height() / 2);
903 } else { // AlignBottom
904 textPoint.setY(markPosition + fontMetrics.ascent());
905 }
906 }
907
908 pMark->m_label.prerender(textPoint,
909 QPixmap(),
910 text,
911 markerFont,
912 m_labelTextColor,
913 m_labelBackgroundColor,
914 width(),
915 getDevicePixelRatioF(this));
916 }
917
918 // Show cue position when hovered
919 // The area it will be drawn in needs to be calculated here
920 // before drawMarkLabels so drawMarkLabels can avoid drawing
921 // labels over the cue position.
922 // This can happen for example if the user shows the cue position
923 // of a hotcue which is near the intro end position because the
924 // intro_end_position WaveformMark label is drawn at the top.
925 // However, the drawing of this text needs to happen in
926 // drawMarkLabels so none of the WaveformMark lines are drawn
927 // on top of the position text.
928
929 // WaveformMark::m_align refers to the alignment of the label,
930 // so if the label is on bottom draw the position text on top and
931 // vice versa.
932 if (pMark == m_pHoveredMark) {
933 Qt::Alignment valign = pMark->m_align & Qt::AlignVertical_Mask;
934 QPointF positionTextPoint(markPosition + 1.5, 0);
935 if (valign == Qt::AlignTop) {
936 positionTextPoint.setY(float(height()) - 0.5f);
937 } else {
938 positionTextPoint.setY(fontMetrics.height());
939 }
940
941 double markSamples = pMark->getSamplePosition();
942 double trackSamples = m_trackSamplesControl->get();
943 double currentPositionSamples = m_playpositionControl->get() * trackSamples;
944 double markTime = samplePositionToSeconds(markSamples);
945 double markTimeRemaining = samplePositionToSeconds(trackSamples - markSamples);
946 double markTimeDistance = samplePositionToSeconds(markSamples - currentPositionSamples);
947 QString cuePositionText = mixxx::Duration::formatTime(markTime) + " -" +
948 mixxx::Duration::formatTime(markTimeRemaining);
949 QString cueTimeDistanceText = mixxx::Duration::formatTime(fabs(markTimeDistance));
950 // Cast to int to avoid confusingly switching from -0:00 to 0:00 as
951 // the playhead passes the cue
952 if (static_cast<int>(markTimeDistance) < 0) {
953 cueTimeDistanceText = "-" + cueTimeDistanceText;
954 }
955
956 m_cuePositionLabel.prerender(positionTextPoint,
957 QPixmap(),
958 cuePositionText,
959 markerFont,
960 m_labelTextColor,
961 m_labelBackgroundColor,
962 width(),
963 getDevicePixelRatioF(this));
964
965 QPointF timeDistancePoint(positionTextPoint.x(),
966 (fontMetrics.height() + height()) / 2);
967
968 m_cueTimeDistanceLabel.prerender(timeDistancePoint,
969 QPixmap(),
970 cueTimeDistanceText,
971 markerFont,
972 m_labelTextColor,
973 m_labelBackgroundColor,
974 width(),
975 getDevicePixelRatioF(this));
976 markHovered = true;
977 }
978 }
979 if (!markHovered) {
980 m_cuePositionLabel.clear();
981 m_cueTimeDistanceLabel.clear();
982 }
983 }
984
drawPickupPosition(QPainter * pPainter)985 void WOverview::drawPickupPosition(QPainter* pPainter) {
986 PainterScope painterScope(pPainter);
987
988 if (m_orientation == Qt::Vertical) {
989 pPainter->setTransform(QTransform(0, 1, 1, 0, 0, 0));
990 }
991
992 // draw dark play position outlines
993 pPainter->setPen(QPen(QBrush(m_backgroundColor), m_scaleFactor));
994 pPainter->setOpacity(0.5);
995 pPainter->drawLine(m_iPickupPos + 1, 0, m_iPickupPos + 1, breadth());
996 pPainter->drawLine(m_iPickupPos - 1, 0, m_iPickupPos - 1, breadth());
997
998 // draw colored play position line
999 pPainter->setPen(QPen(m_playPosColor, m_scaleFactor));
1000 pPainter->setOpacity(1.0);
1001 pPainter->drawLine(m_iPickupPos, 0, m_iPickupPos, breadth());
1002
1003 // draw triangle at the top
1004 pPainter->drawLine(m_iPickupPos - 2, 0, m_iPickupPos, 2);
1005 pPainter->drawLine(m_iPickupPos, 2, m_iPickupPos + 2, 0);
1006 pPainter->drawLine(m_iPickupPos - 2, 0, m_iPickupPos + 2, 0);
1007
1008 // draw triangle at the bottom
1009 pPainter->drawLine(m_iPickupPos - 2, breadth() - 1, m_iPickupPos, breadth() - 3);
1010 pPainter->drawLine(m_iPickupPos, breadth() - 3, m_iPickupPos + 2, breadth() - 1);
1011 pPainter->drawLine(m_iPickupPos - 2, breadth() - 1, m_iPickupPos + 2, breadth() - 1);
1012 }
1013
drawTimeRuler(QPainter * pPainter)1014 void WOverview::drawTimeRuler(QPainter* pPainter) {
1015 QFont markerFont = pPainter->font();
1016 markerFont.setPixelSize(static_cast<int>(m_iLabelFontSize * m_scaleFactor));
1017 QFontMetricsF fontMetrics(markerFont);
1018
1019 QFont shadowFont = pPainter->font();
1020 shadowFont.setWeight(99);
1021 shadowFont.setPixelSize(static_cast<int>(m_iLabelFontSize * m_scaleFactor));
1022 QPen shadowPen(Qt::black, 2.5 * m_scaleFactor);
1023
1024 if (m_bTimeRulerActive) {
1025 if (!m_bLeftClickDragging) {
1026 QLineF line;
1027 if (m_orientation == Qt::Horizontal) {
1028 line.setLine(m_timeRulerPos.x(), 0.0, m_timeRulerPos.x(), height());
1029 } else {
1030 line.setLine(0.0, m_timeRulerPos.x(), width(), m_timeRulerPos.x());
1031 }
1032 pPainter->setPen(shadowPen);
1033 pPainter->drawLine(line);
1034
1035 pPainter->setPen(QPen(m_playPosColor, m_scaleFactor));
1036 pPainter->drawLine(line);
1037 }
1038
1039 QPointF textPoint = m_timeRulerPos;
1040 QPointF textPointDistance = m_timeRulerPos;
1041 qreal widgetPositionFraction;
1042 qreal padding = 1.0; // spacing between line and text
1043 if (m_orientation == Qt::Horizontal) {
1044 textPoint = QPointF(textPoint.x() + padding, fontMetrics.height());
1045 textPointDistance = QPointF(textPointDistance.x() + padding,
1046 (fontMetrics.height() + height()) / 2);
1047 widgetPositionFraction = m_timeRulerPos.x() / width();
1048 } else {
1049 textPoint.setX(0);
1050 textPointDistance.setX(0);
1051 widgetPositionFraction = m_timeRulerPos.y() / height();
1052 }
1053 qreal trackSamples = m_trackSamplesControl->get();
1054 qreal timePosition = samplePositionToSeconds(
1055 widgetPositionFraction * trackSamples);
1056 qreal timePositionTillEnd = samplePositionToSeconds(
1057 (1 - widgetPositionFraction) * trackSamples);
1058 qreal timeDistance = samplePositionToSeconds(
1059 (widgetPositionFraction - m_playpositionControl->get()) * trackSamples);
1060
1061 QString timeText = mixxx::Duration::formatTime(timePosition) + " -" + mixxx::Duration::formatTime(timePositionTillEnd);
1062
1063 m_timeRulerPositionLabel.prerender(textPoint,
1064 QPixmap(),
1065 timeText,
1066 markerFont,
1067 m_labelTextColor,
1068 m_labelBackgroundColor,
1069 width(),
1070 getDevicePixelRatioF(this));
1071 m_timeRulerPositionLabel.draw(pPainter);
1072
1073 QString timeDistanceText = mixxx::Duration::formatTime(fabs(timeDistance));
1074 // Cast to int to avoid confusingly switching from -0:00 to 0:00 as
1075 // the playhead passes the point
1076 if (static_cast<int>(timeDistance) < 0) {
1077 timeDistanceText = "-" + timeDistanceText;
1078 }
1079 m_timeRulerDistanceLabel.prerender(textPointDistance,
1080 QPixmap(),
1081 timeDistanceText,
1082 markerFont,
1083 m_labelTextColor,
1084 m_labelBackgroundColor,
1085 width(),
1086 getDevicePixelRatioF(this));
1087 m_timeRulerDistanceLabel.draw(pPainter);
1088 } else {
1089 m_timeRulerPositionLabel.clear();
1090 m_timeRulerDistanceLabel.clear();
1091 }
1092 }
1093
drawMarkLabels(QPainter * pPainter,const float offset,const float gain)1094 void WOverview::drawMarkLabels(QPainter* pPainter, const float offset, const float gain) {
1095 QFont markerFont = pPainter->font();
1096 markerFont.setPixelSize(static_cast<int>(m_iLabelFontSize * m_scaleFactor));
1097 QFontMetricsF fontMetrics(markerFont);
1098
1099 // Draw WaveformMark labels
1100 for (const auto& pMark : qAsConst(m_marksToRender)) {
1101 if (m_pHoveredMark != nullptr && pMark != m_pHoveredMark) {
1102 if (pMark->m_label.intersects(m_pHoveredMark->m_label)) {
1103 continue;
1104 }
1105 }
1106 if (m_bShowCueTimes &&
1107 (pMark->m_label.intersects(m_cuePositionLabel) || pMark->m_label.intersects(m_cueTimeDistanceLabel))) {
1108 continue;
1109 }
1110 if (pMark->m_label.intersects(m_timeRulerPositionLabel) || pMark->m_label.intersects(m_timeRulerDistanceLabel)) {
1111 continue;
1112 }
1113
1114 pMark->m_label.draw(pPainter);
1115 }
1116
1117 if (m_bShowCueTimes) {
1118 m_cuePositionLabel.draw(pPainter);
1119 m_cueTimeDistanceLabel.draw(pPainter);
1120 }
1121
1122 // draw duration of WaveformMarkRanges
1123 for (auto&& markRange : m_markRanges) {
1124 if (markRange.showDuration() && markRange.active() && markRange.visible()) {
1125 // Active mark ranges by definition have starts/ends that are not
1126 // disabled.
1127 const qreal startValue = markRange.start();
1128 const qreal endValue = markRange.end();
1129
1130 const qreal startPosition = offset + startValue * gain;
1131 const qreal endPosition = offset + endValue * gain;
1132
1133 if (startPosition < 0.0 && endPosition < 0.0) {
1134 continue;
1135 }
1136 QString duration = mixxx::Duration::formatTime(
1137 samplePositionToSeconds(endValue - startValue));
1138
1139 QRectF durationRect = fontMetrics.boundingRect(duration);
1140 qreal x;
1141
1142 WaveformMarkRange::DurationTextLocation textLocation = markRange.durationTextLocation();
1143 if (textLocation == WaveformMarkRange::DurationTextLocation::Before) {
1144 x = startPosition - durationRect.width() - 5.5;
1145 } else {
1146 x = endPosition + 1.5;
1147 }
1148
1149 QPointF durationBottomLeft(x, fontMetrics.height());
1150
1151 markRange.m_durationLabel.prerender(durationBottomLeft,
1152 QPixmap(),
1153 duration,
1154 markerFont,
1155 m_labelTextColor,
1156 m_labelBackgroundColor,
1157 width(),
1158 getDevicePixelRatioF(this));
1159
1160 if (!(markRange.m_durationLabel.intersects(m_cuePositionLabel) || markRange.m_durationLabel.intersects(m_cueTimeDistanceLabel) || markRange.m_durationLabel.intersects(m_timeRulerPositionLabel) || markRange.m_durationLabel.intersects(m_timeRulerDistanceLabel))) {
1161 markRange.m_durationLabel.draw(pPainter);
1162 }
1163 }
1164 }
1165 }
1166
drawPassthroughOverlay(QPainter * pPainter)1167 void WOverview::drawPassthroughOverlay(QPainter* pPainter) {
1168 if (!m_waveformSourceImage.isNull() && m_passthroughOverlayColor.alpha() > 0) {
1169 // Overlay the entire overview-waveform with a skin defined color
1170 pPainter->fillRect(rect(), m_passthroughOverlayColor);
1171 }
1172 }
1173
paintText(const QString & text,QPainter * pPainter)1174 void WOverview::paintText(const QString& text, QPainter* pPainter) {
1175 PainterScope painterScope(pPainter);
1176 m_lowColor.setAlphaF(0.5);
1177 QPen lowColorPen(
1178 QBrush(m_lowColor), 1.25 * m_scaleFactor, Qt::SolidLine, Qt::RoundCap);
1179 pPainter->setPen(lowColorPen);
1180 QFont font = pPainter->font();
1181 QFontMetrics fm(font);
1182
1183 // TODO: The following use of QFontMetrics::width(const QString&, int) const
1184 // is deprecated and should be replaced with
1185 // QFontMetrics::horizontalAdvance(const QString&, int) const. However, the
1186 // proposed alternative has just been introduced in Qt 5.11.
1187 // Until the minimum required Qt version of Mixxx is increased, we need a
1188 // version check here.
1189 #if (QT_VERSION < QT_VERSION_CHECK(5, 11, 0))
1190 int textWidth = fm.width(text);
1191 #else
1192 int textWidth = fm.horizontalAdvance(text);
1193 #endif
1194
1195 if (textWidth > length()) {
1196 qreal pointSize = font.pointSizeF();
1197 pointSize = pointSize * (length() - 5 * m_scaleFactor) / textWidth;
1198 if (pointSize < 6 * m_scaleFactor) {
1199 pointSize = 6 * m_scaleFactor;
1200 }
1201 font.setPointSizeF(pointSize);
1202 pPainter->setFont(font);
1203 }
1204 if (m_orientation == Qt::Vertical) {
1205 pPainter->setTransform(QTransform(0, 1, -1, 0, width(), 0));
1206 }
1207 pPainter->drawText(QPointF(10 * m_scaleFactor, 12 * m_scaleFactor), text);
1208 pPainter->resetTransform();
1209 }
1210
samplePositionToSeconds(double sample)1211 double WOverview::samplePositionToSeconds(double sample) {
1212 double trackTime = sample /
1213 (m_trackSampleRateControl->get() * mixxx::kEngineChannelCount);
1214 return trackTime / m_pRateRatioControl->get();
1215 }
1216
resizeEvent(QResizeEvent * pEvent)1217 void WOverview::resizeEvent(QResizeEvent* pEvent) {
1218 Q_UNUSED(pEvent);
1219 // Play-position potmeters range from 0 to 1 but they allow out-of-range
1220 // sets. This is to give VC access to the pre-roll area.
1221 const double kMaxPlayposRange = 1.0;
1222 const double kMinPlayposRange = 0.0;
1223
1224 // Values of zero and one in normalized space.
1225 const double zero = (0.0 - kMinPlayposRange) / (kMaxPlayposRange - kMinPlayposRange);
1226 const double one = (1.0 - kMinPlayposRange) / (kMaxPlayposRange - kMinPlayposRange);
1227
1228 // These coefficients convert between widget space and normalized value
1229 // space.
1230 m_a = (length() - 1) / (one - zero);
1231 m_b = zero * m_a;
1232
1233 m_devicePixelRatio = getDevicePixelRatioF(this);
1234
1235 m_waveformImageScaled = QImage();
1236 m_diffGain = 0;
1237 Init();
1238 }
1239
dragEnterEvent(QDragEnterEvent * pEvent)1240 void WOverview::dragEnterEvent(QDragEnterEvent* pEvent) {
1241 DragAndDropHelper::handleTrackDragEnterEvent(pEvent, m_group, m_pConfig);
1242 }
dropEvent(QDropEvent * pEvent)1243 void WOverview::dropEvent(QDropEvent* pEvent) {
1244 DragAndDropHelper::handleTrackDropEvent(pEvent, *this, m_group, m_pConfig);
1245 }
1246