]> git.sesse.net Git - kdenlive/blob - src/projectlist.cpp
Proxy: fix unnecessary reload on document load, use nicer graphics for progress and...
[kdenlive] / src / projectlist.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 #include "projectlist.h"
21 #include "projectitem.h"
22 #include "commands/addfoldercommand.h"
23 #include "kdenlivesettings.h"
24 #include "slideshowclip.h"
25 #include "ui_colorclip_ui.h"
26 #include "titlewidget.h"
27 #include "definitions.h"
28 #include "clipmanager.h"
29 #include "docclipbase.h"
30 #include "kdenlivedoc.h"
31 #include "renderer.h"
32 #include "kthumb.h"
33 #include "projectlistview.h"
34 #include "timecodedisplay.h"
35 #include "profilesdialog.h"
36 #include "commands/editclipcommand.h"
37 #include "commands/editclipcutcommand.h"
38 #include "commands/editfoldercommand.h"
39 #include "commands/addclipcutcommand.h"
40
41 #include "ui_templateclip_ui.h"
42
43 #include <KDebug>
44 #include <KAction>
45 #include <KLocale>
46 #include <KFileDialog>
47 #include <KInputDialog>
48 #include <KMessageBox>
49 #include <KIO/NetAccess>
50 #include <KFileItem>
51 #include <KApplication>
52 #include <KStandardDirs>
53
54 #ifdef NEPOMUK
55 #include <nepomuk/global.h>
56 #include <nepomuk/resourcemanager.h>
57 //#include <nepomuk/tag.h>
58 #endif
59
60 #include <QMouseEvent>
61 #include <QStylePainter>
62 #include <QPixmap>
63 #include <QIcon>
64 #include <QMenu>
65 #include <QProcess>
66 #include <QHeaderView>
67 #include <QInputDialog>
68 #include <QtConcurrentRun>
69 #include <QVBoxLayout>
70
71 InvalidDialog::InvalidDialog(const QString &caption, const QString &message, bool infoOnly, QWidget *parent) : KDialog(parent)
72 {
73     setCaption(caption);
74     if (infoOnly) setButtons(KDialog::Ok);
75     else setButtons(KDialog::Yes | KDialog::No);
76     QWidget *w = new QWidget(this);
77     QVBoxLayout *l = new QVBoxLayout;
78     l->addWidget(new QLabel(message));
79     m_clipList = new QListWidget;
80     l->addWidget(m_clipList);
81     w->setLayout(l);
82     setMainWidget(w);
83 }
84
85 InvalidDialog::~InvalidDialog()
86 {
87     delete m_clipList;
88 }
89
90
91 void InvalidDialog::addClip(const QString &id, const QString &path)
92 {
93     QListWidgetItem *item = new QListWidgetItem(path);
94     item->setData(Qt::UserRole, id);
95     m_clipList->addItem(item);
96 }
97
98 QStringList InvalidDialog::getIds() const
99 {
100     QStringList ids;
101     for (int i = 0; i < m_clipList->count(); i++) {
102         ids << m_clipList->item(i)->data(Qt::UserRole).toString();
103     }
104     return ids;
105 }
106
107
108 ProjectList::ProjectList(QWidget *parent) :
109     QWidget(parent),
110     m_render(NULL),
111     m_fps(-1),
112     m_commandStack(NULL),
113     m_openAction(NULL),
114     m_reloadAction(NULL),
115     m_stabilizeAction(NULL),
116     m_transcodeAction(NULL),
117     m_doc(NULL),
118     m_refreshed(false),
119     m_allClipsProcessed(false),
120     m_thumbnailQueue(),
121     m_abortAllProxies(false),
122     m_invalidClipDialog(NULL)
123 {
124     QVBoxLayout *layout = new QVBoxLayout;
125     layout->setContentsMargins(0, 0, 0, 0);
126     layout->setSpacing(0);
127     qRegisterMetaType<QDomElement>("QDomElement");
128     // setup toolbar
129     QFrame *frame = new QFrame;
130     frame->setFrameStyle(QFrame::NoFrame);
131     QHBoxLayout *box = new QHBoxLayout;
132     KTreeWidgetSearchLine *searchView = new KTreeWidgetSearchLine;
133     box->addWidget(searchView);
134
135     int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
136     QSize iconSize(size, size);
137
138     m_addButton = new QToolButton;
139     m_addButton->setPopupMode(QToolButton::MenuButtonPopup);
140     m_addButton->setAutoRaise(true);
141     m_addButton->setIconSize(iconSize);
142     box->addWidget(m_addButton);
143
144     m_editButton = new QToolButton;
145     m_editButton->setAutoRaise(true);
146     m_editButton->setIconSize(iconSize);
147     box->addWidget(m_editButton);
148
149     m_deleteButton = new QToolButton;
150     m_deleteButton->setAutoRaise(true);
151     m_deleteButton->setIconSize(iconSize);
152     box->addWidget(m_deleteButton);
153     frame->setLayout(box);
154     layout->addWidget(frame);
155
156     m_listView = new ProjectListView;
157     layout->addWidget(m_listView);
158     setLayout(layout);
159     searchView->setTreeWidget(m_listView);
160
161     connect(this, SIGNAL(processNextThumbnail()), this, SLOT(slotProcessNextThumbnail()));
162     connect(m_listView, SIGNAL(projectModified()), this, SIGNAL(projectModified()));
163     connect(m_listView, SIGNAL(itemSelectionChanged()), this, SLOT(slotClipSelected()));
164     connect(m_listView, SIGNAL(focusMonitor()), this, SIGNAL(raiseClipMonitor()));
165     connect(m_listView, SIGNAL(pauseMonitor()), this, SLOT(slotPauseMonitor()));
166     connect(m_listView, SIGNAL(requestMenu(const QPoint &, QTreeWidgetItem *)), this, SLOT(slotContextMenu(const QPoint &, QTreeWidgetItem *)));
167     connect(m_listView, SIGNAL(addClip()), this, SLOT(slotAddClip()));
168     connect(m_listView, SIGNAL(addClip(const QList <QUrl>, const QString &, const QString &)), this, SLOT(slotAddClip(const QList <QUrl>, const QString &, const QString &)));
169     connect(m_listView, SIGNAL(addClipCut(const QString &, int, int)), this, SLOT(slotAddClipCut(const QString &, int, int)));
170     connect(m_listView, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(slotItemEdited(QTreeWidgetItem *, int)));
171     connect(m_listView, SIGNAL(showProperties(DocClipBase *)), this, SIGNAL(showClipProperties(DocClipBase *)));
172
173     m_listViewDelegate = new ItemDelegate(m_listView);
174     m_listView->setItemDelegate(m_listViewDelegate);
175 #ifdef NEPOMUK
176     if (KdenliveSettings::activate_nepomuk()) {
177         Nepomuk::ResourceManager::instance()->init();
178         if (!Nepomuk::ResourceManager::instance()->initialized()) {
179             kDebug() << "Cannot communicate with Nepomuk, DISABLING it";
180             KdenliveSettings::setActivate_nepomuk(false);
181         }
182     }
183 #endif
184 }
185
186 ProjectList::~ProjectList()
187 {
188     m_abortAllProxies = true;
189     m_thumbnailQueue.clear();
190     delete m_menu;
191     m_listView->blockSignals(true);
192     m_listView->clear();
193     delete m_listViewDelegate;
194 }
195
196 void ProjectList::focusTree() const
197 {
198     m_listView->setFocus();
199 }
200
201 void ProjectList::setupMenu(QMenu *addMenu, QAction *defaultAction)
202 {
203     QList <QAction *> actions = addMenu->actions();
204     for (int i = 0; i < actions.count(); i++) {
205         if (actions.at(i)->data().toString() == "clip_properties") {
206             m_editButton->setDefaultAction(actions.at(i));
207             actions.removeAt(i);
208             i--;
209         } else if (actions.at(i)->data().toString() == "delete_clip") {
210             m_deleteButton->setDefaultAction(actions.at(i));
211             actions.removeAt(i);
212             i--;
213         } else if (actions.at(i)->data().toString() == "edit_clip") {
214             m_openAction = actions.at(i);
215             actions.removeAt(i);
216             i--;
217         } else if (actions.at(i)->data().toString() == "reload_clip") {
218             m_reloadAction = actions.at(i);
219             actions.removeAt(i);
220             i--;
221         } else if (actions.at(i)->data().toString() == "proxy_clip") {
222             m_proxyAction = actions.at(i);
223             actions.removeAt(i);
224             i--;
225         }
226     }
227
228     QMenu *m = new QMenu();
229     m->addActions(actions);
230     m_addButton->setMenu(m);
231     m_addButton->setDefaultAction(defaultAction);
232     m_menu = new QMenu();
233     m_menu->addActions(addMenu->actions());
234 }
235
236 void ProjectList::setupGeneratorMenu(const QHash<QString,QMenu*>& menus)
237 {
238     if (!menus.contains("addMenu") && ! menus.value("addMenu") )
239         return;
240     QMenu *menu = m_addButton->menu();
241         if (menus.contains("addMenu") && menus.value("addMenu")){ 
242                 QMenu* addMenu=menus.value("addMenu");
243                 menu->addMenu(addMenu);
244                 m_addButton->setMenu(menu);
245
246                 m_menu->addMenu(addMenu);
247                 if (addMenu->isEmpty())
248                         addMenu->setEnabled(false);
249         }
250         if (menus.contains("transcodeMenu") && menus.value("transcodeMenu") ){
251                 QMenu* transcodeMenu=menus.value("transcodeMenu");
252                 m_menu->addMenu(transcodeMenu);
253                 if (transcodeMenu->isEmpty())
254                         transcodeMenu->setEnabled(false);
255                 m_transcodeAction = transcodeMenu;
256         }
257         if (menus.contains("stabilizeMenu") && menus.value("stabilizeMenu") ){
258                 QMenu* stabilizeMenu=menus.value("stabilizeMenu");
259                 m_menu->addMenu(stabilizeMenu);
260                 if (stabilizeMenu->isEmpty())
261                         stabilizeMenu->setEnabled(false);
262                 m_stabilizeAction=stabilizeMenu;
263
264         }
265     m_menu->addAction(m_reloadAction);
266     m_menu->addAction(m_proxyAction);
267         if (menus.contains("inTimelineMenu") && menus.value("inTimelineMenu")){
268                 QMenu* inTimelineMenu=menus.value("inTimelineMenu");
269                 m_menu->addMenu(inTimelineMenu);
270                 inTimelineMenu->setEnabled(false);
271         }
272     m_menu->addAction(m_editButton->defaultAction());
273     m_menu->addAction(m_openAction);
274     m_menu->addAction(m_deleteButton->defaultAction());
275     m_menu->insertSeparator(m_deleteButton->defaultAction());
276 }
277
278 void ProjectList::clearSelection()
279 {
280     m_listView->clearSelection();
281 }
282
283 QByteArray ProjectList::headerInfo() const
284 {
285     return m_listView->header()->saveState();
286 }
287
288 void ProjectList::setHeaderInfo(const QByteArray &state)
289 {
290     m_listView->header()->restoreState(state);
291 }
292
293 void ProjectList::updateProjectFormat(Timecode t)
294 {
295     m_timecode = t;
296 }
297
298 void ProjectList::slotEditClip()
299 {
300     QList<QTreeWidgetItem *> list = m_listView->selectedItems();
301     if (list.isEmpty()) return;
302     if (list.count() > 1 || list.at(0)->type() == PROJECTFOLDERTYPE) {
303         editClipSelection(list);
304         return;
305     }
306     ProjectItem *item;
307     if (!m_listView->currentItem() || m_listView->currentItem()->type() == PROJECTFOLDERTYPE)
308         return;
309     if (m_listView->currentItem()->type() == PROJECTSUBCLIPTYPE)
310         item = static_cast <ProjectItem*>(m_listView->currentItem()->parent());
311     else
312         item = static_cast <ProjectItem*>(m_listView->currentItem());
313     if (item && (item->flags() & Qt::ItemIsDragEnabled)) {
314         emit clipSelected(item->referencedClip());
315         emit showClipProperties(item->referencedClip());
316     }
317 }
318
319 void ProjectList::editClipSelection(QList<QTreeWidgetItem *> list)
320 {
321     // Gather all common properties
322     QMap <QString, QString> commonproperties;
323     QList <DocClipBase *> clipList;
324     commonproperties.insert("force_aspect_num", "-");
325     commonproperties.insert("force_aspect_den", "-");
326     commonproperties.insert("force_fps", "-");
327     commonproperties.insert("force_progressive", "-");
328     commonproperties.insert("force_tff", "-");
329     commonproperties.insert("threads", "-");
330     commonproperties.insert("video_index", "-");
331     commonproperties.insert("audio_index", "-");
332     commonproperties.insert("force_colorspace", "-");
333     commonproperties.insert("full_luma", "-");
334     QString transparency = "-";
335
336     bool allowDurationChange = true;
337     int commonDuration = -1;
338     bool hasImages = false;;
339     ProjectItem *item;
340     for (int i = 0; i < list.count(); i++) {
341         item = NULL;
342         if (list.at(i)->type() == PROJECTFOLDERTYPE) {
343             // Add folder items to the list
344             int ct = list.at(i)->childCount();
345             for (int j = 0; j < ct; j++) {
346                 list.append(list.at(i)->child(j));
347             }
348             continue;
349         }
350         else if (list.at(i)->type() == PROJECTSUBCLIPTYPE)
351             item = static_cast <ProjectItem*>(list.at(i)->parent());
352         else
353             item = static_cast <ProjectItem*>(list.at(i));
354         if (!(item->flags() & Qt::ItemIsDragEnabled))
355             continue;
356         if (item) {
357             // check properties
358             DocClipBase *clip = item->referencedClip();
359             if (clipList.contains(clip)) continue;
360             if (clip->clipType() == IMAGE) {
361                 hasImages = true;
362                 if (clip->getProperty("transparency").isEmpty() || clip->getProperty("transparency").toInt() == 0) {
363                     if (transparency == "-") {
364                         // first non transparent image
365                         transparency = "0";
366                     }
367                     else if (transparency == "1") {
368                         // we have transparent and non transparent clips
369                         transparency = "-1";
370                     }
371                 }
372                 else {
373                     if (transparency == "-") {
374                         // first transparent image
375                         transparency = "1";
376                     }
377                     else if (transparency == "0") {
378                         // we have transparent and non transparent clips
379                         transparency = "-1";
380                     }
381                 }
382             }
383             if (clip->clipType() != COLOR && clip->clipType() != IMAGE && clip->clipType() != TEXT)
384                 allowDurationChange = false;
385             if (allowDurationChange && commonDuration != 0) {
386                 if (commonDuration == -1)
387                     commonDuration = clip->duration().frames(m_fps);
388                 else if (commonDuration != clip->duration().frames(m_fps))
389                     commonDuration = 0;
390             }
391             clipList.append(clip);
392             QMap <QString, QString> clipprops = clip->properties();
393             QMapIterator<QString, QString> p(commonproperties);
394             while (p.hasNext()) {
395                 p.next();
396                 if (p.value().isEmpty()) continue;
397                 if (clipprops.contains(p.key())) {
398                     if (p.value() == "-")
399                         commonproperties.insert(p.key(), clipprops.value(p.key()));
400                     else if (p.value() != clipprops.value(p.key()))
401                         commonproperties.insert(p.key(), QString());
402                 } else {
403                     commonproperties.insert(p.key(), QString());
404                 }
405             }
406         }
407     }
408     if (allowDurationChange)
409         commonproperties.insert("out", QString::number(commonDuration));
410     if (hasImages)
411         commonproperties.insert("transparency", transparency);
412     /*QMapIterator<QString, QString> p(commonproperties);
413     while (p.hasNext()) {
414         p.next();
415         kDebug() << "Result: " << p.key() << " = " << p.value();
416     }*/
417     emit showClipProperties(clipList, commonproperties);
418 }
419
420 void ProjectList::slotOpenClip()
421 {
422     ProjectItem *item;
423     if (!m_listView->currentItem() || m_listView->currentItem()->type() == PROJECTFOLDERTYPE)
424         return;
425     if (m_listView->currentItem()->type() == QTreeWidgetItem::UserType + 1)
426         item = static_cast <ProjectItem*>(m_listView->currentItem()->parent());
427     else
428         item = static_cast <ProjectItem*>(m_listView->currentItem());
429     if (item) {
430         if (item->clipType() == IMAGE) {
431             if (KdenliveSettings::defaultimageapp().isEmpty())
432                 KMessageBox::sorry(kapp->activeWindow(), i18n("Please set a default application to open images in the Settings dialog"));
433             else
434                 QProcess::startDetached(KdenliveSettings::defaultimageapp(), QStringList() << item->clipUrl().path());
435         }
436         if (item->clipType() == AUDIO) {
437             if (KdenliveSettings::defaultaudioapp().isEmpty())
438                 KMessageBox::sorry(kapp->activeWindow(), i18n("Please set a default application to open audio files in the Settings dialog"));
439             else
440                 QProcess::startDetached(KdenliveSettings::defaultaudioapp(), QStringList() << item->clipUrl().path());
441         }
442     }
443 }
444
445 void ProjectList::cleanup()
446 {
447     m_listView->clearSelection();
448     QTreeWidgetItemIterator it(m_listView);
449     ProjectItem *item;
450     while (*it) {
451         if ((*it)->type() != PROJECTCLIPTYPE) {
452             it++;
453             continue;
454         }
455         item = static_cast <ProjectItem *>(*it);
456         if (item->numReferences() == 0)
457             item->setSelected(true);
458         it++;
459     }
460     slotRemoveClip();
461 }
462
463 void ProjectList::trashUnusedClips()
464 {
465     QTreeWidgetItemIterator it(m_listView);
466     ProjectItem *item;
467     QStringList ids;
468     QStringList urls;
469     while (*it) {
470         if ((*it)->type() != PROJECTCLIPTYPE) {
471             it++;
472             continue;
473         }
474         item = static_cast <ProjectItem *>(*it);
475         if (item->numReferences() == 0) {
476             ids << item->clipId();
477             KUrl url = item->clipUrl();
478             if (!url.isEmpty() && !urls.contains(url.path()))
479                 urls << url.path();
480         }
481         it++;
482     }
483
484     // Check that we don't use the URL in another clip
485     QTreeWidgetItemIterator it2(m_listView);
486     while (*it2) {
487         if ((*it2)->type() != PROJECTCLIPTYPE) {
488             it2++;
489             continue;
490         }
491         item = static_cast <ProjectItem *>(*it2);
492         if (item->numReferences() > 0) {
493             KUrl url = item->clipUrl();
494             if (!url.isEmpty() && urls.contains(url.path())) urls.removeAll(url.path());
495         }
496         it2++;
497     }
498
499     emit deleteProjectClips(ids, QMap <QString, QString>());
500     for (int i = 0; i < urls.count(); i++)
501         KIO::NetAccess::del(KUrl(urls.at(i)), this);
502 }
503
504 void ProjectList::slotReloadClip(const QString &id)
505 {
506     QList<QTreeWidgetItem *> selected;
507     if (id.isEmpty())
508         selected = m_listView->selectedItems();
509     else {
510         ProjectItem *itemToReLoad = getItemById(id);
511         if (itemToReLoad) selected.append(itemToReLoad);
512     }
513     ProjectItem *item;
514     for (int i = 0; i < selected.count(); i++) {
515         if (selected.at(i)->type() != PROJECTCLIPTYPE) {
516             if (selected.at(i)->type() == PROJECTFOLDERTYPE) {
517                 for (int j = 0; j < selected.at(i)->childCount(); j++)
518                     selected.append(selected.at(i)->child(j));
519             }
520             continue;
521         }
522         item = static_cast <ProjectItem *>(selected.at(i));
523         if (item && !item->isProxyRunning()) {
524             DocClipBase *clip = item->referencedClip();
525             if (!clip || !clip->isClean() || m_render->isProcessing(item->clipId())) {
526                 kDebug()<<"//// TRYING TO RELOAD: "<<item->clipId()<<", but it is busy";
527                 continue;
528             }
529             CLIPTYPE t = item->clipType();
530             if (t == TEXT) {
531                 if (clip && !clip->getProperty("xmltemplate").isEmpty())
532                     regenerateTemplate(item);
533             } else if (t != COLOR && t != SLIDESHOW && clip && clip->checkHash() == false) {
534                 item->referencedClip()->setPlaceHolder(true);
535                 item->setProperty("file_hash", QString());
536             } else if (t == IMAGE) {
537                 clip->getProducer()->set("force_reload", 1);
538             }
539
540             QDomElement e = item->toXml();
541             // Make sure we get the correct producer length if it was adjusted in timeline
542             if (t == COLOR || t == IMAGE || t == SLIDESHOW || t == TEXT) {
543                 int length = QString(clip->producerProperty("length")).toInt();
544                 if (length > 0 && !e.hasAttribute("length")) {
545                     e.setAttribute("length", length);
546                 }
547             }
548             resetThumbsProducer(clip);
549             m_render->getFileProperties(e, item->clipId(), m_listView->iconSize().height(), true);
550         }
551     }
552 }
553
554 void ProjectList::slotModifiedClip(const QString &id)
555 {
556     ProjectItem *item = getItemById(id);
557     if (item) {
558         QPixmap pixmap = qVariantValue<QPixmap>(item->data(0, Qt::DecorationRole));
559         if (!pixmap.isNull()) {
560             QPainter p(&pixmap);
561             p.fillRect(0, 0, pixmap.width(), pixmap.height(), QColor(255, 255, 255, 200));
562             p.drawPixmap(0, 0, KIcon("view-refresh").pixmap(m_listView->iconSize()));
563             p.end();
564         } else {
565             pixmap = KIcon("view-refresh").pixmap(m_listView->iconSize());
566         }
567         item->setData(0, Qt::DecorationRole, pixmap);
568     }
569 }
570
571 void ProjectList::slotMissingClip(const QString &id)
572 {
573     ProjectItem *item = getItemById(id);
574     if (item) {
575         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled);
576         int height = m_listView->iconSize().height();
577         int width = (int)(height  * m_render->dar());
578         QPixmap pixmap = qVariantValue<QPixmap>(item->data(0, Qt::DecorationRole));
579         if (pixmap.isNull()) {
580             pixmap = QPixmap(width, height);
581             pixmap.fill(Qt::transparent);
582         }
583         KIcon icon("dialog-close");
584         QPainter p(&pixmap);
585         p.drawPixmap(3, 3, icon.pixmap(width - 6, height - 6));
586         p.end();
587         item->setData(0, Qt::DecorationRole, pixmap);
588         if (item->referencedClip()) {
589             item->referencedClip()->setPlaceHolder(true);
590             if (m_render == NULL) {
591                 kDebug() << "*********  ERROR, NULL RENDR";
592                 return;
593             }
594             Mlt::Producer *newProd = m_render->invalidProducer(id);
595             if (item->referencedClip()->getProducer()) {
596                 Mlt::Properties props(newProd->get_properties());
597                 Mlt::Properties src_props(item->referencedClip()->getProducer()->get_properties());
598                 props.inherit(src_props);
599             }
600             item->referencedClip()->setProducer(newProd, true);
601             item->slotSetToolTip();
602             emit clipNeedsReload(id);
603         }
604     }
605     update();
606     emit displayMessage(i18n("Check missing clips"), -2);
607     emit updateRenderStatus();
608 }
609
610 void ProjectList::slotAvailableClip(const QString &id)
611 {
612     ProjectItem *item = getItemById(id);
613     if (item == NULL)
614         return;
615     item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled);
616     if (item->referencedClip()) { // && item->referencedClip()->checkHash() == false) {
617         item->setProperty("file_hash", QString());
618         slotReloadClip(id);
619     }
620     /*else {
621     item->referencedClip()->setValid();
622     item->slotSetToolTip();
623     }
624     update();*/
625     emit updateRenderStatus();
626 }
627
628 bool ProjectList::hasMissingClips()
629 {
630     bool missing = false;
631     QTreeWidgetItemIterator it(m_listView);
632     while (*it) {
633         if ((*it)->type() == PROJECTCLIPTYPE && !((*it)->flags() & Qt::ItemIsDragEnabled)) {
634             missing = true;
635             break;
636         }
637         it++;
638     }
639     return missing;
640 }
641
642 void ProjectList::setRenderer(Render *projectRender)
643 {
644     m_render = projectRender;
645     m_listView->setIconSize(QSize((ProjectItem::itemDefaultHeight() - 2) * m_render->dar(), ProjectItem::itemDefaultHeight() - 2));
646 }
647
648 void ProjectList::slotClipSelected()
649 {
650     QTreeWidgetItem *item = m_listView->currentItem();
651     ProjectItem *clip = NULL;
652     if (item) {
653         if (item->type() == PROJECTFOLDERTYPE) {
654             emit clipSelected(NULL);
655             m_editButton->defaultAction()->setEnabled(item->childCount() > 0);
656             m_deleteButton->defaultAction()->setEnabled(true);
657             m_openAction->setEnabled(false);
658             m_reloadAction->setEnabled(false);
659             m_transcodeAction->setEnabled(false);
660             m_stabilizeAction->setEnabled(false);
661         } else {
662             if (item->type() == PROJECTSUBCLIPTYPE) {
663                 // this is a sub item, use base clip
664                 m_deleteButton->defaultAction()->setEnabled(true);
665                 clip = static_cast <ProjectItem*>(item->parent());
666                 if (clip == NULL) kDebug() << "-----------ERROR";
667                 SubProjectItem *sub = static_cast <SubProjectItem*>(item);
668                 emit clipSelected(clip->referencedClip(), sub->zone());
669                 m_transcodeAction->setEnabled(false);
670                 m_stabilizeAction->setEnabled(false);
671                 m_reloadAction->setEnabled(false);
672                 adjustProxyActions(clip);
673                 return;
674             }
675             clip = static_cast <ProjectItem*>(item);
676             if (clip && clip->referencedClip())
677                 emit clipSelected(clip->referencedClip());
678             m_editButton->defaultAction()->setEnabled(true);
679             m_deleteButton->defaultAction()->setEnabled(true);
680             m_reloadAction->setEnabled(true);
681             m_transcodeAction->setEnabled(true);
682             m_stabilizeAction->setEnabled(true);
683             if (clip && clip->clipType() == IMAGE && !KdenliveSettings::defaultimageapp().isEmpty()) {
684                 m_openAction->setIcon(KIcon(KdenliveSettings::defaultimageapp()));
685                 m_openAction->setEnabled(true);
686             } else if (clip && clip->clipType() == AUDIO && !KdenliveSettings::defaultaudioapp().isEmpty()) {
687                 m_openAction->setIcon(KIcon(KdenliveSettings::defaultaudioapp()));
688                 m_openAction->setEnabled(true);
689             } else {
690                 m_openAction->setEnabled(false);
691             }
692             // Display relevant transcoding actions only
693             adjustTranscodeActions(clip);
694             adjustStabilizeActions(clip);
695             // Display uses in timeline
696             emit findInTimeline(clip->clipId());
697         }
698     } else {
699         emit clipSelected(NULL);
700         m_editButton->defaultAction()->setEnabled(false);
701         m_deleteButton->defaultAction()->setEnabled(false);
702         m_openAction->setEnabled(false);
703         m_reloadAction->setEnabled(false);
704         m_transcodeAction->setEnabled(false);
705         m_stabilizeAction->setEnabled(false);
706     }
707     adjustProxyActions(clip);
708 }
709
710 void ProjectList::adjustProxyActions(ProjectItem *clip) const
711 {
712     if (clip == NULL || clip->type() != PROJECTCLIPTYPE || clip->clipType() == COLOR || clip->clipType() == TEXT || clip->clipType() == SLIDESHOW || clip->clipType() == AUDIO) {
713         m_proxyAction->setEnabled(false);
714         return;
715     }
716     m_proxyAction->setEnabled(useProxy());
717     m_proxyAction->blockSignals(true);
718     m_proxyAction->setChecked(clip->hasProxy());
719     m_proxyAction->blockSignals(false);
720 }
721
722 void ProjectList::adjustStabilizeActions(ProjectItem *clip) const
723 {
724
725     if (clip == NULL || clip->type() != PROJECTCLIPTYPE || clip->clipType() == COLOR || clip->clipType() == TEXT || clip->clipType() == PLAYLIST || clip->clipType() == SLIDESHOW) {
726         m_stabilizeAction->setEnabled(false);
727         return;
728     }
729         m_stabilizeAction->setEnabled(true);
730
731 }
732
733 void ProjectList::adjustTranscodeActions(ProjectItem *clip) const
734 {
735     if (clip == NULL || clip->type() != PROJECTCLIPTYPE || clip->clipType() == COLOR || clip->clipType() == TEXT || clip->clipType() == PLAYLIST || clip->clipType() == SLIDESHOW) {
736         m_transcodeAction->setEnabled(false);
737         return;
738     }
739     m_transcodeAction->setEnabled(true);
740     QList<QAction *> transcodeActions = m_transcodeAction->actions();
741     QStringList data;
742     QString condition;
743     for (int i = 0; i < transcodeActions.count(); i++) {
744         data = transcodeActions.at(i)->data().toStringList();
745         if (data.count() > 2) {
746             condition = data.at(2);
747             if (condition.startsWith("vcodec"))
748                 transcodeActions.at(i)->setEnabled(clip->referencedClip()->hasVideoCodec(condition.section('=', 1, 1)));
749             else if (condition.startsWith("acodec"))
750                 transcodeActions.at(i)->setEnabled(clip->referencedClip()->hasVideoCodec(condition.section('=', 1, 1)));
751         }
752     }
753
754 }
755
756 void ProjectList::slotPauseMonitor()
757 {
758     if (m_render)
759         m_render->pause();
760 }
761
762 void ProjectList::slotUpdateClipProperties(const QString &id, QMap <QString, QString> properties)
763 {
764     ProjectItem *item = getItemById(id);
765     if (item) {
766         slotUpdateClipProperties(item, properties);
767         if (properties.contains("out") || properties.contains("force_fps") || properties.contains("resource")) {
768             slotReloadClip(id);
769         } else if (properties.contains("colour") ||
770                    properties.contains("xmldata") ||
771                    properties.contains("force_aspect_num") ||
772                    properties.contains("force_aspect_den") ||
773                    properties.contains("templatetext")) {
774             slotRefreshClipThumbnail(item);
775             emit refreshClip(id, true);
776         } else if (properties.contains("full_luma") || properties.contains("force_colorspace") || properties.contains("loop")) {
777             emit refreshClip(id, false);
778         }
779     }
780 }
781
782 void ProjectList::slotUpdateClipProperties(ProjectItem *clip, QMap <QString, QString> properties)
783 {
784     if (!clip)
785         return;
786     clip->setProperties(properties);
787     if (properties.contains("name")) {
788         monitorItemEditing(false);
789         clip->setText(0, properties.value("name"));
790         monitorItemEditing(true);
791         emit clipNameChanged(clip->clipId(), properties.value("name"));
792     }
793     if (properties.contains("description")) {
794         CLIPTYPE type = clip->clipType();
795         monitorItemEditing(false);
796         clip->setText(1, properties.value("description"));
797         monitorItemEditing(true);
798 #ifdef NEPOMUK
799         if (KdenliveSettings::activate_nepomuk() && (type == AUDIO || type == VIDEO || type == AV || type == IMAGE || type == PLAYLIST)) {
800             // Use Nepomuk system to store clip description
801             Nepomuk::Resource f(clip->clipUrl().path());
802             f.setDescription(properties.value("description"));
803         }
804 #endif
805         emit projectModified();
806     }
807 }
808
809 void ProjectList::slotItemEdited(QTreeWidgetItem *item, int column)
810 {
811     if (item->type() == PROJECTSUBCLIPTYPE) {
812         // this is a sub-item
813         if (column == 1) {
814             // user edited description
815             SubProjectItem *sub = static_cast <SubProjectItem*>(item);
816             ProjectItem *item = static_cast <ProjectItem *>(sub->parent());
817             EditClipCutCommand *command = new EditClipCutCommand(this, item->clipId(), sub->zone(), sub->zone(), sub->description(), sub->text(1), true);
818             m_commandStack->push(command);
819             //slotUpdateCutClipProperties(sub->clipId(), sub->zone(), sub->text(1), sub->text(1));
820         }
821         return;
822     }
823     if (item->type() == PROJECTFOLDERTYPE) {
824         if (column == 0) {
825             FolderProjectItem *folder = static_cast <FolderProjectItem*>(item);
826             editFolder(item->text(0), folder->groupName(), folder->clipId());
827             folder->setGroupName(item->text(0));
828             m_doc->clipManager()->addFolder(folder->clipId(), item->text(0));
829             const int children = item->childCount();
830             for (int i = 0; i < children; i++) {
831                 ProjectItem *child = static_cast <ProjectItem *>(item->child(i));
832                 child->setProperty("groupname", item->text(0));
833             }
834         }
835         return;
836     }
837
838     ProjectItem *clip = static_cast <ProjectItem*>(item);
839     if (column == 1) {
840         if (clip->referencedClip()) {
841             QMap <QString, QString> oldprops;
842             QMap <QString, QString> newprops;
843             oldprops["description"] = clip->referencedClip()->getProperty("description");
844             newprops["description"] = item->text(1);
845
846             if (clip->clipType() == TEXT) {
847                 // This is a text template clip, update the image
848                 /*oldprops.insert("xmldata", clip->referencedClip()->getProperty("xmldata"));
849                 newprops.insert("xmldata", generateTemplateXml(clip->referencedClip()->getProperty("xmltemplate"), item->text(2)).toString());*/
850                 oldprops.insert("templatetext", clip->referencedClip()->getProperty("templatetext"));
851                 newprops.insert("templatetext", item->text(1));
852             }
853             slotUpdateClipProperties(clip->clipId(), newprops);
854             EditClipCommand *command = new EditClipCommand(this, clip->clipId(), oldprops, newprops, false);
855             m_commandStack->push(command);
856         }
857     } else if (column == 0) {
858         if (clip->referencedClip()) {
859             QMap <QString, QString> oldprops;
860             QMap <QString, QString> newprops;
861             oldprops["name"] = clip->referencedClip()->getProperty("name");
862             if (oldprops.value("name") != item->text(0)) {
863                 newprops["name"] = item->text(0);
864                 slotUpdateClipProperties(clip, newprops);
865                 emit projectModified();
866                 EditClipCommand *command = new EditClipCommand(this, clip->clipId(), oldprops, newprops, false);
867                 m_commandStack->push(command);
868             }
869         }
870     }
871 }
872
873 void ProjectList::slotContextMenu(const QPoint &pos, QTreeWidgetItem *item)
874 {
875     bool enable = item ? true : false;
876     m_editButton->defaultAction()->setEnabled(enable);
877     m_deleteButton->defaultAction()->setEnabled(enable);
878     m_reloadAction->setEnabled(enable);
879     m_transcodeAction->setEnabled(enable);
880     m_stabilizeAction->setEnabled(enable);
881     if (enable) {
882         ProjectItem *clip = NULL;
883         if (m_listView->currentItem()->type() == PROJECTSUBCLIPTYPE) {
884             clip = static_cast <ProjectItem*>(item->parent());
885             m_transcodeAction->setEnabled(false);
886             m_stabilizeAction->setEnabled(false);
887             adjustProxyActions(clip);
888         } else if (m_listView->currentItem()->type() == PROJECTCLIPTYPE) {
889             clip = static_cast <ProjectItem*>(item);
890             // Display relevant transcoding actions only
891             adjustTranscodeActions(clip);
892             adjustStabilizeActions(clip);
893             adjustProxyActions(clip);
894             // Display uses in timeline
895             emit findInTimeline(clip->clipId());
896         } else {
897             m_transcodeAction->setEnabled(false);
898             m_stabilizeAction->setEnabled(false);
899         }
900         if (clip && clip->clipType() == IMAGE && !KdenliveSettings::defaultimageapp().isEmpty()) {
901             m_openAction->setIcon(KIcon(KdenliveSettings::defaultimageapp()));
902             m_openAction->setEnabled(true);
903         } else if (clip && clip->clipType() == AUDIO && !KdenliveSettings::defaultaudioapp().isEmpty()) {
904             m_openAction->setIcon(KIcon(KdenliveSettings::defaultaudioapp()));
905             m_openAction->setEnabled(true);
906         } else {
907             m_openAction->setEnabled(false);
908         }
909
910     } else {
911         m_openAction->setEnabled(false);
912     }
913     m_menu->popup(pos);
914 }
915
916 void ProjectList::slotRemoveClip()
917 {
918     if (!m_listView->currentItem())
919         return;
920     QStringList ids;
921     QMap <QString, QString> folderids;
922     QList<QTreeWidgetItem *> selected = m_listView->selectedItems();
923
924     QUndoCommand *delCommand = new QUndoCommand();
925     delCommand->setText(i18n("Delete Clip Zone"));
926     for (int i = 0; i < selected.count(); i++) {
927         if (selected.at(i)->type() == PROJECTSUBCLIPTYPE) {
928             // subitem
929             SubProjectItem *sub = static_cast <SubProjectItem *>(selected.at(i));
930             ProjectItem *item = static_cast <ProjectItem *>(sub->parent());
931             new AddClipCutCommand(this, item->clipId(), sub->zone().x(), sub->zone().y(), sub->description(), false, true, delCommand);
932         } else if (selected.at(i)->type() == PROJECTFOLDERTYPE) {
933             // folder
934             FolderProjectItem *folder = static_cast <FolderProjectItem *>(selected.at(i));
935             folderids[folder->groupName()] = folder->clipId();
936             int children = folder->childCount();
937
938             if (children > 0 && KMessageBox::questionYesNo(kapp->activeWindow(), i18np("Delete folder <b>%2</b>?<br />This will also remove the clip in that folder", "Delete folder <b>%2</b>?<br />This will also remove the %1 clips in that folder",  children, folder->text(1)), i18n("Delete Folder")) != KMessageBox::Yes)
939                 return;
940             for (int i = 0; i < children; ++i) {
941                 ProjectItem *child = static_cast <ProjectItem *>(folder->child(i));
942                 ids << child->clipId();
943             }
944         } else {
945             ProjectItem *item = static_cast <ProjectItem *>(selected.at(i));
946             ids << item->clipId();
947             if (item->numReferences() > 0 && KMessageBox::questionYesNo(kapp->activeWindow(), i18np("Delete clip <b>%2</b>?<br />This will also remove the clip in timeline", "Delete clip <b>%2</b>?<br />This will also remove its %1 clips in timeline", item->numReferences(), item->names().at(1)), i18n("Delete Clip"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "DeleteAll") == KMessageBox::No) {
948                 KMessageBox::enableMessage("DeleteAll");
949                 return;
950             }
951         }
952     }
953     KMessageBox::enableMessage("DeleteAll");
954     if (delCommand->childCount() == 0)
955         delete delCommand;
956     else
957         m_commandStack->push(delCommand);
958     emit deleteProjectClips(ids, folderids);
959 }
960
961 void ProjectList::updateButtons() const
962 {
963     if (m_listView->topLevelItemCount() == 0) {
964         m_deleteButton->defaultAction()->setEnabled(false);
965         m_editButton->defaultAction()->setEnabled(false);
966     } else {
967         m_deleteButton->defaultAction()->setEnabled(true);
968         if (!m_listView->currentItem())
969             m_listView->setCurrentItem(m_listView->topLevelItem(0));
970         QTreeWidgetItem *item = m_listView->currentItem();
971         if (item && item->type() == PROJECTCLIPTYPE) {
972             m_editButton->defaultAction()->setEnabled(true);
973             m_openAction->setEnabled(true);
974             m_reloadAction->setEnabled(true);
975             m_transcodeAction->setEnabled(true);
976             m_stabilizeAction->setEnabled(true);
977             return;
978         }
979         else if (item && item->type() == PROJECTFOLDERTYPE && item->childCount() > 0) {
980             m_editButton->defaultAction()->setEnabled(true);
981         }
982         else m_editButton->defaultAction()->setEnabled(false);
983     }
984     m_openAction->setEnabled(false);
985     m_reloadAction->setEnabled(false);
986     m_transcodeAction->setEnabled(false);
987     m_stabilizeAction->setEnabled(false);
988     m_proxyAction->setEnabled(false);
989 }
990
991 void ProjectList::selectItemById(const QString &clipId)
992 {
993     ProjectItem *item = getItemById(clipId);
994     if (item)
995         m_listView->setCurrentItem(item);
996 }
997
998
999 void ProjectList::slotDeleteClip(const QString &clipId)
1000 {
1001     ProjectItem *item = getItemById(clipId);
1002     if (!item) {
1003         kDebug() << "/// Cannot find clip to delete";
1004         return;
1005     }
1006     if (item->isProxyRunning()) m_abortProxy.append(item->referencedClip()->getProperty("proxy"));
1007     m_listView->blockSignals(true);
1008     QTreeWidgetItem *newSelectedItem = m_listView->itemAbove(item);
1009     if (!newSelectedItem)
1010         newSelectedItem = m_listView->itemBelow(item);
1011     delete item;
1012     // Pause playing to prevent crash while deleting clip
1013     slotPauseMonitor();
1014     m_doc->clipManager()->deleteClip(clipId);
1015     m_listView->blockSignals(false);
1016     if (newSelectedItem) {
1017         m_listView->setCurrentItem(newSelectedItem);
1018     } else {
1019         updateButtons();
1020         emit clipSelected(NULL);
1021     }
1022 }
1023
1024
1025 void ProjectList::editFolder(const QString folderName, const QString oldfolderName, const QString &clipId)
1026 {
1027     EditFolderCommand *command = new EditFolderCommand(this, folderName, oldfolderName, clipId, false);
1028     m_commandStack->push(command);
1029     m_doc->setModified(true);
1030 }
1031
1032 void ProjectList::slotAddFolder()
1033 {
1034     AddFolderCommand *command = new AddFolderCommand(this, i18n("Folder"), QString::number(m_doc->clipManager()->getFreeFolderId()), true);
1035     m_commandStack->push(command);
1036 }
1037
1038 void ProjectList::slotAddFolder(const QString foldername, const QString &clipId, bool remove, bool edit)
1039 {
1040     if (remove) {
1041         FolderProjectItem *item = getFolderItemById(clipId);
1042         if (item) {
1043             m_doc->clipManager()->deleteFolder(clipId);
1044             QTreeWidgetItem *newSelectedItem = m_listView->itemAbove(item);
1045             if (!newSelectedItem)
1046                 newSelectedItem = m_listView->itemBelow(item);
1047             delete item;
1048             if (newSelectedItem)
1049                 m_listView->setCurrentItem(newSelectedItem);
1050             else
1051                 updateButtons();
1052         }
1053     } else {
1054         if (edit) {
1055             FolderProjectItem *item = getFolderItemById(clipId);
1056             if (item) {
1057                 m_listView->blockSignals(true);
1058                 item->setGroupName(foldername);
1059                 m_listView->blockSignals(false);
1060                 m_doc->clipManager()->addFolder(clipId, foldername);
1061                 const int children = item->childCount();
1062                 for (int i = 0; i < children; i++) {
1063                     ProjectItem *child = static_cast <ProjectItem *>(item->child(i));
1064                     child->setProperty("groupname", foldername);
1065                 }
1066             }
1067         } else {
1068             m_listView->blockSignals(true);
1069             m_listView->setCurrentItem(new FolderProjectItem(m_listView, QStringList() << foldername, clipId));
1070             m_doc->clipManager()->addFolder(clipId, foldername);
1071             m_listView->blockSignals(false);
1072             m_listView->editItem(m_listView->currentItem(), 0);
1073         }
1074         updateButtons();
1075     }
1076     m_doc->setModified(true);
1077 }
1078
1079
1080
1081 void ProjectList::deleteProjectFolder(QMap <QString, QString> map)
1082 {
1083     QMapIterator<QString, QString> i(map);
1084     QUndoCommand *delCommand = new QUndoCommand();
1085     delCommand->setText(i18n("Delete Folder"));
1086     while (i.hasNext()) {
1087         i.next();
1088         new AddFolderCommand(this, i.key(), i.value(), false, delCommand);
1089     }
1090     if (delCommand->childCount() > 0) m_commandStack->push(delCommand);
1091     else delete delCommand;
1092 }
1093
1094 void ProjectList::slotAddClip(DocClipBase *clip, bool getProperties)
1095 {
1096     //m_listView->setEnabled(false);
1097     const QString parent = clip->getProperty("groupid");
1098     ProjectItem *item = NULL;
1099     monitorItemEditing(false);
1100     if (!parent.isEmpty()) {
1101         FolderProjectItem *parentitem = getFolderItemById(parent);
1102         if (!parentitem) {
1103             QStringList text;
1104             QString groupName = clip->getProperty("groupname");
1105             //kDebug() << "Adding clip to new group: " << groupName;
1106             if (groupName.isEmpty()) groupName = i18n("Folder");
1107             text << groupName;
1108             parentitem = new FolderProjectItem(m_listView, text, parent);
1109         }
1110
1111         if (parentitem)
1112             item = new ProjectItem(parentitem, clip);
1113     }
1114     if (item == NULL) {
1115         item = new ProjectItem(m_listView, clip);
1116     }
1117     if (item->data(0, DurationRole).isNull()) item->setData(0, DurationRole, i18n("Loading"));
1118     connect(clip, SIGNAL(createProxy(const QString &)), this, SLOT(slotCreateProxy(const QString &)));
1119     connect(clip, SIGNAL(abortProxy(const QString &, const QString &)), this, SLOT(slotAbortProxy(const QString, const QString)));
1120     if (getProperties) {
1121         int height = m_listView->iconSize().height();
1122         int width = (int)(height  * m_render->dar());
1123         QPixmap pix =  KIcon("video-x-generic").pixmap(QSize(width, height));
1124         item->setData(0, Qt::DecorationRole, pix);
1125         //item->setFlags(Qt::ItemIsSelectable);
1126         m_listView->processLayout();
1127         QDomElement e = clip->toXML().cloneNode().toElement();
1128         e.removeAttribute("file_hash");
1129         resetThumbsProducer(clip);
1130         m_render->getFileProperties(e, clip->getId(), m_listView->iconSize().height(), true);
1131     }
1132     // WARNING: code below triggers unnecessary reload of all proxy clips on document loading... is it useful in some cases?
1133     /*else if (item->hasProxy() && !item->isProxyRunning()) {
1134         slotCreateProxy(clip->getId());
1135     }*/
1136     
1137     KUrl url = clip->fileURL();
1138 #ifdef NEPOMUK
1139     if (!url.isEmpty() && KdenliveSettings::activate_nepomuk()) {
1140         // if file has Nepomuk comment, use it
1141         Nepomuk::Resource f(url.path());
1142         QString annotation = f.description();
1143         if (!annotation.isEmpty()) item->setText(1, annotation);
1144         item->setText(2, QString::number(f.rating()));
1145     }
1146 #endif
1147
1148     // Add info to date column
1149     QFileInfo fileInfo(url.path());
1150     if (fileInfo.exists()) {
1151         item->setText(3, fileInfo.lastModified().toString(QString("yyyy/MM/dd hh:mm:ss")));
1152     }
1153
1154     // Add cut zones
1155     QList <CutZoneInfo> cuts = clip->cutZones();
1156     if (!cuts.isEmpty()) {
1157         for (int i = 0; i < cuts.count(); i++) {
1158             SubProjectItem *sub = new SubProjectItem(item, cuts.at(i).zone.x(), cuts.at(i).zone.y(), cuts.at(i).description);
1159             if (!clip->getClipHash().isEmpty()) {
1160                 QString cachedPixmap = m_doc->projectFolder().path(KUrl::AddTrailingSlash) + "thumbs/" + clip->getClipHash() + '#' + QString::number(cuts.at(i).zone.x()) + ".png";
1161                 if (QFile::exists(cachedPixmap)) {
1162                     QPixmap pix(cachedPixmap);
1163                     if (pix.isNull())
1164                         KIO::NetAccess::del(KUrl(cachedPixmap), this);
1165                     sub->setData(0, Qt::DecorationRole, pix);
1166                 }
1167             }
1168         }
1169     }
1170     monitorItemEditing(true);
1171     updateButtons();
1172 }
1173
1174 void ProjectList::slotGotProxy(const QString &proxyPath)
1175 {
1176     if (proxyPath.isEmpty() || m_abortAllProxies) return;
1177     QTreeWidgetItemIterator it(m_listView);
1178     ProjectItem *item;
1179
1180     while (*it && !m_abortAllProxies) {
1181         if ((*it)->type() == PROJECTCLIPTYPE) {
1182             item = static_cast <ProjectItem *>(*it);
1183             if (item->referencedClip()->getProperty("proxy") == proxyPath)
1184                 slotGotProxy(item);
1185         }
1186         ++it;
1187     }
1188 }
1189
1190 void ProjectList::slotGotProxy(ProjectItem *item)
1191 {
1192     if (item == NULL) return;
1193     DocClipBase *clip = item->referencedClip();
1194     if (!clip || !clip->isClean() || m_render->isProcessing(item->clipId())) {
1195         // Clip is being reprocessed, abort
1196         kDebug()<<"//// TRYING TO PROXY: "<<item->clipId()<<", but it is busy";
1197         return;
1198     }
1199     
1200     // Proxy clip successfully created
1201     QDomElement e = clip->toXML().cloneNode().toElement();
1202
1203     // Make sure we get the correct producer length if it was adjusted in timeline
1204     CLIPTYPE t = item->clipType();
1205     if (t == COLOR || t == IMAGE || t == SLIDESHOW || t == TEXT) {
1206         int length = QString(clip->producerProperty("length")).toInt();
1207         if (length > 0 && !e.hasAttribute("length")) {
1208             e.setAttribute("length", length);
1209         }
1210     }
1211     resetThumbsProducer(clip);
1212     m_render->getFileProperties(e, clip->getId(), m_listView->iconSize().height(), true);
1213 }
1214
1215 void ProjectList::slotResetProjectList()
1216 {
1217     m_listView->blockSignals(true);
1218     m_abortAllProxies = true;
1219     m_proxyThreads.waitForFinished();
1220     m_proxyThreads.clearFutures();
1221     m_thumbnailQueue.clear();
1222     m_listView->clear();
1223     m_listView->setEnabled(true);
1224     emit clipSelected(NULL);
1225     m_refreshed = false;
1226     m_allClipsProcessed = false;
1227     m_abortAllProxies = false;
1228     m_listView->blockSignals(false);
1229 }
1230
1231 void ProjectList::slotUpdateClip(const QString &id)
1232 {
1233     ProjectItem *item = getItemById(id);
1234     monitorItemEditing(false);
1235     if (item) item->setData(0, UsageRole, QString::number(item->numReferences()));
1236     monitorItemEditing(true);
1237 }
1238
1239 void ProjectList::getCachedThumbnail(ProjectItem *item)
1240 {
1241     if (!item) return;
1242     DocClipBase *clip = item->referencedClip();
1243     if (!clip) return;
1244     QString cachedPixmap = m_doc->projectFolder().path(KUrl::AddTrailingSlash) + "thumbs/" + clip->getClipHash() + ".png";
1245     if (QFile::exists(cachedPixmap)) {
1246         QPixmap pix(cachedPixmap);
1247         if (pix.isNull()) {
1248             KIO::NetAccess::del(KUrl(cachedPixmap), this);
1249             requestClipThumbnail(item->clipId());
1250         }
1251         else {
1252             processThumbOverlays(item, pix);
1253             item->setData(0, Qt::DecorationRole, pix);
1254         }
1255     }
1256     else {
1257         requestClipThumbnail(item->clipId());
1258     }
1259 }
1260
1261 void ProjectList::getCachedThumbnail(SubProjectItem *item)
1262 {
1263     if (!item) return;
1264     ProjectItem *parentItem = static_cast <ProjectItem *>(item->parent());
1265     if (!parentItem) return;
1266     DocClipBase *clip = parentItem->referencedClip();
1267     if (!clip) return;
1268     int pos = item->zone().x();
1269     QString cachedPixmap = m_doc->projectFolder().path(KUrl::AddTrailingSlash) + "thumbs/" + clip->getClipHash() + "#" + QString::number(pos) + ".png";
1270     if (QFile::exists(cachedPixmap)) {
1271         QPixmap pix(cachedPixmap);
1272         if (pix.isNull()) {
1273             KIO::NetAccess::del(KUrl(cachedPixmap), this);
1274             requestClipThumbnail(parentItem->clipId() + '#' + QString::number(pos));
1275         }
1276         else item->setData(0, Qt::DecorationRole, pix);
1277     }
1278     else requestClipThumbnail(parentItem->clipId() + '#' + QString::number(pos));
1279 }
1280
1281 void ProjectList::updateAllClips(bool displayRatioChanged, bool fpsChanged, QStringList brokenClips)
1282 {
1283     if (!m_allClipsProcessed) m_listView->setEnabled(false);
1284     m_listView->setSortingEnabled(false);
1285     QTreeWidgetItemIterator it(m_listView);
1286     DocClipBase *clip;
1287     ProjectItem *item;
1288     monitorItemEditing(false);
1289     int height = m_listView->iconSize().height();
1290     int width = (int)(height  * m_render->dar());
1291     QPixmap missingPixmap = QPixmap(width, height);
1292     missingPixmap.fill(Qt::transparent);
1293     KIcon icon("dialog-close");
1294     QPainter p(&missingPixmap);
1295     p.drawPixmap(3, 3, icon.pixmap(width - 6, height - 6));
1296     p.end();
1297     
1298     int max = m_doc->clipManager()->clipsCount();
1299     max = qMax(1, max);
1300     int ct = 0;
1301
1302     while (*it) {
1303         emit displayMessage(i18n("Loading thumbnails"), (int)(100 *(max - ct++) / max));
1304         if ((*it)->type() == PROJECTSUBCLIPTYPE) {
1305             // subitem
1306             SubProjectItem *sub = static_cast <SubProjectItem *>(*it);
1307             if (displayRatioChanged) {
1308                 item = static_cast <ProjectItem *>((*it)->parent());
1309                 requestClipThumbnail(item->clipId() + '#' + QString::number(sub->zone().x()));
1310             }
1311             else if (sub->data(0, Qt::DecorationRole).isNull()) {
1312                 getCachedThumbnail(sub);
1313             }
1314             ++it;
1315             continue;
1316         } else if ((*it)->type() == PROJECTFOLDERTYPE) {
1317             // folder
1318             ++it;
1319             continue;
1320         } else {
1321             item = static_cast <ProjectItem *>(*it);
1322             clip = item->referencedClip();
1323             if (item->referencedClip()->getProducer() == NULL) {
1324                 bool replace = false;
1325                 if (brokenClips.contains(item->clipId())) {
1326                     // if this is a proxy clip, disable proxy
1327                     item->setProxyStatus(NOPROXY);
1328                     clip->setProperty("proxy", "-");
1329                     replace = true;
1330                 }
1331                 if (clip->isPlaceHolder() == false && !item->isProxyRunning()) {
1332                     QDomElement xml = clip->toXML();
1333                     if (fpsChanged) {
1334                         xml.removeAttribute("out");
1335                         xml.removeAttribute("file_hash");
1336                         xml.removeAttribute("proxy_out");
1337                     }
1338                     if (!replace) replace = xml.attribute("replace") == "1";
1339                     if (replace) {
1340                         resetThumbsProducer(clip);
1341                     }
1342                     m_render->getFileProperties(xml, clip->getId(), m_listView->iconSize().height(), replace);
1343                 }
1344                 else if (clip->isPlaceHolder()) {
1345                     item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled);
1346                     if (item->data(0, Qt::DecorationRole).isNull()) {
1347                         item->setData(0, Qt::DecorationRole, missingPixmap);
1348                     }
1349                     else {
1350                         QPixmap pixmap = qVariantValue<QPixmap>(item->data(0, Qt::DecorationRole));
1351                         QPainter p(&pixmap);
1352                         p.drawPixmap(3, 3, KIcon("dialog-close").pixmap(pixmap.width() - 6, pixmap.height() - 6));
1353                         p.end();
1354                         item->setData(0, Qt::DecorationRole, pixmap);
1355                     }
1356                 }
1357             } else {              
1358                 if (displayRatioChanged) {
1359                     requestClipThumbnail(clip->getId());
1360                 }
1361                 else if (item->data(0, Qt::DecorationRole).isNull()) {
1362                     getCachedThumbnail(item);
1363                 }
1364                 if (item->data(0, DurationRole).toString().isEmpty()) {
1365                     item->changeDuration(item->referencedClip()->getProducer()->get_playtime());
1366                 }
1367                 if (clip->isPlaceHolder()) {
1368                     QPixmap pixmap = qVariantValue<QPixmap>(item->data(0, Qt::DecorationRole));
1369                     if (pixmap.isNull()) {
1370                         pixmap = QPixmap(width, height);
1371                         pixmap.fill(Qt::transparent);
1372                     }
1373                     QPainter p(&pixmap);
1374                     p.drawPixmap(3, 3, KIcon("dialog-close").pixmap(pixmap.width() - 6, pixmap.height() - 6));
1375                     p.end();
1376                     item->setData(0, Qt::DecorationRole, pixmap);
1377                 }
1378             }
1379             item->setData(0, UsageRole, QString::number(item->numReferences()));
1380         }
1381         ++it;
1382     }
1383
1384     m_listView->setSortingEnabled(true);
1385     m_allClipsProcessed = true;
1386     if (m_render->processingItems() == 0) {
1387        monitorItemEditing(true);
1388        slotProcessNextThumbnail();
1389     }
1390 }
1391
1392 // static
1393 QString ProjectList::getExtensions()
1394 {
1395     // Build list of mime types
1396     QStringList mimeTypes = QStringList() << "application/x-kdenlive" << "application/x-kdenlivetitle" << "video/mlt-playlist" << "text/plain"
1397                             << "video/x-flv" << "application/vnd.rn-realmedia" << "video/x-dv" << "video/dv" << "video/x-msvideo" << "video/x-matroska" << "video/mpeg" << "video/ogg" << "video/x-ms-wmv" << "video/mp4" << "video/quicktime" << "video/webm"
1398                             << "audio/x-flac" << "audio/x-matroska" << "audio/mp4" << "audio/mpeg" << "audio/x-mp3" << "audio/ogg" << "audio/x-wav" << "audio/x-aiff" << "audio/aiff" << "application/ogg" << "application/mxf" << "application/x-shockwave-flash"
1399                             << "image/gif" << "image/jpeg" << "image/png" << "image/x-tga" << "image/x-bmp" << "image/svg+xml" << "image/tiff" << "image/x-xcf" << "image/x-xcf-gimp" << "image/x-vnd.adobe.photoshop" << "image/x-pcx" << "image/x-exr";
1400
1401     QString allExtensions;
1402     foreach(const QString & mimeType, mimeTypes) {
1403         KMimeType::Ptr mime(KMimeType::mimeType(mimeType));
1404         if (mime) {
1405             allExtensions.append(mime->patterns().join(" "));
1406             allExtensions.append(' ');
1407         }
1408     }
1409     return allExtensions.simplified();
1410 }
1411
1412 void ProjectList::slotAddClip(const QList <QUrl> givenList, const QString &groupName, const QString &groupId)
1413 {
1414     if (!m_commandStack)
1415         kDebug() << "!!!!!!!!!!!!!!!! NO CMD STK";
1416
1417     KUrl::List list;
1418     if (givenList.isEmpty()) {
1419         QString allExtensions = getExtensions();
1420         const QString dialogFilter = allExtensions + ' ' + QLatin1Char('|') + i18n("All Supported Files") + "\n* " + QLatin1Char('|') + i18n("All Files");
1421         QCheckBox *b = new QCheckBox(i18n("Import image sequence"));
1422         b->setChecked(KdenliveSettings::autoimagesequence());
1423         QCheckBox *c = new QCheckBox(i18n("Transparent background for images"));
1424         c->setChecked(KdenliveSettings::autoimagetransparency());
1425         QFrame *f = new QFrame;
1426         f->setFrameShape(QFrame::NoFrame);
1427         QHBoxLayout *l = new QHBoxLayout;
1428         l->addWidget(b);
1429         l->addWidget(c);
1430         l->addStretch(5);
1431         f->setLayout(l);
1432         KFileDialog *d = new KFileDialog(KUrl("kfiledialog:///clipfolder"), dialogFilter, kapp->activeWindow(), f);
1433         d->setOperationMode(KFileDialog::Opening);
1434         d->setMode(KFile::Files);
1435         if (d->exec() == QDialog::Accepted) {
1436             KdenliveSettings::setAutoimagetransparency(c->isChecked());
1437         }
1438         list = d->selectedUrls();
1439         if (b->isChecked() && list.count() == 1) {
1440             // Check for image sequence
1441             KUrl url = list.at(0);
1442             QString fileName = url.fileName().section('.', 0, -2);
1443             if (fileName.at(fileName.size() - 1).isDigit()) {
1444                 KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url);
1445                 if (item.mimetype().startsWith("image")) {
1446                     // import as sequence if we found more than one image in the sequence
1447                     QStringList list;
1448                     QString pattern = SlideshowClip::selectedPath(url.path(), false, QString(), &list);
1449                     int count = list.count();
1450                     if (count > 1) {
1451                         delete d;
1452                         QStringList groupInfo = getGroup();
1453
1454                         // get image sequence base name
1455                         while (fileName.at(fileName.size() - 1).isDigit()) {
1456                             fileName.chop(1);
1457                         }
1458
1459                         m_doc->slotCreateSlideshowClipFile(fileName, pattern, count, m_timecode.reformatSeparators(KdenliveSettings::sequence_duration()),
1460                                                            false, false, false,
1461                                                            m_timecode.getTimecodeFromFrames(int(ceil(m_timecode.fps()))), QString(), 0,
1462                                                            QString(), groupInfo.at(0), groupInfo.at(1));
1463                         return;
1464                     }
1465                 }
1466             }
1467         }
1468         delete d;
1469     } else {
1470         for (int i = 0; i < givenList.count(); i++)
1471             list << givenList.at(i);
1472     }
1473
1474     foreach(const KUrl & file, list) {
1475         // Check there is no folder here
1476         KMimeType::Ptr type = KMimeType::findByUrl(file);
1477         if (type->is("inode/directory")) {
1478             // user dropped a folder
1479             list.removeAll(file);
1480         }
1481     }
1482
1483     if (list.isEmpty())
1484         return;
1485
1486     if (givenList.isEmpty()) {
1487         QStringList groupInfo = getGroup();
1488         m_doc->slotAddClipList(list, groupInfo.at(0), groupInfo.at(1));
1489     } else {
1490         m_doc->slotAddClipList(list, groupName, groupId);
1491     }
1492 }
1493
1494 void ProjectList::slotRemoveInvalidClip(const QString &id, bool replace)
1495 {
1496     ProjectItem *item = getItemById(id);
1497     m_processingClips.removeAll(id);
1498     m_thumbnailQueue.removeAll(id);
1499     if (item) {
1500         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled);
1501         const QString path = item->referencedClip()->fileURL().path();
1502         if (item->referencedClip()->isPlaceHolder()) replace = false;
1503         if (!path.isEmpty()) {
1504             if (m_invalidClipDialog) {
1505                 m_invalidClipDialog->addClip(id, path);
1506                 return;
1507             }
1508             else {
1509                 if (replace)
1510                     m_invalidClipDialog = new InvalidDialog(i18n("Invalid clip"),  i18n("Clip <b>%1</b><br />is invalid, will be removed from project.", QString()), replace, kapp->activeWindow());
1511                 else {
1512                     m_invalidClipDialog = new InvalidDialog(i18n("Invalid clip"),  i18n("Clip <b>%1</b><br />is missing or invalid. Remove it from project?", QString()), replace, kapp->activeWindow());
1513                 }
1514                 m_invalidClipDialog->addClip(id, path);
1515                 int result = m_invalidClipDialog->exec();
1516                 if (result == KDialog::Yes) replace = true;
1517             }
1518         }
1519         if (m_invalidClipDialog) {
1520             if (replace)
1521                 emit deleteProjectClips(m_invalidClipDialog->getIds(), QMap <QString, QString>());
1522             delete m_invalidClipDialog;
1523             m_invalidClipDialog = NULL;
1524         }
1525         
1526     }
1527 }
1528
1529 void ProjectList::slotRemoveInvalidProxy(const QString &id, bool durationError)
1530 {
1531     ProjectItem *item = getItemById(id);
1532     if (item) {
1533         kDebug()<<"// Proxy for clip "<<id<<" is invalid, delete";
1534         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled);
1535         if (durationError) {
1536             kDebug() << "Proxy duration is wrong, try changing transcoding parameters.";
1537             emit displayMessage(i18n("Proxy clip unusable (duration is different from original)."), -2);
1538         }
1539         item->setProxyStatus(PROXYCRASHED);
1540         QString path = item->referencedClip()->getProperty("proxy");
1541         KUrl proxyFolder(m_doc->projectFolder().path( KUrl::AddTrailingSlash) + "proxy/");
1542
1543         //Security check: make sure the invalid proxy file is in the proxy folder
1544         if (proxyFolder.isParentOf(KUrl(path))) {
1545             QFile::remove(path);
1546         }
1547         if (item->referencedClip()->getProducer() == NULL) {
1548             // Clip has no valid producer, request it
1549             slotProxyCurrentItem(false, item);
1550         }
1551         else {
1552             // refresh thumbs producer
1553             item->referencedClip()->reloadThumbProducer();
1554         }
1555     }
1556     m_processingClips.removeAll(id);
1557     m_thumbnailQueue.removeAll(id);
1558 }
1559
1560 void ProjectList::slotAddColorClip()
1561 {
1562     if (!m_commandStack)
1563         kDebug() << "!!!!!!!!!!!!!!!! NO CMD STK";
1564
1565     QDialog *dia = new QDialog(this);
1566     Ui::ColorClip_UI dia_ui;
1567     dia_ui.setupUi(dia);
1568     dia->setWindowTitle(i18n("Color Clip"));
1569     dia_ui.clip_name->setText(i18n("Color Clip"));
1570
1571     TimecodeDisplay *t = new TimecodeDisplay(m_timecode);
1572     t->setValue(KdenliveSettings::color_duration());
1573     dia_ui.clip_durationBox->addWidget(t);
1574     dia_ui.clip_color->setColor(KdenliveSettings::colorclipcolor());
1575
1576     if (dia->exec() == QDialog::Accepted) {
1577         QString color = dia_ui.clip_color->color().name();
1578         KdenliveSettings::setColorclipcolor(color);
1579         color = color.replace(0, 1, "0x") + "ff";
1580         QStringList groupInfo = getGroup();
1581         m_doc->slotCreateColorClip(dia_ui.clip_name->text(), color, m_timecode.getTimecode(t->gentime()), groupInfo.at(0), groupInfo.at(1));
1582     }
1583     delete t;
1584     delete dia;
1585 }
1586
1587
1588 void ProjectList::slotAddSlideshowClip()
1589 {
1590     if (!m_commandStack)
1591         kDebug() << "!!!!!!!!!!!!!!!! NO CMD STK";
1592
1593     SlideshowClip *dia = new SlideshowClip(m_timecode, this);
1594
1595     if (dia->exec() == QDialog::Accepted) {
1596         QStringList groupInfo = getGroup();
1597         m_doc->slotCreateSlideshowClipFile(dia->clipName(), dia->selectedPath(), dia->imageCount(), dia->clipDuration(),
1598                                            dia->loop(), dia->crop(), dia->fade(),
1599                                            dia->lumaDuration(), dia->lumaFile(), dia->softness(),
1600                                            dia->animation(), groupInfo.at(0), groupInfo.at(1));
1601     }
1602     delete dia;
1603 }
1604
1605 void ProjectList::slotAddTitleClip()
1606 {
1607     QStringList groupInfo = getGroup();
1608     m_doc->slotCreateTextClip(groupInfo.at(0), groupInfo.at(1));
1609 }
1610
1611 void ProjectList::slotAddTitleTemplateClip()
1612 {
1613     if (!m_commandStack)
1614         kDebug() << "!!!!!!!!!!!!!!!! NO CMD STK";
1615
1616     QStringList groupInfo = getGroup();
1617
1618     // Get the list of existing templates
1619     QStringList filter;
1620     filter << "*.kdenlivetitle";
1621     const QString path = m_doc->projectFolder().path(KUrl::AddTrailingSlash) + "titles/";
1622     QStringList templateFiles = QDir(path).entryList(filter, QDir::Files);
1623
1624     QDialog *dia = new QDialog(this);
1625     Ui::TemplateClip_UI dia_ui;
1626     dia_ui.setupUi(dia);
1627     for (int i = 0; i < templateFiles.size(); ++i)
1628         dia_ui.template_list->comboBox()->addItem(templateFiles.at(i), path + templateFiles.at(i));
1629
1630     if (!templateFiles.isEmpty())
1631         dia_ui.buttonBox->button(QDialogButtonBox::Ok)->setFocus();
1632     dia_ui.template_list->fileDialog()->setFilter("application/x-kdenlivetitle");
1633     //warning: setting base directory doesn't work??
1634     KUrl startDir(path);
1635     dia_ui.template_list->fileDialog()->setUrl(startDir);
1636     dia_ui.text_box->setHidden(true);
1637     if (dia->exec() == QDialog::Accepted) {
1638         QString textTemplate = dia_ui.template_list->comboBox()->itemData(dia_ui.template_list->comboBox()->currentIndex()).toString();
1639         if (textTemplate.isEmpty()) textTemplate = dia_ui.template_list->comboBox()->currentText();
1640         // Create a cloned template clip
1641         m_doc->slotCreateTextTemplateClip(groupInfo.at(0), groupInfo.at(1), KUrl(textTemplate));
1642     }
1643     delete dia;
1644 }
1645
1646 QStringList ProjectList::getGroup() const
1647 {
1648     QStringList result;
1649     QTreeWidgetItem *item = m_listView->currentItem();
1650     while (item && item->type() != PROJECTFOLDERTYPE)
1651         item = item->parent();
1652
1653     if (item) {
1654         FolderProjectItem *folder = static_cast <FolderProjectItem *>(item);
1655         result << folder->groupName() << folder->clipId();
1656     } else {
1657         result << QString() << QString();
1658     }
1659     return result;
1660 }
1661
1662 void ProjectList::setDocument(KdenliveDoc *doc)
1663 {
1664     m_listView->blockSignals(true);
1665     m_abortAllProxies = true;
1666     m_proxyThreads.waitForFinished();
1667     m_proxyThreads.clearFutures();
1668     m_thumbnailQueue.clear();
1669     m_listView->clear();
1670     m_processingClips.clear();
1671     
1672     m_listView->setSortingEnabled(false);
1673     emit clipSelected(NULL);
1674     m_refreshed = false;
1675     m_allClipsProcessed = false;
1676     m_fps = doc->fps();
1677     m_timecode = doc->timecode();
1678     m_commandStack = doc->commandStack();
1679     m_doc = doc;
1680     m_abortAllProxies = false;
1681
1682     QMap <QString, QString> flist = doc->clipManager()->documentFolderList();
1683     QStringList openedFolders = doc->getExpandedFolders();
1684     QMapIterator<QString, QString> f(flist);
1685     while (f.hasNext()) {
1686         f.next();
1687         FolderProjectItem *folder = new FolderProjectItem(m_listView, QStringList() << f.value(), f.key());
1688         folder->setExpanded(openedFolders.contains(f.key()));
1689     }
1690
1691     QList <DocClipBase*> list = doc->clipManager()->documentClipList();
1692     if (list.isEmpty()) {
1693         // blank document
1694         m_refreshed = true;
1695         m_allClipsProcessed = true;
1696     }
1697     for (int i = 0; i < list.count(); i++)
1698         slotAddClip(list.at(i), false);
1699
1700     m_listView->blockSignals(false);
1701     connect(m_doc->clipManager(), SIGNAL(reloadClip(const QString &)), this, SLOT(slotReloadClip(const QString &)));
1702     connect(m_doc->clipManager(), SIGNAL(modifiedClip(const QString &)), this, SLOT(slotModifiedClip(const QString &)));
1703     connect(m_doc->clipManager(), SIGNAL(missingClip(const QString &)), this, SLOT(slotMissingClip(const QString &)));
1704     connect(m_doc->clipManager(), SIGNAL(availableClip(const QString &)), this, SLOT(slotAvailableClip(const QString &)));
1705     connect(m_doc->clipManager(), SIGNAL(checkAllClips(bool, bool, QStringList)), this, SLOT(updateAllClips(bool, bool, QStringList)));
1706 }
1707
1708 QList <DocClipBase*> ProjectList::documentClipList() const
1709 {
1710     if (m_doc == NULL)
1711         return QList <DocClipBase*> ();
1712
1713     return m_doc->clipManager()->documentClipList();
1714 }
1715
1716 QDomElement ProjectList::producersList()
1717 {
1718     QDomDocument doc;
1719     QDomElement prods = doc.createElement("producerlist");
1720     doc.appendChild(prods);
1721     QTreeWidgetItemIterator it(m_listView);
1722     while (*it) {
1723         if ((*it)->type() != PROJECTCLIPTYPE) {
1724             // subitem
1725             ++it;
1726             continue;
1727         }
1728         prods.appendChild(doc.importNode(((ProjectItem *)(*it))->toXml(), true));
1729         ++it;
1730     }
1731     return prods;
1732 }
1733
1734 void ProjectList::slotCheckForEmptyQueue()
1735 {
1736     if (m_render->processingItems() == 0 && m_thumbnailQueue.isEmpty()) {
1737         if (!m_refreshed && m_allClipsProcessed) {
1738             m_refreshed = true;
1739             m_listView->setEnabled(true);
1740             slotClipSelected();
1741             QTimer::singleShot(500, this, SIGNAL(loadingIsOver()));
1742             emit displayMessage(QString(), -1);
1743         }
1744         updateButtons();
1745     } else if (!m_refreshed) {
1746         QTimer::singleShot(300, this, SLOT(slotCheckForEmptyQueue()));
1747     }
1748 }
1749
1750
1751 void ProjectList::requestClipThumbnail(const QString id)
1752 {
1753     if (!m_thumbnailQueue.contains(id)) m_thumbnailQueue.append(id);
1754     slotProcessNextThumbnail();
1755 }
1756
1757 void ProjectList::resetThumbsProducer(DocClipBase *clip)
1758 {
1759     if (!clip) return;
1760     clip->clearThumbProducer();
1761     QString id = clip->getId();
1762     m_thumbnailQueue.removeAll(id);
1763 }
1764
1765 void ProjectList::slotProcessNextThumbnail()
1766 {
1767     if (m_render->processingItems() > 0) {
1768         return;
1769     }
1770     if (m_thumbnailQueue.isEmpty()) {
1771         slotCheckForEmptyQueue();
1772         return;
1773     }
1774     int max = m_doc->clipManager()->clipsCount();
1775     emit displayMessage(i18n("Loading thumbnails"), (int)(100 *(max - m_thumbnailQueue.count()) / max));
1776     slotRefreshClipThumbnail(m_thumbnailQueue.takeFirst(), false);
1777 }
1778
1779 void ProjectList::slotRefreshClipThumbnail(const QString &clipId, bool update)
1780 {
1781     QTreeWidgetItem *item = getAnyItemById(clipId);
1782     if (item)
1783         slotRefreshClipThumbnail(item, update);
1784     else {
1785         slotProcessNextThumbnail();
1786     }
1787 }
1788
1789 void ProjectList::slotRefreshClipThumbnail(QTreeWidgetItem *it, bool update)
1790 {
1791     if (it == NULL) return;
1792     ProjectItem *item = NULL;
1793     bool isSubItem = false;
1794     int frame;
1795     if (it->type() == PROJECTFOLDERTYPE) return;
1796     if (it->type() == PROJECTSUBCLIPTYPE) {
1797         item = static_cast <ProjectItem *>(it->parent());
1798         frame = static_cast <SubProjectItem *>(it)->zone().x();
1799         isSubItem = true;
1800     } else {
1801         item = static_cast <ProjectItem *>(it);
1802         frame = item->referencedClip()->getClipThumbFrame();
1803     }
1804
1805     if (item) {
1806         DocClipBase *clip = item->referencedClip();
1807         if (!clip) {
1808             slotProcessNextThumbnail();
1809             return;
1810         }
1811         QPixmap pix;
1812         QImage img;
1813         int height = m_listView->iconSize().height();
1814         int swidth = (int)(height  * m_render->frameRenderWidth() / m_render->renderHeight()+ 0.5);
1815         int dwidth = (int)(height  * m_render->dar() + 0.5);
1816         if (clip->clipType() == AUDIO)
1817             pix = KIcon("audio-x-generic").pixmap(QSize(dwidth, height));
1818         else if (clip->clipType() == IMAGE)
1819             img = KThumb::getFrame(item->referencedClip()->getProducer(), 0, swidth, dwidth, height);
1820         else {
1821             img = item->referencedClip()->extractImage(frame, dwidth, height);
1822         }
1823
1824         if (!pix.isNull() || !img.isNull()) {
1825             monitorItemEditing(false);
1826             if (!img.isNull()) {
1827                 pix = QPixmap::fromImage(img);
1828                 processThumbOverlays(item, pix);
1829             }
1830             it->setData(0, Qt::DecorationRole, pix);
1831             monitorItemEditing(true);
1832             
1833             QString hash = item->getClipHash();
1834             if (!hash.isEmpty() && !img.isNull()) {
1835                 if (!isSubItem)
1836                     m_doc->cacheImage(hash, img);
1837                 else
1838                     m_doc->cacheImage(hash + '#' + QString::number(frame), img);
1839             }
1840         }
1841         if (update)
1842             emit projectModified();
1843         slotProcessNextThumbnail();
1844     }
1845 }
1846
1847
1848 void ProjectList::slotReplyGetFileProperties(const QString &clipId, Mlt::Producer *producer, const stringMap &properties, const stringMap &metadata, bool replace)
1849 {
1850     QString toReload;
1851     ProjectItem *item = getItemById(clipId);
1852
1853     int queue = m_render->processingItems();
1854     if (item && producer) {
1855         monitorItemEditing(false);
1856         DocClipBase *clip = item->referencedClip();
1857         if (producer->is_valid()) {
1858             if (clip->isPlaceHolder()) {
1859                 clip->setValid();
1860                 toReload = clipId;
1861             }
1862             item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled);
1863         }
1864         item->setProperties(properties, metadata);
1865         clip->setProducer(producer, replace);
1866
1867         // Proxy stuff
1868         QString size = properties.value("frame_size");
1869         if (!useProxy() && clip->getProperty("proxy").isEmpty()) setProxyStatus(item, NOPROXY);
1870         if (useProxy() && generateProxy() && clip->getProperty("proxy") == "-") setProxyStatus(item, NOPROXY);
1871         else if (useProxy() && !item->hasProxy() && !item->isProxyRunning()) {
1872             // proxy video and image clips
1873             int maxSize;
1874             CLIPTYPE t = item->clipType();
1875             if (t == IMAGE) maxSize = m_doc->getDocumentProperty("proxyimageminsize").toInt();
1876             else maxSize = m_doc->getDocumentProperty("proxyminsize").toInt();
1877             if ((((t == AV || t == VIDEO || t == PLAYLIST) && generateProxy()) || (t == IMAGE && generateImageProxy())) && (size.section('x', 0, 0).toInt() > maxSize || size.section('x', 1, 1).toInt() > maxSize)) {
1878                 if (clip->getProperty("proxy").isEmpty()) {
1879                     KUrl proxyPath = m_doc->projectFolder();
1880                     proxyPath.addPath("proxy/");
1881                     proxyPath.addPath(clip->getClipHash() + "." + (t == IMAGE ? "png" : m_doc->getDocumentProperty("proxyextension")));
1882                     QMap <QString, QString> newProps;
1883                     // insert required duration for proxy
1884                     if (t != IMAGE) newProps.insert("proxy_out", clip->producerProperty("out"));
1885                     newProps.insert("proxy", proxyPath.path());
1886                     QMap <QString, QString> oldProps = clip->properties();
1887                     oldProps.insert("proxy", QString());
1888                     EditClipCommand *command = new EditClipCommand(this, clipId, oldProps, newProps, true);
1889                     m_doc->commandStack()->push(command);
1890                 }
1891             }
1892         }
1893
1894         if (!replace && m_allClipsProcessed && item->data(0, Qt::DecorationRole).isNull()) {
1895             getCachedThumbnail(item);
1896         }
1897         if (!toReload.isEmpty())
1898             item->slotSetToolTip();
1899     } else kDebug() << "////////  COULD NOT FIND CLIP TO UPDATE PRPS...";
1900     if (queue == 0) {
1901         monitorItemEditing(true);
1902         if (item && m_thumbnailQueue.isEmpty()) {
1903             if (!item->hasProxy() || m_render->activeClipId() == item->clipId())
1904                 m_listView->setCurrentItem(item);
1905             bool updatedProfile = false;
1906             if (item->parent()) {
1907                 if (item->parent()->type() == PROJECTFOLDERTYPE)
1908                     static_cast <FolderProjectItem *>(item->parent())->switchIcon();
1909             } else if (KdenliveSettings::checkfirstprojectclip() &&  m_listView->topLevelItemCount() == 1 && m_refreshed && m_allClipsProcessed) {
1910                 // this is the first clip loaded in project, check if we want to adjust project settings to the clip
1911                 updatedProfile = adjustProjectProfileToItem(item);
1912             }
1913             if (updatedProfile == false) {
1914                 //emit clipSelected(item->referencedClip());
1915             }
1916         } else {
1917             int max = m_doc->clipManager()->clipsCount();
1918             if (max > 0) emit displayMessage(i18n("Loading clips"), (int)(100 *(max - queue) / max));
1919         }
1920         if (m_allClipsProcessed) emit processNextThumbnail();
1921     }
1922     if (!item) {
1923         // no item for producer, delete it
1924         delete producer;
1925         return;
1926     }
1927     if (replace) toReload = clipId;
1928     if (!toReload.isEmpty())
1929         emit clipNeedsReload(toReload);
1930 }
1931
1932 bool ProjectList::adjustProjectProfileToItem(ProjectItem *item)
1933 {
1934     if (item == NULL) {
1935         if (m_listView->currentItem() && m_listView->currentItem()->type() != PROJECTFOLDERTYPE)
1936             item = static_cast <ProjectItem*>(m_listView->currentItem());
1937     }
1938     if (item == NULL || item->referencedClip() == NULL) {
1939         KMessageBox::information(kapp->activeWindow(), i18n("Cannot find profile from current clip"));
1940         return false;
1941     }
1942     bool profileUpdated = false;
1943     QString size = item->referencedClip()->getProperty("frame_size");
1944     int width = size.section('x', 0, 0).toInt();
1945     int height = size.section('x', -1).toInt();
1946     double fps = item->referencedClip()->getProperty("fps").toDouble();
1947     double par = item->referencedClip()->getProperty("aspect_ratio").toDouble();
1948     if (item->clipType() == IMAGE || item->clipType() == AV || item->clipType() == VIDEO) {
1949         if (ProfilesDialog::matchProfile(width, height, fps, par, item->clipType() == IMAGE, m_doc->mltProfile()) == false) {
1950             // get a list of compatible profiles
1951             QMap <QString, QString> suggestedProfiles = ProfilesDialog::getProfilesFromProperties(width, height, fps, par, item->clipType() == IMAGE);
1952             if (!suggestedProfiles.isEmpty()) {
1953                 KDialog *dialog = new KDialog(this);
1954                 dialog->setCaption(i18n("Change project profile"));
1955                 dialog->setButtons(KDialog::Ok | KDialog::Cancel);
1956
1957                 QWidget container;
1958                 QVBoxLayout *l = new QVBoxLayout;
1959                 QLabel *label = new QLabel(i18n("Your clip does not match current project's profile.\nDo you want to change the project profile?\n\nThe following profiles match the clip (size: %1, fps: %2)", size, fps));
1960                 l->addWidget(label);
1961                 QListWidget *list = new QListWidget;
1962                 list->setAlternatingRowColors(true);
1963                 QMapIterator<QString, QString> i(suggestedProfiles);
1964                 while (i.hasNext()) {
1965                     i.next();
1966                     QListWidgetItem *item = new QListWidgetItem(i.value(), list);
1967                     item->setData(Qt::UserRole, i.key());
1968                     item->setToolTip(i.key());
1969                 }
1970                 list->setCurrentRow(0);
1971                 l->addWidget(list);
1972                 container.setLayout(l);
1973                 dialog->setButtonText(KDialog::Ok, i18n("Update profile"));
1974                 dialog->setMainWidget(&container);
1975                 if (dialog->exec() == QDialog::Accepted) {
1976                     //Change project profile
1977                     profileUpdated = true;
1978                     if (list->currentItem())
1979                         emit updateProfile(list->currentItem()->data(Qt::UserRole).toString());
1980                 }
1981                 delete list;
1982                 delete label;
1983             } else if (fps > 0) {
1984                 KMessageBox::information(kapp->activeWindow(), i18n("Your clip does not match current project's profile.\nNo existing profile found to match the clip's properties.\nClip size: %1\nFps: %2\n", size, fps));
1985             }
1986         }
1987     }
1988     return profileUpdated;
1989 }
1990
1991 QString ProjectList::getDocumentProperty(const QString &key) const
1992 {
1993     return m_doc->getDocumentProperty(key);
1994 }
1995
1996 bool ProjectList::useProxy() const
1997 {
1998     return m_doc->getDocumentProperty("enableproxy").toInt();
1999 }
2000
2001 bool ProjectList::generateProxy() const
2002 {
2003     return m_doc->getDocumentProperty("generateproxy").toInt();
2004 }
2005
2006 bool ProjectList::generateImageProxy() const
2007 {
2008     return m_doc->getDocumentProperty("generateimageproxy").toInt();
2009 }
2010
2011 void ProjectList::slotReplyGetImage(const QString &clipId, const QImage &img)
2012 {
2013     ProjectItem *item = getItemById(clipId);
2014     if (item && !img.isNull()) {
2015         QPixmap pix = QPixmap::fromImage(img);
2016         processThumbOverlays(item, pix);
2017         monitorItemEditing(false);
2018         item->setData(0, Qt::DecorationRole, pix);
2019         monitorItemEditing(true);
2020         QString hash = item->getClipHash();
2021         if (!hash.isEmpty()) m_doc->cacheImage(hash, img);
2022     }
2023 }
2024
2025 void ProjectList::slotReplyGetImage(const QString &clipId, const QString &name, int width, int height)
2026 {
2027     // For clips that have a generic icon (like audio clips...)
2028     ProjectItem *item = getItemById(clipId);
2029     QPixmap pix =  KIcon(name).pixmap(QSize(width, height));
2030     if (item && !pix.isNull()) {
2031         monitorItemEditing(false);
2032         item->setData(0, Qt::DecorationRole, pix);
2033         monitorItemEditing(true);
2034     }
2035 }
2036
2037 QTreeWidgetItem *ProjectList::getAnyItemById(const QString &id)
2038 {
2039     QTreeWidgetItemIterator it(m_listView);
2040     QString lookId = id;
2041     if (id.contains('#'))
2042         lookId = id.section('#', 0, 0);
2043
2044     ProjectItem *result = NULL;
2045     while (*it) {
2046         if ((*it)->type() != PROJECTCLIPTYPE) {
2047             // subitem
2048             ++it;
2049             continue;
2050         }
2051         ProjectItem *item = static_cast<ProjectItem *>(*it);
2052         if (item->clipId() == lookId) {
2053             result = item;
2054             break;
2055         }
2056         ++it;
2057     }
2058     if (result == NULL || !id.contains('#')) {
2059         return result;
2060     } else {
2061         for (int i = 0; i < result->childCount(); i++) {
2062             SubProjectItem *sub = static_cast <SubProjectItem *>(result->child(i));
2063             if (sub && sub->zone().x() == id.section('#', 1, 1).toInt())
2064                 return sub;
2065         }
2066     }
2067
2068     return NULL;
2069 }
2070
2071
2072 ProjectItem *ProjectList::getItemById(const QString &id)
2073 {
2074     ProjectItem *item;
2075     QTreeWidgetItemIterator it(m_listView);
2076     while (*it) {
2077         if ((*it)->type() != PROJECTCLIPTYPE) {
2078             // subitem or folder
2079             ++it;
2080             continue;
2081         }
2082         item = static_cast<ProjectItem *>(*it);
2083         if (item->clipId() == id)
2084             return item;
2085         ++it;
2086     }
2087     return NULL;
2088 }
2089
2090 FolderProjectItem *ProjectList::getFolderItemById(const QString &id)
2091 {
2092     FolderProjectItem *item;
2093     QTreeWidgetItemIterator it(m_listView);
2094     while (*it) {
2095         if ((*it)->type() == PROJECTFOLDERTYPE) {
2096             item = static_cast<FolderProjectItem *>(*it);
2097             if (item->clipId() == id)
2098                 return item;
2099         }
2100         ++it;
2101     }
2102     return NULL;
2103 }
2104
2105 void ProjectList::slotSelectClip(const QString &ix)
2106 {
2107     ProjectItem *clip = getItemById(ix);
2108     if (clip) {
2109         m_listView->setCurrentItem(clip);
2110         m_listView->scrollToItem(clip);
2111         m_editButton->defaultAction()->setEnabled(true);
2112         m_deleteButton->defaultAction()->setEnabled(true);
2113         m_reloadAction->setEnabled(true);
2114         m_transcodeAction->setEnabled(true);
2115         m_stabilizeAction->setEnabled(true);
2116         if (clip->clipType() == IMAGE && !KdenliveSettings::defaultimageapp().isEmpty()) {
2117             m_openAction->setIcon(KIcon(KdenliveSettings::defaultimageapp()));
2118             m_openAction->setEnabled(true);
2119         } else if (clip->clipType() == AUDIO && !KdenliveSettings::defaultaudioapp().isEmpty()) {
2120             m_openAction->setIcon(KIcon(KdenliveSettings::defaultaudioapp()));
2121             m_openAction->setEnabled(true);
2122         } else {
2123             m_openAction->setEnabled(false);
2124         }
2125     }
2126 }
2127
2128 QString ProjectList::currentClipUrl() const
2129 {
2130     ProjectItem *item;
2131     if (!m_listView->currentItem() || m_listView->currentItem()->type() == PROJECTFOLDERTYPE) return QString();
2132     if (m_listView->currentItem()->type() == PROJECTSUBCLIPTYPE) {
2133         // subitem
2134         item = static_cast <ProjectItem*>(m_listView->currentItem()->parent());
2135     } else {
2136         item = static_cast <ProjectItem*>(m_listView->currentItem());
2137     }
2138     if (item == NULL)
2139         return QString();
2140     return item->clipUrl().path();
2141 }
2142
2143 KUrl::List ProjectList::getConditionalUrls(const QString &condition) const
2144 {
2145     KUrl::List result;
2146     ProjectItem *item;
2147     QList<QTreeWidgetItem *> list = m_listView->selectedItems();
2148     for (int i = 0; i < list.count(); i++) {
2149         if (list.at(i)->type() == PROJECTFOLDERTYPE)
2150             continue;
2151         if (list.at(i)->type() == PROJECTSUBCLIPTYPE) {
2152             // subitem
2153             item = static_cast <ProjectItem*>(list.at(i)->parent());
2154         } else {
2155             item = static_cast <ProjectItem*>(list.at(i));
2156         }
2157         if (item == NULL || item->type() == COLOR || item->type() == SLIDESHOW || item->type() == TEXT)
2158             continue;
2159         DocClipBase *clip = item->referencedClip();
2160         if (!condition.isEmpty()) {
2161             if (condition.startsWith("vcodec") && !clip->hasVideoCodec(condition.section('=', 1, 1)))
2162                 continue;
2163             else if (condition.startsWith("acodec") && !clip->hasAudioCodec(condition.section('=', 1, 1)))
2164                 continue;
2165         }
2166         result.append(item->clipUrl());
2167     }
2168     return result;
2169 }
2170
2171 void ProjectList::regenerateTemplate(const QString &id)
2172 {
2173     ProjectItem *clip = getItemById(id);
2174     if (clip)
2175         regenerateTemplate(clip);
2176 }
2177
2178 void ProjectList::regenerateTemplate(ProjectItem *clip)
2179 {
2180     //TODO: remove this unused method, only force_reload is necessary
2181     clip->referencedClip()->getProducer()->set("force_reload", 1);
2182 }
2183
2184 QDomDocument ProjectList::generateTemplateXml(QString path, const QString &replaceString)
2185 {
2186     QDomDocument doc;
2187     QFile file(path);
2188     if (!file.open(QIODevice::ReadOnly)) {
2189         kWarning() << "ERROR, CANNOT READ: " << path;
2190         return doc;
2191     }
2192     if (!doc.setContent(&file)) {
2193         kWarning() << "ERROR, CANNOT READ: " << path;
2194         file.close();
2195         return doc;
2196     }
2197     file.close();
2198     QDomNodeList texts = doc.elementsByTagName("content");
2199     for (int i = 0; i < texts.count(); i++) {
2200         QString data = texts.item(i).firstChild().nodeValue();
2201         data.replace("%s", replaceString);
2202         texts.item(i).firstChild().setNodeValue(data);
2203     }
2204     return doc;
2205 }
2206
2207
2208 void ProjectList::slotAddClipCut(const QString &id, int in, int out)
2209 {
2210     ProjectItem *clip = getItemById(id);
2211     if (clip == NULL || clip->referencedClip()->hasCutZone(QPoint(in, out)))
2212         return;
2213     AddClipCutCommand *command = new AddClipCutCommand(this, id, in, out, QString(), true, false);
2214     m_commandStack->push(command);
2215 }
2216
2217 void ProjectList::addClipCut(const QString &id, int in, int out, const QString desc, bool newItem)
2218 {
2219     ProjectItem *clip = getItemById(id);
2220     if (clip) {
2221         DocClipBase *base = clip->referencedClip();
2222         base->addCutZone(in, out);
2223         monitorItemEditing(false);
2224         SubProjectItem *sub = new SubProjectItem(clip, in, out, desc);
2225         if (newItem && desc.isEmpty() && !m_listView->isColumnHidden(1)) {
2226             if (!clip->isExpanded())
2227                 clip->setExpanded(true);
2228             m_listView->scrollToItem(sub);
2229             m_listView->editItem(sub, 1);
2230         }
2231         QImage img = clip->referencedClip()->extractImage(in, (int)(sub->sizeHint(0).height()  * m_render->dar()), sub->sizeHint(0).height() - 2);
2232         sub->setData(0, Qt::DecorationRole, QPixmap::fromImage(img));
2233         QString hash = clip->getClipHash();
2234         if (!hash.isEmpty()) m_doc->cacheImage(hash + '#' + QString::number(in), img);
2235         monitorItemEditing(true);
2236     }
2237     emit projectModified();
2238 }
2239
2240 void ProjectList::removeClipCut(const QString &id, int in, int out)
2241 {
2242     ProjectItem *clip = getItemById(id);
2243     if (clip) {
2244         DocClipBase *base = clip->referencedClip();
2245         base->removeCutZone(in, out);
2246         SubProjectItem *sub = getSubItem(clip, QPoint(in, out));
2247         if (sub) {
2248             monitorItemEditing(false);
2249             delete sub;
2250             monitorItemEditing(true);
2251         }
2252     }
2253     emit projectModified();
2254 }
2255
2256 SubProjectItem *ProjectList::getSubItem(ProjectItem *clip, QPoint zone)
2257 {
2258     SubProjectItem *sub = NULL;
2259     if (clip) {
2260         for (int i = 0; i < clip->childCount(); i++) {
2261             QTreeWidgetItem *it = clip->child(i);
2262             if (it->type() == PROJECTSUBCLIPTYPE) {
2263                 sub = static_cast <SubProjectItem*>(it);
2264                 if (sub->zone() == zone)
2265                     break;
2266                 else
2267                     sub = NULL;
2268             }
2269         }
2270     }
2271     return sub;
2272 }
2273
2274 void ProjectList::slotUpdateClipCut(QPoint p)
2275 {
2276     if (!m_listView->currentItem() || m_listView->currentItem()->type() != PROJECTSUBCLIPTYPE)
2277         return;
2278     SubProjectItem *sub = static_cast <SubProjectItem*>(m_listView->currentItem());
2279     ProjectItem *item = static_cast <ProjectItem *>(sub->parent());
2280     EditClipCutCommand *command = new EditClipCutCommand(this, item->clipId(), sub->zone(), p, sub->text(1), sub->text(1), true);
2281     m_commandStack->push(command);
2282 }
2283
2284 void ProjectList::doUpdateClipCut(const QString &id, const QPoint oldzone, const QPoint zone, const QString &comment)
2285 {
2286     ProjectItem *clip = getItemById(id);
2287     SubProjectItem *sub = getSubItem(clip, oldzone);
2288     if (sub == NULL || clip == NULL)
2289         return;
2290     DocClipBase *base = clip->referencedClip();
2291     base->updateCutZone(oldzone.x(), oldzone.y(), zone.x(), zone.y(), comment);
2292     monitorItemEditing(false);
2293     sub->setZone(zone);
2294     sub->setDescription(comment);
2295     monitorItemEditing(true);
2296     emit projectModified();
2297 }
2298
2299 void ProjectList::slotForceProcessing(const QString &id)
2300 {
2301     m_render->forceProcessing(id);
2302 }
2303
2304 void ProjectList::slotAddOrUpdateSequence(const QString frameName)
2305 {
2306     QString fileName = KUrl(frameName).fileName().section('_', 0, -2);
2307     QStringList list;
2308     QString pattern = SlideshowClip::selectedPath(frameName, false, QString(), &list);
2309     int count = list.count();
2310     if (count > 1) {
2311         const QList <DocClipBase *> existing = m_doc->clipManager()->getClipByResource(pattern);
2312         if (!existing.isEmpty()) {
2313             // Sequence already exists, update
2314             QString id = existing.at(0)->getId();
2315             //ProjectItem *item = getItemById(id);
2316             QMap <QString, QString> oldprops;
2317             QMap <QString, QString> newprops;
2318             int ttl = existing.at(0)->getProperty("ttl").toInt();
2319             oldprops["out"] = existing.at(0)->getProperty("out");
2320             newprops["out"] = QString::number(ttl * count - 1);
2321             slotUpdateClipProperties(id, newprops);
2322             EditClipCommand *command = new EditClipCommand(this, id, oldprops, newprops, false);
2323             m_commandStack->push(command);
2324         } else {
2325             // Create sequence
2326             QStringList groupInfo = getGroup();
2327             m_doc->slotCreateSlideshowClipFile(fileName, pattern, count, m_timecode.reformatSeparators(KdenliveSettings::sequence_duration()),
2328                                                false, false, false,
2329                                                m_timecode.getTimecodeFromFrames(int(ceil(m_timecode.fps()))), QString(), 0,
2330                                                QString(), groupInfo.at(0), groupInfo.at(1));
2331         }
2332     } else emit displayMessage(i18n("Sequence not found"), -2);
2333 }
2334
2335 QMap <QString, QString> ProjectList::getProxies()
2336 {
2337     QMap <QString, QString> list;
2338     ProjectItem *item;
2339     QTreeWidgetItemIterator it(m_listView);
2340     while (*it) {
2341         if ((*it)->type() != PROJECTCLIPTYPE) {
2342             ++it;
2343             continue;
2344         }
2345         item = static_cast<ProjectItem *>(*it);
2346         if (item && item->referencedClip() != NULL) {
2347             if (item->hasProxy()) {
2348                 QString proxy = item->referencedClip()->getProperty("proxy");
2349                 list.insert(proxy, item->clipUrl().path());
2350             }
2351         }
2352         ++it;
2353     }
2354     return list;
2355 }
2356
2357 void ProjectList::slotCreateProxy(const QString id)
2358 {
2359     ProjectItem *item = getItemById(id);
2360     if (!item || item->isProxyRunning() || item->referencedClip()->isPlaceHolder()) return;
2361     QString path = item->referencedClip()->getProperty("proxy");
2362     if (path.isEmpty()) {
2363         setProxyStatus(item, PROXYCRASHED);
2364         return;
2365     }
2366     setProxyStatus(item, PROXYWAITING);
2367     if (m_abortProxy.contains(path)) m_abortProxy.removeAll(path);
2368     if (m_processingProxy.contains(path)) {
2369         // Proxy is already being generated
2370         return;
2371     }
2372     if (QFileInfo(path).size() > 0) {
2373         // Proxy already created
2374         setProxyStatus(item, PROXYDONE);
2375         slotGotProxy(path);
2376         return;
2377     }
2378     m_processingProxy.append(path);
2379
2380     PROXYINFO info;
2381     info.dest = path;
2382     info.src = item->clipUrl().path();
2383     info.type = item->clipType();
2384     info.exif = QString(item->referencedClip()->producerProperty("_exif_orientation")).toInt();
2385     m_proxyList.append(info);
2386     if (!m_proxyThreads.futures().isEmpty()) {
2387         // Remove inactive threads
2388         QList <QFuture<void> > futures = m_proxyThreads.futures();
2389         m_proxyThreads.clearFutures();
2390         for (int i = 0; i < futures.count(); i++)
2391             if (!futures.at(i).isFinished()) {
2392                 m_proxyThreads.addFuture(futures.at(i));
2393             }
2394     }
2395     if (m_proxyThreads.futures().isEmpty() || m_proxyThreads.futures().count() < KdenliveSettings::proxythreads()) m_proxyThreads.addFuture(QtConcurrent::run(this, &ProjectList::slotGenerateProxy));
2396 }
2397
2398 void ProjectList::slotAbortProxy(const QString id, const QString path)
2399 {
2400     QTreeWidgetItemIterator it(m_listView);
2401     ProjectItem *item = getItemById(id);
2402     if (!path.isEmpty() && m_processingProxy.contains(path)) {
2403         m_abortProxy << path;
2404         setProxyStatus(item, NOPROXY);
2405     }
2406     else {
2407         setProxyStatus(item, NOPROXY);
2408         slotGotProxy(item);
2409     }
2410 }
2411
2412 void ProjectList::slotGenerateProxy()
2413 {
2414     while (!m_proxyList.isEmpty() && !m_abortAllProxies) {
2415         emit projectModified();
2416         PROXYINFO info = m_proxyList.takeFirst();
2417         if (m_abortProxy.contains(info.dest)) {
2418             m_abortProxy.removeAll(info.dest);
2419             continue;
2420         }
2421         // Get the list of clips that will need to get progress info
2422         QTreeWidgetItemIterator it(m_listView);
2423         QList <ProjectItem *> processingItems;
2424         while (*it && !m_abortAllProxies) {
2425             if ((*it)->type() == PROJECTCLIPTYPE) {
2426                 ProjectItem *item = static_cast <ProjectItem *>(*it);
2427                 if (item->referencedClip()->getProperty("proxy") == info.dest) {
2428                     processingItems.append(item);
2429                 }
2430             }
2431             ++it;
2432         }
2433
2434         // Make sure proxy path is writable
2435         QFile file(info.dest);
2436         if (!file.open(QIODevice::WriteOnly)) {
2437             for (int i = 0; i < processingItems.count(); i++)
2438                 setProxyStatus(processingItems.at(i), PROXYCRASHED);
2439             m_processingProxy.removeAll(info.dest);
2440             continue;
2441         }
2442         file.close();
2443         QFile::remove(info.dest);
2444     
2445         for (int i = 0; i < processingItems.count(); i++)
2446             setProxyStatus(processingItems.at(i), CREATINGPROXY);
2447
2448         // Special case: playlist clips (.mlt or .kdenlive project files)
2449         if (info.type == PLAYLIST) {
2450             // change FFmpeg params to MLT format
2451             QStringList parameters;
2452             parameters << info.src;
2453             parameters << "-consumer" << "avformat:" + info.dest;
2454             QStringList params = m_doc->getDocumentProperty("proxyparams").simplified().split('-', QString::SkipEmptyParts);
2455         
2456             foreach(QString s, params) {
2457                 s = s.simplified();
2458                 if (s.count(' ') == 0) {
2459                     s.append("=1");
2460                 }
2461                 else s.replace(' ', '=');
2462                 parameters << s;
2463             }
2464         
2465             parameters.append(QString("real_time=-%1").arg(KdenliveSettings::mltthreads()));
2466
2467             //TODO: currently, when rendering an xml file through melt, the display ration is lost, so we enforce it manualy
2468             double display_ratio = KdenliveDoc::getDisplayRatio(info.src);
2469             parameters << "aspect=" + QString::number(display_ratio);
2470
2471             //kDebug()<<"TRANSCOD: "<<parameters;
2472             QProcess myProcess;
2473             myProcess.setProcessChannelMode(QProcess::MergedChannels);
2474             myProcess.start(KdenliveSettings::rendererpath(), parameters);
2475             myProcess.waitForStarted();
2476             int result = -1;
2477             int duration = 0;
2478             while (myProcess.state() != QProcess::NotRunning) {
2479                 // building proxy file
2480                 if (m_abortProxy.contains(info.dest) || m_abortAllProxies) {
2481                     myProcess.close();
2482                     myProcess.waitForFinished();
2483                     QFile::remove(info.dest);
2484                     m_abortProxy.removeAll(info.dest);
2485                     m_processingProxy.removeAll(info.dest);
2486                     for (int i = 0; i < processingItems.count(); i++)
2487                         setProxyStatus(processingItems.at(i), NOPROXY);
2488                     result = -2;
2489                 }
2490                 else {
2491                     QString log = QString(myProcess.readAll());
2492                     processLogInfo(processingItems, &duration, log);
2493                 }
2494                 myProcess.waitForFinished(500);
2495             }
2496             myProcess.waitForFinished();
2497             m_processingProxy.removeAll(info.dest);
2498             if (result == -1) result = myProcess.exitStatus();
2499             if (result == 0) {
2500                 // proxy successfully created
2501                 for (int i = 0; i < processingItems.count(); i++)
2502                     setProxyStatus(processingItems.at(i), PROXYDONE);
2503                 slotGotProxy(info.dest);
2504             }
2505             else if (result == 1) {
2506                 // Proxy process crashed
2507                 QFile::remove(info.dest);
2508                 for (int i = 0; i < processingItems.count(); i++)
2509                     setProxyStatus(processingItems.at(i), PROXYCRASHED);
2510             }
2511             continue;
2512         }
2513     
2514         if (info.type == IMAGE) {
2515             // Image proxy
2516             QImage i(info.src);
2517             if (i.isNull()) {
2518                 // Cannot load image
2519                 for (int i = 0; i < processingItems.count(); i++)
2520                     setProxyStatus(processingItems.at(i), PROXYCRASHED);
2521                 continue;
2522             }
2523             QImage proxy;
2524             // Images are scaled to profile size. 
2525             //TODO: Make it be configurable?
2526             if (i.width() > i.height()) proxy = i.scaledToWidth(m_render->frameRenderWidth());
2527             else proxy = i.scaledToHeight(m_render->renderHeight());
2528             if (info.exif > 1) {
2529                 // Rotate image according to exif data
2530                 QImage processed;
2531                 QMatrix matrix;
2532
2533                 switch ( info.exif ) {
2534                     case 2:
2535                         matrix.scale( -1, 1 );
2536                         break;
2537                     case 3:
2538                         matrix.rotate( 180 );
2539                         break;
2540                     case 4:
2541                         matrix.scale( 1, -1 );
2542                         break;
2543                     case 5:
2544                         matrix.rotate( 270 );
2545                         matrix.scale( -1, 1 );
2546                         break;
2547                     case 6:
2548                         matrix.rotate( 90 );
2549                         break;
2550                     case 7:
2551                         matrix.rotate( 90 );
2552                         matrix.scale( -1, 1 );
2553                         break;
2554                     case 8:
2555                         matrix.rotate( 270 );
2556                         break;
2557                 }
2558                 processed = proxy.transformed( matrix );
2559                 processed.save(info.dest);
2560             }
2561             else proxy.save(info.dest);
2562             for (int i = 0; i < processingItems.count(); i++)
2563                 setProxyStatus(processingItems.at(i), PROXYDONE);
2564             slotGotProxy(info.dest);
2565             m_abortProxy.removeAll(info.dest);
2566             m_processingProxy.removeAll(info.dest);
2567             continue;
2568         }
2569
2570         QStringList parameters;
2571         parameters << "-i" << info.src;
2572         QString params = m_doc->getDocumentProperty("proxyparams").simplified();
2573         foreach(QString s, params.split(' '))
2574         parameters << s;
2575
2576         // Make sure we don't block when proxy file already exists
2577         parameters << "-y";
2578         parameters << info.dest;
2579         QProcess myProcess;
2580         myProcess.setProcessChannelMode(QProcess::MergedChannels);
2581         myProcess.start("ffmpeg", parameters);
2582         myProcess.waitForStarted();
2583         int result = -1;
2584         int duration = 0;
2585    
2586         while (myProcess.state() != QProcess::NotRunning) {
2587             // building proxy file
2588             if (m_abortProxy.contains(info.dest) || m_abortAllProxies) {
2589                 myProcess.close();
2590                 myProcess.waitForFinished();
2591                 m_abortProxy.removeAll(info.dest);
2592                 m_processingProxy.removeAll(info.dest);
2593                 QFile::remove(info.dest);
2594                 if (!m_abortAllProxies) {
2595                     for (int i = 0; i < processingItems.count(); i++)
2596                         setProxyStatus(processingItems.at(i), NOPROXY);
2597                 }
2598                 else continue;
2599                 result = -2;
2600             }
2601             else {
2602                 QString log = QString(myProcess.readAll());
2603                 processLogInfo(processingItems, &duration, log);
2604             }
2605             myProcess.waitForFinished(500);
2606         }
2607         myProcess.waitForFinished();
2608         m_abortProxy.removeAll(info.dest);
2609         m_processingProxy.removeAll(info.dest);
2610         if (result == -1) {
2611             result = myProcess.exitStatus();
2612         }
2613         
2614         // FFmpeg process terminated normally, but make sure proxy clip exists
2615         if (result != -2 && QFileInfo(info.dest).size() == 0) {
2616             result = QProcess::CrashExit;
2617         }
2618
2619         if (result == QProcess::NormalExit) {
2620             // proxy successfully created
2621             for (int i = 0; i < processingItems.count(); i++)
2622                 setProxyStatus(processingItems.at(i), PROXYDONE);
2623             slotGotProxy(info.dest);
2624         }
2625         else if (result == QProcess::CrashExit) {
2626             // Proxy process crashed
2627             QFile::remove(info.dest);
2628             for (int i = 0; i < processingItems.count(); i++)
2629                 setProxyStatus(processingItems.at(i), PROXYCRASHED);
2630         }
2631     }
2632 }
2633
2634
2635 void ProjectList::processLogInfo(QList <ProjectItem *>items, int *duration, const QString &log)
2636 {
2637     int progress;
2638     if (*duration == 0) {
2639         if (log.contains("Duration:")) {
2640             QString data = log.section("Duration:", 1, 1).section(',', 0, 0).simplified();
2641             QStringList numbers = data.split(':');
2642             *duration = (int) (numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble());
2643         }
2644     }
2645     else if (log.contains("time=")) {
2646         QString time = log.section("time=", 1, 1).simplified().section(' ', 0, 0);
2647         if (time.contains(':')) {
2648             QStringList numbers = time.split(':');
2649             progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble();
2650         }
2651         else progress = (int) time.toDouble();
2652         for (int i = 0; i < items.count(); i++)
2653             setProxyStatus(items.at(i), CREATINGPROXY, (int) (100.0 * progress / (*duration)));
2654     }
2655 }
2656
2657 void ProjectList::updateProxyConfig()
2658 {
2659     ProjectItem *item;
2660     QTreeWidgetItemIterator it(m_listView);
2661     QUndoCommand *command = new QUndoCommand();
2662     command->setText(i18n("Update proxy settings"));
2663     QString proxydir = m_doc->projectFolder().path( KUrl::AddTrailingSlash) + "proxy/";
2664     while (*it) {
2665         if ((*it)->type() != PROJECTCLIPTYPE) {
2666             ++it;
2667             continue;
2668         }
2669         item = static_cast<ProjectItem *>(*it);
2670         if (item == NULL) {
2671             ++it;
2672             continue;
2673         }
2674         CLIPTYPE t = item->clipType();
2675         if ((t == VIDEO || t == AV || t == UNKNOWN) && item->referencedClip() != NULL) {
2676             if  (generateProxy() && useProxy() && !item->isProxyRunning()) {
2677                 DocClipBase *clip = item->referencedClip();
2678                 if (clip->getProperty("frame_size").section('x', 0, 0).toInt() > m_doc->getDocumentProperty("proxyminsize").toInt()) {
2679                     if (clip->getProperty("proxy").isEmpty()) {
2680                         // We need to insert empty proxy in old properties so that undo will work
2681                         QMap <QString, QString> oldProps;// = clip->properties();
2682                         oldProps.insert("proxy", QString());
2683                         QMap <QString, QString> newProps;
2684                         newProps.insert("proxy", proxydir + item->referencedClip()->getClipHash() + "." + m_doc->getDocumentProperty("proxyextension"));
2685                         new EditClipCommand(this, clip->getId(), oldProps, newProps, true, command);
2686                     }
2687                 }
2688             }
2689             else if (item->hasProxy()) {
2690                 // remove proxy
2691                 QMap <QString, QString> newProps;
2692                 newProps.insert("proxy", QString());
2693                 newProps.insert("replace", "1");
2694                 // insert required duration for proxy
2695                 newProps.insert("proxy_out", item->referencedClip()->producerProperty("out"));
2696                 new EditClipCommand(this, item->clipId(), item->referencedClip()->currentProperties(newProps), newProps, true, command);
2697             }
2698         }
2699         else if (t == IMAGE && item->referencedClip() != NULL) {
2700             if  (generateImageProxy() && useProxy()) {
2701                 DocClipBase *clip = item->referencedClip();
2702                 int maxImageSize = m_doc->getDocumentProperty("proxyimageminsize").toInt();
2703                 if (clip->getProperty("frame_size").section('x', 0, 0).toInt() > maxImageSize || clip->getProperty("frame_size").section('x', 1, 1).toInt() > maxImageSize) {
2704                     if (clip->getProperty("proxy").isEmpty()) {
2705                         // We need to insert empty proxy in old properties so that undo will work
2706                         QMap <QString, QString> oldProps = clip->properties();
2707                         oldProps.insert("proxy", QString());
2708                         QMap <QString, QString> newProps;
2709                         newProps.insert("proxy", proxydir + item->referencedClip()->getClipHash() + ".png");
2710                         new EditClipCommand(this, clip->getId(), oldProps, newProps, true, command);
2711                     }
2712                 }
2713             }
2714             else if (item->hasProxy()) {
2715                 // remove proxy
2716                 QMap <QString, QString> newProps;
2717                 newProps.insert("proxy", QString());
2718                 newProps.insert("replace", "1");
2719                 new EditClipCommand(this, item->clipId(), item->referencedClip()->properties(), newProps, true, command);
2720             }
2721         }
2722         ++it;
2723     }
2724     if (command->childCount() > 0) m_doc->commandStack()->push(command);
2725     else delete command;
2726 }
2727
2728 void ProjectList::slotProxyCurrentItem(bool doProxy, ProjectItem *itemToProxy)
2729 {
2730     QList<QTreeWidgetItem *> list;
2731     if (itemToProxy == NULL) list = m_listView->selectedItems();
2732     else list << itemToProxy;
2733
2734     // expand list (folders, subclips) to get real clips
2735     QTreeWidgetItem *listItem;
2736     QList<ProjectItem *> clipList;
2737     for (int i = 0; i < list.count(); i++) {
2738         listItem = list.at(i);
2739         if (listItem->type() == PROJECTFOLDERTYPE) {
2740             for (int j = 0; j < listItem->childCount(); j++) {
2741                 QTreeWidgetItem *sub = listItem->child(j);
2742                 if (sub->type() == PROJECTCLIPTYPE) {
2743                     ProjectItem *item = static_cast <ProjectItem*>(sub);
2744                     if (!clipList.contains(item)) clipList.append(item);
2745                 }
2746             }
2747         }
2748         else if (listItem->type() == PROJECTSUBCLIPTYPE) {
2749             QTreeWidgetItem *sub = listItem->parent();
2750             ProjectItem *item = static_cast <ProjectItem*>(sub);
2751             if (!clipList.contains(item)) clipList.append(item);
2752         }
2753         else if (listItem->type() == PROJECTCLIPTYPE) {
2754             ProjectItem *item = static_cast <ProjectItem*>(listItem);
2755             if (!clipList.contains(item)) clipList.append(item);
2756         }
2757     }
2758     
2759     QUndoCommand *command = new QUndoCommand();
2760     if (doProxy) command->setText(i18np("Add proxy clip", "Add proxy clips", clipList.count()));
2761     else command->setText(i18np("Remove proxy clip", "Remove proxy clips", clipList.count()));
2762     
2763     // Make sure the proxy folder exists
2764     QString proxydir = m_doc->projectFolder().path( KUrl::AddTrailingSlash) + "proxy/";
2765     KStandardDirs::makeDir(proxydir);
2766                 
2767     QMap <QString, QString> newProps;
2768     QMap <QString, QString> oldProps;
2769     if (!doProxy) newProps.insert("proxy", "-");
2770     for (int i = 0; i < clipList.count(); i++) {
2771         ProjectItem *item = clipList.at(i);
2772         CLIPTYPE t = item->clipType();
2773         if ((t == VIDEO || t == AV || t == UNKNOWN || t == IMAGE || t == PLAYLIST) && item->referencedClip()) {
2774             if ((doProxy && item->hasProxy()) || (!doProxy && !item->hasProxy() && item->referencedClip()->getProducer() != NULL)) continue;
2775             DocClipBase *clip = item->referencedClip();
2776             if (!clip || !clip->isClean() || m_render->isProcessing(item->clipId())) {
2777                 kDebug()<<"//// TRYING TO PROXY: "<<item->clipId()<<", but it is busy";
2778                 continue;
2779             }
2780                 
2781             oldProps = clip->properties();
2782             if (doProxy) {
2783                 newProps.clear();
2784                 QString path = proxydir + clip->getClipHash() + "." + (t == IMAGE ? "png" : m_doc->getDocumentProperty("proxyextension"));
2785                 // insert required duration for proxy
2786                 newProps.insert("proxy_out", clip->producerProperty("out"));
2787                 newProps.insert("proxy", path);
2788                 // We need to insert empty proxy so that undo will work
2789                 oldProps.insert("proxy", QString());
2790             }
2791             else if (item->referencedClip()->getProducer() == NULL) {
2792                 // Force clip reload
2793                 kDebug()<<"// CLIP HAD NULL PROD------------";
2794                 newProps.insert("resource", item->referencedClip()->getProperty("resource"));
2795             }
2796             new EditClipCommand(this, item->clipId(), oldProps, newProps, true, command);
2797         }
2798     }
2799     if (command->childCount() > 0) {
2800         m_doc->commandStack()->push(command);
2801     }
2802     else delete command;
2803 }
2804
2805
2806 void ProjectList::slotDeleteProxy(const QString proxyPath)
2807 {
2808     if (proxyPath.isEmpty()) return;
2809     QUndoCommand *proxyCommand = new QUndoCommand();
2810     proxyCommand->setText(i18n("Remove Proxy"));
2811     QTreeWidgetItemIterator it(m_listView);
2812     ProjectItem *item;
2813     while (*it) {
2814         if ((*it)->type() == PROJECTCLIPTYPE) {
2815             item = static_cast <ProjectItem *>(*it);
2816             if (item->referencedClip()->getProperty("proxy") == proxyPath) {
2817                 QMap <QString, QString> props;
2818                 props.insert("proxy", QString());
2819                 new EditClipCommand(this, item->clipId(), item->referencedClip()->currentProperties(props), props, true, proxyCommand);
2820             
2821             }
2822         }
2823         ++it;
2824     }
2825     if (proxyCommand->childCount() == 0)
2826         delete proxyCommand;
2827     else
2828         m_commandStack->push(proxyCommand);
2829     QFile::remove(proxyPath);
2830 }
2831
2832 void ProjectList::setProxyStatus(ProjectItem *item, PROXYSTATUS status, int progress)
2833 {
2834     if (item == NULL || m_abortAllProxies) return;
2835     monitorItemEditing(false);
2836     item->setProxyStatus(status, progress);
2837     if (status == PROXYCRASHED) {
2838         DocClipBase *clip = item->referencedClip();
2839         if (!clip) {
2840             kDebug()<<"// PROXY CRASHED";
2841         }
2842         else if (clip->getProducer() == NULL && !clip->isPlaceHolder()) {
2843             // disable proxy and fetch real clip
2844             clip->setProperty("proxy", "-");
2845             QDomElement xml = clip->toXML();
2846             m_render->getFileProperties(xml, clip->getId(), m_listView->iconSize().height(), true);
2847         }
2848         else {
2849             // Disable proxy for this clip
2850             clip->setProperty("proxy", "-");
2851         }
2852     }
2853     monitorItemEditing(true);
2854 }
2855
2856 void ProjectList::monitorItemEditing(bool enable)
2857 {
2858     if (enable) connect(m_listView, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(slotItemEdited(QTreeWidgetItem *, int)));     
2859     else disconnect(m_listView, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(slotItemEdited(QTreeWidgetItem *, int)));     
2860 }
2861
2862 QStringList ProjectList::expandedFolders() const
2863 {
2864     QStringList result;
2865     FolderProjectItem *item;
2866     QTreeWidgetItemIterator it(m_listView);
2867     while (*it) {
2868         if ((*it)->type() != PROJECTFOLDERTYPE) {
2869             ++it;
2870             continue;
2871         }
2872         if ((*it)->isExpanded()) {
2873             item = static_cast<FolderProjectItem *>(*it);
2874             result.append(item->clipId());
2875         }
2876         ++it;
2877     }
2878     return result;
2879 }
2880
2881 void ProjectList::processThumbOverlays(ProjectItem *item, QPixmap &pix)
2882 {
2883     if (item->hasProxy()) {
2884         QPainter p(&pix);
2885         QColor c = QPalette().base().color();
2886         c.setAlpha(160);
2887         QBrush br(c);
2888         p.setBrush(br);
2889         p.setPen(Qt::NoPen);
2890         QRect r(1, 1, 10, 10);
2891         p.drawRect(r);
2892         p.setPen(QPalette().text().color());
2893         p.drawText(r, Qt::AlignCenter, i18nc("The first letter of Proxy, used as abbreviation", "P"));
2894     }
2895 }
2896
2897 #include "projectlist.moc"