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