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