You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1092 lines
33 KiB
1092 lines
33 KiB
15 years ago
|
/* -*- 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-2008
|
||
|
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,
|
||
|
KMainWindow *parentMainWindow,
|
||
|
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_parentMainWindow(parentMainWindow),
|
||
|
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()));
|
||
|
|
||
|
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");
|
||
|
|
||
|
new KAction(i18n("Insert Tempo Change at Playback Position"), 0, 0, this,
|
||
|
SLOT(slotInsertTempoAtPointer()), actionCollection(),
|
||
|
"insert_tempo_at_pointer");
|
||
|
|
||
|
icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-delete.png"));
|
||
|
new KAction(i18n("Delete Tempo Change"), icon, 0, this,
|
||
|
SLOT(slotDeleteTempoChange()), actionCollection(),
|
||
|
"delete_tempo");
|
||
|
|
||
|
new KAction(i18n("Ramp Tempo to Next Tempo"), 0, 0, this,
|
||
|
SLOT(slotRampToNext()), actionCollection(),
|
||
|
"ramp_to_next");
|
||
|
|
||
|
new KAction(i18n("Un-Ramp Tempo"), 0, 0, this,
|
||
|
SLOT(slotUnramp()), actionCollection(),
|
||
|
"unramp");
|
||
|
|
||
|
icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-edit.png"));
|
||
|
new KAction(i18n("Edit Tempo..."), icon, 0, this,
|
||
|
SLOT(slotEditTempo()), actionCollection(),
|
||
|
"edit_tempo");
|
||
|
|
||
|
new KAction(i18n("Edit Time Signature..."), 0, 0, this,
|
||
|
SLOT(slotEditTimeSignature()), actionCollection(),
|
||
|
"edit_time_signature");
|
||
|
|
||
|
new KAction(i18n("Open Tempo and Time Signature Editor"), 0, 0, this,
|
||
|
SLOT(slotEditTempos()), actionCollection(),
|
||
|
"edit_tempos");
|
||
|
|
||
|
setMouseTracking(false);
|
||
|
}
|
||
|
|
||
|
TempoRuler::~TempoRuler()
|
||
|
{
|
||
|
// we have to do this so that the menu is re-created properly
|
||
|
// when the main window is itself recreated (on a File->New for instance)
|
||
|
KXMLGUIFactory* factory = m_parentMainWindow->factory();
|
||
|
if (factory)
|
||
|
factory->removeClient(this);
|
||
|
}
|
||
|
|
||
|
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(deleteTempo(timeT)),
|
||
|
RosegardenGUIApp::self(),
|
||
|
SLOT(slotDeleteTempo(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)
|
||
|
createMenu();
|
||
|
if (m_menu) {
|
||
|
// enable 'delete' action only if cursor is actually over a tempo change
|
||
|
actionCollection()->action("delete_tempo")->setEnabled(m_illuminatePoint);
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
TempoRuler::createMenu()
|
||
|
{
|
||
|
setXMLFile("temporuler.rc");
|
||
|
|
||
|
KXMLGUIFactory* factory = m_parentMainWindow->factory();
|
||
|
factory->addClient(this);
|
||
|
|
||
|
QWidget* tmp = factory->container("tempo_ruler_menu", this);
|
||
|
|
||
|
m_menu = dynamic_cast<QPopupMenu*>(tmp);
|
||
|
|
||
|
if (!m_menu) {
|
||
|
RG_DEBUG << "MarkerRuler::createMenu() failed\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
#include "TempoRuler.moc"
|