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