/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */

/*
    Rosegarden
    A MIDI and audio sequencer and musical notation editor.
 
    This program is Copyright 2000-2007
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <richard.bown@ferventsoftware.com>
 
    The moral rights of Guillaume Laurent, Chris Cannam, and Richard
    Bown to claim authorship of this work have been asserted.
 
    Other copyrights also apply to some parts of this work.  Please
    see the AUTHORS file and individual file headers for details.
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/


#include "TempoRuler.h"

#include <klocale.h>
#include <kstddirs.h>
#include "misc/Debug.h"
#include "base/Composition.h"
#include "base/NotationTypes.h"
#include "base/RealTime.h"
#include "base/RulerScale.h"
#include "base/SnapGrid.h"
#include "document/RosegardenGUIDoc.h"
#include "document/MultiViewCommandHistory.h"
#include "gui/application/RosegardenGUIApp.h"
#include "gui/dialogs/TempoDialog.h"
#include "gui/general/GUIPalette.h"
#include "gui/widgets/TextFloat.h"
#include "TempoColour.h"
#include <kaction.h>
#include <kglobal.h>
#include <kxmlguiclient.h>
#include <kxmlguifactory.h>
#include <qcolor.h>
#include <qcursor.h>
#include <qevent.h>
#include <qfont.h>
#include <qfontmetrics.h>
#include <qiconset.h>
#include <qobject.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qpoint.h>
#include <qpopupmenu.h>
#include <qrect.h>
#include <qsize.h>
#include <qstring.h>
#include <qwidget.h>


namespace Rosegarden
{

TempoRuler::TempoRuler(RulerScale *rulerScale,
                       RosegardenGUIDoc *doc,
                       KXMLGUIFactory *factory,
                       double xorigin,
                       int height,
                       bool small,
                       QWidget *parent,
                       const char *name) :
        QWidget(parent, name),
        m_xorigin(xorigin),
        m_height(height),
        m_currentXOffset(0),
        m_width( -1),
        m_small(small),
        m_illuminate( -1),
        m_illuminatePoint(false),
        m_illuminateTarget(false),
        m_refreshLinesOnly(false),
        m_dragVert(false),
        m_dragTarget(false),
        m_dragHoriz(false),
        m_dragStartY(0),
        m_dragStartX(0),
        m_dragFine(false),
        m_clickX(0),
        m_dragStartTempo( -1),
        m_dragStartTarget( -1),
        m_dragOriginalTempo( -1),
        m_dragOriginalTarget( -1),
        m_composition(&doc->getComposition()),
        m_rulerScale(rulerScale),
        m_menu(0),
        m_factory(0),
        m_fontMetrics(m_boldFont)
{
    //    m_font.setPointSize(m_small ? 9 : 11);
    //    m_boldFont.setPointSize(m_small ? 9 : 11);

    //    m_font.setPixelSize(m_height * 2 / 3);
    //    m_boldFont.setPixelSize(m_height * 2 / 3);

    m_font.setPixelSize(m_height / 3);
    m_boldFont.setPixelSize(m_height * 2 / 5);
    m_boldFont.setBold(true);
    m_fontMetrics = QFontMetrics(m_boldFont);

    m_textFloat = new TextFloat(this);
    m_textFloat->hide();

    //    setBackgroundColor(GUIPalette::getColour(GUIPalette::TextRulerBackground));
    setBackgroundMode(Qt::NoBackground);

    QObject::connect
    (doc->getCommandHistory(), SIGNAL(commandExecuted()),
     this, SLOT(update()));

    m_menu = new QPopupMenu;

    QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
    QIconSet icon;

    icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-insert-tempo.png"));
    (new KAction(i18n("Insert Tempo Change"), icon, 0, this,
                 SLOT(slotInsertTempoHere()), actionCollection(),
                 "insert_tempo_here"))->plug(m_menu);

    (new KAction(i18n("Insert Tempo Change at Playback Position"), 0, 0, this,
                 SLOT(slotInsertTempoAtPointer()), actionCollection(),
                 "insert_tempo_at_pointer"))->plug(m_menu);

    m_menu->insertSeparator();

    icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-delete.png"));
    (new KAction(i18n("Delete Tempo Change"), icon, 0, this,
                 SLOT(slotDeleteTempoChange()), actionCollection(),
                 "delete_tempo"))->plug(m_menu);

    m_menu->insertSeparator();

    (new KAction(i18n("Ramp Tempo to Next Tempo"), 0, 0, this,
                 SLOT(slotRampToNext()), actionCollection(),
                 "ramp_to_next"))->plug(m_menu);

    (new KAction(i18n("Un-Ramp Tempo"), 0, 0, this,
                 SLOT(slotUnramp()), actionCollection(),
                 "unramp"))->plug(m_menu);

    m_menu->insertSeparator();

    icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-edit.png"));
    (new KAction(i18n("Edit Tempo..."), icon, 0, this,
                 SLOT(slotEditTempo()), actionCollection(),
                 "edit_tempo"))->plug(m_menu);

    (new KAction(i18n("Edit Time Signature..."), 0, 0, this,
                 SLOT(slotEditTimeSignature()), actionCollection(),
                 "edit_time_signature"))->plug(m_menu);

    (new KAction(i18n("Open Tempo and Time Signature Editor"), 0, 0, this,
                 SLOT(slotEditTempos()), actionCollection(),
                 "edit_tempos"))->plug(m_menu);

    setMouseTracking(false);
}

TempoRuler::~TempoRuler()
{
    delete m_menu;
}

void
TempoRuler::connectSignals()
{
    connect(this,
            SIGNAL(doubleClicked(timeT)),
            RosegardenGUIApp::self(),
            SLOT(slotEditTempos(timeT)));

    connect(this,
            SIGNAL(changeTempo(timeT,
                               tempoT,
                               tempoT,
                               TempoDialog::TempoDialogAction)),
            RosegardenGUIApp::self(),
            SLOT(slotChangeTempo(timeT,
                                 tempoT,
                                 tempoT,
                                 TempoDialog::TempoDialogAction)));

    connect(this,
            SIGNAL(moveTempo(timeT,
                             timeT)),
            RosegardenGUIApp::self(),
            SLOT(slotMoveTempo(timeT,
                               timeT)));

    connect(this,
            SIGNAL(editTempo(timeT)),
            RosegardenGUIApp::self(),
            SLOT(slotEditTempo(timeT)));

    connect(this,
            SIGNAL(editTimeSignature(timeT)),
            RosegardenGUIApp::self(),
            SLOT(slotEditTimeSignature(timeT)));

    connect(this,
            SIGNAL(editTempos(timeT)),
            RosegardenGUIApp::self(),
            SLOT(slotEditTempos(timeT)));
}

void
TempoRuler::slotScrollHoriz(int x)
{
    int w = width(), h = height();
    int dx = x - ( -m_currentXOffset);
    m_currentXOffset = -x;

    if (dx > w*3 / 4 || dx < -w*3 / 4) {
        update();
        return ;
    }

    if (dx > 0) { // moving right, so the existing stuff moves left
        bitBlt(this, 0, 0, this, dx, 0, w - dx, h);
        repaint(w - dx, 0, dx, h);
    } else {      // moving left, so the existing stuff moves right
        bitBlt(this, -dx, 0, this, 0, 0, w + dx, h);
        repaint(0, 0, -dx, h);
    }
}

void
TempoRuler::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == LeftButton) {

        if (e->type() == QEvent::MouseButtonDblClick) {
            timeT t = m_rulerScale->getTimeForX
                      (e->x() - m_currentXOffset - m_xorigin);
            emit doubleClicked(t);
            return ;
        }

        int x = e->x() + 1;
        int y = e->y();
        timeT t = m_rulerScale->getTimeForX(x - m_currentXOffset - m_xorigin);
        int tcn = m_composition->getTempoChangeNumberAt(t);

        if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
            return ;

        std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
        std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);

        m_dragStartY = y;
        m_dragStartX = x;
        m_dragStartTime = tc.first;
        m_dragPreviousTime = m_dragStartTime;
        m_dragStartTempo = tc.second;
        m_dragStartTarget = tr.first ? tr.second : -1;
        m_dragOriginalTempo = m_dragStartTempo;
        m_dragOriginalTarget = m_dragStartTarget;
        m_dragFine = ((e->state() & Qt::ShiftButton) != 0);

        int px = m_rulerScale->getXForTime(tc.first) + m_currentXOffset + m_xorigin;
        if (x >= px && x < px + 5) {
            m_dragHoriz = true;
            m_dragVert = false;
            setCursor(Qt::SplitHCursor);
        } else {
            timeT nt = m_composition->getEndMarker();
            if (tcn < m_composition->getTempoChangeCount() - 1) {
                nt = m_composition->getTempoChange(tcn + 1).first;
            }
            int nx = m_rulerScale->getXForTime(nt) + m_currentXOffset + m_xorigin;
            if (x > px + 5 && x > nx - 5) {
                m_dragTarget = true;
                setCursor(Qt::SizeVerCursor);
            } else {
                m_dragTarget = false;
                setCursor(Qt::SplitVCursor);
            }
            m_dragVert = true;
            m_dragHoriz = false;
        }

    } else if (e->button() == RightButton) {

        m_clickX = e->x();
        if (m_menu)
            m_menu->exec(QCursor::pos());

    }
}

void
TempoRuler::mouseReleaseEvent(QMouseEvent *e)
{
    if (m_dragVert) {

        m_dragVert = false;
        unsetCursor();

        if (e->x() < 0 || e->x() >= width() ||
                e->y() < 0 || e->y() >= height()) {
            leaveEvent(0);
        }

        // First we make a note of the values that we just set and
        // restore the tempo to whatever it was previously, so that
        // the undo for any following command will work correctly.
        // Then we emit so that our user can issue the right command.

        int tcn = m_composition->getTempoChangeNumberAt(m_dragStartTime);
        std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
        std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);

        if (tc.second != m_dragOriginalTempo) {
            m_composition->addTempoAtTime(m_dragStartTime,
                                          m_dragOriginalTempo,
                                          m_dragOriginalTarget);
            emit changeTempo(m_dragStartTime, tc.second,
                             tr.first ? tr.second : -1,
                             TempoDialog::AddTempo);
        }

        return ;

    } else if (m_dragHoriz) {

        m_dragHoriz = false;
        unsetCursor();

        if (e->x() < 0 || e->x() >= width() ||
                e->y() < 0 || e->y() >= height()) {
            leaveEvent(0);
        }

        if (m_dragPreviousTime != m_dragStartTime) {

            // As above, restore the original tempo and then emit a
            // signal to ensure a proper command happens.

            int tcn = m_composition->getTempoChangeNumberAt(m_dragPreviousTime);
            m_composition->removeTempoChange(tcn);
            m_composition->addTempoAtTime(m_dragStartTime,
                                          m_dragStartTempo,
                                          m_dragStartTarget);

            emit moveTempo(m_dragStartTime, m_dragPreviousTime);
        }

        return ;
    }
}

void
TempoRuler::mouseMoveEvent(QMouseEvent *e)
{
    bool shiftPressed = ((e->state() & Qt::ShiftButton) != 0);

    if (m_dragVert) {

        if (shiftPressed != m_dragFine) {

            m_dragFine = shiftPressed;
            m_dragStartY = e->y();

            // reset the start tempi to whatever we last updated them
            // to as we switch into or out of fine mode
            int tcn = m_composition->getTempoChangeNumberAt(m_dragStartTime);
            std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
            std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
            m_dragStartTempo = tc.second;
            m_dragStartTarget = tr.first ? tr.second : -1;
        }

        int diff = m_dragStartY - e->y(); // +ve for upwards drag
        tempoT newTempo = m_dragStartTempo;
        tempoT newTarget = m_dragStartTarget;

        if (diff != 0) {

            float qpm = m_composition->getTempoQpm(newTempo);

            if (m_dragTarget && newTarget > 0) {
                qpm = m_composition->getTempoQpm(newTarget);
            }

            float qdiff = (m_dragFine ? diff * 0.05 : diff * 0.5);
            qpm += qdiff;
            if (qpm < 1)
                qpm = 1;

            if (m_dragTarget) {

                newTarget = m_composition->getTempoForQpm(qpm);

            } else {

                newTempo = m_composition->getTempoForQpm(qpm);

                if (newTarget >= 0) {
                    qpm = m_composition->getTempoQpm(newTarget);
                    qpm += qdiff;
                    if (qpm < 1)
                        qpm = 1;
                    newTarget = m_composition->getTempoForQpm(qpm);
                }
            }
        }

        showTextFloat(newTempo, newTarget, m_dragStartTime);
        m_composition->addTempoAtTime(m_dragStartTime, newTempo, newTarget);
        update();

    } else if (m_dragHoriz) {

        int x = e->x();

        SnapGrid grid(m_rulerScale);
        if (shiftPressed) {
            grid.setSnapTime(SnapGrid::NoSnap);
        } else {
            grid.setSnapTime(SnapGrid::SnapToUnit);
        }
        timeT newTime = grid.snapX(x - m_currentXOffset - m_xorigin,
                                   SnapGrid::SnapEither);

        int tcn = m_composition->getTempoChangeNumberAt(m_dragPreviousTime);
        int ncn = m_composition->getTempoChangeNumberAt(newTime);
        if (ncn > tcn || ncn < tcn - 1)
            return ;
        if (ncn >= 0 && ncn == tcn - 1) {
            std::pair<timeT, tempoT> nc = m_composition->getTempoChange(ncn);
            if (nc.first == newTime)
                return ;
        }

        //	std::cerr << " -> " << newTime << std::endl;

        m_composition->removeTempoChange(tcn);
        m_composition->addTempoAtTime(newTime,
                                      m_dragStartTempo,
                                      m_dragStartTarget);
        showTextFloat(m_dragStartTempo, m_dragStartTarget, newTime, true);
        m_dragPreviousTime = newTime;
        update();

    } else {

        int x = e->x() + 1;
        timeT t = m_rulerScale->getTimeForX(x - m_currentXOffset - m_xorigin);
        int tcn = m_composition->getTempoChangeNumberAt(t);

        if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
            return ;

        std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
        std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);

        int bar, beat, fraction, remainder;
        m_composition->getMusicalTimeForAbsoluteTime(tc.first, bar, beat,
                fraction, remainder);
        RG_DEBUG << "Tempo change: tempo " << m_composition->getTempoQpm(tc.second) << " at " << bar << ":" << beat << ":" << fraction << ":" << remainder << endl;

        m_illuminate = tcn;
        m_illuminatePoint = false;
        m_illuminateTarget = false;
        //!!!	m_refreshLinesOnly = true;

        //!!! merge this test with the one in mousePressEvent as
        //isCloseToStart or equiv, and likewise for close to end

        int px = m_rulerScale->getXForTime(tc.first) + m_currentXOffset + m_xorigin;
        if (x >= px && x < px + 5) {
            m_illuminatePoint = true;
        } else {
            timeT nt = m_composition->getEndMarker();
            if (tcn < m_composition->getTempoChangeCount() - 1) {
                nt = m_composition->getTempoChange(tcn + 1).first;
            }
            int nx = m_rulerScale->getXForTime(nt) + m_currentXOffset + m_xorigin;
            if (x > px + 5 && x > nx - 5) {
                m_illuminateTarget = true;
            }

            //	    std::cerr << "nt = " << nt << ", nx = " << nx << ", x = " << x << ", m_illuminateTarget = " << m_illuminateTarget << std::endl;
        }

        showTextFloat(tc.second, tr.first ? tr.second : -1,
                      tc.first, m_illuminatePoint);

        update();
    }
}

void
TempoRuler::wheelEvent(QWheelEvent *e)
{}

void
TempoRuler::enterEvent(QEvent *)
{
    setMouseTracking(true);
}

void
TempoRuler::leaveEvent(QEvent *)
{
    if (!m_dragVert && !m_dragHoriz) {
        setMouseTracking(false);
        m_illuminate = -1;
        m_illuminatePoint = false;
        //!!!	m_refreshLinesOnly = true;
        m_textFloat->hide();
        update();
    }
}

void
TempoRuler::showTextFloat(tempoT tempo, tempoT target,
                          timeT time, bool showTime)
{
    float qpm = m_composition->getTempoQpm(tempo);
    int qi = int(qpm + 0.0001);
    int q0 = int(qpm * 10 + 0.0001) % 10;
    int q00 = int(qpm * 100 + 0.0001) % 10;

    bool haveSet = false;

    QString tempoText, timeText;

    if (time >= 0) {

        if (showTime) {
            int bar, beat, fraction, remainder;
            m_composition->getMusicalTimeForAbsoluteTime
            (time, bar, beat, fraction, remainder);
            RealTime rt = m_composition->getElapsedRealTime(time);

            // blargh -- duplicated with TempoView::makeTimeString
            timeText = QString("%1%2%3-%4%5-%6%7-%8%9")
                       .arg(bar / 100)
                       .arg((bar % 100) / 10)
                       .arg(bar % 10)
                       .arg(beat / 10)
                       .arg(beat % 10)
                       .arg(fraction / 10)
                       .arg(fraction % 10)
                       .arg(remainder / 10)
                       .arg(remainder % 10);

            timeText = QString("%1\n%2")
                       .arg(timeText)
                       //		.arg(rt.toString().c_str());
                       .arg(rt.toText(true).c_str());
        }

        TimeSignature sig =
            m_composition->getTimeSignatureAt(time);

        if (sig.getBeatDuration() !=
                Note(Note::Crotchet).getDuration()) {

            float bpm =
                (qpm *
                 Note(Note::Crotchet).getDuration())
                / sig.getBeatDuration();
            int bi = int(bpm + 0.0001);
            int b0 = int(bpm * 10 + 0.0001) % 10;
            int b00 = int(bpm * 100 + 0.0001) % 10;

            tempoText = i18n("%1.%2%3 (%4.%5%6 bpm)")
                        .arg(qi).arg(q0).arg(q00)
                        .arg(bi).arg(b0).arg(b00);
            haveSet = true;
        }
    }

    if (!haveSet) {
        tempoText = i18n("%1.%2%3 bpm").arg(qi).arg(q0).arg(q00);
    }

    if (target > 0 && target != tempo) {
        float tq = m_composition->getTempoQpm(target);
        int tqi = int(tq + 0.0001);
        int tq0 = int(tq * 10 + 0.0001) % 10;
        int tq00 = int(tq * 100 + 0.0001) % 10;
        tempoText = i18n("%1 - %2.%3%4").arg(tempoText).arg(tqi).arg(tq0).arg(tq00);
    }

    if (showTime && time >= 0) {
        m_textFloat->setText(QString("%1\n%2").arg(timeText).arg(tempoText));
    } else {
        m_textFloat->setText(tempoText);
    }

    QPoint cp = mapFromGlobal(QPoint(QCursor::pos()));
    //    std::cerr << "cp = " << cp.x() << "," << cp.y() << ", tempo = " << qpm << std::endl;
    QPoint mp = cp + pos();

    QWidget *parent = parentWidget();
    while (parent->parentWidget() &&
            !parent->isTopLevel() &&
            !parent->isDialog()) {
        mp += parent->pos();
        parent = parent->parentWidget();
    }

    int yoff = cp.y() + m_textFloat->height() + 3;
    mp = QPoint(mp.x() + 10, mp.y() > yoff ? mp.y() - yoff : 0);

    m_textFloat->move(mp);
    m_textFloat->show();
}

QSize
TempoRuler::sizeHint() const
{
    double width =
        m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) +
        m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar()) +
        m_xorigin;

    QSize res(std::max(int(width), m_width), m_height);

    return res;
}

QSize
TempoRuler::minimumSizeHint() const
{
    double firstBarWidth = m_rulerScale->getBarWidth(0) + m_xorigin;
    QSize res = QSize(int(firstBarWidth), m_height);
    return res;
}

int
TempoRuler::getYForTempo(tempoT tempo)
{
    int drawh = height() - 4;
    int y = drawh / 2;

    tempoT minTempo = m_composition->getMinTempo();
    tempoT maxTempo = m_composition->getMaxTempo();

    if (maxTempo > minTempo) {
        y = drawh -
            int((double(tempo - minTempo) / double(maxTempo - minTempo))
                * drawh + 0.5);
    }

    return y;
}

tempoT
TempoRuler::getTempoForY(int y)
{
    int drawh = height() - 4;

    tempoT minTempo = m_composition->getMinTempo();
    tempoT maxTempo = m_composition->getMaxTempo();

    tempoT tempo = minTempo;

    if (maxTempo > minTempo) {
        tempo = (maxTempo - minTempo) *
                (double(drawh - y) / double(drawh)) + minTempo + 0.5;
    }

    return tempo;
}

void
TempoRuler::paintEvent(QPaintEvent* e)
{
    QRect clipRect = e->rect();

    if (m_buffer.width() < width() || m_buffer.height() < height()) {
        m_buffer = QPixmap(width(), height());
    }

    m_buffer.fill(GUIPalette::getColour
                  (GUIPalette::TextRulerBackground));

    QPainter paint(&m_buffer);
    paint.setPen(GUIPalette::getColour
                 (GUIPalette::TextRulerForeground));

    paint.setClipRegion(e->region());
    paint.setClipRect(clipRect);

    if (m_xorigin > 0) {
        paint.fillRect(0, 0, m_xorigin, height(), paletteBackgroundColor());
    }

    timeT from = m_rulerScale->getTimeForX
                 (clipRect.x() - m_currentXOffset - 100 - m_xorigin);
    timeT to = m_rulerScale->getTimeForX
               (clipRect.x() + clipRect.width() - m_currentXOffset + 100 - m_xorigin);

    QRect boundsForHeight = m_fontMetrics.boundingRect("019");
    int fontHeight = boundsForHeight.height();
    int textY = fontHeight + 2;

    double prevEndX = -1000.0;
    double prevTempo = 0.0;
    long prevBpm = 0;

    typedef std::map<timeT, int> TimePoints;
    int tempoChangeHere = 1;
    int timeSigChangeHere = 2;
    TimePoints timePoints;

    for (int tempoNo = m_composition->getTempoChangeNumberAt(from);
            tempoNo <= m_composition->getTempoChangeNumberAt(to) + 1; ++tempoNo) {

        if (tempoNo >= 0 && tempoNo < m_composition->getTempoChangeCount()) {
            timePoints.insert
            (TimePoints::value_type
             (m_composition->getTempoChange(tempoNo).first,
              tempoChangeHere));
        }
    }

    for (int sigNo = m_composition->getTimeSignatureNumberAt(from);
            sigNo <= m_composition->getTimeSignatureNumberAt(to) + 1; ++sigNo) {

        if (sigNo >= 0 && sigNo < m_composition->getTimeSignatureCount()) {
            timeT time(m_composition->getTimeSignatureChange(sigNo).first);
            if (timePoints.find(time) != timePoints.end()) {
                timePoints[time] |= timeSigChangeHere;
            } else {
                timePoints.insert(TimePoints::value_type(time, timeSigChangeHere));
            }
        }
    }

    int lastx = 0, lasty = 0, lastx1 = 0;
    bool haveSome = false;
    //    tempoT minTempo = m_composition->getMinTempo();
    //    tempoT maxTempo = m_composition->getMaxTempo();
    bool illuminate = false;

    if (m_illuminate >= 0) {
        int tcn = m_composition->getTempoChangeNumberAt(from);
        illuminate = (m_illuminate == tcn);
    }

    for (TimePoints::iterator i = timePoints.begin(); ; ++i) {

        timeT t0, t1;

        if (i == timePoints.begin()) {
            t0 = from;
        } else {
            TimePoints::iterator j(i);
            --j;
            t0 = j->first;
        }

        if (i == timePoints.end()) {
            t1 = to;
        } else {
            t1 = i->first;
        }

        if (t1 <= t0)
            t1 = to;

        int tcn = m_composition->getTempoChangeNumberAt(t0);
        tempoT tempo = m_composition->getTempoAtTime(t0);

        std::pair<bool, tempoT> ramping(false, tempo);
        if (tcn > 0 && tcn < m_composition->getTempoChangeCount() + 1) {
            ramping = m_composition->getTempoRamping(tcn - 1, true);
        }

        double x0, x1;
        x0 = m_rulerScale->getXForTime(t0) + m_currentXOffset + m_xorigin;
        x1 = m_rulerScale->getXForTime(t1) + m_currentXOffset + m_xorigin;
        /*!!!
        	if (x0 > e->rect().x()) {
        	    paint.fillRect(e->rect().x(), 0, x0 - e->rect().x(), height(),
        			   paletteBackgroundColor());
        	}
        */
        QColor colour = TempoColour::getColour(m_composition->getTempoQpm(tempo));
        paint.setPen(colour);
        paint.setBrush(colour);

        if (!m_refreshLinesOnly) {
            // 	    RG_DEBUG << "TempoRuler: draw rect from " << x0 << " to " << x1 << endl;
            paint.drawRect(int(x0), 0, int(x1 - x0) + 1, height());
        }

        int y = getYForTempo(tempo);
        /*!!!
        	int drawh = height() - 4;
        	int y = drawh / 2;
        	if (maxTempo > minTempo) {
        	    y = drawh - 
        		int((double(tempo - minTempo) / double(maxTempo - minTempo))
        		    * drawh + 0.5);
        	}
        */
        y += 2;

        if (haveSome) {

            int x = int(x0) + 1;
            int ry = lasty;

            bool illuminateLine = (illuminate &&
                                   !m_illuminatePoint && !m_illuminateTarget);

            paint.setPen(illuminateLine ? Qt::white : Qt::black);

            if (ramping.first) {
                ry = getYForTempo(ramping.second);
                ry += 2;
                /*!!!
                		ry = drawh - 
                		    int((double(ramping.second - minTempo) /
                			 double(maxTempo - minTempo))
                			* drawh + 0.5);
                */
            }

            paint.drawLine(lastx + 1, lasty, x - 2, ry);

            if (!illuminateLine && illuminate && m_illuminateTarget) {
                if (x > lastx) {
                    paint.setPen(Qt::white);
                    paint.drawLine(x - 6, ry - ((ry - lasty) * 6) / (x - lastx),
                                   x - 2, ry);
                }
            }

            if (m_illuminate >= 0) {
                illuminate = (m_illuminate == tcn);
            }

            bool illuminatePoint = (illuminate && m_illuminatePoint);

            paint.setPen(illuminatePoint ? Qt::white : Qt::black);
            paint.drawRect(x - 1, y - 1, 3, 3);

            paint.setPen(illuminatePoint ? Qt::black : Qt::white);
            paint.drawPoint(x, y);
        }

        lastx = int(x0) + 1;
        lastx1 = int(x1) + 1;
        lasty = y;
        if (i == timePoints.end())
            break;
        haveSome = true;
    }

    if (lastx1 < e->rect().x() + e->rect().width()) {
        /*!!!
        	paint.fillRect(lastx1, 0,
        		       e->rect().x() + e->rect().width() - lastx1, height(),
        		       paletteBackgroundColor());
        */
    }

    if (haveSome) {
        bool illuminateLine = (illuminate && !m_illuminatePoint);
        paint.setPen(illuminateLine ? Qt::white : Qt::black);
        paint.drawLine(lastx + 1, lasty, width(), lasty);
    } else if (!m_refreshLinesOnly) {
        tempoT tempo = m_composition->getTempoAtTime(from);
        QColor colour = TempoColour::getColour(m_composition->getTempoQpm(tempo));
        paint.setPen(colour);
        paint.setBrush(colour);
        paint.drawRect(e->rect());
    }

    paint.setPen(Qt::black);
    paint.setBrush(Qt::black);
    paint.drawLine(0, 0, width(), 0);

    for (TimePoints::iterator i = timePoints.begin();
            i != timePoints.end(); ++i) {

        timeT time = i->first;
        double x = m_rulerScale->getXForTime(time) + m_currentXOffset
                   + m_xorigin;

        /*
        	paint.drawLine(static_cast<int>(x),
        		       height() - (height()/4),
        		       static_cast<int>(x),
        		       height());
        */

        if ((i->second & timeSigChangeHere) && !m_refreshLinesOnly) {

            TimeSignature sig =
                m_composition->getTimeSignatureAt(time);

            QString str = QString("%1/%2")
                          .arg(sig.getNumerator())
                          .arg(sig.getDenominator());

            paint.setFont(m_boldFont);
            paint.drawText(static_cast<int>(x) + 2, m_height - 2, str);
        }

        if ((i->second & tempoChangeHere) && !m_refreshLinesOnly) {

            double tempo = m_composition->getTempoQpm(m_composition->getTempoAtTime(time));
            long bpm = long(tempo);
            //	    long frac = long(tempo * 100 + 0.001) - 100 * bpm;

            QString tempoString = QString("%1").arg(bpm);

            if (tempo == prevTempo) {
                if (m_small)
                    continue;
                tempoString = "=";
            } else if (bpm == prevBpm) {
                tempoString = (tempo > prevTempo ? "+" : "-");
            } else {
                if (m_small && (bpm != (bpm / 10 * 10))) {
                    if (bpm == prevBpm + 1)
                        tempoString = "+";
                    else if (bpm == prevBpm - 1)
                        tempoString = "-";
                }
            }
            prevTempo = tempo;
            prevBpm = bpm;

            QRect bounds = m_fontMetrics.boundingRect(tempoString);

            paint.setFont(m_font);
            if (time > 0)
                x -= bounds.width() / 2;
            //	    if (x > bounds.width() / 2) x -= bounds.width() / 2;
            if (prevEndX >= x - 3)
                x = prevEndX + 3;
            paint.drawText(static_cast<int>(x), textY, tempoString);
            prevEndX = x + bounds.width();
        }
    }

    paint.end();

    QPainter dbpaint(this);
    //    dbpaint.drawPixmap(0, 0, m_buffer);
    dbpaint.drawPixmap(clipRect.x(), clipRect.y(),
                       m_buffer,
                       clipRect.x(), clipRect.y(),
                       clipRect.width(), clipRect.height());

    dbpaint.end();

    m_refreshLinesOnly = false;
}

void
TempoRuler::slotInsertTempoHere()
{
    SnapGrid grid(m_rulerScale);
    grid.setSnapTime(SnapGrid::SnapToUnit);
    timeT t = grid.snapX(m_clickX - m_currentXOffset - m_xorigin,
                         SnapGrid::SnapLeft);
    tempoT tempo = Composition::getTempoForQpm(120.0);

    int tcn = m_composition->getTempoChangeNumberAt(t);
    if (tcn >= 0 && tcn < m_composition->getTempoChangeCount()) {
        std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
        if (tc.first == t)
            return ;
        tempo = tc.second;
    }

    emit changeTempo(t, tempo, -1, TempoDialog::AddTempo);
}

void
TempoRuler::slotInsertTempoAtPointer()
{
    timeT t = m_composition->getPosition();
    tempoT tempo = Composition::getTempoForQpm(120.0);

    int tcn = m_composition->getTempoChangeNumberAt(t);
    if (tcn >= 0 && tcn < m_composition->getTempoChangeCount()) {
        std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
        if (tc.first == t)
            return ;
        tempo = tc.second;
    }

    emit changeTempo(t, tempo, -1, TempoDialog::AddTempo);
}

void
TempoRuler::slotDeleteTempoChange()
{
    timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
    emit deleteTempo(t);
}

void
TempoRuler::slotRampToNext()
{
    timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);

    int tcn = m_composition->getTempoChangeNumberAt(t);
    if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
        return ;

    std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);

    emit changeTempo(tc.first, tc.second, 0, TempoDialog::AddTempo);
}

void
TempoRuler::slotUnramp()
{
    timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);

    int tcn = m_composition->getTempoChangeNumberAt(t);
    if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
        return ;

    std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);

    emit changeTempo(tc.first, tc.second, -1, TempoDialog::AddTempo);
}

void
TempoRuler::slotEditTempo()
{
    timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
    emit editTempo(t);
}

void
TempoRuler::slotEditTimeSignature()
{
    timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
    emit editTimeSignature(t);
}

void
TempoRuler::slotEditTempos()
{
    timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
    emit editTempos(t);
}

}
#include "TempoRuler.moc"
