/* -*- 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 , Chris Cannam , Richard Bown 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 #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 #include #include #include #include #include #include #include namespace Rosegarden { ChordNameRuler::ChordNameRuler(RulerScale *rulerScale, RosegardenGUIDoc *doc, double xorigin, int height, QWidget *parent, const char *name) : QWidget(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 = QFontMetrics(m_boldFont); setBackgroundColor(GUIPalette::getColour(GUIPalette::ChordNameRulerBackground)); m_compositionRefreshStatusId = m_composition->getNewRefreshStatusId(); QObject::connect(doc->getCommandHistory(), SIGNAL(commandExecuted()), this, SLOT(update())); QToolTip::add (this, i18n("Chord name ruler.\nTurn it on and off from the Settings->Rulers menu.")); } ChordNameRuler::ChordNameRuler(RulerScale *rulerScale, RosegardenGUIDoc *doc, std::vector &segments, double xorigin, int height, QWidget *parent, const char *name) : QWidget(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 = QFontMetrics(m_boldFont); setBackgroundColor(GUIPalette::getColour(GUIPalette::ChordNameRulerBackground)); m_compositionRefreshStatusId = m_composition->getNewRefreshStatusId(); QObject::connect(doc->getCommandHistory(), SIGNAL(commandExecuted()), this, SLOT(update())); for (std::vector::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); } } QSize 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; QSize res(std::max(int(width), m_width), m_height); return res; } QSize ChordNameRuler::minimumSizeHint() const { double firstBarWidth = m_rulerScale->getBarWidth(0); QSize res = QSize(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 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::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(QPaintEvent* e) { if (!m_composition || !m_ready) return ; NOTATION_DEBUG << "*** Chord Name Ruler: paintEvent" << endl; Profiler profiler1("ChordNameRuler::paintEvent (whole)"); QPainter paint(this); paint.setPen(GUIPalette::getColour(GUIPalette::ChordNameRulerForeground)); paint.setClipRegion(e->region()); paint.setClipRect(e->rect().normalize()); QRect 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)"); QRect 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 (Text::TextPropertyName)); if ((*i)->get (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 (TEXT_FORMAL_X, (long)x); QRect textBounds = m_fontMetrics.boundingRect(strtoqstr(text)); int width = textBounds.width(); x -= width / 2; if (prevX >= x - 3) x = prevX + 3; (*i)->set (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 (Text::TextPropertyName)); std::string type((*i)->get (Text::TextTypePropertyName)); if (!(*i)->has(TEXT_FORMAL_X)) continue; long formalX = (*i)->get (TEXT_FORMAL_X); long actualX = (*i)->get (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"