]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
Small GUI fix
[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, 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     proxy_params->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
69     connect(generate_proxy, SIGNAL(toggled(bool)), proxy_minsize, SLOT(setEnabled(bool)));
70     connect(generate_imageproxy, SIGNAL(toggled(bool)), proxy_imageminsize, SLOT(setEnabled(bool)));
71     
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         proxy_params->setPlainText(projectlist->getDocumentProperty("proxyparams"));
77         generate_imageproxy->setChecked(projectlist->getDocumentProperty("generateimageproxy").toInt());
78         proxy_imageminsize->setValue(projectlist->getDocumentProperty("proxyimageminsize").toInt());
79         proxy_extension->setText(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         proxy_params->setPlainText(KdenliveSettings::proxyparams());
86         generate_imageproxy->setChecked(KdenliveSettings::generateimageproxy());
87         proxy_imageminsize->setValue(KdenliveSettings::proxyimageminsize());
88         proxy_extension->setText(KdenliveSettings::proxyextension());
89       
90     }
91
92     proxy_minsize->setEnabled(generate_proxy->isChecked());
93     proxy_imageminsize->setEnabled(generate_imageproxy->isChecked());
94     
95     if (readOnlyTracks) {
96         video_tracks->setEnabled(false);
97         audio_tracks->setEnabled(false);
98     }
99     slotUpdateDisplay();
100     if (m_projectList != NULL) {
101         slotUpdateFiles();
102         connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
103         connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
104         connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
105     } else tabWidget->widget(1)->setEnabled(false);
106     connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
107     connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
108     connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
109 }
110
111 void ProjectSettings::slotDeleteUnused()
112 {
113     QStringList toDelete;
114     QList <DocClipBase*> list = m_projectList->documentClipList();
115     for (int i = 0; i < list.count(); i++) {
116         DocClipBase *clip = list.at(i);
117         if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
118             KUrl url = clip->fileURL();
119             if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
120         }
121     }
122
123     // make sure our urls are not used in another clip
124     for (int i = 0; i < list.count(); i++) {
125         DocClipBase *clip = list.at(i);
126         if (clip->numReferences() > 0) {
127             KUrl url = clip->fileURL();
128             if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
129         }
130     }
131
132     if (toDelete.count() == 0) {
133         // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
134         if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
135         m_projectList->cleanup();
136         slotUpdateFiles();
137         return;
138     }
139     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;
140     m_projectList->trashUnusedClips();
141     slotUpdateFiles();
142 }
143
144 void ProjectSettings::slotClearCache()
145 {
146     buttonBox->setEnabled(false);
147     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
148     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
149     buttonBox->setEnabled(true);
150     slotUpdateFiles(true);
151 }
152
153 void ProjectSettings::slotDeleteProxies()
154 {
155     buttonBox->setEnabled(false);
156     
157     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
158     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
159     buttonBox->setEnabled(true);
160     slotUpdateFiles(true);
161 }
162
163 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
164 {
165     KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
166     job->exec();
167     thumbs_count->setText(QString::number(job->totalFiles()));
168     thumbs_size->setText(KIO::convertSize(job->totalSize()));
169     job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
170     job->exec();
171     proxy_count->setText(QString::number(job->totalFiles()));
172     proxy_size->setText(KIO::convertSize(job->totalSize()));
173     delete job;
174     if (cacheOnly) return;
175     int unused = 0;
176     int used = 0;
177     KIO::filesize_t usedSize = 0;
178     KIO::filesize_t unUsedSize = 0;
179     QList <DocClipBase*> list = m_projectList->documentClipList();
180     files_list->clear();
181
182     // List all files that are used in the project. That also means:
183     // images included in slideshow and titles, files in playlist clips
184     // TODO: images used in luma transitions, files used for LADSPA effects?
185
186     // Setup categories
187     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
188     videos->setIcon(0, KIcon("video-x-generic"));
189     videos->setExpanded(true);
190     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
191     sounds->setIcon(0, KIcon("audio-x-generic"));
192     sounds->setExpanded(true);
193     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
194     images->setIcon(0, KIcon("image-x-generic"));
195     images->setExpanded(true);
196     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
197     slideshows->setIcon(0, KIcon("image-x-generic"));
198     slideshows->setExpanded(true);
199     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
200     texts->setIcon(0, KIcon("text-plain"));
201     texts->setExpanded(true);
202     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
203     others->setIcon(0, KIcon("unknown"));
204     others->setExpanded(true);
205     int count = 0;
206     QStringList allFonts;
207     foreach(const QString & file, m_lumas) {
208         count++;
209         new QTreeWidgetItem(images, QStringList() << file);
210     }
211
212     for (int i = 0; i < list.count(); i++) {
213         DocClipBase *clip = list.at(i);
214         if (clip->clipType() == SLIDESHOW) {
215             QStringList subfiles = extractSlideshowUrls(clip->fileURL());
216             foreach(const QString & file, subfiles) {
217                 count++;
218                 new QTreeWidgetItem(slideshows, QStringList() << file);
219             }
220         } else if (!clip->fileURL().isEmpty()) {
221             //allFiles.append(clip->fileURL().path());
222             switch (clip->clipType()) {
223             case TEXT:
224                 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
225                 break;
226             case AUDIO:
227                 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
228                 break;
229             case IMAGE:
230                 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
231                 break;
232             case UNKNOWN:
233                 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
234                 break;
235             default:
236                 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
237                 break;
238             }
239             count++;
240         }
241         if (clip->clipType() == TEXT) {
242             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
243             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
244             foreach(const QString & file, imagefiles) {
245                 count++;
246                 new QTreeWidgetItem(images, QStringList() << file);
247             }
248             allFonts << fonts;
249         } else if (clip->clipType() == PLAYLIST) {
250             QStringList files = extractPlaylistUrls(clip->fileURL().path());
251             foreach(const QString & file, files) {
252                 count++;
253                 new QTreeWidgetItem(others, QStringList() << file);
254             }
255         }
256
257         if (clip->numReferences() == 0) {
258             unused++;
259             unUsedSize += clip->fileSize();
260         } else {
261             used++;
262             usedSize += clip->fileSize();
263         }
264     }
265 #if QT_VERSION >= 0x040500
266     allFonts.removeDuplicates();
267 #endif
268     // Hide unused categories
269     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
270         if (files_list->topLevelItem(i)->childCount() == 0) {
271             files_list->topLevelItem(i)->setHidden(true);
272         }
273     }
274     files_count->setText(QString::number(count));
275     fonts_list->addItems(allFonts);
276     if (allFonts.isEmpty()) {
277         fonts_list->setHidden(true);
278         label_fonts->setHidden(true);
279     }
280     used_count->setText(QString::number(used));
281     used_size->setText(KIO::convertSize(usedSize));
282     unused_count->setText(QString::number(unused));
283     unused_size->setText(KIO::convertSize(unUsedSize));
284     delete_unused->setEnabled(unused > 0);
285 }
286
287 void ProjectSettings::accept()
288 {
289     if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
290         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;
291     QDialog::accept();
292 }
293
294 void ProjectSettings::slotUpdateDisplay()
295 {
296     QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
297     QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
298     p_size->setText(values.value("width") + 'x' + values.value("height"));
299     p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
300     p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
301     p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
302     if (values.value("progressive").toInt() == 0) {
303         p_progressive->setText(i18n("Interlaced (%1 fields per second)",
304                                     QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
305     } else {
306         p_progressive->setText(i18n("Progressive"));
307     }
308     p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
309 }
310
311 void ProjectSettings::slotUpdateButton(const QString &path)
312 {
313     if (path.isEmpty()) m_buttonOk->setEnabled(false);
314     else {
315         m_buttonOk->setEnabled(true);
316         slotUpdateFiles(true);
317     }
318 }
319
320 QString ProjectSettings::selectedProfile() const
321 {
322     return profiles_list->itemData(profiles_list->currentIndex()).toString();
323 }
324
325 KUrl ProjectSettings::selectedFolder() const
326 {
327     return project_folder->url();
328 }
329
330 QPoint ProjectSettings::tracks()
331 {
332     QPoint p;
333     p.setX(video_tracks->value());
334     p.setY(audio_tracks->value());
335     return p;
336 }
337
338 bool ProjectSettings::enableVideoThumbs() const
339 {
340     return video_thumbs->isChecked();
341 }
342
343 bool ProjectSettings::enableAudioThumbs() const
344 {
345     return audio_thumbs->isChecked();
346 }
347
348 bool ProjectSettings::useProxy() const
349 {
350     return enable_proxy->isChecked();
351 }
352
353 bool ProjectSettings::generateProxy() const
354 {
355     return generate_proxy->isChecked();
356 }
357
358 bool ProjectSettings::generateImageProxy() const
359 {
360     return generate_imageproxy->isChecked();
361 }
362
363 int ProjectSettings::proxyMinSize() const
364 {
365     return proxy_minsize->value();
366 }
367
368 int ProjectSettings::proxyImageMinSize() const
369 {
370     return proxy_imageminsize->value();
371 }
372
373 QString ProjectSettings::proxyParams() const
374 {
375     return proxy_params->toPlainText();
376 }
377
378 QString ProjectSettings::proxyExtension() const
379 {
380     return proxy_extension->text();
381 }
382
383 //static
384 QStringList ProjectSettings::extractPlaylistUrls(QString path)
385 {
386     QStringList urls;
387     QDomDocument doc;
388     QFile file(path);
389     if (!file.open(QIODevice::ReadOnly))
390         return urls;
391     if (!doc.setContent(&file)) {
392         file.close();
393         return urls;
394     }
395     file.close();
396     QString root = doc.documentElement().attribute("root");
397     if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
398     QDomNodeList files = doc.elementsByTagName("producer");
399     for (int i = 0; i < files.count(); i++) {
400         QDomElement e = files.at(i).toElement();
401         QString type = EffectsList::property(e, "mlt_service");
402         if (type != "colour") {
403             QString url = EffectsList::property(e, "resource");
404             if (!url.isEmpty()) {
405                 if (!url.startsWith('/')) url.prepend(root);
406                 if (url.section('.', 0, -2).endsWith("/.all")) {
407                     // slideshow clip, extract image urls
408                     urls << extractSlideshowUrls(KUrl(url));
409                 } else urls << url;
410                 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
411                     //TODO: Do something to avoid infinite loops if 2 files reference themselves...
412                     urls << extractPlaylistUrls(url);
413                 }
414             }
415         }
416     }
417
418     // luma files for transitions
419     files = doc.elementsByTagName("transition");
420     for (int i = 0; i < files.count(); i++) {
421         QDomElement e = files.at(i).toElement();
422         QString url = EffectsList::property(e, "luma");
423         if (!url.isEmpty()) {
424             if (!url.startsWith('/')) url.prepend(root);
425             urls << url;
426         }
427     }
428
429     return urls;
430 }
431
432
433 //static
434 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
435 {
436     QStringList urls;
437     QString path = url.directory(KUrl::AppendTrailingSlash);
438     QString ext = url.path().section('.', -1);
439     QDir dir(path);
440     if (url.path().contains(".all.")) {
441         // this is a mime slideshow, like *.jpeg
442         QStringList filters;
443         filters << "*." + ext;
444         dir.setNameFilters(filters);
445         QStringList result = dir.entryList(QDir::Files);
446         urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
447     } else {
448         // this is a pattern slideshow, like sequence%4d.jpg
449         QString filter = url.fileName();
450         QString ext = filter.section('.', -1);
451         filter = filter.section('%', 0, -2);
452         QString regexp = "^" + filter + "\\d+\\." + ext + "$";
453         QRegExp rx(regexp);
454         int count = 0;
455         QStringList result = dir.entryList(QDir::Files);
456         foreach(const QString & path, result) {
457             if (rx.exactMatch(path)) count++;
458         }
459         urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
460     }
461     return urls;
462 }
463
464 void ProjectSettings::slotExportToText()
465 {
466     QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
467     if (savePath.isEmpty()) return;
468     QString data;
469     data.append(i18n("Project folder: %1",  project_folder->url().path()) + "\n");
470     data.append(i18n("Project profile: %1",  profiles_list->currentText()) + "\n");
471     data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
472     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
473         if (files_list->topLevelItem(i)->childCount() > 0) {
474             data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
475             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
476                 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
477             }
478         }
479     }
480     KTemporaryFile tmpfile;
481     if (!tmpfile.open()) {
482         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
483         return;
484     }
485     QFile xmlf(tmpfile.fileName());
486     xmlf.open(QIODevice::WriteOnly);
487     xmlf.write(data.toUtf8());
488     if (xmlf.error() != QFile::NoError) {
489         xmlf.close();
490         return;
491     }
492     xmlf.close();
493     KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
494 }
495
496
497
498 #include "projectsettings.moc"
499
500