]> git.sesse.net Git - kdenlive/blob - src/stopmotion/stopmotion.cpp
Stop motion widget: create sequence thumbnails in secondary thread so that ui keeps...
[kdenlive] / src / stopmotion / stopmotion.cpp
1 /***************************************************************************
2                           stopmotion.cpp  -  description
3                              -------------------
4     begin                : Feb 28 2008
5     copyright            : (C) 2010 by Jean-Baptiste Mardelle
6     email                : jb@kdenlive.org
7  ***************************************************************************/
8
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17
18 #include "stopmotion.h"
19 #include "../blackmagic/devices.h"
20 #include "../slideshowclip.h"
21 #include "kdenlivesettings.h"
22
23
24 #include <KDebug>
25 #include <KGlobalSettings>
26 #include <KFileDialog>
27 #include <KStandardDirs>
28 #include <KMessageBox>
29 #include <kdeversion.h>
30 #include <KNotification>
31
32 #include <QtConcurrentRun>
33 #include <QInputDialog>
34 #include <QComboBox>
35 #include <QVBoxLayout>
36 #include <QTimer>
37 #include <QPainter>
38 #include <QAction>
39 #include <QWheelEvent>
40
41 MyLabel::MyLabel(QWidget *parent) :
42         QLabel(parent)
43 {
44 }
45
46 void MyLabel::setImage(QImage img)
47 {
48     m_img = img;
49 }
50
51 //virtual
52 void MyLabel::wheelEvent(QWheelEvent * event)
53 {
54     if (event->delta() > 0) emit seek(true);
55     else emit seek(false);
56 }
57
58 //virtual
59 void MyLabel::paintEvent( QPaintEvent * event)
60 {
61     Q_UNUSED(event);
62
63     QRect r(0, 0, width(), height());
64     QPainter p(this);
65     p.fillRect(r, QColor(KdenliveSettings::window_background()));
66     double aspect_ratio = (double) m_img.width() / m_img.height();
67     int pictureHeight = height();
68     int pictureWidth = width();
69     int calculatedWidth = aspect_ratio * height();
70     if (calculatedWidth > width()) pictureHeight = width() / aspect_ratio;
71     else {
72         int calculatedHeight = width() / aspect_ratio;
73         if (calculatedHeight > height()) pictureWidth = height() * aspect_ratio;
74     }
75     p.drawImage(QRect((width() - pictureWidth) / 2, (height() - pictureHeight) / 2, pictureWidth, pictureHeight), m_img, QRect(0, 0, m_img.width(), m_img.height()));
76     p.end();
77 }
78
79
80 StopmotionWidget::StopmotionWidget(KUrl projectFolder, QWidget *parent) :
81         QDialog(parent)
82         , Ui::Stopmotion_UI()
83         , m_projectFolder(projectFolder)
84         , m_sequenceFrame(0)
85         , m_animatedIndex(-1)
86 {
87     setupUi(this);
88     setWindowTitle(i18n("Stop Motion Capture"));
89     setFont(KGlobalSettings::toolBarFont());
90
91     live_button->setIcon(KIcon("camera-photo"));
92     frameoverlay_button->setIcon(KIcon("edit-paste"));
93     m_captureAction = new QAction(KIcon("media-record"), i18n("Capture frame"), this);
94     m_captureAction->setShortcut(QKeySequence(Qt::Key_Space));
95     connect(m_captureAction, SIGNAL(triggered()), this, SLOT(slotCaptureFrame()));
96     capture_button->setDefaultAction(m_captureAction);
97     
98     preview_button->setIcon(KIcon("media-playback-start"));
99     removelast_button->setIcon(KIcon("edit-delete"));
100     frameoverlay_button->setEnabled(false);
101     removelast_button->setEnabled(false);
102     capture_button->setEnabled(false);
103
104     connect(sequence_name, SIGNAL(textChanged(const QString &)), this, SLOT(sequenceNameChanged(const QString &)));
105     BMInterface::getBlackMagicDeviceList(capture_device, NULL);
106     QVBoxLayout *lay = new QVBoxLayout;
107     m_bmCapture = new CaptureHandler(lay);
108     m_frame_preview = new MyLabel(this);
109     connect(m_frame_preview, SIGNAL(seek(bool)), this, SLOT(slotSeekFrame(bool)));
110     lay->addWidget(m_frame_preview);
111     m_frame_preview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
112     video_preview->setLayout(lay);
113     live_button->setChecked(false);
114     frameoverlay_button->setChecked(false);
115     button_addsequence->setEnabled(false);
116     connect(live_button, SIGNAL(clicked(bool)), this, SLOT(slotLive(bool)));
117     connect(frameoverlay_button, SIGNAL(clicked(bool)), this, SLOT(slotShowOverlay(bool)));
118     connect(frame_number, SIGNAL(valueChanged(int)), this, SLOT(slotShowFrame(int)));
119     connect(button_addsequence, SIGNAL(clicked(bool)), this, SLOT(slotAddSequence()));
120     connect(preview_button, SIGNAL(clicked(bool)), this, SLOT(slotPlayPreview()));
121     connect(frame_list, SIGNAL(currentRowChanged(int)), this, SLOT(slotShowSelectedFrame()));
122     connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage,int)));
123     
124     parseExistingSequences();
125 }
126
127 StopmotionWidget::~StopmotionWidget()
128 {
129     m_bmCapture->stopPreview();
130 }
131
132 void StopmotionWidget::parseExistingSequences()
133 {
134     sequence_name->clear();
135     sequence_name->addItem(QString());
136     QDir dir(m_projectFolder.path());
137     QStringList filters;
138     filters << "*_0000.png";
139     //dir.setNameFilters(filters);
140     QStringList sequences = dir.entryList(filters, QDir::Files, QDir::Name);
141     //kDebug()<<"PF: "<<<<", sm: "<<sequences;
142     foreach(QString sequencename, sequences) {
143         sequence_name->addItem(sequencename.section("_", 0, -2));
144     }
145 }
146
147 void StopmotionWidget::slotLive(bool isOn)
148 {
149     if (isOn) {
150         m_frame_preview->setImage(QImage());
151         m_frame_preview->setHidden(true);
152         m_bmCapture->startPreview(KdenliveSettings::hdmicapturedevice(), KdenliveSettings::hdmicapturemode());
153         capture_button->setEnabled(true);
154     }
155     else {
156         m_bmCapture->stopPreview();
157         capture_button->setEnabled(false);
158     }
159 }
160
161 void StopmotionWidget::slotShowOverlay(bool isOn)
162 {
163     if (isOn) {
164         if (live_button->isChecked() && m_sequenceFrame > 0) {
165             slotUpdateOverlay();
166         }
167     }
168     else {
169         m_bmCapture->hideOverlay();
170     }
171 }
172
173 void StopmotionWidget::slotUpdateOverlay()
174 {
175     QString path = getPathForFrame(m_sequenceFrame - 1);
176     if (!QFile::exists(path)) return;
177     QImage img(path);
178     if (img.isNull()) {
179         QTimer::singleShot(1000, this, SLOT(slotUpdateOverlay()));
180         return;
181     }
182     m_bmCapture->showOverlay(img);
183 }
184
185 void StopmotionWidget::sequenceNameChanged(const QString &name)
186 {
187     // Get rid of frames from previous sequence
188     disconnect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage,int)));
189     m_filesList.clear();
190     m_future.waitForFinished();
191     frame_list->clear();
192     if (name.isEmpty()) {
193         button_addsequence->setEnabled(false);
194         frame_number->blockSignals(true);
195         frame_number->setValue(m_sequenceFrame);
196         frame_number->blockSignals(false);
197         frameoverlay_button->setEnabled(false);
198         removelast_button->setEnabled(false);
199     }
200     else {
201         // Check if we are editing an existing sequence
202         int count = 0;
203         QString pattern = SlideshowClip::selectedPath(getPathForFrame(0, sequence_name->currentText()), false, QString(), &count);
204         m_sequenceFrame = count;
205         frame_number->blockSignals(true);
206         frame_number->setValue(0);
207         frame_number->blockSignals(false);
208         m_currentIndex = 0;
209         if (count > 0) {
210             m_sequenceName = sequence_name->currentText();
211             //TODO: Do the thumbnail stuff in a thread
212             for (int i = 0; i < count; i++) {
213                 //slotUpdateFrameList(i);
214                 m_filesList.append(getPathForFrame(i));
215             }
216             connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage,int)));
217             m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
218             button_addsequence->setEnabled(true);
219             frameoverlay_button->setEnabled(true);
220         }
221         else {
222             button_addsequence->setEnabled(false);
223             frameoverlay_button->setEnabled(false);
224         }
225         frame_number->setRange(0, m_sequenceFrame);
226         capture_button->setEnabled(true);
227     }
228 }
229
230 void StopmotionWidget::slotCaptureFrame()
231 {
232     if (sequence_name->currentText().isEmpty()) {
233         QString seqName = QInputDialog::getText(this, i18n("Create New Sequence"), i18n("Enter sequence name"));
234         if (seqName.isEmpty()) return;
235         sequence_name->blockSignals(true);
236         sequence_name->setItemText(sequence_name->currentIndex(), seqName);
237         sequence_name->blockSignals(false);
238     }
239     if (m_sequenceName != sequence_name->currentText()) {
240         m_sequenceName = sequence_name->currentText();
241         m_sequenceFrame = 0;
242     }
243     capture_button->setEnabled(false);
244     m_bmCapture->captureFrame(getPathForFrame(m_sequenceFrame));
245     KNotification::event("FrameCaptured");
246     frameoverlay_button->setEnabled(true);
247     m_sequenceFrame++;
248     frame_number->setRange(0, m_sequenceFrame);
249     frame_number->blockSignals(true);
250     frame_number->setValue(m_sequenceFrame);
251     frame_number->blockSignals(false);
252     button_addsequence->setEnabled(true);
253     //if (frameoverlay_button->isChecked()) QTimer::singleShot(1000, this, SLOT(slotUpdateOverlay()));
254     QTimer::singleShot(1000, this, SLOT(slotUpdateFrameList()));
255 }
256
257 void StopmotionWidget::slotPrepareThumbs()
258 {
259     if (m_filesList.isEmpty()) return;
260     QString path = m_filesList.takeFirst();
261     emit doCreateThumbs(QImage(path), m_currentIndex++);
262     
263 }
264
265 void StopmotionWidget::slotCreateThumbs(QImage img, int ix)
266 {
267     if (img.isNull()) {
268         m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
269         return;
270     }
271     int height = 90;
272     int width = height * img.width() / img.height();
273     frame_list->setIconSize(QSize(width, height));
274     QPixmap pix = QPixmap::fromImage(img).scaled(width, height);
275     QString nb = QString::number(ix);
276     QPainter p(&pix);
277     QFontInfo finfo(font());
278     p.fillRect(0, 0, finfo.pixelSize() * nb.count() + 6, finfo.pixelSize() + 6, QColor(0, 0, 0, 150));
279     p.setPen(Qt::white);
280     p.drawText(QPoint(3, finfo.pixelSize() + 3), nb);
281     p.end();
282     QIcon icon(pix);
283     QListWidgetItem *item = new QListWidgetItem(icon, QString(), frame_list);
284     item->setData(Qt::UserRole, ix);
285     m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
286 }
287
288 void StopmotionWidget::slotUpdateFrameList(int ix)
289 {
290     kDebug()<< "// GET FRAME: "<<ix;
291     if (ix == -1) ix = m_sequenceFrame - 1;
292     QString path = getPathForFrame(ix);
293     if (!QFile::exists(path)) {
294         capture_button->setEnabled(true);
295         return;
296     }
297     QImage img(path);
298     if (img.isNull()) {
299         if (ix == m_sequenceFrame - 1) QTimer::singleShot(1000, this, SLOT(slotUpdateFrameList()));
300         return;
301     }
302     int height = 90;
303     int width = height * img.width() / img.height();
304     frame_list->setIconSize(QSize(width, height));
305     QPixmap pix = QPixmap::fromImage(img).scaled(width, height);
306     QString nb = QString::number(ix);
307     QPainter p(&pix);
308     QFontInfo finfo(font());
309     p.fillRect(0, 0, finfo.pixelSize() * nb.count() + 6, finfo.pixelSize() + 6, QColor(0, 0, 0, 150));
310     p.setPen(Qt::white);
311     p.drawText(QPoint(3, finfo.pixelSize() + 3), nb);
312     p.end();
313     QIcon icon(pix);
314     QListWidgetItem *item = new QListWidgetItem(icon, QString(), frame_list);
315     item->setData(Qt::UserRole, ix);
316     capture_button->setEnabled(true);
317 }
318
319 QString StopmotionWidget::getPathForFrame(int ix, QString seqName)
320 {
321     if (seqName.isEmpty()) seqName = m_sequenceName;
322     return m_projectFolder.path(KUrl::AddTrailingSlash) + seqName + "_" + QString::number(ix).rightJustified(4, '0', false) + ".png";
323 }
324
325 void StopmotionWidget::slotShowFrame(int ix)
326 {
327     if (m_sequenceFrame == 0) {
328         //there are no images in sequence
329         return;
330     }
331     frameoverlay_button->blockSignals(true);
332     frameoverlay_button->setChecked(false);
333     frameoverlay_button->blockSignals(false);
334     if (ix < m_sequenceFrame) {
335         // Show previous frame
336         slotLive(false);
337         live_button->setChecked(false);
338         QImage img(getPathForFrame(ix));
339         capture_button->setEnabled(false);
340         if (!img.isNull()) {
341             //m_bmCapture->showOverlay(img, false);
342             m_bmCapture->hidePreview(true);
343             m_frame_preview->setImage(img);
344             m_frame_preview->setHidden(false);
345             m_frame_preview->update();
346             selectFrame(ix);
347         }
348     }
349     /*else {
350         slotLive(true);
351         m_frame_preview->setImage(QImage());
352         m_frame_preview->setHidden(true);
353         m_bmCapture->hideOverlay();
354         m_bmCapture->hidePreview(false);
355         capture_button->setEnabled(true);
356     }*/   
357 }
358
359 void StopmotionWidget::slotShowSelectedFrame()
360 {
361     QListWidgetItem *item = frame_list->currentItem();
362     if (item) {
363         int ix = item->data(Qt::UserRole).toInt();
364         //frame_number->blockSignals(true);
365         frame_number->setValue(ix);
366         //frame_number->blockSignals(false);
367         //slotShowFrame(ix);
368     }
369 }
370
371 void StopmotionWidget::slotAddSequence()
372 {
373     emit addOrUpdateSequence(getPathForFrame(0));
374 }
375
376 void StopmotionWidget::slotPlayPreview()
377 {
378     if (m_animatedIndex != -1) {
379         // stop animation
380         m_animatedIndex = -1;
381         return;
382     }
383     QListWidgetItem *item = frame_list->currentItem();
384     if (item) {
385         m_animatedIndex = item->data(Qt::UserRole).toInt();
386     }
387     QTimer::singleShot(200, this, SLOT(slotAnimate()));
388 }
389
390 void StopmotionWidget::slotAnimate()
391 {
392     slotShowFrame(m_animatedIndex);
393     m_animatedIndex++;
394     if (m_animatedIndex < m_sequenceFrame) QTimer::singleShot(200, this, SLOT(slotAnimate()));
395     else m_animatedIndex = -1;
396 }
397
398 void StopmotionWidget::selectFrame(int ix)
399 {
400     frame_list->blockSignals(true);
401     QListWidgetItem *item = frame_list->item(ix);
402     int current = item->data(Qt::UserRole).toInt();
403     if (current == ix) {
404         frame_list->setCurrentItem(item);
405     }
406     else if (current < ix) {
407         for (int i = ix; i < frame_list->count(); i++) {
408             item = frame_list->item(i);
409             current = item->data(Qt::UserRole).toInt();
410             if (current == ix) {
411                 frame_list->setCurrentItem(item);
412                 break;
413             }
414         }
415     }
416     else {
417         for (int i = ix; i >= 0; i--) {
418             item = frame_list->item(i);
419             current = item->data(Qt::UserRole).toInt();
420             if (current == ix) {
421                 frame_list->setCurrentItem(item);
422                 break;
423             }
424         }
425     }
426     frame_list->blockSignals(false);
427 }
428
429 void StopmotionWidget::slotSeekFrame(bool forward)
430 {
431     int ix = frame_list->currentRow();
432     if (forward) {
433         if (ix < frame_list->count() - 1) frame_list->setCurrentRow(ix + 1);
434     }
435     else if (ix > 0) frame_list->setCurrentRow(ix - 1);
436 }
437
438