Automated with clang-tidy readability-identifier-naming (config added to .clang-tidy) plus scripted passes, per the updated rules now documented in CONTRIBUTING.md: - types (class/struct/enum/alias/template params): PascalCase - functions, variables, members: snake_case (incl. rational -> Rational) - private/protected members: trailing underscore; static member variables likewise (instance_, available_themes_) - constants and enum values: snake_case (kLinear -> k_linear, F32P -> f32p); ALL_CAPS reserved for macros - macros: OAK_ prefix (OLIVE_ADD_TEST/OLIVE_ASSERT/OLIVE_CONFIG -> OAK_ADD_TEST/OAK_ASSERT/OAK_CONFIG, GL_PREAMBLE -> OAK_GL_PREAMBLE, include guards -> OAK_*) - file names: all lowercase (Current/Plugin/OliveHost/OliveClip/ OlivePluginInstance -> current/plugin/olivehost/oliveclip/ oliveplugininstance) - getters share the member name sans underscore, setters set_foo() - Qt and third-party (OpenFX) virtual overrides and framework callbacks keep their original names (exempt in .clang-tidy) Manual follow-ups required where automation could not reach: - string-based QMetaObject/SIGNAL/SLOT references updated to renamed methods (AddTask, CreatedFile, DeleteSpecificFile, moveSelectionUp, ...) - macro bodies referencing renamed methods (OLIVE_CONFIG, NODE_DEFAULT_DESTRUCTOR, MANAGEDDISPLAYWIDGET_*) - self-shadowing locals renamed where signals/methods became same-named (size_changed, worker_count, selected_items, import param, filters) - third_party OFX member/namespace usages restored (OFX::Host::*, _created, _clipPrefsDirty, createInstance, clearPersistentMessage) - STL protocol aliases restored (const_iterator) with .clang-tidy ignore rules; qHash overloads restored Full build and test suite pass: ctest 4/4, ~1960 gtest cases green.
314 lines
6.9 KiB
C++
314 lines
6.9 KiB
C++
/***
|
|
|
|
Olive - Non-Linear Video Editor
|
|
Copyright (C) 2022 Olive Team
|
|
Modifications Copyright (C) 2025 mikesolar
|
|
|
|
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 "inputimmediate.h"
|
|
|
|
#include "common/lerp.h"
|
|
#include "common/tohex.h"
|
|
#include "node.h"
|
|
|
|
namespace olive
|
|
{
|
|
|
|
NodeInputImmediate::NodeInputImmediate(NodeValue::Type type,
|
|
const SplitValue &default_val)
|
|
: default_value_(default_val)
|
|
, keyframing_(false)
|
|
{
|
|
set_data_type(type);
|
|
}
|
|
|
|
void NodeInputImmediate::set_standard_value_on_track(const QVariant &value,
|
|
int track)
|
|
{
|
|
standard_value_.replace(track, value);
|
|
}
|
|
|
|
void NodeInputImmediate::set_split_standard_value(const SplitValue &value)
|
|
{
|
|
for (int i = 0; i < value.size() && i < standard_value_.size(); i++) {
|
|
standard_value_[i] = value[i];
|
|
}
|
|
}
|
|
|
|
QVector<NodeKeyframe *>
|
|
NodeInputImmediate::get_keyframe_at_time(const Rational &time) const
|
|
{
|
|
QVector<NodeKeyframe *> keys;
|
|
|
|
for (int i = 0; i < keyframe_tracks_.size(); i++) {
|
|
NodeKeyframe *key_at_time = get_keyframe_at_time_on_track(time, i);
|
|
|
|
if (key_at_time) {
|
|
keys.append(key_at_time);
|
|
}
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
NodeKeyframe *
|
|
NodeInputImmediate::get_keyframe_at_time_on_track(const Rational &time,
|
|
int track) const
|
|
{
|
|
if (!is_using_standard_value(track)) {
|
|
foreach (NodeKeyframe *key, keyframe_tracks_.at(track)) {
|
|
if (key->time() == time) {
|
|
return key;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
NodeKeyframe *
|
|
NodeInputImmediate::get_closest_keyframe_to_time_on_track(const Rational &time,
|
|
int track) const
|
|
{
|
|
if (is_using_standard_value(track)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const NodeKeyframeTrack &key_track = keyframe_tracks_.at(track);
|
|
|
|
if (time <= key_track.first()->time()) {
|
|
return key_track.first();
|
|
}
|
|
|
|
if (time >= key_track.last()->time()) {
|
|
return key_track.last();
|
|
}
|
|
|
|
for (int i = 1; i < key_track.size(); i++) {
|
|
NodeKeyframe *prev_key = key_track.at(i - 1);
|
|
NodeKeyframe *next_key = key_track.at(i);
|
|
|
|
if (prev_key->time() <= time && next_key->time() >= time) {
|
|
// Return whichever is closer
|
|
Rational prev_diff = time - prev_key->time();
|
|
Rational next_diff = next_key->time() - time;
|
|
|
|
if (next_diff < prev_diff) {
|
|
return next_key;
|
|
} else {
|
|
return prev_key;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
NodeKeyframe *
|
|
NodeInputImmediate::get_closest_keyframe_before_time(const Rational &time) const
|
|
{
|
|
NodeKeyframe *key = nullptr;
|
|
|
|
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
|
|
foreach (NodeKeyframe *k, track) {
|
|
if (k->time() >= time) {
|
|
break;
|
|
} else if (!key || k->time() > key->time()) {
|
|
key = k;
|
|
}
|
|
}
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
NodeKeyframe *
|
|
NodeInputImmediate::get_closest_keyframe_after_time(const Rational &time) const
|
|
{
|
|
NodeKeyframe *key = nullptr;
|
|
|
|
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
|
|
for (int i = track.size() - 1; i >= 0; i--) {
|
|
NodeKeyframe *k = track.at(i);
|
|
|
|
if (k->time() <= time) {
|
|
break;
|
|
} else if (!key || k->time() < key->time()) {
|
|
key = k;
|
|
}
|
|
}
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
NodeKeyframe::Type
|
|
NodeInputImmediate::get_best_keyframe_type_for_time(const Rational &time,
|
|
int track) const
|
|
{
|
|
NodeKeyframe *closest_key =
|
|
get_closest_keyframe_to_time_on_track(time, track);
|
|
|
|
if (closest_key) {
|
|
return closest_key->type();
|
|
}
|
|
|
|
return NodeKeyframe::k_default_type;
|
|
}
|
|
|
|
bool NodeInputImmediate::has_keyframe_at_time(const Rational &time) const
|
|
{
|
|
if (!is_keyframing()) {
|
|
return false;
|
|
}
|
|
|
|
// Loop through keyframes to see if any match
|
|
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
|
|
foreach (NodeKeyframe *key, track) {
|
|
if (key->time() == time) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// None match
|
|
return false;
|
|
}
|
|
|
|
void NodeInputImmediate::set_data_type(NodeValue::Type type)
|
|
{
|
|
int track_size = NodeValue::get_number_of_keyframe_tracks(type);
|
|
|
|
keyframe_tracks_.resize(track_size);
|
|
standard_value_.resize(track_size);
|
|
|
|
set_split_standard_value(default_value_);
|
|
}
|
|
|
|
NodeKeyframe *NodeInputImmediate::get_earliest_keyframe() const
|
|
{
|
|
NodeKeyframe *earliest = nullptr;
|
|
|
|
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
|
|
if (!track.isEmpty()) {
|
|
NodeKeyframe *earliest_in_track = track.first();
|
|
|
|
if (!earliest || earliest_in_track->time() < earliest->time()) {
|
|
earliest = earliest_in_track;
|
|
}
|
|
}
|
|
}
|
|
|
|
return earliest;
|
|
}
|
|
|
|
NodeKeyframe *NodeInputImmediate::get_latest_keyframe() const
|
|
{
|
|
NodeKeyframe *latest = nullptr;
|
|
|
|
foreach (const NodeKeyframeTrack &track, keyframe_tracks_) {
|
|
if (!track.isEmpty()) {
|
|
NodeKeyframe *latest_in_track = track.last();
|
|
|
|
if (!latest || latest_in_track->time() > latest->time()) {
|
|
latest = latest_in_track;
|
|
}
|
|
}
|
|
}
|
|
|
|
return latest;
|
|
}
|
|
|
|
void NodeInputImmediate::insert_keyframe(NodeKeyframe *key)
|
|
{
|
|
NodeKeyframeTrack &key_track = keyframe_tracks_[key->track()];
|
|
|
|
int insert_index = key_track.size();
|
|
|
|
for (int i = 0; i < key_track.size(); i++) {
|
|
NodeKeyframe *compare = key_track.at(i);
|
|
|
|
// Ensure we aren't trying to insert two keyframes at the same time
|
|
Q_ASSERT(compare->time() != key->time());
|
|
|
|
if (compare->time() > key->time()) {
|
|
insert_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
key_track.insert(insert_index, key);
|
|
|
|
NodeKeyframe *previous = insert_index > 0 ? key_track.at(insert_index - 1) :
|
|
nullptr;
|
|
NodeKeyframe *next = insert_index < key_track.size() - 1 ?
|
|
key_track.at(insert_index + 1) :
|
|
nullptr;
|
|
|
|
key->set_previous(previous);
|
|
key->set_next(next);
|
|
|
|
if (previous) {
|
|
previous->set_next(key);
|
|
}
|
|
|
|
if (next) {
|
|
next->set_previous(key);
|
|
}
|
|
}
|
|
|
|
void NodeInputImmediate::remove_keyframe(NodeKeyframe *key)
|
|
{
|
|
if (key->previous()) {
|
|
key->previous()->set_next(key->next());
|
|
}
|
|
|
|
if (key->next()) {
|
|
key->next()->set_previous(key->previous());
|
|
}
|
|
|
|
key->set_previous(nullptr);
|
|
key->set_next(nullptr);
|
|
|
|
keyframe_tracks_[key->track()].removeOne(key);
|
|
}
|
|
|
|
void NodeInputImmediate::delete_all_keyframes(QObject *parent)
|
|
{
|
|
for (NodeKeyframeTrack &track : keyframe_tracks_) {
|
|
// Iterate over a copy of the track, since the keyframes may be removed
|
|
// from it as we go
|
|
const NodeKeyframeTrack copy = track;
|
|
|
|
for (NodeKeyframe *key : copy) {
|
|
if (!dynamic_cast<Node *>(key->QObject::parent())) {
|
|
// Keyframe isn't parented to a node, so reparenting/deleting it
|
|
// won't remove it from the track automatically
|
|
remove_keyframe(key);
|
|
}
|
|
|
|
if (parent) {
|
|
key->setParent(parent);
|
|
} else {
|
|
delete key;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|