]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
Cleaning code style of Definitions.
[kdenlive] / src / projectsettings.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
18  ***************************************************************************/
19
20 #include "projectsettings.h"
21 #include "kdenlivesettings.h"
22 #include "profilesdialog.h"
23 #include "docclipbase.h"
24 #include "widgets/titlewidget.h"
25 #include "effectslist.h"
26
27 #include <KStandardDirs>
28 #include <KMessageBox>
29 #include <KDebug>
30 #include <kio/directorysizejob.h>
31 #include <KIO/NetAccess>
32 #include <KTemporaryFile>
33 #include <KFileDialog>
34
35 #include <QDir>
36 #include <kmessagebox.h>
37
38 ProjectSettings::ProjectSettings(ProjectList *projectlist, QMap <QString, QString> metadata, const QStringList &lumas, int videotracks, int audiotracks, const QString &projectPath, bool readOnlyTracks, bool savedProject, QWidget * parent) :
39     QDialog(parent), m_savedProject(savedProject), m_projectList(projectlist), m_lumas(lumas)
40 {
41     setupUi(this);
42
43     list_search->setTreeWidget(files_list);
44
45     QMap <QString, QString> profilesInfo = ProfilesDialog::getProfilesInfo();
46     QMapIterator<QString, QString> i(profilesInfo);
47     while (i.hasNext()) {
48         i.next();
49         profiles_list->addItem(i.key(), i.value());
50     }
51     project_folder->setMode(KFile::Directory);
52     project_folder->setUrl(KUrl(projectPath));
53     QString currentProf = KdenliveSettings::current_profile();
54
55     for (int i = 0; i < profiles_list->count(); ++i) {
56         if (profiles_list->itemData(i).toString() == currentProf) {
57             profiles_list->setCurrentIndex(i);
58             break;
59         }
60     }
61
62     m_buttonOk = buttonBox->button(QDialogButtonBox::Ok);
63     //buttonOk->setEnabled(false);
64     audio_thumbs->setChecked(KdenliveSettings::audiothumbnails());
65     video_thumbs->setChecked(KdenliveSettings::videothumbnails());
66     audio_tracks->setValue(audiotracks);
67     video_tracks->setValue(videotracks);
68     connect(generate_proxy, SIGNAL(toggled(bool)), proxy_minsize, SLOT(setEnabled(bool)));
69     connect(generate_imageproxy, SIGNAL(toggled(bool)), proxy_imageminsize, SLOT(setEnabled(bool)));
70     QString proxyparameters;
71     QString proxyextension;
72     if (projectlist) {
73         enable_proxy->setChecked(projectlist->getDocumentProperty("enableproxy").toInt());
74         generate_proxy->setChecked(projectlist->getDocumentProperty("generateproxy").toInt());
75         proxy_minsize->setValue(projectlist->getDocumentProperty("proxyminsize").toInt());
76         proxyparameters = projectlist->getDocumentProperty("proxyparams");
77         generate_imageproxy->setChecked(projectlist->getDocumentProperty("generateimageproxy").toInt());
78         proxy_imageminsize->setValue(projectlist->getDocumentProperty("proxyimageminsize").toInt());
79         proxyextension = projectlist->getDocumentProperty("proxyextension");
80     }
81     else {
82         enable_proxy->setChecked(KdenliveSettings::enableproxy());
83         generate_proxy->setChecked(KdenliveSettings::generateproxy());
84         proxy_minsize->setValue(KdenliveSettings::proxyminsize());
85         proxyparameters = KdenliveSettings::proxyparams();
86         generate_imageproxy->setChecked(KdenliveSettings::generateimageproxy());
87         proxy_imageminsize->setValue(KdenliveSettings::proxyimageminsize());
88         proxyextension = KdenliveSettings::proxyextension();
89       
90     }
91
92     proxy_minsize->setEnabled(generate_proxy->isChecked());
93     proxy_imageminsize->setEnabled(generate_imageproxy->isChecked());
94
95
96     // load proxy profiles
97     KConfig conf("encodingprofiles.rc", KConfig::CascadeConfig, "appdata");
98     KConfigGroup group(&conf, "proxy");
99     QMap <QString, QString> values = group.entryMap();
100     QMapIterator<QString, QString> k(values);
101     int ix = -1;
102     while (k.hasNext()) {
103         k.next();
104         if (!k.key().isEmpty()) {
105             QString params = k.value().section(';', 0, 0);
106             QString extension = k.value().section(';', 1, 1);
107             if (ix == -1 && ((params == proxyparameters && extension == proxyextension) || (proxyparameters.isEmpty() || proxyextension.isEmpty()))) {
108                 // this is the current profile
109                 ix = proxy_profile->count();
110             }
111             proxy_profile->addItem(k.key(), k.value());
112         }
113     }
114     if (ix == -1) {
115         // Current project proxy settings not found
116         ix = proxy_profile->count();
117         proxy_profile->addItem(i18n("Current Settings"), QString(proxyparameters + ';' + proxyextension));
118     }
119     proxy_profile->setCurrentIndex(ix);
120     slotUpdateProxyParams();
121
122     // Proxy GUI stuff
123     proxy_showprofileinfo->setIcon(KIcon("help-about"));
124     connect(proxy_profile, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateProxyParams()));
125     proxyparams->setVisible(false);
126     proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
127     connect(proxy_showprofileinfo, SIGNAL(clicked(bool)), proxyparams, SLOT(setVisible(bool)));
128     
129     if (readOnlyTracks) {
130         video_tracks->setEnabled(false);
131         audio_tracks->setEnabled(false);
132     }
133     
134     
135     // Metadata list
136     QTreeWidgetItem *item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Title"));
137     item->setData(0, Qt::UserRole, QString("meta.attr.title.markup"));
138     if (metadata.contains("meta.attr.title.markup")) {
139         item->setText(1, metadata.value("meta.attr.title.markup"));
140         metadata.remove("meta.attr.title.markup");
141     }
142     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
143     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Author"));
144     item->setData(0, Qt::UserRole, QString("meta.attr.author.markup"));
145     if (metadata.contains("meta.attr.author.markup")) {
146         item->setText(1, metadata.value("meta.attr.author.markup"));
147         metadata.remove("meta.attr.author.markup");
148     }
149     else if (metadata.contains("meta.attr.artist.markup")) {
150         item->setText(0, i18n("Artist"));
151         item->setData(0, Qt::UserRole, QString("meta.attr.artist.markup"));
152         item->setText(1, metadata.value("meta.attr.artist.markup"));
153         metadata.remove("meta.attr.artist.markup");
154     }
155     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
156     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Copyright"));
157     item->setData(0, Qt::UserRole, QString("meta.attr.copyright.markup"));
158     if (metadata.contains("meta.attr.copyright.markup")) {
159         item->setText(1, metadata.value("meta.attr.copyright.markup"));
160         metadata.remove("meta.attr.copyright.markup");
161     }
162     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
163     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Year"));
164     item->setData(0, Qt::UserRole, QString("meta.attr.year.markup"));
165     if (metadata.contains("meta.attr.year.markup")) {
166         item->setText(1, metadata.value("meta.attr.year.markup"));
167         metadata.remove("meta.attr.year.markup");
168     }
169     else if (metadata.contains("meta.attr.date.markup")) {
170         item->setText(0, i18n("Date"));
171         item->setData(0, Qt::UserRole, QString("meta.attr.date.markup"));
172         item->setText(1, metadata.value("meta.attr.date.markup"));
173         metadata.remove("meta.attr.date.markup");
174     }
175     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
176     
177     QMap<QString, QString>::const_iterator meta = metadata.constBegin();
178     while (meta != metadata.constEnd()) {
179         item = new QTreeWidgetItem(metadata_list, QStringList() << meta.key().section('.', 2,2));
180         item->setData(0, Qt::UserRole, meta.key());
181         item->setText(1, meta.value());
182         item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
183         ++meta;
184     }
185     
186     connect(add_metadata, SIGNAL(clicked()), this, SLOT(slotAddMetadataField()));
187     connect(delete_metadata, SIGNAL(clicked()), this, SLOT(slotDeleteMetadataField()));
188     add_metadata->setIcon(KIcon("list-add"));
189     delete_metadata->setIcon(KIcon("list-remove"));
190     
191     slotUpdateDisplay();
192     if (m_projectList != NULL) {
193         slotUpdateFiles();
194         connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
195         connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
196         connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
197     } else tabWidget->widget(1)->setEnabled(false);
198     connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
199     connect(project_folder, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButton(QString)));
200     connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
201 }
202
203 void ProjectSettings::slotDeleteUnused()
204 {
205     QStringList toDelete;
206     QList <DocClipBase*> list = m_projectList->documentClipList();
207     for (int i = 0; i < list.count(); ++i) {
208         DocClipBase *clip = list.at(i);
209         if (clip->numReferences() == 0 && clip->clipType() != SlideShow) {
210             KUrl url = clip->fileURL();
211             if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
212         }
213     }
214
215     // make sure our urls are not used in another clip
216     for (int i = 0; i < list.count(); ++i) {
217         DocClipBase *clip = list.at(i);
218         if (clip->numReferences() > 0) {
219             KUrl url = clip->fileURL();
220             if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
221         }
222     }
223
224     if (toDelete.count() == 0) {
225         // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
226         if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
227         m_projectList->cleanup();
228         slotUpdateFiles();
229         return;
230     }
231     if (KMessageBox::warningYesNoList(this, i18n("This will remove the following files from your hard drive.\nThis action cannot be undone, only use if you know what you are doing.\nAre you sure you want to continue?"), toDelete, i18n("Delete unused clips")) != KMessageBox::Yes) return;
232     m_projectList->trashUnusedClips();
233     slotUpdateFiles();
234 }
235
236 void ProjectSettings::slotClearCache()
237 {
238     buttonBox->setEnabled(false);
239     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
240     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
241     buttonBox->setEnabled(true);
242     slotUpdateFiles(true);
243 }
244
245 void ProjectSettings::slotDeleteProxies()
246 {
247     if (KMessageBox::warningContinueCancel(this, i18n("Deleting proxy clips will disable proxies for this project.")) != KMessageBox::Continue) return;
248     buttonBox->setEnabled(false);
249     enable_proxy->setChecked(false);
250     emit disableProxies();
251     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
252     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
253     buttonBox->setEnabled(true);
254     slotUpdateFiles(true);
255 }
256
257 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
258 {
259     KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
260     job->exec();
261     thumbs_count->setText(QString::number(job->totalFiles()));
262     thumbs_size->setText(KIO::convertSize(job->totalSize()));
263     job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
264     job->exec();
265     proxy_count->setText(QString::number(job->totalFiles()));
266     proxy_size->setText(KIO::convertSize(job->totalSize()));
267     delete job;
268     if (cacheOnly) return;
269     int unused = 0;
270     int used = 0;
271     KIO::filesize_t usedSize = 0;
272     KIO::filesize_t unUsedSize = 0;
273     QList <DocClipBase*> list = m_projectList->documentClipList();
274     files_list->clear();
275
276     // List all files that are used in the project. That also means:
277     // images included in slideshow and titles, files in playlist clips
278     // TODO: images used in luma transitions?
279
280     // Setup categories
281     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
282     videos->setIcon(0, KIcon("video-x-generic"));
283     videos->setExpanded(true);
284     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
285     sounds->setIcon(0, KIcon("audio-x-generic"));
286     sounds->setExpanded(true);
287     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
288     images->setIcon(0, KIcon("image-x-generic"));
289     images->setExpanded(true);
290     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
291     slideshows->setIcon(0, KIcon("image-x-generic"));
292     slideshows->setExpanded(true);
293     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
294     texts->setIcon(0, KIcon("text-plain"));
295     texts->setExpanded(true);
296     QTreeWidgetItem *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips"));
297     playlists->setIcon(0, KIcon("video-mlt-playlist"));
298     playlists->setExpanded(true);
299     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
300     others->setIcon(0, KIcon("unknown"));
301     others->setExpanded(true);
302     int count = 0;
303     QStringList allFonts;
304     foreach(const QString & file, m_lumas) {
305         count++;
306         new QTreeWidgetItem(images, QStringList() << file);
307     }
308
309     for (int i = 0; i < list.count(); ++i) {
310         DocClipBase *clip = list.at(i);
311         if (clip->clipType() == SlideShow) {
312             QStringList subfiles = extractSlideshowUrls(clip->fileURL());
313             foreach(const QString & file, subfiles) {
314                 count++;
315                 new QTreeWidgetItem(slideshows, QStringList() << file);
316             }
317         } else if (!clip->fileURL().isEmpty()) {
318             //allFiles.append(clip->fileURL().path());
319             switch (clip->clipType()) {
320             case Text:
321                 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
322                 break;
323             case Audio:
324                 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
325                 break;
326             case Image:
327                 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
328                 break;
329             case Playlist:
330                 new QTreeWidgetItem(playlists, QStringList() << clip->fileURL().path());
331                 break;
332             case Unknown:
333                 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
334                 break;
335             default:
336                 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
337                 break;
338             }
339             count++;
340         }
341         if (clip->clipType() == Text) {
342             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
343             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
344             foreach(const QString & file, imagefiles) {
345                 count++;
346                 new QTreeWidgetItem(images, QStringList() << file);
347             }
348             allFonts << fonts;
349         } else if (clip->clipType() == Playlist) {
350             QStringList files = extractPlaylistUrls(clip->fileURL().path());
351             foreach(const QString & file, files) {
352                 count++;
353                 new QTreeWidgetItem(others, QStringList() << file);
354             }
355         }
356
357         if (clip->numReferences() == 0) {
358             unused++;
359             unUsedSize += clip->fileSize();
360         } else {
361             used++;
362             usedSize += clip->fileSize();
363         }
364     }
365     allFonts.removeDuplicates();
366     // Hide unused categories
367     for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
368         if (files_list->topLevelItem(i)->childCount() == 0) {
369             files_list->topLevelItem(i)->setHidden(true);
370         }
371     }
372     files_count->setText(QString::number(count));
373     fonts_list->addItems(allFonts);
374     if (allFonts.isEmpty()) {
375         fonts_list->setHidden(true);
376         label_fonts->setHidden(true);
377     }
378     used_count->setText(QString::number(used));
379     used_size->setText(KIO::convertSize(usedSize));
380     unused_count->setText(QString::number(unused));
381     unused_size->setText(KIO::convertSize(unUsedSize));
382     delete_unused->setEnabled(unused > 0);
383 }
384
385 void ProjectSettings::accept()
386 {
387     if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
388         if (KMessageBox::warningContinueCancel(this, i18n("Changing the profile of your project cannot be undone.\nIt is recommended to save your project before attempting this operation that might cause some corruption in transitions.\n Are you sure you want to proceed?"), i18n("Confirm profile change")) == KMessageBox::Cancel) return;
389     QDialog::accept();
390 }
391
392 void ProjectSettings::slotUpdateDisplay()
393 {
394     QLocale locale;
395     QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
396     QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
397     p_size->setText(values.value("width") + 'x' + values.value("height"));
398     p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
399     p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
400     p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
401     if (values.value("progressive").toInt() == 0) {
402         p_progressive->setText(i18n("Interlaced (%1 fields per second)",
403                                     locale.toString((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
404     } else {
405         p_progressive->setText(i18n("Progressive"));
406     }
407     p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
408 }
409
410 void ProjectSettings::slotUpdateButton(const QString &path)
411 {
412     if (path.isEmpty()) m_buttonOk->setEnabled(false);
413     else {
414         m_buttonOk->setEnabled(true);
415         slotUpdateFiles(true);
416     }
417 }
418
419 QString ProjectSettings::selectedProfile() const
420 {
421     return profiles_list->itemData(profiles_list->currentIndex()).toString();
422 }
423
424 KUrl ProjectSettings::selectedFolder() const
425 {
426     return project_folder->url();
427 }
428
429 QPoint ProjectSettings::tracks() const
430 {
431     QPoint p;
432     p.setX(video_tracks->value());
433     p.setY(audio_tracks->value());
434     return p;
435 }
436
437 bool ProjectSettings::enableVideoThumbs() const
438 {
439     return video_thumbs->isChecked();
440 }
441
442 bool ProjectSettings::enableAudioThumbs() const
443 {
444     return audio_thumbs->isChecked();
445 }
446
447 bool ProjectSettings::useProxy() const
448 {
449     return enable_proxy->isChecked();
450 }
451
452 bool ProjectSettings::generateProxy() const
453 {
454     return generate_proxy->isChecked();
455 }
456
457 bool ProjectSettings::generateImageProxy() const
458 {
459     return generate_imageproxy->isChecked();
460 }
461
462 int ProjectSettings::proxyMinSize() const
463 {
464     return proxy_minsize->value();
465 }
466
467 int ProjectSettings::proxyImageMinSize() const
468 {
469     return proxy_imageminsize->value();
470 }
471
472 QString ProjectSettings::proxyParams() const
473 {
474     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
475     return params.section(';', 0, 0);
476 }
477
478 QString ProjectSettings::proxyExtension() const
479 {
480     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
481     return params.section(';', 1, 1);
482 }
483
484 //static
485 QStringList ProjectSettings::extractPlaylistUrls(const QString &path)
486 {
487     QStringList urls;
488     QDomDocument doc;
489     QFile file(path);
490     if (!file.open(QIODevice::ReadOnly))
491         return urls;
492     if (!doc.setContent(&file)) {
493         file.close();
494         return urls;
495     }
496     file.close();
497     QString root = doc.documentElement().attribute("root");
498     if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
499     QDomNodeList files = doc.elementsByTagName("producer");
500     for (int i = 0; i < files.count(); ++i) {
501         QDomElement e = files.at(i).toElement();
502         QString type = EffectsList::property(e, "mlt_service");
503         if (type != "colour") {
504             QString url = EffectsList::property(e, "resource");
505             if (type == "framebuffer") {
506                 url = url.section('?', 0, 0);
507             }
508             if (!url.isEmpty()) {
509                 if (!url.startsWith('/')) url.prepend(root);
510                 if (url.section('.', 0, -2).endsWith("/.all")) {
511                     // slideshow clip, extract image urls
512                     urls << extractSlideshowUrls(KUrl(url));
513                 } else urls << url;
514                 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
515                     //TODO: Do something to avoid infinite loops if 2 files reference themselves...
516                     urls << extractPlaylistUrls(url);
517                 }
518             }
519         }
520     }
521
522     // luma files for transitions
523     files = doc.elementsByTagName("transition");
524     for (int i = 0; i < files.count(); ++i) {
525         QDomElement e = files.at(i).toElement();
526         QString url = EffectsList::property(e, "luma");
527         if (!url.isEmpty()) {
528             if (!url.startsWith('/')) url.prepend(root);
529             urls << url;
530         }
531     }
532
533     return urls;
534 }
535
536
537 //static
538 QStringList ProjectSettings::extractSlideshowUrls(const KUrl &url)
539 {
540     QStringList urls;
541     QString path = url.directory(KUrl::AppendTrailingSlash);
542     QString ext = url.path().section('.', -1);
543     QDir dir(path);
544     if (url.path().contains(".all.")) {
545         // this is a mime slideshow, like *.jpeg
546         QStringList filters;
547         filters << "*." + ext;
548         dir.setNameFilters(filters);
549         QStringList result = dir.entryList(QDir::Files);
550         urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ')');
551     } else {
552         // this is a pattern slideshow, like sequence%4d.jpg
553         QString filter = url.fileName();
554         QString ext = filter.section('.', -1);
555         filter = filter.section('%', 0, -2);
556         QString regexp = '^' + filter + "\\d+\\." + ext + '$';
557         QRegExp rx(regexp);
558         int count = 0;
559         QStringList result = dir.entryList(QDir::Files);
560         foreach(const QString & path, result) {
561             if (rx.exactMatch(path)) count++;
562         }
563         urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ')');
564     }
565     return urls;
566 }
567
568 void ProjectSettings::slotExportToText()
569 {
570     QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
571     if (savePath.isEmpty()) return;
572     QString data;
573     data.append(i18n("Project folder: %1",  project_folder->url().path()) + '\n');
574     data.append(i18n("Project profile: %1",  profiles_list->currentText()) + '\n');
575     data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
576     for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
577         if (files_list->topLevelItem(i)->childCount() > 0) {
578             data.append('\n' + files_list->topLevelItem(i)->text(0) + ":\n\n");
579             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
580                 data.append(files_list->topLevelItem(i)->child(j)->text(0) + '\n');
581             }
582         }
583     }
584     KTemporaryFile tmpfile;
585     if (!tmpfile.open()) {
586         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
587         return;
588     }
589     QFile xmlf(tmpfile.fileName());
590     if (!xmlf.open(QIODevice::WriteOnly))
591         return;
592     xmlf.write(data.toUtf8());
593     if (xmlf.error() != QFile::NoError) {
594         xmlf.close();
595         return;
596     }
597     xmlf.close();
598     KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
599 }
600
601 void ProjectSettings::slotUpdateProxyParams()
602 {
603     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
604     proxyparams->setPlainText(params.section(';', 0, 0));
605 }
606
607 const QMap <QString, QString> ProjectSettings::metadata() const
608 {
609     QMap <QString, QString> metadata;
610     for (int i = 0; i < metadata_list->topLevelItemCount(); ++i)
611     {
612         QTreeWidgetItem *item = metadata_list->topLevelItem(i);
613         if (!item->text(1).simplified().isEmpty()) {
614             // Insert metadata entry
615             QString key = item->data(0, Qt::UserRole).toString();
616             if (key.isEmpty()) key = "meta.attr." + item->text(0).simplified() + ".markup";
617             QString value = item->text(1);
618             metadata.insert(key, value);
619         }
620     }
621     return metadata;
622 }
623
624 void ProjectSettings::slotAddMetadataField()
625 {
626     QTreeWidgetItem *item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("field_name"));
627     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
628 }
629
630 void ProjectSettings::slotDeleteMetadataField()
631 {
632     QTreeWidgetItem *item = metadata_list->currentItem();
633     if (item) delete item;
634 }
635
636 #include "projectsettings.moc"
637
638