X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fclipitem.cpp;h=1b493385ae4661d36d449d56581480c230129950;hb=c5720a8894dd5a0dbd06f3330299dc669454fa06;hp=4820f3dc1c0dfd6282c7f5d0ff0538adef3a2bb3;hpb=6f413e334a82cbbf5bc3a9d920a14d3e50268ab3;p=kdenlive diff --git a/src/clipitem.cpp b/src/clipitem.cpp index 4820f3dc..1b493385 100644 --- a/src/clipitem.cpp +++ b/src/clipitem.cpp @@ -32,26 +32,28 @@ #include "clipitem.h" #include "customtrackview.h" +#include "customtrackscene.h" #include "renderer.h" #include "docclipbase.h" #include "transition.h" -#include "events.h" #include "kdenlivesettings.h" #include "kthumb.h" -ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, GenTime cropStart, double scale, double fps) - : AbstractClipItem(info, QRectF(), fps), m_clip(clip), m_resizeMode(NONE), m_grabPoint(0), m_maxTrack(0), m_hasThumbs(false), startThumbTimer(NULL), endThumbTimer(NULL), m_effectsCounter(1), audioThumbWasDrawn(false), m_opacity(1.0), m_timeLine(0), m_thumbsRequested(0), m_startFade(0), m_endFade(0), m_hover(false) { - QRectF rect((double) info.startPos.frames(fps) * scale, (double)(info.track * KdenliveSettings::trackheight() + 1), (double)(info.endPos - info.startPos).frames(fps) * scale, (double)(KdenliveSettings::trackheight() - 1)); - setRect(rect); + +ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, double fps, bool generateThumbs) + : AbstractClipItem(info, QRectF(), fps), m_clip(clip), m_resizeMode(NONE), m_grabPoint(0), m_maxTrack(0), m_hasThumbs(false), startThumbTimer(NULL), endThumbTimer(NULL), audioThumbWasDrawn(false), m_opacity(1.0), m_timeLine(0), m_startThumbRequested(false), m_endThumbRequested(false), m_startFade(0), m_endFade(0), m_hover(false), m_selectedEffect(-1), m_speed(1.0), framePixelWidth(0), m_startPix(QPixmap()), m_endPix(QPixmap()) { + setZValue(1); + setRect(0, 0, (info.endPos - info.startPos).frames(fps) - 0.02, (double)(KdenliveSettings::trackheight() - 2)); + setPos(info.startPos.frames(fps), (double)(info.track * KdenliveSettings::trackheight()) + 1); m_clipName = clip->name(); m_producer = clip->getId(); m_clipType = clip->clipType(); - m_cropStart = cropStart; - + m_cropStart = info.cropStart; m_maxDuration = clip->maxDuration(); setAcceptDrops(true); audioThumbReady = clip->audioThumbCreated(); + /* m_cropStart = xml.attribute("in", 0).toInt(); m_maxDuration = xml.attribute("duration", 0).toInt(); @@ -63,28 +65,35 @@ ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, GenTime cropStart, double s setFlags(QGraphicsItem::ItemClipsToShape | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); setAcceptsHoverEvents(true); - connect(this , SIGNAL(prepareAudioThumb(double, QPainterPath, int, int)) , this, SLOT(slotPrepareAudioThumb(double, QPainterPath, int, int))); + connect(this , SIGNAL(prepareAudioThumb(double, int, int, int)) , this, SLOT(slotPrepareAudioThumb(double, int, int, int))); setBrush(QColor(141, 166, 215)); - if (m_clipType == VIDEO || m_clipType == AV || m_clipType == SLIDESHOW) { + if (m_clipType == VIDEO || m_clipType == AV || m_clipType == SLIDESHOW || m_clipType == PLAYLIST) { m_hasThumbs = true; - connect(this, SIGNAL(getThumb(int, int)), clip->thumbProducer(), SLOT(extractImage(int, int))); - connect(clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap))); - connect(clip, SIGNAL(gotAudioData()), this, SLOT(slotGotAudioData())); - QTimer::singleShot(300, this, SLOT(slotFetchThumbs())); - startThumbTimer = new QTimer(this); startThumbTimer->setSingleShot(true); connect(startThumbTimer, SIGNAL(timeout()), this, SLOT(slotGetStartThumb())); endThumbTimer = new QTimer(this); endThumbTimer->setSingleShot(true); connect(endThumbTimer, SIGNAL(timeout()), this, SLOT(slotGetEndThumb())); + + connect(this, SIGNAL(getThumb(int, int)), clip->thumbProducer(), SLOT(extractImage(int, int))); + //connect(this, SIGNAL(getThumb(int, int)), clip->thumbProducer(), SLOT(getVideoThumbs(int, int))); + + connect(clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap))); + connect(clip, SIGNAL(gotAudioData()), this, SLOT(slotGotAudioData())); + if (generateThumbs) QTimer::singleShot(200, this, SLOT(slotFetchThumbs())); + + /*if (m_clip->producer()) { + videoThumbProducer.init(this, m_clip->producer(), KdenliveSettings::trackheight() * KdenliveSettings::project_display_ratio(), KdenliveSettings::trackheight()); + slotFetchThumbs(); + }*/ } else if (m_clipType == COLOR) { QString colour = clip->getProperty("colour"); colour = colour.replace(0, 2, "#"); setBrush(QColor(colour.left(7))); } else if (m_clipType == IMAGE || m_clipType == TEXT) { - m_startPix = KThumb::getImage(KUrl(clip->getProperty("resource")), (int)(50 * KdenliveSettings::project_display_ratio()), 50); + m_startPix = KThumb::getImage(KUrl(clip->getProperty("resource")), (int)(KdenliveSettings::trackheight() * KdenliveSettings::project_display_ratio()), KdenliveSettings::trackheight()); m_endPix = m_startPix; } else if (m_clipType == AUDIO) { connect(clip, SIGNAL(gotAudioData()), this, SLOT(slotGotAudioData())); @@ -95,9 +104,236 @@ ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, GenTime cropStart, double s ClipItem::~ClipItem() { if (startThumbTimer) delete startThumbTimer; if (endThumbTimer) delete endThumbTimer; + if (m_timeLine) m_timeLine; +} + +ClipItem *ClipItem::clone(ItemInfo info) const { + ClipItem *duplicate = new ClipItem(m_clip, info, m_fps); + if (info.cropStart == cropStart()) duplicate->slotSetStartThumb(m_startPix); + if (info.cropStart + (info.endPos - info.startPos) == m_cropStart + m_cropDuration) duplicate->slotSetEndThumb(m_endPix); + kDebug() << "// CLoning clip: " << (info.cropStart + (info.endPos - info.startPos)).frames(m_fps) << ", CURRENT end: " << (cropStart() + duration()).frames(m_fps); + duplicate->setEffectList(m_effectList.clone()); + duplicate->setSpeed(m_speed); + return duplicate; +} + +void ClipItem::setEffectList(const EffectsList effectList) { + m_effectList = effectList; + m_effectNames = m_effectList.effectNames().join(" / "); +} + +const EffectsList ClipItem::effectList() { + return m_effectList; +} + +int ClipItem::selectedEffectIndex() const { + return m_selectedEffect; +} + +void ClipItem::initEffect(QDomElement effect) { + // the kdenlive_ix int is used to identify an effect in mlt's playlist, should + // not be changed + if (effect.attribute("kdenlive_ix").toInt() == 0) + effect.setAttribute("kdenlive_ix", QString::number(effectsCounter())); + // init keyframes if required + QDomNodeList params = effect.elementsByTagName("parameter"); + for (int i = 0; i < params.count(); i++) { + QDomElement e = params.item(i).toElement(); + kDebug() << "// inint eff: " << e.attribute("name"); + if (!e.isNull() && e.attribute("type") == "keyframe") { + QString def = e.attribute("default"); + // Effect has a keyframe type parameter, we need to set the values + if (e.attribute("keyframes").isEmpty()) { + e.setAttribute("keyframes", QString::number(m_cropStart.frames(m_fps)) + ":" + def + ";" + QString::number((m_cropStart + m_cropDuration).frames(m_fps)) + ":" + def); + //kDebug() << "///// EFFECT KEYFRAMES INITED: " << e.attribute("keyframes"); + break; + } + } + } + + if (effect.attribute("tag") == "volume") { + if (effect.attribute("id") == "fadeout") { + int end = (duration() + cropStart()).frames(m_fps); + int start = end - EffectsList::parameter(effect, "in").toInt(); + EffectsList::setParameter(effect, "in", QString::number(start)); + EffectsList::setParameter(effect, "out", QString::number(end)); + } else if (effect.attribute("id") == "fadein") { + int start = cropStart().frames(m_fps); + int end = start + EffectsList::parameter(effect, "out").toInt(); + EffectsList::setParameter(effect, "in", QString::number(start)); + EffectsList::setParameter(effect, "out", QString::number(end)); + } + } +} + +bool ClipItem::checkKeyFrames() { + bool clipEffectsModified = false; + for (int ix = 0; ix < m_effectList.count(); ix ++) { + QString kfr = keyframes(ix); + if (!kfr.isEmpty()) { + const QStringList keyframes = kfr.split(";", QString::SkipEmptyParts); + QStringList newKeyFrames; + bool cutKeyFrame = false; + bool modified = false; + int lastPos = -1; + double lastValue = -1; + int start = m_cropStart.frames(m_fps); + int end = (m_cropStart + m_cropDuration).frames(m_fps); + foreach(const QString str, keyframes) { + int pos = str.section(":", 0, 0).toInt(); + double val = str.section(":", 1, 1).toDouble(); + if (pos - start < 0) { + // a keyframe is defined before the start of the clip + cutKeyFrame = true; + } else if (cutKeyFrame) { + // create new keyframe at clip start, calculate interpolated value + if (pos > start) { + int diff = pos - lastPos; + double ratio = (double)(start - lastPos) / diff; + double newValue = lastValue + (val - lastValue) * ratio; + newKeyFrames.append(QString::number(start) + ":" + QString::number(newValue)); + modified = true; + } + cutKeyFrame = false; + } + if (!cutKeyFrame) { + if (pos > end) { + // create new keyframe at clip end, calculate interpolated value + int diff = pos - lastPos; + if (diff != 0) { + double ratio = (double)(end - lastPos) / diff; + double newValue = lastValue + (val - lastValue) * ratio; + newKeyFrames.append(QString::number(end) + ":" + QString::number(newValue)); + modified = true; + } + break; + } else { + newKeyFrames.append(QString::number(pos) + ":" + QString::number(val)); + } + } + lastPos = pos; + lastValue = val; + } + if (modified) { + // update KeyFrames + setKeyframes(ix, newKeyFrames.join(";")); + clipEffectsModified = true; + } + } + } + return clipEffectsModified; +} + +void ClipItem::setKeyframes(const int ix, const QString keyframes) { + QDomElement effect = effectAt(ix); + if (effect.attribute("disabled") == "1") return; + QDomNodeList params = effect.elementsByTagName("parameter"); + for (int i = 0; i < params.count(); i++) { + QDomElement e = params.item(i).toElement(); + if (!e.isNull() && e.attribute("type") == "keyframe") { + e.setAttribute("keyframes", keyframes); + if (ix == m_selectedEffect) { + m_keyframes.clear(); + double max = e.attribute("max").toDouble(); + double min = e.attribute("min").toDouble(); + m_keyframeFactor = 100.0 / (max - min); + m_keyframeDefault = e.attribute("default").toDouble(); + // parse keyframes + const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts); + foreach(const QString str, keyframes) { + int pos = str.section(":", 0, 0).toInt(); + double val = str.section(":", 1, 1).toDouble(); + m_keyframes[pos] = val; + } + update(); + return; + } + break; + } + } +} + + +void ClipItem::setSelectedEffect(const int ix) { + m_selectedEffect = ix; + QDomElement effect = effectAt(m_selectedEffect); + QDomNodeList params = effect.elementsByTagName("parameter"); + if (effect.attribute("disabled") != "1") + for (int i = 0; i < params.count(); i++) { + QDomElement e = params.item(i).toElement(); + if (!e.isNull() && e.attribute("type") == "keyframe") { + m_keyframes.clear(); + double max = e.attribute("max").toDouble(); + double min = e.attribute("min").toDouble(); + m_keyframeFactor = 100.0 / (max - min); + m_keyframeDefault = e.attribute("default").toDouble(); + // parse keyframes + const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts); + foreach(const QString str, keyframes) { + int pos = str.section(":", 0, 0).toInt(); + double val = str.section(":", 1, 1).toDouble(); + m_keyframes[pos] = val; + } + update(); + return; + } + } + if (!m_keyframes.isEmpty()) { + m_keyframes.clear(); + update(); + } +} + +QString ClipItem::keyframes(const int index) { + QString result; + QDomElement effect = effectAt(index); + QDomNodeList params = effect.elementsByTagName("parameter"); + + for (int i = 0; i < params.count(); i++) { + QDomElement e = params.item(i).toElement(); + if (!e.isNull() && e.attribute("type") == "keyframe") { + result = e.attribute("keyframes"); + break; + } + } + return result; +} + +void ClipItem::updateKeyframeEffect() { + // regenerate xml parameter from the clip keyframes + QDomElement effect = effectAt(m_selectedEffect); + if (effect.attribute("disabled") == "1") return; + QDomNodeList params = effect.elementsByTagName("parameter"); + + for (int i = 0; i < params.count(); i++) { + QDomElement e = params.item(i).toElement(); + if (!e.isNull() && e.attribute("type") == "keyframe") { + QString keyframes; + if (m_keyframes.count() > 1) { + QMap::const_iterator i = m_keyframes.constBegin(); + double x1; + double y1; + while (i != m_keyframes.constEnd()) { + keyframes.append(QString::number(i.key()) + ":" + QString::number(i.value()) + ";"); + ++i; + } + } + // Effect has a keyframe type parameter, we need to set the values + //kDebug() << "::::::::::::::: SETTING EFFECT KEYFRAMES: " << keyframes; + e.setAttribute("keyframes", keyframes); + break; + } + } +} + +QDomElement ClipItem::selectedEffect() { + if (m_selectedEffect == -1 || m_effectList.isEmpty()) return QDomElement(); + return effectAt(m_selectedEffect); } void ClipItem::resetThumbs() { + m_startPix = QPixmap(); + m_endPix = QPixmap(); slotFetchThumbs(); audioThumbCachePic.clear(); } @@ -105,50 +341,123 @@ void ClipItem::resetThumbs() { void ClipItem::refreshClip() { m_maxDuration = m_clip->maxDuration(); - if (m_clipType == VIDEO || m_clipType == AV || m_clipType == SLIDESHOW) slotFetchThumbs(); - else if (m_clipType == COLOR) { + if (m_clipType == COLOR) { QString colour = m_clip->getProperty("colour"); colour = colour.replace(0, 2, "#"); setBrush(QColor(colour.left(7))); - } else if (m_clipType == IMAGE || m_clipType == TEXT) { - m_startPix = KThumb::getImage(KUrl(m_clip->getProperty("resource")), (int)(50 * KdenliveSettings::project_display_ratio()), 50); - m_endPix = m_startPix; - } + } else slotFetchThumbs(); } void ClipItem::slotFetchThumbs() { - m_thumbsRequested += 2; - emit getThumb((int)m_cropStart.frames(m_fps), (int)(m_cropStart + m_cropDuration).frames(m_fps)); + if (m_endPix.isNull() && m_startPix.isNull()) { + m_startThumbRequested = true; + m_endThumbRequested = true; + emit getThumb((int)cropStart().frames(m_fps), (int)(cropStart() + cropDuration()).frames(m_fps) - 1); + } else { + if (m_endPix.isNull()) { + slotGetEndThumb(); + } + if (m_startPix.isNull()) { + slotGetStartThumb(); + } + } + /* + if (m_hasThumbs) { + if (m_endPix.isNull() && m_startPix.isNull()) { + int frame1 = (int)m_cropStart.frames(m_fps); + int frame2 = (int)(m_cropStart + m_cropDuration).frames(m_fps) - 1; + //videoThumbProducer.setThumbFrames(m_clip->producer(), frame1, frame2); + //videoThumbProducer.start(QThread::LowestPriority); + } else { + if (m_endPix.isNull()) slotGetEndThumb(); + else slotGetStartThumb(); + } + + } else if (m_startPix.isNull()) slotGetStartThumb();*/ } void ClipItem::slotGetStartThumb() { - m_thumbsRequested++; - emit getThumb((int)m_cropStart.frames(m_fps), -1); + m_startThumbRequested = true; + emit getThumb((int)cropStart().frames(m_fps), -1); + //videoThumbProducer.setThumbFrames(m_clip->producer(), (int)m_cropStart.frames(m_fps), - 1); + //videoThumbProducer.start(QThread::LowestPriority); } void ClipItem::slotGetEndThumb() { - m_thumbsRequested++; - emit getThumb(-1, (int)(m_cropStart + m_cropDuration).frames(m_fps)); + m_endThumbRequested = true; + emit getThumb(-1, (int)(cropStart() + cropDuration()).frames(m_fps) - 1); + //videoThumbProducer.setThumbFrames(m_clip->producer(), -1, (int)(m_cropStart + m_cropDuration).frames(m_fps) - 1); + //videoThumbProducer.start(QThread::LowestPriority); +} + + +void ClipItem::slotSetStartThumb(QImage img) { + if (!img.isNull() && img.format() == QImage::Format_ARGB32) { + QPixmap pix = QPixmap::fromImage(img); + m_startPix = pix; + QRectF r = sceneBoundingRect(); + r.setRight(pix.width() + 2); + update(r); + } +} + +void ClipItem::slotSetEndThumb(QImage img) { + if (!img.isNull() && img.format() == QImage::Format_ARGB32) { + QPixmap pix = QPixmap::fromImage(img); + m_endPix = pix; + QRectF r = sceneBoundingRect(); + r.setLeft(r.right() - pix.width() - 2); + update(r); + } } void ClipItem::slotThumbReady(int frame, QPixmap pix) { - if (m_thumbsRequested == 0) return; - if (frame == m_cropStart.frames(m_fps)) m_startPix = pix; - else m_endPix = pix; - update(); - m_thumbsRequested--; + if (scene() == NULL) return; + QRectF r = sceneBoundingRect(); + double width = m_startPix.width() / projectScene()->scale(); + if (m_startThumbRequested && frame == cropStart().frames(m_fps)) { + m_startPix = pix; + m_startThumbRequested = false; + double height = r.height(); + update(r.x(), r.y(), width, height); + } else if (m_endThumbRequested && frame == (cropStart() + cropDuration()).frames(m_fps) - 1) { + m_endPix = pix; + m_endThumbRequested = false; + double height = r.height(); + update(r.right() - width, r.y(), width, height); + } +} + +void ClipItem::slotSetStartThumb(const QPixmap pix) { + m_startPix = pix; +} + +void ClipItem::slotSetEndThumb(const QPixmap pix) { + m_endPix = pix; +} + +QPixmap ClipItem::startThumb() const { + return m_startPix; +} + +QPixmap ClipItem::endThumb() const { + return m_endPix; } void ClipItem::slotGotAudioData() { audioThumbReady = true; - update(); + if (m_clipType == AV) { + QRectF r = boundingRect(); + r.setTop(r.top() + r.height() / 2 - 1); + update(r); + } else update(); } int ClipItem::type() const { return AVWIDGET; } -DocClipBase *ClipItem::baseClip() { +DocClipBase *ClipItem::baseClip() const { return m_clip; } @@ -156,15 +465,15 @@ QDomElement ClipItem::xml() const { return m_clip->toXML(); } -int ClipItem::clipType() { +int ClipItem::clipType() const { return m_clipType; } -QString ClipItem::clipName() { +QString ClipItem::clipName() const { return m_clipName; } -int ClipItem::clipProducer() { +const QString &ClipItem::clipProducer() const { return m_producer; } @@ -178,70 +487,108 @@ void ClipItem::flashClip() { } void ClipItem::animate(qreal value) { - //m_opacity = value; - update(); + QRectF r = boundingRect(); + r.setHeight(20); + update(r); } // virtual void ClipItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, - QWidget *widget) { - painter->setOpacity(m_opacity); + QWidget *) { + /*if (parentItem()) m_opacity = 0.5; + else m_opacity = 1.0; + painter->setOpacity(m_opacity);*/ QBrush paintColor = brush(); if (isSelected()) paintColor = QBrush(QColor(79, 93, 121)); QRectF br = rect(); - double scale = br.width() / m_cropDuration.frames(m_fps); + QRectF exposed = option->exposedRect; + QRectF mapped = painter->matrix().mapRect(br); - // kDebug()<<"/// EXPOSED RECT: "<exposedRect.x()<<" X "<exposedRect.right(); - painter->setClipRect(option->exposedRect); - int startpixel = (int)option->exposedRect.x() - rect().x(); + const double itemWidth = br.width(); + const double itemHeight = br.height(); + const double scale = option->matrix.m11(); - if (startpixel < 0) - startpixel = 0; - int endpixel = (int)option->exposedRect.right() - rect().x(); - if (endpixel < 0) - endpixel = 0; //painter->setRenderHints(QPainter::Antialiasing); - QPainterPath roundRectPathUpper = upperRectPart(br), roundRectPathLower = lowerRectPart(br); - + //QPainterPath roundRectPathUpper = upperRectPart(br), roundRectPathLower = lowerRectPart(br); + painter->setClipRect(exposed); - // build path around clip - QPainterPath resultClipPath = roundRectPathUpper.united(roundRectPathLower); - painter->fillPath(resultClipPath, paintColor); + //build path around clip + //QPainterPath resultClipPath = roundRectPathUpper.united(roundRectPathLower); + //painter->fillPath(resultClipPath, paintColor); + painter->fillRect(exposed, paintColor); + + //painter->setClipPath(resultClipPath, Qt::IntersectClip); // draw thumbnails - if (!m_startPix.isNull() && KdenliveSettings::videothumbnails()) { - if (m_clipType == IMAGE) { - painter->drawPixmap(QPointF(br.x() + br.width() - m_startPix.width(), br.y()), m_startPix); - QLineF l(br.x() + br.width() - m_startPix.width(), br.y(), br.x() + br.width() - m_startPix.width(), br.y() + br.height()); + painter->setMatrixEnabled(false); + + if (KdenliveSettings::videothumbnails()) { + QPen pen = painter->pen(); + pen.setStyle(Qt::DotLine); + pen.setColor(Qt::white); + painter->setPen(pen); + if (m_clipType == IMAGE && !m_startPix.isNull()) { + QPointF p1 = painter->matrix().map(QPointF(itemWidth, 0)) - QPointF(m_startPix.width(), 0); + QPointF p2 = painter->matrix().map(QPointF(itemWidth, itemHeight)) - QPointF(m_startPix.width(), 0); + painter->drawPixmap(p1, m_startPix); + QLineF l(p1, p2); painter->drawLine(l); - } else { - painter->drawPixmap(QPointF(br.x() + br.width() - m_endPix.width(), br.y()), m_endPix); - QLineF l(br.x() + br.width() - m_endPix.width(), br.y(), br.x() + br.width() - m_endPix.width(), br.y() + br.height()); + } else if (!m_endPix.isNull()) { + QPointF p1 = painter->matrix().map(QPointF(itemWidth, 0)) - QPointF(m_endPix.width(), 0); + QPointF p2 = painter->matrix().map(QPointF(itemWidth, itemHeight)) - QPointF(m_endPix.width(), 0); + painter->drawPixmap(p1, m_endPix); + QLineF l(p1, p2); painter->drawLine(l); } - - painter->drawPixmap(QPointF(br.x(), br.y()), m_startPix); - QLineF l2(br.x() + m_startPix.width(), br.y(), br.x() + m_startPix.width(), br.y() + br.height()); - painter->drawLine(l2); + if (!m_startPix.isNull()) { + QPointF p1 = painter->matrix().map(QPointF(0, 0)); + QPointF p2 = painter->matrix().map(QPointF(0, itemHeight)); + painter->drawPixmap(p1, m_startPix); + QLineF l2(p1.x() + m_startPix.width(), p1.y(), p2.x() + m_startPix.width(), p2.y()); + painter->drawLine(l2); + } + painter->setPen(Qt::black); } // draw audio thumbnails - if ((m_clipType == AV || m_clipType == AUDIO) && audioThumbReady && KdenliveSettings::audiothumbnails()) { - - QPainterPath path = m_clipType == AV ? roundRectPathLower : resultClipPath; - if (m_clipType == AV) painter->fillPath(path, QBrush(QColor(200, 200, 200, 140))); - - int channels = 2; + if (KdenliveSettings::audiothumbnails() && m_speed == 1.0 && ((m_clipType == AV && exposed.bottom() > (itemHeight / 2)) || m_clipType == AUDIO) && audioThumbReady) { + + double startpixel = exposed.left(); + if (startpixel < 0) + startpixel = 0; + double endpixel = exposed.right(); + if (endpixel < 0) + endpixel = 0; + //kDebug()<<"/// REPAINTING AUDIO THMBS ZONE: "<matrix().mapRect(re); + //painter->fillRect(mappedRect, QBrush(QColor(200, 200, 200, 140))); + } else mappedRect = mapped; + + int channels = baseClip()->getProperty("channels").toInt(); if (scale != framePixelWidth) audioThumbCachePic.clear(); - emit prepareAudioThumb(scale, path, startpixel, endpixel + 200);//200 more for less missing parts before repaint after scrolling - int cropLeft = (int)((m_cropStart).frames(m_fps) * scale); - for (int startCache = startpixel - startpixel % 100; startCache < endpixel + 300;startCache += 100) { + double cropLeft = m_cropStart.frames(m_fps); + const int clipStart = mappedRect.x(); + const int mappedStartPixel = painter->matrix().map(QPointF(startpixel + cropLeft, 0)).x() - clipStart; + const int mappedEndPixel = painter->matrix().map(QPointF(endpixel + cropLeft, 0)).x() - clipStart; + cropLeft = cropLeft * scale; + + if (channels >= 1) { + emit prepareAudioThumb(scale, mappedStartPixel, mappedEndPixel, channels); + } + + for (int startCache = mappedStartPixel - (mappedStartPixel) % 100; startCache < mappedEndPixel; startCache += 100) { if (audioThumbCachePic.contains(startCache) && !audioThumbCachePic[startCache].isNull()) - painter->drawPixmap((int)(roundRectPathUpper.united(roundRectPathLower).boundingRect().x() + startCache - cropLeft), (int)(path.boundingRect().y()), audioThumbCachePic[startCache]); + painter->drawPixmap(clipStart + startCache - cropLeft, mappedRect.y(), audioThumbCachePic[startCache]); } } @@ -252,7 +599,7 @@ void ClipItem::paint(QPainter *painter, double framepos; const int markerwidth = 4; QBrush markerBrush; - markerBrush = QBrush(QColor(120, 120, 0, 100)); + markerBrush = QBrush(QColor(120, 120, 0, 140)); QPen pen = painter->pen(); pen.setColor(QColor(255, 255, 255, 200)); pen.setStyle(Qt::DotLine); @@ -261,11 +608,17 @@ void ClipItem::paint(QPainter *painter, pos = (*it).time() - cropStart(); if (pos > GenTime()) { if (pos > duration()) break; - framepos = scale * pos.frames(m_fps); - QLineF l(br.x() + framepos, br.y() + 5, br.x() + framepos, br.y() + br.height() - 5); - painter->drawLine(l); + QLineF l(br.x() + pos.frames(m_fps), br.y() + 5, br.x() + pos.frames(m_fps), br.bottom() - 5); + QLineF l2 = painter->matrix().map(l); + //framepos = scale * pos.frames(m_fps); + //QLineF l(framepos, 5, framepos, itemHeight - 5); + painter->drawLine(l2); if (KdenliveSettings::showmarkers()) { - const QRectF txtBounding = painter->boundingRect(br.x() + framepos + 1, br.y() + 10, br.width() - framepos - 2, br.height() - 10, Qt::AlignLeft | Qt::AlignTop, " " + (*it).comment() + " "); + framepos = br.x() + pos.frames(m_fps); + const QRectF r1(framepos + 0.04, 10, itemWidth - framepos - 2, itemHeight - 10); + const QRectF r2 = painter->matrix().mapRect(r1); + const QRectF txtBounding = painter->boundingRect(r2, Qt::AlignLeft | Qt::AlignTop, " " + (*it).comment() + " "); + QPainterPath path; path.addRoundedRect(txtBounding, 3, 3); painter->fillPath(path, markerBrush); @@ -276,8 +629,7 @@ void ClipItem::paint(QPainter *painter, } pen.setColor(Qt::black); pen.setStyle(Qt::SolidLine); - - + painter->setPen(pen); // draw start / end fades QBrush fades; @@ -287,32 +639,34 @@ void ClipItem::paint(QPainter *painter, if (m_startFade != 0) { QPainterPath fadeInPath; - fadeInPath.moveTo(br.x() , br.y()); - fadeInPath.lineTo(br.x() , br.bottom()); - fadeInPath.lineTo(br.x() + m_startFade * scale, br.y()); + fadeInPath.moveTo(0, 0); + fadeInPath.lineTo(0, itemHeight); + fadeInPath.lineTo(m_startFade, 0); fadeInPath.closeSubpath(); - painter->fillPath(fadeInPath.intersected(resultClipPath), fades); - if (isSelected()) { - QLineF l(br.x() + m_startFade * scale, br.y(), br.x(), br.bottom()); + QPainterPath f1 = painter->matrix().map(fadeInPath); + painter->fillPath(f1/*.intersected(resultClipPath)*/, fades); + /*if (isSelected()) { + QLineF l(m_startFade * scale, 0, 0, itemHeight); painter->drawLine(l); - } + }*/ } if (m_endFade != 0) { QPainterPath fadeOutPath; - fadeOutPath.moveTo(br.right(), br.y()); - fadeOutPath.lineTo(br.right(), br.bottom()); - fadeOutPath.lineTo(br.right() - m_endFade * scale, br.y()); + fadeOutPath.moveTo(itemWidth, 0); + fadeOutPath.lineTo(itemWidth, itemHeight); + fadeOutPath.lineTo(itemWidth - m_endFade, 0); fadeOutPath.closeSubpath(); - painter->fillPath(fadeOutPath.intersected(resultClipPath), fades); - if (isSelected()) { - QLineF l(br.right() - m_endFade * scale, br.y(), br.x() + br.width(), br.bottom()); + QPainterPath f1 = painter->matrix().map(fadeOutPath); + painter->fillPath(f1/*.intersected(resultClipPath)*/, fades); + /*if (isSelected()) { + QLineF l(itemWidth - m_endFade * scale, 0, itemWidth, itemHeight); painter->drawLine(l); - } + }*/ } // Draw effects names - if (!m_effectNames.isEmpty() && br.width() > 30) { - QRectF txtBounding = painter->boundingRect(br, Qt::AlignLeft | Qt::AlignTop, m_effectNames); + if (!m_effectNames.isEmpty() && itemWidth * scale > 40) { + QRectF txtBounding = painter->boundingRect(mapped, Qt::AlignLeft | Qt::AlignTop, m_effectNames); txtBounding.setRight(txtBounding.right() + 15); painter->setPen(Qt::white); QBrush markerBrush(Qt::SolidPattern); @@ -323,19 +677,30 @@ void ClipItem::paint(QPainter *painter, } else markerBrush.setColor(QColor(50, 50, 50, 150)); QPainterPath path; path.addRoundedRect(txtBounding, 4, 4); - painter->fillPath(path.intersected(resultClipPath), markerBrush); + painter->fillPath(path/*.intersected(resultClipPath)*/, markerBrush); painter->drawText(txtBounding, Qt::AlignCenter, m_effectNames); painter->setPen(Qt::black); } // Draw clip name - QRectF txtBounding = painter->boundingRect(br, Qt::AlignHCenter | Qt::AlignTop, " " + m_clipName + " "); - //painter->fillRect(txtBounding, QBrush(QColor(255, 255, 255, 150))); - painter->setPen(QColor(0, 0, 0, 180)); - painter->drawText(txtBounding, Qt::AlignCenter, m_clipName); + QRectF txtBounding = painter->boundingRect(mapped, Qt::AlignHCenter | Qt::AlignVCenter, " " + m_clipName + " "); + painter->fillRect(txtBounding, QBrush(QColor(0, 0, 0, 150))); + //painter->setPen(QColor(0, 0, 0, 180)); + //painter->drawText(txtBounding, Qt::AlignCenter, m_clipName); txtBounding.translate(QPointF(1, 1)); painter->setPen(QColor(255, 255, 255, 255)); painter->drawText(txtBounding, Qt::AlignCenter, m_clipName); + + + // draw transition handles on hover + if (m_hover && itemWidth * scale > 40) { + QPointF p1 = painter->matrix().map(QPointF(0, itemHeight / 2)) + QPointF(10, 0); + painter->drawPixmap(p1, projectScene()->m_transitionPixmap); + p1 = painter->matrix().map(QPointF(itemWidth, itemHeight / 2)) - QPointF(22, 0); + painter->drawPixmap(p1, projectScene()->m_transitionPixmap); + } + + // draw frame around clip if (isSelected()) { pen.setColor(Qt::red); @@ -344,69 +709,60 @@ void ClipItem::paint(QPainter *painter, pen.setColor(Qt::black); //pen.setWidth(1); } + + // draw effect or transition keyframes + if (itemWidth > 20) drawKeyFrames(painter, exposed); + + painter->setMatrixEnabled(true); + + // draw clip border + + //kDebug()<<"/// ITEM PAINTING:: exposed="< ClipItem::snapMarkers() { +QList ClipItem::snapMarkers() const { QList < GenTime > snaps; QList < GenTime > markers = baseClip()->snapMarkers(); GenTime pos; @@ -422,21 +778,37 @@ QList ClipItem::snapMarkers() { return snaps; } -void ClipItem::slotPrepareAudioThumb(double pixelForOneFrame, QPainterPath path, int startpixel, int endpixel) { - int channels = 2; +QList ClipItem::commentedSnapMarkers() const { + QList < CommentedTime > snaps; + QList < CommentedTime > markers = baseClip()->commentedSnapMarkers(); + GenTime pos; + double framepos; + + for (int i = 0; i < markers.size(); i++) { + pos = markers.at(i).time() - cropStart(); + if (pos > GenTime()) { + if (pos > duration()) break; + else snaps.append(CommentedTime(pos + startPos(), markers.at(i).comment())); + } + } + return snaps; +} - QRectF re = path.boundingRect(); +void ClipItem::slotPrepareAudioThumb(double pixelForOneFrame, int startpixel, int endpixel, int channels) { + QRectF re = sceneBoundingRect(); + if (m_clipType == AV) re.setTop(re.y() + re.height() / 2); + //kDebug() << "// PREP AUDIO THMB FRMO : scale:" << pixelForOneFrame<< ", from: " << startpixel << ", to: " << endpixel; //if ( (!audioThumbWasDrawn || framePixelWidth!=pixelForOneFrame ) && !baseClip()->audioFrameChache.isEmpty()){ - for (int startCache = startpixel - startpixel % 100;startCache + 100 < endpixel ;startCache += 100) { + for (int startCache = startpixel - startpixel % 100;startCache < endpixel;startCache += 100) { //kDebug() << "creating " << startCache; //if (framePixelWidth!=pixelForOneFrame || if (framePixelWidth == pixelForOneFrame && audioThumbCachePic.contains(startCache)) continue; if (audioThumbCachePic[startCache].isNull() || framePixelWidth != pixelForOneFrame) { audioThumbCachePic[startCache] = QPixmap(100, (int)(re.height())); - audioThumbCachePic[startCache].fill(QColor(200, 200, 200, 0)); + audioThumbCachePic[startCache].fill(QColor(180, 180, 200, 140)); } bool fullAreaDraw = pixelForOneFrame < 10; QMap positiveChannelPaths; @@ -447,6 +819,12 @@ void ClipItem::slotPrepareAudioThumb(double pixelForOneFrame, QPainterPath path, pixpainter.setPen(audiopen); //pixpainter.setRenderHint(QPainter::Antialiasing,true); //pixpainter.drawLine(0,0,100,re.height()); + // Bail out, if caller provided invalid data + if (channels <= 0) { + kWarning() << "Unable to draw image with " << channels << "number of channels"; + return; + } + int channelHeight = audioThumbCachePic[startCache].height() / channels; for (int i = 0;i < channels;i++) { @@ -511,22 +889,24 @@ uint ClipItem::fadeOut() const { } -void ClipItem::setFadeIn(int pos, double scale) { +void ClipItem::setFadeIn(int pos) { + if (pos == m_startFade) return; int oldIn = m_startFade; if (pos < 0) pos = 0; - if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps) / 2); + if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps)); m_startFade = pos; - if (oldIn > pos) update(rect().x(), rect().y(), oldIn * scale, rect().height()); - else update(rect().x(), rect().y(), pos * scale, rect().height()); + QRectF rect = boundingRect(); + update(rect.x(), rect.y(), qMax(oldIn, pos), rect.height()); } -void ClipItem::setFadeOut(int pos, double scale) { +void ClipItem::setFadeOut(int pos) { + if (pos == m_endFade) return; int oldOut = m_endFade; if (pos < 0) pos = 0; - if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps) / 2); + if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps)); m_endFade = pos; - if (oldOut > pos) update(rect().x() + rect().width() - pos * scale, rect().y(), pos * scale, rect().height()); - else update(rect().x() + rect().width() - oldOut * scale, rect().y(), oldOut * scale, rect().height()); + QRectF rect = boundingRect(); + update(rect.x() + rect.width() - qMax(oldOut, pos), rect.y(), qMax(oldOut, pos), rect.height()); } @@ -547,109 +927,292 @@ void ClipItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) { } //virtual -void ClipItem::hoverEnterEvent(QGraphicsSceneHoverEvent *) { +void ClipItem::hoverEnterEvent(QGraphicsSceneHoverEvent *e) { + //if (e->pos().x() < 20) m_hover = true; m_hover = true; - update(); + QRectF r = boundingRect(); + double width = 35 / projectScene()->scale(); + double height = r.height() / 2; + //WARNING: seems like it generates a full repaint of the clip, maybe not so good... + update(r.x(), r.y() + height, width, height); + update(r.right() - width, r.y() + height, width, height); } //virtual void ClipItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { m_hover = false; - update(); + QRectF r = boundingRect(); + double width = 35 / projectScene()->scale(); + double height = r.height() / 2; + //WARNING: seems like it generates a full repaint of the clip, maybe not so good... + update(r.x(), r.y() + height, width, height); + update(r.right() - width, r.y() + height, width, height); } -void ClipItem::resizeStart(int posx, double scale) { - AbstractClipItem::resizeStart(posx, scale); - if (m_hasThumbs) startThumbTimer->start(100); +void ClipItem::resizeStart(int posx, double speed) { + const int min = (startPos() - cropStart()).frames(m_fps); + if (posx < min) posx = min; + if (posx == startPos().frames(m_fps)) return; + const int previous = cropStart().frames(m_fps); + AbstractClipItem::resizeStart(posx, m_speed); + checkEffectsKeyframesPos(previous, cropStart().frames(m_fps), true); + if (m_hasThumbs && KdenliveSettings::videothumbnails()) { + /*connect(m_clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap)));*/ + startThumbTimer->start(100); + } } -void ClipItem::resizeEnd(int posx, double scale) { - AbstractClipItem::resizeEnd(posx, scale); - if (m_hasThumbs) endThumbTimer->start(100); +void ClipItem::resizeEnd(int posx, double speed, bool updateKeyFrames) { + const int max = (startPos() - cropStart() + maxDuration()).frames(m_fps) + 1; + if (posx > max) posx = max; + if (posx == endPos().frames(m_fps)) return; + //kDebug() << "// NEW POS: " << posx << ", OLD END: " << endPos().frames(m_fps); + const int previous = (cropStart() + duration()).frames(m_fps); + AbstractClipItem::resizeEnd(posx, m_speed); + if (updateKeyFrames) checkEffectsKeyframesPos(previous, (cropStart() + duration()).frames(m_fps), false); + if (m_hasThumbs && KdenliveSettings::videothumbnails()) { + /*connect(m_clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap)));*/ + endThumbTimer->start(100); + } } -// virtual -void ClipItem::mouseMoveEvent(QGraphicsSceneMouseEvent * event) { + +void ClipItem::checkEffectsKeyframesPos(const int previous, const int current, bool fromStart) { + for (int i = 0; i < m_effectList.size(); i++) { + QDomElement effect = m_effectList.at(i); + QDomNodeList params = effect.elementsByTagName("parameter"); + for (int j = 0; j < params.count(); j++) { + QDomElement e = params.item(i).toElement(); + if (e.attribute("type") == "keyframe") { + // parse keyframes and adjust values + const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts); + QMap kfr; + int pos; + double val; + foreach(const QString str, keyframes) { + pos = str.section(":", 0, 0).toInt(); + val = str.section(":", 1, 1).toDouble(); + if (pos == previous) kfr[current] = val; + else { + if (fromStart && pos >= current) kfr[pos] = val; + else if (!fromStart && pos <= current) kfr[pos] = val; + } + } + QString newkfr; + QMap::const_iterator k = kfr.constBegin(); + while (k != kfr.constEnd()) { + newkfr.append(QString::number(k.key()) + ":" + QString::number(k.value()) + ";"); + ++k; + } + e.setAttribute("keyframes", newkfr); + break; + } + } + } + if (m_selectedEffect >= 0) setSelectedEffect(m_selectedEffect); +} + +//virtual +QVariant ClipItem::itemChange(GraphicsItemChange change, const QVariant &value) { + if (change == ItemPositionChange && scene()) { + // calculate new position. + if (parentItem()) return pos(); + QPointF newPos = value.toPointF(); + //kDebug() << "/// MOVING CLIP ITEM.------------\n++++++++++"; + int xpos = projectScene()->getSnapPointForPos((int) newPos.x(), KdenliveSettings::snaptopoints()); + xpos = qMax(xpos, 0); + newPos.setX(xpos); + int newTrack = newPos.y() / KdenliveSettings::trackheight(); + newTrack = qMin(newTrack, projectScene()->tracksCount() - 1); + newTrack = qMax(newTrack, 0); + newPos.setY((int)(newTrack * KdenliveSettings::trackheight() + 1)); + // Only one clip is moving + QRectF sceneShape = rect(); + sceneShape.translate(newPos); + QList items = scene()->items(sceneShape, Qt::IntersectsItemShape); + items.removeAll(this); + bool forwardMove = newPos.x() > pos().x(); + int offset = 0; + if (!items.isEmpty()) { + for (int i = 0; i < items.count(); i++) { + if (items.at(i)->type() == type()) { + // Collision! + QPointF otherPos = items.at(i)->pos(); + if ((int) otherPos.y() != (int) pos().y()) { + return pos(); + } + if (forwardMove) { + offset = qMax(offset, (int)(newPos.x() - (static_cast < AbstractClipItem* >(items.at(i))->startPos() - m_cropDuration).frames(m_fps))); + } else { + offset = qMax(offset, (int)((static_cast < AbstractClipItem* >(items.at(i))->endPos().frames(m_fps)) - newPos.x())); + } + + if (offset > 0) { + if (forwardMove) { + sceneShape.translate(QPointF(-offset, 0)); + newPos.setX(newPos.x() - offset); + } else { + sceneShape.translate(QPointF(offset, 0)); + newPos.setX(newPos.x() + offset); + } + QList subitems = scene()->items(sceneShape, Qt::IntersectsItemShape); + subitems.removeAll(this); + for (int j = 0; j < subitems.count(); j++) { + if (subitems.at(j)->type() == type()) return pos(); + } + } + + m_track = newTrack; + m_startPos = GenTime((int) newPos.x(), m_fps); + return newPos; + } + } + } + m_track = newTrack; + m_startPos = GenTime((int) newPos.x(), m_fps); + //kDebug()<<"// ITEM NEW POS: "< m_effectList.count() - 1 || ix < 0) return QDomElement(); return m_effectList.at(ix); } void ClipItem::setEffectAt(int ix, QDomElement effect) { - kDebug() << "CHange EFFECT AT: " << ix << ", CURR: " << m_effectList.at(ix).attribute("tag") << ", NEW: " << effect.attribute("tag"); + kDebug() << "CHange EFFECT AT: " << ix << ", CURR: " << m_effectList.at(ix).attribute("tag") << ", NEW: " << effect.attribute("tag"); + effect.setAttribute("kdenlive_ix", ix + 1); m_effectList.insert(ix, effect); m_effectList.removeAt(ix + 1); m_effectNames = m_effectList.effectNames().join(" / "); - update(boundingRect()); + if (effect.attribute("id") == "fadein" || effect.attribute("id") == "fadeout") update(boundingRect()); + else { + QRectF r = boundingRect(); + r.setHeight(20); + update(r); + } } -QMap ClipItem::addEffect(QDomElement effect, bool animate) { - QMap effectParams; +EffectsParameterList ClipItem::addEffect(QDomElement effect, bool animate) { + + bool needRepaint = false; /*QDomDocument doc; doc.appendChild(doc.importNode(effect, true)); - kDebug() << "/////// CLIP ADD EFFECT: "<< doc.toString();*/ + kDebug() << "/////// CLIP ADD EFFECT: " << doc.toString();*/ m_effectList.append(effect); - effectParams["tag"] = effect.attribute("tag"); + + EffectsParameterList parameters; + parameters.addParam("tag", effect.attribute("tag")); + parameters.addParam("kdenlive_ix", effect.attribute("kdenlive_ix")); + if (effect.hasAttribute("src")) parameters.addParam("src", effect.attribute("src")); + + QString state = effect.attribute("disabled"); + if (!state.isEmpty()) { + parameters.addParam("disabled", state); + } + QString effectId = effect.attribute("id"); if (effectId.isEmpty()) effectId = effect.attribute("tag"); - effectParams["id"] = effectId; - effectParams["kdenlive_ix"] = effect.attribute("kdenlive_ix"); - QString state = effect.attribute("disabled"); - if (!state.isEmpty()) effectParams["disabled"] = state; + parameters.addParam("id", effectId); + QDomNodeList params = effect.elementsByTagName("parameter"); int fade = 0; for (int i = 0; i < params.count(); i++) { QDomElement e = params.item(i).toElement(); if (!e.isNull()) { - if (e.attribute("factor").isEmpty()) { - effectParams[e.attribute("name")] = e.attribute("value"); + if (e.attribute("type") == "keyframe") { + parameters.addParam("keyframes", e.attribute("keyframes")); + parameters.addParam("max", e.attribute("max")); + parameters.addParam("min", e.attribute("min")); + parameters.addParam("factor", e.attribute("factor", "1")); + parameters.addParam("starttag", e.attribute("starttag", "start")); + parameters.addParam("endtag", e.attribute("endtag", "end")); + } + + double f = e.attribute("factor", "1").toDouble(); + + if (f == 1) { + parameters.addParam(e.attribute("name"), e.attribute("value")); + // check if it is a fade effect if (effectId == "fadein") { + needRepaint = true; if (e.attribute("name") == "out") fade += e.attribute("value").toInt(); else if (e.attribute("name") == "in") fade -= e.attribute("value").toInt(); } else if (effectId == "fadeout") { + needRepaint = true; if (e.attribute("name") == "out") fade -= e.attribute("value").toInt(); else if (e.attribute("name") == "in") fade += e.attribute("value").toInt(); } } else { - effectParams[e.attribute("name")] = QString::number(effectParams[e.attribute("name")].toDouble() / e.attribute("factor").toDouble()); + parameters.addParam(e.attribute("name"), QString::number(e.attribute("value").toDouble() / f)); } } } m_effectNames = m_effectList.effectNames().join(" / "); if (fade > 0) m_startFade = fade; else if (fade < 0) m_endFade = -fade; + if (needRepaint) update(boundingRect()); if (animate) { flashClip(); + } else if (!needRepaint) { + QRectF r = boundingRect(); + r.setHeight(20); + update(r); } - update(boundingRect()); - return effectParams; + if (m_selectedEffect == -1) { + m_selectedEffect = 0; + setSelectedEffect(m_selectedEffect); + } + return parameters; } -QMap ClipItem::getEffectArgs(QDomElement effect) { - QMap effectParams; - effectParams["tag"] = effect.attribute("tag"); - effectParams["kdenlive_ix"] = effect.attribute("kdenlive_ix"); - effectParams["id"] = effect.attribute("id"); +EffectsParameterList ClipItem::getEffectArgs(QDomElement effect) { + EffectsParameterList parameters; + parameters.addParam("tag", effect.attribute("tag")); + parameters.addParam("kdenlive_ix", effect.attribute("kdenlive_ix")); + parameters.addParam("id", effect.attribute("id")); + if (effect.hasAttribute("src")) parameters.addParam("src", effect.attribute("src")); QString state = effect.attribute("disabled"); - if (!state.isEmpty()) effectParams["disabled"] = state; + if (!state.isEmpty()) { + parameters.addParam("disabled", state); + } + QDomNodeList params = effect.elementsByTagName("parameter"); for (int i = 0; i < params.count(); i++) { QDomElement e = params.item(i).toElement(); - if (e.attribute("namedesc").contains(";")) { + //kDebug() << "/ / / /SENDING EFFECT PARAM: " << e.attribute("type") << ", NAME_ " << e.attribute("tag"); + if (e.attribute("type") == "keyframe") { + kDebug() << "/ / / /SENDING KEYFR EFFECT TYPE"; + parameters.addParam("keyframes", e.attribute("keyframes")); + parameters.addParam("max", e.attribute("max")); + parameters.addParam("min", e.attribute("min")); + parameters.addParam("factor", e.attribute("factor", "1")); + parameters.addParam("starttag", e.attribute("starttag", "start")); + parameters.addParam("endtag", e.attribute("endtag", "end")); + } else if (e.attribute("namedesc").contains(";")) { QString format = e.attribute("format"); QStringList separators = format.split("%d", QString::SkipEmptyParts); QStringList values = e.attribute("value").split(QRegExp("[,:;x]")); @@ -661,28 +1224,64 @@ QMap ClipItem::getEffectArgs(QDomElement effect) { txtNeu << separators[i]; txtNeu << (int)(values[i+1].toDouble()); } - effectParams["start"] = neu; - } else if (!e.isNull()) { - if (!e.attribute("factor").isEmpty()) - effectParams[e.attribute("name")] = QString::number(effectParams[e.attribute("name")].toDouble() / e.attribute("factor").toDouble()); - else effectParams[e.attribute("name")] = e.attribute("value"); + parameters.addParam("start", neu); + } else { + if (e.attribute("factor", "1") != "1") { + parameters.addParam(e.attribute("name"), QString::number(e.attribute("value").toDouble() / e.attribute("factor").toDouble())); + } else { + parameters.addParam(e.attribute("name"), e.attribute("value")); + } } } - return effectParams; + return parameters; } void ClipItem::deleteEffect(QString index) { + bool needRepaint = false; + QString ix; for (int i = 0; i < m_effectList.size(); ++i) { - if (m_effectList.at(i).attribute("kdenlive_ix") == index) { - if (m_effectList.at(i).attribute("id") == "fadein") m_startFade = 0; - else if (m_effectList.at(i).attribute("id") == "fadeout") m_endFade = 0; + ix = m_effectList.at(i).attribute("kdenlive_ix"); + if (ix == index) { + if (m_effectList.at(i).attribute("id") == "fadein") { + m_startFade = 0; + needRepaint = true; + } else if (m_effectList.at(i).attribute("id") == "fadeout") { + m_endFade = 0; + needRepaint = true; + } m_effectList.removeAt(i); - break; - } + } else if (ix.toInt() > index.toInt()) m_effectList[i].setAttribute("kdenlive_ix", ix.toInt() - 1); } m_effectNames = m_effectList.effectNames().join(" / "); + if (needRepaint) update(boundingRect()); flashClip(); - update(boundingRect()); +} + +double ClipItem::speed() const { + return m_speed; +} + +void ClipItem::setSpeed(const double speed) { + m_speed = speed; + if (m_speed == 1.0) m_clipName = baseClip()->name(); + else m_clipName = baseClip()->name() + " - " + QString::number(speed * 100, 'f', 0) + "%"; + //update(); +} + +GenTime ClipItem::maxDuration() const { + return m_maxDuration / m_speed; +} + +GenTime ClipItem::cropStart() const { + return m_cropStart / m_speed; +} + +GenTime ClipItem::cropDuration() const { + return m_cropDuration / m_speed; +} + +GenTime ClipItem::endPos() const { + return m_startPos + cropDuration(); } //virtual