]> git.sesse.net Git - kdenlive/blob - src/clipitem.cpp
Show error message when there is a problem deleting a clip, fix problem when selectin...
[kdenlive] / src / clipitem.cpp
1 /***************************************************************************
2  *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
18  ***************************************************************************/
19
20
21
22 #include <QPainter>
23 #include <QTimer>
24 #include <QStyleOptionGraphicsItem>
25 #include <QGraphicsScene>
26 #include <QGraphicsView>
27 #include <QScrollBar>
28 #include <QMimeData>
29 #include <QApplication>
30
31 #include <KDebug>
32
33 #include "clipitem.h"
34 #include "customtrackview.h"
35 #include "customtrackscene.h"
36 #include "renderer.h"
37 #include "docclipbase.h"
38 #include "transition.h"
39 #include "kdenlivesettings.h"
40 #include "kthumb.h"
41
42
43 ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, double fps, bool generateThumbs)
44         : 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()) {
45     setRect(0, 0, (info.endPos - info.startPos).frames(fps) - 0.02, (double)(KdenliveSettings::trackheight() - 2));
46     setPos(info.startPos.frames(fps), (double)(info.track * KdenliveSettings::trackheight()) + 1);
47
48     m_clipName = clip->name();
49     m_producer = clip->getId();
50     m_clipType = clip->clipType();
51     m_cropStart = info.cropStart;
52     m_maxDuration = clip->maxDuration();
53     setAcceptDrops(true);
54     audioThumbReady = clip->audioThumbCreated();
55
56     /*
57       m_cropStart = xml.attribute("in", 0).toInt();
58       m_maxDuration = xml.attribute("duration", 0).toInt();
59       if (m_maxDuration == 0) m_maxDuration = xml.attribute("out", 0).toInt() - m_cropStart;
60
61       if (duration != -1) m_cropDuration = duration;
62       else m_cropDuration = m_maxDuration;*/
63
64
65     setFlags(QGraphicsItem::ItemClipsToShape | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
66     setAcceptsHoverEvents(true);
67     connect(this , SIGNAL(prepareAudioThumb(double, int, int, int)) , this, SLOT(slotPrepareAudioThumb(double, int, int, int)));
68
69     setBrush(QColor(141, 166, 215));
70     if (m_clipType == VIDEO || m_clipType == AV || m_clipType == SLIDESHOW || m_clipType == PLAYLIST) {
71         m_hasThumbs = true;
72         startThumbTimer = new QTimer(this);
73         startThumbTimer->setSingleShot(true);
74         connect(startThumbTimer, SIGNAL(timeout()), this, SLOT(slotGetStartThumb()));
75         endThumbTimer = new QTimer(this);
76         endThumbTimer->setSingleShot(true);
77         connect(endThumbTimer, SIGNAL(timeout()), this, SLOT(slotGetEndThumb()));
78
79         connect(this, SIGNAL(getThumb(int, int)), clip->thumbProducer(), SLOT(extractImage(int, int)));
80         //connect(this, SIGNAL(getThumb(int, int)), clip->thumbProducer(), SLOT(getVideoThumbs(int, int)));
81
82         connect(clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap)));
83         connect(clip, SIGNAL(gotAudioData()), this, SLOT(slotGotAudioData()));
84         if (generateThumbs) QTimer::singleShot(200, this, SLOT(slotFetchThumbs()));
85
86         /*if (m_clip->producer()) {
87             videoThumbProducer.init(this, m_clip->producer(), KdenliveSettings::trackheight() * KdenliveSettings::project_display_ratio(), KdenliveSettings::trackheight());
88             slotFetchThumbs();
89         }*/
90     } else if (m_clipType == COLOR) {
91         QString colour = clip->getProperty("colour");
92         colour = colour.replace(0, 2, "#");
93         setBrush(QColor(colour.left(7)));
94     } else if (m_clipType == IMAGE || m_clipType == TEXT) {
95         m_startPix = KThumb::getImage(KUrl(clip->getProperty("resource")), (int)(KdenliveSettings::trackheight() * KdenliveSettings::project_display_ratio()), KdenliveSettings::trackheight());
96         m_endPix = m_startPix;
97     } else if (m_clipType == AUDIO) {
98         connect(clip, SIGNAL(gotAudioData()), this, SLOT(slotGotAudioData()));
99     }
100 }
101
102
103 ClipItem::~ClipItem() {
104     if (startThumbTimer) delete startThumbTimer;
105     if (endThumbTimer) delete endThumbTimer;
106     if (m_timeLine) m_timeLine;
107 }
108
109 ClipItem *ClipItem::clone(ItemInfo info) const {
110     ClipItem *duplicate = new ClipItem(m_clip, info, m_fps);
111     if (info.cropStart == cropStart()) duplicate->slotSetStartThumb(m_startPix);
112     if (info.cropStart + (info.endPos - info.startPos) == m_cropStart + m_cropDuration) duplicate->slotSetEndThumb(m_endPix);
113     kDebug() << "// CLoning clip: " << (info.cropStart + (info.endPos - info.startPos)).frames(m_fps) << ", CURRENT end: " << (cropStart() + duration()).frames(m_fps);
114     duplicate->setEffectList(m_effectList.clone());
115     duplicate->setSpeed(m_speed);
116     return duplicate;
117 }
118
119 void ClipItem::setEffectList(const EffectsList effectList) {
120     m_effectList = effectList;
121     m_effectNames = m_effectList.effectNames().join(" / ");
122 }
123
124 const EffectsList ClipItem::effectList() {
125     return m_effectList;
126 }
127
128 int ClipItem::selectedEffectIndex() const {
129     return m_selectedEffect;
130 }
131
132 void ClipItem::initEffect(QDomElement effect) {
133     // the kdenlive_ix int is used to identify an effect in mlt's playlist, should
134     // not be changed
135     if (effect.attribute("kdenlive_ix").toInt() == 0)
136         effect.setAttribute("kdenlive_ix", QString::number(effectsCounter()));
137     // init keyframes if required
138     QDomNodeList params = effect.elementsByTagName("parameter");
139     for (int i = 0; i < params.count(); i++) {
140         QDomElement e = params.item(i).toElement();
141         if (!e.isNull() && e.attribute("type") == "keyframe") {
142             QString def = e.attribute("default");
143             // Effect has a keyframe type parameter, we need to set the values
144             if (e.attribute("keyframes").isEmpty()) {
145                 e.setAttribute("keyframes", QString::number(m_cropStart.frames(m_fps)) + ":" + def + ";" + QString::number((m_cropStart + m_cropDuration).frames(m_fps)) + ":" + def);
146                 //kDebug() << "///// EFFECT KEYFRAMES INITED: " << e.attribute("keyframes");
147                 break;
148             }
149         }
150     }
151
152     if (effect.attribute("tag") == "volume") {
153         if (effect.attribute("id") == "fadeout") {
154             int end = (duration() + cropStart()).frames(m_fps);
155             int start = end - EffectsList::parameter(effect, "in").toInt();
156             EffectsList::setParameter(effect, "in", QString::number(start));
157             EffectsList::setParameter(effect, "out", QString::number(end));
158         } else if (effect.attribute("id") == "fadein") {
159             int start = cropStart().frames(m_fps);
160             int end = start + EffectsList::parameter(effect, "out").toInt();
161             EffectsList::setParameter(effect, "in", QString::number(start));
162             EffectsList::setParameter(effect, "out", QString::number(end));
163         }
164     }
165 }
166
167 bool ClipItem::checkKeyFrames() {
168     bool clipEffectsModified = false;
169     for (int ix = 0; ix < m_effectList.count(); ix ++) {
170         QString kfr = keyframes(ix);
171         if (!kfr.isEmpty()) {
172             const QStringList keyframes = kfr.split(";", QString::SkipEmptyParts);
173             QStringList newKeyFrames;
174             bool cutKeyFrame = false;
175             bool modified = false;
176             int lastPos = -1;
177             double lastValue = -1;
178             int start = m_cropStart.frames(m_fps);
179             int end = (m_cropStart + m_cropDuration).frames(m_fps);
180             foreach(const QString str, keyframes) {
181                 int pos = str.section(":", 0, 0).toInt();
182                 double val = str.section(":", 1, 1).toDouble();
183                 if (pos - start < 0) {
184                     // a keyframe is defined before the start of the clip
185                     cutKeyFrame = true;
186                 } else if (cutKeyFrame) {
187                     // create new keyframe at clip start, calculate interpolated value
188                     if (pos > start) {
189                         int diff = pos - lastPos;
190                         double ratio = (double)(start - lastPos) / diff;
191                         double newValue = lastValue + (val - lastValue) * ratio;
192                         newKeyFrames.append(QString::number(start) + ":" + QString::number(newValue));
193                         modified = true;
194                     }
195                     cutKeyFrame = false;
196                 }
197                 if (!cutKeyFrame) {
198                     if (pos > end) {
199                         // create new keyframe at clip end, calculate interpolated value
200                         int diff = pos - lastPos;
201                         if (diff != 0) {
202                             double ratio = (double)(end - lastPos) / diff;
203                             double newValue = lastValue + (val - lastValue) * ratio;
204                             newKeyFrames.append(QString::number(end) + ":" + QString::number(newValue));
205                             modified = true;
206                         }
207                         break;
208                     } else {
209                         newKeyFrames.append(QString::number(pos) + ":" + QString::number(val));
210                     }
211                 }
212                 lastPos = pos;
213                 lastValue = val;
214             }
215             if (modified) {
216                 // update KeyFrames
217                 setKeyframes(ix, newKeyFrames.join(";"));
218                 clipEffectsModified = true;
219             }
220         }
221     }
222     return clipEffectsModified;
223 }
224
225 void ClipItem::setKeyframes(const int ix, const QString keyframes) {
226     QDomElement effect = effectAt(ix);
227     if (effect.attribute("disabled") == "1") return;
228     QDomNodeList params = effect.elementsByTagName("parameter");
229     for (int i = 0; i < params.count(); i++) {
230         QDomElement e = params.item(i).toElement();
231         if (!e.isNull() && e.attribute("type") == "keyframe") {
232             e.setAttribute("keyframes", keyframes);
233             if (ix == m_selectedEffect) {
234                 m_keyframes.clear();
235                 double max = e.attribute("max").toDouble();
236                 double min = e.attribute("min").toDouble();
237                 m_keyframeFactor = 100.0 / (max - min);
238                 m_keyframeDefault = e.attribute("default").toDouble();
239                 // parse keyframes
240                 const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts);
241                 foreach(const QString str, keyframes) {
242                     int pos = str.section(":", 0, 0).toInt();
243                     double val = str.section(":", 1, 1).toDouble();
244                     m_keyframes[pos] = val;
245                 }
246                 update();
247                 return;
248             }
249             break;
250         }
251     }
252 }
253
254
255 void ClipItem::setSelectedEffect(const int ix) {
256     m_selectedEffect = ix;
257     QDomElement effect = effectAt(m_selectedEffect);
258     QDomNodeList params = effect.elementsByTagName("parameter");
259     if (effect.attribute("disabled") != "1")
260         for (int i = 0; i < params.count(); i++) {
261             QDomElement e = params.item(i).toElement();
262             if (!e.isNull() && e.attribute("type") == "keyframe") {
263                 m_keyframes.clear();
264                 double max = e.attribute("max").toDouble();
265                 double min = e.attribute("min").toDouble();
266                 m_keyframeFactor = 100.0 / (max - min);
267                 m_keyframeDefault = e.attribute("default").toDouble();
268                 // parse keyframes
269                 const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts);
270                 foreach(const QString str, keyframes) {
271                     int pos = str.section(":", 0, 0).toInt();
272                     double val = str.section(":", 1, 1).toDouble();
273                     m_keyframes[pos] = val;
274                 }
275                 update();
276                 return;
277             }
278         }
279     if (!m_keyframes.isEmpty()) {
280         m_keyframes.clear();
281         update();
282     }
283 }
284
285 QString ClipItem::keyframes(const int index) {
286     QString result;
287     QDomElement effect = effectAt(index);
288     QDomNodeList params = effect.elementsByTagName("parameter");
289
290     for (int i = 0; i < params.count(); i++) {
291         QDomElement e = params.item(i).toElement();
292         if (!e.isNull() && e.attribute("type") == "keyframe") {
293             result = e.attribute("keyframes");
294             break;
295         }
296     }
297     return result;
298 }
299
300 void ClipItem::updateKeyframeEffect() {
301     // regenerate xml parameter from the clip keyframes
302     QDomElement effect = effectAt(m_selectedEffect);
303     if (effect.attribute("disabled") == "1") return;
304     QDomNodeList params = effect.elementsByTagName("parameter");
305
306     for (int i = 0; i < params.count(); i++) {
307         QDomElement e = params.item(i).toElement();
308         if (!e.isNull() && e.attribute("type") == "keyframe") {
309             QString keyframes;
310             if (m_keyframes.count() > 1) {
311                 QMap<int, double>::const_iterator i = m_keyframes.constBegin();
312                 double x1;
313                 double y1;
314                 while (i != m_keyframes.constEnd()) {
315                     keyframes.append(QString::number(i.key()) + ":" + QString::number(i.value()) + ";");
316                     ++i;
317                 }
318             }
319             // Effect has a keyframe type parameter, we need to set the values
320             //kDebug() << ":::::::::::::::   SETTING EFFECT KEYFRAMES: " << keyframes;
321             e.setAttribute("keyframes", keyframes);
322             break;
323         }
324     }
325 }
326
327 QDomElement ClipItem::selectedEffect() {
328     if (m_selectedEffect == -1 || m_effectList.isEmpty()) return QDomElement();
329     return effectAt(m_selectedEffect);
330 }
331
332 void ClipItem::resetThumbs() {
333     m_startPix = QPixmap();
334     m_endPix = QPixmap();
335     slotFetchThumbs();
336     audioThumbCachePic.clear();
337 }
338
339
340 void ClipItem::refreshClip() {
341     m_maxDuration = m_clip->maxDuration();
342     if (m_clipType == COLOR) {
343         QString colour = m_clip->getProperty("colour");
344         colour = colour.replace(0, 2, "#");
345         setBrush(QColor(colour.left(7)));
346     } else slotFetchThumbs();
347 }
348
349 void ClipItem::slotFetchThumbs() {
350     if (m_endPix.isNull() && m_startPix.isNull()) {
351         m_startThumbRequested = true;
352         m_endThumbRequested = true;
353         emit getThumb((int)cropStart().frames(m_fps), (int)(cropStart() + cropDuration()).frames(m_fps) - 1);
354     } else {
355         if (m_endPix.isNull()) {
356             slotGetEndThumb();
357         }
358         if (m_startPix.isNull()) {
359             slotGetStartThumb();
360         }
361     }
362     /*
363         if (m_hasThumbs) {
364             if (m_endPix.isNull() && m_startPix.isNull()) {
365                 int frame1 = (int)m_cropStart.frames(m_fps);
366                 int frame2 = (int)(m_cropStart + m_cropDuration).frames(m_fps) - 1;
367                 //videoThumbProducer.setThumbFrames(m_clip->producer(), frame1, frame2);
368                 //videoThumbProducer.start(QThread::LowestPriority);
369             } else {
370                 if (m_endPix.isNull()) slotGetEndThumb();
371                 else slotGetStartThumb();
372             }
373
374         } else if (m_startPix.isNull()) slotGetStartThumb();*/
375 }
376
377 void ClipItem::slotGetStartThumb() {
378     m_startThumbRequested = true;
379     emit getThumb((int)cropStart().frames(m_fps), -1);
380     //videoThumbProducer.setThumbFrames(m_clip->producer(), (int)m_cropStart.frames(m_fps),  - 1);
381     //videoThumbProducer.start(QThread::LowestPriority);
382 }
383
384 void ClipItem::slotGetEndThumb() {
385     m_endThumbRequested = true;
386     emit getThumb(-1, (int)(cropStart() + cropDuration()).frames(m_fps) - 1);
387     //videoThumbProducer.setThumbFrames(m_clip->producer(), -1, (int)(m_cropStart + m_cropDuration).frames(m_fps) - 1);
388     //videoThumbProducer.start(QThread::LowestPriority);
389 }
390
391
392 void ClipItem::slotSetStartThumb(QImage img) {
393     if (!img.isNull() && img.format() == QImage::Format_ARGB32) {
394         QPixmap pix = QPixmap::fromImage(img);
395         m_startPix = pix;
396         QRectF r = sceneBoundingRect();
397         r.setRight(pix.width() + 2);
398         update(r);
399     }
400 }
401
402 void ClipItem::slotSetEndThumb(QImage img) {
403     if (!img.isNull() && img.format() == QImage::Format_ARGB32) {
404         QPixmap pix = QPixmap::fromImage(img);
405         m_endPix = pix;
406         QRectF r = sceneBoundingRect();
407         r.setLeft(r.right() - pix.width() - 2);
408         update(r);
409     }
410 }
411
412 void ClipItem::slotThumbReady(int frame, QPixmap pix) {
413     if (scene() == NULL) return;
414     QRectF r = sceneBoundingRect();
415     double width = m_startPix.width() / projectScene()->scale();
416     if (m_startThumbRequested && frame == cropStart().frames(m_fps)) {
417         m_startPix = pix;
418         m_startThumbRequested = false;
419         double height = r.height();
420         update(r.x(), r.y(), width, height);
421     } else if (m_endThumbRequested && frame == (cropStart() + cropDuration()).frames(m_fps) - 1) {
422         m_endPix = pix;
423         m_endThumbRequested = false;
424         double height = r.height();
425         update(r.right() - width, r.y(), width, height);
426     }
427 }
428
429 void ClipItem::slotSetStartThumb(QPixmap pix) {
430     m_startPix = pix;
431 }
432
433 void ClipItem::slotSetEndThumb(QPixmap pix) {
434     m_endPix = pix;
435 }
436
437 void ClipItem::slotGotAudioData() {
438     audioThumbReady = true;
439     if (m_clipType == AV) {
440         QRectF r = boundingRect();
441         r.setTop(r.top() + r.height() / 2 - 1);
442         update(r);
443     } else update();
444 }
445
446 int ClipItem::type() const {
447     return AVWIDGET;
448 }
449
450 DocClipBase *ClipItem::baseClip() const {
451     return m_clip;
452 }
453
454 QDomElement ClipItem::xml() const {
455     return m_clip->toXML();
456 }
457
458 int ClipItem::clipType() const {
459     return m_clipType;
460 }
461
462 QString ClipItem::clipName() const {
463     return m_clipName;
464 }
465
466 const QString &ClipItem::clipProducer() const {
467     return m_producer;
468 }
469
470 void ClipItem::flashClip() {
471     if (m_timeLine == 0) {
472         m_timeLine = new QTimeLine(750, this);
473         m_timeLine->setCurveShape(QTimeLine::EaseInOutCurve);
474         connect(m_timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(animate(qreal)));
475     }
476     m_timeLine->start();
477 }
478
479 void ClipItem::animate(qreal value) {
480     QRectF r = boundingRect();
481     r.setHeight(20);
482     update(r);
483 }
484
485 // virtual
486 void ClipItem::paint(QPainter *painter,
487                      const QStyleOptionGraphicsItem *option,
488                      QWidget *) {
489     painter->setOpacity(m_opacity);
490     QBrush paintColor = brush();
491     if (isSelected()) paintColor = QBrush(QColor(79, 93, 121));
492     QRectF br = rect();
493     QRectF exposed = option->exposedRect;
494     QRectF mapped = painter->matrix().mapRect(br);
495
496     const double itemWidth = br.width();
497     const double itemHeight = br.height();
498     const double scale = option->matrix.m11();
499
500
501     //painter->setRenderHints(QPainter::Antialiasing);
502
503     //QPainterPath roundRectPathUpper = upperRectPart(br), roundRectPathLower = lowerRectPart(br);
504     painter->setClipRect(exposed);
505
506     //build path around clip
507     //QPainterPath resultClipPath = roundRectPathUpper.united(roundRectPathLower);
508     //painter->fillPath(resultClipPath, paintColor);
509     painter->fillRect(br, paintColor);
510
511     //painter->setClipPath(resultClipPath, Qt::IntersectClip);
512
513     // draw thumbnails
514     painter->setMatrixEnabled(false);
515
516     if (KdenliveSettings::videothumbnails()) {
517         QPen pen = painter->pen();
518         pen.setStyle(Qt::DotLine);
519         pen.setColor(Qt::white);
520         painter->setPen(pen);
521         if (m_clipType == IMAGE && !m_startPix.isNull()) {
522             QPointF p1 = painter->matrix().map(QPointF(itemWidth, 0)) - QPointF(m_startPix.width(), 0);
523             QPointF p2 = painter->matrix().map(QPointF(itemWidth, itemHeight)) - QPointF(m_startPix.width(), 0);
524             painter->drawPixmap(p1, m_startPix);
525             QLineF l(p1, p2);
526             painter->drawLine(l);
527         } else if (!m_endPix.isNull()) {
528             QPointF p1 = painter->matrix().map(QPointF(itemWidth, 0)) - QPointF(m_endPix.width(), 0);
529             QPointF p2 = painter->matrix().map(QPointF(itemWidth, itemHeight)) - QPointF(m_endPix.width(), 0);
530             painter->drawPixmap(p1, m_endPix);
531             QLineF l(p1, p2);
532             painter->drawLine(l);
533         }
534         if (!m_startPix.isNull()) {
535             QPointF p1 = painter->matrix().map(QPointF(0, 0));
536             QPointF p2 = painter->matrix().map(QPointF(0, itemHeight));
537             painter->drawPixmap(p1, m_startPix);
538             QLineF l2(p1.x() + m_startPix.width(), p1.y(), p2.x() + m_startPix.width(), p2.y());
539             painter->drawLine(l2);
540         }
541         painter->setPen(Qt::black);
542     }
543
544     // draw audio thumbnails
545     if (KdenliveSettings::audiothumbnails() && m_speed == 1.0 && ((m_clipType == AV && exposed.bottom() > (itemHeight / 2)) || m_clipType == AUDIO) && audioThumbReady) {
546
547         double startpixel = exposed.left();
548         if (startpixel < 0)
549             startpixel = 0;
550         double endpixel = exposed.right();
551         if (endpixel < 0)
552             endpixel = 0;
553         //kDebug()<<"///  REPAINTING AUDIO THMBS ZONE: "<<startpixel<<"x"<<endpixel;
554
555         /*QPainterPath path = m_clipType == AV ? roundRectPathLower : resultClipPath;*/
556         QRectF mappedRect;
557         if (m_clipType == AV) {
558             QRectF re =  br;
559             re.setTop(re.y() + re.height() / 2);
560             mappedRect = painter->matrix().mapRect(re);
561             //painter->fillRect(mappedRect, QBrush(QColor(200, 200, 200, 140)));
562         } else mappedRect = mapped;
563
564         int channels = baseClip()->getProperty("channels").toInt();
565         if (scale != framePixelWidth)
566             audioThumbCachePic.clear();
567         double cropLeft = m_cropStart.frames(m_fps);
568         const int clipStart = mappedRect.x();
569         const int mappedStartPixel =  painter->matrix().map(QPointF(startpixel + cropLeft, 0)).x() - clipStart;
570         const int mappedEndPixel =  painter->matrix().map(QPointF(endpixel + cropLeft, 0)).x() - clipStart;
571         cropLeft = cropLeft * scale;
572
573         if (channels >= 1) {
574             emit prepareAudioThumb(scale, mappedStartPixel, mappedEndPixel, channels);
575         }
576
577         for (int startCache = mappedStartPixel - (mappedStartPixel) % 100; startCache < mappedEndPixel; startCache += 100) {
578             if (audioThumbCachePic.contains(startCache) && !audioThumbCachePic[startCache].isNull())
579                 painter->drawPixmap(clipStart + startCache - cropLeft, mappedRect.y(),  audioThumbCachePic[startCache]);
580         }
581     }
582
583     // draw markers
584     QList < CommentedTime > markers = baseClip()->commentedSnapMarkers();
585     QList < CommentedTime >::Iterator it = markers.begin();
586     GenTime pos;
587     double framepos;
588     const int markerwidth = 4;
589     QBrush markerBrush;
590     markerBrush = QBrush(QColor(120, 120, 0, 140));
591     QPen pen = painter->pen();
592     pen.setColor(QColor(255, 255, 255, 200));
593     pen.setStyle(Qt::DotLine);
594     painter->setPen(pen);
595     for (; it != markers.end(); ++it) {
596         pos = (*it).time() - cropStart();
597         if (pos > GenTime()) {
598             if (pos > duration()) break;
599             QLineF l(br.x() + pos.frames(m_fps), br.y() + 5, br.x() + pos.frames(m_fps), br.bottom() - 5);
600             QLineF l2 = painter->matrix().map(l);
601             //framepos = scale * pos.frames(m_fps);
602             //QLineF l(framepos, 5, framepos, itemHeight - 5);
603             painter->drawLine(l2);
604             if (KdenliveSettings::showmarkers()) {
605                 framepos = br.x() + pos.frames(m_fps);
606                 const QRectF r1(framepos + 0.04, 10, itemWidth - framepos - 2, itemHeight - 10);
607                 const QRectF r2 = painter->matrix().mapRect(r1);
608                 const QRectF txtBounding = painter->boundingRect(r2, Qt::AlignLeft | Qt::AlignTop, " " + (*it).comment() + " ");
609
610                 QPainterPath path;
611                 path.addRoundedRect(txtBounding, 3, 3);
612                 painter->fillPath(path, markerBrush);
613                 painter->drawText(txtBounding, Qt::AlignCenter, (*it).comment());
614             }
615             //painter->fillRect(QRect(br.x() + framepos, br.y(), 10, br.height()), QBrush(QColor(0, 0, 0, 150)));
616         }
617     }
618     pen.setColor(Qt::black);
619     pen.setStyle(Qt::SolidLine);
620     painter->setPen(pen);
621
622     // draw start / end fades
623     QBrush fades;
624     if (isSelected()) {
625         fades = QBrush(QColor(200, 50, 50, 150));
626     } else fades = QBrush(QColor(200, 200, 200, 200));
627
628     if (m_startFade != 0) {
629         QPainterPath fadeInPath;
630         fadeInPath.moveTo(0, 0);
631         fadeInPath.lineTo(0, itemHeight);
632         fadeInPath.lineTo(m_startFade, 0);
633         fadeInPath.closeSubpath();
634         QPainterPath f1 = painter->matrix().map(fadeInPath);
635         painter->fillPath(f1/*.intersected(resultClipPath)*/, fades);
636         /*if (isSelected()) {
637             QLineF l(m_startFade * scale, 0, 0, itemHeight);
638             painter->drawLine(l);
639         }*/
640     }
641     if (m_endFade != 0) {
642         QPainterPath fadeOutPath;
643         fadeOutPath.moveTo(itemWidth, 0);
644         fadeOutPath.lineTo(itemWidth, itemHeight);
645         fadeOutPath.lineTo(itemWidth - m_endFade, 0);
646         fadeOutPath.closeSubpath();
647         QPainterPath f1 = painter->matrix().map(fadeOutPath);
648         painter->fillPath(f1/*.intersected(resultClipPath)*/, fades);
649         /*if (isSelected()) {
650             QLineF l(itemWidth - m_endFade * scale, 0, itemWidth, itemHeight);
651             painter->drawLine(l);
652         }*/
653     }
654
655     // Draw effects names
656     if (!m_effectNames.isEmpty() && itemWidth * scale > 40) {
657         QRectF txtBounding = painter->boundingRect(mapped, Qt::AlignLeft | Qt::AlignTop, m_effectNames);
658         txtBounding.setRight(txtBounding.right() + 15);
659         painter->setPen(Qt::white);
660         QBrush markerBrush(Qt::SolidPattern);
661         if (m_timeLine && m_timeLine->state() == QTimeLine::Running) {
662             qreal value = m_timeLine->currentValue();
663             txtBounding.setWidth(txtBounding.width() * value);
664             markerBrush.setColor(QColor(50 + 200 * (1.0 - value), 50, 50, 100 + 50 * value));
665         } else markerBrush.setColor(QColor(50, 50, 50, 150));
666         QPainterPath path;
667         path.addRoundedRect(txtBounding, 4, 4);
668         painter->fillPath(path/*.intersected(resultClipPath)*/, markerBrush);
669         painter->drawText(txtBounding, Qt::AlignCenter, m_effectNames);
670         painter->setPen(Qt::black);
671     }
672
673     // Draw clip name
674     QRectF txtBounding = painter->boundingRect(mapped, Qt::AlignHCenter | Qt::AlignVCenter, " " + m_clipName + " ");
675     painter->fillRect(txtBounding, QBrush(QColor(0, 0, 0, 150)));
676     //painter->setPen(QColor(0, 0, 0, 180));
677     //painter->drawText(txtBounding, Qt::AlignCenter, m_clipName);
678     txtBounding.translate(QPointF(1, 1));
679     painter->setPen(QColor(255, 255, 255, 255));
680     painter->drawText(txtBounding, Qt::AlignCenter, m_clipName);
681
682
683     // draw transition handles on hover
684     if (m_hover && itemWidth * scale > 40) {
685         QPointF p1 = painter->matrix().map(QPointF(0, itemHeight / 2)) + QPointF(10, 0);
686         painter->drawPixmap(p1, projectScene()->m_transitionPixmap);
687         p1 = painter->matrix().map(QPointF(itemWidth, itemHeight / 2)) - QPointF(22, 0);
688         painter->drawPixmap(p1, projectScene()->m_transitionPixmap);
689     }
690
691
692     // draw frame around clip
693     if (isSelected()) {
694         pen.setColor(Qt::red);
695         //pen.setWidth(2);
696     } else {
697         pen.setColor(Qt::black);
698         //pen.setWidth(1);
699     }
700
701     // draw effect or transition keyframes
702     if (itemWidth > 20) drawKeyFrames(painter, exposed);
703
704     painter->setMatrixEnabled(true);
705
706     // draw clip border
707
708     //kDebug()<<"/// ITEM PAINTING:: exposed="<<exposed<<", RECT = "<<rect();
709
710     // expand clip rect to allow correct painting of clip border
711     exposed.setRight(exposed.right() + 1 / scale + 0.5);
712     exposed.setBottom(exposed.bottom() + 1);
713     painter->setClipRect(exposed);
714     painter->setPen(pen);
715     painter->drawRect(br);
716 }
717
718
719 OPERATIONTYPE ClipItem::operationMode(QPointF pos) {
720     if (isSelected()) {
721         m_editedKeyframe = mouseOverKeyFrames(pos);
722         if (m_editedKeyframe != -1) return KEYFRAME;
723     }
724     QRectF rect = sceneBoundingRect();
725     const double scale = projectScene()->scale();
726     double maximumOffset = 6 / scale;
727
728     if (qAbs((int)(pos.x() - (rect.x() + m_startFade))) < maximumOffset  && qAbs((int)(pos.y() - rect.y())) < 6) {
729         if (m_startFade == 0) setToolTip(i18n("Add audio fade"));
730         else setToolTip(i18n("Audio fade duration: %1s", GenTime(m_startFade, m_fps).seconds()));
731         return FADEIN;
732     } else if (pos.x() - rect.x() < maximumOffset) {
733         setToolTip(i18n("Crop from start: %1s", cropStart().seconds()));
734         return RESIZESTART;
735     } else if (qAbs((int)(pos.x() - (rect.x() + rect.width() - m_endFade))) < maximumOffset && qAbs((int)(pos.y() - rect.y())) < 6) {
736         if (m_endFade == 0) setToolTip(i18n("Add audio fade"));
737         else setToolTip(i18n("Audio fade duration: %1s", GenTime(m_endFade, m_fps).seconds()));
738         return FADEOUT;
739     } else if (qAbs((int)(pos.x() - (rect.x() + rect.width()))) < maximumOffset) {
740         setToolTip(i18n("Clip duration: %1s", duration().seconds()));
741         return RESIZEEND;
742     } else if (qAbs((int)(pos.x() - (rect.x() + 16 / scale))) < maximumOffset && qAbs((int)(pos.y() - (rect.y() + rect.height() / 2 + 9))) < 6) {
743         setToolTip(i18n("Add transition"));
744         return TRANSITIONSTART;
745     } else if (qAbs((int)(pos.x() - (rect.x() + rect.width() - 21 / scale))) < maximumOffset && qAbs((int)(pos.y() - (rect.y() + rect.height() / 2 + 9))) < 6) {
746         setToolTip(i18n("Add transition"));
747         return TRANSITIONEND;
748     }
749     setToolTip(QString());
750     return MOVE;
751 }
752
753 QList <GenTime> ClipItem::snapMarkers() const {
754     QList < GenTime > snaps;
755     QList < GenTime > markers = baseClip()->snapMarkers();
756     GenTime pos;
757     double framepos;
758
759     for (int i = 0; i < markers.size(); i++) {
760         pos = markers.at(i) - cropStart();
761         if (pos > GenTime()) {
762             if (pos > duration()) break;
763             else snaps.append(pos + startPos());
764         }
765     }
766     return snaps;
767 }
768
769 QList <CommentedTime> ClipItem::commentedSnapMarkers() const {
770     QList < CommentedTime > snaps;
771     QList < CommentedTime > markers = baseClip()->commentedSnapMarkers();
772     GenTime pos;
773     double framepos;
774
775     for (int i = 0; i < markers.size(); i++) {
776         pos = markers.at(i).time() - cropStart();
777         if (pos > GenTime()) {
778             if (pos > duration()) break;
779             else snaps.append(CommentedTime(pos + startPos(), markers.at(i).comment()));
780         }
781     }
782     return snaps;
783 }
784
785 void ClipItem::slotPrepareAudioThumb(double pixelForOneFrame, int startpixel, int endpixel, int channels) {
786     QRectF re =  sceneBoundingRect();
787     if (m_clipType == AV) re.setTop(re.y() + re.height() / 2);
788
789     //kDebug() << "// PREP AUDIO THMB FRMO : scale:" << pixelForOneFrame<< ", from: " << startpixel << ", to: " << endpixel;
790     //if ( (!audioThumbWasDrawn || framePixelWidth!=pixelForOneFrame ) && !baseClip()->audioFrameChache.isEmpty()){
791
792     for (int startCache = startpixel - startpixel % 100;startCache < endpixel;startCache += 100) {
793         //kDebug() << "creating " << startCache;
794         //if (framePixelWidth!=pixelForOneFrame  ||
795         if (framePixelWidth == pixelForOneFrame && audioThumbCachePic.contains(startCache))
796             continue;
797         if (audioThumbCachePic[startCache].isNull() || framePixelWidth != pixelForOneFrame) {
798             audioThumbCachePic[startCache] = QPixmap(100, (int)(re.height()));
799             audioThumbCachePic[startCache].fill(QColor(180, 180, 200, 140));
800         }
801         bool fullAreaDraw = pixelForOneFrame < 10;
802         QMap<int, QPainterPath > positiveChannelPaths;
803         QMap<int, QPainterPath > negativeChannelPaths;
804         QPainter pixpainter(&audioThumbCachePic[startCache]);
805         QPen audiopen;
806         audiopen.setWidth(0);
807         pixpainter.setPen(audiopen);
808         //pixpainter.setRenderHint(QPainter::Antialiasing,true);
809         //pixpainter.drawLine(0,0,100,re.height());
810         // Bail out, if caller provided invalid data
811         if (channels <= 0) {
812             kWarning() << "Unable to draw image with " << channels << "number of channels";
813             return;
814         }
815
816         int channelHeight = audioThumbCachePic[startCache].height() / channels;
817
818         for (int i = 0;i < channels;i++) {
819
820             positiveChannelPaths[i].moveTo(0, channelHeight*i + channelHeight / 2);
821             negativeChannelPaths[i].moveTo(0, channelHeight*i + channelHeight / 2);
822         }
823
824         for (int samples = 0;samples <= 100;samples++) {
825             double frame = (double)(samples + startCache - 0) / pixelForOneFrame;
826             int sample = (int)((frame - (int)(frame)) * 20);   // AUDIO_FRAME_SIZE
827             if (frame < 0 || sample < 0 || sample > 19)
828                 continue;
829             QMap<int, QByteArray> frame_channel_data = baseClip()->audioFrameChache[(int)frame];
830
831             for (int channel = 0;channel < channels && frame_channel_data[channel].size() > 0;channel++) {
832
833                 int y = channelHeight * channel + channelHeight / 2;
834                 int delta = (int)(frame_channel_data[channel][sample] - 127 / 2)  * channelHeight / 64;
835                 if (fullAreaDraw) {
836                     positiveChannelPaths[channel].lineTo(samples, 0.1 + y + qAbs(delta));
837                     negativeChannelPaths[channel].lineTo(samples, 0.1 + y - qAbs(delta));
838                 } else {
839                     positiveChannelPaths[channel].lineTo(samples, 0.1 + y + delta);
840                     negativeChannelPaths[channel].lineTo(samples, 0.1 + y - delta);
841                 }
842             }
843             for (int channel = 0;channel < channels ;channel++)
844                 if (fullAreaDraw && samples == 100) {
845                     positiveChannelPaths[channel].lineTo(samples, channelHeight*channel + channelHeight / 2);
846                     negativeChannelPaths[channel].lineTo(samples, channelHeight*channel + channelHeight / 2);
847                     positiveChannelPaths[channel].lineTo(0, channelHeight*channel + channelHeight / 2);
848                     negativeChannelPaths[channel].lineTo(0, channelHeight*channel + channelHeight / 2);
849                 }
850
851         }
852         if (m_clipType != AV) pixpainter.setBrush(QBrush(QColor(200, 200, 100)));
853         else {
854             pixpainter.setPen(QPen(QColor(0, 0, 0)));
855             pixpainter.setBrush(QBrush(QColor(60, 60, 60)));
856         }
857         for (int i = 0;i < channels;i++) {
858             if (fullAreaDraw) {
859                 //pixpainter.fillPath(positiveChannelPaths[i].united(negativeChannelPaths[i]),QBrush(Qt::SolidPattern));//or singleif looks better
860                 pixpainter.drawPath(positiveChannelPaths[i].united(negativeChannelPaths[i]));//or singleif looks better
861             } else
862                 pixpainter.drawPath(positiveChannelPaths[i]);
863         }
864     }
865     //audioThumbWasDrawn=true;
866     framePixelWidth = pixelForOneFrame;
867
868     //}
869 }
870
871 uint ClipItem::fadeIn() const {
872     return m_startFade;
873 }
874
875 uint ClipItem::fadeOut() const {
876     return m_endFade;
877 }
878
879
880 void ClipItem::setFadeIn(int pos) {
881     if (pos == m_startFade) return;
882     int oldIn = m_startFade;
883     if (pos < 0) pos = 0;
884     if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps));
885     m_startFade = pos;
886     QRectF rect = boundingRect();
887     update(rect.x(), rect.y(), qMax(oldIn, pos), rect.height());
888 }
889
890 void ClipItem::setFadeOut(int pos) {
891     if (pos == m_endFade) return;
892     int oldOut = m_endFade;
893     if (pos < 0) pos = 0;
894     if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps));
895     m_endFade = pos;
896     QRectF rect = boundingRect();
897     update(rect.x() + rect.width() - qMax(oldOut, pos), rect.y(), qMax(oldOut, pos), rect.height());
898
899 }
900
901 // virtual
902 void ClipItem::mousePressEvent(QGraphicsSceneMouseEvent * event) {
903     /*m_resizeMode = operationMode(event->pos());
904     if (m_resizeMode == MOVE) {
905       m_maxTrack = scene()->sceneRect().height();
906       m_grabPoint = (int) (event->pos().x() - rect().x());
907     }*/
908     QGraphicsRectItem::mousePressEvent(event);
909 }
910
911 // virtual
912 void ClipItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
913     m_resizeMode = NONE;
914     QGraphicsRectItem::mouseReleaseEvent(event);
915 }
916
917 //virtual
918 void ClipItem::hoverEnterEvent(QGraphicsSceneHoverEvent *e) {
919     //if (e->pos().x() < 20) m_hover = true;
920     m_hover = true;
921     QRectF r = boundingRect();
922     double width = 35 / projectScene()->scale();
923     double height = r.height() / 2;
924     //WARNING: seems like it generates a full repaint of the clip, maybe not so good...
925     update(r.x(), r.y() + height, width, height);
926     update(r.right() - width, r.y() + height, width, height);
927 }
928
929 //virtual
930 void ClipItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
931     m_hover = false;
932     QRectF r = boundingRect();
933     double width = 35 / projectScene()->scale();
934     double height = r.height() / 2;
935     //WARNING: seems like it generates a full repaint of the clip, maybe not so good...
936     update(r.x(), r.y() + height, width, height);
937     update(r.right() - width, r.y() + height, width, height);
938 }
939
940 void ClipItem::resizeStart(int posx, double speed) {
941     const int min = (startPos() - cropStart()).frames(m_fps);
942     if (posx < min) posx = min;
943     if (posx == startPos().frames(m_fps)) return;
944     const int previous = cropStart().frames(m_fps);
945     AbstractClipItem::resizeStart(posx, m_speed);
946     checkEffectsKeyframesPos(previous, cropStart().frames(m_fps), true);
947     if (m_hasThumbs && KdenliveSettings::videothumbnails()) {
948         /*connect(m_clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap)));*/
949         startThumbTimer->start(100);
950     }
951 }
952
953 void ClipItem::resizeEnd(int posx, double speed, bool updateKeyFrames) {
954     const int max = (startPos() - cropStart() + maxDuration()).frames(m_fps) + 1;
955     if (posx > max) posx = max;
956     if (posx == endPos().frames(m_fps)) return;
957     kDebug() << "// NEW POS: " << posx << ", OLD END: " << endPos().frames(m_fps);
958     const int previous = (cropStart() + duration()).frames(m_fps);
959     AbstractClipItem::resizeEnd(posx, m_speed);
960     if (updateKeyFrames) checkEffectsKeyframesPos(previous, (cropStart() + duration()).frames(m_fps), false);
961     if (m_hasThumbs && KdenliveSettings::videothumbnails()) {
962         /*connect(m_clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap)));*/
963         endThumbTimer->start(100);
964     }
965 }
966
967
968 void ClipItem::checkEffectsKeyframesPos(const int previous, const int current, bool fromStart) {
969     for (int i = 0; i < m_effectList.size(); i++) {
970         QDomElement effect = m_effectList.at(i);
971         QDomNodeList params = effect.elementsByTagName("parameter");
972         for (int j = 0; j < params.count(); j++) {
973             QDomElement e = params.item(i).toElement();
974             if (e.attribute("type") == "keyframe") {
975                 // parse keyframes and adjust values
976                 const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts);
977                 QMap <int, double> kfr;
978                 int pos;
979                 double val;
980                 foreach(const QString str, keyframes) {
981                     pos = str.section(":", 0, 0).toInt();
982                     val = str.section(":", 1, 1).toDouble();
983                     if (pos == previous) kfr[current] = val;
984                     else {
985                         if (fromStart && pos >= current) kfr[pos] = val;
986                         else if (!fromStart && pos <= current) kfr[pos] = val;
987                     }
988                 }
989                 QString newkfr;
990                 QMap<int, double>::const_iterator k = kfr.constBegin();
991                 while (k != kfr.constEnd()) {
992                     newkfr.append(QString::number(k.key()) + ":" + QString::number(k.value()) + ";");
993                     ++k;
994                 }
995                 e.setAttribute("keyframes", newkfr);
996                 break;
997             }
998         }
999     }
1000     if (m_selectedEffect >= 0) setSelectedEffect(m_selectedEffect);
1001 }
1002
1003 //virtual
1004 QVariant ClipItem::itemChange(GraphicsItemChange change, const QVariant &value) {
1005     if (change == ItemPositionChange && scene()) {
1006         // calculate new position.
1007         if (group() != 0) return pos();
1008         QPointF newPos = value.toPointF();
1009         //kDebug() << "/// MOVING CLIP ITEM.------------";
1010         int xpos = projectScene()->getSnapPointForPos((int) newPos.x(), KdenliveSettings::snaptopoints());
1011         xpos = qMax(xpos, 0);
1012         newPos.setX(xpos);
1013         int newTrack = newPos.y() / KdenliveSettings::trackheight();
1014         newTrack = qMin(newTrack, projectScene()->tracksCount() - 1);
1015         newTrack = qMax(newTrack, 0);
1016         newPos.setY((int)(newTrack  * KdenliveSettings::trackheight() + 1));
1017         // Only one clip is moving
1018         QRectF sceneShape = rect();
1019         sceneShape.translate(newPos);
1020         QList<QGraphicsItem*> items = scene()->items(sceneShape, Qt::IntersectsItemShape);
1021         items.removeAll(this);
1022
1023         if (!items.isEmpty()) {
1024             for (int i = 0; i < items.count(); i++) {
1025                 if (items.at(i)->type() == type()) {
1026                     // Collision!
1027                     QPointF otherPos = items.at(i)->pos();
1028                     if ((int) otherPos.y() != (int) pos().y()) return pos();
1029                     if (pos().x() < otherPos.x()) {
1030                         // move clip just before colliding clip
1031                         int npos = (static_cast < AbstractClipItem* >(items.at(i))->startPos() - m_cropDuration).frames(m_fps);
1032                         // check we don't run into another clip
1033                         newPos.setX(npos);
1034                         sceneShape = rect();
1035                         sceneShape.translate(newPos);
1036                         QList<QGraphicsItem*> subitems = scene()->items(sceneShape, Qt::IntersectsItemShape);
1037                         items.removeAll(this);
1038                         for (int j = 0; j < subitems.count(); j++) {
1039                             if (subitems.at(j)->type() == type()) return pos();
1040                         }
1041                     } else {
1042                         // get pos just after colliding clip
1043                         int npos = static_cast < AbstractClipItem* >(items.at(i))->endPos().frames(m_fps);
1044                         // check we don't run into another clip
1045                         newPos.setX(npos);
1046                         sceneShape = rect();
1047                         sceneShape.translate(newPos);
1048                         QList<QGraphicsItem*> subitems = scene()->items(sceneShape, Qt::IntersectsItemShape);
1049                         items.removeAll(this);
1050                         for (int j = 0; j < subitems.count(); j++) {
1051                             if (subitems.at(j)->type() == type()) return pos();
1052                         }
1053                     }
1054                     m_track = newTrack;
1055                     m_startPos = GenTime((int) newPos.x(), m_fps);
1056                     return newPos;
1057                 }
1058             }
1059         }
1060         m_track = newTrack;
1061         m_startPos = GenTime((int) newPos.x(), m_fps);
1062         //kDebug()<<"// ITEM NEW POS: "<<newPos.x()<<", mapped: "<<mapToScene(newPos.x(), 0).x();
1063         return newPos;
1064     }
1065     return QGraphicsItem::itemChange(change, value);
1066 }
1067
1068 // virtual
1069 /*void ClipItem::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
1070 }*/
1071
1072 int ClipItem::effectsCounter() {
1073     return effectsCount() + 1;
1074 }
1075
1076 int ClipItem::effectsCount() {
1077     return m_effectList.size();
1078 }
1079
1080 int ClipItem::hasEffect(const QString &tag, const QString &id) const {
1081     return m_effectList.hasEffect(tag, id);
1082 }
1083
1084 QStringList ClipItem::effectNames() {
1085     return m_effectList.effectNames();
1086 }
1087
1088 QDomElement ClipItem::effectAt(int ix) {
1089     if (ix > m_effectList.count() - 1 || ix < 0) return QDomElement();
1090     return m_effectList.at(ix);
1091 }
1092
1093 void ClipItem::setEffectAt(int ix, QDomElement effect) {
1094     kDebug() << "CHange EFFECT AT: " << ix << ", CURR: " << m_effectList.at(ix).attribute("tag") << ", NEW: " << effect.attribute("tag");
1095     effect.setAttribute("kdenlive_ix", ix + 1);
1096     m_effectList.insert(ix, effect);
1097     m_effectList.removeAt(ix + 1);
1098     m_effectNames = m_effectList.effectNames().join(" / ");
1099     if (effect.attribute("id") == "fadein" || effect.attribute("id") == "fadeout") update(boundingRect());
1100     else {
1101         QRectF r = boundingRect();
1102         r.setHeight(20);
1103         update(r);
1104     }
1105 }
1106
1107 QHash <QString, QString> ClipItem::addEffect(QDomElement effect, bool animate) {
1108     QHash <QString, QString> effectParams;
1109     bool needRepaint = false;
1110     /*QDomDocument doc;
1111     doc.appendChild(doc.importNode(effect, true));
1112     kDebug() << "///////  CLIP ADD EFFECT: " << doc.toString();*/
1113     m_effectList.append(effect);
1114     effectParams["tag"] = effect.attribute("tag");
1115     QString effectId = effect.attribute("id");
1116     if (effectId.isEmpty()) effectId = effect.attribute("tag");
1117     effectParams["id"] = effectId;
1118     effectParams["kdenlive_ix"] = effect.attribute("kdenlive_ix");
1119     QString state = effect.attribute("disabled");
1120     if (!state.isEmpty()) effectParams["disabled"] = state;
1121     QDomNodeList params = effect.elementsByTagName("parameter");
1122     int fade = 0;
1123     for (int i = 0; i < params.count(); i++) {
1124         QDomElement e = params.item(i).toElement();
1125         if (!e.isNull()) {
1126             if (e.attribute("type") == "keyframe") {
1127                 effectParams["keyframes"] = e.attribute("keyframes");
1128                 effectParams["min"] = e.attribute("min");
1129                 effectParams["max"] = e.attribute("max");
1130                 effectParams["factor"] = e.attribute("factor", "1");
1131                 effectParams["starttag"] = e.attribute("starttag", "start");
1132                 effectParams["endtag"] = e.attribute("endtag", "end");
1133             }
1134
1135             double f = e.attribute("factor", "1").toDouble();
1136
1137             if (f == 1) {
1138                 effectParams[e.attribute("name")] = e.attribute("value");
1139                 // check if it is a fade effect
1140                 if (effectId == "fadein") {
1141                     needRepaint = true;
1142                     if (e.attribute("name") == "out") fade += e.attribute("value").toInt();
1143                     else if (e.attribute("name") == "in") fade -= e.attribute("value").toInt();
1144                 } else if (effectId == "fadeout") {
1145                     needRepaint = true;
1146                     if (e.attribute("name") == "out") fade -= e.attribute("value").toInt();
1147                     else if (e.attribute("name") == "in") fade += e.attribute("value").toInt();
1148                 }
1149             } else {
1150                 effectParams[e.attribute("name")] =  QString::number(e.attribute("value").toDouble() / f);
1151             }
1152         }
1153     }
1154     m_effectNames = m_effectList.effectNames().join(" / ");
1155     if (fade > 0) m_startFade = fade;
1156     else if (fade < 0) m_endFade = -fade;
1157     if (needRepaint) update(boundingRect());
1158     if (animate) {
1159         flashClip();
1160     } else if (!needRepaint) {
1161         QRectF r = boundingRect();
1162         r.setHeight(20);
1163         update(r);
1164     }
1165     if (m_selectedEffect == -1) {
1166         m_selectedEffect = 0;
1167         setSelectedEffect(m_selectedEffect);
1168     }
1169     return effectParams;
1170 }
1171
1172 QHash <QString, QString> ClipItem::getEffectArgs(QDomElement effect) {
1173     QHash <QString, QString> effectParams;
1174     effectParams["tag"] = effect.attribute("tag");
1175     effectParams["kdenlive_ix"] = effect.attribute("kdenlive_ix");
1176     effectParams["id"] = effect.attribute("id");
1177     QString state = effect.attribute("disabled");
1178     if (!state.isEmpty()) effectParams["disabled"] = state;
1179     QDomNodeList params = effect.elementsByTagName("parameter");
1180     for (int i = 0; i < params.count(); i++) {
1181         QDomElement e = params.item(i).toElement();
1182         //kDebug() << "/ / / /SENDING EFFECT PARAM: " << e.attribute("type") << ", NAME_ " << e.attribute("tag");
1183         if (e.attribute("type") == "keyframe") {
1184             kDebug() << "/ / / /SENDING KEYFR EFFECT TYPE";
1185             effectParams["keyframes"] = e.attribute("keyframes");
1186             effectParams["max"] = e.attribute("max");
1187             effectParams["min"] = e.attribute("min");
1188             effectParams["factor"] = e.attribute("factor", "1");
1189             effectParams["starttag"] = e.attribute("starttag", "start");
1190             effectParams["endtag"] = e.attribute("endtag", "end");
1191         } else if (e.attribute("namedesc").contains(";")) {
1192             QString format = e.attribute("format");
1193             QStringList separators = format.split("%d", QString::SkipEmptyParts);
1194             QStringList values = e.attribute("value").split(QRegExp("[,:;x]"));
1195             QString neu;
1196             QTextStream txtNeu(&neu);
1197             if (values.size() > 0)
1198                 txtNeu << (int)values[0].toDouble();
1199             for (int i = 0;i < separators.size() && i + 1 < values.size();i++) {
1200                 txtNeu << separators[i];
1201                 txtNeu << (int)(values[i+1].toDouble());
1202             }
1203             effectParams["start"] = neu;
1204         } else {
1205             if (e.attribute("factor", "1") != "1")
1206                 effectParams[e.attribute("name")] =  QString::number(e.attribute("value").toDouble() / e.attribute("factor").toDouble());
1207             else effectParams[e.attribute("name")] = e.attribute("value");
1208         }
1209     }
1210     return effectParams;
1211 }
1212
1213 void ClipItem::deleteEffect(QString index) {
1214     bool needRepaint = false;
1215     QString ix;
1216     for (int i = 0; i < m_effectList.size(); ++i) {
1217         ix = m_effectList.at(i).attribute("kdenlive_ix");
1218         if (ix == index) {
1219             if (m_effectList.at(i).attribute("id") == "fadein") {
1220                 m_startFade = 0;
1221                 needRepaint = true;
1222             } else if (m_effectList.at(i).attribute("id") == "fadeout") {
1223                 m_endFade = 0;
1224                 needRepaint = true;
1225             }
1226             m_effectList.removeAt(i);
1227         } else if (ix.toInt() > index.toInt()) m_effectList[i].setAttribute("kdenlive_ix", ix.toInt() - 1);
1228     }
1229     m_effectNames = m_effectList.effectNames().join(" / ");
1230     if (needRepaint) update(boundingRect());
1231     flashClip();
1232 }
1233
1234 double ClipItem::speed() const {
1235     return m_speed;
1236 }
1237
1238 void ClipItem::setSpeed(const double speed) {
1239     m_speed = speed;
1240     if (m_speed == 1.0) m_clipName = baseClip()->name();
1241     else m_clipName = baseClip()->name() + " - " + QString::number(speed * 100, 'f', 0) + "%";
1242     //update();
1243 }
1244
1245 GenTime ClipItem::maxDuration() const {
1246     return m_maxDuration / m_speed;
1247 }
1248
1249 GenTime ClipItem::cropStart() const {
1250     return m_cropStart / m_speed;
1251 }
1252
1253 GenTime ClipItem::cropDuration() const {
1254     return m_cropDuration / m_speed;
1255 }
1256
1257 GenTime ClipItem::endPos() const {
1258     return m_startPos + cropDuration();
1259 }
1260
1261 //virtual
1262 void ClipItem::dropEvent(QGraphicsSceneDragDropEvent * event) {
1263     QString effects = QString(event->mimeData()->data("kdenlive/effectslist"));
1264     QDomDocument doc;
1265     doc.setContent(effects, true);
1266     QDomElement e = doc.documentElement();
1267     CustomTrackView *view = (CustomTrackView *) scene()->views()[0];
1268     if (view) view->slotAddEffect(e, m_startPos, track());
1269 }
1270
1271 //virtual
1272 void ClipItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event) {
1273     event->setAccepted(event->mimeData()->hasFormat("kdenlive/effectslist"));
1274 }
1275
1276 void ClipItem::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) {
1277     Q_UNUSED(event);
1278 }
1279 void ClipItem::addTransition(Transition* t) {
1280     m_transitionsList.append(t);
1281     CustomTrackView *view = (CustomTrackView *) scene()->views()[0];
1282     QDomDocument doc;
1283     QDomElement e = doc.documentElement();
1284     //if (view) view->slotAddTransition(this, t->toXML() , t->startPos(), track());
1285 }
1286 // virtual
1287 /*
1288 void CustomTrackView::mousePressEvent ( QMouseEvent * event )
1289 {
1290   int pos = event->x();
1291   if (event->modifiers() == Qt::ControlModifier)
1292     setDragMode(QGraphicsView::ScrollHandDrag);
1293   else if (event->modifiers() == Qt::ShiftModifier)
1294     setDragMode(QGraphicsView::RubberBandDrag);
1295   else {
1296     QGraphicsItem * item = itemAt(event->pos());
1297     if (item) {
1298     }
1299     else emit cursorMoved((int) mapToScene(event->x(), 0).x());
1300   }
1301   kDebug()<<pos;
1302   QGraphicsView::mousePressEvent(event);
1303 }
1304
1305 void CustomTrackView::mouseReleaseEvent ( QMouseEvent * event )
1306 {
1307   QGraphicsView::mouseReleaseEvent(event);
1308   setDragMode(QGraphicsView::NoDrag);
1309 }
1310 */
1311
1312 #include "clipitem.moc"