]> git.sesse.net Git - kdenlive/blob - src/clipmanager.cpp
Merge branch 'refs/heads/v0.8.2'
[kdenlive] / src / clipmanager.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 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 #include "clipmanager.h"
22 #include "commands/addclipcommand.h"
23 #include "kdenlivesettings.h"
24 #include "docclipbase.h"
25 #include "kdenlivedoc.h"
26 #include "abstractclipitem.h"
27 #include "abstractgroupitem.h"
28 #include "titledocument.h"
29 #include "kthumb.h"
30
31 #include <mlt++/Mlt.h>
32
33 #include <KDebug>
34 #include <KMessageBox>
35 #include <KFileDialog>
36 #include <KApplication>
37 #include <kio/netaccess.h>
38
39 #include <QGraphicsItemGroup>
40 #include <QtConcurrentRun>
41
42 #include <KFileMetaInfo>
43
44 ClipManager::ClipManager(KdenliveDoc *doc) :
45     QObject(),
46     m_audioThumbsQueue(),
47     m_doc(doc),
48     m_generatingAudioId(),
49     m_abortThumb(false),
50     m_closing(false)
51 {
52     m_clipIdCounter = 1;
53     m_folderIdCounter = 1;
54     m_modifiedTimer.setInterval(1500);
55     connect(&m_fileWatcher, SIGNAL(dirty(const QString &)), this, SLOT(slotClipModified(const QString &)));
56     connect(&m_fileWatcher, SIGNAL(deleted(const QString &)), this, SLOT(slotClipMissing(const QString &)));
57     connect(&m_fileWatcher, SIGNAL(created(const QString &)), this, SLOT(slotClipAvailable(const QString &)));
58     connect(&m_modifiedTimer, SIGNAL(timeout()), this, SLOT(slotProcessModifiedClips()));
59
60 #if KDE_IS_VERSION(4,5,0)
61     KImageCache::deleteCache("kdenlive-thumbs");
62     pixmapCache = new KImageCache("kdenlive-thumbs", 1000000);
63     pixmapCache->setEvictionPolicy(KSharedDataCache::EvictOldest);
64 #endif
65 }
66
67 ClipManager::~ClipManager()
68 {
69     m_closing = true;
70     m_abortThumb = true;
71     m_thumbsThread.waitForFinished();
72     m_thumbsMutex.lock();
73     m_requestedThumbs.clear();
74     m_thumbsMutex.unlock();
75     m_audioThumbsQueue.clear();
76     m_generatingAudioId.clear();
77     m_thumbsMutex.lock();
78     m_requestedThumbs.clear();
79     m_thumbsMutex.unlock();
80     qDeleteAll(m_clipList);
81     m_clipList.clear();
82 #if KDE_IS_VERSION(4,5,0)
83     delete pixmapCache;
84 #endif
85 }
86
87 void ClipManager::clear()
88 {
89     m_abortThumb = true;
90     m_thumbsThread.waitForFinished();
91     m_thumbsMutex.lock();
92     m_requestedThumbs.clear();
93     m_thumbsMutex.unlock();
94     m_abortThumb = false;
95     m_folderList.clear();
96     m_audioThumbsQueue.clear();
97     m_modifiedClips.clear();
98     qDeleteAll(m_clipList);
99     m_clipList.clear();
100     m_clipIdCounter = 1;
101     m_folderIdCounter = 1;
102 #if KDE_IS_VERSION(4,5,0)
103     pixmapCache->clear();
104 #endif
105 }
106
107 void ClipManager::clearCache()
108 {
109 #if KDE_IS_VERSION(4,5,0)
110     pixmapCache->clear();
111 #endif
112 }
113
114 void ClipManager::requestThumbs(const QString id, QList <int> frames)
115 {
116     m_thumbsMutex.lock();
117     foreach (int frame, frames) {
118         m_requestedThumbs.insertMulti(id, frame);
119     }
120     m_thumbsMutex.unlock();
121     if (!m_thumbsThread.isRunning() && !m_abortThumb) {
122         m_thumbsThread = QtConcurrent::run(this, &ClipManager::slotGetThumbs);
123     }
124 }
125
126 void ClipManager::stopThumbs(const QString &id)
127 {
128     if (m_requestedThumbs.isEmpty() || m_closing) return;
129     m_abortThumb = true;
130     m_thumbsThread.waitForFinished();
131     m_thumbsMutex.lock();
132     m_requestedThumbs.remove(id);
133     m_thumbsMutex.unlock();
134     m_abortThumb = false;
135     if (!m_thumbsThread.isRunning()) {
136         m_thumbsThread = QtConcurrent::run(this, &ClipManager::slotGetThumbs);
137     }
138 }
139
140 void ClipManager::slotGetThumbs()
141 {
142     QMap<QString, int>::iterator i = m_requestedThumbs.begin();
143     while (i != m_requestedThumbs.end() && !m_abortThumb) {
144         QString producerId = i.key();
145         m_thumbsMutex.lock();
146         QList<int> values = m_requestedThumbs.values(producerId);
147         i = m_requestedThumbs.erase(i);
148         m_thumbsMutex.unlock();
149         qSort(values);
150         DocClipBase *clip = getClipById(producerId);
151         if (!clip) continue;
152         while (!values.isEmpty() && clip->thumbProducer() && !m_abortThumb) {
153             clip->thumbProducer()->getThumb(values.takeFirst());
154         }
155     }
156 }
157
158 void ClipManager::checkAudioThumbs()
159 {
160     if (!KdenliveSettings::audiothumbnails()) {
161         if (!m_generatingAudioId.isEmpty()) {
162             DocClipBase *clip = getClipById(m_generatingAudioId);
163             if (clip) clip->slotClearAudioCache();
164         }
165         m_audioThumbsQueue.clear();
166         m_generatingAudioId.clear();
167         return;
168     }
169
170     for (int i = 0; i < m_clipList.count(); i++) {
171         m_audioThumbsQueue.append(m_clipList.at(i)->getId());
172     }
173     if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
174 }
175
176 void ClipManager::askForAudioThumb(const QString &id)
177 {
178     DocClipBase *clip = getClipById(id);
179     if (clip && KdenliveSettings::audiothumbnails()) {
180         m_audioThumbsQueue.append(id);
181         if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
182     }
183 }
184
185 void ClipManager::startAudioThumbsGeneration()
186 {
187     if (!KdenliveSettings::audiothumbnails()) {
188         m_audioThumbsQueue.clear();
189         m_generatingAudioId.clear();
190         return;
191     }
192     if (!m_audioThumbsQueue.isEmpty()) {
193         m_generatingAudioId = m_audioThumbsQueue.takeFirst();
194         DocClipBase *clip = getClipById(m_generatingAudioId);
195         if (!clip || !clip->slotGetAudioThumbs())
196             endAudioThumbsGeneration(m_generatingAudioId);
197     } else {
198         m_generatingAudioId.clear();
199     }
200 }
201
202 void ClipManager::endAudioThumbsGeneration(const QString &requestedId)
203 {
204     if (!KdenliveSettings::audiothumbnails()) {
205         m_audioThumbsQueue.clear();
206         m_generatingAudioId.clear();
207         return;
208     }
209     if (!m_audioThumbsQueue.isEmpty()) {
210         if (m_generatingAudioId == requestedId) {
211             startAudioThumbsGeneration();
212         }
213     } else {
214         m_generatingAudioId.clear();
215     }
216 }
217
218 void ClipManager::setThumbsProgress(const QString &message, int progress)
219 {
220     m_doc->setThumbsProgress(message, progress);
221 }
222
223 QList <DocClipBase*> ClipManager::documentClipList() const
224 {
225     return m_clipList;
226 }
227
228 QMap <QString, QString> ClipManager::documentFolderList() const
229 {
230     return m_folderList;
231 }
232
233 void ClipManager::addClip(DocClipBase *clip)
234 {
235     m_clipList.append(clip);
236     if (clip->clipType() != COLOR && clip->clipType() != SLIDESHOW  && !clip->fileURL().isEmpty()) {
237         // listen for file change
238         //kDebug() << "// LISTEN FOR: " << clip->fileURL().path();
239         m_fileWatcher.addFile(clip->fileURL().path());
240     }
241     const QString id = clip->getId();
242     if (id.toInt() >= m_clipIdCounter) m_clipIdCounter = id.toInt() + 1;
243     const QString gid = clip->getProperty("groupid");
244     if (!gid.isEmpty() && gid.toInt() >= m_folderIdCounter) m_folderIdCounter = gid.toInt() + 1;
245 }
246
247 void ClipManager::slotDeleteClips(QStringList ids)
248 {
249     QUndoCommand *delClips = new QUndoCommand();
250     delClips->setText(i18np("Delete clip", "Delete clips", ids.size()));
251
252     for (int i = 0; i < ids.size(); i++) {
253         DocClipBase *clip = getClipById(ids.at(i));
254         if (clip) {
255             new AddClipCommand(m_doc, clip->toXML(), ids.at(i), false, delClips);
256         }
257     }
258     m_doc->commandStack()->push(delClips);
259 }
260
261 void ClipManager::deleteClip(const QString &clipId)
262 {
263     for (int i = 0; i < m_clipList.count(); i++) {
264         if (m_clipList.at(i)->getId() == clipId) {
265             if (m_clipList.at(i)->clipType() != COLOR && m_clipList.at(i)->clipType() != SLIDESHOW  && !m_clipList.at(i)->fileURL().isEmpty()) {
266                 //if (m_clipList.at(i)->clipType() == IMAGE || m_clipList.at(i)->clipType() == AUDIO || (m_clipList.at(i)->clipType() == TEXT && !m_clipList.at(i)->fileURL().isEmpty())) {
267                 // listen for file change
268                 m_fileWatcher.removeFile(m_clipList.at(i)->fileURL().path());
269             }
270             DocClipBase *clip = m_clipList.takeAt(i);
271             delete clip;
272             clip = NULL;
273             break;
274         }
275     }
276 }
277
278 DocClipBase *ClipManager::getClipAt(int pos)
279 {
280     return m_clipList.at(pos);
281 }
282
283 DocClipBase *ClipManager::getClipById(QString clipId)
284 {
285     //kDebug() << "++++  CLIP MAN, LOOKING FOR CLIP ID: " << clipId;
286     clipId = clipId.section('_', 0, 0);
287     for (int i = 0; i < m_clipList.count(); i++) {
288         if (m_clipList.at(i)->getId() == clipId) {
289             //kDebug() << "++++  CLIP MAN, FOUND FOR CLIP ID: " << clipId;
290             return m_clipList.at(i);
291         }
292     }
293     return NULL;
294 }
295
296 const QList <DocClipBase *> ClipManager::getClipByResource(QString resource)
297 {
298     QList <DocClipBase *> list;
299     QString clipResource;
300     QString proxyResource;
301     for (int i = 0; i < m_clipList.count(); i++) {
302         clipResource = m_clipList.at(i)->getProperty("resource");
303         proxyResource = m_clipList.at(i)->getProperty("proxy");
304         if (clipResource.isEmpty()) clipResource = m_clipList.at(i)->getProperty("colour");
305         if (clipResource == resource || proxyResource == resource) {
306             list.append(m_clipList.at(i));
307         }
308     }
309     return list;
310 }
311
312
313 void ClipManager::clearUnusedProducers()
314 {
315     for (int i = 0; i < m_clipList.count(); i++) {
316         if (m_clipList.at(i)->numReferences() == 0) m_clipList.at(i)->deleteProducers();
317     }
318 }
319
320 void ClipManager::resetProducersList(const QList <Mlt::Producer *> prods, bool displayRatioChanged, bool fpsChanged)
321 {
322     for (int i = 0; i < m_clipList.count(); i++) {
323         if (m_clipList.at(i)->numReferences() > 0 || displayRatioChanged || fpsChanged) {
324             m_clipList.at(i)->deleteProducers();
325         }
326     }
327     QString id;
328     Mlt::Producer *prod;
329     QStringList brokenClips;
330     for (int i = 0; i < prods.count(); i++) {
331         prod = prods.at(i);
332         id = prod->get("id");
333         if (id.contains('_')) id = id.section('_', 0, 0);
334         DocClipBase *clip = getClipById(id);
335         QString markup = prod->get("markup");
336         if (prod->is_blank() || !prod->is_valid() || !markup.isEmpty()) {
337             // The clip is broken (missing proxy or source clip)
338             kDebug()<<"// WARNING, CLIP "<<id<<" Cannot be loaded";
339             brokenClips << id;
340         }
341         else if (clip) {
342             clip->setProducer(prod, false, true);
343         }
344     }
345     emit checkAllClips(displayRatioChanged, fpsChanged, brokenClips);
346 }
347
348 void ClipManager::slotAddClipList(const KUrl::List urls, const QString &group, const QString &groupId)
349 {
350     QUndoCommand *addClips = new QUndoCommand();
351
352     foreach(const KUrl & file, urls) {
353         if (KIO::NetAccess::exists(file, KIO::NetAccess::SourceSide, NULL)) {
354             if (!getClipByResource(file.path()).empty()) {
355                 if (KMessageBox::warningContinueCancel(kapp->activeWindow(), i18n("Clip <b>%1</b><br />already exists in project, what do you want to do?", file.path()), i18n("Clip already exists")) == KMessageBox::Cancel)
356                     continue;
357             }
358             kDebug() << "Adding clip: " << file.path();
359             QDomDocument doc;
360             QDomElement prod = doc.createElement("producer");
361             doc.appendChild(prod);
362             prod.setAttribute("resource", file.path());
363             uint id = m_clipIdCounter++;
364             prod.setAttribute("id", QString::number(id));
365             if (!group.isEmpty()) {
366                 prod.setAttribute("groupname", group);
367                 prod.setAttribute("groupid", groupId);
368             }
369             KMimeType::Ptr type = KMimeType::findByUrl(file);
370             if (type->name().startsWith("image/")) {
371                 prod.setAttribute("type", (int) IMAGE);
372                 prod.setAttribute("in", 0);
373                 prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::image_duration()));
374                 if (KdenliveSettings::autoimagetransparency()) prod.setAttribute("transparency", 1);
375                 // Read EXIF metadata for JPEG
376                 if (type->is("image/jpeg")) {
377                     KFileMetaInfo metaInfo(file.path(), QString("image/jpeg"), KFileMetaInfo::TechnicalInfo);
378                     const QHash<QString, KFileMetaInfoItem> metaInfoItems = metaInfo.items();
379                     foreach(const KFileMetaInfoItem & metaInfoItem, metaInfoItems) {
380                         prod.setAttribute("meta.attr." + metaInfoItem.name().section("#", 1), metaInfoItem.value().toString());
381                     }
382                 }
383             } else if (type->is("application/x-kdenlivetitle")) {
384                 // opening a title file
385                 QDomDocument txtdoc("titledocument");
386                 QFile txtfile(file.path());
387                 if (txtfile.open(QIODevice::ReadOnly) && txtdoc.setContent(&txtfile)) {
388                     txtfile.close();
389                     prod.setAttribute("type", (int) TEXT);
390                     // extract embeded images
391                     QDomNodeList items = txtdoc.elementsByTagName("content");
392                     for (int i = 0; i < items.count() ; i++) {
393                         QDomElement content = items.item(i).toElement();
394                         if (content.hasAttribute("base64")) {
395                             QString titlesFolder = m_doc->projectFolder().path(KUrl::AddTrailingSlash) + "titles/";
396                             QString path = TitleDocument::extractBase64Image(titlesFolder, content.attribute("base64"));
397                             if (!path.isEmpty()) {
398                                 content.setAttribute("url", path);
399                                 content.removeAttribute("base64");
400                             }
401                         }
402                     }
403                     QString titleData = txtdoc.toString();
404                     prod.setAttribute("xmldata", titleData);
405                     prod.setAttribute("transparency", 1);
406                     prod.setAttribute("in", 0);
407                     int out = txtdoc.documentElement().attribute("out").toInt();
408                     if (out > 0)
409                         prod.setAttribute("out", out);
410                     else
411                         prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::title_duration()));
412                 } else
413                     txtfile.close();
414             }
415             new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true, addClips);
416         }
417     }
418     if (addClips->childCount() > 0) {
419         addClips->setText(i18np("Add clip", "Add clips", addClips->childCount()));
420         m_doc->commandStack()->push(addClips);
421     }
422 }
423
424 void ClipManager::slotAddClipFile(const KUrl &url, const QString &group, const QString &groupId)
425 {
426     slotAddClipList(KUrl::List(url), group, groupId);
427 }
428
429 void ClipManager::slotAddXmlClipFile(const QString &name, const QDomElement &xml, const QString &group, const QString &groupId)
430 {
431     QDomDocument doc;
432     doc.appendChild(doc.importNode(xml, true));
433     QDomElement prod = doc.documentElement();
434     prod.setAttribute("type", (int) PLAYLIST);
435     uint id = m_clipIdCounter++;
436     prod.setAttribute("id", QString::number(id));
437     prod.setAttribute("name", name);
438     if (!group.isEmpty()) {
439         prod.setAttribute("groupname", group);
440         prod.setAttribute("groupid", groupId);
441     }
442     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
443     m_doc->commandStack()->push(command);
444 }
445
446 void ClipManager::slotAddColorClipFile(const QString &name, const QString &color, QString duration, const QString &group, const QString &groupId)
447 {
448     QDomDocument doc;
449     QDomElement prod = doc.createElement("producer");
450     doc.appendChild(prod);
451     prod.setAttribute("mlt_service", "colour");
452     prod.setAttribute("colour", color);
453     prod.setAttribute("type", (int) COLOR);
454     uint id = m_clipIdCounter++;
455     prod.setAttribute("id", QString::number(id));
456     prod.setAttribute("in", "0");
457     prod.setAttribute("out", m_doc->getFramePos(duration));
458     prod.setAttribute("name", name);
459     if (!group.isEmpty()) {
460         prod.setAttribute("groupname", group);
461         prod.setAttribute("groupid", groupId);
462     }
463     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
464     m_doc->commandStack()->push(command);
465 }
466
467 void ClipManager::slotAddSlideshowClipFile(const QString &name, const QString &path, int count, const QString &duration,
468         const bool loop, const bool crop, const bool fade,
469         const QString &luma_duration, const QString &luma_file, const int softness,
470         const QString &animation, const QString &group, const QString &groupId)
471 {
472     QDomDocument doc;
473     QDomElement prod = doc.createElement("producer");
474     doc.appendChild(prod);
475     prod.setAttribute("resource", path);
476     prod.setAttribute("type", (int) SLIDESHOW);
477     uint id = m_clipIdCounter++;
478     prod.setAttribute("id", QString::number(id));
479     prod.setAttribute("in", "0");
480     prod.setAttribute("out", m_doc->getFramePos(duration) * count);
481     prod.setAttribute("ttl", m_doc->getFramePos(duration));
482     prod.setAttribute("luma_duration", m_doc->getFramePos(luma_duration));
483     prod.setAttribute("name", name);
484     prod.setAttribute("loop", loop);
485     prod.setAttribute("crop", crop);
486     prod.setAttribute("fade", fade);
487     prod.setAttribute("softness", QString::number(softness));
488     prod.setAttribute("luma_file", luma_file);
489     prod.setAttribute("animation", animation);
490     if (!group.isEmpty()) {
491         prod.setAttribute("groupname", group);
492         prod.setAttribute("groupid", groupId);
493     }
494     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
495     m_doc->commandStack()->push(command);
496 }
497
498
499
500 void ClipManager::slotAddTextClipFile(const QString &titleName, int out, const QString &xml, const QString &group, const QString &groupId)
501 {
502     QDomDocument doc;
503     QDomElement prod = doc.createElement("producer");
504     doc.appendChild(prod);
505     //prod.setAttribute("resource", imagePath);
506     prod.setAttribute("name", titleName);
507     prod.setAttribute("xmldata", xml);
508     uint id = m_clipIdCounter++;
509     prod.setAttribute("id", QString::number(id));
510     if (!group.isEmpty()) {
511         prod.setAttribute("groupname", group);
512         prod.setAttribute("groupid", groupId);
513     }
514     prod.setAttribute("type", (int) TEXT);
515     prod.setAttribute("transparency", "1");
516     prod.setAttribute("in", "0");
517     prod.setAttribute("out", out);
518     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
519     m_doc->commandStack()->push(command);
520 }
521
522 void ClipManager::slotAddTextTemplateClip(QString titleName, const KUrl &path, const QString &group, const QString &groupId)
523 {
524     QDomDocument doc;
525     QDomElement prod = doc.createElement("producer");
526     doc.appendChild(prod);
527     prod.setAttribute("name", titleName);
528     prod.setAttribute("resource", path.path());
529     uint id = m_clipIdCounter++;
530     prod.setAttribute("id", QString::number(id));
531     if (!group.isEmpty()) {
532         prod.setAttribute("groupname", group);
533         prod.setAttribute("groupid", groupId);
534     }
535     prod.setAttribute("type", (int) TEXT);
536     prod.setAttribute("transparency", "1");
537     prod.setAttribute("in", "0");
538
539     int out = 0;
540     QDomDocument titledoc;
541     QFile txtfile(path.path());
542     if (txtfile.open(QIODevice::ReadOnly) && titledoc.setContent(&txtfile)) {
543         txtfile.close();
544         out = titledoc.documentElement().attribute("out").toInt();
545     } else txtfile.close();
546
547     if (out == 0) out = m_doc->getFramePos(KdenliveSettings::image_duration());
548     prod.setAttribute("out", out);
549
550     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
551     m_doc->commandStack()->push(command);
552 }
553
554 int ClipManager::getFreeClipId()
555 {
556     return m_clipIdCounter++;
557 }
558
559 int ClipManager::getFreeFolderId()
560 {
561     return m_folderIdCounter++;
562 }
563
564 int ClipManager::lastClipId() const
565 {
566     return m_clipIdCounter - 1;
567 }
568
569 QString ClipManager::projectFolder() const
570 {
571     return m_doc->projectFolder().path();
572 }
573
574 void ClipManager::addFolder(const QString &id, const QString &name)
575 {
576     m_folderList.insert(id, name);
577 }
578
579 void ClipManager::deleteFolder(const QString &id)
580 {
581     m_folderList.remove(id);
582 }
583
584 AbstractGroupItem *ClipManager::createGroup()
585 {
586     AbstractGroupItem *group = new AbstractGroupItem(m_doc->fps());
587     m_groupsList.append(group);
588     return group;
589 }
590
591 void ClipManager::removeGroup(AbstractGroupItem *group)
592 {
593     m_groupsList.removeAll(group);
594 }
595
596 QDomElement ClipManager::groupsXml() const
597 {
598     QDomDocument doc;
599     QDomElement groups = doc.createElement("groups");
600     doc.appendChild(groups);
601     for (int i = 0; i < m_groupsList.count(); i++) {
602         QDomElement group = doc.createElement("group");
603         groups.appendChild(group);
604         QList <QGraphicsItem *> children = m_groupsList.at(i)->childItems();
605         for (int j = 0; j < children.count(); j++) {
606             if (children.at(j)->type() == AVWIDGET || children.at(j)->type() == TRANSITIONWIDGET) {
607                 AbstractClipItem *item = static_cast <AbstractClipItem *>(children.at(j));
608                 ItemInfo info = item->info();
609                 if (item->type() == AVWIDGET) {
610                     QDomElement clip = doc.createElement("clipitem");
611                     clip.setAttribute("track", info.track);
612                     clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
613                     group.appendChild(clip);
614                 } else if (item->type() == TRANSITIONWIDGET) {
615                     QDomElement clip = doc.createElement("transitionitem");
616                     clip.setAttribute("track", info.track);
617                     clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
618                     group.appendChild(clip);
619                 }
620             }
621         }
622     }
623     return doc.documentElement();
624 }
625
626
627 void ClipManager::slotClipModified(const QString &path)
628 {
629     //kDebug() << "// CLIP: " << path << " WAS MODIFIED";
630     const QList <DocClipBase *> list = getClipByResource(path);
631     for (int i = 0; i < list.count(); i++) {
632         DocClipBase *clip = list.at(i);
633         if (clip != NULL) {
634             QString id = clip->getId();
635             if (!m_modifiedClips.contains(id))
636                 emit modifiedClip(id);
637             m_modifiedClips[id] = QTime::currentTime();
638         }
639     }
640     if (!m_modifiedTimer.isActive()) m_modifiedTimer.start();
641 }
642
643 void ClipManager::slotProcessModifiedClips()
644 {
645     if (!m_modifiedClips.isEmpty()) {
646         QMapIterator<QString, QTime> i(m_modifiedClips);
647         while (i.hasNext()) {
648             i.next();
649             if (QTime::currentTime().msecsTo(i.value()) <= -1500) {
650                 emit reloadClip(i.key());
651                 m_modifiedClips.remove(i.key());
652                 break;
653             }
654         }
655     }
656     if (m_modifiedClips.isEmpty()) m_modifiedTimer.stop();
657 }
658
659 void ClipManager::slotClipMissing(const QString &path)
660 {
661     // kDebug() << "// CLIP: " << path << " WAS MISSING";
662     const QList <DocClipBase *> list = getClipByResource(path);
663     for (int i = 0; i < list.count(); i++) {
664         DocClipBase *clip = list.at(i);
665         if (clip != NULL) emit missingClip(clip->getId());
666     }
667 }
668
669 void ClipManager::slotClipAvailable(const QString &path)
670 {
671     // kDebug() << "// CLIP: " << path << " WAS ADDED";
672     const QList <DocClipBase *> list = getClipByResource(path);
673     for (int i = 0; i < list.count(); i++) {
674         DocClipBase *clip = list.at(i);
675         if (clip != NULL) emit availableClip(clip->getId());
676     }
677 }
678
679 int ClipManager::clipsCount() const
680 {
681     return m_clipList.count();
682 }
683
684