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