]> git.sesse.net Git - kdenlive/blob - src/keyframeedit.cpp
Prevent crash with corrupted keyframe effect
[kdenlive] / src / keyframeedit.cpp
1 /***************************************************************************
2                           geomeytrval.cpp  -  description
3                              -------------------
4     begin                : 03 Aug 2008
5     copyright            : (C) 2008 by Marco Gittler
6     email                : g.marco@freenet.de
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 "keyframeedit.h"
19 #include "doubleparameterwidget.h"
20 #include "kdenlivesettings.h"
21
22 #include <KDebug>
23 #include <KGlobalSettings>
24
25 #include <QHeaderView>
26
27 KeyframeEdit::KeyframeEdit(QDomElement e, int minFrame, int maxFrame, int minVal, int maxVal, Timecode tc, int active_keyframe, QWidget* parent) :
28         QWidget(parent),
29         m_min(minFrame),
30         m_max(maxFrame),
31         m_minVal(minVal),
32         m_maxVal(maxVal),
33         m_timecode(tc),
34         m_previousPos(0),
35         m_active_keyframe(active_keyframe)
36 {
37     kDebug() << " / / / /MODIFIED KFR: " << m_active_keyframe;
38     setupUi(this);
39     m_params.append(e.cloneNode().toElement());
40     keyframe_list->setFont(KGlobalSettings::generalFont());
41     keyframe_seek->setChecked(KdenliveSettings::keyframeseek());
42     connect(keyframe_seek, SIGNAL(stateChanged(int)), this, SLOT(slotSetSeeking(int)));
43
44     button_add->setIcon(KIcon("list-add"));
45     button_add->setToolTip(i18n("Add keyframe"));
46     button_delete->setIcon(KIcon("list-remove"));
47     button_delete->setToolTip(i18n("Delete keyframe"));
48     connect(keyframe_list, SIGNAL(itemSelectionChanged()), this, SLOT(slotAdjustKeyframeInfo()));
49     connect(keyframe_list, SIGNAL(cellChanged(int, int)), this, SLOT(slotGenerateParams(int, int)));
50     setupParam();
51
52     keyframe_list->resizeRowsToContents();
53     keyframe_list->resizeColumnsToContents();
54     //keyframe_list->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
55     connect(button_delete, SIGNAL(clicked()), this, SLOT(slotDeleteKeyframe()));
56     connect(button_add, SIGNAL(clicked()), this, SLOT(slotAddKeyframe()));
57     //connect(keyframe_list, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotSaveCurrentParam(QTreeWidgetItem *, int)));
58     connect(keyframe_pos, SIGNAL(valueChanged(int)), this, SLOT(slotAdjustKeyframePos(int)));
59     //connect(keyframe_val, SIGNAL(valueChanged(int)), this, SLOT(slotAdjustKeyframeValue(int)));
60     keyframe_pos->setPageStep(1);
61     if (!keyframe_list->currentItem()) {
62         keyframe_list->setCurrentCell(0, 0);
63         keyframe_list->selectRow(0);
64     }
65     // ensure the keyframe list shows at least 3 lines
66     keyframe_list->setMinimumHeight(QFontInfo(keyframe_list->font()).pixelSize() * 9);
67     /*m_delegate = new KeyItemDelegate(minVal, maxVal);
68     keyframe_list->setItemDelegate(m_delegate);*/
69 }
70
71 KeyframeEdit::~KeyframeEdit()
72 {
73     keyframe_list->blockSignals(true);
74     keyframe_list->clear();
75     QLayoutItem *child;
76     while ((child = m_slidersLayout->takeAt(0)) != 0) {
77         QWidget *wid = child->widget();
78         delete child;
79         if (wid)
80             delete wid;
81     }
82     //delete m_delegate;
83 }
84
85 void KeyframeEdit::addParameter(QDomElement e)
86 {
87     keyframe_list->blockSignals(true);
88     m_params.append(e.cloneNode().toElement());
89     QDomNode na = e.firstChildElement("name");
90     QString paramName = i18n(na.toElement().text().toUtf8().data());
91     int columnId = keyframe_list->columnCount();
92     keyframe_list->insertColumn(columnId);
93     keyframe_list->setHorizontalHeaderItem(columnId, new QTableWidgetItem(paramName));
94
95     DoubleParameterWidget *doubleparam = new DoubleParameterWidget(paramName, 0,
96             m_params.at(columnId).attribute("min").toInt(), m_params.at(columnId).attribute("max").toInt(),
97             m_params.at(columnId).attribute("default").toInt(), m_params.at(columnId).attribute("suffix"), this);
98     connect(doubleparam, SIGNAL(valueChanged(int)), this, SLOT(slotAdjustKeyframeValue(int)));
99     m_slidersLayout->addWidget(doubleparam, columnId, 0);
100
101     QStringList frames = e.attribute("keyframes").split(";", QString::SkipEmptyParts);
102     for (int i = 0; i < frames.count(); i++) {
103         int frame = frames.at(i).section(':', 0, 0).toInt();
104         bool found = false;
105         int j;
106         for (j = 0; j < keyframe_list->rowCount(); j++) {
107             int currentPos = getPos(j);
108             if (frame == currentPos) {
109                 keyframe_list->setItem(j, columnId, new QTableWidgetItem(frames.at(i).section(':', 1, 1)));
110                 found = true;
111                 break;
112             } else if (currentPos > frame) {
113                 break;
114             }
115         }
116         if (!found) {
117             keyframe_list->insertRow(j);
118             keyframe_list->setVerticalHeaderItem(j, new QTableWidgetItem(getPosString(frame)));
119             keyframe_list->setItem(j, columnId, new QTableWidgetItem(frames.at(i).section(':', 1, 1)));
120             keyframe_list->resizeRowToContents(j);
121         }
122     }
123     keyframe_list->resizeColumnsToContents();
124     keyframe_list->blockSignals(false);
125     slotAdjustKeyframeInfo(false);
126 }
127
128 void KeyframeEdit::setupParam()
129 {
130     keyframe_list->blockSignals(true);
131     keyframe_list->clear();
132     int col = keyframe_list->columnCount();
133     QDomNode na = m_params.at(0).firstChildElement("name");
134     QString paramName = i18n(na.toElement().text().toUtf8().data());
135     kDebug() << " INSERT COL: " << col << ", " << paramName;
136     keyframe_list->insertColumn(col);
137     keyframe_list->setHorizontalHeaderItem(col, new QTableWidgetItem(paramName));
138     m_slidersLayout = new QGridLayout(param_sliders);
139
140     DoubleParameterWidget *doubleparam = new DoubleParameterWidget(paramName, 0,
141             m_params.at(0).attribute("min").toInt(), m_params.at(0).attribute("max").toInt(),
142             m_params.at(0).attribute("default").toInt(), m_params.at(0).attribute("suffix"), this);
143     connect(doubleparam, SIGNAL(valueChanged(int)), this, SLOT(slotAdjustKeyframeValue(int)));
144     m_slidersLayout->addWidget(doubleparam, 0, 0);
145
146     keyframe_list->setSelectionBehavior(QAbstractItemView::SelectRows);
147     keyframe_list->setSelectionMode(QAbstractItemView::SingleSelection);
148
149     QStringList frames = m_params.at(0).attribute("keyframes").split(";", QString::SkipEmptyParts);
150     setEnabled(frames.count() > 0);
151     for (int i = 0; i < frames.count(); i++) {
152         keyframe_list->insertRow(i);
153         int currentpos = frames.at(i).section(':', 0, 0).toInt();
154         keyframe_list->setVerticalHeaderItem(i, new QTableWidgetItem(getPosString(currentpos)));
155         keyframe_list->setItem(i, col, new QTableWidgetItem(frames.at(i).section(':', 1, 1)));
156         if ((m_active_keyframe > -1) && (m_active_keyframe == currentpos)) {
157             keyframe_list->setCurrentCell(i, 0);
158             keyframe_list->selectRow(i);
159         }
160         //item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled);
161     }
162     /*QTreeWidgetItem *first = keyframe_list->topLevelItem(0);
163     if (first) keyframe_list->setCurrentItem(first);*/
164     keyframe_list->blockSignals(false);
165     //keyframe_list->setCurrentCell(0, 0);
166     slotAdjustKeyframeInfo(false);
167     button_delete->setEnabled(keyframe_list->rowCount() > 1);
168 }
169
170 void KeyframeEdit::slotDeleteKeyframe()
171 {
172     if (keyframe_list->rowCount() < 2)
173         return;
174     int col = keyframe_list->currentColumn();
175     int row = keyframe_list->currentRow();
176     keyframe_list->removeRow(keyframe_list->currentRow());
177     row = qMin(row, keyframe_list->rowCount() - 1);
178     keyframe_list->setCurrentCell(row, col);
179     keyframe_list->selectRow(row);
180     generateAllParams();
181     button_delete->setEnabled(keyframe_list->rowCount() > 1);
182 }
183
184 void KeyframeEdit::slotAddKeyframe()
185 {
186     keyframe_list->blockSignals(true);
187     QTableWidgetItem *item = keyframe_list->currentItem();
188     int row = keyframe_list->currentRow();
189     int col = keyframe_list->currentColumn();
190     int newrow = row;
191     int pos1 = getPos(row);
192
193     int result;
194     kDebug() << "// ADD KF: " << row << ", MAX: " << keyframe_list->rowCount() << ", POS: " << pos1;
195     if (row < (keyframe_list->rowCount() - 1)) {
196         result = pos1 + (getPos(row + 1) - pos1) / 2;
197         newrow++;
198     } else if (row == 0) {
199         if (pos1 == m_min) {
200             result = m_max - 1;
201             newrow++;
202         } else {
203             result = m_min;
204         }
205     } else {
206         int pos2 = getPos(row - 1);
207         result = pos2 + (pos1 - pos2) / 2;
208     }
209
210     keyframe_list->insertRow(newrow);
211     keyframe_list->setVerticalHeaderItem(newrow, new QTableWidgetItem(getPosString(result)));
212     for (int i = 0; i < keyframe_list->columnCount(); i++)
213         keyframe_list->setItem(newrow, i, new QTableWidgetItem(keyframe_list->item(item->row(), i)->text()));
214
215     keyframe_list->resizeRowsToContents();
216     //keyframe_list->resizeRowToContents(newrow);
217     slotAdjustKeyframeInfo();
218     keyframe_list->blockSignals(false);
219     generateAllParams();
220     button_delete->setEnabled(keyframe_list->rowCount() > 1);
221     keyframe_list->setCurrentCell(newrow, col);
222     keyframe_list->selectRow(newrow);
223     //slotGenerateParams(newrow, 0);
224 }
225
226 void KeyframeEdit::slotGenerateParams(int row, int column)
227 {
228     if (column == -1) {
229         // position of keyframe changed
230         QTableWidgetItem *item = keyframe_list->item(row, 0);
231         if (item == NULL)
232             return;
233
234         int pos = getPos(row);
235         if (pos <= m_min)
236             pos = m_min;
237         if (pos > m_max)
238             pos = m_max;
239         QString val = getPosString(pos);
240         if (val != keyframe_list->verticalHeaderItem(row)->text())
241             keyframe_list->verticalHeaderItem(row)->setText(val);
242
243         for (int col = 0; col < keyframe_list->horizontalHeader()->count(); col++) {
244             item = keyframe_list->item(row, col);
245             int v = item->text().toInt();
246             if (v >= m_params.at(col).attribute("max").toInt())
247                 item->setText(m_params.at(col).attribute("max"));
248             if (v <= m_params.at(col).attribute("min").toInt())
249                 item->setText(m_params.at(col).attribute("min"));
250             QString keyframes;
251             for (int i = 0; i < keyframe_list->rowCount(); i++) {
252                 if (keyframe_list->item(i, col))
253                     keyframes.append(QString::number(getPos(i)) + ':' + keyframe_list->item(i, col)->text() + ';');
254             }
255             m_params[col].setAttribute("keyframes", keyframes);
256         }
257
258         emit parameterChanged();
259         return;
260
261     }
262     QTableWidgetItem *item = keyframe_list->item(row, column);
263     if (item == NULL)
264         return;
265
266     int pos = getPos(row);
267     if (pos <= m_min)
268         pos = m_min;
269     if (pos > m_max)
270         pos = m_max;
271     /*QList<QTreeWidgetItem *> duplicates = keyframe_list->findItems(val, Qt::MatchExactly, 0);
272     duplicates.removeAll(item);
273     if (!duplicates.isEmpty()) {
274         // Trying to insert a keyframe at existing value, revert it
275         val = m_timecode.getTimecodeFromFrames(m_previousPos);
276     }*/
277     QString val = getPosString(pos);
278     if (val != keyframe_list->verticalHeaderItem(row)->text())
279         keyframe_list->verticalHeaderItem(row)->setText(val);
280
281     int v = item->text().toInt();
282     if (v >= m_params.at(column).attribute("max").toInt())
283         item->setText(m_params.at(column).attribute("max"));
284     if (v <= m_params.at(column).attribute("min").toInt())
285         item->setText(m_params.at(column).attribute("min"));
286     slotAdjustKeyframeInfo(false);
287
288     QString keyframes;
289     for (int i = 0; i < keyframe_list->rowCount(); i++) {
290         if (keyframe_list->item(i, column))
291             keyframes.append(QString::number(getPos(i)) + ':' + keyframe_list->item(i, column)->text() + ';');
292     }
293     m_params[column].setAttribute("keyframes", keyframes);
294     emit parameterChanged();
295 }
296
297 void KeyframeEdit::generateAllParams()
298 {
299     for (int col = 0; col < keyframe_list->columnCount(); col++) {
300         QString keyframes;
301         for (int i = 0; i < keyframe_list->rowCount(); i++) {
302             if (keyframe_list->item(i, col))
303                 keyframes.append(QString::number(getPos(i)) + ':' + keyframe_list->item(i, col)->text() + ';');
304         }
305         m_params[col].setAttribute("keyframes", keyframes);
306     }
307     emit parameterChanged();
308 }
309
310 const QString KeyframeEdit::getValue(const QString &name)
311 {
312     for (int col = 0; col < keyframe_list->columnCount(); col++) {
313         QDomNode na = m_params.at(col).firstChildElement("name");
314         QString paramName = i18n(na.toElement().text().toUtf8().data());
315         kDebug() << paramName << " == " << name;
316         if (paramName == name)
317             return m_params.at(col).attribute("keyframes");
318     }
319     return QString();
320 }
321
322 void KeyframeEdit::slotAdjustKeyframeInfo(bool seek)
323 {
324     QTableWidgetItem *item = keyframe_list->currentItem();
325     if (!item)
326         return;
327     int min = m_min;
328     int max = m_max;
329     QTableWidgetItem *above = keyframe_list->item(item->row() - 1, item->column());
330     QTableWidgetItem *below = keyframe_list->item(item->row() + 1, item->column());
331     if (above)
332         min = getPos(above->row()) + 1;
333     if (below)
334         max = getPos(below->row()) - 1;
335
336     keyframe_pos->blockSignals(true);
337     keyframe_pos->setRange(min, max);
338     keyframe_pos->setValue(getPos(item->row()));
339     keyframe_pos->blockSignals(false);
340     for (int col = 0; col < keyframe_list->columnCount(); col++) {
341         DoubleParameterWidget *doubleparam = static_cast <DoubleParameterWidget*>(m_slidersLayout->itemAtPosition(col, 0)->widget());
342         if (!doubleparam)
343             continue;
344         doubleparam->blockSignals(true);
345         if (keyframe_list->item(item->row(), col)) {
346             doubleparam->setValue(keyframe_list->item(item->row(), col)->text().toInt());
347         } else {
348             kDebug() << "Null pointer exception caught: http://www.kdenlive.org/mantis/view.php?id=1771";
349         }
350         doubleparam->blockSignals(false);
351     }
352     if (KdenliveSettings::keyframeseek() && seek)
353         emit seekToPos(keyframe_pos->value() - m_min);
354 }
355
356 void KeyframeEdit::slotAdjustKeyframePos(int value)
357 {
358     QTableWidgetItem *item = keyframe_list->currentItem();
359     keyframe_list->verticalHeaderItem(item->row())->setText(getPosString(value));
360     slotGenerateParams(item->row(), -1);
361     if (KdenliveSettings::keyframeseek())
362         emit seekToPos(value - m_min);
363 }
364
365 void KeyframeEdit::slotAdjustKeyframeValue(int /*value*/)
366 {
367     QTableWidgetItem *item = keyframe_list->currentItem();
368     for (int col = 0; col < keyframe_list->columnCount(); col++) {
369         DoubleParameterWidget *doubleparam = static_cast <DoubleParameterWidget*>(m_slidersLayout->itemAtPosition(col, 0)->widget());
370         if (!doubleparam)
371             continue;
372         int val = doubleparam->getValue();
373         QTableWidgetItem *nitem = keyframe_list->item(item->row(), col);
374         if (nitem->text().toInt() != val)
375             nitem->setText(QString::number(val));
376     }
377     //keyframe_list->item(item->row() - 1, item->column());
378
379 }
380
381 int KeyframeEdit::getPos(int row)
382 {
383     if (KdenliveSettings::frametimecode())
384         return keyframe_list->verticalHeaderItem(row)->text().toInt();
385     else
386         return m_timecode.getFrameCount(keyframe_list->verticalHeaderItem(row)->text());
387 }
388
389 QString KeyframeEdit::getPosString(int pos)
390 {
391     if (KdenliveSettings::frametimecode())
392         return QString::number(pos);
393     else
394         return m_timecode.getTimecodeFromFrames(pos);
395 }
396
397 void KeyframeEdit::slotSetSeeking(int state)
398 {
399     KdenliveSettings::setKeyframeseek(state == Qt::Checked);
400 }
401
402 void KeyframeEdit::updateTimecodeFormat()
403 {
404     for (int row = 0; row < keyframe_list->rowCount(); ++row) {
405         QString pos = keyframe_list->verticalHeaderItem(row)->text();
406         if (KdenliveSettings::frametimecode())
407             keyframe_list->verticalHeaderItem(row)->setText(QString::number(m_timecode.getFrameCount(pos)));
408         else
409             keyframe_list->verticalHeaderItem(row)->setText(m_timecode.getTimecodeFromFrames(pos.toInt()));
410     }
411 }
412
413 /*void KeyframeEdit::slotSaveCurrentParam(QTreeWidgetItem *item, int column)
414 {
415     if (item && column == 0) m_previousPos = m_timecode.getFrameCount(item->text(0));
416 }*/
417