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.
522 lines
16 KiB
522 lines
16 KiB
/*
|
|
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 "ChordNameRuler.h"
|
|
#include "misc/Debug.h"
|
|
|
|
#include <tdelocale.h>
|
|
#include "misc/Strings.h"
|
|
#include "base/AnalysisTypes.h"
|
|
#include "base/Composition.h"
|
|
#include "base/CompositionTimeSliceAdapter.h"
|
|
#include "base/Instrument.h"
|
|
#include "base/NotationTypes.h"
|
|
#include "base/Profiler.h"
|
|
#include "base/PropertyName.h"
|
|
#include "base/NotationQuantizer.h"
|
|
#include "base/RefreshStatus.h"
|
|
#include "base/RulerScale.h"
|
|
#include "base/Segment.h"
|
|
#include "base/Selection.h"
|
|
#include "base/Studio.h"
|
|
#include "base/Track.h"
|
|
#include "document/RosegardenGUIDoc.h"
|
|
#include "document/MultiViewCommandHistory.h"
|
|
#include "gui/general/GUIPalette.h"
|
|
#include <tqfont.h>
|
|
#include <tqfontmetrics.h>
|
|
#include <tqobject.h>
|
|
#include <tqpainter.h>
|
|
#include <tqrect.h>
|
|
#include <tqsize.h>
|
|
#include <tqtooltip.h>
|
|
#include <tqwidget.h>
|
|
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
ChordNameRuler::ChordNameRuler(RulerScale *rulerScale,
|
|
RosegardenGUIDoc *doc,
|
|
double xorigin,
|
|
int height,
|
|
TQWidget *parent,
|
|
const char *name) :
|
|
TQWidget(parent, name),
|
|
m_xorigin(xorigin),
|
|
m_height(height),
|
|
m_currentXOffset(0),
|
|
m_width( -1),
|
|
m_ready(false),
|
|
m_rulerScale(rulerScale),
|
|
m_composition(&doc->getComposition()),
|
|
m_regetSegmentsOnChange(true),
|
|
m_currentSegment(0),
|
|
m_studio(0),
|
|
m_chordSegment(0),
|
|
m_fontMetrics(m_boldFont),
|
|
TEXT_FORMAL_X("TextFormalX"),
|
|
TEXT_ACTUAL_X("TextActualX")
|
|
{
|
|
m_font.setPointSize(11);
|
|
m_font.setPixelSize(12);
|
|
m_boldFont.setPointSize(11);
|
|
m_boldFont.setPixelSize(12);
|
|
m_boldFont.setBold(true);
|
|
m_fontMetrics = TQFontMetrics(m_boldFont);
|
|
setBackgroundColor(GUIPalette::getColour(GUIPalette::ChordNameRulerBackground));
|
|
|
|
m_compositionRefreshStatusId = m_composition->getNewRefreshStatusId();
|
|
|
|
TQObject::connect(doc->getCommandHistory(), TQ_SIGNAL(commandExecuted()),
|
|
this, TQ_SLOT(update()));
|
|
|
|
TQToolTip::add
|
|
(this, i18n("Chord name ruler.\nTurn it on and off from the Settings->Rulers menu."));
|
|
|
|
}
|
|
|
|
ChordNameRuler::ChordNameRuler(RulerScale *rulerScale,
|
|
RosegardenGUIDoc *doc,
|
|
std::vector<Segment *> &segments,
|
|
double xorigin,
|
|
int height,
|
|
TQWidget *parent,
|
|
const char *name) :
|
|
TQWidget(parent, name),
|
|
m_xorigin(xorigin),
|
|
m_height(height),
|
|
m_currentXOffset(0),
|
|
m_width( -1),
|
|
m_ready(false),
|
|
m_rulerScale(rulerScale),
|
|
m_composition(&doc->getComposition()),
|
|
m_regetSegmentsOnChange(false),
|
|
m_currentSegment(0),
|
|
m_studio(0),
|
|
m_chordSegment(0),
|
|
m_fontMetrics(m_boldFont),
|
|
TEXT_FORMAL_X("TextFormalX"),
|
|
TEXT_ACTUAL_X("TextActualX")
|
|
{
|
|
m_font.setPointSize(11);
|
|
m_font.setPixelSize(12);
|
|
m_boldFont.setPointSize(11);
|
|
m_boldFont.setPixelSize(12);
|
|
m_boldFont.setBold(true);
|
|
m_fontMetrics = TQFontMetrics(m_boldFont);
|
|
setBackgroundColor(GUIPalette::getColour(GUIPalette::ChordNameRulerBackground));
|
|
|
|
m_compositionRefreshStatusId = m_composition->getNewRefreshStatusId();
|
|
|
|
TQObject::connect(doc->getCommandHistory(), TQ_SIGNAL(commandExecuted()),
|
|
this, TQ_SLOT(update()));
|
|
|
|
for (std::vector<Segment *>::iterator i = segments.begin();
|
|
i != segments.end(); ++i) {
|
|
m_segments.insert(SegmentRefreshMap::value_type
|
|
(*i, (*i)->getNewRefreshStatusId()));
|
|
}
|
|
}
|
|
|
|
ChordNameRuler::~ChordNameRuler()
|
|
{
|
|
delete m_chordSegment;
|
|
}
|
|
|
|
void
|
|
ChordNameRuler::setReady()
|
|
{
|
|
m_ready = true;
|
|
update();
|
|
}
|
|
|
|
void
|
|
ChordNameRuler::setCurrentSegment(Segment *segment)
|
|
{
|
|
m_currentSegment = segment;
|
|
}
|
|
|
|
void
|
|
ChordNameRuler::setStudio(Studio *studio)
|
|
{
|
|
m_studio = studio;
|
|
}
|
|
|
|
void
|
|
ChordNameRuler::slotScrollHoriz(int x)
|
|
{
|
|
int w = width(), h = height();
|
|
int dx = x - ( -m_currentXOffset);
|
|
m_currentXOffset = -x;
|
|
|
|
if (dx == 0)
|
|
return ;
|
|
|
|
if (dx > w*7 / 8 || dx < -w*7 / 8) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
TQSize
|
|
ChordNameRuler::sizeHint() const
|
|
{
|
|
double width =
|
|
m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) +
|
|
m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar());
|
|
|
|
NOTATION_DEBUG << "Returning chord-label ruler width as " << width << endl;
|
|
|
|
TQSize res(std::max(int(width), m_width), m_height);
|
|
|
|
return res;
|
|
}
|
|
|
|
TQSize
|
|
ChordNameRuler::minimumSizeHint() const
|
|
{
|
|
double firstBarWidth = m_rulerScale->getBarWidth(0);
|
|
TQSize res = TQSize(int(firstBarWidth), m_height);
|
|
return res;
|
|
}
|
|
|
|
void
|
|
ChordNameRuler::recalculate(timeT from, timeT to)
|
|
{
|
|
if (!m_ready)
|
|
return ;
|
|
|
|
Profiler profiler("ChordNameRuler::recalculate");
|
|
NOTATION_DEBUG << "ChordNameRuler[" << this << "]::recalculate" << endl;
|
|
|
|
bool regetSegments = false;
|
|
|
|
enum RecalcLevel { RecalcNone, RecalcVisible, RecalcWhole };
|
|
RecalcLevel level = RecalcNone;
|
|
|
|
if (m_segments.empty()) {
|
|
|
|
regetSegments = true;
|
|
|
|
} else if (m_regetSegmentsOnChange) {
|
|
|
|
RefreshStatus &rs =
|
|
m_composition->getRefreshStatus(m_compositionRefreshStatusId);
|
|
|
|
if (rs.needsRefresh()) {
|
|
rs.setNeedsRefresh(false);
|
|
regetSegments = true;
|
|
}
|
|
}
|
|
|
|
if (regetSegments) {
|
|
|
|
SegmentSelection ss;
|
|
|
|
for (Composition::iterator ci = m_composition->begin();
|
|
ci != m_composition->end(); ++ci) {
|
|
|
|
if (m_studio) {
|
|
|
|
TrackId ti = (*ci)->getTrack();
|
|
|
|
Instrument *instr = m_studio->getInstrumentById
|
|
(m_composition->getTrackById(ti)->getInstrument());
|
|
|
|
if (instr &&
|
|
instr->getInstrumentType() == Instrument::Midi &&
|
|
instr->isPercussion()) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ss.insert(*ci);
|
|
}
|
|
|
|
std::vector<SegmentRefreshMap::iterator> eraseThese;
|
|
|
|
for (SegmentRefreshMap::iterator si = m_segments.begin();
|
|
si != m_segments.end(); ++si) {
|
|
if (ss.find(si->first) == ss.end()) {
|
|
eraseThese.push_back(si);
|
|
level = RecalcWhole;
|
|
NOTATION_DEBUG << "Segment deleted, updating (now have " << m_segments.size() << " segments)" << endl;
|
|
}
|
|
}
|
|
|
|
for (std::vector<SegmentRefreshMap::iterator>::iterator ei = eraseThese.begin();
|
|
ei != eraseThese.end(); ++ei) {
|
|
m_segments.erase(*ei);
|
|
}
|
|
|
|
|
|
for (SegmentSelection::iterator si = ss.begin();
|
|
si != ss.end(); ++si) {
|
|
|
|
if (m_segments.find(*si) == m_segments.end()) {
|
|
m_segments.insert(SegmentRefreshMap::value_type
|
|
(*si, (*si)->getNewRefreshStatusId()));
|
|
level = RecalcWhole;
|
|
NOTATION_DEBUG << "Segment created, adding (now have " << m_segments.size() << " segments)" << endl;
|
|
}
|
|
}
|
|
|
|
if (m_currentSegment &&
|
|
ss.find(m_currentSegment) == ss.end()) {
|
|
m_currentSegment = 0;
|
|
level = RecalcWhole;
|
|
}
|
|
}
|
|
|
|
if (!m_chordSegment)
|
|
m_chordSegment = new Segment();
|
|
if (m_segments.empty())
|
|
return ;
|
|
|
|
SegmentRefreshStatus overallStatus;
|
|
overallStatus.setNeedsRefresh(false);
|
|
|
|
for (SegmentRefreshMap::iterator i = m_segments.begin();
|
|
i != m_segments.end(); ++i) {
|
|
SegmentRefreshStatus &status =
|
|
i->first->getRefreshStatus(i->second);
|
|
if (status.needsRefresh()) {
|
|
overallStatus.push(status.from(), status.to());
|
|
}
|
|
}
|
|
|
|
// We now have the overall area affected by these changes, across
|
|
// all segments. If it's entirely within our displayed area, just
|
|
// recalculate the displayed area; if it overlaps, calculate the
|
|
// union of the two areas; if it's entirely without, calculate
|
|
// nothing.
|
|
|
|
if (level == RecalcNone) {
|
|
if (from == to) {
|
|
NOTATION_DEBUG << "ChordNameRuler::recalculate: from==to, recalculating all" << endl;
|
|
level = RecalcWhole;
|
|
} else if (overallStatus.from() == overallStatus.to()) {
|
|
NOTATION_DEBUG << "ChordNameRuler::recalculate: overallStatus.from==overallStatus.to, ignoring" << endl;
|
|
level = RecalcNone;
|
|
} else if (overallStatus.from() >= from && overallStatus.to() <= to) {
|
|
NOTATION_DEBUG << "ChordNameRuler::recalculate: change is " << overallStatus.from() << "->" << overallStatus.to() << ", I show " << from << "->" << to << ", recalculating visible area" << endl;
|
|
level = RecalcVisible;
|
|
} else if (overallStatus.from() >= to || overallStatus.to() <= from) {
|
|
NOTATION_DEBUG << "ChordNameRuler::recalculate: change is " << overallStatus.from() << "->" << overallStatus.to() << ", I show " << from << "->" << to << ", ignoring" << endl;
|
|
level = RecalcNone;
|
|
} else {
|
|
NOTATION_DEBUG << "ChordNameRuler::recalculate: change is " << overallStatus.from() << "->" << overallStatus.to() << ", I show " << from << "->" << to << ", recalculating whole" << endl;
|
|
level = RecalcWhole;
|
|
}
|
|
}
|
|
|
|
if (level == RecalcNone)
|
|
return ;
|
|
|
|
for (SegmentRefreshMap::iterator i = m_segments.begin();
|
|
i != m_segments.end(); ++i) {
|
|
i->first->getRefreshStatus(i->second).setNeedsRefresh(false);
|
|
}
|
|
|
|
if (!m_currentSegment) { //!!! arbitrary, must do better
|
|
//!!! need a segment starting at zero or so with a clef and key in it!
|
|
m_currentSegment = m_segments.begin()->first;
|
|
}
|
|
|
|
/*!!!
|
|
|
|
for (Composition::iterator ci = m_composition->begin();
|
|
ci != m_composition->end(); ++ci) {
|
|
|
|
if ((*ci)->getEndMarkerTime() >= from &&
|
|
((*ci)->getStartTime() <= from ||
|
|
(clefKeySegment &&
|
|
(*ci)->getStartTime() < clefKeySegment->getStartTime()))) {
|
|
|
|
clefKeySegment = *ci;
|
|
}
|
|
}
|
|
|
|
if (!clefKeySegment) return;
|
|
}
|
|
*/
|
|
|
|
if (level == RecalcWhole) {
|
|
|
|
m_chordSegment->clear();
|
|
|
|
timeT clefKeyTime = m_currentSegment->getStartTime();
|
|
//(from < m_currentSegment->getStartTime() ?
|
|
// m_currentSegment->getStartTime() : from);
|
|
|
|
Clef clef = m_currentSegment->getClefAtTime(clefKeyTime);
|
|
m_chordSegment->insert(clef.getAsEvent( -1));
|
|
|
|
::Rosegarden::Key key = m_currentSegment->getKeyAtTime(clefKeyTime);
|
|
m_chordSegment->insert(key.getAsEvent( -1));
|
|
|
|
from = 0;
|
|
to = 0;
|
|
|
|
} else {
|
|
Segment::iterator i = m_chordSegment->findTime(from);
|
|
Segment::iterator j = m_chordSegment->findTime(to);
|
|
m_chordSegment->erase(i, j);
|
|
}
|
|
|
|
SegmentSelection selection;
|
|
for (SegmentRefreshMap::iterator si = m_segments.begin(); si != m_segments.end();
|
|
++si) {
|
|
selection.insert(si->first);
|
|
}
|
|
|
|
CompositionTimeSliceAdapter adapter(m_composition, &selection, from, to);
|
|
AnalysisHelper helper;
|
|
helper.labelChords(adapter, *m_chordSegment, m_composition->getNotationQuantizer());
|
|
}
|
|
|
|
void
|
|
ChordNameRuler::paintEvent(TQPaintEvent* e)
|
|
{
|
|
if (!m_composition || !m_ready)
|
|
return ;
|
|
|
|
NOTATION_DEBUG << "*** Chord Name Ruler: paintEvent" << endl;
|
|
|
|
Profiler profiler1("ChordNameRuler::paintEvent (whole)");
|
|
|
|
TQPainter paint(this);
|
|
paint.setPen(GUIPalette::getColour(GUIPalette::ChordNameRulerForeground));
|
|
|
|
paint.setClipRegion(e->region());
|
|
paint.setClipRect(e->rect().normalize());
|
|
|
|
TQRect clipRect = paint.clipRegion().boundingRect();
|
|
|
|
timeT from = m_rulerScale->getTimeForX
|
|
(clipRect.x() - m_currentXOffset - m_xorigin - 50);
|
|
timeT to = m_rulerScale->getTimeForX
|
|
(clipRect.x() + clipRect.width() - m_currentXOffset - m_xorigin + 50);
|
|
|
|
recalculate(from, to);
|
|
|
|
if (!m_chordSegment)
|
|
return ;
|
|
|
|
Profiler profiler2("ChordNameRuler::paintEvent (paint)");
|
|
|
|
TQRect boundsForHeight = m_fontMetrics.boundingRect("^j|lM");
|
|
int fontHeight = boundsForHeight.height();
|
|
int textY = (height() - 6) / 2 + fontHeight / 2;
|
|
|
|
double prevX = 0;
|
|
timeT keyAt = from - 1;
|
|
std::string keyText;
|
|
|
|
NOTATION_DEBUG << "*** Chord Name Ruler: paint " << from << " -> " << to << endl;
|
|
|
|
for (Segment::iterator i = m_chordSegment->findTime(from);
|
|
i != m_chordSegment->findTime(to); ++i) {
|
|
|
|
NOTATION_DEBUG << "type " << (*i)->getType() << " at " << (*i)->getAbsoluteTime()
|
|
<< endl;
|
|
|
|
if (!(*i)->isa(Text::EventType) ||
|
|
!(*i)->has(Text::TextPropertyName) ||
|
|
!(*i)->has(Text::TextTypePropertyName))
|
|
continue;
|
|
|
|
std::string text((*i)->get
|
|
<String>(Text::TextPropertyName));
|
|
|
|
if ((*i)->get
|
|
<String>(Text::TextTypePropertyName) == Text::KeyName) {
|
|
timeT myTime = (*i)->getAbsoluteTime();
|
|
if (myTime == keyAt && text == keyText)
|
|
continue;
|
|
else {
|
|
keyAt = myTime;
|
|
keyText = text;
|
|
}
|
|
}
|
|
|
|
double x = m_rulerScale->getXForTime((*i)->getAbsoluteTime());
|
|
(*i)->set
|
|
<Int>(TEXT_FORMAL_X, (long)x);
|
|
|
|
TQRect textBounds = m_fontMetrics.boundingRect(strtoqstr(text));
|
|
int width = textBounds.width();
|
|
|
|
x -= width / 2;
|
|
if (prevX >= x - 3)
|
|
x = prevX + 3;
|
|
(*i)->set
|
|
<Int>(TEXT_ACTUAL_X, long(x));
|
|
prevX = x + width;
|
|
}
|
|
|
|
for (Segment::iterator i = m_chordSegment->findTime(from);
|
|
i != m_chordSegment->findTime(to); ++i) {
|
|
|
|
if (!(*i)->isa(Text::EventType))
|
|
continue;
|
|
std::string text((*i)->get
|
|
<String>(Text::TextPropertyName));
|
|
std::string type((*i)->get
|
|
<String>(Text::TextTypePropertyName));
|
|
|
|
if (!(*i)->has(TEXT_FORMAL_X))
|
|
continue;
|
|
|
|
long formalX = (*i)->get
|
|
<Int>(TEXT_FORMAL_X);
|
|
long actualX = (*i)->get
|
|
<Int>(TEXT_ACTUAL_X);
|
|
|
|
formalX += m_currentXOffset + long(m_xorigin);
|
|
actualX += m_currentXOffset + long(m_xorigin);
|
|
|
|
paint.drawLine(formalX, height() - 4, formalX, height());
|
|
|
|
if (type == Text::KeyName) {
|
|
paint.setFont(m_boldFont);
|
|
} else {
|
|
paint.setFont(m_font);
|
|
}
|
|
|
|
paint.drawText(actualX, textY, strtoqstr(text));
|
|
}
|
|
}
|
|
|
|
}
|
|
#include "ChordNameRuler.moc"
|