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