]> git.sesse.net Git - kdenlive/blob - src/clipproperties.cpp
Reindent the codebase using 'linux' bracket placement.
[kdenlive] / src / clipproperties.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
21 #include "clipproperties.h"
22 #include "kdenlivesettings.h"
23 #include "kthumb.h"
24 #include "markerdialog.h"
25
26 #include <KStandardDirs>
27 #include <KDebug>
28 #include <KFileItem>
29
30 #include <QDir>
31
32 static const int VIDEOTAB = 0;
33 static const int AUDIOTAB = 1;
34 static const int COLORTAB = 2;
35 static const int SLIDETAB = 3;
36 static const int IMAGETAB = 4;
37 static const int MARKERTAB = 5;
38 static const int METATAB = 6;
39 static const int ADVANCEDTAB = 7;
40
41 static const int TYPE_JPEG = 0;
42 static const int TYPE_PNG = 1;
43 static const int TYPE_BMP = 2;
44 static const int TYPE_GIF = 3;
45
46 ClipProperties::ClipProperties(DocClipBase *clip, Timecode tc, double fps, QWidget * parent): QDialog(parent), m_tc(tc), m_clip(clip), m_fps(fps), m_clipNeedsRefresh(false), m_count(0)
47 {
48     setFont(KGlobalSettings::toolBarFont());
49     m_view.setupUi(this);
50     KUrl url = m_clip->fileURL();
51     m_view.clip_path->setText(url.path());
52     m_view.clip_description->setText(m_clip->description());
53     QMap <QString, QString> props = m_clip->properties();
54
55     if (props.contains("force_aspect_ratio") && props.value("force_aspect_ratio").toDouble() > 0) {
56         m_view.clip_force_ar->setChecked(true);
57         m_view.clip_ar->setEnabled(true);
58         m_view.clip_ar->setValue(props.value("force_aspect_ratio").toDouble());
59     }
60
61     if (props.contains("threads") && props.value("threads").toInt() != 1) {
62         m_view.clip_force_threads->setChecked(true);
63         m_view.clip_threads->setEnabled(true);
64         m_view.clip_threads->setValue(props.value("threads").toInt());
65     }
66
67     if (props.contains("video_index") && props.value("video_index").toInt() != 0) {
68         m_view.clip_force_vindex->setChecked(true);
69         m_view.clip_vindex->setEnabled(true);
70         m_view.clip_vindex->setValue(props.value("video_index").toInt());
71     }
72
73     if (props.contains("audio_index") && props.value("audio_index").toInt() != 0) {
74         m_view.clip_force_aindex->setChecked(true);
75         m_view.clip_aindex->setEnabled(true);
76         m_view.clip_aindex->setValue(props.value("audio_index").toInt());
77     }
78
79     if (props.contains("audio_max")) {
80         m_view.clip_aindex->setMaximum(props.value("audio_max").toInt());
81     }
82
83     if (props.contains("video_max")) {
84         m_view.clip_vindex->setMaximum(props.value("video_max").toInt());
85     }
86
87     // Check for Metadata
88     QMap<QString, QString> meta = m_clip->metadata();
89     QMap<QString, QString>::const_iterator i = meta.constBegin();
90     while (i != meta.constEnd()) {
91         QTreeWidgetItem *metaitem = new QTreeWidgetItem(m_view.metadata_list);
92         metaitem->setText(0, i.key()); //i18n(i.key().section('.', 2, 3).toUtf8().data()));
93         metaitem->setText(1, i.value());
94         ++i;
95     }
96
97     connect(m_view.clip_force_ar, SIGNAL(toggled(bool)), m_view.clip_ar, SLOT(setEnabled(bool)));
98     connect(m_view.clip_force_threads, SIGNAL(toggled(bool)), m_view.clip_threads, SLOT(setEnabled(bool)));
99     connect(m_view.clip_force_vindex, SIGNAL(toggled(bool)), m_view.clip_vindex, SLOT(setEnabled(bool)));
100     connect(m_view.clip_force_aindex, SIGNAL(toggled(bool)), m_view.clip_aindex, SLOT(setEnabled(bool)));
101
102     if (props.contains("audiocodec"))
103         m_view.clip_acodec->setText(props.value("audiocodec"));
104     if (props.contains("frequency"))
105         m_view.clip_frequency->setText(props.value("frequency"));
106     if (props.contains("channels"))
107         m_view.clip_channels->setText(props.value("channels"));
108
109     CLIPTYPE t = m_clip->clipType();
110     if (t != AUDIO && t != AV) {
111         m_view.clip_force_aindex->setEnabled(false);
112     }
113
114     if (t != VIDEO && t != AV) {
115         m_view.clip_force_vindex->setEnabled(false);
116     }
117
118     if (t == IMAGE) {
119         m_view.tabWidget->removeTab(SLIDETAB);
120         m_view.tabWidget->removeTab(COLORTAB);
121         m_view.tabWidget->removeTab(AUDIOTAB);
122         m_view.tabWidget->removeTab(VIDEOTAB);
123         if (props.contains("frame_size"))
124             m_view.image_size->setText(props.value("frame_size"));
125         if (props.contains("transparency"))
126             m_view.image_transparency->setChecked(props.value("transparency").toInt());
127     } else if (t == COLOR) {
128         m_view.clip_path->setEnabled(false);
129         m_view.tabWidget->removeTab(METATAB);
130         m_view.tabWidget->removeTab(IMAGETAB);
131         m_view.tabWidget->removeTab(SLIDETAB);
132         m_view.tabWidget->removeTab(AUDIOTAB);
133         m_view.tabWidget->removeTab(VIDEOTAB);
134         m_view.clip_thumb->setHidden(true);
135         m_view.clip_color->setColor(QColor('#' + props.value("colour").right(8).left(6)));
136     } else if (t == SLIDESHOW) {
137         m_view.clip_path->setText(url.directory());
138         m_view.tabWidget->removeTab(METATAB);
139         m_view.tabWidget->removeTab(IMAGETAB);
140         m_view.tabWidget->removeTab(COLORTAB);
141         m_view.tabWidget->removeTab(AUDIOTAB);
142         m_view.tabWidget->removeTab(VIDEOTAB);
143         QStringList types;
144         types << "JPG" << "PNG" << "BMP" << "GIF";
145         m_view.image_type->addItems(types);
146         m_view.slide_loop->setChecked(props.value("loop").toInt());
147         m_view.slide_fade->setChecked(props.value("fade").toInt());
148         m_view.luma_softness->setValue(props.value("softness").toInt());
149         QString path = props.value("resource");
150         if (path.endsWith("png")) m_view.image_type->setCurrentIndex(TYPE_PNG);
151         else if (path.endsWith("bmp")) m_view.image_type->setCurrentIndex(TYPE_BMP);
152         else if (path.endsWith("gif")) m_view.image_type->setCurrentIndex(TYPE_GIF);
153         m_view.slide_duration->setText(tc.getTimecodeFromFrames(props.value("ttl").toInt()));
154         parseFolder();
155
156         m_view.luma_duration->setText(tc.getTimecodeFromFrames(props.value("luma_duration").toInt()));
157         QString lumaFile = props.value("luma_file");
158
159         // Check for Kdenlive installed luma files
160         QStringList filters;
161         filters << "*.pgm" << "*.png";
162
163         QStringList customLumas = KGlobal::dirs()->findDirs("appdata", "lumas");
164         foreach(const QString &folder, customLumas) {
165             QStringList filesnames = QDir(folder).entryList(filters, QDir::Files);
166             foreach(const QString &fname, filesnames) {
167                 m_view.luma_file->addItem(KIcon(folder + '/' + fname), fname, folder + '/' + fname);
168             }
169         }
170
171         // Check for MLT lumas
172         QString profilePath = KdenliveSettings::mltpath();
173         QString folder = profilePath.section('/', 0, -3);
174         folder.append("/lumas/PAL"); // TODO: cleanup the PAL / NTSC mess in luma files
175         QDir lumafolder(folder);
176         QStringList filesnames = lumafolder.entryList(filters, QDir::Files);
177         foreach(const QString &fname, filesnames) {
178             m_view.luma_file->addItem(KIcon(folder + '/' + fname), fname, folder + '/' + fname);
179         }
180
181         slotEnableLuma(m_view.slide_fade->isChecked());
182         slotEnableLumaFile(m_view.slide_luma->isChecked());
183
184         if (!lumaFile.isEmpty()) {
185             m_view.slide_luma->setChecked(true);
186             m_view.luma_file->setCurrentIndex(m_view.luma_file->findData(lumaFile));
187         } else m_view.luma_file->setEnabled(false);
188         connect(m_view.slide_fade, SIGNAL(stateChanged(int)), this, SLOT(slotEnableLuma(int)));
189         connect(m_view.slide_luma, SIGNAL(stateChanged(int)), this, SLOT(slotEnableLumaFile(int)));
190
191         connect(m_view.image_type, SIGNAL(currentIndexChanged(int)), this, SLOT(parseFolder()));
192     } else if (t != AUDIO) {
193         m_view.tabWidget->removeTab(IMAGETAB);
194         m_view.tabWidget->removeTab(SLIDETAB);
195         m_view.tabWidget->removeTab(COLORTAB);
196         if (props.contains("frame_size"))
197             m_view.clip_size->setText(props.value("frame_size"));
198         if (props.contains("videocodec"))
199             m_view.clip_vcodec->setText(props.value("videocodec"));
200         if (props.contains("fps"))
201             m_view.clip_fps->setText(props.value("fps"));
202         if (props.contains("aspect_ratio"))
203             m_view.clip_ratio->setText(props.value("aspect_ratio"));
204
205         QPixmap pix = m_clip->thumbProducer()->getImage(url, m_clip->getClipThumbFrame(), 240, 180);
206         m_view.clip_thumb->setPixmap(pix);
207         if (t == IMAGE || t == VIDEO) m_view.tabWidget->removeTab(AUDIOTAB);
208     } else {
209         m_view.tabWidget->removeTab(IMAGETAB);
210         m_view.tabWidget->removeTab(SLIDETAB);
211         m_view.tabWidget->removeTab(COLORTAB);
212         m_view.tabWidget->removeTab(VIDEOTAB);
213         m_view.clip_thumb->setHidden(true);
214     }
215
216     KFileItem f(KFileItem::Unknown, KFileItem::Unknown, url, true);
217     m_view.clip_filesize->setText(KIO::convertSize(f.size()));
218     m_view.clip_duration->setText(tc.getTimecode(m_clip->duration(), m_fps));
219     if (t != IMAGE && t != COLOR && t != TEXT) m_view.clip_duration->setReadOnly(true);
220     else connect(m_view.clip_duration, SIGNAL(editingFinished()), this, SLOT(slotCheckMaxLength()));
221
222     // markers
223     m_view.marker_new->setIcon(KIcon("document-new"));
224     m_view.marker_new->setToolTip(i18n("Add marker"));
225     m_view.marker_edit->setIcon(KIcon("document-properties"));
226     m_view.marker_edit->setToolTip(i18n("Edit marker"));
227     m_view.marker_delete->setIcon(KIcon("trash-empty"));
228     m_view.marker_delete->setToolTip(i18n("Delete marker"));
229
230     slotFillMarkersList();
231     connect(m_view.marker_new, SIGNAL(clicked()), this, SLOT(slotAddMarker()));
232     connect(m_view.marker_edit, SIGNAL(clicked()), this, SLOT(slotEditMarker()));
233     connect(m_view.marker_delete, SIGNAL(clicked()), this, SLOT(slotDeleteMarker()));
234     connect(m_view.markers_list, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(slotEditMarker()));
235
236     adjustSize();
237 }
238
239 void ClipProperties::slotEnableLuma(int state)
240 {
241     bool enable = false;
242     if (state == Qt::Checked) enable = true;
243     m_view.luma_duration->setEnabled(enable);
244     m_view.slide_luma->setEnabled(enable);
245     if (enable) {
246         m_view.luma_file->setEnabled(m_view.slide_luma->isChecked());
247     } else m_view.luma_file->setEnabled(false);
248     m_view.label_softness->setEnabled(m_view.slide_luma->isChecked() && enable);
249     m_view.luma_softness->setEnabled(m_view.label_softness->isEnabled());
250 }
251
252 void ClipProperties::slotEnableLumaFile(int state)
253 {
254     bool enable = false;
255     if (state == Qt::Checked) enable = true;
256     m_view.luma_file->setEnabled(enable);
257     m_view.luma_softness->setEnabled(enable);
258     m_view.label_softness->setEnabled(enable);
259 }
260
261 void ClipProperties::slotFillMarkersList()
262 {
263     m_view.markers_list->clear();
264     QList < CommentedTime > marks = m_clip->commentedSnapMarkers();
265     for (uint count = 0; count < marks.count(); ++count) {
266         QString time = m_tc.getTimecode(marks[count].time(), m_tc.fps());
267         QStringList itemtext;
268         itemtext << time << marks[count].comment();
269         (void) new QTreeWidgetItem(m_view.markers_list, itemtext);
270     }
271 }
272
273 void ClipProperties::slotAddMarker()
274 {
275     CommentedTime marker(GenTime(), i18n("Marker"));
276     MarkerDialog d(m_clip, marker, m_tc, i18n("Add Marker"), this);
277     if (d.exec() == QDialog::Accepted) {
278         emit addMarker(m_clip->getId(), d.newMarker().time(), d.newMarker().comment());
279     }
280     QTimer::singleShot(500, this, SLOT(slotFillMarkersList()));
281 }
282
283 void ClipProperties::slotEditMarker()
284 {
285     QList < CommentedTime > marks = m_clip->commentedSnapMarkers();
286     int pos = m_view.markers_list->currentIndex().row();
287     if (pos < 0 || pos > marks.count() - 1) return;
288     MarkerDialog d(m_clip, marks.at(pos), m_tc, i18n("Edit Marker"), this);
289     if (d.exec() == QDialog::Accepted) {
290         emit addMarker(m_clip->getId(), d.newMarker().time(), d.newMarker().comment());
291     }
292     QTimer::singleShot(500, this, SLOT(slotFillMarkersList()));
293 }
294
295 void ClipProperties::slotDeleteMarker()
296 {
297     QList < CommentedTime > marks = m_clip->commentedSnapMarkers();
298     int pos = m_view.markers_list->currentIndex().row();
299     if (pos < 0 || pos > marks.count() - 1) return;
300     emit addMarker(m_clip->getId(), marks.at(pos).time(), QString());
301
302     QTimer::singleShot(500, this, SLOT(slotFillMarkersList()));
303 }
304
305 const QString &ClipProperties::clipId() const
306 {
307     return m_clip->getId();
308 }
309
310
311 QMap <QString, QString> ClipProperties::properties()
312 {
313     QMap <QString, QString> props;
314     CLIPTYPE t = m_clip->clipType();
315     QMap <QString, QString> old_props = m_clip->properties();
316
317     if (old_props.value("description") != m_view.clip_description->text())
318         props["description"] = m_view.clip_description->text();
319
320     double aspect = m_view.clip_ar->value();
321     if (m_view.clip_force_ar->isChecked()) {
322         if (aspect != old_props.value("force_aspect_ratio").toDouble()) {
323             props["force_aspect_ratio"] = QString::number(aspect);
324             m_clipNeedsRefresh = true;
325         }
326     } else if (old_props.contains("force_aspect_ratio")) {
327         props["force_aspect_ratio"].clear();
328         m_clipNeedsRefresh = true;
329     }
330
331     int threads = m_view.clip_threads->value();
332     if (m_view.clip_force_threads->isChecked()) {
333         if (threads != old_props.value("threads").toInt()) {
334             props["threads"] = QString::number(threads);
335         }
336     } else if (old_props.contains("threads")) {
337         props["threads"].clear();
338     }
339
340     int vindex = m_view.clip_vindex->value();
341     if (m_view.clip_force_vindex->isChecked()) {
342         if (vindex != old_props.value("video_index").toInt()) {
343             props["video_index"] = QString::number(vindex);
344         }
345     } else if (old_props.contains("video_index")) {
346         props["video_index"].clear();
347     }
348
349     int aindex = m_view.clip_aindex->value();
350     if (m_view.clip_force_aindex->isChecked()) {
351         if (aindex != old_props.value("audio_index").toInt()) {
352             props["audio_index"] = QString::number(aindex);
353         }
354     } else if (old_props.contains("audio_index")) {
355         props["audio_index"].clear();
356     }
357
358     if (t == COLOR) {
359         QString new_color = m_view.clip_color->color().name();
360         if (new_color != QString('#' + old_props.value("colour").right(8).left(6))) {
361             m_clipNeedsRefresh = true;
362             props["colour"] = "0x" + new_color.right(6) + "ff";
363         }
364         int duration = m_tc.getFrameCount(m_view.clip_duration->text(), m_fps);
365         if (duration != m_clip->duration().frames(m_fps)) {
366             props["out"] = QString::number(duration);
367         }
368     } else if (t == IMAGE) {
369         if ((int) m_view.image_transparency->isChecked() != old_props.value("transparency").toInt()) {
370             props["transparency"] = QString::number((int)m_view.image_transparency->isChecked());
371             m_clipNeedsRefresh = true;
372         }
373         int duration = m_tc.getFrameCount(m_view.clip_duration->text(), m_fps);
374         if (duration != m_clip->duration().frames(m_fps)) {
375             props["out"] = QString::number(duration);
376         }
377     } else if (t == SLIDESHOW) {
378         QString value = QString::number((int) m_view.slide_loop->isChecked());
379         if (old_props.value("loop") != value) props["loop"] = value;
380         value = QString::number((int) m_view.slide_fade->isChecked());
381         if (old_props.value("fade") != value) props["fade"] = value;
382         value = QString::number((int) m_view.luma_softness->value());
383         if (old_props.value("softness") != value) props["softness"] = value;
384
385         QString extension;
386         switch (m_view.image_type->currentIndex()) {
387         case TYPE_PNG:
388             extension = "/.all.png";
389             break;
390         case TYPE_BMP:
391             extension = "/.all.bmp";
392             break;
393         case TYPE_GIF:
394             extension = "/.all.gif";
395             break;
396         default:
397             extension = "/.all.jpg";
398             break;
399         }
400         QString new_path = m_view.clip_path->text() + extension;
401         if (new_path != old_props.value("resource")) {
402             m_clipNeedsRefresh = true;
403             props["resource"] = new_path;
404             kDebug() << "////  SLIDE EDIT, NEW:" << new_path << ", OLD; " << old_props.value("resource");
405         }
406         int duration = m_tc.getFrameCount(m_view.slide_duration->text(), m_fps);
407         if (duration != old_props.value("ttl").toInt()) {
408             m_clipNeedsRefresh = true;
409             props["ttl"] = QString::number(duration);
410             props["out"] = QString::number(duration * m_count);
411         }
412         if (duration * m_count != old_props.value("out").toInt()) {
413             m_clipNeedsRefresh = true;
414             props["out"] = QString::number(duration * m_count);
415         }
416         if (m_view.slide_fade->isChecked()) {
417             int luma_duration = m_tc.getFrameCount(m_view.luma_duration->text(), m_fps);
418             if (luma_duration != old_props.value("luma_duration").toInt()) {
419                 m_clipNeedsRefresh = true;
420                 props["luma_duration"] = QString::number(luma_duration);
421             }
422             QString lumaFile;
423             if (m_view.slide_luma->isChecked())
424                 lumaFile = m_view.luma_file->itemData(m_view.luma_file->currentIndex()).toString();
425             if (lumaFile != old_props.value("luma_file")) {
426                 m_clipNeedsRefresh = true;
427                 props["luma_file"] = lumaFile;
428             }
429         } else {
430             if (!old_props.value("luma_file").isEmpty()) {
431                 props["luma_file"].clear();
432             }
433         }
434
435     }
436     return props;
437 }
438
439 bool ClipProperties::needsTimelineRefresh() const
440 {
441     return m_clipNeedsRefresh;
442 }
443
444 void ClipProperties::parseFolder()
445 {
446
447     QDir dir(m_view.clip_path->text());
448     QStringList filters;
449     QString extension;
450     switch (m_view.image_type->currentIndex()) {
451     case TYPE_PNG:
452         filters << "*.png";
453         extension = "/.all.png";
454         break;
455     case TYPE_BMP:
456         filters << "*.bmp";
457         extension = "/.all.bmp";
458         break;
459     case TYPE_GIF:
460         filters << "*.gif";
461         extension = "/.all.gif";
462         break;
463     default:
464         filters << "*.jpg";
465         // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
466         // << "*.jpeg";
467         extension = "/.all.jpg";
468         break;
469     }
470
471     dir.setNameFilters(filters);
472     QStringList result = dir.entryList(QDir::Files);
473     m_count = result.count();
474     m_view.slide_info->setText(i18n("%1 images found", m_count));
475     QDomElement xml = m_clip->toXML();
476     xml.setAttribute("resource", m_view.clip_path->text() + extension);
477     QPixmap pix = m_clip->thumbProducer()->getImage(KUrl(m_view.clip_path->text() + extension), 1, 240, 180);
478     QMap <QString, QString> props = m_clip->properties();
479     m_view.clip_duration->setText(m_tc.getTimecodeFromFrames(props.value("ttl").toInt() * m_count));
480     m_view.clip_thumb->setPixmap(pix);
481 }
482
483 void ClipProperties::slotCheckMaxLength()
484 {
485     int duration = m_tc.getFrameCount(m_view.clip_duration->text(), m_fps);
486     if (duration > m_clip->maxDuration().frames(m_fps)) {
487         m_view.clip_duration->setText(m_tc.getTimecode(m_clip->maxDuration(), m_fps));
488     }
489 }
490
491 #include "clipproperties.moc"
492
493