diff options
Diffstat (limited to 'src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp')
-rw-r--r-- | src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp | 1328 |
1 files changed, 1328 insertions, 0 deletions
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp new file mode 100644 index 0000000..39deb2e --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp @@ -0,0 +1,1328 @@ +/* -*- 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 <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 <cmath> +#include <algorithm> +#include "CompositionModelImpl.h" + +#include "base/BaseProperties.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "AudioPreviewThread.h" +#include "AudioPreviewUpdater.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/SnapGrid.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "CompositionItemHelper.h" +#include "CompositionItemImpl.h" +#include "CompositionModel.h" +#include "CompositionRect.h" +#include "CompositionColourCache.h" +#include "AudioPreviewPainter.h" +#include "gui/general/GUIPalette.h" +#include "SegmentOrderer.h" +#include <qbrush.h> +#include <qcolor.h> +#include <qpen.h> +#include <qpoint.h> +#include <qrect.h> +#include <qregexp.h> +#include <qsize.h> +#include <qstring.h> + + + +namespace Rosegarden +{ + +CompositionModelImpl::CompositionModelImpl(Composition& compo, + Studio& studio, + RulerScale *rulerScale, + int vStep) + : m_composition(compo), + m_studio(studio), + m_grid(rulerScale, vStep), + m_pointerTimePos(0), + m_audioPreviewThread(0) +{ + m_notationPreviewDataCache.setAutoDelete(true); + m_audioPreviewDataCache.setAutoDelete(true); + m_composition.addObserver(this); + + setTrackHeights(); + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + (*i)->addObserver(this); + } +} + +CompositionModelImpl::~CompositionModelImpl() +{ + RG_DEBUG << "CompositionModelImpl::~CompositionModelImpl()" << endl; + + if (!isCompositionDeleted()) { + + m_composition.removeObserver(this); + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + (*i)->removeObserver(this); + } + } + + RG_DEBUG << "CompositionModelImpl::~CompositionModelImpl(): removal from Segment & Composition observers OK" << endl; + + if (m_audioPreviewThread) { + while (!m_audioPreviewUpdaterMap.empty()) { + // Cause any running previews to be cancelled + delete m_audioPreviewUpdaterMap.begin()->second; + m_audioPreviewUpdaterMap.erase(m_audioPreviewUpdaterMap.begin()); + } + } +} + +struct RectCompare { + bool operator()(const QRect &r1, const QRect &r2) const { + return r1.x() < r2.x(); + } +}; + +void CompositionModelImpl::makeNotationPreviewRects(RectRanges* npRects, QPoint basePoint, + const Segment* segment, const QRect& clipRect) +{ + + rectlist* cachedNPData = getNotationPreviewData(segment); + + if (cachedNPData->empty()) + return ; + + rectlist::iterator npEnd = cachedNPData->end(); + + rectlist::iterator npi = std::lower_bound(cachedNPData->begin(), npEnd, clipRect, RectCompare()); + + if (npi == npEnd) + return ; + + if (npi != cachedNPData->begin()) + --npi; + + RectRange interval; + + interval.range.first = npi; + + int segEndX = int(nearbyint(m_grid.getRulerScale()->getXForTime(segment->getEndMarkerTime()))); + int xLim = std::min(clipRect.topRight().x(), segEndX); + + // RG_DEBUG << "CompositionModelImpl::makeNotationPreviewRects : basePoint.x : " + // << basePoint.x() << endl; + + // move iterator forward + // + while (npi->x() < xLim && npi != npEnd) + ++npi; + + interval.range.second = npi; + interval.basePoint.setX(0); + interval.basePoint.setY(basePoint.y()); + interval.color = computeSegmentPreviewColor(segment); + + npRects->push_back(interval); +} + +void CompositionModelImpl::makeNotationPreviewRectsMovingSegment(RectRanges* npRects, QPoint basePoint, + const Segment* segment, const QRect& currentSR) +{ + CompositionRect unmovedSR = computeSegmentRect(*segment); + + rectlist* cachedNPData = getNotationPreviewData(segment); + + if (cachedNPData->empty()) + return ; + + rectlist::iterator npEnd = cachedNPData->end(), + npBegin = cachedNPData->begin(); + + rectlist::iterator npi; + + if (getChangeType() == ChangeResizeFromStart) + npi = std::lower_bound(npBegin, npEnd, currentSR, RectCompare()); + else + npi = std::lower_bound(npBegin, npEnd, unmovedSR, RectCompare()); + + if (npi == npEnd) + return ; + + if (npi != npBegin && getChangeType() != ChangeResizeFromStart) { + --npi; + } + + RectRange interval; + + interval.range.first = npi; + + int xLim = getChangeType() == ChangeMove ? unmovedSR.topRight().x() : currentSR.topRight().x(); + + // RG_DEBUG << "CompositionModelImpl::makeNotationPreviewRectsMovingSegment : basePoint.x : " + // << basePoint.x() << endl; + + // move iterator forward + // + while (npi->x() < xLim && npi != npEnd) + ++npi; + + interval.range.second = npi; + interval.basePoint.setY(basePoint.y()); + + if (getChangeType() == ChangeMove) + interval.basePoint.setX(basePoint.x() - unmovedSR.x()); + else + interval.basePoint.setX(0); + + interval.color = computeSegmentPreviewColor(segment); + + npRects->push_back(interval); +} + +void CompositionModelImpl::makeAudioPreviewRects(AudioPreviewDrawData* apRects, const Segment* segment, + const CompositionRect& segRect, const QRect& clipRect) +{ + Profiler profiler("CompositionModelImpl::makeAudioPreviewRects", true); + RG_DEBUG << "CompositionModelImpl::makeAudioPreviewRects - segRect = " << segRect << endl; + + PixmapArray previewImage = getAudioPreviewPixmap(segment); + + QPoint basePoint = segRect.topLeft(); + + AudioPreviewDrawDataItem previewItem(previewImage, basePoint, segRect); + + if (getChangeType() == ChangeResizeFromStart) { + CompositionRect originalRect = computeSegmentRect(*segment); + previewItem.resizeOffset = segRect.x() - originalRect.x(); + } + + apRects->push_back(previewItem); +} + +void CompositionModelImpl::computeRepeatMarks(CompositionItem& item) +{ + Segment* s = CompositionItemHelper::getSegment(item); + CompositionRect& sr = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item)->getCompRect(); + computeRepeatMarks(sr, s); +} + +void CompositionModelImpl::computeRepeatMarks(CompositionRect& sr, const Segment* s) +{ + if (s->isRepeating()) { + + timeT startTime = s->getStartTime(); + timeT endTime = s->getEndMarkerTime(); + timeT repeatInterval = endTime - startTime; + + if (repeatInterval <= 0) { + // std::cerr << "WARNING: CompositionModelImpl::computeRepeatMarks: Segment at " << startTime << " has repeatInterval " << repeatInterval << std::endl; + // std::cerr << kdBacktrace() << std::endl; + return ; + } + + timeT repeatStart = endTime; + timeT repeatEnd = s->getRepeatEndTime(); + sr.setWidth(int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime, + repeatEnd - startTime)))); + + CompositionRect::repeatmarks repeatMarks; + + // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : repeatStart = " + // << repeatStart << " - repeatEnd = " << repeatEnd << endl; + + for (timeT repeatMark = repeatStart; repeatMark < repeatEnd; repeatMark += repeatInterval) { + int mark = int(nearbyint(m_grid.getRulerScale()->getXForTime(repeatMark))); + // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : mark at " << mark << endl; + repeatMarks.push_back(mark); + } + sr.setRepeatMarks(repeatMarks); + if (repeatMarks.size() > 0) + sr.setBaseWidth(repeatMarks[0] - sr.x()); + else { + // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : no repeat marks\n"; + sr.setBaseWidth(sr.width()); + } + + // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : s = " + // << s << " base width = " << sr.getBaseWidth() + // << " - nb repeat marks = " << repeatMarks.size() << endl; + + } +} + +void CompositionModelImpl::setAudioPreviewThread(AudioPreviewThread *thread) +{ + // std::cerr << "\nCompositionModelImpl::setAudioPreviewThread()\n" << std::endl; + + while (!m_audioPreviewUpdaterMap.empty()) { + // Cause any running previews to be cancelled + delete m_audioPreviewUpdaterMap.begin()->second; + m_audioPreviewUpdaterMap.erase(m_audioPreviewUpdaterMap.begin()); + } + + m_audioPreviewThread = thread; +} + +void CompositionModelImpl::clearPreviewCache() +{ + RG_DEBUG << "CompositionModelImpl::clearPreviewCache\n"; + + m_notationPreviewDataCache.clear(); + m_audioPreviewDataCache.clear(); + m_audioSegmentPreviewMap.clear(); + + for (AudioPreviewUpdaterMap::iterator i = m_audioPreviewUpdaterMap.begin(); + i != m_audioPreviewUpdaterMap.end(); ++i) { + i->second->cancel(); + } + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + if ((*i)->getType() == Segment::Audio) { + // This will create the audio preview updater. The + // preview won't be calculated and cached until the + // updater completes and calls back. + updatePreviewCacheForAudioSegment((*i), 0); + } + } +} + +void CompositionModelImpl::updatePreviewCacheForNotationSegment(const Segment* segment, rectlist* npData) +{ + npData->clear(); + + int segStartX = int(nearbyint(m_grid.getRulerScale()->getXForTime(segment->getStartTime()))); + + bool isPercussion = false; + Track *track = m_composition.getTrackById(segment->getTrack()); + if (track) { + InstrumentId iid = track->getInstrument(); + Instrument *instrument = m_studio.getInstrumentById(iid); + if (instrument && instrument->isPercussion()) isPercussion = true; + } + + for (Segment::iterator i = segment->begin(); + i != segment->end(); ++i) { + + long pitch = 0; + if (!(*i)->isa(Note::EventType) || + !(*i)->get<Int>(BaseProperties::PITCH, pitch)) { + continue; + } + + timeT eventStart = (*i)->getAbsoluteTime(); + timeT eventEnd = eventStart + (*i)->getDuration(); + // if (eventEnd > segment->getEndMarkerTime()) { + // eventEnd = segment->getEndMarkerTime(); + // } + + int x = int(nearbyint(m_grid.getRulerScale()->getXForTime(eventStart))); + int width = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(eventStart, + eventEnd - eventStart))); + + if (x <= segStartX) { + ++x; + if (width > 1) + --width; + } + if (width > 1) + --width; + if (width < 1) + ++width; + + double y0 = 0; + double y1 = m_grid.getYSnap(); + double y = y1 + ((y0 - y1) * (pitch - 16)) / 96; + + int height = 2; + + if (isPercussion) { + height = 3; + if (width > 2) width = 2; + } + + if (y < y0) + y = y0; + if (y > y1 - height + 1) + y = y1 - height + 1; + + QRect r(x, (int)y, width, height); + + // RG_DEBUG << "CompositionModelImpl::updatePreviewCacheForNotationSegment() : npData = " + // << npData << ", preview rect = " + // << r << endl; + npData->push_back(r); + } + +} + +QColor CompositionModelImpl::computeSegmentPreviewColor(const Segment* segment) +{ + // compute the preview color so it's as visible as possible over the segment's color + QColor segColor = GUIPalette::convertColour(m_composition.getSegmentColourMap().getColourByIndex(segment->getColourIndex())); + int h, s, v; + segColor.hsv(&h, &s, &v); + + // colors with saturation lower than value should be pastel tints, and + // they get a value of 0; yellow and green hues close to the dead center + // point for that hue were taking a value of 255 with the (s < v) + // formula, so I added an extra hack to force hues in those two narrow + // ranges toward black. Black always looks good, while white washes out + // badly against intense yellow, and doesn't look very good against + // intense green either... hacky, but this produces pleasant results against + // every bizarre extreme of color I could cook up to throw at it, plus + // (the real reason for all this convoluted fiddling, it does all that while keeping + // white against bright reds and blues, which looks better than black) + if ( ((((h > 57) && (h < 66)) || ((h > 93) && (h < 131))) && (s > 127) && (v > 127) ) || + (s < v) ) { + v = 0; + } else { + v = 255; + } + s = 31; + h += 180; + + segColor.setHsv(h, s, v); + + return segColor; +} + +void CompositionModelImpl::updatePreviewCacheForAudioSegment(const Segment* segment, AudioPreviewData* apData) +{ + if (m_audioPreviewThread) { + // std::cerr << "CompositionModelImpl::updatePreviewCacheForAudioSegment() - new audio preview started" << std::endl; + + CompositionRect segRect = computeSegmentRect(*segment); + segRect.setWidth(segRect.getBaseWidth()); // don't use repeating area + segRect.moveTopLeft(QPoint(0, 0)); + + if (apData) + apData->setSegmentRect(segRect); + + if (m_audioPreviewUpdaterMap.find(segment) == + m_audioPreviewUpdaterMap.end()) { + + AudioPreviewUpdater *updater = new AudioPreviewUpdater + (*m_audioPreviewThread, m_composition, segment, segRect, this); + + connect(updater, SIGNAL(audioPreviewComplete(AudioPreviewUpdater*)), + this, SLOT(slotAudioPreviewComplete(AudioPreviewUpdater*))); + + m_audioPreviewUpdaterMap[segment] = updater; + + } else { + + m_audioPreviewUpdaterMap[segment]->setDisplayExtent(segRect); + } + + m_audioPreviewUpdaterMap[segment]->update(); + + } else { + RG_DEBUG << "CompositionModelImpl::updatePreviewCacheForAudioSegment() - no audio preview thread set\n"; + } +} + +void CompositionModelImpl::slotAudioPreviewComplete(AudioPreviewUpdater* apu) +{ + RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete()\n"; + + AudioPreviewData *apData = getAudioPreviewData(apu->getSegment()); + QRect updateRect; + + if (apData) { + RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete(" << apu << "): apData contains " << apData->getValues().size() << " values already" << endl; + unsigned int channels = 0; + const std::vector<float> &values = apu->getComputedValues(channels); + if (channels > 0) { + RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete: set " + << values.size() << " samples on " << channels << " channels\n"; + apData->setChannels(channels); + apData->setValues(values); + updateRect = postProcessAudioPreview(apData, apu->getSegment()); + } + } + + if (!updateRect.isEmpty()) + emit needContentUpdate(updateRect); +} + +QRect CompositionModelImpl::postProcessAudioPreview(AudioPreviewData* apData, const Segment* segment) +{ + // RG_DEBUG << "CompositionModelImpl::postProcessAudioPreview()\n"; + + AudioPreviewPainter previewPainter(*this, apData, m_composition, segment); + previewPainter.paintPreviewImage(); + + m_audioSegmentPreviewMap[segment] = previewPainter.getPreviewImage(); + + return previewPainter.getSegmentRect(); +} + +void CompositionModelImpl::slotInstrumentParametersChanged(InstrumentId id) +{ + std::cerr << "CompositionModelImpl::slotInstrumentParametersChanged()\n"; + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + Segment* s = *i; + TrackId trackId = s->getTrack(); + Track *track = getComposition().getTrackById(trackId); + + // We need to update the cache for audio segments, because the + // instrument playback level is reflected in the audio + // preview. And we need to update it for midi segments, + // because the preview style differs depending on whether the + // segment is on a percussion instrument or not + + if (track && track->getInstrument() == id) { + removePreviewCache(s); + emit needContentUpdate(computeSegmentRect(*s)); + } + } +} + +void CompositionModelImpl::slotAudioFileFinalized(Segment* s) +{ + // RG_DEBUG << "CompositionModelImpl::slotAudioFileFinalized()\n"; + removePreviewCache(s); +} + +PixmapArray CompositionModelImpl::getAudioPreviewPixmap(const Segment* s) +{ + getAudioPreviewData(s); + return m_audioSegmentPreviewMap[s]; +} + +void CompositionModelImpl::eventAdded(const Segment *s, Event *) +{ + // RG_DEBUG << "CompositionModelImpl::eventAdded()\n"; + removePreviewCache(s); + emit needContentUpdate(computeSegmentRect(*s)); +} + +void CompositionModelImpl::eventRemoved(const Segment *s, Event *) +{ + // RG_DEBUG << "CompositionModelImpl::eventRemoved" << endl; + removePreviewCache(s); + emit needContentUpdate(computeSegmentRect(*s)); +} + +void CompositionModelImpl::appearanceChanged(const Segment *s) +{ + // RG_DEBUG << "CompositionModelImpl::appearanceChanged" << endl; + clearInCache(s, true); + emit needContentUpdate(computeSegmentRect(*s)); +} + +void CompositionModelImpl::endMarkerTimeChanged(const Segment *s, bool shorten) +{ + // RG_DEBUG << "CompositionModelImpl::endMarkerTimeChanged(" << shorten << ")" << endl; + clearInCache(s, true); + if (shorten) { + emit needContentUpdate(); // no longer know former segment dimension + } else { + emit needContentUpdate(computeSegmentRect(*s)); + } +} + +void CompositionModelImpl::makePreviewCache(const Segment *s) +{ + if (s->getType() == Segment::Internal) { + makeNotationPreviewDataCache(s); + } else { + makeAudioPreviewDataCache(s); + } +} + +void CompositionModelImpl::removePreviewCache(const Segment *s) +{ + if (s->getType() == Segment::Internal) { + m_notationPreviewDataCache.remove(const_cast<Segment*>(s)); + } else { + m_audioPreviewDataCache.remove(const_cast<Segment*>(s)); + m_audioSegmentPreviewMap.erase(s); + } + +} + +void CompositionModelImpl::segmentAdded(const Composition *, Segment *s) +{ + std::cerr << "CompositionModelImpl::segmentAdded: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl; + setTrackHeights(s); + + makePreviewCache(s); + s->addObserver(this); + emit needContentUpdate(); +} + +void CompositionModelImpl::segmentRemoved(const Composition *, Segment *s) +{ + setTrackHeights(); + + QRect r = computeSegmentRect(*s); + + m_selectedSegments.erase(s); + + clearInCache(s, true); + s->removeObserver(this); + m_recordingSegments.erase(s); // this could be a recording segment + emit needContentUpdate(r); +} + +void CompositionModelImpl::segmentTrackChanged(const Composition *, Segment *s, TrackId tid) +{ + std::cerr << "CompositionModelImpl::segmentTrackChanged: segment " << s << " on track " << tid << ", calling setTrackHeights" << std::endl; + + // we don't call setTrackHeights(s), because some of the tracks + // above s may have changed height as well (if s was moved off one + // of them) + if (setTrackHeights()) { + std::cerr << "... changed, updating" << std::endl; + emit needContentUpdate(); + } +} + +void CompositionModelImpl::segmentStartChanged(const Composition *, Segment *s, timeT) +{ +// std::cerr << "CompositionModelImpl::segmentStartChanged: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl; + if (setTrackHeights(s)) emit needContentUpdate(); +} + +void CompositionModelImpl::segmentEndMarkerChanged(const Composition *, Segment *s, bool) +{ +// std::cerr << "CompositionModelImpl::segmentEndMarkerChanged: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl; + if (setTrackHeights(s)) { +// std::cerr << "... changed, updating" << std::endl; + emit needContentUpdate(); + } +} + +void CompositionModelImpl::segmentRepeatChanged(const Composition *, Segment *s, bool) +{ + clearInCache(s); + setTrackHeights(s); + emit needContentUpdate(); +} + +void CompositionModelImpl::endMarkerTimeChanged(const Composition *, bool) +{ + emit needSizeUpdate(); +} + +void CompositionModelImpl::setSelectionRect(const QRect& r) +{ + m_selectionRect = r.normalize(); + + m_previousTmpSelectedSegments = m_tmpSelectedSegments; + m_tmpSelectedSegments.clear(); + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + QRect updateRect = m_selectionRect; + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + Segment* s = *i; + CompositionRect sr = computeSegmentRect(*s); + if (sr.intersects(m_selectionRect)) { + m_tmpSelectedSegments.insert(s); + updateRect |= sr; + } + } + + updateRect = updateRect.normalize(); + + if (!updateRect.isNull() && !m_previousSelectionUpdateRect.isNull()) { + + if (m_tmpSelectedSegments != m_previousTmpSelectedSegments) + emit needContentUpdate(updateRect | m_previousSelectionUpdateRect); + + emit needArtifactsUpdate(); + } + + + m_previousSelectionUpdateRect = updateRect; + +} + +void CompositionModelImpl::finalizeSelectionRect() +{ + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + Segment* s = *i; + CompositionRect sr = computeSegmentRect(*s); + if (sr.intersects(m_selectionRect)) { + setSelected(s); + } + } + + m_previousSelectionUpdateRect = m_selectionRect = QRect(); + m_tmpSelectedSegments.clear(); +} + +QRect CompositionModelImpl::getSelectionContentsRect() +{ + QRect selectionRect; + + SegmentSelection sel = getSelectedSegments(); + for (SegmentSelection::iterator i = sel.begin(); + i != sel.end(); ++i) { + + Segment* s = *i; + CompositionRect sr = computeSegmentRect(*s); + selectionRect |= sr; + } + + return selectionRect; +} + +void CompositionModelImpl::addRecordingItem(const CompositionItem& item) +{ + m_recordingSegments.insert(CompositionItemHelper::getSegment(item)); + emit needContentUpdate(); + + RG_DEBUG << "CompositionModelImpl::addRecordingItem: now have " + << m_recordingSegments.size() << " recording items\n"; +} + +void CompositionModelImpl::removeRecordingItem(const CompositionItem &item) +{ + Segment* s = CompositionItemHelper::getSegment(item); + + m_recordingSegments.erase(s); + clearInCache(s, true); + + emit needContentUpdate(); + + RG_DEBUG << "CompositionModelImpl::removeRecordingItem: now have " + << m_recordingSegments.size() << " recording items\n"; +} + +void CompositionModelImpl::clearRecordingItems() +{ + for (recordingsegmentset::iterator i = m_recordingSegments.begin(); + i != m_recordingSegments.end(); ++i) + clearInCache(*i, true); + + m_recordingSegments.clear(); + + emit needContentUpdate(); + RG_DEBUG << "CompositionModelImpl::clearRecordingItem\n"; +} + +bool CompositionModelImpl::isMoving(const Segment* sm) const +{ + itemcontainer::const_iterator movEnd = m_changingItems.end(); + + for (itemcontainer::const_iterator i = m_changingItems.begin(); i != movEnd; ++i) { + const CompositionItemImpl* ci = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)(*i)); + const Segment* s = ci->getSegment(); + if (sm == s) + return true; + } + + return false; +} + +bool CompositionModelImpl::isRecording(const Segment* s) const +{ + return m_recordingSegments.find(const_cast<Segment*>(s)) != m_recordingSegments.end(); +} + +CompositionModel::itemcontainer CompositionModelImpl::getItemsAt(const QPoint& point) +{ + itemcontainer res; + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segments.end(); ++i) { + + Segment* s = *i; + + CompositionRect sr = computeSegmentRect(*s); + if (sr.contains(point)) { + // RG_DEBUG << "CompositionModelImpl::getItemsAt() adding " << sr << " for segment " << s << endl; + CompositionItem item(new CompositionItemImpl(*s, sr)); + unsigned int z = computeZForSegment(s); + // RG_DEBUG << "CompositionModelImpl::getItemsAt() z = " << z << endl; + item->setZ(z); + res.insert(item); + } else { + // RG_DEBUG << "CompositionModelImpl::getItemsAt() skiping " << sr << endl; + } + + } + + if (res.size() == 1) { // only one segment under click point + Segment* s = CompositionItemHelper::getSegment(*(res.begin())); + m_segmentOrderer.segmentClicked(s); + } + + return res; +} + +void CompositionModelImpl::setPointerPos(int xPos) +{ + m_pointerTimePos = grid().getRulerScale()->getTimeForX(xPos); + + for (recordingsegmentset::iterator i = m_recordingSegments.begin(); + i != m_recordingSegments.end(); ++i) { + emit needContentUpdate(computeSegmentRect(**i)); + } +} + +void CompositionModelImpl::setSelected(const CompositionItem& item, bool selected) +{ + const CompositionItemImpl* itemImpl = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)item); + if (itemImpl) { + Segment* segment = const_cast<Segment*>(itemImpl->getSegment()); + setSelected(segment, selected); + } +} + +void CompositionModelImpl::setSelected(const itemcontainer& items) +{ + for (itemcontainer::const_iterator i = items.begin(); i != items.end(); ++i) { + setSelected(*i); + } +} + +void CompositionModelImpl::setSelected(const Segment* segment, bool selected) +{ + RG_DEBUG << "CompositionModelImpl::setSelected " << segment << " - " << selected << endl; + if (selected) { + if (!isSelected(segment)) + m_selectedSegments.insert(const_cast<Segment*>(segment)); + } else { + SegmentSelection::iterator i = m_selectedSegments.find(const_cast<Segment*>(segment)); + if (i != m_selectedSegments.end()) + m_selectedSegments.erase(i); + } + emit needContentUpdate(); +} + +void CompositionModelImpl::signalSelection() +{ + // RG_DEBUG << "CompositionModelImpl::signalSelection()\n"; + emit selectedSegments(getSelectedSegments()); +} + +void CompositionModelImpl::signalContentChange() +{ + // RG_DEBUG << "CompositionModelImpl::signalContentChange" << endl; + emit needContentUpdate(); +} + +void CompositionModelImpl::clearSelected() +{ + RG_DEBUG << "CompositionModelImpl::clearSelected" << endl; + m_selectedSegments.clear(); + emit needContentUpdate(); +} + +bool CompositionModelImpl::isSelected(const CompositionItem& ci) const +{ + const CompositionItemImpl* itemImpl = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)ci); + return itemImpl ? isSelected(itemImpl->getSegment()) : 0; +} + +bool CompositionModelImpl::isSelected(const Segment* s) const +{ + return m_selectedSegments.find(const_cast<Segment*>(s)) != m_selectedSegments.end(); +} + +bool CompositionModelImpl::isTmpSelected(const Segment* s) const +{ + return m_tmpSelectedSegments.find(const_cast<Segment*>(s)) != m_tmpSelectedSegments.end(); +} + +bool CompositionModelImpl::wasTmpSelected(const Segment* s) const +{ + return m_previousTmpSelectedSegments.find(const_cast<Segment*>(s)) != m_previousTmpSelectedSegments.end(); +} + +void CompositionModelImpl::startChange(const CompositionItem& item, CompositionModel::ChangeType change) +{ + m_changeType = change; + + itemcontainer::iterator i = m_changingItems.find(item); + + // if an "identical" composition item has already been inserted, drop this one + if (i != m_changingItems.end()) { + RG_DEBUG << "CompositionModelImpl::startChange : item already in\n"; + m_itemGC.push_back(item); + } else { + item->saveRect(); + m_changingItems.insert(item); + } +} + +void CompositionModelImpl::startChangeSelection(CompositionModel::ChangeType change) +{ + SegmentSelection::iterator i = m_selectedSegments.begin(); + for (; i != m_selectedSegments.end(); ++i) { + Segment* s = *i; + CompositionRect sr = computeSegmentRect(*s); + startChange(CompositionItem(new CompositionItemImpl(*s, sr)), change); + } + +} + +void CompositionModelImpl::endChange() +{ + for (itemcontainer::const_iterator i = m_changingItems.begin(); i != m_changingItems.end(); ++i) { + delete *i; + } + + m_changingItems.clear(); + + for (itemgc::iterator i = m_itemGC.begin(); i != m_itemGC.end(); ++i) { + delete *i; + } + m_itemGC.clear(); + RG_DEBUG << "CompositionModelImpl::endChange\n"; + emit needContentUpdate(); +} + +void CompositionModelImpl::setLength(int width) +{ + timeT endMarker = m_grid.snapX(width); + m_composition.setEndMarker(endMarker); +} + +int CompositionModelImpl::getLength() +{ + timeT endMarker = m_composition.getEndMarker(); + int w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(0, endMarker))); + return w; +} + +timeT CompositionModelImpl::getRepeatTimeAt(const QPoint& p, const CompositionItem& cItem) +{ + // timeT timeAtClick = m_grid.getRulerScale()->getTimeForX(p.x()); + + CompositionItemImpl* itemImpl = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)cItem); + + const Segment* s = itemImpl->getSegment(); + + timeT startTime = s->getStartTime(); + timeT endTime = s->getEndMarkerTime(); + timeT repeatInterval = endTime - startTime; + + int rWidth = int(nearbyint(m_grid.getRulerScale()->getXForTime(repeatInterval))); + + int count = (p.x() - int(itemImpl->rect().x())) / rWidth; + RG_DEBUG << "CompositionModelImpl::getRepeatTimeAt() : count = " << count << endl; + + return count != 0 ? startTime + (count * (s->getEndMarkerTime() - s->getStartTime())) : 0; +} + +bool CompositionModelImpl::setTrackHeights(Segment *s) +{ + bool heightsChanged = false; + +// std::cerr << "CompositionModelImpl::setTrackHeights" << std::endl; + + for (Composition::trackcontainer::const_iterator i = + m_composition.getTracks().begin(); + i != m_composition.getTracks().end(); ++i) { + + if (s && i->first != s->getTrack()) continue; + + int max = m_composition.getMaxContemporaneousSegmentsOnTrack(i->first); + if (max == 0) max = 1; + +// std::cerr << "for track " << i->first << ": height = " << max << ", old height = " << m_trackHeights[i->first] << std::endl; + + if (max != m_trackHeights[i->first]) { + heightsChanged = true; + m_trackHeights[i->first] = max; + } + + m_grid.setBinHeightMultiple(i->second->getPosition(), max); + } + + if (heightsChanged) { +// std::cerr << "CompositionModelImpl::setTrackHeights: heights have changed" << std::endl; + for (Composition::segmentcontainer::iterator i = m_composition.begin(); + i != m_composition.end(); ++i) { + computeSegmentRect(**i); + } + } + + return heightsChanged; +} + +QPoint CompositionModelImpl::computeSegmentOrigin(const Segment& s) +{ + // Profiler profiler("CompositionModelImpl::computeSegmentOrigin", true); + + int trackPosition = m_composition.getTrackPositionById(s.getTrack()); + timeT startTime = s.getStartTime(); + + QPoint res; + + res.setX(int(nearbyint(m_grid.getRulerScale()->getXForTime(startTime)))); + + res.setY(m_grid.getYBinCoordinate(trackPosition) + + m_composition.getSegmentVoiceIndex(&s) * + m_grid.getYSnap() + 1); + + return res; +} + +bool CompositionModelImpl::isCachedRectCurrent(const Segment& s, const CompositionRect& r, QPoint cachedSegmentOrigin, timeT cachedSegmentEndTime) +{ + return s.isRepeating() == r.isRepeating() && + ((cachedSegmentOrigin.x() != r.x() && s.getEndMarkerTime() != cachedSegmentEndTime) || + (cachedSegmentOrigin.x() == r.x() && s.getEndMarkerTime() == cachedSegmentEndTime)); +} + +void CompositionModelImpl::clearInCache(const Segment* s, bool clearPreview) +{ + if (s) { + m_segmentRectMap.erase(s); + m_segmentEndTimeMap.erase(s); + if (clearPreview) + removePreviewCache(s); + } else { // clear the whole cache + m_segmentRectMap.clear(); + m_segmentEndTimeMap.clear(); + if (clearPreview) + clearPreviewCache(); + } +} + +void CompositionModelImpl::putInCache(const Segment*s, const CompositionRect& cr) +{ + m_segmentRectMap[s] = cr; + m_segmentEndTimeMap[s] = s->getEndMarkerTime(); +} + +CompositionRect CompositionModelImpl::computeSegmentRect(const Segment& s, bool computeZ) +{ + // Profiler profiler("CompositionModelImpl::computeSegmentRect", true); + + QPoint origin = computeSegmentOrigin(s); + + bool isRecordingSegment = isRecording(&s); + + if (!isRecordingSegment) { + timeT endTime = 0; + + CompositionRect cachedCR = getFromCache(&s, endTime); + // don't cache repeating segments - it's just hopeless, because the segment's rect may have to be recomputed + // in other cases than just when the segment itself is moved, + // for instance if another segment is moved over it + if (!s.isRepeating() && cachedCR.isValid() && isCachedRectCurrent(s, cachedCR, origin, endTime)) { + // RG_DEBUG << "CompositionModelImpl::computeSegmentRect() : using cache for seg " + // << &s << " - cached rect repeating = " << cachedCR.isRepeating() << " - base width = " + // << cachedCR.getBaseWidth() << endl; + + bool xChanged = origin.x() != cachedCR.x(); + bool yChanged = origin.y() != cachedCR.y(); + + cachedCR.moveTopLeft(origin); + + if (s.isRepeating() && (xChanged || yChanged)) { // update repeat marks + + // this doesn't work in the general case (if there's another segment on the same track for instance), + // it's better to simply recompute all the marks + // CompositionRect::repeatmarks repeatMarks = cachedCR.getRepeatMarks(); + // for(unsigned int i = 0; i < repeatMarks.size(); ++i) { + // repeatMarks[i] += deltaX; + // } + // cachedCR.setRepeatMarks(repeatMarks); + computeRepeatMarks(cachedCR, &s); + } + putInCache(&s, cachedCR); + return cachedCR; + } + } + + timeT startTime = s.getStartTime(); + timeT endTime = isRecordingSegment ? m_pointerTimePos /*s.getEndTime()*/ : s.getEndMarkerTime(); + + + int h = m_grid.getYSnap() - 2; + int w; + + RG_DEBUG << "CompositionModelImpl::computeSegmentRect: x " << origin.x() << ", y " << origin.y() << " startTime " << startTime << ", endTime " << endTime << endl; + + if (s.isRepeating()) { + timeT repeatStart = endTime; + timeT repeatEnd = s.getRepeatEndTime(); + w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime, + repeatEnd - startTime))); + // RG_DEBUG << "CompositionModelImpl::computeSegmentRect : s is repeating - repeatStart = " + // << repeatStart << " - repeatEnd : " << repeatEnd + // << " w = " << w << endl; + } else { + w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime, endTime - startTime))); + // RG_DEBUG << "CompositionModelImpl::computeSegmentRect : s is NOT repeating" + // << " w = " << w << " (x for time at start is " << m_grid.getRulerScale()->getXForTime(startTime) << ", end is " << m_grid.getRulerScale()->getXForTime(endTime) << ")" << endl; + } + + CompositionRect cr(origin, QSize(w, h)); + QString label = strtoqstr(s.getLabel()); + if (s.getType() == Segment::Audio) { + static QRegExp re1("( *\\([^)]*\\))*$"); // (inserted) (copied) (etc) + static QRegExp re2("\\.[^.]+$"); // filename suffix + label.replace(re1, "").replace(re2, ""); + } + cr.setLabel(label); + + if (s.isRepeating()) { + computeRepeatMarks(cr, &s); + } else { + cr.setBaseWidth(cr.width()); + } + + putInCache(&s, cr); + + return cr; +} + +unsigned int CompositionModelImpl::computeZForSegment(const Rosegarden::Segment* s) +{ + return m_segmentOrderer.getZForSegment(s); +} + +const CompositionRect& CompositionModelImpl::getFromCache(const Rosegarden::Segment* s, timeT& endTime) +{ + endTime = m_segmentEndTimeMap[s]; + return m_segmentRectMap[s]; +} + +unsigned int CompositionModelImpl::getNbRows() +{ + return m_composition.getNbTracks(); +} + +const CompositionModel::rectcontainer& CompositionModelImpl::getRectanglesIn(const QRect& rect, + RectRanges* npData, + AudioPreviewDrawData* apData) +{ + // Profiler profiler("CompositionModelImpl::getRectanglesIn", true); + + m_res.clear(); + + // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: ruler scale is " + // << (dynamic_cast<SimpleRulerScale *>(m_grid.getRulerScale()))->getUnitsPerPixel() << endl; + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: Composition contains segment " << *i << " (" << (*i)->getStartTime() << "->" << (*i)->getEndTime() << ")"<< endl; + + Segment* s = *i; + + if (isMoving(s)) + continue; + + CompositionRect sr = computeSegmentRect(*s); + // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: seg rect = " << sr << endl; + + if (sr.intersects(rect)) { + bool tmpSelected = isTmpSelected(s), + pTmpSelected = wasTmpSelected(s); + +// RG_DEBUG << "CompositionModelImpl::getRectanglesIn: segment " << s +// << " selected : " << isSelected(s) << " - tmpSelected : " << isTmpSelected(s) << endl; + + if (isSelected(s) || isTmpSelected(s) || sr.intersects(m_selectionRect)) { + sr.setSelected(true); + } + + if (pTmpSelected != tmpSelected) + sr.setNeedsFullUpdate(true); + + bool isAudio = (s && s->getType() == Segment::Audio); + + if (!isRecording(s)) { + QColor brushColor = GUIPalette::convertColour(m_composition. + getSegmentColourMap().getColourByIndex(s->getColourIndex())); + sr.setBrush(brushColor); + sr.setPen(CompositionColourCache::getInstance()->SegmentBorder); + } else { + // border is the same for both audio and MIDI + sr.setPen(CompositionColourCache::getInstance()->RecordingSegmentBorder); + // audio color + if (isAudio) { + sr.setBrush(CompositionColourCache::getInstance()->RecordingAudioSegmentBlock); + // MIDI/default color + } else { + sr.setBrush(CompositionColourCache::getInstance()->RecordingInternalSegmentBlock); + } + } + + // Notation preview data + if (npData && s->getType() == Segment::Internal) { + makeNotationPreviewRects(npData, QPoint(0, sr.y()), s, rect); + // Audio preview data + } else if (apData && s->getType() == Segment::Audio) { + makeAudioPreviewRects(apData, s, sr, rect); + } + + m_res.push_back(sr); + } else { + // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: - segment out of rect\n"; + } + + } + + // changing items + + itemcontainer::iterator movEnd = m_changingItems.end(); + for (itemcontainer::iterator i = m_changingItems.begin(); i != movEnd; ++i) { + CompositionRect sr((*i)->rect()); + if (sr.intersects(rect)) { + Segment* s = CompositionItemHelper::getSegment(*i); + sr.setSelected(true); + QColor brushColor = GUIPalette::convertColour(m_composition.getSegmentColourMap().getColourByIndex(s->getColourIndex())); + sr.setBrush(brushColor); + + sr.setPen(CompositionColourCache::getInstance()->SegmentBorder); + + // Notation preview data + if (npData && s->getType() == Segment::Internal) { + makeNotationPreviewRectsMovingSegment(npData, sr.topLeft(), s, sr); + // Audio preview data + } else if (apData && s->getType() == Segment::Audio) { + makeAudioPreviewRects(apData, s, sr, rect); + } + + m_res.push_back(sr); + } + } + + return m_res; +} + +CompositionModel::heightlist +CompositionModelImpl::getTrackDividersIn(const QRect& rect) +{ + int top = m_grid.getYBin(rect.y()); + int bottom = m_grid.getYBin(rect.y() + rect.height()); + +// std::cerr << "CompositionModelImpl::getTrackDividersIn: rect " +// << rect.x() << ", " << rect.y() << ", " +// << rect.width() << "x" << rect.height() << ", top = " << top +// << ", bottom = " << bottom << std::endl; + + CompositionModel::heightlist list; + + for (int pos = top; pos <= bottom; ++pos) { + int divider = m_grid.getYBinCoordinate(pos); + list.push_back(divider); +// std::cerr << "divider at " << divider << std::endl; + } + + return list; +} + +CompositionModel::rectlist* CompositionModelImpl::getNotationPreviewData(const Segment* s) +{ + rectlist* npData = m_notationPreviewDataCache[const_cast<Segment*>(s)]; + + if (!npData) { + npData = makeNotationPreviewDataCache(s); + } + + return npData; +} + +CompositionModel::AudioPreviewData* CompositionModelImpl::getAudioPreviewData(const Segment* s) +{ + // Profiler profiler("CompositionModelImpl::getAudioPreviewData", true); + RG_DEBUG << "CompositionModelImpl::getAudioPreviewData\n"; + + AudioPreviewData* apData = m_audioPreviewDataCache[const_cast<Segment*>(s)]; + + if (!apData) { + apData = makeAudioPreviewDataCache(s); + } + + RG_DEBUG << "CompositionModelImpl::getAudioPreviewData returning\n"; + return apData; +} + +CompositionModel::rectlist* CompositionModelImpl::makeNotationPreviewDataCache(const Segment *s) +{ + rectlist* npData = new rectlist(); + updatePreviewCacheForNotationSegment(s, npData); + m_notationPreviewDataCache.insert(const_cast<Segment*>(s), npData); + return npData; +} + +CompositionModel::AudioPreviewData* CompositionModelImpl::makeAudioPreviewDataCache(const Segment *s) +{ + RG_DEBUG << "CompositionModelImpl::makeAudioPreviewDataCache(" << s << ")" << endl; + + AudioPreviewData* apData = new AudioPreviewData(false, 0); // 0 channels -> empty + updatePreviewCacheForAudioSegment(s, apData); + m_audioPreviewDataCache.insert(const_cast<Segment*>(s), apData); + return apData; +} + +} +#include "CompositionModelImpl.moc" |