/***************************************************************************
* Copyright (C) 2011 by Till Theato (root@ttill.de) *
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive (www.kdenlive.org). *
* *
* Kdenlive 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) any later version. *
* *
* Kdenlive 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 Kdenlive. If not, see . *
***************************************************************************/
#include "keyframeview.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "core.h"
#include "kdenlivesettings.h"
#include
#include
#include
#include
#include
#include
#include
KeyframeView::KeyframeView(std::shared_ptr model, int duration, int inPoint, QWidget *parent)
: QWidget(parent)
, m_model(std::move(model))
, m_duration(duration)
, m_inPoint(inPoint)
, m_position(0)
, m_currentKeyframe(-1)
, m_currentKeyframeOriginal(-1)
, m_hoverKeyframe(-1)
, m_scale(1)
, m_zoomFactor(1)
, m_zoomStart(0)
, m_moveKeyframeMode(false)
, m_clickPoint(-1)
, m_clickEnd(-1)
, m_zoomHandle(0,1)
, m_hoverZoomIn(false)
, m_hoverZoomOut(false)
, m_hoverZoom(false)
, m_clickOffset(0)
{
setMouseTracking(true);
setMinimumSize(QSize(150, 20));
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
QPalette p = palette();
KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window);
m_colSelected = palette().highlight().color();
m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color();
m_size = QFontInfo(font()).pixelSize() * 3;
m_lineHeight = int(m_size / 2.1);
m_zoomHeight = m_size * 3 / 4;
m_offset = m_size / 4;
setFixedHeight(m_size);
setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged);
}
void KeyframeView::slotModelChanged()
{
int offset = pCore->getItemIn(m_model->getOwnerId());
emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
emit modified();
update();
}
void KeyframeView::slotSetPosition(int pos, bool isInRange)
{
if (!isInRange) {
m_position = -1;
update();
return;
}
if (pos != m_position) {
m_position = pos;
int offset = pCore->getItemIn(m_model->getOwnerId());
emit atKeyframe(m_model->hasKeyframe(pos + offset), m_model->singleKeyframe());
double zoomPos = double(m_position) / m_duration;
if (zoomPos < m_zoomHandle.x()) {
double interval = m_zoomHandle.y() - m_zoomHandle.x();
zoomPos = qBound(0.0, zoomPos - interval / 5, 1.0);
m_zoomHandle.setX(zoomPos);
m_zoomHandle.setY(zoomPos + interval);
} else if (zoomPos > m_zoomHandle.y()) {
double interval = m_zoomHandle.y() - m_zoomHandle.x();
zoomPos = qBound(0.0, zoomPos + interval / 5, 1.0);
m_zoomHandle.setX(zoomPos - interval);
m_zoomHandle.setY(zoomPos);
}
update();
}
}
void KeyframeView::initKeyframePos()
{
emit atKeyframe(m_model->hasKeyframe(m_position), m_model->singleKeyframe());
}
void KeyframeView::slotDuplicateKeyframe()
{
int offset = pCore->getItemIn(m_model->getOwnerId());
if (m_currentKeyframe > -1 && !m_model->hasKeyframe(m_position + offset)) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
int delta = m_position - m_currentKeyframe;
for (int kf : qAsConst(m_selectedKeyframes)) {
m_model->duplicateKeyframeWithUndo(GenTime(kf + offset, pCore->getCurrentFps()), GenTime(kf + delta + offset, pCore->getCurrentFps()), undo, redo);
}
pCore->pushUndo(undo, redo, i18n("Duplicate keyframe"));
}
}
void KeyframeView::slotAddKeyframe(int pos)
{
if (pos < 0) {
pos = m_position;
}
int offset = pCore->getItemIn(m_model->getOwnerId());
m_model->addKeyframe(GenTime(pos + offset, pCore->getCurrentFps()), KeyframeType(KdenliveSettings::defaultkeyframeinterp()));
}
const QString KeyframeView::getAssetId()
{
return m_model->getAssetId();
}
void KeyframeView::slotAddRemove()
{
emit activateEffect();
int offset = pCore->getItemIn(m_model->getOwnerId());
if (m_model->hasKeyframe(m_position + offset)) {
if (m_selectedKeyframes.contains(m_position)) {
// Delete all selected keyframes
slotRemoveKeyframe(m_selectedKeyframes);
} else {
slotRemoveKeyframe({m_position});
}
} else {
slotAddKeyframe(m_position);
}
}
void KeyframeView::slotEditType(int type, const QPersistentModelIndex &index)
{
int offset = pCore->getItemIn(m_model->getOwnerId());
if (m_model->hasKeyframe(m_position + offset)) {
m_model->updateKeyframeType(GenTime(m_position + offset, pCore->getCurrentFps()), type, index);
}
}
void KeyframeView::slotRemoveKeyframe(QVector positions)
{
if (m_model->singleKeyframe()) {
// Don't allow zero keyframe
pCore->displayMessage(i18n("Cannot remove the last keyframe"), MessageType::ErrorMessage, 500);
return;
}
int offset = pCore->getItemIn(m_model->getOwnerId());
Fun undo = []() { return true; };
Fun redo = []() { return true; };
for (int pos : positions) {
if (pos == 0) {
// Don't allow moving first keyframe
continue;
}
if (m_selectedKeyframes.contains(pos)) {
m_selectedKeyframes.removeAll(pos);
}
m_model->removeKeyframeWithUndo(GenTime(pos + offset, pCore->getCurrentFps()), undo, redo);
}
pCore->pushUndo(undo, redo, i18np("Remove keyframe", "Remove keyframes", positions.size()));
}
void KeyframeView::setDuration(int dur, int inPoint)
{
m_duration = dur;
m_inPoint = inPoint;
int offset = pCore->getItemIn(m_model->getOwnerId());
emit atKeyframe(m_model->hasKeyframe(m_position + offset), m_model->singleKeyframe());
// Unselect keyframes that are outside range if any
QVector toDelete;
for (auto &p : m_selectedKeyframes) {
if (p < m_inPoint || p >= m_inPoint + m_duration) {
toDelete << p;
}
}
for (auto &p : toDelete) {
m_selectedKeyframes.removeAll(p);
}
update();
}
void KeyframeView::slotGoToNext()
{
emit activateEffect();
if (m_position == m_duration - 1) {
return;
}
bool ok;
int offset = pCore->getItemIn(m_model->getOwnerId());
auto next = m_model->getNextKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
if (ok) {
emit seekToPos(qMin(int(next.first.frames(pCore->getCurrentFps())) - offset, m_duration - 1) + m_inPoint);
} else {
// no keyframe after current position
emit seekToPos(m_duration - 1 + m_inPoint);
}
}
void KeyframeView::slotGoToPrev()
{
emit activateEffect();
if (m_position == 0) {
return;
}
bool ok;
int offset = pCore->getItemIn(m_model->getOwnerId());
auto prev = m_model->getPrevKeyframe(GenTime(m_position + offset, pCore->getCurrentFps()), &ok);
if (ok) {
emit seekToPos(qMax(0, int(prev.first.frames(pCore->getCurrentFps())) - offset) + m_inPoint);
} else {
// no keyframe after current position
emit seekToPos(m_duration - 1 + m_inPoint);
}
}
void KeyframeView::slotCenterKeyframe()
{
if (m_currentKeyframeOriginal == -1 || m_currentKeyframeOriginal == m_position || m_currentKeyframeOriginal == 0) {
return;
}
int offset = pCore->getItemIn(m_model->getOwnerId());
if (!m_model->hasKeyframe(m_currentKeyframeOriginal + offset)) {
return;
}
Fun undo = []() { return true; };
Fun redo = []() { return true; };
int delta = m_position - m_currentKeyframeOriginal;
QVectorcurrentSelection = m_selectedKeyframes;
int sourcePosition = m_currentKeyframeOriginal;
QVectorupdatedSelection;
for (int kf : qAsConst(m_selectedKeyframes)) {
if (kf == 0) {
// Don't allow moving first keyframe
continue;
}
GenTime initPos(kf + offset, pCore->getCurrentFps());
GenTime targetPos(kf + delta + offset, pCore->getCurrentFps());
m_model->moveKeyframeWithUndo(initPos, targetPos, undo, redo);
updatedSelection << (kf + delta);
}
Fun local_redo = [this, updatedSelection]() {
m_currentKeyframe = m_currentKeyframeOriginal = m_position;
m_selectedKeyframes = updatedSelection;
update();
return true;
};
Fun local_undo = [this, currentSelection, sourcePosition]() {
m_currentKeyframe = m_currentKeyframeOriginal = sourcePosition;
m_selectedKeyframes = currentSelection;
update();
return true;
};
local_redo();
PUSH_LAMBDA(local_redo, redo);
PUSH_FRONT_LAMBDA(local_undo, undo);
pCore->pushUndo(undo, redo, i18nc("@action", "Move keyframe"));
}
void KeyframeView::mousePressEvent(QMouseEvent *event)
{
emit activateEffect();
int offset = pCore->getItemIn(m_model->getOwnerId());
double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart ) / m_scale);
pos = qBound(0, pos, m_duration - 1);
m_moveKeyframeMode = false;
if (event->button() == Qt::LeftButton) {
if (event->y() < m_lineHeight) {
// mouse click in keyframes area
bool ok;
GenTime position(pos + offset, pCore->getCurrentFps());
if (event->modifiers() & Qt::ShiftModifier) {
m_clickPoint = pos;
return;
}
auto keyframe = m_model->getClosestKeyframe(position, &ok);
if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset) * m_scale * m_zoomFactor < QApplication::startDragDistance()) {
m_currentKeyframeOriginal = keyframe.first.frames(pCore->getCurrentFps()) - offset;
if (event->modifiers() & Qt::ControlModifier) {
if (m_selectedKeyframes.contains(m_currentKeyframeOriginal)) {
m_selectedKeyframes.removeAll(m_currentKeyframeOriginal);
m_currentKeyframeOriginal = -1;
} else {
m_selectedKeyframes << m_currentKeyframeOriginal;
}
} else if (!m_selectedKeyframes.contains(m_currentKeyframeOriginal)) {
m_selectedKeyframes = {m_currentKeyframeOriginal};
}
// Select and seek to keyframe
m_currentKeyframe = m_currentKeyframeOriginal;
if (m_currentKeyframeOriginal > -1) {
m_moveKeyframeMode = true;
if (KdenliveSettings::keyframeseek()) {
emit seekToPos(m_currentKeyframeOriginal + m_inPoint);
} else {
update();
}
} else {
update();
}
return;
}
// no keyframe next to mouse
m_selectedKeyframes.clear();
m_currentKeyframe = m_currentKeyframeOriginal = -1;
} else if (event->y() > m_zoomHeight + 2) {
// click on zoom area
if (m_hoverZoom) {
m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
}
return;
}
} else if (event->button() == Qt::RightButton && event->y() > m_zoomHeight + 2) {
// Right click on zoom, switch between no zoom and last zoom status
if (m_zoomHandle == QPointF(0, 1)) {
if (!m_lastZoomHandle.isNull()) {
m_zoomHandle = m_lastZoomHandle;
update();
return;
}
} else {
m_lastZoomHandle = m_zoomHandle;
m_zoomHandle = QPointF(0, 1);
update();
return;
}
}
if (pos != m_position) {
emit seekToPos(pos + m_inPoint);
update();
}
}
void KeyframeView::mouseMoveEvent(QMouseEvent *event)
{
int offset = pCore->getItemIn(m_model->getOwnerId());
double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
int pos = int(((double(event->x()) - m_offset) / zoomFactor + zoomStart ) / m_scale);
pos = qBound(0, pos, m_duration - 1);
GenTime position(pos + offset, pCore->getCurrentFps());
if ((event->buttons() & Qt::LeftButton) != 0u) {
if (m_hoverZoomIn || m_hoverZoomOut || m_hoverZoom) {
// Moving zoom handles
if (m_hoverZoomIn) {
m_zoomHandle.setX(qMin(qMax(0., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015));
update();
return;
}
if (m_hoverZoomOut) {
m_zoomHandle.setY(qMax(qMin(1., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015));
update();
return;
}
// moving zoom zone
if (m_hoverZoom) {
double clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset;
double newX = m_zoomHandle.x() + clickOffset;
if (newX < 0) {
clickOffset = - m_zoomHandle.x();
newX = 0;
}
double newY = m_zoomHandle.y() + clickOffset;
if (newY > 1) {
clickOffset = 1 - m_zoomHandle.y();
newY = 1;
newX = m_zoomHandle.x() + clickOffset;
}
m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset);
m_zoomHandle = QPointF(newX, newY);
update();
}
return;
}
if (m_currentKeyframe == pos) {
return;
}
if (m_currentKeyframe > 0 && m_moveKeyframeMode) {
if (!m_model->hasKeyframe(pos + offset)) {
int delta = pos - m_currentKeyframe;
// Check that the move is possible
for (int kf : qAsConst(m_selectedKeyframes)) {
int updatedPos = kf + offset + delta;
if (!m_selectedKeyframes.contains(updatedPos) && m_model->hasKeyframe(updatedPos)) {
// Don't allow moving over another keyframe
return;
}
}
if (delta > 0) {
// Sort kfrs in revert order to prevent conflicts
std::sort(m_selectedKeyframes.rbegin(), m_selectedKeyframes.rend());
} else {
std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end());
}
for (int kf : qAsConst(m_selectedKeyframes)) {
if (kf == 0) {
// Don't allow moving first keyframe
continue;
}
GenTime currentPos(kf + offset, pCore->getCurrentFps());
GenTime updatedPos(kf + offset + delta, pCore->getCurrentFps());
if (m_model->moveKeyframe(currentPos, updatedPos, false)) {
if (kf == m_currentKeyframe) {
m_currentKeyframe = pos;
}
} else {
qDebug()<<"=== FAILED KF MOVE!!!";
Q_ASSERT(false);
}
}
for (int &kf : m_selectedKeyframes) {
if (kf == 0) {
// Don't allow moving first keyframe
continue;
}
kf += delta;
}
}
}
// Rubberband selection
if (m_clickPoint >= 0) {
m_clickEnd = pos;
int min = qMin(m_clickPoint, m_clickEnd);
int max = qMax(m_clickPoint, m_clickEnd);
min = qMax(1, min);
m_selectedKeyframes.clear();
m_currentKeyframeOriginal = m_currentKeyframe = -1;
double fps = pCore->getCurrentFps();
for (const auto &keyframe : *m_model.get()) {
int pos = keyframe.first.frames(fps) - offset;
if (pos > min && pos <= max) {
m_selectedKeyframes << pos;
}
}
if (!m_selectedKeyframes.isEmpty()) {
m_currentKeyframeOriginal = m_currentKeyframe = m_selectedKeyframes.first();
}
update();
return;
}
if (!m_moveKeyframeMode || KdenliveSettings::keyframeseek()) {
if (pos != m_position) {
emit seekToPos(pos + m_inPoint);
}
}
return;
}
if (event->y() < m_lineHeight) {
bool ok;
auto keyframe = m_model->getClosestKeyframe(position, &ok);
if (ok && qAbs(((position.frames(pCore->getCurrentFps()) - keyframe.first.frames(pCore->getCurrentFps())) * m_scale) * m_zoomFactor) < QApplication::startDragDistance()) {
m_hoverKeyframe = keyframe.first.frames(pCore->getCurrentFps()) - offset;
setCursor(Qt::PointingHandCursor);
m_hoverZoomIn = false;
m_hoverZoomOut = false;
m_hoverZoom = false;
update();
return;
}
}
if (event->y() > m_zoomHeight + 2) {
// Moving in zoom area
if (qAbs(event->x() - m_offset - (m_zoomHandle.x() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
setCursor(Qt::SizeHorCursor);
m_hoverZoomIn = true;
m_hoverZoomOut = false;
m_hoverZoom = false;
update();
return;
}
if (qAbs(event->x() - m_offset - (m_zoomHandle.y() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) {
setCursor(Qt::SizeHorCursor);
m_hoverZoomOut = true;
m_hoverZoomIn = false;
m_hoverZoom = false;
update();
return;
}
if (m_zoomHandle != QPointF(0, 1) && event->x() > m_offset + (m_zoomHandle.x() * (width() - 2 * m_offset)) && event->x() < m_offset + (m_zoomHandle.y() * (width() - 2 * m_offset))) {
setCursor(Qt::PointingHandCursor);
m_hoverZoom = true;
m_hoverZoomIn = false;
m_hoverZoomOut = false;
update();
return;
}
}
if (m_hoverKeyframe != -1 || m_hoverZoomOut || m_hoverZoomIn || m_hoverZoom) {
m_hoverKeyframe = -1;
m_hoverZoomOut = false;
m_hoverZoomIn = false;
m_hoverZoom = false;
setCursor(Qt::ArrowCursor);
update();
}
}
void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event)
m_moveKeyframeMode = false;
m_clickPoint = -1;
if (m_clickEnd >= 0) {
m_clickEnd = -1;
update();
}
if (m_currentKeyframe >= 0 && m_currentKeyframeOriginal != m_currentKeyframe) {
int offset = pCore->getItemIn(m_model->getOwnerId());
int delta = m_currentKeyframe - m_currentKeyframeOriginal;
// Move back all keyframes to their initial positions
// Sort keyframes so we don't move a keyframe over another one
if (delta > 0) {
std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end());
} else {
std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end(), std::greater<>());
}
for (int kf : qAsConst(m_selectedKeyframes)) {
if (kf == 0) {
// Don't allow moving first keyframe
continue;
}
GenTime initPos(kf - delta + offset, pCore->getCurrentFps());
GenTime targetPos(kf + offset, pCore->getCurrentFps());
m_model->moveKeyframe(targetPos, initPos, false);
}
// Move all keyframes to their new positions
Fun undo = []() { return true; };
Fun redo = []() { return true; };
// Sort keyframes so we don't move a keyframe over another one
if (delta > 0) {
std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end(), std::greater<>());
} else {
std::sort(m_selectedKeyframes.begin(), m_selectedKeyframes.end());
}
for (int kf : qAsConst(m_selectedKeyframes)) {
if (kf == 0) {
// Don't allow moving first keyframe
continue;
}
GenTime initPos(kf - delta + offset, pCore->getCurrentFps());
GenTime targetPos(kf + offset, pCore->getCurrentFps());
m_model->moveKeyframeWithUndo(initPos, targetPos, undo, redo);
}
m_currentKeyframeOriginal = m_currentKeyframe;
pCore->pushUndo(undo, redo, i18np("Move keyframe", "Move keyframes", m_selectedKeyframes.size()));
qDebug() << "RELEASING keyframe move" << delta;
}
}
void KeyframeView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && event->y() < m_lineHeight) {
int offset = pCore->getItemIn(m_model->getOwnerId());
double zoomStart = m_zoomHandle.x() * (width() - 2 * m_offset);
double zoomEnd = m_zoomHandle.y() * (width() - 2 * m_offset);
double zoomFactor = (width() - 2 * m_offset) / (zoomEnd - zoomStart);
int pos = int(((event->x() - m_offset) / zoomFactor + zoomStart ) / m_scale);
pos = qBound(0, pos, m_duration - 1);
GenTime position(pos + offset, pCore->getCurrentFps());
bool ok;
auto keyframe = m_model->getClosestKeyframe(position, &ok);
if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos - offset)* m_scale * m_zoomFactor < QApplication::startDragDistance()) {
if (keyframe.first.frames(pCore->getCurrentFps()) != offset) {
m_model->removeKeyframe(keyframe.first);
if (keyframe.first.frames(pCore->getCurrentFps()) == m_currentKeyframe + offset) {
if (m_selectedKeyframes.contains(m_currentKeyframe)) {
m_selectedKeyframes.removeAll(m_currentKeyframe);
}
m_currentKeyframe = m_currentKeyframeOriginal = -1;
}
if (keyframe.first.frames(pCore->getCurrentFps()) == m_position + offset) {
emit atKeyframe(false, m_model->singleKeyframe());
}
}
return;
}
// add new keyframe
m_model->addKeyframe(position, KeyframeType(KdenliveSettings::defaultkeyframeinterp()));
} else {
QWidget::mouseDoubleClickEvent(event);
}
}
void KeyframeView::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::AltModifier) {
// Alt modifier seems to invert x/y axis
if (event->angleDelta().x() > 0) {
slotGoToPrev();
} else {
slotGoToNext();
}
return;
}
if (event->modifiers() & Qt::ControlModifier) {
int maxWidth = width() - 2 * m_offset;
m_zoomStart = m_zoomHandle.x() * maxWidth;
m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
double scaledPos = m_position * m_scale;
double zoomRange = (m_zoomHandle.y() - m_zoomHandle.x()) * maxWidth;
if (event->angleDelta().y() > 0) {
zoomRange /= 1.5;
} else {
zoomRange *= 1.5;
}
if (zoomRange < 5) {
// Don't allow too small zoombar
return;
}
double length = (scaledPos - zoomRange / 2) / maxWidth;
m_zoomHandle.setX(qMax(0., length));
if (length < 0) {
m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth - length));
} else {
m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth));
}
update();
return;
}
int change = event->angleDelta().y() > 0 ? -1 : 1;
int pos = qBound(0, m_position + change, m_duration - 1);
emit seekToPos(pos + m_inPoint);
}
void KeyframeView::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QStylePainter p(this);
int maxWidth = width() - 2 * m_offset;
if (m_duration > 1) {
m_scale = maxWidth / double(m_duration - 1);
} else {
m_scale = maxWidth;
}
int headOffset = m_lineHeight / 2;
int offset = pCore->getItemIn(m_model->getOwnerId());
m_zoomStart = m_zoomHandle.x() * maxWidth;
m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart);
int zoomEnd = qCeil(m_zoomHandle.y() * maxWidth);
/* ticks */
double fps = pCore->getCurrentFps();
int displayedLength = int(m_duration / m_zoomFactor / fps);
double factor = 1;
if (displayedLength < 2) {
// 1 frame tick
} else if (displayedLength < 30 ) {
// 1 sec tick
factor = fps;
} else if (displayedLength < 150) {
// 5 sec tick
factor = 5 * fps;
} else if (displayedLength < 300) {
// 10 sec tick
factor = 10 * fps;
} else if (displayedLength < 900) {
// 30 sec tick
factor = 30 * fps;
} else if (displayedLength < 1800) {
// 1 min. tick
factor = 60 * fps;
} else if (displayedLength < 9000) {
// 5 min tick
factor = 300 * fps;
} else if (displayedLength < 18000) {
// 10 min tick
factor = 600 * fps;
} else {
// 30 min tick
factor = 1800 * fps;
}
// Position of left border in frames
double tickOffset = m_zoomStart * m_zoomFactor;
double frameSize = factor * m_scale * m_zoomFactor;
int base = int(tickOffset / frameSize);
tickOffset = frameSize - (tickOffset - (base * frameSize));
// Draw frame ticks
int scaledTick = 0;
for (int i = 0; i < maxWidth / frameSize; i++) {
scaledTick = int(m_offset + (i * frameSize) + tickOffset);
if (scaledTick >= maxWidth + m_offset) {
break;
}
p.drawLine(QPointF(scaledTick , m_lineHeight + 1), QPointF(scaledTick, m_lineHeight - 3));
}
/*
* keyframes
*/
for (const auto &keyframe : *m_model.get()) {
int pos = keyframe.first.frames(fps) - offset;
if (pos < 0) continue;
double scaledPos = pos * m_scale;
if (scaledPos < m_zoomStart || qFloor(scaledPos) > zoomEnd) {
continue;
}
if (pos == m_currentKeyframe) {
p.setBrush(Qt::red);
} else if (m_selectedKeyframes.contains(pos)) {
p.setBrush(Qt::darkRed);
} else if (pos == m_currentKeyframe || pos == m_hoverKeyframe) {
p.setBrush(m_colSelected);
} else {
p.setBrush(m_colKeyframe);
}
scaledPos -= m_zoomStart;
scaledPos *= m_zoomFactor;
scaledPos += m_offset;
p.drawLine(QPointF(scaledPos, headOffset), QPointF(scaledPos, m_lineHeight - 1));
switch (keyframe.second.first) {
case KeyframeType::Linear: {
QPolygonF position = QPolygonF() << QPointF(-headOffset / 2.0, headOffset / 2.0) << QPointF(0, 0) << QPointF(headOffset / 2.0, headOffset / 2.0)
<< QPointF(0, headOffset);
position.translate(scaledPos, 0);
p.drawPolygon(position);
break;
}
case KeyframeType::Discrete:
p.drawRect(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset));
break;
default:
p.drawEllipse(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset));
break;
}
}
p.setPen(palette().dark().color());
/*
* Time-"line"
*/
p.setPen(m_colKeyframe);
p.drawLine(m_offset, m_lineHeight, width() - m_offset, m_lineHeight);
p.drawLine(m_offset, m_lineHeight - headOffset / 2, m_offset, m_lineHeight + headOffset / 2);
p.drawLine(width() - m_offset, m_lineHeight - headOffset / 2, width() - m_offset, m_lineHeight + headOffset / 2);
/*
* current position cursor
*/
if (m_position >= 0 && m_position < m_duration) {
double scaledPos = m_position * m_scale;
if (scaledPos >= m_zoomStart && qFloor(scaledPos) <= zoomEnd) {
scaledPos -= m_zoomStart;
scaledPos *= m_zoomFactor;
scaledPos += m_offset;
QPolygon pa(3);
int cursorwidth = int((m_zoomHeight - m_lineHeight) / 1.8);
QPolygonF position = QPolygonF() << QPointF(-cursorwidth, m_zoomHeight - 3) << QPointF(cursorwidth, m_zoomHeight - 3) << QPointF(0, m_lineHeight + 1);
position.translate(scaledPos, 0);
p.setBrush(m_colKeyframe);
p.drawPolygon(position);
}
}
// Rubberband
if (m_clickEnd >= 0) {
int min = int((qMin(m_clickPoint, m_clickEnd) * m_scale - m_zoomStart) * m_zoomFactor + m_offset);
int max = int((qMax(m_clickPoint, m_clickEnd) * m_scale - m_zoomStart) * m_zoomFactor + m_offset);
p.setOpacity(0.5);
p.fillRect(QRect(min, 0, max - min, m_lineHeight), palette().highlight());
p.setOpacity(1);
}
// Zoom bar
p.setPen(Qt::NoPen);
p.setBrush(palette().mid());
p.drawRoundedRect(m_offset, m_zoomHeight + 3, width() - 2 * m_offset, m_size - m_zoomHeight - 3, m_lineHeight / 5, m_lineHeight / 5);
p.setBrush(palette().highlight());
p.drawRoundedRect(int(m_offset + (width() - m_offset) * m_zoomHandle.x()),
m_zoomHeight + 3,
int((width() - 2 * m_offset) * (m_zoomHandle.y() - m_zoomHandle.x())),
m_size - m_zoomHeight - 3,
m_lineHeight / 5, m_lineHeight / 5);
}
void KeyframeView::copyCurrentValue(QModelIndex ix, const QString paramName)
{
int offset = pCore->getItemIn(m_model->getOwnerId());
const QString val = m_model->getInterpolatedValue(m_position + offset, ix).toString();
QString newVal;
const QStringList vals = val.split(QLatin1Char(' '));
qDebug()<<"=== COPYING VALS: "<getInterpolatedValue(kf, ix).toString();
QStringList oldVals = oldValue.split(QLatin1Char(' '));
bool found = false;
if (paramName.contains(QLatin1String("spinX"))) {
oldVals[0] = vals.at(0);
newVal = oldVals.join(QLatin1Char(' '));
found = true;
if (!multiParams) {
parentCommand->setText(i18n("Update keyframes X position"));
}
}
if (paramName.contains(QLatin1String("spinY"))) {
oldVals[1] = vals.at(1);
newVal = oldVals.join(QLatin1Char(' '));
found = true;
if (!multiParams) {
parentCommand->setText(i18n("Update keyframes Y position"));
}
}
if (paramName.contains(QLatin1String("spinW"))) {
oldVals[2] = vals.at(2);
newVal = oldVals.join(QLatin1Char(' '));
found = true;
if (!multiParams) {
parentCommand->setText(i18n("Update keyframes width"));
}
}
if (paramName.contains(QLatin1String("spinH"))) {
oldVals[3] = vals.at(3);
newVal = oldVals.join(QLatin1Char(' '));
found = true;
if (!multiParams) {
parentCommand->setText(i18n("Update keyframes height"));
}
}
if (paramName.contains(QLatin1String("spinO"))) {
oldVals[4] = vals.at(4);
newVal = oldVals.join(QLatin1Char(' '));
found = true;
if (!multiParams) {
parentCommand->setText(i18n("Update keyframes opacity"));
}
}
if (!found) {
newVal = val;
parentCommand->setText(i18n("Update keyframes value"));
} else if (multiParams) {
parentCommand->setText(i18n("Update keyframes value"));
}
bool result = m_model->updateKeyframe(GenTime(kf + offset, pCore->getCurrentFps()), newVal, ix, parentCommand);
if (result) {
pCore->displayMessage(i18n("Keyframe value copied"), InformationMessage);
}
}
pCore->pushUndo(parentCommand);
}