]> git.sesse.net Git - kdenlive/blobdiff - src/archivewidget.cpp
Const'ref
[kdenlive] / src / archivewidget.cpp
index cee0ab5468062fe8dba11887fd2141fb7486797e..a645172e5a9efe19f83a28dc7423d5a5f6d353c4 100644 (file)
 #include <KDebug>
 #include <KApplication>
 #include <kio/directorysizejob.h>
+#if KDE_IS_VERSION(4,7,0)
+#include <KMessageWidget>
+#endif
 
 #include <QTreeWidget>
 #include <QtConcurrentRun>
 #include "projectsettings.h"
 
 
-ArchiveWidget::ArchiveWidget(QString projectName, QDomDocument doc, QList <DocClipBase*> list, QStringList luma_list, QWidget * parent) :
-        QDialog(parent),
-        m_requestedSize(0),
-        m_copyJob(NULL),
-        m_name(projectName.section('.', 0, -2)),
-        m_doc(doc),
-        m_abortArchive(false),
-        m_extractMode(false),
-        m_extractArchive(NULL)
+ArchiveWidget::ArchiveWidget(const QString &projectName, const QDomDocument &doc, const QList <DocClipBase*> &list, const QStringList &luma_list, QWidget * parent) :
+        QDialog(parent)
+        , m_requestedSize(0)
+        , m_copyJob(NULL)
+        , m_name(projectName.section('.', 0, -2))
+        , m_doc(doc)
+       , m_temp(NULL)
+        , m_abortArchive(false)
+        , m_extractMode(false)
+       , m_progressTimer(NULL)
+        , m_extractArchive(NULL)
+        , m_missingClips(0)
 {
     setAttribute(Qt::WA_DeleteOnClose);
     setupUi(this);
     setWindowTitle(i18n("Archive Project"));
     archive_url->setUrl(KUrl(QDir::homePath()));
-    connect(archive_url, SIGNAL(textChanged (const QString &)), this, SLOT(slotCheckSpace()));
+    connect(archive_url, SIGNAL(textChanged(QString)), this, SLOT(slotCheckSpace()));
     connect(this, SIGNAL(archivingFinished(bool)), this, SLOT(slotArchivingFinished(bool)));
     connect(this, SIGNAL(archiveProgress(int)), this, SLOT(slotArchivingProgress(int)));
+    connect(proxy_only, SIGNAL(stateChanged(int)), this, SLOT(slotProxyOnly(int)));
 
     // Setup categories
     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
@@ -77,6 +84,10 @@ ArchiveWidget::ArchiveWidget(QString projectName, QDomDocument doc, QList <DocCl
     texts->setIcon(0, KIcon("text-plain"));
     texts->setData(0, Qt::UserRole, "texts");
     texts->setExpanded(false);
+    QTreeWidgetItem *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips"));
+    playlists->setIcon(0, KIcon("video-mlt-playlist"));
+    playlists->setData(0, Qt::UserRole, "playlist");
+    playlists->setExpanded(false);
     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
     others->setIcon(0, KIcon("unknown"));
     others->setData(0, Qt::UserRole, "others");
@@ -95,58 +106,85 @@ ArchiveWidget::ArchiveWidget(QString projectName, QDomDocument doc, QList <DocCl
     QStringList allFonts;
     KUrl::List fileUrls;
     QStringList fileNames;
+    QStringList extraImageUrls;
+    QStringList otherUrls;
     generateItems(lumas, luma_list);
 
-    QStringList slideUrls;
-    QStringList audioUrls;
-    QStringList videoUrls;
-    QStringList imageUrls;
-    QStringList otherUrls;
-    QStringList proxyUrls;
+    QMap <QString, QString> slideUrls;
+    QMap <QString, QString> audioUrls;
+    QMap <QString, QString>videoUrls;
+    QMap <QString, QString>imageUrls;
+    QMap <QString, QString>playlistUrls;
+    QMap <QString, QString>proxyUrls;
 
-    for (int i = 0; i < list.count(); i++) {
+    for (int i = 0; i < list.count(); ++i) {
         DocClipBase *clip = list.at(i);
         CLIPTYPE t = clip->clipType();
+        QString id = clip->getId();
         if (t == SLIDESHOW) {
             KUrl slideUrl = clip->fileURL();
             //TODO: Slideshow files
-            slideUrls << slideUrl.path();
+            slideUrls.insert(id, slideUrl.path());
         }
-        else if (t == IMAGE) imageUrls << clip->fileURL().path();
+        else if (t == IMAGE) imageUrls.insert(id, clip->fileURL().path());
         else if (t == TEXT) {
             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
-            imageUrls << imagefiles;
+            extraImageUrls << imagefiles;
             allFonts << fonts;
         } else if (t == PLAYLIST) {
+            playlistUrls.insert(id, clip->fileURL().path());
             QStringList files = ProjectSettings::extractPlaylistUrls(clip->fileURL().path());
             otherUrls << files;
         }
         else if (!clip->fileURL().isEmpty()) {
-            if (t == AUDIO) audioUrls << clip->fileURL().path();
+            if (t == AUDIO) audioUrls.insert(id, clip->fileURL().path());
             else {
-                videoUrls << clip->fileURL().path();
+                videoUrls.insert(id, clip->fileURL().path());
                 // Check if we have a proxy
                 QString proxy = clip->getProperty("proxy");
-                if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls << proxy;
+                if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls.insert(id, proxy);
             }
         }
     }
 
+    generateItems(images, extraImageUrls);
     generateItems(sounds, audioUrls);
     generateItems(videos, videoUrls);
     generateItems(images, imageUrls);
     generateItems(slideshows, slideUrls);
+    generateItems(playlists, playlistUrls);
     generateItems(others, otherUrls);
     generateItems(proxies, proxyUrls);
     
     allFonts.removeDuplicates();
 
+#if KDE_IS_VERSION(4,7,0)
+        m_infoMessage = new KMessageWidget(this);
+        QVBoxLayout *s =  static_cast <QVBoxLayout*> (layout());
+        s->insertWidget(5, m_infoMessage);
+        m_infoMessage->setCloseButtonVisible(false);
+        m_infoMessage->setWordWrap(true);
+        m_infoMessage->hide();
+#endif
+        
+        // missing clips, warn user
+    if (m_missingClips > 0) {
+        QString infoText = i18np("You have %1 missing clip in your project.", "You have %1 missing clips in your project.", m_missingClips);
+#if KDE_IS_VERSION(4,7,0)
+        m_infoMessage->setMessageType(KMessageWidget::Warning);
+        m_infoMessage->setText(infoText);
+        m_infoMessage->animatedShow();
+#else
+        KMessageBox::sorry(this, infoText);
+#endif
+    }
+
     //TODO: fonts
 
     // Hide unused categories, add item count
     int total = 0;
-    for (int i = 0; i < files_list->topLevelItemCount(); i++) {
+    for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
         QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
         int items = parentItem->childCount();
         if (items == 0) {
@@ -161,7 +199,7 @@ ArchiveWidget::ArchiveWidget(QString projectName, QDomDocument doc, QList <DocCl
                 }
             }
             else total += items;
-            parentItem->setText(0, files_list->topLevelItem(i)->text(0) + " " + i18np("(%1 item)", "(%1 items)", items));
+            parentItem->setText(0, files_list->topLevelItem(i)->text(0) + ' ' + i18np("(%1 item)", "(%1 items)", items));
         }
     }
     if (m_name.isEmpty()) m_name = i18n("Untitled");
@@ -183,10 +221,15 @@ ArchiveWidget::ArchiveWidget(const KUrl &url, QWidget * parent):
     //setAttribute(Qt::WA_DeleteOnClose);
 
     setupUi(this);
+    m_progressTimer = new QTimer;
+    m_progressTimer->setInterval(800);
+    m_progressTimer->setSingleShot(false);
+    connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(slotExtractProgress()));
     connect(this, SIGNAL(extractingFinished()), this, SLOT(slotExtractingFinished()));
-    connect(this, SIGNAL(showMessage(const QString &, const QString &)), this, SLOT(slotDisplayMessage(const QString &, const QString &)));
+    connect(this, SIGNAL(showMessage(QString,QString)), this, SLOT(slotDisplayMessage(QString,QString)));
     
     compressed_archive->setHidden(true);
+    proxy_only->setHidden(true);
     project_files->setHidden(true);
     files_list->setHidden(true);
     label->setText(i18n("Extract to"));
@@ -194,7 +237,7 @@ ArchiveWidget::ArchiveWidget(const KUrl &url, QWidget * parent):
     archive_url->setUrl(KUrl(QDir::homePath()));
     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Extract"));
     connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartExtracting()));
-    buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
+    buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
     adjustSize();
     m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::openArchiveForExtraction);
 }
@@ -202,15 +245,29 @@ ArchiveWidget::ArchiveWidget(const KUrl &url, QWidget * parent):
 
 ArchiveWidget::~ArchiveWidget()
 {
-    if (m_extractArchive) delete m_extractArchive;
+    delete m_extractArchive;
+    delete m_progressTimer;
 }
 
 void ArchiveWidget::slotDisplayMessage(const QString &icon, const QString &text)
-{
+{    
     icon_info->setPixmap(KIcon(icon).pixmap(16, 16));
     text_info->setText(text);
 }
 
+void ArchiveWidget::slotJobResult(bool success, const QString &text)
+{
+#if KDE_IS_VERSION(4,7,0)
+    m_infoMessage->setMessageType(success ? KMessageWidget::Positive : KMessageWidget::Warning);
+    m_infoMessage->setText(text);
+    m_infoMessage->animatedShow();
+#else
+    if (success) icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
+    else icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
+    text_info->setText(text);
+#endif
+}
+
 void ArchiveWidget::openArchiveForExtraction()
 {
     emit showMessage("system-run", i18n("Opening archive..."));
@@ -224,7 +281,7 @@ void ArchiveWidget::openArchiveForExtraction()
     // Check that it is a kdenlive project archive
     bool isProjectArchive = false;
     QStringList files = m_extractArchive->directory()->entries();
-    for (int i = 0; i < files.count(); i++) {
+    for (int i = 0; i < files.count(); ++i) {
         if (files.at(i).endsWith(".kdenlive")) {
             m_projectName = files.at(i);
             isProjectArchive = true;
@@ -235,6 +292,7 @@ void ArchiveWidget::openArchiveForExtraction()
     if (!isProjectArchive) {
         emit showMessage("dialog-close", i18n("File %1\n is not an archived Kdenlive project", m_extractUrl.path()));
         groupBox->setEnabled(false);
+       buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
         return;
     }
     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
@@ -267,7 +325,7 @@ bool ArchiveWidget::closeAccepted()
 }
 
 
-void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items)
+void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, const QStringList& items)
 {
     QStringList filesList;
     QString fileName;
@@ -285,17 +343,19 @@ void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items
             if (slideUrl.fileName().startsWith(".all.")) {
                 // mimetype slideshow (for example *.png)
                     QStringList filters;
-                    QString extension;
                     // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
                     filters << "*." + slideUrl.fileName().section('.', -1);
                     dir.setNameFilters(filters);
                     QFileInfoList resultList = dir.entryInfoList(QDir::Files);
                     QStringList slideImages;
-                    for (int i = 0; i < resultList.count(); i++) {
-                        m_requestedSize += resultList.at(i).size();
+                    qint64 totalSize = 0;
+                    for (int i = 0; i < resultList.count(); ++i) {
+                        totalSize += resultList.at(i).size();
                         slideImages << resultList.at(i).absoluteFilePath();
                     }
                     item->setData(0, Qt::UserRole + 1, slideImages);
+                    item->setData(0, Qt::UserRole + 3, totalSize);
+                    m_requestedSize += totalSize;
             }
             else {
                 // pattern url (like clip%.3d.png)
@@ -303,24 +363,112 @@ void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items
                 QString filter = slideUrl.fileName();
                 QString ext = filter.section('.', -1);
                 filter = filter.section('%', 0, -2);
-                QString regexp = "^" + filter + "\\d+\\." + ext + "$";
+                QString regexp = '^' + filter + "\\d+\\." + ext + '$';
                 QRegExp rx(regexp);
                 QStringList slideImages;
                 QString directory = dir.absolutePath();
                 if (!directory.endsWith('/')) directory.append('/');
+                qint64 totalSize = 0;
                 foreach(const QString & path, result) {
                     if (rx.exactMatch(path)) {
-                        m_requestedSize += QFileInfo(directory + path).size();
+                        totalSize += QFileInfo(directory + path).size();
                         slideImages <<  directory + path;
                     }
                 }
                 item->setData(0, Qt::UserRole + 1, slideImages);
+                item->setData(0, Qt::UserRole + 3, totalSize);
+                m_requestedSize += totalSize;
             }                    
         }
         else if (filesList.contains(fileName)) {
             // we have 2 files with same name
             int ix = 0;
-            QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
+            QString newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1);
+            while (filesList.contains(newFileName)) {
+                ix ++;
+                newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1);
+            }
+            fileName = newFileName;
+            item->setData(0, Qt::UserRole, fileName);
+        }
+        if (!isSlideshow) {
+            qint64 fileSize = QFileInfo(file).size();
+            if (fileSize <= 0) {
+                item->setIcon(0, KIcon("edit-delete"));
+                m_missingClips++;
+            }
+            else {
+                m_requestedSize += fileSize;
+                item->setData(0, Qt::UserRole + 3, fileSize);
+            }
+            filesList << fileName;
+        }
+    }
+}
+
+void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, const QMap <QString, QString>& items)
+{
+    QStringList filesList;
+    QString fileName;
+    int ix = 0;
+    bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
+    QMap<QString, QString>::const_iterator it = items.constBegin();
+    while (it != items.constEnd()) {
+        QString file = it.value();
+        QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
+        // Store the clip's id
+        item->setData(0, Qt::UserRole + 2, it.key());
+        fileName = KUrl(file).fileName();
+        if (isSlideshow) {
+            // we store each slideshow in a separate subdirectory
+            item->setData(0, Qt::UserRole, ix);
+            ix++;
+            KUrl slideUrl(file);
+            QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
+            if (slideUrl.fileName().startsWith(".all.")) {
+                // mimetype slideshow (for example *.png)
+                    QStringList filters;
+                    // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
+                    filters << "*." + slideUrl.fileName().section('.', -1);
+                    dir.setNameFilters(filters);
+                    QFileInfoList resultList = dir.entryInfoList(QDir::Files);
+                    QStringList slideImages;
+                    qint64 totalSize = 0;
+                    for (int i = 0; i < resultList.count(); ++i) {
+                        totalSize += resultList.at(i).size();
+                        slideImages << resultList.at(i).absoluteFilePath();
+                    }
+                    item->setData(0, Qt::UserRole + 1, slideImages);
+                    item->setData(0, Qt::UserRole + 3, totalSize);
+                    m_requestedSize += totalSize;
+            }
+            else {
+                // pattern url (like clip%.3d.png)
+                QStringList result = dir.entryList(QDir::Files);
+                QString filter = slideUrl.fileName();
+                QString ext = filter.section('.', -1);
+                filter = filter.section('%', 0, -2);
+                QString regexp = '^' + filter + "\\d+\\." + ext + '$';
+                QRegExp rx(regexp);
+                QStringList slideImages;
+                qint64 totalSize = 0;
+                QString directory = dir.absolutePath();
+                if (!directory.endsWith('/')) directory.append('/');
+                foreach(const QString & path, result) {
+                    if (rx.exactMatch(path)) {
+                        totalSize += QFileInfo(directory + path).size();
+                        slideImages <<  directory + path;
+                    }
+                }
+                item->setData(0, Qt::UserRole + 1, slideImages);
+                item->setData(0, Qt::UserRole + 3, totalSize);
+                m_requestedSize += totalSize;
+            }                    
+        }
+        else if (filesList.contains(fileName)) {
+            // we have 2 files with same name
+            int ix = 0;
+            QString newFileName = fileName.section('.', 0, -2) + '_' + QString::number(ix) + '.' + fileName.section('.', -1);
             while (filesList.contains(newFileName)) {
                 ix ++;
                 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
@@ -329,9 +477,18 @@ void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items
             item->setData(0, Qt::UserRole, fileName);
         }
         if (!isSlideshow) {
-            m_requestedSize += QFileInfo(file).size();
+            qint64 fileSize = QFileInfo(file).size();
+            if (fileSize <= 0) {
+                item->setIcon(0, KIcon("edit-delete"));
+                m_missingClips++;
+            }
+            else {
+                m_requestedSize += fileSize;
+                item->setData(0, Qt::UserRole + 3, fileSize);
+            }
             filesList << fileName;
         }
+        ++it;
     }
 }
 
@@ -370,6 +527,7 @@ bool ArchiveWidget::slotStartArchiving(bool firstPass)
         slotDisplayMessage("system-run", i18n("Archiving..."));
         repaint();
         archive_url->setEnabled(false);
+        proxy_only->setEnabled(false);
         compressed_archive->setEnabled(false);
     }
     KUrl::List files;
@@ -377,28 +535,35 @@ bool ArchiveWidget::slotStartArchiving(bool firstPass)
     QString destPath;
     QTreeWidgetItem *parentItem;
     bool isSlideshow = false;
-    for (int i = 0; i < files_list->topLevelItemCount(); i++) {
+    int items = 0;
+    
+    // We parse all files going into one folder, then start the copy job
+    for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
         parentItem = files_list->topLevelItem(i);
-        if (parentItem->childCount() > 0 && !parentItem->isDisabled()) {
+        if (parentItem->isDisabled()) {
+            parentItem->setExpanded(false);
+            continue;
+        }
+        if (parentItem->childCount() > 0) {
             if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") {
                 KUrl slideFolder(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows");
                 if (isArchive) m_foldersList.append("slideshows");
                 else KIO::NetAccess::mkdir(slideFolder, this);
                 isSlideshow = true;
             }
+            else isSlideshow = false;
             files_list->setCurrentItem(parentItem);
-            if (!isSlideshow) parentItem->setDisabled(true);
-            destPath = parentItem->data(0, Qt::UserRole).toString() + "/";
+            parentItem->setExpanded(true);
+            destPath = parentItem->data(0, Qt::UserRole).toString() + '/';
             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
             QTreeWidgetItem *item;
             for (int j = 0; j < parentItem->childCount(); j++) {
                 item = parentItem->child(j);
+                if (item->isDisabled()) continue;
                 // Special case: slideshows
+               items++;
                 if (isSlideshow) {
-                    if (item->isDisabled()) {
-                        continue;
-                    }
-                    destPath.append(item->data(0, Qt::UserRole).toString() + "/");
+                    destPath += item->data(0, Qt::UserRole).toString() + '/';
                     destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + destPath);
                     QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList();
                     for (int k = 0; k < srcFiles.count(); k++) {
@@ -423,10 +588,17 @@ bool ArchiveWidget::slotStartArchiving(bool firstPass)
                     else m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
                 }
             }
+            if (!isSlideshow) parentItem->setDisabled(true);
             break;
         }
     }
 
+    if (items == 0) {
+       // No clips to archive
+       slotArchivingFinished(NULL, true);
+       return true;
+    }
+    
     if (destPath.isEmpty()) {
         if (m_duplicateFiles.isEmpty()) return false;        
         QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
@@ -436,15 +608,15 @@ bool ArchiveWidget::slotStartArchiving(bool firstPass)
             KUrl startJobDst = i.value();
             m_duplicateFiles.remove(startJobSrc);
             KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo);
-            connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
-            connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
+            connect(job, SIGNAL(result(KJob*)), this, SLOT(slotArchivingFinished(KJob*)));
+            connect(job, SIGNAL(processedSize(KJob*,qulonglong)), this, SLOT(slotArchivingProgress(KJob*,qulonglong)));
         }
         return true;
     }
 
     if (isArchive) {
         m_foldersList.append(destPath);
-        for (int i = 0; i < files.count(); i++) {
+        for (int i = 0; i < files.count(); ++i) {
             m_filesList.insert(files.at(i).path(), destPath + files.at(i).fileName());
         }
         slotArchivingFinished();
@@ -455,8 +627,8 @@ bool ArchiveWidget::slotStartArchiving(bool firstPass)
     else {
         KIO::NetAccess::mkdir(destUrl, this);
         m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
-        connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
-        connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
+        connect(m_copyJob, SIGNAL(result(KJob*)), this, SLOT(slotArchivingFinished(KJob*)));
+        connect(m_copyJob, SIGNAL(processedSize(KJob*,qulonglong)), this, SLOT(slotArchivingProgress(KJob*,qulonglong)));
     }
     if (firstPass) {
         progressBar->setValue(0);
@@ -465,10 +637,10 @@ bool ArchiveWidget::slotStartArchiving(bool firstPass)
     return true;
 }
 
-void ArchiveWidget::slotArchivingFinished(KJob *job)
+void ArchiveWidget::slotArchivingFinished(KJob *job, bool finished)
 {
     if (job == NULL || job->error() == 0) {
-        if (slotStartArchiving(false)) {
+        if (!finished && slotStartArchiving(false)) {
             // We still have files to archive
             return;
         }
@@ -476,22 +648,23 @@ void ArchiveWidget::slotArchivingFinished(KJob *job)
             // Archiving finished
             progressBar->setValue(100);
             if (processProjectFile()) {
-                slotDisplayMessage("dialog-ok", i18n("Project was successfully archived."));
+                slotJobResult(true, i18n("Project was successfully archived."));
             }
             else {
-                slotDisplayMessage("dialog-close", i18n("There was an error processing project file"));
+                slotJobResult(false, i18n("There was an error processing project file"));
             }
         } else processProjectFile();
     }
     else {
         m_copyJob = NULL;
-        slotDisplayMessage("dialog-close", i18n("There was an error while copying the files: %1", job->errorString()));
+        slotJobResult(false, i18n("There was an error while copying the files: %1", job->errorString()));
     }
     if (!compressed_archive->isChecked()) {
         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
         archive_url->setEnabled(true);
+        proxy_only->setEnabled(true);
         compressed_archive->setEnabled(true);
-        for (int i = 0; i < files_list->topLevelItemCount(); i++) {
+        for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
             files_list->topLevelItem(i)->setDisabled(false);
             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
                 files_list->topLevelItem(i)->child(j)->setDisabled(false);        
@@ -511,7 +684,7 @@ bool ArchiveWidget::processProjectFile()
     QTreeWidgetItem *item;
     bool isArchive = compressed_archive->isChecked();
 
-    for (int i = 0; i < files_list->topLevelItemCount(); i++) {
+    for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
         QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
         if (parentItem->childCount() > 0) {
             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString());
@@ -521,7 +694,7 @@ bool ArchiveWidget::processProjectFile()
                 KUrl src(item->text(0));
                 KUrl dest = destUrl;
                 if (isSlideshow) {
-                    dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/" + src.fileName());
+                    dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + '/' + src.fileName());
                 }
                 else if (item->data(0, Qt::UserRole).isNull()) {
                     dest.addPath(src.fileName());
@@ -535,7 +708,7 @@ bool ArchiveWidget::processProjectFile()
     }
     
     QDomElement mlt = m_doc.documentElement();
-    QString root = mlt.attribute("root") + "/";
+    QString root = mlt.attribute("root") + '/';
 
     // Adjust global settings
     QString basePath;
@@ -547,7 +720,7 @@ bool ArchiveWidget::processProjectFile()
 
     // process kdenlive producers
     QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
-    for (int i = 0; i < prods.count(); i++) {
+    for (int i = 0; i < prods.count(); ++i) {
         QDomElement e = prods.item(i).toElement();
         if (e.isNull()) continue;
         if (e.hasAttribute("resource")) {
@@ -564,7 +737,7 @@ bool ArchiveWidget::processProjectFile()
 
     // process mlt producers
     prods = mlt.elementsByTagName("producer");
-    for (int i = 0; i < prods.count(); i++) {
+    for (int i = 0; i < prods.count(); ++i) {
         QDomElement e = prods.item(i).toElement();
         if (e.isNull()) continue;
         QString src = EffectsList::property(e, "resource");
@@ -579,7 +752,7 @@ bool ArchiveWidget::processProjectFile()
     // process mlt transitions (for luma files)
     prods = mlt.elementsByTagName("transition");
     QString attribute;
-    for (int i = 0; i < prods.count(); i++) {
+    for (int i = 0; i < prods.count(); ++i) {
         QDomElement e = prods.item(i).toElement();
         if (e.isNull()) continue;
         attribute = "resource";
@@ -601,8 +774,8 @@ bool ArchiveWidget::processProjectFile()
         QString endString("\"");
         endString.append(basePath);
         playList.replace(startString, endString);
-        startString = ">" + archive_url->url().path(KUrl::RemoveTrailingSlash);
-        endString = ">" + basePath;
+        startString = '>' + archive_url->url().path(KUrl::RemoveTrailingSlash);
+        endString = '>' + basePath;
         playList.replace(startString, endString);
     }
 
@@ -657,25 +830,31 @@ void ArchiveWidget::createArchive()
     }
 
     // Add project file
-    archive.addLocalFile(m_temp->fileName(), m_name + ".kdenlive");
-    bool result = archive.close();
-    delete m_temp;
+    bool result = false;
+    if (m_temp) {
+        archive.addLocalFile(m_temp->fileName(), m_name + ".kdenlive");
+        result = archive.close();
+        delete m_temp;
+        m_temp = 0;
+    }
     emit archivingFinished(result);
 }
 
 void ArchiveWidget::slotArchivingFinished(bool result)
 {
     if (result) {
-        slotDisplayMessage("dialog-ok", i18n("Project was successfully archived."));
+        slotJobResult(true, i18n("Project was successfully archived."));
+        buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
     }
     else {
-        slotDisplayMessage("dialog-close", i18n("There was an error processing project file"));
+        slotJobResult(false, i18n("There was an error processing project file"));
     }
     progressBar->setValue(100);
     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
     archive_url->setEnabled(true);
+    proxy_only->setEnabled(true);
     compressed_archive->setEnabled(true);
-    for (int i = 0; i < files_list->topLevelItemCount(); i++) {
+    for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
         files_list->topLevelItem(i)->setDisabled(false);
         for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
             files_list->topLevelItem(i)->child(j)->setDisabled(false);
@@ -698,10 +877,6 @@ void ArchiveWidget::slotStartExtracting()
     KIO::NetAccess::mkdir(archive_url->url().path(KUrl::RemoveTrailingSlash), this);
     slotDisplayMessage("system-run", i18n("Extracting..."));
     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
-    m_progressTimer = new QTimer;
-    m_progressTimer->setInterval(800);
-    m_progressTimer->setSingleShot(false);
-    connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(slotExtractProgress()));
     m_archiveThread = QtConcurrent::run(this, &ArchiveWidget::doExtracting);
     m_progressTimer->start();
 }
@@ -728,7 +903,7 @@ void ArchiveWidget::doExtracting()
     emit extractingFinished();    
 }
 
-QString ArchiveWidget::extractedProjectFile()
+QString ArchiveWidget::extractedProjectFile() const
 {
     return archive_url->url().path(KUrl::AddTrailingSlash) + m_projectName;
 }
@@ -736,7 +911,6 @@ QString ArchiveWidget::extractedProjectFile()
 void ArchiveWidget::slotExtractingFinished()
 {
     m_progressTimer->stop();
-    delete m_progressTimer;
     // Process project file
     QFile file(extractedProjectFile());
     bool error = false;
@@ -744,7 +918,7 @@ void ArchiveWidget::slotExtractingFinished()
         error = true;
     }
     else {
-        QString playList = file.readAll();
+        QString playList = QString::fromUtf8(file.readAll());
         file.close();
         if (playList.isEmpty()) {
             error = true;
@@ -770,3 +944,76 @@ void ArchiveWidget::slotExtractingFinished()
     }
     else accept();
 }
+
+void ArchiveWidget::slotProxyOnly(int onlyProxy)
+{
+    m_requestedSize = 0;
+    if (onlyProxy == Qt::Checked) {
+        // Archive proxy clips
+        QStringList proxyIdList;
+        QTreeWidgetItem *parentItem = NULL;
+
+        // Build list of existing proxy ids
+        for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
+            parentItem = files_list->topLevelItem(i);
+            if (parentItem->data(0, Qt::UserRole).toString() == "proxy") break;
+        }
+        if (!parentItem) return;
+        int items = parentItem->childCount();
+        for (int j = 0; j < items; j++) {
+            proxyIdList << parentItem->child(j)->data(0, Qt::UserRole + 2).toString();
+        }
+        
+        // Parse all items to disable original clips for existing proxies
+        for (int i = 0; i < proxyIdList.count(); ++i) {
+            QString id = proxyIdList.at(i);
+            if (id.isEmpty()) continue;
+            for (int j = 0; j < files_list->topLevelItemCount(); j++) {
+                parentItem = files_list->topLevelItem(j);
+                if (parentItem->data(0, Qt::UserRole).toString() == "proxy") continue;
+                items = parentItem->childCount();
+                for (int k = 0; k < items; k++) {
+                    if (parentItem->child(k)->data(0, Qt::UserRole + 2).toString() == id) {
+                        // This item has a proxy, do not archive it
+                        parentItem->child(k)->setFlags(Qt::ItemIsSelectable);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    else {
+        // Archive all clips
+        for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
+            QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
+            int items = parentItem->childCount();
+            for (int j = 0; j < items; j++) {
+                parentItem->child(j)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+            }
+        }
+    }
+    
+    // Calculate requested size
+    int total = 0;
+    for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
+        QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
+        int items = parentItem->childCount();
+        int itemsCount = 0;
+        bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
+        
+        for (int j = 0; j < items; j++) {
+            if (!parentItem->child(j)->isDisabled()) {
+                m_requestedSize += parentItem->child(j)->data(0, Qt::UserRole + 3).toInt();
+                if (isSlideshow) total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
+                else total ++;
+                itemsCount ++;
+            }
+        }
+        parentItem->setText(0, parentItem->text(0).section('(', 0, 0) + i18np("(%1 item)", "(%1 items)", itemsCount));
+    }
+    project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
+    slotCheckSpace();
+}
+
+
+#include "archivewidget.moc"