]> git.sesse.net Git - kdenlive/blob - src/effectstack/collapsibleeffect.cpp
Effect stack: do not change parameter value on mousewheel when scrolling.
[kdenlive] / src / effectstack / collapsibleeffect.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 "collapsibleeffect.h"
22
23 #include "ui_listval_ui.h"
24 #include "ui_boolval_ui.h"
25 #include "ui_wipeval_ui.h"
26 #include "ui_urlval_ui.h"
27 #include "complexparameter.h"
28 #include "geometryval.h"
29 #include "positionedit.h"
30 #include "projectlist.h"
31 #include "effectslist.h"
32 #include "kdenlivesettings.h"
33 #include "profilesdialog.h"
34 #include "kis_curve_widget.h"
35 #include "kis_cubic_curve.h"
36 #include "choosecolorwidget.h"
37 #include "geometrywidget.h"
38 #include "colortools.h"
39 #include "doubleparameterwidget.h"
40 #include "cornerswidget.h"
41 #include "dragvalue.h"
42 #include "beziercurve/beziersplinewidget.h"
43 #ifdef USE_QJSON
44 #include "rotoscoping/rotowidget.h"
45 #endif
46
47
48 #include <QDialog>
49 #include <QVBoxLayout>
50 #include <KDebug>
51 #include <KGlobalSettings>
52 #include <KLocale>
53 #include <KFileDialog>
54 #include <KUrlRequester>
55
56 class Boolval: public QWidget, public Ui::Boolval_UI
57 {
58 };
59
60 class Listval: public QWidget, public Ui::Listval_UI
61 {
62 };
63
64 class Wipeval: public QWidget, public Ui::Wipeval_UI
65 {
66 };
67
68 class Urlval: public QWidget, public Ui::Urlval_UI
69 {
70 };
71
72 QMap<QString, QImage> CollapsibleEffect::iconCache;
73
74 void clearLayout(QLayout *layout)
75 {
76     QLayoutItem *item;
77     while((item = layout->takeAt(0))) {
78         if (item->layout()) {
79             clearLayout(item->layout());
80             delete item->layout();
81         }
82         if (item->widget()) {
83             delete item->widget();
84         }
85         delete item;
86     }
87 }
88
89 MySpinBox::MySpinBox(QWidget * parent):
90     QSpinBox(parent)
91 {
92     setFocusPolicy(Qt::StrongFocus);
93 }
94
95 void MySpinBox::focusInEvent(QFocusEvent*)
96 {
97      setFocusPolicy(Qt::WheelFocus);
98 }
99
100 void MySpinBox::focusOutEvent(QFocusEvent*)
101 {
102      setFocusPolicy(Qt::StrongFocus);
103 }
104
105 CollapsibleEffect::CollapsibleEffect(QDomElement effect, QDomElement original_effect, ItemInfo info, int ix, EffectMetaInfo *metaInfo, bool lastEffect, QWidget * parent) :
106         QWidget(parent),
107         m_paramWidget(NULL),
108         m_effect(effect),
109         m_original_effect(original_effect),
110         m_lastEffect(lastEffect),
111         m_active(false)
112 {
113     //setMouseTracking(true);
114     setupUi(this);
115     frame->setBackgroundRole(QPalette::Midlight);
116     frame->setAutoFillBackground(true);
117     setFont(KGlobalSettings::smallestReadableFont());
118     QDomElement namenode = m_effect.firstChildElement("name");
119     if (namenode.isNull()) return;
120     QString type = m_effect.attribute("type", QString());
121     KIcon icon;
122     if (type == "audio") icon = KIcon("kdenlive-show-audio");
123     else if (m_effect.attribute("tag") == "region") icon = KIcon("kdenlive-mask-effect");
124     else if (type == "custom") icon = KIcon("kdenlive-custom-effect");
125     else icon = KIcon("kdenlive-show-video");
126    
127     buttonUp->setIcon(KIcon("go-up"));
128     buttonUp->setToolTip(i18n("Move effect up"));
129     if (!lastEffect) {
130         buttonDown->setIcon(KIcon("go-down"));
131         buttonDown->setToolTip(i18n("Move effect down"));
132     }
133     buttonDel->setIcon(KIcon("edit-delete"));
134     buttonDel->setToolTip(i18n("Delete effect"));
135     buttonSave->setIcon(KIcon("document-save"));
136     buttonSave->setToolTip(i18n("Save effect"));
137
138     buttonUp->setVisible(false);
139     buttonDown->setVisible(false);
140     buttonSave->setVisible(false);
141     buttonDel->setVisible(false);
142     
143     /*buttonReset->setIcon(KIcon("view-refresh"));
144     buttonReset->setToolTip(i18n("Reset effect"));*/
145     //checkAll->setToolTip(i18n("Enable/Disable all effects"));
146     //buttonShowComments->setIcon(KIcon("help-about"));
147     //buttonShowComments->setToolTip(i18n("Show additional information for the parameters"));
148             
149     title->setText(i18n(namenode.text().toUtf8().data()));
150     effectIcon->setPixmap(icon.pixmap(QSize(16,16)));
151     
152     //QLabel *lab = new QLabel("HEllo", widgetFrame);
153     //m_vbox->addWidget(lab);
154     if (m_effect.attribute("disable") == "1") {
155         enabledBox->setCheckState(Qt::Unchecked);
156         title->setEnabled(false);
157     }
158     else {
159         enabledBox->setCheckState(Qt::Checked);
160     }
161
162     connect(collapseButton, SIGNAL(clicked()), this, SLOT(slotSwitch()));
163     connect(enabledBox, SIGNAL(toggled(bool)), this, SLOT(slotEnable(bool)));
164     connect(buttonUp, SIGNAL(clicked()), this, SLOT(slotEffectUp()));
165     connect(buttonDown, SIGNAL(clicked()), this, SLOT(slotEffectDown()));
166     connect(buttonDel, SIGNAL(clicked()), this, SLOT(slotDeleteEffect()));
167     setupWidget(info, ix, metaInfo);
168     Q_FOREACH( QSpinBox * sp, findChildren<QSpinBox*>() ) {
169         sp->installEventFilter( this );
170         sp->setFocusPolicy( Qt::StrongFocus );
171     }
172     Q_FOREACH( KComboBox * cb, findChildren<KComboBox*>() ) {
173         cb->installEventFilter( this );
174         cb->setFocusPolicy( Qt::StrongFocus );
175     }
176     Q_FOREACH( QProgressBar * cb, findChildren<QProgressBar*>() ) {
177         cb->installEventFilter( this );
178         cb->setFocusPolicy( Qt::StrongFocus );
179     }
180     
181 }
182
183 CollapsibleEffect::~CollapsibleEffect()
184 {
185     if (m_paramWidget) delete m_paramWidget;
186 }
187
188 bool CollapsibleEffect::eventFilter( QObject * o, QEvent * e ) 
189 {
190     if(e->type() == QEvent::Wheel) {
191         if (qobject_cast<QAbstractSpinBox*>(o)) {
192             if(qobject_cast<QAbstractSpinBox*>(o)->focusPolicy() == Qt::WheelFocus)
193             {
194                 e->accept();
195                 return false;
196             }
197             else
198             {
199                 e->ignore();
200                 return true;
201             }
202         }
203         if (qobject_cast<KComboBox*>(o)) {
204             if(qobject_cast<KComboBox*>(o)->focusPolicy() == Qt::WheelFocus)
205             {
206                 e->accept();
207                 return false;
208             }
209             else
210             {
211                 e->ignore();
212                 return true;
213             }
214         }
215         if (qobject_cast<QProgressBar*>(o)) {
216             if(qobject_cast<QProgressBar*>(o)->focusPolicy() == Qt::WheelFocus)
217             {
218                 e->accept();
219                 return false;
220             }
221             else
222             {
223                 e->ignore();
224                 return true;
225             }
226         }
227     }
228     return QWidget::eventFilter(o, e);
229 }
230
231
232 void CollapsibleEffect::setActive(bool activate)
233 {
234     m_active = activate;
235     frame->setBackgroundRole(m_active ? QPalette::Mid : QPalette::Midlight);
236     frame->setAutoFillBackground(activate);
237 }
238
239 void CollapsibleEffect::mouseDoubleClickEvent ( QMouseEvent * event )
240 {
241     if (frame->underMouse() && collapseButton->isEnabled()) slotSwitch();
242     QWidget::mouseDoubleClickEvent(event);
243 }
244
245 void CollapsibleEffect::mousePressEvent ( QMouseEvent *event )
246 {
247     if (!m_active) emit activateEffect(m_paramWidget->index());
248     QWidget::mousePressEvent(event);
249 }
250
251 void CollapsibleEffect::enterEvent ( QEvent * event )
252 {
253     if (m_paramWidget->index() > 0) buttonUp->setVisible(true);
254     if (!m_lastEffect) buttonDown->setVisible(true);
255     buttonSave->setVisible(true);
256     buttonDel->setVisible(true);
257     if (!m_active) frame->setBackgroundRole(QPalette::Midlight);
258     frame->setAutoFillBackground(true);
259     QWidget::enterEvent(event);
260 }
261
262 void CollapsibleEffect::leaveEvent ( QEvent * event )
263 {
264     buttonUp->setVisible(false);
265     buttonDown->setVisible(false);
266     buttonSave->setVisible(false);
267     buttonDel->setVisible(false);
268     if (!m_active) frame->setAutoFillBackground(false);
269     QWidget::leaveEvent(event);
270 }
271
272 void CollapsibleEffect::slotEnable(bool enable)
273 {
274     title->setEnabled(enable);
275     m_effect.setAttribute("disable", enable ? 0 : 1);
276     if (enable || KdenliveSettings::disable_effect_parameters()) {
277         widgetFrame->setEnabled(enable);
278     }
279     emit effectStateChanged(!enable, m_paramWidget->index());
280 }
281
282 void CollapsibleEffect::slotDeleteEffect()
283 {
284     emit deleteEffect(m_effect, m_paramWidget->index());
285 }
286
287 void CollapsibleEffect::slotEffectUp()
288 {
289     emit changeEffectPosition(m_paramWidget->index(), true);
290 }
291
292 void CollapsibleEffect::slotEffectDown()
293 {
294     emit changeEffectPosition(m_paramWidget->index(), false);
295 }
296
297 void CollapsibleEffect::slotSwitch()
298 {
299     bool enable = !widgetFrame->isVisible();
300     slotShow(enable);
301 }
302
303 void CollapsibleEffect::slotShow(bool show)
304 {
305     widgetFrame->setVisible(show);
306     if (show) {
307         collapseButton->setArrowType(Qt::DownArrow);
308         m_original_effect.removeAttribute("k_collapsed");
309     }
310     else {
311         collapseButton->setArrowType(Qt::RightArrow);
312         m_original_effect.setAttribute("k_collapsed", 1);
313     }
314 }
315
316
317 void CollapsibleEffect::setupWidget(ItemInfo info, int index, EffectMetaInfo *metaInfo)
318 {
319     if (m_effect.isNull()) {
320 //         kDebug() << "// EMPTY EFFECT STACK";
321         return;
322     }
323     if (m_effect.attribute("tag") == "region") {
324         QVBoxLayout *vbox = new QVBoxLayout(widgetFrame);
325         vbox->setContentsMargins(0, 0, 0, 0);
326         vbox->setSpacing(2);
327         QDomNodeList effects =  m_effect.elementsByTagName("effect");
328         QDomNodeList origin_effects =  m_original_effect.elementsByTagName("effect");
329         QWidget *container = new QWidget(widgetFrame);
330         vbox->addWidget(container);
331         m_paramWidget = new ParameterContainer(m_effect.toElement(), info, metaInfo, index, container);
332         for (int i = 0; i < effects.count(); i++) {
333             CollapsibleEffect *coll = new CollapsibleEffect(effects.at(i).toElement(), origin_effects.at(i).toElement(), info, i, metaInfo, container);
334             m_subParamWidgets.append(coll);
335             //container = new QWidget(widgetFrame);
336             vbox->addWidget(coll);
337             //p = new ParameterContainer(effects.at(i).toElement(), info, isEffect, container);
338         }
339         
340     }
341     else {
342         m_paramWidget = new ParameterContainer(m_effect, info, metaInfo, index, widgetFrame);
343         if (m_effect.firstChildElement("parameter").isNull()) {
344             // Effect has no parameter, don't allow expand
345             collapseButton->setEnabled(false);
346             widgetFrame->setVisible(false);            
347         }
348     }
349     if (collapseButton->isEnabled()) slotShow(!m_effect.hasAttribute("k_collapsed"));
350     connect (m_paramWidget, SIGNAL(parameterChanged(const QDomElement, const QDomElement, int)), this, SIGNAL(parameterChanged(const QDomElement, const QDomElement, int)));
351     connect (this, SIGNAL(syncEffectsPos(int)), m_paramWidget, SIGNAL(syncEffectsPos(int)));
352     connect (this, SIGNAL(effectStateChanged(bool)), m_paramWidget, SIGNAL(effectStateChanged(bool)));
353     connect (m_paramWidget, SIGNAL(checkMonitorPosition(int)), this, SIGNAL(checkMonitorPosition(int)));
354     connect (m_paramWidget, SIGNAL(seekTimeline(int)), this, SIGNAL(seekTimeline(int)));
355     
356     
357 }
358
359 void CollapsibleEffect::updateTimecodeFormat()
360 {
361     m_paramWidget->updateTimecodeFormat();
362     if (!m_subParamWidgets.isEmpty()) {
363         // we have a group
364         for (int i = 0; i < m_subParamWidgets.count(); i++)
365             m_subParamWidgets.at(i)->updateTimecodeFormat();
366     }
367 }
368
369 void CollapsibleEffect::slotSyncEffectsPos(int pos)
370 {
371     emit syncEffectsPos(pos);
372 }
373
374
375
376 ParameterContainer::ParameterContainer(QDomElement effect, ItemInfo info, EffectMetaInfo *metaInfo, int index, QWidget * parent) :
377         m_index(index),
378         m_keyframeEditor(NULL),
379         m_geometryWidget(NULL),
380         m_metaInfo(metaInfo),
381         m_effect(effect)
382 {
383     m_in = info.cropStart.frames(KdenliveSettings::project_fps());
384     m_out = (info.cropStart + info.cropDuration).frames(KdenliveSettings::project_fps()) - 1;
385
386     QDomNodeList namenode = effect.childNodes(); //elementsByTagName("parameter");
387     
388     QDomElement e = effect.toElement();
389     int minFrame = e.attribute("start").toInt();
390     int maxFrame = e.attribute("end").toInt();
391     // In transitions, maxFrame is in fact one frame after the end of transition
392     if (maxFrame > 0) maxFrame --;
393
394     bool disable = effect.attribute("disable") == "1" && KdenliveSettings::disable_effect_parameters();
395     parent->setEnabled(!disable);
396
397     bool stretch = true;
398     m_vbox = new QVBoxLayout(parent);
399     m_vbox->setContentsMargins(0, 0, 0, 0);
400     m_vbox->setSpacing(2);
401
402     for (int i = 0; i < namenode.count() ; i++) {
403         QDomElement pa = namenode.item(i).toElement();
404         if (pa.tagName() != "parameter") continue;
405         QDomElement na = pa.firstChildElement("name");
406         QDomElement commentElem = pa.firstChildElement("comment");
407         QString type = pa.attribute("type");
408         QString paramName = na.isNull() ? pa.attribute("name") : i18n(na.text().toUtf8().data());
409         QString comment;
410         if (!commentElem.isNull())
411             comment = i18n(commentElem.text().toUtf8().data());
412         QWidget * toFillin = new QWidget(parent);
413         QString value = pa.attribute("value").isNull() ?
414                         pa.attribute("default") : pa.attribute("value");
415
416
417         /** See effects/README for info on the different types */
418
419         if (type == "double" || type == "constant") {
420             double min;
421             double max;
422             if (pa.attribute("min").contains('%'))
423                 min = ProfilesDialog::getStringEval(m_metaInfo->profile, pa.attribute("min"), m_metaInfo->frameSize);
424             else
425                 min = pa.attribute("min").toDouble();
426             if (pa.attribute("max").contains('%'))
427                 max = ProfilesDialog::getStringEval(m_metaInfo->profile, pa.attribute("max"), m_metaInfo->frameSize);
428             else
429                 max = pa.attribute("max").toDouble();
430
431             DoubleParameterWidget *doubleparam = new DoubleParameterWidget(paramName, value.toDouble(), min, max,
432                     pa.attribute("default").toDouble(), comment, -1, pa.attribute("suffix"), pa.attribute("decimals").toInt(), parent);
433             doubleparam->setFocusPolicy(Qt::StrongFocus);
434             m_vbox->addWidget(doubleparam);
435             m_valueItems[paramName] = doubleparam;
436             connect(doubleparam, SIGNAL(valueChanged(double)), this, SLOT(slotCollectAllParameters()));
437             connect(this, SIGNAL(showComments(bool)), doubleparam, SLOT(slotShowComment(bool)));
438         } else if (type == "list") {
439             Listval *lsval = new Listval;
440             lsval->setupUi(toFillin);
441             lsval->list->setFocusPolicy(Qt::StrongFocus);
442             QStringList listitems = pa.attribute("paramlist").split(';');
443             if (listitems.count() == 1) {
444                 // probably custom effect created before change to ';' as separator
445                 listitems = pa.attribute("paramlist").split(",");
446             }
447             QDomElement list = pa.firstChildElement("paramlistdisplay");
448             QStringList listitemsdisplay;
449             if (!list.isNull()) {
450                 listitemsdisplay = i18n(list.text().toUtf8().data()).split(',');
451             } else {
452                 listitemsdisplay = i18n(pa.attribute("paramlistdisplay").toUtf8().data()).split(',');
453             }
454             if (listitemsdisplay.count() != listitems.count())
455                 listitemsdisplay = listitems;
456             lsval->list->setIconSize(QSize(30, 30));
457             for (int i = 0; i < listitems.count(); i++) {
458                 lsval->list->addItem(listitemsdisplay.at(i), listitems.at(i));
459                 QString entry = listitems.at(i);
460                 if (!entry.isEmpty() && (entry.endsWith(".png") || entry.endsWith(".pgm"))) {
461                     if (!CollapsibleEffect::iconCache.contains(entry)) {
462                         QImage pix(entry);
463                         CollapsibleEffect::iconCache[entry] = pix.scaled(30, 30);
464                     }
465                     lsval->list->setItemIcon(i, QPixmap::fromImage(CollapsibleEffect::iconCache[entry]));
466                 }
467             }
468             if (!value.isEmpty()) lsval->list->setCurrentIndex(listitems.indexOf(value));
469             lsval->name->setText(paramName);
470             lsval->labelComment->setText(comment);
471             lsval->widgetComment->setHidden(true);
472             m_valueItems[paramName] = lsval;
473             connect(lsval->list, SIGNAL(currentIndexChanged(int)) , this, SLOT(slotCollectAllParameters()));
474             if (!comment.isEmpty())
475                 connect(this, SIGNAL(showComments(bool)), lsval->widgetComment, SLOT(setVisible(bool)));
476             m_uiItems.append(lsval);
477         } else if (type == "bool") {
478             Boolval *bval = new Boolval;
479             bval->setupUi(toFillin);
480             bval->checkBox->setCheckState(value == "0" ? Qt::Unchecked : Qt::Checked);
481             bval->name->setText(paramName);
482             bval->labelComment->setText(comment);
483             bval->widgetComment->setHidden(true);
484             m_valueItems[paramName] = bval;
485             connect(bval->checkBox, SIGNAL(stateChanged(int)) , this, SLOT(slotCollectAllParameters()));
486             if (!comment.isEmpty())
487                 connect(this, SIGNAL(showComments(bool)), bval->widgetComment, SLOT(setVisible(bool)));
488             m_uiItems.append(bval);
489         } else if (type == "complex") {
490             ComplexParameter *pl = new ComplexParameter;
491             pl->setupParam(effect, pa.attribute("name"), 0, 100);
492             m_vbox->addWidget(pl);
493             m_valueItems[paramName+"complex"] = pl;
494             connect(pl, SIGNAL(parameterChanged()), this, SLOT(slotCollectAllParameters()));
495         } else if (type == "geometry") {
496             if (KdenliveSettings::on_monitor_effects()) {
497                 m_geometryWidget = new GeometryWidget(m_metaInfo->monitor, m_metaInfo->timecode, 0, true, effect.hasAttribute("showrotation"), parent);
498                 m_geometryWidget->setFrameSize(m_metaInfo->frameSize);
499                 m_geometryWidget->slotShowScene(!disable);
500                 // connect this before setupParam to make sure the monitor scene shows up at startup
501                 connect(m_geometryWidget, SIGNAL(checkMonitorPosition(int)), this, SIGNAL(checkMonitorPosition(int)));
502                 connect(m_geometryWidget, SIGNAL(parameterChanged()), this, SLOT(slotCollectAllParameters()));
503                 if (minFrame == maxFrame)
504                     m_geometryWidget->setupParam(pa, m_in, m_out);
505                 else
506                     m_geometryWidget->setupParam(pa, minFrame, maxFrame);
507                 m_vbox->addWidget(m_geometryWidget);
508                 m_valueItems[paramName+"geometry"] = m_geometryWidget;
509                 connect(m_geometryWidget, SIGNAL(seekToPos(int)), this, SIGNAL(seekTimeline(int)));
510                 connect(this, SIGNAL(syncEffectsPos(int)), m_geometryWidget, SLOT(slotSyncPosition(int)));
511                 connect(this, SIGNAL(effectStateChanged(bool)), m_geometryWidget, SLOT(slotShowScene(bool)));
512             } else {
513                 Geometryval *geo = new Geometryval(m_metaInfo->profile, m_metaInfo->timecode, m_metaInfo->frameSize, 0);
514                 if (minFrame == maxFrame)
515                     geo->setupParam(pa, m_in, m_out);
516                 else
517                     geo->setupParam(pa, minFrame, maxFrame);
518                 m_vbox->addWidget(geo);
519                 m_valueItems[paramName+"geometry"] = geo;
520                 connect(geo, SIGNAL(parameterChanged()), this, SLOT(slotCollectAllParameters()));
521                 connect(geo, SIGNAL(seekToPos(int)), this, SIGNAL(seekTimeline(int)));
522                 connect(this, SIGNAL(syncEffectsPos(int)), geo, SLOT(slotSyncPosition(int)));
523             }
524         } else if (type == "addedgeometry") {
525             // this is a parameter that should be linked to the geometry widget, for example rotation, shear, ...
526             if (m_geometryWidget) m_geometryWidget->addParameter(pa);
527         } else if (type == "keyframe" || type == "simplekeyframe") {
528             // keyframe editor widget
529             if (m_keyframeEditor == NULL) {
530                 KeyframeEdit *geo;
531                 if (pa.attribute("widget") == "corners") {
532                     // we want a corners-keyframe-widget
533                     CornersWidget *corners = new CornersWidget(m_metaInfo->monitor, pa, m_in, m_out, m_metaInfo->timecode, e.attribute("active_keyframe", "-1").toInt(), parent);
534                     corners->slotShowScene(!disable);
535                     connect(corners, SIGNAL(checkMonitorPosition(int)), this, SIGNAL(checkMonitorPosition(int)));
536                     connect(this, SIGNAL(effectStateChanged(bool)), corners, SLOT(slotShowScene(bool)));
537                     connect(this, SIGNAL(syncEffectsPos(int)), corners, SLOT(slotSyncPosition(int)));
538                     geo = static_cast<KeyframeEdit *>(corners);
539                 } else {
540                     geo = new KeyframeEdit(pa, m_in, m_out, m_metaInfo->timecode, e.attribute("active_keyframe", "-1").toInt());
541                 }
542                 m_vbox->addWidget(geo);
543                 m_valueItems[paramName+"keyframe"] = geo;
544                 m_keyframeEditor = geo;
545                 connect(geo, SIGNAL(parameterChanged()), this, SLOT(slotCollectAllParameters()));
546                 connect(geo, SIGNAL(seekToPos(int)), this, SIGNAL(seekTimeline(int)));
547                 connect(this, SIGNAL(showComments(bool)), geo, SIGNAL(showComments(bool)));
548             } else {
549                 // we already have a keyframe editor, so just add another column for the new param
550                 m_keyframeEditor->addParameter(pa);
551             }
552         } else if (type == "color") {
553             if (value.startsWith('#'))
554                 value = value.replace('#', "0x");
555             ChooseColorWidget *choosecolor = new ChooseColorWidget(paramName, value, parent);
556             m_vbox->addWidget(choosecolor);
557             m_valueItems[paramName] = choosecolor;
558             connect(choosecolor, SIGNAL(displayMessage(const QString&, int)), this, SIGNAL(displayMessage(const QString&, int)));
559             connect(choosecolor, SIGNAL(modified()) , this, SLOT(slotCollectAllParameters()));
560         } else if (type == "position") {
561             int pos = value.toInt();
562             if (effect.attribute("id") == "fadein" || effect.attribute("id") == "fade_from_black") {
563                 pos = pos - m_in;
564             } else if (effect.attribute("id") == "fadeout" || effect.attribute("id") == "fade_to_black") {
565                 // fadeout position starts from clip end
566                 pos = m_out - pos;
567             }
568             PositionEdit *posedit = new PositionEdit(paramName, pos, 0, m_out - m_in, m_metaInfo->timecode);
569             m_vbox->addWidget(posedit);
570             m_valueItems[paramName+"position"] = posedit;
571             connect(posedit, SIGNAL(parameterChanged()), this, SLOT(slotCollectAllParameters()));
572         } else if (type == "curve") {
573             KisCurveWidget *curve = new KisCurveWidget(parent);
574             curve->setMaxPoints(pa.attribute("max").toInt());
575             QList<QPointF> points;
576             int number = EffectsList::parameter(e, pa.attribute("number")).toInt();
577             QString inName = pa.attribute("inpoints");
578             QString outName = pa.attribute("outpoints");
579             int start = pa.attribute("min").toInt();
580             for (int j = start; j <= number; j++) {
581                 QString in = inName;
582                 in.replace("%i", QString::number(j));
583                 QString out = outName;
584                 out.replace("%i", QString::number(j));
585                 points << QPointF(EffectsList::parameter(e, in).toDouble(), EffectsList::parameter(e, out).toDouble());
586             }
587             if (!points.isEmpty())
588                 curve->setCurve(KisCubicCurve(points));
589             MySpinBox *spinin = new MySpinBox();
590             spinin->setRange(0, 1000);
591             MySpinBox *spinout = new MySpinBox();
592             spinout->setRange(0, 1000);
593             curve->setupInOutControls(spinin, spinout, 0, 1000);
594             m_vbox->addWidget(curve);
595             m_vbox->addWidget(spinin);
596             m_vbox->addWidget(spinout);
597
598             connect(curve, SIGNAL(modified()), this, SLOT(slotCollectAllParameters()));
599             m_valueItems[paramName] = curve;
600
601             QString depends = pa.attribute("depends");
602             if (!depends.isEmpty())
603                 meetDependency(paramName, type, EffectsList::parameter(e, depends));
604         } else if (type == "bezier_spline") {
605             BezierSplineWidget *widget = new BezierSplineWidget(value, parent);
606             stretch = false;
607             m_vbox->addWidget(widget);
608             m_valueItems[paramName] = widget;
609             connect(widget, SIGNAL(modified()), this, SLOT(slotCollectAllParameters()));
610             QString depends = pa.attribute("depends");
611             if (!depends.isEmpty())
612                 meetDependency(paramName, type, EffectsList::parameter(e, depends));
613 #ifdef USE_QJSON
614         } else if (type == "roto-spline") {
615             RotoWidget *roto = new RotoWidget(value, m_metaInfo->monitor, info, m_metaInfo->timecode, parent);
616             roto->slotShowScene(!disable);
617             connect(roto, SIGNAL(valueChanged()), this, SLOT(slotCollectAllParameters()));
618             connect(roto, SIGNAL(checkMonitorPosition(int)), this, SIGNAL(checkMonitorPosition(int)));
619             connect(roto, SIGNAL(seekToPos(int)), this, SIGNAL(seekTimeline(int)));
620             connect(this, SIGNAL(syncEffectsPos(int)), roto, SLOT(slotSyncPosition(int)));
621             connect(this, SIGNAL(effectStateChanged(bool)), roto, SLOT(slotShowScene(bool)));
622             m_vbox->addWidget(roto);
623             m_valueItems[paramName] = roto;
624 #endif
625         } else if (type == "wipe") {
626             Wipeval *wpval = new Wipeval;
627             wpval->setupUi(toFillin);
628             wipeInfo w = getWipeInfo(value);
629             switch (w.start) {
630             case UP:
631                 wpval->start_up->setChecked(true);
632                 break;
633             case DOWN:
634                 wpval->start_down->setChecked(true);
635                 break;
636             case RIGHT:
637                 wpval->start_right->setChecked(true);
638                 break;
639             case LEFT:
640                 wpval->start_left->setChecked(true);
641                 break;
642             default:
643                 wpval->start_center->setChecked(true);
644                 break;
645             }
646             switch (w.end) {
647             case UP:
648                 wpval->end_up->setChecked(true);
649                 break;
650             case DOWN:
651                 wpval->end_down->setChecked(true);
652                 break;
653             case RIGHT:
654                 wpval->end_right->setChecked(true);
655                 break;
656             case LEFT:
657                 wpval->end_left->setChecked(true);
658                 break;
659             default:
660                 wpval->end_center->setChecked(true);
661                 break;
662             }
663             wpval->start_transp->setValue(w.startTransparency);
664             wpval->end_transp->setValue(w.endTransparency);
665             m_valueItems[paramName] = wpval;
666             connect(wpval->end_up, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
667             connect(wpval->end_down, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
668             connect(wpval->end_left, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
669             connect(wpval->end_right, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
670             connect(wpval->end_center, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
671             connect(wpval->start_up, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
672             connect(wpval->start_down, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
673             connect(wpval->start_left, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
674             connect(wpval->start_right, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
675             connect(wpval->start_center, SIGNAL(clicked()), this, SLOT(slotCollectAllParameters()));
676             connect(wpval->start_transp, SIGNAL(valueChanged(int)), this, SLOT(slotCollectAllParameters()));
677             connect(wpval->end_transp, SIGNAL(valueChanged(int)), this, SLOT(slotCollectAllParameters()));
678             //wpval->title->setTitle(na.toElement().text());
679             m_uiItems.append(wpval);
680         } else if (type == "url") {
681             Urlval *cval = new Urlval;
682             cval->setupUi(toFillin);
683             cval->label->setText(paramName);
684             cval->urlwidget->fileDialog()->setFilter(ProjectList::getExtensions());
685             m_valueItems[paramName] = cval;
686             cval->urlwidget->setUrl(KUrl(value));
687             connect(cval->urlwidget, SIGNAL(returnPressed()) , this, SLOT(slotCollectAllParameters()));
688             connect(cval->urlwidget, SIGNAL(urlSelected(const KUrl&)) , this, SLOT(slotCollectAllParameters()));
689             m_uiItems.append(cval);
690         } else {
691             delete toFillin;
692             toFillin = NULL;
693         }
694
695         if (toFillin)
696             m_vbox->addWidget(toFillin);
697     }
698
699     if (stretch)
700         m_vbox->addStretch();
701
702     if (m_keyframeEditor)
703         m_keyframeEditor->checkVisibleParam();
704
705     // Make sure all doubleparam spinboxes have the same width, looks much better
706     QList<DoubleParameterWidget *> allWidgets = findChildren<DoubleParameterWidget *>();
707     int minSize = 0;
708     for (int i = 0; i < allWidgets.count(); i++) {
709         if (minSize < allWidgets.at(i)->spinSize()) minSize = allWidgets.at(i)->spinSize();
710     }
711     for (int i = 0; i < allWidgets.count(); i++) {
712         allWidgets.at(i)->setSpinSize(minSize);
713     }
714 }
715
716 ParameterContainer::~ParameterContainer()
717 {
718     clearLayout(m_vbox);
719     delete m_vbox;
720 }
721
722 void ParameterContainer::meetDependency(const QString& name, QString type, QString value)
723 {
724     if (type == "curve") {
725         KisCurveWidget *curve = (KisCurveWidget*)m_valueItems[name];
726         if (curve) {
727             int color = value.toInt();
728             curve->setPixmap(QPixmap::fromImage(ColorTools::rgbCurvePlane(curve->size(), (ColorTools::ColorsRGB)(color == 3 ? 4 : color), 0.8)));
729         }
730     } else if (type == "bezier_spline") {
731         BezierSplineWidget *widget = (BezierSplineWidget*)m_valueItems[name];
732         if (widget) {
733             widget->setMode((BezierSplineWidget::CurveModes)((int)(value.toDouble() * 10)));
734         }
735     }
736 }
737
738 wipeInfo ParameterContainer::getWipeInfo(QString value)
739 {
740     wipeInfo info;
741     // Convert old geometry values that used a comma as separator
742     if (value.contains(',')) value.replace(',','/');
743     QString start = value.section(';', 0, 0);
744     QString end = value.section(';', 1, 1).section('=', 1, 1);
745     if (start.startsWith("-100%/0"))
746         info.start = LEFT;
747     else if (start.startsWith("100%/0"))
748         info.start = RIGHT;
749     else if (start.startsWith("0%/100%"))
750         info.start = DOWN;
751     else if (start.startsWith("0%/-100%"))
752         info.start = UP;
753     else
754         info.start = CENTER;
755
756     if (start.count(':') == 2)
757         info.startTransparency = start.section(':', -1).toInt();
758     else
759         info.startTransparency = 100;
760
761     if (end.startsWith("-100%/0"))
762         info.end = LEFT;
763     else if (end.startsWith("100%/0"))
764         info.end = RIGHT;
765     else if (end.startsWith("0%/100%"))
766         info.end = DOWN;
767     else if (end.startsWith("0%/-100%"))
768         info.end = UP;
769     else
770         info.end = CENTER;
771
772     if (end.count(':') == 2)
773         info.endTransparency = end.section(':', -1).toInt();
774     else
775         info.endTransparency = 100;
776
777     return info;
778 }
779
780 void ParameterContainer::updateTimecodeFormat()
781 {
782     if (m_keyframeEditor)
783         m_keyframeEditor->updateTimecodeFormat();
784
785     QDomNodeList namenode = m_effect.elementsByTagName("parameter");
786     for (int i = 0; i < namenode.count() ; i++) {
787         QDomNode pa = namenode.item(i);
788         QDomElement na = pa.firstChildElement("name");
789         QString type = pa.attributes().namedItem("type").nodeValue();
790         QString paramName = na.isNull() ? pa.attributes().namedItem("name").nodeValue() : i18n(na.text().toUtf8().data());
791
792         if (type == "geometry") {
793             if (KdenliveSettings::on_monitor_effects()) {
794                 if (m_geometryWidget) m_geometryWidget->updateTimecodeFormat();
795             } else {
796                 Geometryval *geom = ((Geometryval*)m_valueItems[paramName+"geometry"]);
797                 geom->updateTimecodeFormat();
798             }
799             break;
800         } else if (type == "position") {
801             PositionEdit *posi = ((PositionEdit*)m_valueItems[paramName+"position"]);
802             posi->updateTimecodeFormat();
803             break;
804 #ifdef USE_QJSON
805         } else if (type == "roto-spline") {
806             RotoWidget *widget = static_cast<RotoWidget *>(m_valueItems[paramName]);
807             widget->updateTimecodeFormat();
808 #endif
809         }
810     }
811 }
812
813 void ParameterContainer::slotCollectAllParameters()
814 {
815     if (m_valueItems.isEmpty() || m_effect.isNull()) return;
816     QLocale locale;
817     locale.setNumberOptions(QLocale::OmitGroupSeparator);
818     const QDomElement oldparam = m_effect.cloneNode().toElement();
819     QDomElement newparam = oldparam.cloneNode().toElement();
820     QDomNodeList namenode = newparam.elementsByTagName("parameter");
821
822     for (int i = 0; i < namenode.count() ; i++) {
823         QDomNode pa = namenode.item(i);
824         QDomElement na = pa.firstChildElement("name");
825         QString type = pa.attributes().namedItem("type").nodeValue();
826         QString paramName = na.isNull() ? pa.attributes().namedItem("name").nodeValue() : i18n(na.text().toUtf8().data());
827         if (type == "complex")
828             paramName.append("complex");
829         else if (type == "position")
830             paramName.append("position");
831         else if (type == "geometry")
832             paramName.append("geometry");
833         else if (type == "keyframe")
834             paramName.append("keyframe");
835         if (type != "simplekeyframe" && type != "fixed" && type != "addedgeometry" && !m_valueItems.contains(paramName)) {
836             kDebug() << "// Param: " << paramName << " NOT FOUND";
837             continue;
838         }
839
840         QString setValue;
841         if (type == "double" || type == "constant") {
842             DoubleParameterWidget *doubleparam = (DoubleParameterWidget*)m_valueItems.value(paramName);
843             setValue = locale.toString(doubleparam->getValue());
844         } else if (type == "list") {
845             KComboBox *box = ((Listval*)m_valueItems.value(paramName))->list;
846             setValue = box->itemData(box->currentIndex()).toString();
847         } else if (type == "bool") {
848             QCheckBox *box = ((Boolval*)m_valueItems.value(paramName))->checkBox;
849             setValue = box->checkState() == Qt::Checked ? "1" : "0" ;
850         } else if (type == "color") {
851             ChooseColorWidget *choosecolor = ((ChooseColorWidget*)m_valueItems.value(paramName));
852             setValue = choosecolor->getColor();
853         } else if (type == "complex") {
854             ComplexParameter *complex = ((ComplexParameter*)m_valueItems.value(paramName));
855             namenode.item(i) = complex->getParamDesc();
856         } else if (type == "geometry") {
857             if (KdenliveSettings::on_monitor_effects()) {
858                 if (m_geometryWidget) namenode.item(i).toElement().setAttribute("value", m_geometryWidget->getValue());
859             } else {
860                 Geometryval *geom = ((Geometryval*)m_valueItems.value(paramName));
861                 namenode.item(i).toElement().setAttribute("value", geom->getValue());
862             }
863         } else if (type == "addedgeometry") {
864             namenode.item(i).toElement().setAttribute("value", m_geometryWidget->getExtraValue(namenode.item(i).toElement().attribute("name")));
865         } else if (type == "position") {
866             PositionEdit *pedit = ((PositionEdit*)m_valueItems.value(paramName));
867             int pos = pedit->getPosition();
868             setValue = QString::number(pos);
869             if (newparam.attribute("id") == "fadein" || newparam.attribute("id") == "fade_from_black") {
870                 // Make sure duration is not longer than clip
871                 /*if (pos > m_out) {
872                     pos = m_out;
873                     pedit->setPosition(pos);
874                 }*/
875                 EffectsList::setParameter(newparam, "in", QString::number(m_in));
876                 EffectsList::setParameter(newparam, "out", QString::number(m_in + pos));
877                 setValue.clear();
878             } else if (newparam.attribute("id") == "fadeout" || newparam.attribute("id") == "fade_to_black") {
879                 // Make sure duration is not longer than clip
880                 /*if (pos > m_out) {
881                     pos = m_out;
882                     pedit->setPosition(pos);
883                 }*/
884                 EffectsList::setParameter(newparam, "in", QString::number(m_out - pos));
885                 EffectsList::setParameter(newparam, "out", QString::number(m_out));
886                 setValue.clear();
887             }
888         } else if (type == "curve") {
889             KisCurveWidget *curve = ((KisCurveWidget*)m_valueItems.value(paramName));
890             QList<QPointF> points = curve->curve().points();
891             QString number = pa.attributes().namedItem("number").nodeValue();
892             QString inName = pa.attributes().namedItem("inpoints").nodeValue();
893             QString outName = pa.attributes().namedItem("outpoints").nodeValue();
894             int off = pa.attributes().namedItem("min").nodeValue().toInt();
895             int end = pa.attributes().namedItem("max").nodeValue().toInt();
896             EffectsList::setParameter(newparam, number, QString::number(points.count()));
897             for (int j = 0; (j < points.count() && j + off <= end); j++) {
898                 QString in = inName;
899                 in.replace("%i", QString::number(j + off));
900                 QString out = outName;
901                 out.replace("%i", QString::number(j + off));
902                 EffectsList::setParameter(newparam, in, locale.toString(points.at(j).x()));
903                 EffectsList::setParameter(newparam, out, locale.toString(points.at(j).y()));
904             }
905             QString depends = pa.attributes().namedItem("depends").nodeValue();
906             if (!depends.isEmpty())
907                 meetDependency(paramName, type, EffectsList::parameter(newparam, depends));
908         } else if (type == "bezier_spline") {
909             BezierSplineWidget *widget = (BezierSplineWidget*)m_valueItems.value(paramName);
910             setValue = widget->spline();
911             QString depends = pa.attributes().namedItem("depends").nodeValue();
912             if (!depends.isEmpty())
913                 meetDependency(paramName, type, EffectsList::parameter(newparam, depends));
914 #ifdef USE_QJSON
915         } else if (type == "roto-spline") {
916             RotoWidget *widget = static_cast<RotoWidget *>(m_valueItems.value(paramName));
917             setValue = widget->getSpline();
918 #endif
919         } else if (type == "wipe") {
920             Wipeval *wp = (Wipeval*)m_valueItems.value(paramName);
921             wipeInfo info;
922             if (wp->start_left->isChecked())
923                 info.start = LEFT;
924             else if (wp->start_right->isChecked())
925                 info.start = RIGHT;
926             else if (wp->start_up->isChecked())
927                 info.start = UP;
928             else if (wp->start_down->isChecked())
929                 info.start = DOWN;
930             else if (wp->start_center->isChecked())
931                 info.start = CENTER;
932             else
933                 info.start = LEFT;
934             info.startTransparency = wp->start_transp->value();
935
936             if (wp->end_left->isChecked())
937                 info.end = LEFT;
938             else if (wp->end_right->isChecked())
939                 info.end = RIGHT;
940             else if (wp->end_up->isChecked())
941                 info.end = UP;
942             else if (wp->end_down->isChecked())
943                 info.end = DOWN;
944             else if (wp->end_center->isChecked())
945                 info.end = CENTER;
946             else
947                 info.end = RIGHT;
948             info.endTransparency = wp->end_transp->value();
949
950             setValue = getWipeString(info);
951         } else if ((type == "simplekeyframe" || type == "keyframe") && m_keyframeEditor) {
952             QDomElement elem = pa.toElement();
953             QString realName = i18n(na.toElement().text().toUtf8().data());
954             QString val = m_keyframeEditor->getValue(realName);
955             elem.setAttribute("keyframes", val);
956
957             if (m_keyframeEditor->isVisibleParam(realName))
958                 elem.setAttribute("intimeline", "1");
959             else if (elem.hasAttribute("intimeline"))
960                 elem.removeAttribute("intimeline");
961         } else if (type == "url") {
962             KUrlRequester *req = ((Urlval*)m_valueItems.value(paramName))->urlwidget;
963             setValue = req->url().path();
964         }
965
966         if (!setValue.isNull())
967             pa.attributes().namedItem("value").setNodeValue(setValue);
968
969     }
970     emit parameterChanged(oldparam, newparam, m_index);
971 }
972
973 QString ParameterContainer::getWipeString(wipeInfo info)
974 {
975
976     QString start;
977     QString end;
978     switch (info.start) {
979     case LEFT:
980         start = "-100%/0%:100%x100%";
981         break;
982     case RIGHT:
983         start = "100%/0%:100%x100%";
984         break;
985     case DOWN:
986         start = "0%/100%:100%x100%";
987         break;
988     case UP:
989         start = "0%/-100%:100%x100%";
990         break;
991     default:
992         start = "0%/0%:100%x100%";
993         break;
994     }
995     start.append(':' + QString::number(info.startTransparency));
996
997     switch (info.end) {
998     case LEFT:
999         end = "-100%/0%:100%x100%";
1000         break;
1001     case RIGHT:
1002         end = "100%/0%:100%x100%";
1003         break;
1004     case DOWN:
1005         end = "0%/100%:100%x100%";
1006         break;
1007     case UP:
1008         end = "0%/-100%:100%x100%";
1009         break;
1010     default:
1011         end = "0%/0%:100%x100%";
1012         break;
1013     }
1014     end.append(':' + QString::number(info.endTransparency));
1015     return QString(start + ";-1=" + end);
1016 }
1017
1018 int ParameterContainer::index()
1019 {
1020     return m_index;
1021 }
1022
1023