1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
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. *
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. *
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 ***************************************************************************/
20 #include "projectsettings.h"
21 #include "kdenlivesettings.h"
22 #include "profilesdialog.h"
23 #include "docclipbase.h"
24 #include "titlewidget.h"
25 #include "effectslist.h"
27 #include <KStandardDirs>
28 #include <KMessageBox>
30 #include <kio/directorysizejob.h>
31 #include <KIO/NetAccess>
32 #include <KTemporaryFile>
33 #include <KFileDialog>
36 #include <kmessagebox.h>
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)
43 list_search->setTreeWidget(files_list);
45 QMap <QString, QString> profilesInfo = ProfilesDialog::getProfilesInfo();
46 QMapIterator<QString, QString> i(profilesInfo);
49 profiles_list->addItem(i.key(), i.value());
51 project_folder->setMode(KFile::Directory);
52 project_folder->setUrl(KUrl(projectPath));
53 QString currentProf = KdenliveSettings::current_profile();
55 for (int i = 0; i < profiles_list->count(); ++i) {
56 if (profiles_list->itemData(i).toString() == currentProf) {
57 profiles_list->setCurrentIndex(i);
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;
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");
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();
92 proxy_minsize->setEnabled(generate_proxy->isChecked());
93 proxy_imageminsize->setEnabled(generate_imageproxy->isChecked());
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);
102 while (k.hasNext()) {
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();
111 proxy_profile->addItem(k.key(), k.value());
115 // Current project proxy settings not found
116 ix = proxy_profile->count();
117 proxy_profile->addItem(i18n("Current Settings"), QString(proxyparameters + ';' + proxyextension));
119 proxy_profile->setCurrentIndex(ix);
120 slotUpdateProxyParams();
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)));
129 if (readOnlyTracks) {
130 video_tracks->setEnabled(false);
131 audio_tracks->setEnabled(false);
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");
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");
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");
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");
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");
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");
175 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
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);
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"));
192 if (m_projectList != NULL) {
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()));
203 void ProjectSettings::slotDeleteUnused()
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();
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());
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();
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();
236 void ProjectSettings::slotClearCache()
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);
245 void ProjectSettings::slotDeleteProxies()
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);
257 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
259 KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
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/");
265 proxy_count->setText(QString::number(job->totalFiles()));
266 proxy_size->setText(KIO::convertSize(job->totalSize()));
268 if (cacheOnly) return;
271 KIO::filesize_t usedSize = 0;
272 KIO::filesize_t unUsedSize = 0;
273 QList <DocClipBase*> list = m_projectList->documentClipList();
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?
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);
303 QStringList allFonts;
304 foreach(const QString & file, m_lumas) {
306 new QTreeWidgetItem(images, QStringList() << file);
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) {
315 new QTreeWidgetItem(slideshows, QStringList() << file);
317 } else if (!clip->fileURL().isEmpty()) {
318 //allFiles.append(clip->fileURL().path());
319 switch (clip->clipType()) {
321 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
324 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
327 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
330 new QTreeWidgetItem(playlists, QStringList() << clip->fileURL().path());
333 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
336 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
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) {
346 new QTreeWidgetItem(images, QStringList() << file);
349 } else if (clip->clipType() == PLAYLIST) {
350 QStringList files = extractPlaylistUrls(clip->fileURL().path());
351 foreach(const QString & file, files) {
353 new QTreeWidgetItem(others, QStringList() << file);
357 if (clip->numReferences() == 0) {
359 unUsedSize += clip->fileSize();
362 usedSize += clip->fileSize();
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);
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);
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);
385 void ProjectSettings::accept()
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;
392 void ProjectSettings::slotUpdateDisplay()
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)));
405 p_progressive->setText(i18n("Progressive"));
407 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
410 void ProjectSettings::slotUpdateButton(const QString &path)
412 if (path.isEmpty()) m_buttonOk->setEnabled(false);
414 m_buttonOk->setEnabled(true);
415 slotUpdateFiles(true);
419 QString ProjectSettings::selectedProfile() const
421 return profiles_list->itemData(profiles_list->currentIndex()).toString();
424 KUrl ProjectSettings::selectedFolder() const
426 return project_folder->url();
429 QPoint ProjectSettings::tracks()
432 p.setX(video_tracks->value());
433 p.setY(audio_tracks->value());
437 bool ProjectSettings::enableVideoThumbs() const
439 return video_thumbs->isChecked();
442 bool ProjectSettings::enableAudioThumbs() const
444 return audio_thumbs->isChecked();
447 bool ProjectSettings::useProxy() const
449 return enable_proxy->isChecked();
452 bool ProjectSettings::generateProxy() const
454 return generate_proxy->isChecked();
457 bool ProjectSettings::generateImageProxy() const
459 return generate_imageproxy->isChecked();
462 int ProjectSettings::proxyMinSize() const
464 return proxy_minsize->value();
467 int ProjectSettings::proxyImageMinSize() const
469 return proxy_imageminsize->value();
472 QString ProjectSettings::proxyParams() const
474 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
475 return params.section(';', 0, 0);
478 QString ProjectSettings::proxyExtension() const
480 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
481 return params.section(';', 1, 1);
485 QStringList ProjectSettings::extractPlaylistUrls(const QString &path)
490 if (!file.open(QIODevice::ReadOnly))
492 if (!doc.setContent(&file)) {
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);
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));
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);
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);
538 QStringList ProjectSettings::extractSlideshowUrls(const KUrl &url)
541 QString path = url.directory(KUrl::AppendTrailingSlash);
542 QString ext = url.path().section('.', -1);
544 if (url.path().contains(".all.")) {
545 // this is a mime slideshow, like *.jpeg
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()) + ')');
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 + '$';
559 QStringList result = dir.entryList(QDir::Files);
560 foreach(const QString & path, result) {
561 if (rx.exactMatch(path)) count++;
563 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ')');
568 void ProjectSettings::slotExportToText()
570 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
571 if (savePath.isEmpty()) return;
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');
584 KTemporaryFile tmpfile;
585 if (!tmpfile.open()) {
586 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
589 QFile xmlf(tmpfile.fileName());
590 if (!xmlf.open(QIODevice::WriteOnly))
592 xmlf.write(data.toUtf8());
593 if (xmlf.error() != QFile::NoError) {
598 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
601 void ProjectSettings::slotUpdateProxyParams()
603 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
604 proxyparams->setPlainText(params.section(';', 0, 0));
607 const QMap <QString, QString> ProjectSettings::metadata() const
609 QMap <QString, QString> metadata;
610 for (int i = 0; i < metadata_list->topLevelItemCount(); ++i)
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);
624 void ProjectSettings::slotAddMetadataField()
626 QTreeWidgetItem *item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("field_name"));
627 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
630 void ProjectSettings::slotDeleteMetadataField()
632 QTreeWidgetItem *item = metadata_list->currentItem();
633 if (item) delete item;
636 #include "projectsettings.moc"