keyframeview.cpp 38.6 KB
Newer Older
1 2
/*
Copyright (C) 2016  Jean-Baptiste Mardelle <jb@kdenlive.org>
3
Copyright (C) 2016  Vincent Pinon <vpinon@kde.org>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
This file is part of Kdenlive. See www.kdenlive.org.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy 
defined in Section 14 of version 3 of the license.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <QPainter>
24
#include <QAction>
25
#include <QApplication>
26

27 28
#include "klocalizedstring.h"

29
#include "keyframeview.h"
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
30
#include "../mltcontroller/effectscontroller.h"
31 32

KeyframeView::KeyframeView(int handleSize, QObject *parent) : QObject(parent)
33
    , activeKeyframe(-1)
Vincent Pinon's avatar
Vincent Pinon committed
34
    , originalKeyframe(-1)
35
    , attachToEnd(-2)
36 37 38 39 40 41 42
    , duration(0)
    , m_keyframeType(KEYFRAMETYPE::NoKeyframe)
    , m_keyframeDefault(0)
    , m_keyframeMin(0)
    , m_keyframeMax(1)
    , m_keyframeFactor(1)
    , m_handleSize(handleSize)
43 44
    , m_useOffset(false)
    , m_offset(0)
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
{
}


KeyframeView::~KeyframeView()
{
}

double KeyframeView::keyframeUnmap(QRectF br, double y) {
    return ((br.bottom() - y) / br.height() * (m_keyframeMax - m_keyframeMin) + m_keyframeMin) / m_keyframeFactor;
}

double KeyframeView::keyframeMap(QRectF br, double value) {
    return br.bottom() - br.height() * (value * m_keyframeFactor - m_keyframeMin) / (m_keyframeMax - m_keyframeMin);
}

QPointF KeyframeView::keyframeMap(QRectF br, int frame, double value) {
    return QPointF(br.x() + br.width() * frame / duration,
                   br.bottom() - br.height() * (value * m_keyframeFactor - m_keyframeMin) / (m_keyframeMax - m_keyframeMin));
}

QPointF KeyframeView::keyframePoint(QRectF br, int index) {
    int frame = m_keyAnim.key_get_frame(index);
68
    return keyframeMap(br, frame < 0 ? frame + duration + m_offset : frame + m_offset, m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), frame, duration - m_offset));
69 70 71 72 73
}

QPointF KeyframeView::keyframePoint(QRectF br, int frame, double value, double factor, double min, double max) {
    return QPointF(br.x() + br.width() * frame / duration,
                   br.bottom() - br.height() * (value * factor - min) / (max - min));
74 75 76 77
}

void KeyframeView::drawKeyFrames(QRectF br, int length, bool active, QPainter *painter, const QTransform &transformation)
{
78
    if (duration == 0 || m_keyframeType == NoKeyframe || !m_keyAnim.is_valid() || m_keyAnim.key_count() < 1)
79 80
        return;
    duration = length;
81
    //m_keyAnim.set_length(length);
82 83 84 85 86 87 88
    painter->save();
    QPointF h(m_handleSize, m_handleSize);

    // draw keyframes
    // Special case: Geometry keyframes are just vertical lines
    if (m_keyframeType == GeometryKeyframe) {
        for(int i = 0; i < m_keyAnim.key_count(); ++i) {
89 90
            int frame = m_keyAnim.key_get_frame(i);
            QColor color = (frame == activeKeyframe) ? QColor(Qt::red) : QColor(Qt::blue);
91 92 93 94 95 96
            if (active)
                painter->setPen(color);
            QPointF k = keyframePoint(br, i);
            painter->drawLine(transformation.map(QLineF(k.x(), br.top(), k.x(), br.height())));
            if (active) {
                k.setY(br.top() + br.height()/2);
97
                painter->setBrush((m_keyAnim.key_get_frame(i) == activeKeyframe) ? QColor(Qt::red) : QColor(Qt::blue));
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
                painter->drawEllipse(QRectF(transformation.map(k) - h/2, transformation.map(k) + h/2));
            }
        }
        painter->restore();
        return;
    }

    // draw line showing default value
    if (active) {
        QColor col(Qt::black);
        col.setAlpha(140);
        painter->fillRect(QRectF(transformation.map(br.topLeft()), transformation.map(br.bottomRight())), col);
        double y = keyframeMap(br, m_keyframeDefault);
        QLineF line = transformation.map(QLineF(br.x(), y, br.right(), y));
        painter->setPen(QColor(168, 168, 168, 180));
        painter->drawLine(line);
        painter->setPen(QColor(108, 108, 108, 180));
        painter->drawLine(line.translated(0, 1));
        painter->setPen(QColor(Qt::white));
        painter->setRenderHint(QPainter::Antialiasing);
118 119 120 121 122 123 124 125
        // Draw zone where keyframes are attached to end
        if (attachToEnd > -2) {
            QRectF negZone = br;
            negZone.setLeft(br.x() + br.width() * attachToEnd / (double) duration);
            QColor neg(Qt::darkYellow);
            neg.setAlpha(190);
            painter->fillRect(QRectF(transformation.map(negZone.topLeft()), transformation.map(negZone.bottomRight())), neg);
        }
126
    }
127 128 129 130 131 132 133 134 135 136

    int cnt = m_keyProperties.count();
    QStringList paramNames;
    for (int i = 0; i < cnt; i++) {
        paramNames << m_keyProperties.get_name(i);
    }
    paramNames.removeAll(m_inTimeline);
    // Make sure edited param is painted last
    paramNames.append(m_inTimeline);
    foreach (const QString &paramName, paramNames) {
137 138 139 140 141
        ParameterInfo info = m_paramInfos.value(paramName);
        if (info.max == info.min) {
            // this is probably an animated rect
            continue;
        }
142
        Mlt::Animation drawAnim = m_keyProperties.get_animation(paramName.toUtf8().constData());
143
        if (!drawAnim.is_valid()) continue;
144 145
        QPainterPath path;
        int frame = drawAnim.key_get_frame(0);
146
        double value = m_keyProperties.anim_get_double(paramName.toUtf8().constData(), frame, duration - m_offset);
147
        QPointF start = keyframePoint(br, frame + m_offset, value, info.factor, info.min, info.max);
148 149 150 151 152 153 154 155 156 157 158
        path.moveTo(br.x(), br.bottom());
        path.lineTo(br.x(), start.y());
        path.lineTo(start);
        painter->setPen(paramName == m_inTimeline ? QColor(Qt::white) : Qt::NoPen);
        for(int i = 0; i < drawAnim.key_count(); ++i) {
            if (active && paramName == m_inTimeline) {
                painter->setBrush((drawAnim.key_get_frame(i) == activeKeyframe) ? QColor(Qt::red) : QColor(Qt::blue));
                painter->drawEllipse(QRectF(transformation.map(start) - h/2, transformation.map(start) + h / 2));
            }
            if (i + 1 < drawAnim.key_count()) {
                frame = drawAnim.key_get_frame(i + 1);
159
                value = m_keyProperties.anim_get_double(paramName.toUtf8().constData(), frame, duration - m_offset);
160
                QPointF end = keyframePoint(br, frame + m_offset, value, info.factor, info.min, info.max);
161 162 163 164 165 166 167 168 169 170 171
                //QPointF end = keyframePoint(br, i + 1);
                switch (drawAnim.key_get_type(i)) {
                    case mlt_keyframe_discrete:
                        path.lineTo(end.x(), start.y());
                        path.lineTo(end);
                        break;
                    case mlt_keyframe_linear:
                        path.lineTo(end);
                        break;
                    case mlt_keyframe_smooth:
                        frame = drawAnim.key_get_frame(qMax(i - 1, 0));
172
                        value = m_keyProperties.anim_get_double(paramName.toUtf8().constData(), frame, duration - m_offset);
173 174
                        QPointF pre = keyframePoint(br, frame, value, info.factor, info.min, info.max);
                        frame = drawAnim.key_get_frame(qMin(i + 2, drawAnim.key_count() - 1));
175
                        value = m_keyProperties.anim_get_double(paramName.toUtf8().constData(), frame, duration - m_offset);
176 177 178 179 180 181 182 183 184 185 186 187 188
                        QPointF post = keyframePoint(br, frame, value, info.factor, info.min, info.max);
                        QPointF c1 = (end - pre) / 6.0; // + start
                        QPointF c2 = (start - post) / 6.0; // + end
                        double mid = (end.x() - start.x()) / 2;
                        if (c1.x() >  mid) c1 = c1 * mid / c1.x(); // scale down tangent vector to not go beyond middle
                        if (c2.x() < -mid) c2 = c2 * -mid / c2.x();
                        path.cubicTo(start + c1, end + c2, end);
                        break;
                }
                start = end;
            } else {
                path.lineTo(br.right(), start.y());
            }
189
        }
190 191 192 193 194 195 196 197 198 199 200 201 202
        path.lineTo(br.right(), br.bottom());
        if (paramName == m_inTimeline) {
            QColor col(Qt::white);
            col.setAlpha(active ? 120 : 80);
            painter->setBrush(col);
        } else {
            QColor col;
            switch (paramNames.indexOf(paramName)) {
                case 0:
                    col = Qt::blue;
                    break;
                case 1:
                    col = Qt::green;
203
                    break;
204 205
                case 2:
                    col = Qt::yellow;
206
                    break;
207 208 209 210 211 212 213 214
                case 3:
                    col = Qt::red;
                    break;
                case 4:
                    col = Qt::magenta;
                    break;
                default:
                    col = Qt::cyan;
215 216
                    break;
            }
217 218
            col.setAlpha(80);
            painter->setBrush(col);
219
        }
220
        painter->drawPath(transformation.map(path));
221 222 223 224
    }
    painter->restore();
}

225
void KeyframeView::drawKeyFrameChannels(QRectF br, int in, int out, QPainter *painter, QList <QPoint> maximas, int limitKeyframes, QColor textColor)
226
{
227 228 229 230 231
    double frameFactor = (double) (out - in) / br.width();
    int offset = 1;
    if (limitKeyframes > 0) {
        offset = (out - in) / limitKeyframes / frameFactor;
    }
232 233 234 235 236 237 238 239 240 241 242 243
    double xDist = maximas.at(0).y() - maximas.at(0).x();
    double yDist = maximas.at(1).y() - maximas.at(1).x();
    double wDist = maximas.at(2).y() - maximas.at(2).x();
    double hDist = maximas.at(3).y() - maximas.at(3).x();
    double xOffset = maximas.at(0).x();
    double yOffset = maximas.at(1).x();
    double wOffset = maximas.at(2).x();
    double hOffset = maximas.at(3).x();
    QColor cX(255, 0, 0, 100);
    QColor cY(0, 255, 0, 100);
    QColor cW(0, 0, 255, 100);
    QColor cH(255, 255, 0, 100);
244
    // Draw curves labels
245 246 247 248 249 250 251 252 253 254 255
    QRectF txtRect = painter->boundingRect(br, QStringLiteral("t"));
    txtRect.setX(2);
    txtRect.setWidth(br.width() - 4);
    txtRect.moveTop(br.height() - txtRect.height());
    QRectF drawnText;
    int maxHeight = br.height() - txtRect.height() - 2;
    painter->setPen(textColor);
    int rectSize = txtRect.height() / 2;
    if (xDist > 0) {
        painter->fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cX);
        txtRect.setX(txtRect.x() + rectSize * 2);
256
        painter->drawText(txtRect, 0, i18nc("X as in x coordinate", "X") + QString(" (%1-%2)").arg(maximas.at(0).x()).arg(maximas.at(0).y()), &drawnText);
257 258 259 260 261
    }
    if (yDist > 0) {
        if (drawnText.isValid()) txtRect.setX(drawnText.right() + rectSize);
        painter->fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cY);
        txtRect.setX(txtRect.x() + rectSize * 2);
262
        painter->drawText(txtRect, 0, i18nc("Y as in y coordinate", "Y") + QString(" (%1-%2)").arg(maximas.at(1).x()).arg(maximas.at(1).y()), &drawnText);
263 264 265 266 267
    }
    if (wDist > 0) {
        if (drawnText.isValid()) txtRect.setX(drawnText.right() + rectSize);
        painter->fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cW);
        txtRect.setX(txtRect.x() + rectSize * 2);
268
        painter->drawText(txtRect, 0, i18n("Width") + QString(" (%1-%2)").arg(maximas.at(2).x()).arg(maximas.at(2).y()), &drawnText);
269 270 271 272 273
    }
    if (hDist > 0) {
        if (drawnText.isValid()) txtRect.setX(drawnText.right() + rectSize);
        painter->fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cH);
        txtRect.setX(txtRect.x() + rectSize * 2);
274
        painter->drawText(txtRect, 0, i18n("Height") + QString(" (%1-%2)").arg(maximas.at(3).x()).arg(maximas.at(3).y()), &drawnText);
275 276
    }

277
    // Draw curves
278
    for (int i = 0; i < br.width(); i++) {
279
        mlt_rect rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), (int) (i * frameFactor) + in);
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
        if (xDist > 0) {
            painter->setPen(cX);
            int val = (rect.x - xOffset) * maxHeight / xDist;
            painter->drawLine(i, maxHeight - val, i, maxHeight);
        }
        if (yDist > 0) {
            painter->setPen(cY);
            int val = (rect.y - yOffset) * maxHeight / yDist;
            painter->drawLine(i, maxHeight - val, i, maxHeight);
        }
        if (wDist > 0) {
            painter->setPen(cW);
            int val = (rect.w - wOffset) * maxHeight / wDist;
            painter->drawLine(i, maxHeight - val, i, maxHeight);
        }
        if (hDist > 0) {
            painter->setPen(cH);
            int val = (rect.h - hOffset) * maxHeight / hDist;
            painter->drawLine(i, maxHeight - val, i, maxHeight);
        }
    }
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
    if (offset > 1) {
        // Overlay limited keyframes curve
        cX.setAlpha(255);
        cY.setAlpha(255);
        cW.setAlpha(255);
        cH.setAlpha(255);
        mlt_rect rect1 = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), in);
        int prevPos = 0;
        for (int i = offset; i < br.width(); i+= offset) {
            mlt_rect rect2 = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), (int) (i * frameFactor) + in);
            if (xDist > 0) {
                painter->setPen(cX);
                int val1 = (rect1.x - xOffset) * maxHeight / xDist;
                int val2 = (rect2.x - xOffset) * maxHeight / xDist;
                painter->drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
            }
            if (yDist > 0) {
                painter->setPen(cY);
                int val1 = (rect1.y - yOffset) * maxHeight / yDist;
                int val2 = (rect2.y - yOffset) * maxHeight / yDist;
                painter->drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
            }
            if (wDist > 0) {
                painter->setPen(cW);
                int val1 = (rect1.w - wOffset) * maxHeight / wDist;
                int val2 = (rect2.w - wOffset) * maxHeight / wDist;
                painter->drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
            }
            if (hDist > 0) {
                painter->setPen(cH);
                int val1 = (rect1.h - hOffset) * maxHeight / hDist;
                int val2 = (rect2.h - hOffset) * maxHeight / hDist;
                painter->drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
            }
            rect1 = rect2;
            prevPos =i;
        }
    }
339 340
}

341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
QString KeyframeView::getSingleAnimation(int ix, int in, int out, int offset, int limitKeyframes, QPoint maximas, double min, double max)
{
    m_keyProperties.set("kdenlive_import", "");
    int newduration = out - in + offset;
    m_keyProperties.anim_get_double("kdenlive_import", 0, newduration);
    Mlt::Animation anim = m_keyProperties.get_animation("kdenlive_import");
    double factor = (max - min) / (maximas.y() - maximas.x());
    mlt_rect rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), in, duration);
    double value;
    switch (ix) {
        case 1:
            value = rect.y;
            break;
        case 2:
            value = rect.w;
            break;
        case 3:
            value = rect.h;
            break;
        default:
            value = rect.x;
            break;
    }
    if (maximas.x() > 0) {
        value -= maximas.x();
    }
    value = value * factor + min;
    m_keyProperties.anim_set("kdenlive_import", value, offset, newduration, limitKeyframes > 0 ? mlt_keyframe_smooth : mlt_keyframe_linear);
    if (limitKeyframes > 0) {
        int step = (out - in) / limitKeyframes;
        for (int i = step; i < out; i+= step) {
            rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), in + i, duration);
            switch (ix) {
                case 1:
                    value = rect.y;
                    break;
                case 2:
                    value = rect.w;
                    break;
                case 3:
                    value = rect.h;
                    break;
                default:
                    value = rect.x;
                    break;
            }
            if (maximas.x() > 0) {
                value -= maximas.x();
            }
            value = value * factor + min;
            m_keyProperties.anim_set("kdenlive_import", value, offset + i, newduration, mlt_keyframe_smooth);
        }
    } else {
        int next = m_keyAnim.next_key(in + 1);
        while (next < out && next > 0) {
            rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), next, duration);
            switch (ix) {
                case 1:
                    value = rect.y;
                    break;
                case 2:
                    value = rect.w;
                    break;
                case 3:
                    value = rect.h;
                    break;
                default:
                    value = rect.x;
                    break;
            }
            if (maximas.x() > 0) {
                value -= maximas.x();
            }
            value = value * factor + min;
            m_keyProperties.anim_set("kdenlive_import", value, offset + next - in, newduration, mlt_keyframe_linear);
            next = m_keyAnim.next_key(next + 1);
        }
    }
    QString result = anim.serialize_cut();
    m_keyProperties.set("kdenlive_import", (char*) NULL);
    return result;
}

424
QString KeyframeView::getOffsetAnimation(int in, int out, int offset, int limitKeyframes, ProfileInfo profile, bool allowAnimation, bool positionOnly)
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
{
    m_keyProperties.set("kdenlive_import", "");
    int newduration = out - in + offset;
    int pWidth = profile.profileSize.width();
    int pHeight = profile.profileSize.height();
    m_keyProperties.anim_get_double("kdenlive_import", 0, newduration);
    Mlt::Animation anim = m_keyProperties.get_animation("kdenlive_import");
    mlt_keyframe_type kftype = (limitKeyframes > 0 && allowAnimation) ? mlt_keyframe_smooth : mlt_keyframe_linear;
    mlt_rect rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), in, duration);
    rect.x = (int) rect.x;
    rect.y = (int) rect.y;
    rect.w = pWidth;
    rect.h = pHeight;
    rect.o = 100;
    m_keyProperties.anim_set("kdenlive_import", rect, offset, newduration, kftype);
440
    if (limitKeyframes > 0 && m_keyAnim.key_count() > limitKeyframes) {
441 442 443 444 445
        int step = (out - in) / limitKeyframes;
        for (int i = step; i < out; i+= step) {
            rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), in + i, duration);
            rect.x = (int) rect.x;
            rect.y = (int) rect.y;
446 447 448 449 450
            if (positionOnly) {
                rect.w = pWidth;
                rect.h = pHeight;
                rect.o = 100;
            }
451 452 453
            m_keyProperties.anim_set("kdenlive_import", rect, offset + i, newduration, kftype);
        }
    } else {
454 455 456 457
        int pos;
        for(int i = 0; i < m_keyAnim.key_count(); ++i) {
            m_keyAnim.key_get(i, pos, kftype);
            rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), pos, duration);
458 459
            rect.x = (int) rect.x;
            rect.y = (int) rect.y;
460 461 462 463 464 465
            if (positionOnly) {
                rect.w = pWidth;
                rect.h = pHeight;
                rect.o = 100;
            }
            m_keyProperties.anim_set("kdenlive_import", rect, offset + pos - in, newduration, kftype);
466 467 468 469 470 471 472 473
        }
    }
    QString result = anim.serialize_cut();
    m_keyProperties.set("kdenlive_import", (char*) NULL);
    return result;
}


474
int KeyframeView::mouseOverKeyFrames(QRectF br, QPointF pos, double scale)
475
{
476 477
    if (m_keyframeType == NoKeyframe)
        return -1;
478
    pos.setX((pos.x() - m_offset) * scale);
479
    int previousEdit = activeKeyframe;
480 481
    for(int i = 0; i < m_keyAnim.key_count(); ++i) {
        int key = m_keyAnim.key_get_frame(i);
482 483 484
        if (key < 0) {
            key += duration;
        }
485
        double value = m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), key, duration - m_offset);
486
        QPointF p = keyframeMap(br, key, value);
487
        p.setX(p.x() * scale);
488 489
        if (m_keyframeType == GeometryKeyframe)
            p.setY(br.bottom() - br.height() / 2);
490
        if ((pos - p).manhattanLength() <= m_handleSize / 2) {
491 492 493
	    //TODO
            /*setToolTip('[' + QString::number((GenTime(key, m_fps) - cropStart()).seconds(), 'f', 2)
                       + i18n("seconds") + ", " + QString::number(value, 'f', 1) + ']');*/
494 495
	    activeKeyframe = key;
            if (previousEdit != activeKeyframe) {
496 497
                updateKeyframes();
            }
498
            return key;
499 500
        }
        if (p.x() > pos.x()) {
501
            break;
502
        }
503 504
    }
    //setToolTip(QString());
505 506
    activeKeyframe = -1;
    if (previousEdit != activeKeyframe) {
507 508
        updateKeyframes();
    }
509 510 511 512 513
    return -1;
}

double KeyframeView::editedKeyFrameValue()
{
514
    return m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), activeKeyframe, duration - m_offset);
515 516 517 518
}

void KeyframeView::updateKeyFramePos(QRectF br, int frame, const double y)
{
519
    if (!m_keyAnim.is_key(activeKeyframe)) {
520
        return;
521 522
    }
    int prev = m_keyAnim.key_count() <= 1 || m_keyAnim.key_get_frame(0) == activeKeyframe ? 0 : m_keyAnim.previous_key(activeKeyframe - 1) + 1;
523 524
    prev = qMax(prev, -m_offset);
    int next = m_keyAnim.key_count() <= 1 || m_keyAnim.key_get_frame(m_keyAnim.key_count() - 1) == activeKeyframe ? duration - m_offset :  m_keyAnim.next_key(activeKeyframe + 1) - 1;
525
    if (next < 0) next += duration;
526
    int newpos = qBound(prev, frame - m_offset, next);
527
    double newval = keyframeUnmap(br, y);
528 529 530
    mlt_keyframe_type type = m_keyAnim.keyframe_type(activeKeyframe);
    m_keyProperties.anim_set(m_inTimeline.toUtf8().constData(), newval, newpos, duration - m_offset, type);
    if (activeKeyframe != newpos) {
531
        m_keyAnim.remove(activeKeyframe);
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
        // Move keyframe in other geometries
        int cnt = m_keyProperties.count();
        QStringList paramNames;
        for (int i = 0; i < cnt; i++) {
            paramNames << m_keyProperties.get_name(i);
        }
        paramNames.removeAll(m_inTimeline);
        foreach (const QString &paramName, paramNames) {
            ParameterInfo info = m_paramInfos.value(paramName);
            if (info.max == info.min) {
                // this is probably an animated rect
                mlt_rect rect = m_keyProperties.anim_get_rect(paramName.toUtf8().constData(), activeKeyframe - m_offset, duration - m_offset);
                m_keyProperties.anim_set(paramName.toUtf8().constData(), rect, newpos, duration - m_offset, type);

            } else {
                double val = m_keyProperties.anim_get_double(paramName.toUtf8().constData(), activeKeyframe - m_offset, duration - m_offset);
                m_keyProperties.anim_set(paramName.toUtf8().constData(), val, newpos, duration - m_offset, type);
            }
            // Remove kfr at previous position
            m_keyAnim = m_keyProperties.get_animation(paramName.toUtf8().constData());
            m_keyAnim.remove(activeKeyframe);
        }
        m_keyAnim = m_keyProperties.get_animation(m_inTimeline.toUtf8().constData());
    }
556 557 558 559
    if (attachToEnd == activeKeyframe) {
        attachToEnd = newpos;
    }
    activeKeyframe = newpos;
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
    emit updateKeyframes();
}

double KeyframeView::getKeyFrameClipHeight(QRectF br, const double y)
{
    return keyframeUnmap(br, y);
}

int KeyframeView::keyframesCount()
{
    if (duration == 0) return -1;
    return m_keyAnim.key_count();
}

mlt_keyframe_type KeyframeView::type(int frame)
{
    if (m_keyAnim.is_key(frame)) {
	return m_keyAnim.keyframe_type(frame);
    }
    // no keyframe at position frame, try to get previous key's type
    int previous = m_keyAnim.previous_key(frame);
    return m_keyAnim.keyframe_type(previous);
}

void KeyframeView::addKeyframe(int frame, double value, mlt_keyframe_type type)
{
586
    m_keyProperties.anim_set(m_inTimeline.toUtf8().constData(), value, frame - m_offset, duration - m_offset, type);
587 588
    // Last keyframe should stick to end
    if (frame == duration - 1) {
589
        attachToEnd = frame - m_offset;
590
    }
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
    // Add keyframe in other animations if any
    int cnt = m_keyProperties.count();
    QStringList paramNames;
    for (int i = 0; i < cnt; i++) {
        paramNames << m_keyProperties.get_name(i);
    }
    paramNames.removeAll(m_inTimeline);
    foreach (const QString &paramName, paramNames) {
        ParameterInfo info = m_paramInfos.value(paramName);
        if (info.max == info.min) {
            // this is probably an animated rect
            mlt_rect rect = m_keyProperties.anim_get_rect(paramName.toUtf8().constData(), frame - m_offset, duration - m_offset);
            m_keyProperties.anim_set(paramName.toUtf8().constData(), rect, frame - m_offset, duration - m_offset, type);

        } else {
            double val = m_keyProperties.anim_get_double(paramName.toUtf8().constData(), frame - m_offset, duration - m_offset);
            m_keyProperties.anim_set(paramName.toUtf8().constData(), val, frame - m_offset, duration - m_offset, type);
        }
    }
610 611
}

612
void KeyframeView::addDefaultKeyframe(ProfileInfo profile, int frame, mlt_keyframe_type type)
613 614
{
    double value = m_keyframeDefault;
615
    if (m_keyAnim.key_count() == 1 && frame != m_keyAnim.key_get_frame(0)) {
616
	value = m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), m_keyAnim.key_get_frame(0), duration - m_offset);
617
    }
618
    m_keyProperties.anim_set(m_inTimeline.toUtf8().constData(), value, frame, duration - m_offset, type);
619
    // Last keyframe should stick to end
620
    if (frame >= duration - 1) {
621
        attachToEnd = frame - m_offset;
622
    }
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
    // Add keyframe in other animations if any
    int cnt = m_keyProperties.count();
    QStringList paramNames;
    QLocale locale;
    for (int i = 0; i < cnt; i++) {
        paramNames << m_keyProperties.get_name(i);
    }
    paramNames.removeAll(m_inTimeline);
    foreach (const QString &paramName, paramNames) {
        ParameterInfo info = m_paramInfos.value(paramName);
        if (info.max == info.min) {
            // this is probably an animated rect
            QString defaultVal = info.defaultValue;
            if (defaultVal.contains('%'))
                defaultVal = EffectsController::getStringRectEval(profile, defaultVal).simplified();
            mlt_rect rect;
            rect.x = locale.toDouble(defaultVal.section(QStringLiteral(" "), 0, 0));
            rect.y = locale.toDouble(defaultVal.section(QStringLiteral(" "), 1, 1));
            rect.w = locale.toDouble(defaultVal.section(QStringLiteral(" "), 2, 2));
            rect.h = locale.toDouble(defaultVal.section(QStringLiteral(" "), 3, 3));
            rect.o = defaultVal.count(QLatin1Char(' ')) > 3 ? locale.toDouble(defaultVal.section(QStringLiteral(" "), 4, 4)) : 1.0;
            m_keyProperties.anim_set(paramName.toUtf8().constData(), rect, frame - m_offset, duration - m_offset, type);

        } else {
            double val = locale.toDouble(info.defaultValue);
            m_keyProperties.anim_set(paramName.toUtf8().constData(), val, frame - m_offset, duration - m_offset, type);
        }
    }
651 652 653 654 655 656
}


void KeyframeView::removeKeyframe(int frame)
{
    m_keyAnim.remove(frame);
657 658 659
    if (frame == duration - 1 && frame == attachToEnd) {
        attachToEnd = -2;
    }
660 661 662 663 664 665 666 667 668 669 670 671
    // Remove keyframe in other animations if any
    int cnt = m_keyProperties.count();
    QStringList paramNames;
    for (int i = 0; i < cnt; i++) {
        paramNames << m_keyProperties.get_name(i);
    }
    paramNames.removeAll(m_inTimeline);
    foreach (const QString &paramName, paramNames) {
        m_keyAnim = m_keyProperties.get_animation(paramName.toUtf8().constData());
        m_keyAnim.remove(frame);
    }
    m_keyAnim = m_keyProperties.get_animation(m_inTimeline.toUtf8().constData());
672 673
}

674 675 676
QAction *KeyframeView::parseKeyframeActions(QList <QAction *>actions)
{

677
    mlt_keyframe_type type = m_keyAnim.keyframe_type(activeKeyframe);
678 679 680 681 682 683 684 685
    for (int i = 0; i < actions.count(); i++) {
        if (actions.at(i)->data().toInt() == type) {
            return actions.at(i);
        }
    }
    return NULL;
}

686
void KeyframeView::attachKeyframeToEnd(bool attach)
687
{
688 689 690 691 692 693 694 695 696 697 698 699 700
    if (attach) {
        attachToEnd = activeKeyframe;
    } else {
        if (attachToEnd != duration -1) {
            // Check if there is a keyframe at end pos, and attach it to end if it is the case
            if (m_keyAnim.is_key(duration -1)) {
                attachToEnd = duration -1;
            }
        } else {
            // We want to detach last keyframe from end
            attachToEnd = -2;
        }
    }
701 702 703
    emit updateKeyframes();
}

704 705
void KeyframeView::editKeyframeType(int type)
{
706
    if (m_keyAnim.is_key(activeKeyframe)) {
707
        // This is a keyframe
708 709
        double val = m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), activeKeyframe, duration - m_offset);
        m_keyProperties.anim_set(m_inTimeline.toUtf8().constData(), val, activeKeyframe, duration - m_offset, (mlt_keyframe_type) type);
710 711 712
    }
}

713 714 715 716 717
bool KeyframeView::activeParam(const QString &name) const
{
    return name == m_inTimeline;
}

718
const QString KeyframeView::serialize(const QString &name, bool rectAnimation)
719
{
720 721 722
    if (!name.isEmpty())
        m_keyAnim = m_keyProperties.get_animation(name.toUtf8().constData()); 
    if (attachToEnd == -2 || rectAnimation) {
723 724 725 726 727 728
        return m_keyAnim.serialize_cut();
    }
    mlt_keyframe_type type;
    QString key;
    QLocale locale;
    QStringList result;
729
    int pos;
730 731
    for(int i = 0; i < m_keyAnim.key_count(); ++i) {
        m_keyAnim.key_get(i, pos, type);
732
        double val = m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), pos, duration - m_offset);
733
        if (pos >= attachToEnd) {
734
            pos = qMin(pos - (duration - m_offset), -1);
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
        }
        key = QString::number(pos);
        switch (type) {
            case mlt_keyframe_discrete:
                key.append("|=");
                break;
            case mlt_keyframe_smooth:
                key.append("~=");
                break;
            default:
                key.append("=");
                break;
        }
        key.append(locale.toString(val));
        result << key;
    }
751 752
    if (!name.isEmpty())
        m_keyAnim = m_keyProperties.get_animation(m_inTimeline.toUtf8().constData());
753
    return result.join(";");
754 755
}

756
QList <QPoint> KeyframeView::loadKeyframes(const QString &data)
757
{
758
    m_keyframeType = NoKeyframe;
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
    m_inTimeline = QStringLiteral("imported");
    m_keyProperties.set(m_inTimeline.toUtf8().constData(), data.toUtf8().constData());
    // We need to initialize with length so that negative keyframes are correctly interpreted
    m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), 0);
    m_keyAnim = m_keyProperties.get_animation(m_inTimeline.toUtf8().constData());
    duration = m_keyAnim.length();
    // calculate minimas / maximas
    int max = m_keyAnim.key_count();
    int frame = m_keyAnim.key_get_frame(0);
    mlt_rect rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), frame, duration);
    QPoint pX(rect.x, rect.x);
    QPoint pY(rect.y, rect.y);
    QPoint pW(rect.w, rect.w);
    QPoint pH(rect.h, rect.h);
    for (int i = 1; i < max; i++) {
        frame = m_keyAnim.key_get_frame(i);
        rect = m_keyProperties.anim_get_rect(m_inTimeline.toUtf8().constData(), frame, duration);
        // Check x bounds
        if (rect.x < pX.x()) {
            pX.setX(rect.x);
        }
        if (rect.x > pX.y()) {
            pX.setY(rect.x);
        }
        // Check y bounds
        if (rect.y < pY.x()) {
            pY.setX(rect.y);
        }
        if (rect.y > pY.y()) {
            pY.setY(rect.y);
        }
        // Check w bounds
        if (rect.w < pW.x()) {
            pW.setX(rect.w);
        }
        if (rect.w > pW.y()) {
            pW.setY(rect.w);
        }
        // Check h bounds
        if (rect.h < pH.x()) {
            pH.setX(rect.h);
        }
        if (rect.h > pH.y()) {
            pH.setY(rect.h);
        }
    }
    QList <QPoint> result;
    result << pX << pY << pW << pH;
    return result;
}

bool KeyframeView::loadKeyframes(const QLocale locale, QDomElement effect, int cropStart, int length)
{
812
    m_keyframeType = NoKeyframe;
813 814 815 816 817 818
    duration = length;
    m_inTimeline.clear();
    // reset existing properties
    int max = m_keyProperties.count();
    for (int i = max -1; i >= 0; i--) {
        m_keyProperties.set(m_keyProperties.get_name(i), (char*) NULL);
819
    }
820
    attachToEnd = -2;
821
    m_useOffset = effect.attribute(QStringLiteral("kdenlive:sync_in_out")) != QLatin1String("1");
822
    m_offset = effect.attribute("in").toInt() - cropStart;
823 824 825 826 827 828 829
    QDomNodeList params = effect.elementsByTagName(QStringLiteral("parameter"));
    for (int i = 0; i < params.count(); ++i) {
        QDomElement e = params.item(i).toElement();
        if (e.isNull()) continue;
        QString type = e.attribute(QStringLiteral("type"));
        if (type == QLatin1String("keyframe")) m_keyframeType = NormalKeyframe;
        else if (type == QLatin1String("simplekeyframe")) m_keyframeType = SimpleKeyframe;
830
        else if (type == QLatin1String("geometry") || type == QLatin1String("animatedrect")) m_keyframeType = GeometryKeyframe;
831 832 833 834 835 836 837
        else if (type == QLatin1String("animated")) m_keyframeType = AnimatedKeyframe;
        else continue;
        QString paramName = e.attribute(QStringLiteral("name"));
        ParameterInfo info;
        info.factor = locale.toDouble(e.attribute(QStringLiteral("factor")));
        info.max = locale.toDouble(e.attribute(QStringLiteral("max")));
        info.min = locale.toDouble(e.attribute(QStringLiteral("min")));
838
        info.defaultValue = e.attribute(QStringLiteral("default"));
839 840 841 842 843 844 845 846
        if (info.factor == 0) {
            info.factor = 1;
        }
        m_paramInfos.insert(paramName, info);
        if (!e.hasAttribute(QStringLiteral("intimeline")) || e.attribute(QStringLiteral("intimeline")) == QLatin1String("1")) {
            // Active parameter
            m_keyframeMin = info.min;
            m_keyframeMax = info.max;
847
            m_keyframeDefault = locale.toDouble(info.defaultValue);
848
            m_keyframeFactor = info.factor;
849
            attachToEnd = checkNegatives(e.attribute("value").toUtf8().constData(), duration - m_offset);
850 851
            m_inTimeline = paramName;
        }
852
        // parse keyframes
853 854 855 856
        QString value = e.attribute(QStringLiteral("value"));
        if (value.isEmpty()) {
            value = e.attribute(QStringLiteral("default"));
        }
857 858
        switch (m_keyframeType) {
            case GeometryKeyframe:
859
                m_keyProperties.set(paramName.toUtf8().constData(), value.toUtf8().constData());
860
                m_keyProperties.anim_get_rect(paramName.toUtf8().constData(), 0, length);
861 862
                break;
            case AnimatedKeyframe:
863
                m_keyProperties.set(paramName.toUtf8().constData(), value.toUtf8().constData());
864
                // We need to initialize with length so that negative keyframes are correctly interpreted
865
                m_keyProperties.anim_get_double(paramName.toUtf8().constData(), 0, length);
866 867
                break;
            default:
868 869 870 871 872 873
                m_keyProperties.set(m_inTimeline.toUtf8().constData(), e.attribute(m_inTimeline.toUtf8().constData()).toUtf8().constData());
                m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), 0, length);
        }
        if (paramName == m_inTimeline) {
            m_keyAnim = m_keyProperties.get_animation(m_inTimeline.toUtf8().constData());
            if (m_keyAnim.next_key(activeKeyframe) <= activeKeyframe) activeKeyframe = -1;
874 875
        }
    }
876
    return (!m_inTimeline.isEmpty());
877 878
}

879 880
void KeyframeView::setOffset(int frames)
{
881
    if (m_useOffset) {
882
        m_offset -= frames;
883
    }
884 885
}

886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906
// static
int KeyframeView::checkNegatives(const QString &data, int maxDuration)
{
    int result = -2;
    QStringList frames = data.split(";");
    for (int i = 0; i < frames.count(); i++) {
        if (frames.at(i).startsWith("-")) {
            // We found a negative kfr
            QString sub = frames.at(i).section("=", 0, 0);
            if (!sub.at(sub.length() - 1).isDigit()) {
                // discrete or smooth keyframe, we need to remove the tag (| or ~)
                sub.chop(1);
            }
            int negPos = sub.toInt() + maxDuration;
            if (result == -2 || result > negPos) {
                result = negPos;
            }
        }
    }
    return result;
}
907 908 909 910 911 912 913 914 915

void KeyframeView::reset()
{
    if (m_keyframeType == NoKeyframe) {
	// nothing to do
	return;
    }
    m_keyframeType = NoKeyframe;
    duration = 0;
916 917
    attachToEnd = -2;
    activeKeyframe = -1;
918 919 920 921
    int max = m_keyProperties.count();
    for (int i = max -1; i >= 0; i--) {
        m_keyProperties.set(m_keyProperties.get_name(i), (char*) NULL);
    }
922 923 924 925
    emit updateKeyframes(); 
}

//static
926
QString KeyframeView::cutAnimation(const QString &animation, int start, int duration, int fullduration, bool doCut)
927 928 929
{
    Mlt::Properties props;
    props.set("keyframes", animation.toUtf8().constData());
930
    props.anim_get_double("keyframes", 0, fullduration);
931 932 933
    Mlt::Animation anim = props.get_animation("keyframes");
    if (start > 0 && !anim.is_key(start)) {
	// insert new keyframe at start
934
	double value = props.anim_get_double("keyframes", start, fullduration);
935 936
	int previous = anim.previous_key(start);
	mlt_keyframe_type type = anim.keyframe_type(previous);
937
	props.anim_set("keyframes", value, start, fullduration, type);
938
    }
939 940 941
    if (!anim.is_key(start + duration)) {
	double value = props.anim_get_double("keyframes", start + duration, fullduration);
	int previous = anim.previous_key(start + duration);
942
	mlt_keyframe_type type = anim.keyframe_type(previous);
943 944 945 946
	props.anim_set("keyframes", value, start + duration, fullduration, type);	
    }
    if (!doCut) {
        return anim.serialize_cut();
947
    }
948
    return anim.serialize_cut(start, start + duration);
949 950 951 952 953 954 955 956 957 958 959 960 961
}


/*
void KeyframeView::updateAnimatedKeyframes(QDomElement effect, int paramIndex, ItemInfo oldInfo)
{
    QDomElement param = effect.elementsByTagName("parameter").item(paramIndex).toElement();
    int offset = oldInfo.cropStart.frames(m_fps);
    if (offset > 0) {
        for(int i = 0; i < m_keyAnim.key_count(); ++i){
            int oldPos = m_keyAnim.key_get_frame(i);
            int newPos = oldPos + offset;
            m_keyAnim.remove(oldPos);
962
            m_keyProperties.anim_set(m_inTimeline.toUtf8().constData(), m_keyProperties.anim_get_double(m_inTimeline.toUtf8().constData(), oldPos), newPos, 0, m_keyAnim.keyframe_type(i));
963 964 965 966 967 968
        }
    }
    QString result = m_keyAnim.serialize_cut();
    param.setAttribute("value", result);
}*/