]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
Initial support for project metadata (that can be embedded in rendered files)
[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 "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, 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::FullConfig, "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")) item->setText(1, metadata.value("meta.attr.title.markup"));
139     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
140     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Author"));
141     item->setData(0, Qt::UserRole, QString("meta.attr.author.markup"));
142     if (metadata.contains("meta.attr.author.markup")) item->setText(1, metadata.value("meta.attr.author.markup"));
143     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
144     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Copyright"));
145     item->setData(0, Qt::UserRole, QString("meta.attr.copyright.markup"));
146     if (metadata.contains("meta.attr.copyright.markup")) item->setText(1, metadata.value("meta.attr.copyright.markup"));
147     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
148     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Year"));
149     item->setData(0, Qt::UserRole, QString("meta.attr.year.markup"));
150     if (metadata.contains("meta.attr.year.markup")) item->setText(1, metadata.value("meta.attr.year.markup"));
151     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
152     
153     slotUpdateDisplay();
154     if (m_projectList != NULL) {
155         slotUpdateFiles();
156         connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
157         connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
158         connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
159     } else tabWidget->widget(1)->setEnabled(false);
160     connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
161     connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
162     connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
163 }
164
165 void ProjectSettings::slotDeleteUnused()
166 {
167     QStringList toDelete;
168     QList <DocClipBase*> list = m_projectList->documentClipList();
169     for (int i = 0; i < list.count(); i++) {
170         DocClipBase *clip = list.at(i);
171         if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
172             KUrl url = clip->fileURL();
173             if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
174         }
175     }
176
177     // make sure our urls are not used in another clip
178     for (int i = 0; i < list.count(); i++) {
179         DocClipBase *clip = list.at(i);
180         if (clip->numReferences() > 0) {
181             KUrl url = clip->fileURL();
182             if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
183         }
184     }
185
186     if (toDelete.count() == 0) {
187         // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
188         if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
189         m_projectList->cleanup();
190         slotUpdateFiles();
191         return;
192     }
193     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;
194     m_projectList->trashUnusedClips();
195     slotUpdateFiles();
196 }
197
198 void ProjectSettings::slotClearCache()
199 {
200     buttonBox->setEnabled(false);
201     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
202     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
203     buttonBox->setEnabled(true);
204     slotUpdateFiles(true);
205 }
206
207 void ProjectSettings::slotDeleteProxies()
208 {
209     if (KMessageBox::warningContinueCancel(this, i18n("Deleting proxy clips will disable proxies for this project.")) != KMessageBox::Continue) return;
210     buttonBox->setEnabled(false);
211     enable_proxy->setChecked(false);
212     emit disableProxies();
213     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
214     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
215     buttonBox->setEnabled(true);
216     slotUpdateFiles(true);
217 }
218
219 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
220 {
221     KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
222     job->exec();
223     thumbs_count->setText(QString::number(job->totalFiles()));
224     thumbs_size->setText(KIO::convertSize(job->totalSize()));
225     job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
226     job->exec();
227     proxy_count->setText(QString::number(job->totalFiles()));
228     proxy_size->setText(KIO::convertSize(job->totalSize()));
229     delete job;
230     if (cacheOnly) return;
231     int unused = 0;
232     int used = 0;
233     KIO::filesize_t usedSize = 0;
234     KIO::filesize_t unUsedSize = 0;
235     QList <DocClipBase*> list = m_projectList->documentClipList();
236     files_list->clear();
237
238     // List all files that are used in the project. That also means:
239     // images included in slideshow and titles, files in playlist clips
240     // TODO: images used in luma transitions?
241
242     // Setup categories
243     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
244     videos->setIcon(0, KIcon("video-x-generic"));
245     videos->setExpanded(true);
246     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
247     sounds->setIcon(0, KIcon("audio-x-generic"));
248     sounds->setExpanded(true);
249     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
250     images->setIcon(0, KIcon("image-x-generic"));
251     images->setExpanded(true);
252     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
253     slideshows->setIcon(0, KIcon("image-x-generic"));
254     slideshows->setExpanded(true);
255     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
256     texts->setIcon(0, KIcon("text-plain"));
257     texts->setExpanded(true);
258     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
259     others->setIcon(0, KIcon("unknown"));
260     others->setExpanded(true);
261     int count = 0;
262     QStringList allFonts;
263     foreach(const QString & file, m_lumas) {
264         count++;
265         new QTreeWidgetItem(images, QStringList() << file);
266     }
267
268     for (int i = 0; i < list.count(); i++) {
269         DocClipBase *clip = list.at(i);
270         if (clip->clipType() == SLIDESHOW) {
271             QStringList subfiles = extractSlideshowUrls(clip->fileURL());
272             foreach(const QString & file, subfiles) {
273                 count++;
274                 new QTreeWidgetItem(slideshows, QStringList() << file);
275             }
276         } else if (!clip->fileURL().isEmpty()) {
277             //allFiles.append(clip->fileURL().path());
278             switch (clip->clipType()) {
279             case TEXT:
280                 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
281                 break;
282             case AUDIO:
283                 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
284                 break;
285             case IMAGE:
286                 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
287                 break;
288             case UNKNOWN:
289                 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
290                 break;
291             default:
292                 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
293                 break;
294             }
295             count++;
296         }
297         if (clip->clipType() == TEXT) {
298             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
299             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
300             foreach(const QString & file, imagefiles) {
301                 count++;
302                 new QTreeWidgetItem(images, QStringList() << file);
303             }
304             allFonts << fonts;
305         } else if (clip->clipType() == PLAYLIST) {
306             QStringList files = extractPlaylistUrls(clip->fileURL().path());
307             foreach(const QString & file, files) {
308                 count++;
309                 new QTreeWidgetItem(others, QStringList() << file);
310             }
311         }
312
313         if (clip->numReferences() == 0) {
314             unused++;
315             unUsedSize += clip->fileSize();
316         } else {
317             used++;
318             usedSize += clip->fileSize();
319         }
320     }
321     allFonts.removeDuplicates();
322     // Hide unused categories
323     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
324         if (files_list->topLevelItem(i)->childCount() == 0) {
325             files_list->topLevelItem(i)->setHidden(true);
326         }
327     }
328     files_count->setText(QString::number(count));
329     fonts_list->addItems(allFonts);
330     if (allFonts.isEmpty()) {
331         fonts_list->setHidden(true);
332         label_fonts->setHidden(true);
333     }
334     used_count->setText(QString::number(used));
335     used_size->setText(KIO::convertSize(usedSize));
336     unused_count->setText(QString::number(unused));
337     unused_size->setText(KIO::convertSize(unUsedSize));
338     delete_unused->setEnabled(unused > 0);
339 }
340
341 void ProjectSettings::accept()
342 {
343     if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
344         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;
345     QDialog::accept();
346 }
347
348 void ProjectSettings::slotUpdateDisplay()
349 {
350     QLocale locale;
351     QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
352     QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
353     p_size->setText(values.value("width") + 'x' + values.value("height"));
354     p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
355     p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
356     p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
357     if (values.value("progressive").toInt() == 0) {
358         p_progressive->setText(i18n("Interlaced (%1 fields per second)",
359                                     locale.toString((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
360     } else {
361         p_progressive->setText(i18n("Progressive"));
362     }
363     p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
364 }
365
366 void ProjectSettings::slotUpdateButton(const QString &path)
367 {
368     if (path.isEmpty()) m_buttonOk->setEnabled(false);
369     else {
370         m_buttonOk->setEnabled(true);
371         slotUpdateFiles(true);
372     }
373 }
374
375 QString ProjectSettings::selectedProfile() const
376 {
377     return profiles_list->itemData(profiles_list->currentIndex()).toString();
378 }
379
380 KUrl ProjectSettings::selectedFolder() const
381 {
382     return project_folder->url();
383 }
384
385 QPoint ProjectSettings::tracks()
386 {
387     QPoint p;
388     p.setX(video_tracks->value());
389     p.setY(audio_tracks->value());
390     return p;
391 }
392
393 bool ProjectSettings::enableVideoThumbs() const
394 {
395     return video_thumbs->isChecked();
396 }
397
398 bool ProjectSettings::enableAudioThumbs() const
399 {
400     return audio_thumbs->isChecked();
401 }
402
403 bool ProjectSettings::useProxy() const
404 {
405     return enable_proxy->isChecked();
406 }
407
408 bool ProjectSettings::generateProxy() const
409 {
410     return generate_proxy->isChecked();
411 }
412
413 bool ProjectSettings::generateImageProxy() const
414 {
415     return generate_imageproxy->isChecked();
416 }
417
418 int ProjectSettings::proxyMinSize() const
419 {
420     return proxy_minsize->value();
421 }
422
423 int ProjectSettings::proxyImageMinSize() const
424 {
425     return proxy_imageminsize->value();
426 }
427
428 QString ProjectSettings::proxyParams() const
429 {
430     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
431     return params.section(';', 0, 0);
432 }
433
434 QString ProjectSettings::proxyExtension() const
435 {
436     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
437     return params.section(';', 1, 1);
438 }
439
440 //static
441 QStringList ProjectSettings::extractPlaylistUrls(QString path)
442 {
443     QStringList urls;
444     QDomDocument doc;
445     QFile file(path);
446     if (!file.open(QIODevice::ReadOnly))
447         return urls;
448     if (!doc.setContent(&file)) {
449         file.close();
450         return urls;
451     }
452     file.close();
453     QString root = doc.documentElement().attribute("root");
454     if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
455     QDomNodeList files = doc.elementsByTagName("producer");
456     for (int i = 0; i < files.count(); i++) {
457         QDomElement e = files.at(i).toElement();
458         QString type = EffectsList::property(e, "mlt_service");
459         if (type != "colour") {
460             QString url = EffectsList::property(e, "resource");
461             if (!url.isEmpty()) {
462                 if (!url.startsWith('/')) url.prepend(root);
463                 if (url.section('.', 0, -2).endsWith("/.all")) {
464                     // slideshow clip, extract image urls
465                     urls << extractSlideshowUrls(KUrl(url));
466                 } else urls << url;
467                 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
468                     //TODO: Do something to avoid infinite loops if 2 files reference themselves...
469                     urls << extractPlaylistUrls(url);
470                 }
471             }
472         }
473     }
474
475     // luma files for transitions
476     files = doc.elementsByTagName("transition");
477     for (int i = 0; i < files.count(); i++) {
478         QDomElement e = files.at(i).toElement();
479         QString url = EffectsList::property(e, "luma");
480         if (!url.isEmpty()) {
481             if (!url.startsWith('/')) url.prepend(root);
482             urls << url;
483         }
484     }
485
486     return urls;
487 }
488
489
490 //static
491 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
492 {
493     QStringList urls;
494     QString path = url.directory(KUrl::AppendTrailingSlash);
495     QString ext = url.path().section('.', -1);
496     QDir dir(path);
497     if (url.path().contains(".all.")) {
498         // this is a mime slideshow, like *.jpeg
499         QStringList filters;
500         filters << "*." + ext;
501         dir.setNameFilters(filters);
502         QStringList result = dir.entryList(QDir::Files);
503         urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
504     } else {
505         // this is a pattern slideshow, like sequence%4d.jpg
506         QString filter = url.fileName();
507         QString ext = filter.section('.', -1);
508         filter = filter.section('%', 0, -2);
509         QString regexp = "^" + filter + "\\d+\\." + ext + "$";
510         QRegExp rx(regexp);
511         int count = 0;
512         QStringList result = dir.entryList(QDir::Files);
513         foreach(const QString & path, result) {
514             if (rx.exactMatch(path)) count++;
515         }
516         urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
517     }
518     return urls;
519 }
520
521 void ProjectSettings::slotExportToText()
522 {
523     QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
524     if (savePath.isEmpty()) return;
525     QString data;
526     data.append(i18n("Project folder: %1",  project_folder->url().path()) + "\n");
527     data.append(i18n("Project profile: %1",  profiles_list->currentText()) + "\n");
528     data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
529     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
530         if (files_list->topLevelItem(i)->childCount() > 0) {
531             data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
532             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
533                 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
534             }
535         }
536     }
537     KTemporaryFile tmpfile;
538     if (!tmpfile.open()) {
539         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
540         return;
541     }
542     QFile xmlf(tmpfile.fileName());
543     xmlf.open(QIODevice::WriteOnly);
544     xmlf.write(data.toUtf8());
545     if (xmlf.error() != QFile::NoError) {
546         xmlf.close();
547         return;
548     }
549     xmlf.close();
550     KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
551 }
552
553 void ProjectSettings::slotUpdateProxyParams()
554 {
555     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
556     proxyparams->setPlainText(params.section(';', 0, 0));
557 }
558
559 const QMap <QString, QString> ProjectSettings::metadata() const
560 {
561     QMap <QString, QString> metadata;
562     for (int i = 0; i < metadata_list->topLevelItemCount(); i++)
563     {
564         QTreeWidgetItem *item = metadata_list->topLevelItem(i);
565         if (!item->text(1).simplified().isEmpty()) {
566             // Insert metadata entry
567             QString key = item->data(0, Qt::UserRole).toString();
568             QString value = item->text(1);
569             metadata.insert(key, value);
570         }
571     }
572     return metadata;
573 }
574
575 #include "projectsettings.moc"
576
577