]> git.sesse.net Git - kdenlive/blob - src/documentchecker.cpp
a43378e7c6f135bad001c87956e7f10dc5a7676c
[kdenlive] / src / documentchecker.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 "documentchecker.h"
22 #include "kthumb.h"
23 #include "docclipbase.h"
24 #include "titlewidget.h"
25 #include "definitions.h"
26 #include "kdenlivesettings.h"
27
28 #include <KDebug>
29 #include <KGlobalSettings>
30 #include <KFileItem>
31 #include <KIO/NetAccess>
32 #include <KFileDialog>
33 #include <KApplication>
34 #include <KUrlRequesterDialog>
35 #include <KMessageBox>
36
37 #include <QTreeWidgetItem>
38 #include <QFile>
39 #include <QHeaderView>
40 #include <QIcon>
41 #include <QPixmap>
42 #include <QTimer>
43 #include <QCryptographicHash>
44
45 const int hashRole = Qt::UserRole;
46 const int sizeRole = Qt::UserRole + 1;
47 const int idRole = Qt::UserRole + 2;
48 const int statusRole = Qt::UserRole + 3;
49 const int typeRole = Qt::UserRole + 4;
50 const int typeOriginalResource = Qt::UserRole + 5;
51
52 const int CLIPMISSING = 0;
53 const int CLIPOK = 1;
54 const int CLIPPLACEHOLDER = 2;
55 const int CLIPWRONGDURATION = 3;
56
57 const int LUMAMISSING = 10;
58 const int LUMAOK = 11;
59 const int LUMAPLACEHOLDER = 12;
60
61 enum TITLECLIPTYPE { TITLE_IMAGE_ELEMENT = 20, TITLE_FONT_ELEMENT = 21 };
62
63 DocumentChecker::DocumentChecker(QDomNodeList infoproducers, QDomDocument doc):
64     m_info(infoproducers), m_doc(doc), m_dialog(NULL)
65 {
66
67 }
68
69
70 bool DocumentChecker::hasErrorInClips()
71 {
72     int clipType;
73     QDomElement e;
74     QString resource;
75     QDomNodeList documentProducers = m_doc.elementsByTagName("producer");
76     QList <QDomElement> wrongDurationClips;
77     QList <QDomElement> missingClips;
78     for (int i = 0; i < m_info.count(); i++) {
79         e = m_info.item(i).toElement();
80         clipType = e.attribute("type").toInt();
81         if (clipType == COLOR) continue;
82         if (clipType != TEXT && clipType != IMAGE) {
83             QString id = e.attribute("id");
84             int duration = e.attribute("duration").toInt();
85             // Check that the duration is in sync between Kdenlive's info and MLT's playlist
86             for (int j = 0; j < documentProducers.count(); j++) {
87                 QDomElement mltProd = documentProducers.at(j).toElement();
88                 QString prodId = mltProd.attribute("id");
89                 // Don't check slowmotion clips for now... (TODO?)
90                 if (prodId.startsWith("slowmotion")) continue;
91                 if (prodId.contains("_")) prodId = prodId.section("_", 0, 0);
92                 if (prodId != id) continue;
93                 int mltDuration = mltProd.attribute("out").toInt() + 1;
94                 if (mltDuration != duration && !e.hasAttribute("_mismatch")) {
95                     // Duration mismatch
96                     e.setAttribute("_mismatch", mltDuration);
97                     if (!wrongDurationClips.contains(e)) wrongDurationClips.append(e);
98                     kDebug() << "WRONG DURTAION: "<<id<<", TYPE: "<<clipType;
99                 }
100             }
101         }
102         
103         if (clipType == TEXT) {
104             //TODO: Check is clip template is missing (xmltemplate) or hash changed
105             QStringList images = TitleWidget::extractImageList(e.attribute("xmldata"));
106             QStringList fonts = TitleWidget::extractFontList(e.attribute("xmldata"));
107             checkMissingImages(missingClips, images, fonts, e.attribute("id"), e.attribute("name"));
108             continue;
109         }
110         resource = e.attribute("resource");
111         if (clipType == SLIDESHOW) resource = KUrl(resource).directory();
112         if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) {
113             // Missing clip found
114             missingClips.append(e);
115         } else {
116             // Check if the clip has changed
117             if (clipType != SLIDESHOW && e.hasAttribute("file_hash")) {
118                 if (e.attribute("file_hash") != DocClipBase::getHash(e.attribute("resource")))
119                     e.removeAttribute("file_hash");
120             }
121         }
122     }
123
124     QStringList missingLumas;
125     QString root = m_doc.documentElement().attribute("root");
126     if (!root.isEmpty()) root = KUrl(root).path(KUrl::AddTrailingSlash);
127     QDomNodeList trans = m_doc.elementsByTagName("transition");
128     for (int i = 0; i < trans.count(); i++) {
129         QString luma = getProperty(trans.at(i).toElement(), "luma");
130         if (!luma.isEmpty()) {
131             if (!luma.startsWith('/')) luma.prepend(root);
132             if (!QFile::exists(luma) && !missingLumas.contains(luma)) {
133                 missingLumas.append(luma);
134             }
135         }
136     }
137
138     if (missingClips.isEmpty() && missingLumas.isEmpty() && wrongDurationClips.isEmpty())
139         return false;
140
141     m_dialog = new QDialog();
142     m_dialog->setFont(KGlobalSettings::toolBarFont());
143     m_ui.setupUi(m_dialog);
144
145     foreach(const QString l, missingLumas) {
146         QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Luma file") << l);
147         item->setIcon(0, KIcon("dialog-close"));
148         item->setData(0, idRole, l);
149         item->setData(0, statusRole, LUMAMISSING);
150     }
151
152     m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
153     for (int i = 0; i < missingClips.count(); i++) {
154         e = missingClips.at(i).toElement();
155         QString clipType;
156         int t = e.attribute("type").toInt();
157         switch (t) {
158         case AV:
159             clipType = i18n("Video clip");
160             break;
161         case VIDEO:
162             clipType = i18n("Mute video clip");
163             break;
164         case AUDIO:
165             clipType = i18n("Audio clip");
166             break;
167         case PLAYLIST:
168             clipType = i18n("Playlist clip");
169             break;
170         case IMAGE:
171             clipType = i18n("Image clip");
172             break;
173         case SLIDESHOW:
174             clipType = i18n("Slideshow clip");
175             break;
176         case TITLE_IMAGE_ELEMENT:
177             clipType = i18n("Title Image");
178             break;
179         case TITLE_FONT_ELEMENT:
180             clipType = i18n("Title Font");
181             break;
182         default:
183             clipType = i18n("Video clip");
184         }
185         QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
186         if (t == TITLE_IMAGE_ELEMENT) {
187             item->setIcon(0, KIcon("dialog-warning"));
188             item->setToolTip(1, e.attribute("name"));
189             item->setText(1, e.attribute("resource"));
190             item->setData(0, statusRole, CLIPPLACEHOLDER);
191             item->setData(0, typeOriginalResource, e.attribute("resource"));
192         } else if (t == TITLE_FONT_ELEMENT) {
193             item->setIcon(0, KIcon("dialog-warning"));
194             item->setToolTip(1, e.attribute("name"));
195             QString ft = e.attribute("resource");
196             QString newft = QFontInfo(QFont(ft)).family();
197             item->setText(1, i18n("%1 will be replaced by %2", ft, newft));
198             item->setData(0, statusRole, CLIPPLACEHOLDER);
199         } else {
200             item->setIcon(0, KIcon("dialog-close"));
201             item->setText(1, e.attribute("resource"));
202             item->setData(0, hashRole, e.attribute("file_hash"));
203             item->setData(0, sizeRole, e.attribute("file_size"));
204             item->setData(0, statusRole, CLIPMISSING);
205         }
206         item->setData(0, typeRole, t);
207         item->setData(0, idRole, e.attribute("id"));
208         item->setToolTip(0, i18n("Missing item"));
209     }
210
211     if (missingClips.count() > 0) {
212         if (wrongDurationClips.count() > 0) {
213             m_ui.infoLabel->setText(i18n("The project file contains missing clips or files and clip duration mismatch"));
214         }
215         else {
216             m_ui.infoLabel->setText(i18n("The project file contains missing clips or files"));
217         }
218     }
219     else if (wrongDurationClips.count() > 0) {
220         m_ui.infoLabel->setText(i18n("The project file contains clips with duration mismatch"));
221     }
222     m_ui.removeSelected->setEnabled(!missingClips.isEmpty());
223     m_ui.recursiveSearch->setEnabled(!missingClips.isEmpty());
224     m_ui.usePlaceholders->setEnabled(!missingClips.isEmpty());
225     m_ui.fixDuration->setEnabled(!wrongDurationClips.isEmpty());
226         
227     for (int i = 0; i < wrongDurationClips.count(); i++) {
228         e = wrongDurationClips.at(i).toElement();
229         QString clipType;
230         int t = e.attribute("type").toInt();
231         switch (t) {
232         case AV:
233             clipType = i18n("Video clip");
234             break;
235         case VIDEO:
236             clipType = i18n("Mute video clip");
237             break;
238         case AUDIO:
239             clipType = i18n("Audio clip");
240             break;
241         case PLAYLIST:
242             clipType = i18n("Playlist clip");
243             break;
244         case IMAGE:
245             clipType = i18n("Image clip");
246             break;
247         case SLIDESHOW:
248             clipType = i18n("Slideshow clip");
249             break;
250         default:
251             clipType = i18n("Video clip");
252         }
253         QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
254         item->setIcon(0, KIcon("timeadjust"));
255         item->setText(1, e.attribute("resource"));
256         item->setData(0, hashRole, e.attribute("file_hash"));
257         item->setData(0, sizeRole, e.attribute("_mismatch"));
258         e.removeAttribute("_mismatch");
259         item->setData(0, statusRole, CLIPWRONGDURATION);
260         item->setData(0, typeRole, t);
261         item->setData(0, idRole, e.attribute("id"));
262         item->setToolTip(0, i18n("Duration mismatch"));
263     }
264     
265     connect(m_ui.recursiveSearch, SIGNAL(pressed()), this, SLOT(slotSearchClips()));
266     connect(m_ui.usePlaceholders, SIGNAL(pressed()), this, SLOT(slotPlaceholders()));
267     connect(m_ui.removeSelected, SIGNAL(pressed()), this, SLOT(slotDeleteSelected()));
268     connect(m_ui.fixDuration, SIGNAL(pressed()), this, SLOT(slotFixDuration()));
269     connect(m_ui.treeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotEditItem(QTreeWidgetItem *, int)));
270     connect(m_ui.treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckButtons()));
271     //adjustSize();
272     if (m_ui.treeWidget->topLevelItem(0)) m_ui.treeWidget->setCurrentItem(m_ui.treeWidget->topLevelItem(0));
273     checkStatus();
274     int acceptMissing = m_dialog->exec();
275     if (acceptMissing == QDialog::Accepted) acceptDialog();
276     return (acceptMissing != QDialog::Accepted);
277 }
278
279 DocumentChecker::~DocumentChecker()
280 {
281     if (m_dialog) delete m_dialog;
282 }
283
284
285 QString DocumentChecker::getProperty(QDomElement effect, const QString &name)
286 {
287     QDomNodeList params = effect.elementsByTagName("property");
288     for (int i = 0; i < params.count(); i++) {
289         QDomElement e = params.item(i).toElement();
290         if (e.attribute("name") == name) {
291             return e.firstChild().nodeValue();
292         }
293     }
294     return QString();
295 }
296
297 void DocumentChecker::setProperty(QDomElement effect, const QString &name, const QString value)
298 {
299     QDomNodeList params = effect.elementsByTagName("property");
300     for (int i = 0; i < params.count(); i++) {
301         QDomElement e = params.item(i).toElement();
302         if (e.attribute("name") == name) {
303             e.firstChild().setNodeValue(value);
304         }
305     }
306 }
307
308 void DocumentChecker::slotSearchClips()
309 {
310     QString newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Clips folder"));
311     if (newpath.isEmpty()) return;
312     int ix = 0;
313     bool fixed = false;
314     m_ui.recursiveSearch->setEnabled(false);
315     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
316     while (child) {
317         if (child->data(0, statusRole).toInt() == CLIPMISSING) {
318             QString clipPath = searchFileRecursively(QDir(newpath), child->data(0, sizeRole).toString(), child->data(0, hashRole).toString());
319             if (!clipPath.isEmpty()) {
320                 fixed = true;
321                 child->setText(1, clipPath);
322                 child->setIcon(0, KIcon("dialog-ok"));
323                 child->setData(0, statusRole, CLIPOK);
324             }
325         } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
326             QString fileName = searchLuma(child->data(0, idRole).toString());
327             if (!fileName.isEmpty()) {
328                 fixed = true;
329                 child->setText(1, fileName);
330                 child->setIcon(0, KIcon("dialog-ok"));
331                 child->setData(0, statusRole, LUMAOK);
332             }
333         }
334         ix++;
335         child = m_ui.treeWidget->topLevelItem(ix);
336     }
337     m_ui.recursiveSearch->setEnabled(true);
338     if (fixed) {
339         // original doc was modified
340         QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
341         QDomElement infoXml = infoXmlNode.toElement();
342         infoXml.setAttribute("modified", "1");
343     }
344     checkStatus();
345 }
346
347
348 QString DocumentChecker::searchLuma(QString file) const
349 {
350     KUrl searchPath(KdenliveSettings::mltpath());
351     if (file.contains("PAL"))
352         searchPath.cd("../lumas/PAL");
353     else
354         searchPath.cd("../lumas/NTSC");
355     QString result = searchPath.path(KUrl::AddTrailingSlash) + KUrl(file).fileName();
356     if (QFile::exists(result))
357         return result;
358     return QString();
359 }
360
361
362 QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const
363 {
364     QString foundFileName;
365     QByteArray fileData;
366     QByteArray fileHash;
367     QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
368     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
369         QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
370         if (QString::number(file.size()) == matchSize) {
371             if (file.open(QIODevice::ReadOnly)) {
372                 /*
373                 * 1 MB = 1 second per 450 files (or faster)
374                 * 10 MB = 9 seconds per 450 files (or faster)
375                 */
376                 if (file.size() > 1000000 * 2) {
377                     fileData = file.read(1000000);
378                     if (file.seek(file.size() - 1000000))
379                         fileData.append(file.readAll());
380                 } else
381                     fileData = file.readAll();
382                 file.close();
383                 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
384                 if (QString(fileHash.toHex()) == matchHash)
385                     return file.fileName();
386             }
387         }
388         //kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
389     }
390     filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
391     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
392         foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
393         if (!foundFileName.isEmpty())
394             break;
395     }
396     return foundFileName;
397 }
398
399 void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int)
400 {
401     int t = item->data(0, typeRole).toInt();
402     if (t == TITLE_FONT_ELEMENT) return;
403     //|| t == TITLE_IMAGE_ELEMENT) {
404
405     KUrl url = KUrlRequesterDialog::getUrl(item->text(1), m_dialog, i18n("Enter new location for file"));
406     if (url.isEmpty()) return;
407     item->setText(1, url.path());
408     if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
409         item->setIcon(0, KIcon("dialog-ok"));
410         int id = item->data(0, statusRole).toInt();
411         if (id < 10) item->setData(0, statusRole, CLIPOK);
412         else item->setData(0, statusRole, LUMAOK);
413         checkStatus();
414     } else {
415         item->setIcon(0, KIcon("dialog-close"));
416         int id = item->data(0, statusRole).toInt();
417         if (id < 10) item->setData(0, statusRole, CLIPMISSING);
418         else item->setData(0, statusRole, LUMAMISSING);
419         checkStatus();
420     }
421 }
422
423
424 void DocumentChecker::acceptDialog()
425 {
426     QDomElement e, property;
427     QDomNodeList producers = m_doc.elementsByTagName("producer");
428     QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
429     QDomNodeList properties;
430     int ix = 0;
431
432     // prepare transitions
433     QDomNodeList trans = m_doc.elementsByTagName("transition");
434
435     // Mark document as modified
436     m_doc.documentElement().setAttribute("modified", 1);
437
438     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
439     while (child) {
440         int t = child->data(0, typeRole).toInt();
441         if (child->data(0, statusRole).toInt() == CLIPOK) {
442             QString id = child->data(0, idRole).toString();
443             if (t == TITLE_IMAGE_ELEMENT) {
444                 // edit images embedded in titles
445                 for (int i = 0; i < infoproducers.count(); i++) {
446                     e = infoproducers.item(i).toElement();
447                     if (e.attribute("id") == id) {
448                         // Fix clip
449                         QString xml = e.attribute("xmldata");
450                         xml.replace(child->data(0, typeOriginalResource).toString(), child->text(1));
451                         e.setAttribute("xmldata", xml);
452                         break;
453                     }
454                 }
455                 for (int i = 0; i < producers.count(); i++) {
456                     e = producers.item(i).toElement();
457                     if (e.attribute("id").section('_', 0, 0) == id) {
458                         // Fix clip
459                         properties = e.childNodes();
460                         for (int j = 0; j < properties.count(); ++j) {
461                             property = properties.item(j).toElement();
462                             if (property.attribute("name") == "xmldata") {
463                                 QString xml = property.firstChild().nodeValue();
464                                 xml.replace(child->data(0, typeOriginalResource).toString(), child->text(1));
465                                 property.firstChild().setNodeValue(xml);
466                                 break;
467                             }
468                         }
469                     }
470                 }
471             } else {
472                 // edit clip url
473                 for (int i = 0; i < infoproducers.count(); i++) {
474                     e = infoproducers.item(i).toElement();
475                     if (e.attribute("id") == id) {
476                         // Fix clip
477                         e.setAttribute("resource", child->text(1));
478                         e.setAttribute("name", KUrl(child->text(1)).fileName());
479                         break;
480                     }
481                 }
482                 for (int i = 0; i < producers.count(); i++) {
483                     e = producers.item(i).toElement();
484                     if (e.attribute("id").section('_', 0, 0) == id || e.attribute("id").section(':', 1, 1) == id) {
485                         // Fix clip
486                         properties = e.childNodes();
487                         for (int j = 0; j < properties.count(); ++j) {
488                             property = properties.item(j).toElement();
489                             if (property.attribute("name") == "resource") {
490                                 QString resource = property.firstChild().nodeValue();
491                                 if (resource.contains(QRegExp("\\?[0-9]+\\.[0-9]+(&amp;strobe=[0-9]+)?$")))
492                                     property.firstChild().setNodeValue(child->text(1) + '?' + resource.section('?', -1));
493                                 else
494                                     property.firstChild().setNodeValue(child->text(1));
495                                 break;
496                             }
497                         }
498                     }
499                 }
500             }
501         } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER && t != TITLE_FONT_ELEMENT && t != TITLE_IMAGE_ELEMENT) {
502             QString id = child->data(0, idRole).toString();
503             for (int i = 0; i < infoproducers.count(); i++) {
504                 e = infoproducers.item(i).toElement();
505                 if (e.attribute("id") == id) {
506                     // Fix clip
507                     e.setAttribute("placeholder", '1');
508                     break;
509                 }
510             }
511         } else if (child->data(0, statusRole).toInt() == LUMAOK) {
512             for (int i = 0; i < trans.count(); i++) {
513                 QString luma = getProperty(trans.at(i).toElement(), "luma");
514                 kDebug() << "luma: " << luma;
515                 if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) {
516                     setProperty(trans.at(i).toElement(), "luma", child->text(1));
517                     kDebug() << "replace with; " << child->text(1);
518                 }
519             }
520         } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
521             for (int i = 0; i < trans.count(); i++) {
522                 QString luma = getProperty(trans.at(i).toElement(), "luma");
523                 if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) {
524                     setProperty(trans.at(i).toElement(), "luma", QString());
525                 }
526             }
527         }
528         ix++;
529         child = m_ui.treeWidget->topLevelItem(ix);
530     }
531     //QDialog::accept();
532 }
533
534 void DocumentChecker::slotPlaceholders()
535 {
536     int ix = 0;
537     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
538     while (child) {
539         if (child->data(0, statusRole).toInt() == CLIPMISSING) {
540             child->setData(0, statusRole, CLIPPLACEHOLDER);
541             child->setIcon(0, KIcon("dialog-ok"));
542         } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
543             child->setData(0, statusRole, LUMAPLACEHOLDER);
544             child->setIcon(0, KIcon("dialog-ok"));
545         }
546         ix++;
547         child = m_ui.treeWidget->topLevelItem(ix);
548     }
549     checkStatus();
550 }
551
552 void DocumentChecker::slotFixDuration()
553 {
554     int ix = 0;
555     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
556     while (child) {
557         if (child->data(0, statusRole).toInt() == CLIPWRONGDURATION) {
558             child->setData(0, statusRole, CLIPOK);
559             child->setIcon(0, KIcon("dialog-ok"));
560             QString id = child->data(0, idRole).toString();
561             for (int i = 0; i < m_info.count(); i++) {
562                 QDomElement e = m_info.at(i).toElement();
563                 if (e.attribute("id") == id) {
564                     e.setAttribute("duration", child->data(0, sizeRole).toString());
565                     break;
566                 }
567             }
568         }
569         ix++;
570         child = m_ui.treeWidget->topLevelItem(ix);
571     }
572     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
573     QDomElement infoXml = infoXmlNode.toElement();
574     infoXml.setAttribute("modified", "1");
575     m_ui.fixDuration->setEnabled(false);
576     checkStatus();
577 }
578
579
580 void DocumentChecker::checkStatus()
581 {
582     bool status = true;
583     int ix = 0;
584     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
585     while (child) {
586         int status = child->data(0, statusRole).toInt();
587         if (status == CLIPMISSING || status == LUMAMISSING || status == CLIPWRONGDURATION) {
588             status = false;
589             break;
590         }
591         ix++;
592         child = m_ui.treeWidget->topLevelItem(ix);
593     }
594     m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
595 }
596
597
598 void DocumentChecker::slotDeleteSelected()
599 {
600     if (KMessageBox::warningContinueCancel(m_dialog, i18np("This will remove the selected clip from this project", "This will remove the selected clips from this project", m_ui.treeWidget->selectedItems().count()), i18n("Remove clips")) == KMessageBox::Cancel)
601         return;
602     QStringList deletedIds;
603     QDomNodeList playlists = m_doc.elementsByTagName("playlist");
604
605     foreach(QTreeWidgetItem *child, m_ui.treeWidget->selectedItems()) {
606         if (child->data(0, statusRole).toInt() < 10) {
607             deletedIds.append(child->data(0, idRole).toString());
608             delete child;
609         }
610     }
611     kDebug() << "// Clips to delete: " << deletedIds;
612
613     if (!deletedIds.isEmpty()) {
614         QDomElement e;
615         QDomNodeList producers = m_doc.elementsByTagName("producer");
616         QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
617
618         QDomElement mlt = m_doc.firstChildElement("mlt");
619         QDomElement kdenlivedoc = mlt.firstChildElement("kdenlivedoc");
620
621         for (int i = 0, j = 0; i < infoproducers.count() && j < deletedIds.count(); i++) {
622             e = infoproducers.item(i).toElement();
623             if (deletedIds.contains(e.attribute("id"))) {
624                 // Remove clip
625                 kdenlivedoc.removeChild(e);
626                 i--;
627                 j++;
628             }
629         }
630
631         for (int i = 0; i < producers.count(); i++) {
632             e = producers.item(i).toElement();
633             if (deletedIds.contains(e.attribute("id").section('_', 0, 0)) || deletedIds.contains(e.attribute("id").section(':', 1, 1).section('_', 0, 0))) {
634                 // Remove clip
635                 mlt.removeChild(e);
636                 i--;
637             }
638         }
639
640         for (int i = 0; i < playlists.count(); i++) {
641             QDomNodeList entries = playlists.at(i).toElement().elementsByTagName("entry");
642             for (int j = 0; j < entries.count(); j++) {
643                 e = entries.item(j).toElement();
644                 if (deletedIds.contains(e.attribute("producer").section('_', 0, 0)) || deletedIds.contains(e.attribute("producer").section(':', 1, 1).section('_', 0, 0))) {
645                     // Replace clip with blank
646                     while (e.childNodes().count() > 0)
647                         e.removeChild(e.firstChild());
648                     e.setTagName("blank");
649                     e.removeAttribute("producer");
650                     int length = e.attribute("out").toInt() - e.attribute("in").toInt();
651                     e.setAttribute("length", length);
652                     j--;
653                 }
654             }
655         }
656         QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
657         QDomElement infoXml = infoXmlNode.toElement();
658         infoXml.setAttribute("modified", "1");
659         checkStatus();
660     }
661 }
662
663 void DocumentChecker::checkMissingImages(QList <QDomElement>&missingClips, QStringList images, QStringList fonts, QString id, QString baseClip)
664 {
665     QDomDocument doc;
666     foreach(const QString &img, images) {
667         if (!KIO::NetAccess::exists(KUrl(img), KIO::NetAccess::SourceSide, 0)) {
668             QDomElement e = doc.createElement("missingclip");
669             e.setAttribute("type", TITLE_IMAGE_ELEMENT);
670             e.setAttribute("resource", img);
671             e.setAttribute("id", id);
672             e.setAttribute("name", baseClip);
673             missingClips.append(e);
674         }
675     }
676     kDebug() << "/ / / CHK FONTS: " << fonts;
677     foreach(const QString &fontelement, fonts) {
678         QFont f(fontelement);
679         kDebug() << "/ / / CHK FONTS: " << fontelement << " = " << QFontInfo(f).family();
680         if (fontelement != QFontInfo(f).family()) {
681             QDomElement e = doc.createElement("missingclip");
682             e.setAttribute("type", TITLE_FONT_ELEMENT);
683             e.setAttribute("resource", fontelement);
684             e.setAttribute("id", id);
685             e.setAttribute("name", baseClip);
686             missingClips.append(e);
687         }
688     }
689 }
690
691
692 void DocumentChecker::slotCheckButtons()
693 {
694     if (m_ui.treeWidget->currentItem()) {
695         QTreeWidgetItem *item = m_ui.treeWidget->currentItem();
696         int t = item->data(0, typeRole).toInt();
697         if (t == TITLE_FONT_ELEMENT || t == TITLE_IMAGE_ELEMENT) {
698             m_ui.removeSelected->setEnabled(false);
699         } else m_ui.removeSelected->setEnabled(true);
700     }
701
702 }
703
704 #include "documentchecker.moc"
705
706