1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Rosegarden
5 A MIDI and audio sequencer and musical notation editor.
6 Copyright 2000-2021 the Rosegarden development team.
7
8 Other copyrights also apply to some parts of this work. Please
9 see the AUTHORS file and individual file headers for details.
10
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License as
13 published by the Free Software Foundation; either version 2 of the
14 License, or (at your option) any later version. See the file
15 COPYING included with this distribution for more information.
16 */
17
18 #define RG_MODULE_STRING "[TempoRuler]"
19
20 #include "TempoRuler.h"
21
22 #include "misc/Debug.h"
23 #include "base/Composition.h"
24 #include "base/NotationTypes.h"
25 #include "base/RealTime.h"
26 #include "base/RulerScale.h"
27 #include "base/SnapGrid.h"
28 #include "document/RosegardenDocument.h"
getColour(double tempo)29 #include "document/CommandHistory.h"
30 #include "gui/dialogs/TempoDialog.h"
31 #include "gui/general/GUIPalette.h"
32 #include "gui/general/EditTempoController.h"
33 #include "gui/widgets/TextFloat.h"
34 #include "TempoColour.h"
35
36 #include <QColor>
37 #include <QCursor>
38 #include <QEvent>
39 #include <QFont>
40 #include <QFontMetrics>
41 #include <QIcon>
42 #include <QObject>
43 #include <QPainter>
44 #include <QPixmap>
45 #include <QPoint>
46 #include <QMenu>
47 #include <QRect>
48 #include <QSize>
49 #include <QString>
50 #include <QWidget>
51 #include <QAction>
52 #include <QEvent>
53 #include <QPaintEvent>
54 #include <QMouseEvent>
55
56
57
58 namespace Rosegarden
59 {
60
61 TempoRuler::TempoRuler(RulerScale *rulerScale,
62 RosegardenDocument *doc,
63 int height,
64 bool small,
65 bool Thorn) :
66 QWidget(nullptr),
67 m_height(height),
68 m_currentXOffset(0),
69 m_width( -1),
70 m_small(small),
71 m_illuminate( -1),
72 m_illuminatePoint(false),
73 m_illuminateTarget(false),
74 m_refreshLinesOnly(false),
75 m_dragVert(false),
76 m_dragTarget(false),
77 m_dragHoriz(false),
78 m_dragStartY(0),
79 m_dragStartX(0),
80 m_dragFine(false),
81 m_clickX(0),
82 m_dragStartTempo( -1),
83 m_dragStartTarget( -1),
84 m_dragOriginalTempo( -1),
85 m_dragOriginalTarget( -1),
86 m_composition(&doc->getComposition()),
87 m_rulerScale(rulerScale),
88 m_menu(nullptr),
89 m_editTempoController(EditTempoController::self()),
90 m_fontMetrics(m_boldFont),
91 m_Thorn(Thorn)
92 {
93 m_font.setPixelSize(m_height / 3);
94 m_boldFont.setPixelSize(m_height * 2 / 5);
95 m_boldFont.setBold(true);
96 m_fontMetrics = QFontMetrics(m_boldFont);
97
98 m_editTempoController->setDocument(doc); // in case self() just created it
99
100 QObject::connect
101 (CommandHistory::getInstance(), SIGNAL(commandExecuted()),
102 this, SLOT(update()));
103
104 createAction("insert_tempo_here", SLOT(slotInsertTempoHere()));
105 createAction("insert_tempo_at_pointer", SLOT(slotInsertTempoAtPointer()));
106 createAction("delete_tempo", SLOT(slotDeleteTempoChange()));
107 createAction("ramp_to_next", SLOT(slotRampToNext()));
108 createAction("unramp", SLOT(slotUnramp()));
109 createAction("edit_tempo", SLOT(slotEditTempo()));
110 createAction("edit_time_signature", SLOT(slotEditTimeSignature()));
111 createAction("edit_tempos", SLOT(slotEditTempos()));
112
113 setMouseTracking(false);
114 }
115
116 TempoRuler::~TempoRuler()
117 {
118 }
119
120 void
121 TempoRuler::slotScrollHoriz(int x)
122 {
123 // int w = width();
124 // int h = height();
125 // int dx = x - ( -m_currentXOffset);
126 m_currentXOffset = -x;
127
128 update();
129 }
130
131 void
132 TempoRuler::mousePressEvent(QMouseEvent *e)
133 {
134 if (e->button() == Qt::LeftButton) {
135
136 if (e->type() == QEvent::MouseButtonDblClick) {
137 timeT t = m_rulerScale->getTimeForX
138 (e->pos().x() - m_currentXOffset);
139 m_editTempoController->emitEditTempos(t);
140 return;
141 }
142
143 emit mousePress();
144
145 int x = e->pos().x() + 1;
146 int y = e->pos().y();
147 timeT t = m_rulerScale->getTimeForX(x - m_currentXOffset);
148 int tcn = m_composition->getTempoChangeNumberAt(t);
149
150 if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
151 return ;
152
153 std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
154 std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
155
156 m_dragStartY = y;
157 m_dragStartX = x;
158 m_dragStartTime = tc.first;
159 m_dragPreviousTime = m_dragStartTime;
160 m_dragStartTempo = tc.second;
161 m_dragStartTarget = tr.first ? tr.second : -1;
162 m_dragOriginalTempo = m_dragStartTempo;
163 m_dragOriginalTarget = m_dragStartTarget;
164 m_dragFine = ((e->modifiers() & Qt::ShiftModifier) != 0);
165
166 int px = m_rulerScale->getXForTime(tc.first) + m_currentXOffset;
167 if (x >= px && x < px + 5) {
168 m_dragHoriz = true;
169 m_dragVert = false;
170 setCursor(Qt::SplitHCursor);
171 } else {
172 timeT nt = m_composition->getEndMarker();
173 if (tcn < m_composition->getTempoChangeCount() - 1) {
174 nt = m_composition->getTempoChange(tcn + 1).first;
175 }
176 int nx = m_rulerScale->getXForTime(nt) + m_currentXOffset;
177 if (x > px + 5 && x > nx - 5) {
178 m_dragTarget = true;
179 setCursor(Qt::SizeVerCursor);
180 } else {
181 m_dragTarget = false;
182 setCursor(Qt::SplitVCursor);
183 }
184 m_dragVert = true;
185 m_dragHoriz = false;
186 }
187
188 } else if (e->button() == Qt::RightButton) {
189
190 m_clickX = e->pos().x();
191 if (!m_menu)
192 createMenu();
193 if (m_menu) {
194 // enable 'delete' action only if cursor is actually over a tempo change
195 // actionCollection()->action("delete_tempo")->setEnabled(m_illuminatePoint);
196 findAction("delete_tempo")->setEnabled(m_illuminatePoint);
197
198 m_menu->exec(QCursor::pos());
199 }
200
201 }
202 }
203
204 void
205 TempoRuler::mouseReleaseEvent(QMouseEvent *e)
206 {
207 emit mouseRelease();
208
209 if (m_dragVert) {
210
211 m_dragVert = false;
212 unsetCursor();
213
214 if (e->pos().x() < 0 || e->pos().x() >= width() ||
215 e->pos().y() < 0 || e->pos().y() >= height()) {
216 leaveEvent(nullptr);
217 }
218
219 // First we make a note of the values that we just set and
220 // restore the tempo to whatever it was previously, so that
221 // the undo for any following command will work correctly.
222 // Then we emit so that our user can issue the right command.
223
224 int tcn = m_composition->getTempoChangeNumberAt(m_dragStartTime);
225 std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
226 std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
227
228 if (tc.second != m_dragOriginalTempo) {
229 m_composition->addTempoAtTime(m_dragStartTime,
230 m_dragOriginalTempo,
231 m_dragOriginalTarget);
232 m_editTempoController->changeTempo(m_dragStartTime, tc.second,
233 tr.first ? tr.second : -1,
234 TempoDialog::AddTempo);
235 }
236
237 } else if (m_dragHoriz) {
238
239 m_dragHoriz = false;
240 unsetCursor();
241
242 if (e->pos().x() < 0 || e->pos().x() >= width() ||
243 e->pos().y() < 0 || e->pos().y() >= height()) {
244 leaveEvent(nullptr);
245 }
246
247 if (m_dragPreviousTime != m_dragStartTime) {
248
249 // As above, restore the original tempo and then emit a
250 // signal to ensure a proper command happens.
251
252 int tcn = m_composition->getTempoChangeNumberAt(m_dragPreviousTime);
253 m_composition->removeTempoChange(tcn);
254 m_composition->addTempoAtTime(m_dragStartTime,
255 m_dragStartTempo,
256 m_dragStartTarget);
257
258 m_editTempoController->moveTempo(m_dragStartTime, m_dragPreviousTime);
259 }
260 }
261 }
262
263 void
264 TempoRuler::mouseMoveEvent(QMouseEvent *e)
265 {
266 bool shiftPressed = ((e->modifiers() & Qt::ShiftModifier) != 0);
267
268 if (m_dragVert) {
269
270 if (shiftPressed != m_dragFine) {
271
272 m_dragFine = shiftPressed;
273 m_dragStartY = e->pos().y();
274
275 // reset the start tempi to whatever we last updated them
276 // to as we switch into or out of fine mode
277 int tcn = m_composition->getTempoChangeNumberAt(m_dragStartTime);
278 std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
279 std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
280 m_dragStartTempo = tc.second;
281 m_dragStartTarget = tr.first ? tr.second : -1;
282 }
283
284 int diff = m_dragStartY - e->pos().y(); // +ve for upwards drag
285 tempoT newTempo = m_dragStartTempo;
286 tempoT newTarget = m_dragStartTarget;
287
288 if (diff != 0) {
289
290 float qpm = m_composition->getTempoQpm(newTempo);
291
292 if (m_dragTarget && newTarget > 0) {
293 qpm = m_composition->getTempoQpm(newTarget);
294 }
295
296 float qdiff = (m_dragFine ? diff * 0.05 : diff * 0.5);
297 qpm += qdiff;
298 if (qpm < 1)
299 qpm = 1;
300
301 if (m_dragTarget) {
302
303 newTarget = m_composition->getTempoForQpm(qpm);
304
305 } else {
306
307 newTempo = m_composition->getTempoForQpm(qpm);
308
309 if (newTarget >= 0) {
310 qpm = m_composition->getTempoQpm(newTarget);
311 qpm += qdiff;
312 if (qpm < 1)
313 qpm = 1;
314 newTarget = m_composition->getTempoForQpm(qpm);
315 }
316 }
317 }
318
319 showTextFloat(newTempo, newTarget, m_dragStartTime);
320 m_composition->addTempoAtTime(m_dragStartTime, newTempo, newTarget);
321 update();
322
323 } else if (m_dragHoriz) {
324
325 int x = e->pos().x();
326
327 SnapGrid grid(m_rulerScale);
328 if (shiftPressed) {
329 grid.setSnapTime(SnapGrid::NoSnap);
330 } else {
331 grid.setSnapTime(SnapGrid::SnapToUnit);
332 }
333 timeT newTime = grid.snapX(x - m_currentXOffset,
334 SnapGrid::SnapEither);
335
336 int tcn = m_composition->getTempoChangeNumberAt(m_dragPreviousTime);
337 int ncn = m_composition->getTempoChangeNumberAt(newTime);
338 if (ncn > tcn || ncn < tcn - 1)
339 return ;
340 if (ncn >= 0 && ncn == tcn - 1) {
341 std::pair<timeT, tempoT> nc = m_composition->getTempoChange(ncn);
342 if (nc.first == newTime)
343 return ;
344 }
345
346 // std::cerr << " -> " << newTime << std::endl;
347
348 m_composition->removeTempoChange(tcn);
349 m_composition->addTempoAtTime(newTime,
350 m_dragStartTempo,
351 m_dragStartTarget);
352 showTextFloat(m_dragStartTempo, m_dragStartTarget, newTime, true);
353 m_dragPreviousTime = newTime;
354 update();
355
356 } else {
357
358 int x = e->pos().x() + 1;
359 timeT t = m_rulerScale->getTimeForX(x - m_currentXOffset);
360 int tcn = m_composition->getTempoChangeNumberAt(t);
361
362 if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
363 return ;
364
365 std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
366 std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
367
368 int bar, beat, fraction, remainder;
369 m_composition->getMusicalTimeForAbsoluteTime(tc.first, bar, beat,
370 fraction, remainder);
371 //RG_DEBUG << "Tempo change: tempo " << m_composition->getTempoQpm(tc.second) << " at " << bar << ":" << beat << ":" << fraction << ":" << remainder;
372
373 m_illuminate = tcn;
374 m_illuminatePoint = false;
375 m_illuminateTarget = false;
376 //!!! m_refreshLinesOnly = true;
377
378 //!!! merge this test with the one in mousePressEvent as
379 //isCloseToStart or equiv, and likewise for close to end
380
381 int px = m_rulerScale->getXForTime(tc.first) + m_currentXOffset;
382 if (x >= px && x < px + 5) {
383 m_illuminatePoint = true;
384 } else {
385 timeT nt = m_composition->getEndMarker();
386 if (tcn < m_composition->getTempoChangeCount() - 1) {
387 nt = m_composition->getTempoChange(tcn + 1).first;
388 }
389 int nx = m_rulerScale->getXForTime(nt) + m_currentXOffset;
390 if (x > px + 5 && x > nx - 5) {
391 m_illuminateTarget = true;
392 }
393
394 // std::cerr << "nt = " << nt << ", nx = " << nx << ", x = " << x << ", m_illuminateTarget = " << m_illuminateTarget << std::endl;
395 }
396
397 showTextFloat(tc.second, tr.first ? tr.second : -1,
398 tc.first, m_illuminatePoint);
399
400 update();
401 }
402 }
403
404 void
405 TempoRuler::wheelEvent(QWheelEvent */* e */)
406 {}
407
408 void
409 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
410 TempoRuler::enterEvent(QEnterEvent *)
411 #else
412 TempoRuler::enterEvent(QEvent *)
413 #endif
414 {
415 TextFloat::getTextFloat()->attach(this);
416 setMouseTracking(true);
417 }
418
419 void
420 TempoRuler::leaveEvent(QEvent *)
421 {
422 if (!m_dragVert && !m_dragHoriz) {
423 setMouseTracking(false);
424 m_illuminate = -1;
425 m_illuminatePoint = false;
426 //!!! m_refreshLinesOnly = true;
427 TextFloat::getTextFloat()->hide();
428
429 update();
430 }
431 }
432
433 void
434 TempoRuler::showTextFloat(tempoT tempo, tempoT target,
435 timeT time, bool showTime)
436 {
437 float qpm = m_composition->getTempoQpm(tempo);
438 int qi = int(qpm + 0.0001);
439 int q0 = int(qpm * 10 + 0.0001) % 10;
440 int q00 = int(qpm * 100 + 0.0001) % 10;
441
442 bool haveSet = false;
443
444 QString tempoText, timeText;
445
446 if (time >= 0) {
447
448 if (showTime) {
449 int bar, beat, fraction, remainder;
450 m_composition->getMusicalTimeForAbsoluteTime
451 (time, bar, beat, fraction, remainder);
452 RealTime rt = m_composition->getElapsedRealTime(time);
453
454 // bars in user space start at 1, not 0
455 bar++;
456
457 // blargh -- duplicated with TempoView::makeTimeString
458 timeText = QString("%1%2%3-%4%5-%6%7-%8%9")
459 .arg(bar / 100)
460 .arg((bar % 100) / 10)
461 .arg(bar % 10)
462 .arg(beat / 10)
463 .arg(beat % 10)
464 .arg(fraction / 10)
465 .arg(fraction % 10)
466 .arg(remainder / 10)
467 .arg(remainder % 10);
468
469 timeText = QString("%1\n%2")
470 .arg(timeText)
471 // .arg(rt.toString().c_str());
472 .arg(rt.toText(true).c_str());
473 }
474
475 TimeSignature sig =
476 m_composition->getTimeSignatureAt(time);
477
478 if (sig.getBeatDuration() !=
479 Note(Note::Crotchet).getDuration()) {
480
481 float bpm =
482 (qpm *
483 Note(Note::Crotchet).getDuration())
484 / sig.getBeatDuration();
485 int bi = int(bpm + 0.0001);
486 int b0 = int(bpm * 10 + 0.0001) % 10;
487 int b00 = int(bpm * 100 + 0.0001) % 10;
488
489 tempoText = tr("%1.%2%3 (%4.%5%6 bpm)")
490 .arg(qi).arg(q0).arg(q00)
491 .arg(bi).arg(b0).arg(b00);
492 haveSet = true;
493 }
494 }
495
496 if (!haveSet) {
497 tempoText = tr("%1.%2%3 bpm").arg(qi).arg(q0).arg(q00);
498 }
499
500 if (target > 0 && target != tempo) {
501 float tq = m_composition->getTempoQpm(target);
502 int tqi = int(tq + 0.0001);
503 int tq0 = int(tq * 10 + 0.0001) % 10;
504 int tq00 = int(tq * 100 + 0.0001) % 10;
505 tempoText = tr("%1 - %2.%3%4")
506 .arg(tempoText).arg(tqi).arg(tq0).arg(tq00);
507 }
508
509 TextFloat *textFloat = TextFloat::getTextFloat();
510
511 if (showTime && time >= 0) {
512 textFloat->setText(QString("%1\n%2").arg(timeText).arg(tempoText));
513 } else {
514 textFloat->setText(tempoText);
515 }
516
517 QPoint cp = mapFromGlobal(QPoint(QCursor::pos()));
518 // std::cerr << "cp = " << cp.x() << "," << cp.y() << ", tempo = " << qpm << std::endl;
519
520 QPoint offset = cp + QPoint(10, 25 - cp.y() - textFloat->height());
521 textFloat->display(offset);
522
523 }
524
525 QSize
526 TempoRuler::sizeHint() const
527 {
528 double width =
529 m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) +
530 m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar());
531
532 QSize res(std::max(int(width), m_width), m_height);
533
534 return res;
535 }
536
537 QSize
538 TempoRuler::minimumSizeHint() const
539 {
540 double firstBarWidth = m_rulerScale->getBarWidth(0);
541 QSize res = QSize(int(firstBarWidth), m_height);
542 return res;
543 }
544
545 int
546 TempoRuler::getYForTempo(tempoT tempo)
547 {
548 int drawh = height() - 4;
549 int y = drawh / 2;
550
551 tempoT minTempo = m_composition->getMinTempo();
552 tempoT maxTempo = m_composition->getMaxTempo();
553
554 if (maxTempo > minTempo) {
555 y = drawh -
556 int((double(tempo - minTempo) / double(maxTempo - minTempo))
557 * drawh + 0.5);
558 }
559
560 return y;
561 }
562
563 tempoT
564 TempoRuler::getTempoForY(int y)
565 {
566 int drawh = height() - 4;
567
568 tempoT minTempo = m_composition->getMinTempo();
569 tempoT maxTempo = m_composition->getMaxTempo();
570
571 tempoT tempo = minTempo;
572
573 if (maxTempo > minTempo) {
574 tempo = (maxTempo - minTempo) *
575 (double(drawh - y) / double(drawh)) + minTempo + 0.5;
576 }
577
578 return tempo;
579 }
580
581 void
582 TempoRuler::paintEvent(QPaintEvent* e)
583 {
584 QRect clipRect = e->rect();
585
586 if (m_buffer.width() < width() || m_buffer.height() < height()) {
587 m_buffer = QPixmap(width(), height());
588 }
589
590 // NEW: don't set the background for "null space" to the TextRulerBackground
591 // color, because this has always looked like crap. If we're not using
592 // Thorn, just leave the background at system default, because there's
593 // nothing in this part of the ruler that means anything anyway. If we're
594 // using Thorn, use a nice dark gray that just contrasts with the black
595 // horizontal line here
596 QColor kuller(0x40, 0x40, 0x40);
597 if (!m_Thorn) kuller = palette().window().color();
598 m_buffer.fill(kuller);
599
600 QPainter paint(&m_buffer);
601 paint.setPen(GUIPalette::getColour
602 (GUIPalette::TextRulerForeground));
603
604 paint.setClipRegion(e->region());
605 paint.setClipRect(clipRect);
606
607 timeT from = m_rulerScale->getTimeForX
608 (clipRect.x() - m_currentXOffset - 100);
609 timeT to = m_rulerScale->getTimeForX
610 (clipRect.x() + clipRect.width() - m_currentXOffset + 100);
611
612 QRect boundsForHeight = m_fontMetrics.boundingRect("019");
613 int fontHeight = boundsForHeight.height();
614 //int textY = fontHeight + 2;
615 // bmp text aligns better in temporuler now - is this font dependent?
616 int textY = fontHeight - 3;
617
618 double prevEndX = -1000.0;
619 double prevTempo = 0.0;
620 long prevBpm = 0;
621
622 typedef std::map<timeT, int> TimePoints;
623 int tempoChangeHere = 1;
624 int timeSigChangeHere = 2;
625 TimePoints timePoints;
626
627 for (int tempoNo = m_composition->getTempoChangeNumberAt(from);
628 tempoNo <= m_composition->getTempoChangeNumberAt(to) + 1; ++tempoNo) {
629
630 if (tempoNo >= 0 && tempoNo < m_composition->getTempoChangeCount()) {
631 timePoints.insert
632 (TimePoints::value_type
633 (m_composition->getTempoChange(tempoNo).first,
634 tempoChangeHere));
635 }
636 }
637
638 for (int sigNo = m_composition->getTimeSignatureNumberAt(from);
639 sigNo <= m_composition->getTimeSignatureNumberAt(to) + 1; ++sigNo) {
640
641 if (sigNo >= 0 && sigNo < m_composition->getTimeSignatureCount()) {
642 timeT time(m_composition->getTimeSignatureChange(sigNo).first);
643 if (timePoints.find(time) != timePoints.end()) {
644 timePoints[time] |= timeSigChangeHere;
645 } else {
646 timePoints.insert(TimePoints::value_type(time, timeSigChangeHere));
647 }
648 }
649 }
650
651 int lastx = 0, lasty = 0, lastx1 = 0;
652 bool haveSome = false;
653 // tempoT minTempo = m_composition->getMinTempo();
654 // tempoT maxTempo = m_composition->getMaxTempo();
655 bool illuminate = false;
656
657 if (m_illuminate >= 0) {
658 int tcn = m_composition->getTempoChangeNumberAt(from);
659 illuminate = (m_illuminate == tcn);
660 }
661
662 for (TimePoints::iterator i = timePoints.begin(); ; ++i) {
663
664 timeT t0, t1;
665
666 if (i == timePoints.begin()) {
667 t0 = from;
668 } else {
669 TimePoints::iterator j(i);
670 --j;
671 t0 = j->first;
672 }
673
674 if (i == timePoints.end()) {
675 t1 = to;
676 } else {
677 t1 = i->first;
678 }
679
680 if (t1 <= t0)
681 t1 = to;
682
683 int tcn = m_composition->getTempoChangeNumberAt(t0);
684 tempoT tempo = m_composition->getTempoAtTime(t0);
685
686 std::pair<bool, tempoT> ramping(false, tempo);
687 if (tcn > 0 && tcn < m_composition->getTempoChangeCount() + 1) {
688 ramping = m_composition->getTempoRamping(tcn - 1, true);
689 }
690
691 double x0, x1;
692 x0 = m_rulerScale->getXForTime(t0) + m_currentXOffset;
693 x1 = m_rulerScale->getXForTime(t1) + m_currentXOffset;
694 /*!!!
695 if (x0 > e->rect().x()) {
696 paint.fillRect(e->rect().x(), 0, x0 - e->rect().x(), height(),
697 paletteBackgroundColor());
698 }
699 */
700 QColor colour = TempoColour::getColour(m_composition->getTempoQpm(tempo));
701 paint.setPen(colour);
702 paint.setBrush(colour);
703
704 if (!m_refreshLinesOnly) {
705 // RG_DEBUG << "TempoRuler: draw rect from " << x0 << " to " << x1;
706 paint.drawRect(int(x0), 0, int(x1 - x0) + 1, height());
707 }
708
709 int y = getYForTempo(tempo);
710 /*!!!
711 int drawh = height() - 4;
712 int y = drawh / 2;
713 if (maxTempo > minTempo) {
714 y = drawh -
715 int((double(tempo - minTempo) / double(maxTempo - minTempo))
716 * drawh + 0.5);
717 }
718 */
719 y += 2;
720
721 if (haveSome) {
722
723 int x = int(x0) + 1;
724 int ry = lasty;
725
726 bool illuminateLine = (illuminate &&
727 !m_illuminatePoint && !m_illuminateTarget);
728
729 paint.setPen(illuminateLine ? QColor(Qt::white) : QColor(Qt::black));
730
731 if (ramping.first) {
732 ry = getYForTempo(ramping.second);
733 ry += 2;
734 /*!!!
735 ry = drawh -
736 int((double(ramping.second - minTempo) /
737 double(maxTempo - minTempo))
738 * drawh + 0.5);
739 */
740 }
741
742 paint.drawLine(lastx + 1, lasty, x - 2, ry);
743
744 if (!illuminateLine && illuminate && m_illuminateTarget) {
745 if (x > lastx) {
746 paint.setPen(QColor(Qt::white));
747 paint.drawLine(x - 6, ry - ((ry - lasty) * 6) / (x - lastx),
748 x - 2, ry);
749 }
750 }
751
752 if (m_illuminate >= 0) {
753 illuminate = (m_illuminate == tcn);
754 }
755
756 bool illuminatePoint = (illuminate && m_illuminatePoint);
757
758 paint.setPen(illuminatePoint ? QColor(Qt::white) : QColor(Qt::black));
759 paint.drawRect(x - 1, y - 1, 3, 3);
760
761 paint.setPen(illuminatePoint ? QColor(Qt::black) : QColor(Qt::white));
762 paint.drawPoint(x, y);
763 }
764
765 lastx = int(x0) + 1;
766 lastx1 = int(x1) + 1;
767 lasty = y;
768 if (i == timePoints.end())
769 break;
770 haveSome = true;
771 }
772
773 if (lastx1 < e->rect().x() + e->rect().width()) {
774 /*!!!
775 paint.fillRect(lastx1, 0,
776 e->rect().x() + e->rect().width() - lastx1, height(),
777 paletteBackgroundColor());
778 */
779 }
780
781 if (haveSome) {
782 bool illuminateLine = (illuminate && !m_illuminatePoint);
783 paint.setPen(illuminateLine ? QColor(Qt::white) : QColor(Qt::black));
784 paint.drawLine(lastx + 1, lasty, width(), lasty);
785 } else if (!m_refreshLinesOnly) {
786 tempoT tempo = m_composition->getTempoAtTime(from);
787 QColor colour = TempoColour::getColour(m_composition->getTempoQpm(tempo));
788 paint.setPen(colour);
789 paint.setBrush(colour);
790 paint.drawRect(e->rect());
791 }
792
793 paint.setPen(QColor(Qt::black));
794 paint.setBrush(QColor(Qt::black));
795 paint.drawLine(0, 0, width(), 0);
796
797 for (TimePoints::iterator i = timePoints.begin();
798 i != timePoints.end(); ++i) {
799
800 timeT time = i->first;
801 double x = m_rulerScale->getXForTime(time) + m_currentXOffset;
802
803 /*
804 paint.drawLine(static_cast<int>(x),
805 height() - (height()/4),
806 static_cast<int>(x),
807 height());
808 */
809
810 if ((i->second & timeSigChangeHere) && !m_refreshLinesOnly) {
811
812 TimeSignature sig =
813 m_composition->getTimeSignatureAt(time);
814
815 QString str = QString("%1/%2")
816 .arg(sig.getNumerator())
817 .arg(sig.getDenominator());
818
819 paint.setFont(m_boldFont);
820 paint.drawText(static_cast<int>(x) + 2, m_height - 2, str);
821 }
822
823 if ((i->second & tempoChangeHere) && !m_refreshLinesOnly) {
824
825 double tempo = m_composition->getTempoQpm(m_composition->getTempoAtTime(time));
826 long bpm = long(tempo);
827 // long frac = long(tempo * 100 + 0.001) - 100 * bpm;
828
829 QString tempoString = QString("%1").arg(bpm);
830
831 if (tempo == prevTempo) {
832 if (m_small)
833 continue;
834 tempoString = "=";
835 } else if (bpm == prevBpm) {
836 tempoString = (tempo > prevTempo ? "+" : "-");
837 } else {
838 if (m_small && (bpm != (bpm / 10 * 10))) {
839 if (bpm == prevBpm + 1)
840 tempoString = "+";
841 else if (bpm == prevBpm - 1)
842 tempoString = "-";
843 }
844 }
845 prevTempo = tempo;
846 prevBpm = bpm;
847
848 QRect bounds = m_fontMetrics.boundingRect(tempoString);
849
850 x += 3; // bmp text aligns better in temporuler now - is this font dependent?
851
852 paint.setFont(m_font);
853 if (time > 0)
854 x -= bounds.width() / 2;
855 // if (x > bounds.width() / 2) x -= bounds.width() / 2;
856 if (prevEndX >= x - 3)
857 x = prevEndX + 3;
858 paint.drawText(static_cast<int>(x), textY, tempoString);
859 prevEndX = x + bounds.width();
860 }
861 }
862
863 paint.end();
864
865 QPainter dbpaint(this);
866 // dbpaint.drawPixmap(0, 0, m_buffer);
867 dbpaint.drawPixmap(clipRect.x(), clipRect.y(),
868 m_buffer,
869 clipRect.x(), clipRect.y(),
870 clipRect.width(), clipRect.height());
871
872 dbpaint.end();
873
874 m_refreshLinesOnly = false;
875 }
876
877 void
878 TempoRuler::slotInsertTempoHere()
879 {
880 SnapGrid grid(m_rulerScale);
881 grid.setSnapTime(SnapGrid::SnapToUnit);
882 timeT t = grid.snapX(m_clickX - m_currentXOffset,
883 SnapGrid::SnapLeft);
884 tempoT tempo = Composition::getTempoForQpm(120.0);
885
886 int tcn = m_composition->getTempoChangeNumberAt(t);
887 if (tcn >= 0 && tcn < m_composition->getTempoChangeCount()) {
888 std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
889 if (tc.first == t)
890 return ;
891 tempo = tc.second;
892 }
893
894 m_editTempoController->changeTempo(t, tempo, -1, TempoDialog::AddTempo);
895 }
896
897 void
898 TempoRuler::slotInsertTempoAtPointer()
899 {
900 timeT t = m_composition->getPosition();
901 tempoT tempo = Composition::getTempoForQpm(120.0);
902
903 int tcn = m_composition->getTempoChangeNumberAt(t);
904 if (tcn >= 0 && tcn < m_composition->getTempoChangeCount()) {
905 std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
906 if (tc.first == t)
907 return ;
908 tempo = tc.second;
909 }
910
911 m_editTempoController->changeTempo(t, tempo, -1, TempoDialog::AddTempo);
912 }
913
914 void
915 TempoRuler::slotDeleteTempoChange()
916 {
917 timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset);
918 m_editTempoController->deleteTempoChange(t);
919 }
920
921 void
922 TempoRuler::slotRampToNext()
923 {
924 timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset);
925
926 int tcn = m_composition->getTempoChangeNumberAt(t);
927 if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
928 return ;
929
930 std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
931
932 m_editTempoController->changeTempo(tc.first, tc.second, 0, TempoDialog::AddTempo);
933 }
934
935 void
936 TempoRuler::slotUnramp()
937 {
938 timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset);
939
940 int tcn = m_composition->getTempoChangeNumberAt(t);
941 if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
942 return ;
943
944 std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
945
946 m_editTempoController->changeTempo(tc.first, tc.second, -1, TempoDialog::AddTempo);
947 }
948
949 void
950 TempoRuler::slotEditTempo()
951 {
952 const timeT atTime = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset);
953 m_editTempoController->editTempo(this, atTime);
954 }
955
956 void
957 TempoRuler::slotEditTimeSignature()
958 {
959 timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset);
960 m_editTempoController->editTimeSignature(this, t);
961 }
962
963 void
964 TempoRuler::slotEditTempos()
965 {
966 timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset);
967 m_editTempoController->emitEditTempos(t);
968 }
969
970 void
971 TempoRuler::createMenu()
972 {
973 createMenusAndToolbars("temporuler.rc");
974
975 m_menu = findChild<QMenu *>("tempo_ruler_menu");
976
977 // if (!tmp) {
978 // RG_DEBUG << "MarkerRuler::createMenu() menu not found\n"
979 // << domDocument().toString(4) << endl;
980 // }
981
982 if (!m_menu) {
983 RG_DEBUG << "TempoRuler::createMenu() failed\n";
984 }
985 }
986
987
988 } // namespace
989