]> git.sesse.net Git - kdenlive/blob - src/stopmotion/stopmotion.cpp
6c8f535e286ed54db9e6a27ab48188d13fd880d8
[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 "../v4l/v4lcapture.h"
21 #include "../slideshowclip.h"
22 #include "ui_smconfig_ui.h"
23 #include "kdenlivesettings.h"
24
25
26 #include <KDebug>
27 #include <KGlobalSettings>
28 #include <KFileDialog>
29 #include <KStandardDirs>
30 #include <KMessageBox>
31 #include <kdeversion.h>
32 #include <KNotification>
33
34 #ifdef QIMAGEBLITZ
35 #include <qimageblitz/qimageblitz.h>
36 #endif
37
38 #include <QtConcurrentRun>
39 #include <QInputDialog>
40 #include <QComboBox>
41 #include <QVBoxLayout>
42 #include <QTimer>
43 #include <QPainter>
44 #include <QAction>
45 #include <QWheelEvent>
46 #include <QMenu>
47
48 MyLabel::MyLabel(QWidget* parent) :
49     QLabel(parent)
50 {
51 }
52
53 void MyLabel::setImage(QImage img)
54 {
55     m_img = img;
56 }
57
58 //virtual
59 void MyLabel::wheelEvent(QWheelEvent* event)
60 {
61     if (event->delta() > 0) emit seek(true);
62     else emit seek(false);
63 }
64
65 //virtual
66 void MyLabel::mousePressEvent(QMouseEvent*)
67 {
68     emit switchToLive();
69 }
70
71 //virtual
72 void MyLabel::paintEvent(QPaintEvent* event)
73 {
74     Q_UNUSED(event);
75
76     QRect r(0, 0, width(), height());
77     QPainter p(this);
78     p.fillRect(r, QColor(KdenliveSettings::window_background()));
79     double aspect_ratio = (double) m_img.width() / m_img.height();
80     int pictureHeight = height();
81     int pictureWidth = width();
82     int calculatedWidth = aspect_ratio * height();
83     if (calculatedWidth > width()) pictureHeight = width() / aspect_ratio;
84     else {
85         int calculatedHeight = width() / aspect_ratio;
86         if (calculatedHeight > height()) pictureWidth = height() * aspect_ratio;
87     }
88     p.drawImage(QRect((width() - pictureWidth) / 2, (height() - pictureHeight) / 2, pictureWidth, pictureHeight), m_img, QRect(0, 0, m_img.width(), m_img.height()));
89     p.end();
90 }
91
92
93 StopmotionWidget::StopmotionWidget(KUrl projectFolder, QList< QAction* > actions, QWidget* parent) :
94     QDialog(parent)
95     , Ui::Stopmotion_UI()
96     , m_projectFolder(projectFolder)
97     , m_bmCapture(NULL)
98     , m_sequenceFrame(0)
99     , m_animatedIndex(-1)
100     , m_animate(false)
101 {
102     //setAttribute(Qt::WA_DeleteOnClose);
103     QAction* analyse = new QAction(i18n("Send frames to color scopes"), this);
104     analyse->setCheckable(true);
105     analyse->setChecked(KdenliveSettings::analyse_stopmotion());
106     connect(analyse, SIGNAL(triggered(bool)), this, SLOT(slotSwitchAnalyse(bool)));
107     addActions(actions);
108     setupUi(this);
109     setWindowTitle(i18n("Stop Motion Capture"));
110     setFont(KGlobalSettings::toolBarFont());
111
112     live_button->setIcon(KIcon("camera-photo"));
113
114     m_captureAction = actions.at(0);
115     connect(m_captureAction, SIGNAL(triggered()), this, SLOT(slotCaptureFrame()));
116     m_captureAction->setCheckable(true);
117     m_captureAction->setChecked(false);
118     capture_button->setDefaultAction(m_captureAction);
119
120     connect(actions.at(1), SIGNAL(triggered()), this, SLOT(slotSwitchLive()));
121
122     QAction *intervalCapture = new QAction(i18n("Interval capture"), this);
123     intervalCapture->setIcon(KIcon("chronometer"));
124     intervalCapture->setCheckable(true);
125     intervalCapture->setChecked(false);
126     capture_interval->setDefaultAction(intervalCapture);
127         
128     preview_button->setIcon(KIcon("media-playback-start"));
129     capture_button->setEnabled(false);
130     
131
132     // Build config menu
133     QMenu* confMenu = new QMenu;
134     m_showOverlay = actions.at(2);
135     connect(m_showOverlay, SIGNAL(triggered(bool)), this, SLOT(slotShowOverlay(bool)));
136     overlay_button->setDefaultAction(m_showOverlay);
137     //confMenu->addAction(m_showOverlay);
138
139 #ifdef QIMAGEBLITZ
140     m_effectIndex = KdenliveSettings::blitzeffect();
141     QMenu* effectsMenu = new QMenu(i18n("Overlay effect"));
142     QActionGroup* effectGroup = new QActionGroup(this);
143     QAction* noEffect = new QAction(i18n("No Effect"), effectGroup);
144     noEffect->setData(1);
145     QAction* contrastEffect = new QAction(i18n("Contrast"), effectGroup);
146     contrastEffect->setData(2);
147     QAction* edgeEffect = new QAction(i18n("Edge detect"), effectGroup);
148     edgeEffect->setData(3);
149     QAction* brightEffect = new QAction(i18n("Brighten"), effectGroup);
150     brightEffect->setData(4);
151     QAction* invertEffect = new QAction(i18n("Invert"), effectGroup);
152     invertEffect->setData(5);
153     QAction* thresEffect = new QAction(i18n("Threshold"), effectGroup);
154     thresEffect->setData(6);
155
156     effectsMenu->addAction(noEffect);
157     effectsMenu->addAction(contrastEffect);
158     effectsMenu->addAction(edgeEffect);
159     effectsMenu->addAction(brightEffect);
160     effectsMenu->addAction(invertEffect);
161     effectsMenu->addAction(thresEffect);
162     QList <QAction*> list = effectsMenu->actions();
163     for (int i = 0; i < list.count(); i++) {
164         list.at(i)->setCheckable(true);
165         if (list.at(i)->data().toInt() == m_effectIndex) {
166             list.at(i)->setChecked(true);
167         }
168     }
169     connect(effectsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotUpdateOverlayEffect(QAction*)));
170     confMenu->addMenu(effectsMenu);
171 #endif
172
173     QAction* showThumbs = new QAction(KIcon("image-x-generic"), i18n("Show sequence thumbnails"), this);
174     showThumbs->setCheckable(true);
175     showThumbs->setChecked(KdenliveSettings::showstopmotionthumbs());
176     connect(showThumbs, SIGNAL(triggered(bool)), this, SLOT(slotShowThumbs(bool)));
177
178     QAction* removeCurrent = new QAction(KIcon("edit-delete"), i18n("Delete current frame"), this);
179     removeCurrent->setShortcut(Qt::Key_Delete);
180     connect(removeCurrent, SIGNAL(triggered()), this, SLOT(slotRemoveFrame()));
181
182     QAction* conf = new QAction(KIcon("configure"), i18n("Configure"), this);
183     connect(conf, SIGNAL(triggered()), this, SLOT(slotConfigure()));
184
185     confMenu->addAction(showThumbs);
186     confMenu->addAction(removeCurrent);
187     confMenu->addAction(analyse);
188     confMenu->addAction(conf);
189     config_button->setIcon(KIcon("configure"));
190     config_button->setMenu(confMenu);
191
192     connect(sequence_name, SIGNAL(textChanged(const QString&)), this, SLOT(sequenceNameChanged(const QString&)));
193     connect(sequence_name, SIGNAL(currentIndexChanged(int)), live_button, SLOT(setFocus()));
194
195     m_layout = new QVBoxLayout;
196     if (BMInterface::getBlackMagicDeviceList(capture_device, NULL)) {
197         // Found a BlackMagic device
198         m_bmCapture = new BmdCaptureHandler(m_layout);
199         connect(m_bmCapture, SIGNAL(gotMessage(const QString&)), this, SLOT(slotGotHDMIMessage(const QString&)));
200     }
201     if (QFile::exists(KdenliveSettings::video4vdevice())) {
202 #if !defined(Q_WS_MAC) && !defined(Q_OS_FREEBSD)
203         V4lCaptureHandler v4l(NULL);
204         // Video 4 Linux device detection
205         for (int i = 0; i < 10; i++) {
206             QString path = "/dev/video" + QString::number(i);
207             if (QFile::exists(path)) {
208                 QStringList deviceInfo = v4l.getDeviceName(path);
209                 if (!deviceInfo.isEmpty()) {
210                     capture_device->addItem(deviceInfo.at(0), "v4l");
211                     capture_device->setItemData(capture_device->count() - 1, path, Qt::UserRole + 1);
212                     capture_device->setItemData(capture_device->count() - 1, deviceInfo.at(1), Qt::UserRole + 2);
213                     if (path == KdenliveSettings::video4vdevice()) capture_device->setCurrentIndex(capture_device->count() - 1);
214                 }
215             }
216         }
217
218         /*V4lCaptureHandler v4lhandler(NULL);
219         QStringList deviceInfo = v4lhandler.getDeviceName(KdenliveSettings::video4vdevice());
220             capture_device->addItem(deviceInfo.at(0), "v4l");
221         capture_device->setItemData(capture_device->count() - 1, deviceInfo.at(3), Qt::UserRole + 1);*/
222         if (m_bmCapture == NULL) {
223             m_bmCapture = new V4lCaptureHandler(m_layout);
224             m_bmCapture->setDevice(capture_device->itemData(capture_device->currentIndex(), Qt::UserRole + 1).toString(), capture_device->itemData(capture_device->currentIndex(), Qt::UserRole + 2).toString());
225         }
226 #endif
227     }
228
229     connect(capture_device, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateHandler()));
230     if (m_bmCapture) {
231         connect(m_bmCapture, SIGNAL(frameSaved(const QString)), this, SLOT(slotNewThumb(const QString)));
232         connect(m_bmCapture, SIGNAL(gotFrame(QImage)), this, SIGNAL(gotFrame(QImage)));
233     } else live_button->setEnabled(false);
234     m_frame_preview = new MyLabel(this);
235     connect(m_frame_preview, SIGNAL(seek(bool)), this, SLOT(slotSeekFrame(bool)));
236     connect(m_frame_preview, SIGNAL(switchToLive()), this, SLOT(slotSwitchLive()));
237     m_layout->addWidget(m_frame_preview);
238     m_frame_preview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
239     video_preview->setLayout(m_layout);
240     live_button->setChecked(false);
241     button_addsequence->setEnabled(false);
242     connect(live_button, SIGNAL(toggled(bool)), this, SLOT(slotLive(bool)));
243     connect(button_addsequence, SIGNAL(clicked(bool)), this, SLOT(slotAddSequence()));
244     connect(preview_button, SIGNAL(clicked(bool)), this, SLOT(slotPlayPreview(bool)));
245     connect(frame_list, SIGNAL(currentRowChanged(int)), this, SLOT(slotShowSelectedFrame()));
246     connect(frame_list, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(slotShowSelectedFrame()));
247     connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage, int)));
248
249     frame_list->addAction(removeCurrent);
250     frame_list->setContextMenuPolicy(Qt::ActionsContextMenu);
251     frame_list->setHidden(!KdenliveSettings::showstopmotionthumbs());
252     parseExistingSequences();
253     QTimer::singleShot(500, this, SLOT(slotLive()));
254     connect(&m_intervalTimer, SIGNAL(timeout()), this, SLOT(slotCaptureFrame()));
255     m_intervalTimer.setSingleShot(true);
256     m_intervalTimer.setInterval(KdenliveSettings::captureinterval() * 1000);
257 }
258
259 StopmotionWidget::~StopmotionWidget()
260 {
261     if (m_bmCapture)
262         m_bmCapture->stopPreview();
263 }
264
265 void StopmotionWidget::slotUpdateOverlayEffect(QAction* act)
266 {
267 #ifdef QIMAGEBLITZ
268     if (act) m_effectIndex = act->data().toInt();
269     KdenliveSettings::setBlitzeffect(m_effectIndex);
270     if (m_showOverlay->isChecked()) slotUpdateOverlay();
271 #endif
272 }
273
274 void StopmotionWidget::closeEvent(QCloseEvent* e)
275 {
276     slotLive(false);
277     QDialog::closeEvent(e);
278 }
279
280 void StopmotionWidget::slotConfigure()
281 {
282     QDialog d;
283     Ui::SmConfig_UI ui;
284     ui.setupUi(&d);
285     d.setWindowTitle(i18n("Configure Stop Motion"));
286     ui.sm_interval->setValue(KdenliveSettings::captureinterval());
287     ui.sm_interval->setSuffix(ki18np(" second", " seconds"));
288     ui.sm_notifytime->setSuffix(ki18np(" second", " seconds"));
289     ui.sm_notifytime->setValue(KdenliveSettings::sm_notifytime());
290     connect(ui.sm_prenotify, SIGNAL(toggled(bool)), ui.sm_notifytime, SLOT(setEnabled(bool)));
291     ui.sm_prenotify->setChecked(KdenliveSettings::sm_prenotify());
292     ui.sm_loop->setChecked(KdenliveSettings::sm_loop());
293     ui.sm_framesplayback->setValue(KdenliveSettings::sm_framesplayback());
294     
295     if (d.exec() == QDialog::Accepted) {
296         KdenliveSettings::setSm_loop(ui.sm_loop->isChecked());
297         KdenliveSettings::setCaptureinterval(ui.sm_interval->value());
298         KdenliveSettings::setSm_framesplayback(ui.sm_framesplayback->value());
299         KdenliveSettings::setSm_notifytime(ui.sm_notifytime->value());
300         KdenliveSettings::setSm_prenotify(ui.sm_prenotify->isChecked());
301         m_intervalTimer.setInterval(KdenliveSettings::captureinterval() * 1000);
302     }
303 }
304
305 void StopmotionWidget::slotShowThumbs(bool show)
306 {
307     KdenliveSettings::setShowstopmotionthumbs(show);
308     if (show) {
309         frame_list->clear();
310         sequenceNameChanged(sequence_name->currentText());
311     } else {
312         m_filesList.clear();
313         frame_list->clear();
314     }
315     frame_list->setHidden(!show);
316 }
317
318
319 void StopmotionWidget::slotUpdateHandler()
320 {
321     QString data = capture_device->itemData(capture_device->currentIndex()).toString();
322     slotLive(false);
323     if (m_bmCapture) {
324         delete m_bmCapture;
325     }
326     m_layout->removeWidget(m_frame_preview);
327     if (data == "v4l") {
328 #if !defined(Q_WS_MAC) && !defined(Q_OS_FREEBSD)
329         m_bmCapture = new V4lCaptureHandler(m_layout);
330         m_bmCapture->setDevice(capture_device->itemData(capture_device->currentIndex(), Qt::UserRole + 1).toString(), capture_device->itemData(capture_device->currentIndex(), Qt::UserRole + 2).toString());
331 #endif
332     } else {
333         m_bmCapture = new BmdCaptureHandler(m_layout);
334         if (m_bmCapture) connect(m_bmCapture, SIGNAL(gotMessage(const QString&)), this, SLOT(slotGotHDMIMessage(const QString&)));
335     }
336     live_button->setEnabled(m_bmCapture != NULL);
337     m_layout->addWidget(m_frame_preview);
338 }
339
340 void StopmotionWidget::slotGotHDMIMessage(const QString& message)
341 {
342     log_box->insertItem(0, message);
343 }
344
345 void StopmotionWidget::parseExistingSequences()
346 {
347     sequence_name->clear();
348     sequence_name->addItem(QString());
349     QDir dir(m_projectFolder.path());
350     QStringList filters;
351     filters << "*_0000.png";
352     //dir.setNameFilters(filters);
353     QStringList sequences = dir.entryList(filters, QDir::Files, QDir::Name);
354     //kDebug()<<"PF: "<<<<", sm: "<<sequences;
355     foreach(QString sequencename, sequences) {
356         sequence_name->addItem(sequencename.section("_", 0, -2));
357     }
358 }
359
360 void StopmotionWidget::slotSwitchLive()
361 {
362     setUpdatesEnabled(false);
363     if (m_frame_preview->isHidden()) {
364         if (m_bmCapture) m_bmCapture->hidePreview(true);
365         m_frame_preview->setHidden(false);
366     } else {
367         m_frame_preview->setHidden(true);
368         if (m_bmCapture) m_bmCapture->hidePreview(false);
369         capture_button->setEnabled(true);
370     }
371     setUpdatesEnabled(true);
372 }
373
374 void StopmotionWidget::slotLive(bool isOn)
375 {
376     live_button->blockSignals(true);
377     if (isOn && m_bmCapture) {
378         //m_frame_preview->setImage(QImage());
379         m_frame_preview->setHidden(true);
380         m_bmCapture->startPreview(KdenliveSettings::hdmi_capturedevice(), KdenliveSettings::hdmi_capturemode(), false);
381         capture_button->setEnabled(true);
382         live_button->setChecked(true);
383     } else {
384         if (m_bmCapture) m_bmCapture->stopPreview();
385         m_frame_preview->setHidden(false);
386         capture_button->setEnabled(false);
387         live_button->setChecked(false);
388     }
389     live_button->blockSignals(false);
390 }
391
392 void StopmotionWidget::slotShowOverlay(bool isOn)
393 {
394     if (isOn) {
395         if (live_button->isChecked() && m_sequenceFrame > 0) {
396             slotUpdateOverlay();
397         }
398     } else if (m_bmCapture) {
399         m_bmCapture->hideOverlay();
400     }
401 }
402
403 void StopmotionWidget::slotUpdateOverlay()
404 {
405     if (m_bmCapture == NULL) return;
406     QString path = getPathForFrame(m_sequenceFrame - 1);
407     if (!QFile::exists(path)) return;
408     QImage img(path);
409     if (img.isNull()) {
410         QTimer::singleShot(1000, this, SLOT(slotUpdateOverlay()));
411         return;
412     }
413
414 #ifdef QIMAGEBLITZ
415     //img = Blitz::convolveEdge(img, 0, Blitz::Low);
416     switch (m_effectIndex) {
417     case 2:
418         img = Blitz::contrast(img, true, 6);
419         break;
420     case 3:
421         img = Blitz::edge(img);
422         break;
423     case 4:
424         img = Blitz::intensity(img, 0.5);
425         break;
426     case 5:
427         Blitz::invert(img);
428         break;
429     case 6:
430         img = Blitz::threshold(img, 120, Blitz::Grayscale, qRgba(0, 0, 0, 0), qRgba(255, 0, 0, 255));
431         //img = Blitz::flatten(img, QColor(255, 0, 0, 255), QColor(0, 0, 0, 0));
432         break;
433     default:
434         break;
435
436     }
437 #endif
438     m_bmCapture->showOverlay(img);
439 }
440
441 void StopmotionWidget::sequenceNameChanged(const QString& name)
442 {
443     // Get rid of frames from previous sequence
444     disconnect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage, int)));
445     m_filesList.clear();
446     m_future.waitForFinished();
447     frame_list->clear();
448     if (name.isEmpty()) {
449         button_addsequence->setEnabled(false);
450     } else {
451         // Check if we are editing an existing sequence
452         QString pattern = SlideshowClip::selectedPath(getPathForFrame(0, sequence_name->currentText()), false, QString(), &m_filesList);
453         m_sequenceFrame = m_filesList.isEmpty() ? 0 : SlideshowClip::getFrameNumberFromPath(m_filesList.last()) + 1;
454         if (!m_filesList.isEmpty()) {
455             m_sequenceName = sequence_name->currentText();
456             connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage, int)));
457             m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
458             button_addsequence->setEnabled(true);
459         } else {
460             // new sequence
461             connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage, int)));
462             button_addsequence->setEnabled(false);
463         }
464         capture_button->setEnabled(live_button->isChecked());
465     }
466 }
467
468 void StopmotionWidget::slotCaptureFrame()
469 {
470     if (m_bmCapture == NULL) return;
471     if (sequence_name->currentText().isEmpty()) {
472         QString seqName = QInputDialog::getText(this, i18n("Create New Sequence"), i18n("Enter sequence name"));
473         if (seqName.isEmpty()) {
474             m_captureAction->setChecked(false);
475             return;
476         }
477         sequence_name->blockSignals(true);
478         sequence_name->setItemText(sequence_name->currentIndex(), seqName);
479         sequence_name->blockSignals(false);
480     }
481     if (m_sequenceName != sequence_name->currentText()) {
482         m_sequenceName = sequence_name->currentText();
483         m_sequenceFrame = 0;
484     }
485     //capture_button->setEnabled(false);
486     if (m_intervalTimer.isActive()) {
487         // stop interval capture
488         m_intervalTimer.stop();
489         return;
490     }
491     QString currentPath = getPathForFrame(m_sequenceFrame);
492     m_bmCapture->captureFrame(currentPath);
493     KNotification::event("FrameCaptured", i18n("Frame Captured"), QPixmap(), this);
494     m_sequenceFrame++;
495     button_addsequence->setEnabled(true);
496     if (capture_interval->isChecked()) {
497         if (KdenliveSettings::sm_prenotify()) QTimer::singleShot((KdenliveSettings::captureinterval() - KdenliveSettings::sm_notifytime()) * 1000, this, SLOT(slotPreNotify()));
498         m_intervalTimer.start();
499     }
500     else m_captureAction->setChecked(false);
501 }
502
503 void StopmotionWidget::slotPreNotify()
504 {
505     if (m_captureAction->isChecked()) KNotification::event("ReadyToCapture", i18n("Going to Capture Frame"), QPixmap(), this);
506 }
507
508
509 void StopmotionWidget::slotNewThumb(const QString path)
510 {
511     if (!KdenliveSettings::showstopmotionthumbs()) return;
512     m_filesList.append(path);
513     if (m_showOverlay->isChecked()) slotUpdateOverlay();
514     if (!m_future.isRunning()) m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
515 }
516
517 void StopmotionWidget::slotPrepareThumbs()
518 {
519     if (m_filesList.isEmpty()) return;
520     QString path = m_filesList.takeFirst();
521     emit doCreateThumbs(QImage(path), SlideshowClip::getFrameNumberFromPath(path));
522
523 }
524
525 void StopmotionWidget::slotCreateThumbs(QImage img, int ix)
526 {
527     if (img.isNull()) {
528         m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
529         return;
530     }
531     int height = 90;
532     int width = height * img.width() / img.height();
533     frame_list->setIconSize(QSize(width, height));
534     QPixmap pix = QPixmap::fromImage(img).scaled(width, height);
535     QString nb = QString::number(ix);
536     QPainter p(&pix);
537     QFontInfo finfo(font());
538     p.fillRect(0, 0, finfo.pixelSize() * nb.count() + 6, finfo.pixelSize() + 6, QColor(80, 80, 80, 150));
539     p.setPen(Qt::white);
540     p.drawText(QPoint(3, finfo.pixelSize() + 3), nb);
541     p.end();
542     QIcon icon(pix);
543     QListWidgetItem* item = new QListWidgetItem(icon, QString(), frame_list);
544     item->setToolTip(getPathForFrame(ix, sequence_name->currentText()));
545     item->setData(Qt::UserRole, ix);
546     frame_list->blockSignals(true);
547     frame_list->setCurrentItem(item);
548     frame_list->blockSignals(false);
549     m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
550 }
551
552 QString StopmotionWidget::getPathForFrame(int ix, QString seqName)
553 {
554     if (seqName.isEmpty()) seqName = m_sequenceName;
555     return m_projectFolder.path(KUrl::AddTrailingSlash) + seqName + "_" + QString::number(ix).rightJustified(4, '0', false) + ".png";
556 }
557
558 void StopmotionWidget::slotShowFrame(const QString& path)
559 {
560     //slotLive(false);
561     QImage img(path);
562     capture_button->setEnabled(false);
563     slotLive(false);
564     if (!img.isNull()) {
565         if (m_bmCapture) m_bmCapture->hidePreview(true);
566         m_frame_preview->setImage(img);
567         m_frame_preview->setHidden(false);
568         m_frame_preview->update();
569     }
570 }
571
572 void StopmotionWidget::slotShowSelectedFrame()
573 {
574     QListWidgetItem* item = frame_list->currentItem();
575     if (item) {
576         //int ix = item->data(Qt::UserRole).toInt();;
577         slotShowFrame(item->toolTip());
578     }
579 }
580
581 void StopmotionWidget::slotAddSequence()
582 {
583     emit addOrUpdateSequence(getPathForFrame(0));
584 }
585
586 void StopmotionWidget::slotPlayPreview(bool animate)
587 {
588     m_animate = animate;
589     if (!animate) {
590         // stop animation
591         m_animationList.clear();
592         return;
593     }
594     if (KdenliveSettings::showstopmotionthumbs()) {
595         if (KdenliveSettings::sm_framesplayback() == 0) frame_list->setCurrentRow(0);
596         else frame_list->setCurrentRow(frame_list->count() - KdenliveSettings::sm_framesplayback());
597         QTimer::singleShot(200, this, SLOT(slotAnimate()));
598     } else {
599         SlideshowClip::selectedPath(getPathForFrame(0, sequence_name->currentText()), false, QString(), &m_animationList);
600         if (KdenliveSettings::sm_framesplayback() > 0) {
601             // only play the last x frames
602             while (m_animationList.count() > KdenliveSettings::sm_framesplayback() + 1) {
603                 m_animationList.removeFirst();
604             }
605         }
606         m_animatedIndex = 0;
607         slotAnimate();
608     }
609 }
610
611 void StopmotionWidget::slotAnimate()
612 {
613     if (m_animate) {
614         if (KdenliveSettings::showstopmotionthumbs()) {
615             int newRow = frame_list->currentRow() + 1;
616             if (KdenliveSettings::sm_loop() || newRow < frame_list->count()) {
617                 if (newRow >= frame_list->count()) {
618                     if (KdenliveSettings::sm_framesplayback() == 0) newRow = 0;
619                     else {
620                         // seek to correct frame
621                         newRow = frame_list->count() - KdenliveSettings::sm_framesplayback();
622                     }
623                 }
624                 frame_list->setCurrentRow(newRow);
625                 QTimer::singleShot(100, this, SLOT(slotAnimate()));
626                 return;
627             }
628         } else {
629             if (m_animatedIndex >= m_animationList.count()) {
630                 if (KdenliveSettings::sm_loop()) m_animatedIndex = 0;
631                 else m_animatedIndex = -1;
632             }
633             if (m_animatedIndex > -1) {
634                 slotShowFrame(m_animationList.at(m_animatedIndex));
635                 m_animatedIndex++;
636                 QTimer::singleShot(100, this, SLOT(slotAnimate()));
637                 return;
638             }
639         }
640     }
641     m_animate = false;
642     preview_button->setChecked(false);
643
644 }
645
646 QListWidgetItem* StopmotionWidget::getFrameFromIndex(int ix)
647 {
648     QListWidgetItem* item = NULL;
649     int pos = ix;
650     if (ix >= frame_list->count()) {
651         pos = frame_list->count() - 1;
652     }
653     if (ix < 0) pos = 0;
654     item = frame_list->item(pos);
655
656     int value = item->data(Qt::UserRole).toInt();
657     if (value == ix) return item;
658     else if (value < ix) {
659         pos++;
660         while (pos < frame_list->count()) {
661             item = frame_list->item(pos);
662             value = item->data(Qt::UserRole).toInt();
663             if (value == ix) return item;
664             pos++;
665         }
666     } else {
667         pos --;
668         while (pos >= 0) {
669             item = frame_list->item(pos);
670             value = item->data(Qt::UserRole).toInt();
671             if (value == ix) return item;
672             pos --;
673         }
674     }
675     return NULL;
676 }
677
678
679 void StopmotionWidget::selectFrame(int ix)
680 {
681     frame_list->blockSignals(true);
682     QListWidgetItem* item = getFrameFromIndex(ix);
683     if (!item) return;
684     frame_list->setCurrentItem(item);
685     frame_list->blockSignals(false);
686 }
687
688 void StopmotionWidget::slotSeekFrame(bool forward)
689 {
690     int ix = frame_list->currentRow();
691     if (forward) {
692         if (ix < frame_list->count() - 1) frame_list->setCurrentRow(ix + 1);
693     } else if (ix > 0) frame_list->setCurrentRow(ix - 1);
694 }
695
696 void StopmotionWidget::slotRemoveFrame()
697 {
698     if (frame_list->currentItem() == NULL) return;
699     QString path = frame_list->currentItem()->toolTip();
700     if (KMessageBox::questionYesNo(this, i18n("Delete frame %1 from disk?", path), i18n("Delete Frame")) != KMessageBox::Yes) return;
701     QFile f(path);
702     if (f.remove()) {
703         QListWidgetItem* item = frame_list->takeItem(frame_list->currentRow());
704         int ix = item->data(Qt::UserRole).toInt();
705         if (ix == m_sequenceFrame - 1) {
706             // We are removing the last frame, update counter
707             QListWidgetItem* item2 = frame_list->item(frame_list->count() - 1);
708             if (item2) m_sequenceFrame = item2->data(Qt::UserRole).toInt() + 1;
709         }
710         delete item;
711     }
712 }
713
714 void StopmotionWidget::slotSwitchAnalyse(bool isOn)
715 {
716     KdenliveSettings::setAnalyse_stopmotion(isOn);
717     m_bmCapture->setAnalyse(isOn);
718 }
719
720