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