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