Files
mailadler/src/docks/keyframesdock.cpp
2026-01-31 15:28:10 +01:00

1236 lines
54 KiB
C++

/*
* Copyright (c) 2016-2026 Meltytech, LLC
*
* 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 3 of the License, or
* (at your option) any later version.
*
* 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 "keyframesdock.h"
#include "Logger.h"
#include "actions.h"
#include "mainwindow.h"
#include "qmltypes/qmlproducer.h"
#include "qmltypes/qmlutilities.h"
#include "qmltypes/qmlview.h"
#include "settings.h"
#include "widgets/docktoolbar.h"
#include <QAction>
#include <QActionGroup>
#include <QDir>
#include <QIcon>
#include <QMenu>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QSlider>
#include <QToolButton>
#include <QUrl>
#include <QVBoxLayout>
#include <cmath>
static QmlMetadata m_emptyQmlMetadata;
static QmlFilter m_emptyQmlFilter;
KeyframesDock::KeyframesDock(QmlProducer *qmlProducer, QWidget *parent)
: QDockWidget(tr("Keyframes"), parent)
, m_qview(QmlUtilities::sharedEngine(), this)
, m_qmlProducer(qmlProducer)
{
LOG_DEBUG() << "begin";
setObjectName("KeyframesDock");
QIcon icon = QIcon::fromTheme("chronometer",
QIcon(":/icons/oxygen/32x32/actions/chronometer.png"));
toggleViewAction()->setIcon(icon);
setMinimumSize(200, 50);
setWhatsThis("https://forum.shotcut.org/t/about-keyframes/12957/1");
setupActions();
m_mainMenu = new QMenu(tr("Keyframes"), this);
m_mainMenu->addAction(Actions["keyframesTrimInAction"]);
m_mainMenu->addAction(Actions["keyframesTrimOutAction"]);
m_mainMenu->addAction(Actions["keyframesAnimateInAction"]);
m_mainMenu->addAction(Actions["keyframesAnimateOutAction"]);
m_mainMenu->addAction(Actions["keyframesScrubDragAction"]);
m_mainMenu->addAction(Actions["keyframesToggleKeyframeAction"]);
m_mainMenu->addAction(Actions["keyframesSeekPreviousAction"]);
m_mainMenu->addAction(Actions["keyframesSeekNextAction"]);
QMenu *viewMenu = new QMenu(tr("View"), this);
viewMenu->addAction(Actions["keyframesZoomOutAction"]);
viewMenu->addAction(Actions["keyframesZoomInAction"]);
viewMenu->addAction(Actions["keyframesZoomFitAction"]);
m_mainMenu->addMenu(viewMenu);
Actions.loadFromMenu(m_mainMenu);
m_keyMenu = new QMenu(tr("Keyframe"), this);
m_keyTypePrevMenu = new QMenu(tr("From Previous"), this);
m_keyTypePrevMenu->addAction(Actions["keyframesTypePrevHoldAction"]);
m_keyTypePrevMenu->addAction(Actions["keyframesTypePrevLinearAction"]);
m_keyTypePrevMenu->addAction(Actions["keyframesTypePrevSmoothNaturalAction"]);
QMenu *keyEaseOutMenu = new QMenu(tr("Ease Out"), this);
icon = QIcon::fromTheme("keyframe-ease-out",
QIcon(":/icons/oxygen/32x32/actions/keyframe-ease-out.png"));
keyEaseOutMenu->setIcon(icon);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutSinuAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutQuadAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutCubeAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutQuartAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutQuintAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutExpoAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutCircAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutBackAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutElasAction"]);
keyEaseOutMenu->addAction(Actions["keyframesTypePrevEaseOutBounAction"]);
m_keyTypePrevMenu->addMenu(keyEaseOutMenu);
m_keyMenu->addMenu(m_keyTypePrevMenu);
m_keyTypeNextMenu = new QMenu(tr("To Next"), this);
m_keyTypeNextMenu->addAction(Actions["keyframesTypeHoldAction"]);
m_keyTypeNextMenu->addAction(Actions["keyframesTypeLinearAction"]);
m_keyTypeNextMenu->addAction(Actions["keyframesTypeSmoothNaturalAction"]);
QMenu *keyEaseInMenu = new QMenu(tr("Ease In"), this);
icon = QIcon::fromTheme("keyframe-ease-in",
QIcon(":/icons/oxygen/32x32/actions/keyframe-ease-in.png"));
keyEaseInMenu->setIcon(icon);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInSinuAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInQuadAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInCubeAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInQuartAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInQuintAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInExpoAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInCircAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInBackAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInElasAction"]);
keyEaseInMenu->addAction(Actions["keyframesTypeEaseInBounAction"]);
m_keyTypeNextMenu->addMenu(keyEaseInMenu);
QMenu *keyEaseInOutMenu = new QMenu(tr("Ease In/Out"), this);
icon = QIcon::fromTheme("keyframe-ease-inout",
QIcon(":/icons/oxygen/32x32/actions/keyframe-ease-inout.png"));
keyEaseInOutMenu->setIcon(icon);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutSinuAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutQuadAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutCubeAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutQuartAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutQuintAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutExpoAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutCircAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutBackAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutElasAction"]);
keyEaseInOutMenu->addAction(Actions["keyframesTypeEaseInOutBounAction"]);
m_keyTypeNextMenu->addMenu(keyEaseInOutMenu);
m_keyMenu->addMenu(m_keyTypeNextMenu);
m_keyMenu->addAction(Actions["keyframesRemoveAction"]);
Actions.loadFromMenu(m_keyMenu);
m_clipMenu = new QMenu(tr("Keyframes Clip"), this);
m_clipMenu->addAction(Actions["keyframesRebuildAudioWaveformAction"]);
Actions.loadFromMenu(m_clipMenu);
QVBoxLayout *vboxLayout = new QVBoxLayout();
vboxLayout->setSpacing(0);
vboxLayout->setContentsMargins(0, 0, 0, 0);
DockToolBar *toolbar = new DockToolBar(tr("Keyframes Controls"));
QToolButton *menuButton = new QToolButton();
menuButton->setIcon(
QIcon::fromTheme("show-menu", QIcon(":/icons/oxygen/32x32/actions/show-menu.png")));
menuButton->setToolTip(tr("Keyframes Menu"));
menuButton->setAutoRaise(true);
menuButton->setPopupMode(QToolButton::QToolButton::InstantPopup);
menuButton->setMenu(m_mainMenu);
toolbar->addWidget(menuButton);
toolbar->addSeparator();
toolbar->addAction(Actions["keyframesTrimInAction"]);
toolbar->addAction(Actions["keyframesTrimOutAction"]);
toolbar->addAction(Actions["keyframesAnimateInAction"]);
toolbar->addAction(Actions["keyframesAnimateOutAction"]);
toolbar->addSeparator();
toolbar->addAction(Actions["timelineSnapAction"]);
toolbar->addAction(Actions["keyframesScrubDragAction"]);
toolbar->addSeparator();
toolbar->addAction(Actions["keyframesZoomOutAction"]);
QSlider *zoomSlider = new QSlider();
zoomSlider->setOrientation(Qt::Horizontal);
zoomSlider->setMaximumWidth(200);
zoomSlider->setMinimum(0);
zoomSlider->setMaximum(300);
zoomSlider->setValue(100);
zoomSlider->setTracking(false);
connect(zoomSlider, &QSlider::valueChanged, this, [&](int value) {
if (!isVisible() || !m_qview.rootObject())
return;
emit setZoom(value / 100.0);
});
connect(this, &KeyframesDock::timeScaleChanged, zoomSlider, [=]() {
double value = round(pow(m_timeScale - 0.01, 1.0 / 3.0) * 100.0);
zoomSlider->setValue(value);
});
toolbar->addWidget(zoomSlider);
toolbar->addAction(Actions["keyframesZoomInAction"]);
toolbar->addAction(Actions["keyframesZoomFitAction"]);
vboxLayout->setMenuBar(toolbar);
m_qview.setFocusPolicy(Qt::StrongFocus);
m_qview.quickWindow()->setPersistentSceneGraph(false);
#ifndef Q_OS_MAC
m_qview.setAttribute(Qt::WA_AcceptTouchEvents);
#endif
setWidget(&m_qview);
QmlUtilities::setCommonProperties(m_qview.rootContext());
m_qview.rootContext()->setContextProperty("keyframes", this);
m_qview.rootContext()->setContextProperty("view", new QmlView(&m_qview));
m_qview.rootContext()->setContextProperty("parameters", &m_model);
m_qview.setResizeMode(QQuickWidget::SizeRootObjectToView);
m_qview.setClearColor(palette().window().color());
m_qview.quickWindow()->setPersistentSceneGraph(false);
#ifndef Q_OS_MAC
m_qview.setAttribute(Qt::WA_AcceptTouchEvents);
#endif
setCurrentFilter(0, 0);
connect(this, SIGNAL(visibilityChanged(bool)), this, SLOT(load(bool)));
vboxLayout->addWidget(&m_qview);
QWidget *dockContentsWidget = new QWidget();
dockContentsWidget->setLayout(vboxLayout);
QDockWidget::setWidget(dockContentsWidget);
LOG_DEBUG() << "end";
}
void KeyframesDock::setupActions()
{
QIcon icon;
QAction *action;
action = new QAction(tr("Set Filter Start"), this);
action->setShortcut(QKeySequence(Qt::Key_BracketLeft));
action->setWhatsThis("https://forum.shotcut.org/t/trimming-filters/13212/1");
icon = QIcon::fromTheme("keyframes-filter-in",
QIcon(":/icons/oxygen/32x32/actions/keyframes-filter-in.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [&]() {
if (m_qmlProducer && m_filter && m_filter->allowTrim()) {
int i = m_qmlProducer->position() + m_qmlProducer->in();
m_model.trimFilterIn(i);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = false;
if (m_filter && m_filter->allowTrim())
enabled = true;
action->setEnabled(enabled);
});
Actions.add("keyframesTrimInAction", action);
action = new QAction(tr("Set Filter End"), this);
action->setShortcut(QKeySequence(Qt::Key_BracketRight));
action->setWhatsThis("https://forum.shotcut.org/t/trimming-filters/13212/1");
icon = QIcon::fromTheme("keyframes-filter-out",
QIcon(":/icons/oxygen/32x32/actions/keyframes-filter-out.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [&]() {
if (m_qmlProducer && m_filter && m_filter->allowTrim()) {
int i = m_qmlProducer->position() + m_qmlProducer->in();
m_model.trimFilterOut(i);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = false;
if (m_filter && m_filter->allowTrim())
enabled = true;
action->setEnabled(enabled);
});
Actions.add("keyframesTrimOutAction", action);
action = new QAction(tr("Set First Simple Keyframe"), this);
action->setShortcut(QKeySequence(Qt::Key_BraceLeft));
action->setWhatsThis("https://forum.shotcut.org/t/simple-keyframes/43639/1");
icon = QIcon::fromTheme("keyframes-simple-in",
QIcon(":/icons/oxygen/32x32/actions/keyframes-simple-in.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [&]() {
if (m_qmlProducer && m_filter && m_filter->allowAnimateIn()) {
int i = m_qmlProducer->position() + m_qmlProducer->in() - m_filter->in();
m_filter->setAnimateIn(i);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = false;
if (m_filter && m_filter->allowAnimateIn())
enabled = true;
action->setEnabled(enabled);
});
Actions.add("keyframesAnimateInAction", action);
action = new QAction(tr("Set Second Simple Keyframe"), this);
action->setShortcut(QKeySequence(Qt::Key_BraceRight));
action->setWhatsThis("https://forum.shotcut.org/t/simple-keyframes/43639/1");
icon = QIcon::fromTheme("keyframes-simple-out",
QIcon(":/icons/oxygen/32x32/actions/keyframes-simple-out.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [&]() {
if (m_qmlProducer && m_filter && m_filter->allowAnimateOut()) {
int i = m_filter->out() - (m_qmlProducer->position() + m_qmlProducer->in());
m_filter->setAnimateOut(i);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = false;
if (m_filter && m_filter->allowAnimateOut())
enabled = true;
action->setEnabled(enabled);
});
Actions.add("keyframesAnimateOutAction", action);
action = new QAction(tr("Scrub While Dragging"), this);
icon = QIcon::fromTheme("scrub_drag", QIcon(":/icons/oxygen/32x32/actions/scrub_drag.png"));
action->setIcon(icon);
action->setCheckable(true);
action->setChecked(Settings.keyframesDragScrub());
connect(action, &QAction::triggered, this, [&](bool checked) {
Settings.setKeyframesDragScrub(checked);
});
connect(&Settings, &ShotcutSettings::keyframesDragScrubChanged, action, [=]() {
action->setChecked(Settings.keyframesDragScrub());
});
Actions.add("keyframesScrubDragAction", action);
action = new QAction(tr("Zoom Keyframes Out"), this);
action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_Minus));
icon = QIcon::fromTheme("zoom-out", QIcon(":/icons/oxygen/32x32/actions/zoom-out.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
emit zoomOut();
});
Actions.add("keyframesZoomOutAction", action);
action = new QAction(tr("Zoom Keyframes In"), this);
action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_Plus));
icon = QIcon::fromTheme("zoom-in", QIcon(":/icons/oxygen/32x32/actions/zoom-in.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
emit zoomIn();
});
Actions.add("keyframesZoomInAction", action);
action = new QAction(tr("Zoom Keyframes To Fit"), this);
action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_0));
icon = QIcon::fromTheme("zoom-fit-best",
QIcon(":/icons/oxygen/32x32/actions/zoom-fit-best.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
emit zoomToFit();
});
Actions.add("keyframesZoomFitAction", action);
// Actions to modify previous keyframes
QActionGroup *keyframeTypePrevActionGroup = new QActionGroup(this);
keyframeTypePrevActionGroup->setExclusive(true);
action = new QAction(tr("Hold"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::DiscreteInterpolation);
}
});
icon = QIcon::fromTheme("keyframe-hold",
QIcon(":/icons/oxygen/32x32/actions/keyframe-hold.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevHoldAction", action);
action = new QAction(tr("Linear"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::LinearInterpolation);
}
});
icon = QIcon::fromTheme("keyframe-linear",
QIcon(":/icons/oxygen/32x32/actions/keyframe-linear.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevLinearAction", action);
action = new QAction(tr("Smooth"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::SmoothNaturalInterpolation);
}
});
icon = QIcon::fromTheme("keyframe-smooth",
QIcon(":/icons/oxygen/32x32/actions/keyframe-smooth.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevSmoothNaturalAction", action);
action = new QAction(tr("Ease Out Sinusoidal"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutSinusoidal);
}
});
icon = QIcon::fromTheme("ease-out-sinu",
QIcon(":/icons/oxygen/32x32/actions/ease-out-sinu.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutSinuAction", action);
action = new QAction(tr("Ease Out Quadratic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutQuadratic);
}
});
icon = QIcon::fromTheme("ease-out-quad",
QIcon(":/icons/oxygen/32x32/actions/ease-out-quad.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutQuadAction", action);
action = new QAction(tr("Ease Out Cubic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutCubic);
}
});
icon = QIcon::fromTheme("ease-out-cube",
QIcon(":/icons/oxygen/32x32/actions/ease-out-cube.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutCubeAction", action);
action = new QAction(tr("Ease Out Quartic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutQuartic);
}
});
icon = QIcon::fromTheme("ease-out-quar",
QIcon(":/icons/oxygen/32x32/actions/ease-out-quar.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutQuartAction", action);
action = new QAction(tr("Ease Out Quintic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutQuintic);
}
});
icon = QIcon::fromTheme("ease-out-quin",
QIcon(":/icons/oxygen/32x32/actions/ease-out-quin.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutQuintAction", action);
action = new QAction(tr("Ease Out Exponential"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutExponential);
}
});
icon = QIcon::fromTheme("ease-out-expo",
QIcon(":/icons/oxygen/32x32/actions/ease-out-expo.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutExpoAction", action);
action = new QAction(tr("Ease Out Circular"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutCircular);
}
});
icon = QIcon::fromTheme("ease-out-circ",
QIcon(":/icons/oxygen/32x32/actions/ease-out-circ.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutCircAction", action);
action = new QAction(tr("Ease Out Back"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutBack);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = true;
if (m_metadata && m_metadata->keyframes() && !m_metadata->keyframes()->allowOvershoot()) {
enabled = false;
}
action->setVisible(enabled);
action->setEnabled(enabled);
});
icon = QIcon::fromTheme("ease-out-back",
QIcon(":/icons/oxygen/32x32/actions/ease-out-back.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutBackAction", action);
action = new QAction(tr("Ease Out Elastic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutElastic);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = true;
if (m_metadata && m_metadata->keyframes() && !m_metadata->keyframes()->allowOvershoot()) {
enabled = false;
}
action->setVisible(enabled);
action->setEnabled(enabled);
});
icon = QIcon::fromTheme("ease-out-elas",
QIcon(":/icons/oxygen/32x32/actions/ease-out-elas.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutElasAction", action);
action = new QAction(tr("Ease Out Bounce"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt() - 1,
KeyframesModel::EaseOutBounce);
}
});
icon = QIcon::fromTheme("ease-out-boun",
QIcon(":/icons/oxygen/32x32/actions/ease-out-boun.png"));
action->setIcon(icon);
keyframeTypePrevActionGroup->addAction(action);
Actions.add("keyframesTypePrevEaseOutBounAction", action);
// Actions to modify selected keyframes
QActionGroup *keyframeTypeActionGroup = new QActionGroup(this);
keyframeTypeActionGroup->setExclusive(true);
action = new QAction(tr("Hold"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::DiscreteInterpolation);
}
});
icon = QIcon::fromTheme("keyframe-hold",
QIcon(":/icons/oxygen/32x32/actions/keyframe-hold.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeHoldAction", action);
action = new QAction(tr("Linear"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::LinearInterpolation);
}
});
icon = QIcon::fromTheme("keyframe-linear",
QIcon(":/icons/oxygen/32x32/actions/keyframe-linear.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeLinearAction", action);
action = new QAction(tr("Smooth"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::SmoothNaturalInterpolation);
}
});
icon = QIcon::fromTheme("keyframe-smooth",
QIcon(":/icons/oxygen/32x32/actions/keyframe-smooth.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeSmoothNaturalAction", action);
action = new QAction(tr("Ease In Sinusoidal"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInSinusoidal);
}
});
icon = QIcon::fromTheme("ease-in-sinu", QIcon(":/icons/oxygen/32x32/actions/ease-in-sinu.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInSinuAction", action);
action = new QAction(tr("Ease In Quadratic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInQuadratic);
}
});
icon = QIcon::fromTheme("ease-in-quad", QIcon(":/icons/oxygen/32x32/actions/ease-in-quad.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInQuadAction", action);
action = new QAction(tr("Ease In Cubic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInCubic);
}
});
icon = QIcon::fromTheme("ease-in-cube", QIcon(":/icons/oxygen/32x32/actions/ease-in-cube.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInCubeAction", action);
action = new QAction(tr("Ease In Quartic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInQuartic);
}
});
icon = QIcon::fromTheme("ease-in-quar", QIcon(":/icons/oxygen/32x32/actions/ease-in-quar.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInQuartAction", action);
action = new QAction(tr("Ease In Quintic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInQuintic);
}
});
icon = QIcon::fromTheme("ease-in-quin", QIcon(":/icons/oxygen/32x32/actions/ease-in-quin.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInQuintAction", action);
action = new QAction(tr("Ease In Exponential"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInExponential);
}
});
icon = QIcon::fromTheme("ease-in-expo", QIcon(":/icons/oxygen/32x32/actions/ease-in-expo.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInExpoAction", action);
action = new QAction(tr("Ease In Circular"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInCircular);
}
});
icon = QIcon::fromTheme("ease-in-circ", QIcon(":/icons/oxygen/32x32/actions/ease-in-circ.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInCircAction", action);
action = new QAction(tr("Ease In Back"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInBack);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = true;
if (m_metadata && m_metadata->keyframes() && !m_metadata->keyframes()->allowOvershoot()) {
enabled = false;
}
action->setVisible(enabled);
action->setEnabled(enabled);
});
icon = QIcon::fromTheme("ease-in-back", QIcon(":/icons/oxygen/32x32/actions/ease-in-back.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInBackAction", action);
action = new QAction(tr("Ease In Elastic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInElastic);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = true;
if (m_metadata && m_metadata->keyframes() && !m_metadata->keyframes()->allowOvershoot()) {
enabled = false;
}
action->setVisible(enabled);
action->setEnabled(enabled);
});
icon = QIcon::fromTheme("ease-in-elas", QIcon(":/icons/oxygen/32x32/actions/ease-in-elas.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInElasAction", action);
action = new QAction(tr("Ease In Bounce"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInBounce);
}
});
icon = QIcon::fromTheme("ease-in-boun", QIcon(":/icons/oxygen/32x32/actions/ease-in-boun.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInBounAction", action);
action = new QAction(tr("Ease In/Out Sinusoidal"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutSinusoidal);
}
});
icon = QIcon::fromTheme("ease-inout-sinu",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-sinu.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutSinuAction", action);
action = new QAction(tr("Ease In/Out Quadratic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutQuadratic);
}
});
icon = QIcon::fromTheme("ease-inout-quad",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-quad.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutQuadAction", action);
action = new QAction(tr("Ease In/Out Cubic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutCubic);
}
});
icon = QIcon::fromTheme("ease-inout-cube",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-cube.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutCubeAction", action);
action = new QAction(tr("Ease In/Out Quartic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutQuartic);
}
});
icon = QIcon::fromTheme("ease-inout-quar",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-quar.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutQuartAction", action);
action = new QAction(tr("Ease In/Out Quintic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutQuintic);
}
});
icon = QIcon::fromTheme("ease-inout-quin",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-quin.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutQuintAction", action);
action = new QAction(tr("Ease In/Out Exponential"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutExponential);
}
});
icon = QIcon::fromTheme("ease-inout-expo",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-expo.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutExpoAction", action);
action = new QAction(tr("Ease In/Out Circular"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutCircular);
}
});
icon = QIcon::fromTheme("ease-inout-circ",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-circ.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutCircAction", action);
action = new QAction(tr("Ease In/Out Back"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutBack);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = true;
if (m_metadata && m_metadata->keyframes() && !m_metadata->keyframes()->allowOvershoot()) {
enabled = false;
}
action->setVisible(enabled);
action->setEnabled(enabled);
});
icon = QIcon::fromTheme("ease-inout-back",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-back.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutBackAction", action);
action = new QAction(tr("Ease In/Out Elastic"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutElastic);
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
bool enabled = true;
if (m_metadata && m_metadata->keyframes() && !m_metadata->keyframes()->allowOvershoot()) {
enabled = false;
}
action->setVisible(enabled);
action->setEnabled(enabled);
});
icon = QIcon::fromTheme("ease-inout-elas",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-elas.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutElasAction", action);
action = new QAction(tr("Ease In/Out Bounce"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.setInterpolation(currentTrack,
keyframeIndex.toInt(),
KeyframesModel::EaseInOutBounce);
}
});
icon = QIcon::fromTheme("ease-inout-boun",
QIcon(":/icons/oxygen/32x32/actions/ease-inout-boun.png"));
action->setIcon(icon);
keyframeTypeActionGroup->addAction(action);
Actions.add("keyframesTypeEaseInOutBounAction", action);
action = new QAction(tr("Remove"), this);
connect(action, &QAction::triggered, this, [&]() {
if (!isVisible() || !m_qview.rootObject())
return;
int currentTrack = m_qview.rootObject()->property("currentTrack").toInt();
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
m_model.remove(currentTrack, keyframeIndex.toInt());
}
});
Actions.add("keyframesRemoveAction", action);
action = new QAction(tr("Rebuild Audio Waveform"), this);
action->setEnabled(Settings.timelineShowWaveforms());
connect(action, &QAction::triggered, this, [&](bool checked) {
if (m_qmlProducer && Settings.timelineShowWaveforms()) {
m_qmlProducer->remakeAudioLevels();
}
});
connect(&Settings, &ShotcutSettings::timelineShowWaveformsChanged, action, [=]() {
action->setEnabled(Settings.timelineShowWaveforms());
});
Actions.add("keyframesRebuildAudioWaveformAction", action);
action = new QAction(tr("Seek Previous Keyframe"), this);
action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_BracketLeft));
action->setEnabled(m_qmlProducer && m_filter);
connect(action, &QAction::triggered, this, [&]() {
if (m_qmlProducer && m_filter) {
if (m_model.advancedKeyframesInUse())
seekPrevious();
else
emit seekPreviousSimple();
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
action->setEnabled(m_qmlProducer && m_filter);
});
Actions.add("keyframesSeekPreviousAction", action);
action = new QAction(tr("Seek Next Keyframe"), this);
action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_BracketRight));
action->setEnabled(m_qmlProducer && m_filter);
connect(action, &QAction::triggered, this, [&]() {
if (m_qmlProducer && m_filter) {
if (m_model.advancedKeyframesInUse())
seekNext();
else
emit seekNextSimple();
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
action->setEnabled(m_qmlProducer && m_filter);
});
Actions.add("keyframesSeekNextAction", action);
action = new QAction(tr("Toggle Keyframe At Playhead"), this);
action->setShortcut(QKeySequence(Qt::Key_Semicolon));
action->setEnabled(m_qmlProducer && m_filter);
connect(action, &QAction::triggered, this, [&]() {
if (m_qmlProducer && m_filter && currentParameter() >= 0) {
auto position = m_qmlProducer->position() - (m_filter->in() - m_qmlProducer->in());
auto parameterIndex = currentParameter();
if (m_model.isKeyframe(parameterIndex, position)) {
auto keyframeIndex = m_model.keyframeIndex(parameterIndex, position);
m_model.remove(parameterIndex, keyframeIndex);
} else {
m_model.addKeyframe(parameterIndex, position);
}
}
});
connect(this, &KeyframesDock::newFilter, action, [=]() {
action->setEnabled(m_qmlProducer && m_filter);
});
Actions.add("keyframesToggleKeyframeAction", action);
}
int KeyframesDock::seekPrevious()
{
if (m_qmlProducer) {
int position = m_model.previousKeyframePosition(currentParameter(),
m_qmlProducer->position()
+ m_qmlProducer->in());
position -= m_qmlProducer->in();
m_qmlProducer->setPosition(position);
return m_model.keyframeIndex(currentParameter(),
position + m_qmlProducer->in() - m_filter->in());
}
return 0;
}
int KeyframesDock::seekNext()
{
if (m_qmlProducer) {
int position = m_model.nextKeyframePosition(currentParameter(),
m_qmlProducer->position() + m_qmlProducer->in());
position -= m_qmlProducer->in();
if (position > m_qmlProducer->position())
m_qmlProducer->setPosition(position);
else
position = m_qmlProducer->position();
return m_model.keyframeIndex(currentParameter(),
position + m_qmlProducer->in() - m_filter->in());
}
return 0;
}
void KeyframesDock::setCurrentFilter(QmlFilter *filter, QmlMetadata *meta)
{
m_filter = filter;
m_metadata = meta;
if (!m_filter || !m_filter->producer().is_valid()) {
m_filter = &m_emptyQmlFilter;
m_metadata = &m_emptyQmlMetadata;
}
m_model.load(m_filter, m_metadata);
disconnect(this, SIGNAL(changed()));
connect(m_filter, SIGNAL(changed(QString)), SIGNAL(changed()));
connect(m_filter, SIGNAL(changed(QString)), &m_model, SLOT(onFilterChanged(QString)));
connect(m_filter, SIGNAL(animateInChanged()), &m_model, SLOT(reload()));
connect(m_filter, SIGNAL(animateOutChanged()), &m_model, SLOT(reload()));
connect(m_filter, SIGNAL(inChanged(int)), &m_model, SLOT(onFilterInChanged(int)));
emit newFilter();
}
bool KeyframesDock::event(QEvent *event)
{
bool result = QDockWidget::event(event);
if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange) {
load(true);
}
return result;
}
void KeyframesDock::keyPressEvent(QKeyEvent *event)
{
QDockWidget::keyPressEvent(event);
if (!event->isAccepted())
MAIN.keyPressEvent(event);
}
void KeyframesDock::keyReleaseEvent(QKeyEvent *event)
{
QDockWidget::keyReleaseEvent(event);
if (!event->isAccepted())
MAIN.keyReleaseEvent(event);
}
int KeyframesDock::currentParameter() const
{
if (!m_qview.rootObject())
return 0;
return m_qview.rootObject()->property("currentTrack").toInt();
}
void KeyframesDock::setTimeScale(double value)
{
m_timeScale = value;
emit timeScaleChanged();
}
void KeyframesDock::load(bool force)
{
LOG_DEBUG() << "begin" << m_qview.source().isEmpty() << force;
if (m_qview.source().isEmpty() || force) {
QDir viewPath = QmlUtilities::qmlDir();
viewPath.cd("views");
viewPath.cd("keyframes");
m_qview.engine()->addImportPath(viewPath.path());
QDir modulePath = QmlUtilities::qmlDir();
modulePath.cd("modules");
m_qview.engine()->addImportPath(modulePath.path());
m_qview.setResizeMode(QQuickWidget::SizeRootObjectToView);
m_qview.quickWindow()->setColor(palette().window().color());
QUrl source = QUrl::fromLocalFile(viewPath.absoluteFilePath("keyframes.qml"));
m_qview.setSource(source);
connect(m_qview.rootObject(), SIGNAL(rightClicked()), this, SLOT(onDockRightClicked()));
connect(m_qview.rootObject(),
SIGNAL(keyframeRightClicked()),
this,
SLOT(onKeyframeRightClicked()));
connect(m_qview.rootObject(), SIGNAL(clipRightClicked()), this, SLOT(onClipRightClicked()));
emit timeScaleChanged();
}
}
void KeyframesDock::reload()
{
load(true);
}
void KeyframesDock::onProducerModified()
{
// The clip name may have changed.
if (m_qmlProducer)
emit m_qmlProducer->producerChanged();
}
void KeyframesDock::onDockRightClicked()
{
m_mainMenu->popup(QCursor::pos());
}
void KeyframesDock::onKeyframeRightClicked()
{
if (!m_qview.rootObject())
return;
bool firstKey = false;
bool lastKey = false;
for (auto keyframeIndex : m_qview.rootObject()->property("selection").toList()) {
int keyIndex = keyframeIndex.toInt();
if (keyIndex == 0) {
firstKey = true;
}
if (keyIndex >= m_model.keyframeCount(currentParameter()) - 1) {
lastKey = true;
}
}
m_keyTypePrevMenu->setEnabled(!firstKey);
m_keyTypeNextMenu->setEnabled(!lastKey);
m_keyMenu->popup(QCursor::pos());
}
void KeyframesDock::onClipRightClicked()
{
m_clipMenu->popup(QCursor::pos());
}