/*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team 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 . ***/ #include "keyframeview.h" #include #include #include #include "common/qtutils.h" #include "dialog/keyframeproperties/keyframeproperties.h" #include "keyframeviewundo.h" #include "node/group/group.h" #include "node/node.h" #include "node/nodeundo.h" #include "node/project/serializer/serializer.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" namespace olive { #define super TimeBasedView KeyframeView::KeyframeView(QWidget *parent) : super(parent), selection_manager_(this), autoselect_siblings_(true), max_scroll_(0), first_chance_mouse_event_(false) { setAlignment(Qt::AlignLeft | Qt::AlignTop); SetDefaultDragMode(RubberBandDrag); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &KeyframeView::customContextMenuRequested, this, &KeyframeView::ShowContextMenu); } void KeyframeView::DeleteSelected() { if (!selection_manager_.IsDragging()) { MultiUndoCommand* command = new MultiUndoCommand(); foreach (NodeKeyframe *key, GetSelectedKeyframes()) { command->add_child(new NodeParamRemoveKeyframeCommand(key)); } Core::instance()->undo_stack()->push(command, tr("Deleted %1 Keyframe(s)").arg(GetSelectedKeyframes().size())); } } KeyframeView::NodeConnections KeyframeView::AddKeyframesOfNode(Node *n) { NodeConnections map; foreach (const QString& i, n->inputs()) { map.insert(i, AddKeyframesOfInput(n, i)); } return map; } KeyframeView::InputConnections KeyframeView::AddKeyframesOfInput(Node* on, const QString& oinput) { InputConnections vec; NodeInput resolved = NodeGroup::ResolveInput(NodeInput(on, oinput)); Node *n = resolved.node(); const QString &input = resolved.input(); if (n->IsInputKeyframable(input)) { int arr_sz = n->InputArraySize(input); vec.resize(arr_sz + 1); for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); ElementConnections vec(tracks.size()); for (int i=0; iGetKeyframes()) { selection_manager_.Deselect(key); } delete connection; Redraw(); emit SelectionChanged(); } } void KeyframeView::SelectAll() { foreach (KeyframeViewInputConnection *track, tracks_) { foreach (NodeKeyframe *key, track->GetKeyframes()) { SelectKeyframe(key); } } } void KeyframeView::DeselectAll() { selection_manager_.ClearSelection(); Redraw(); } void KeyframeView::Clear() { if (!tracks_.isEmpty()) { qDeleteAll(tracks_); tracks_.clear(); Redraw(); } selection_manager_.ClearSelection(); } void KeyframeView::SelectionManagerSelectEvent(void *obj) { if (autoselect_siblings_) { NodeKeyframe *key = static_cast(obj); QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element()); foreach (NodeKeyframe* k, keys) { if (k != key) { SelectKeyframe(k); } } } emit SelectionChanged(); } void KeyframeView::SelectionManagerDeselectEvent(void *obj) { if (autoselect_siblings_) { NodeKeyframe *key = static_cast(obj); QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element()); foreach (NodeKeyframe* k, keys) { if (k != key) { DeselectKeyframe(k); } } } emit SelectionChanged(); } bool KeyframeView::CopySelected(bool cut) { if (!selection_manager_.GetSelectedObjects().empty()) { ProjectSerializer::SaveData sdata(ProjectSerializer::kOnlyKeyframes); sdata.SetOnlySerializeKeyframes(selection_manager_.GetSelectedObjects()); ProjectSerializer::Copy(sdata); if (cut) { DeleteSelected(); } return true; } return false; } bool KeyframeView::Paste(std::function find_node_function) { if (!GetViewerNode()) { return false; } ProjectSerializer::Result res = ProjectSerializer::Paste(ProjectSerializer::kOnlyKeyframes); if (res == ProjectSerializer::kSuccess) { const ProjectSerializer::SerializedKeyframes &keys = res.GetLoadData().keyframes; MultiUndoCommand *command = new MultiUndoCommand(); rational min = RATIONAL_MAX; for (auto it=keys.cbegin(); it!=keys.cend(); it++) { for (NodeKeyframe *key : it.value()) { min = std::min(min, key->time()); } } min -= GetViewerNode()->GetPlayhead(); for (auto it=keys.cbegin(); it!=keys.cend(); it++) { const QString &paste_id = it.key(); // Find a node with this ID Node *node_with_id = find_node_function(paste_id); if (node_with_id) { for (NodeKeyframe *key : it.value()) { // Adjust sequence time to node's time rational t = key->time() - min; t = GetAdjustedTime(GetTimeTarget(), node_with_id, t, Node::kTransformTowardsInput); key->set_time(t); if (NodeKeyframe *existing = node_with_id->GetKeyframeAtTimeOnTrack(key->input(), key->time(), key->track(), key->element())) { command->add_child(new NodeParamRemoveKeyframeCommand(existing)); } command->add_child(new NodeParamInsertKeyframeCommand(node_with_id, key)); } } else { qDeleteAll(it.value()); } } Core::instance()->undo_stack()->push(command, tr("Pasted %1 Keyframe(s)").arg(keys.size())); return true; } return false; } void KeyframeView::CatchUpScrollEvent() { super::CatchUpScrollEvent(); this->selection_manager_.ForceDragUpdate(); } void KeyframeView::mousePressEvent(QMouseEvent *event) { NodeKeyframe *key_under_cursor = selection_manager_.GetObjectAtPoint(event->pos()); if (HandPress(event) || (!key_under_cursor && PlayheadPress(event))) { return; } // Do mouse press things if (FirstChanceMousePress(event)) { first_chance_mouse_event_ = true; } else if (NodeKeyframe *initial_key = selection_manager_.MousePress(event)) { selection_manager_.DragStart(initial_key, event, this); KeyframeDragStart(event); } else { selection_manager_.RubberBandStart(event); } // Update view Redraw(); } void KeyframeView::mouseMoveEvent(QMouseEvent *event) { if (HandMove(event) || PlayheadMove(event)) { return; } if (first_chance_mouse_event_) { FirstChanceMouseMove(event); } else if (selection_manager_.IsDragging()) { QString tip; KeyframeDragMove(event, tip); selection_manager_.DragMove(event->pos(), tip); } else if (selection_manager_.IsRubberBanding()) { selection_manager_.RubberBandMove(event->pos()); Redraw(); } if (event->buttons()) { // Signal cursor pos in case we should scroll to catch up to it emit Dragged(event->pos().x(), event->pos().y()); } } void KeyframeView::mouseReleaseEvent(QMouseEvent *event) { if (HandRelease(event) || PlayheadRelease(event)) { return; } if (first_chance_mouse_event_) { FirstChanceMouseRelease(event); first_chance_mouse_event_ = false; } else if (selection_manager_.IsDragging()) { MultiUndoCommand* command = new MultiUndoCommand(); selection_manager_.DragStop(command); KeyframeDragRelease(event, command); Core::instance()->undo_stack()->push(command, tr("Moved %1 Keyframe(s)").arg(selection_manager_.GetSelectedObjects().size())); } else if (selection_manager_.IsRubberBanding()) { selection_manager_.RubberBandStop(); Redraw(); emit SelectionChanged(); } emit Released(); } int BinarySearchFirstKeyframeAfterOrAt(const QVector &keys, const rational &time) { int low = 0; int high = keys.size()-1; while (low <= high) { int mid = low + (high-low)/2; NodeKeyframe *test_key = keys.at(mid); if (test_key->time() == time || (test_key->time() > time && (mid == 0 || keys.at(mid-1)->time() < time))) { return mid; } else if (test_key->time() < time) { low = mid + 1; } else { high = mid - 1; } } return keys.size(); } void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect) { int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); int key_rad = key_sz/2; selection_manager_.ClearDrawnObjects(); painter->setRenderHint(QPainter::Antialiasing); foreach (KeyframeViewInputConnection *track, tracks_) { const QVector &keys = track->GetKeyframes(); if (keys.isEmpty()) { continue; } if (!IsYAxisEnabled()) { // Filter out if the keyframes are offscreen Y qreal y = GetKeyframeSceneY(track, keys.first()); if (y + key_rad < rect.top() || y - key_rad >= rect.bottom()) { continue; } } // Find first keyframe to show with binary search rational left_time = GetUnadjustedKeyframeTime(keys.first(), SceneToTime(rect.left() - key_sz)); int using_index = BinarySearchFirstKeyframeAfterOrAt(keys, left_time); rational next_key = RATIONAL_MIN; NodeKeyframe::Type last_type = NodeKeyframe::kInvalid; for (int i=using_index; itime() < next_key && key->type() == last_type) { // This key will be drawn at exactly the same location as the last one and therefore // doesn't need to be drawn. See if the next one will be drawn. i++; if (i == keys.size()) { break; } key = keys.at(i); if (key->time() < next_key) { // Next key still won't be drawn, so we'll switch to a binary search i = BinarySearchFirstKeyframeAfterOrAt(keys, next_key); if (i == keys.size()) { break; } key = keys.at(i); } } QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); qreal key_x = GetKeyframeSceneX(key); key_rect.translate(key_x, GetKeyframeSceneY(track, key)); if (key_rect.left() >= rect.right()) { // Break after last keyframe break; } DrawKeyframe(painter, key, track, key_rect); next_key = GetUnadjustedKeyframeTime(key, SceneToTime(key_x + 1)); last_type = key->type(); } } super::drawForeground(painter, rect); } void KeyframeView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) { painter->setPen(Qt::black); if (IsKeyframeSelected(key)) { painter->setBrush(palette().highlight()); } else { painter->setBrush(track->GetBrush()); } selection_manager_.DeclareDrawnObject(key, key_rect); switch (key->type()) { case NodeKeyframe::kInvalid: break; case NodeKeyframe::kLinear: { QPointF points[] = { QPointF(key_rect.center().x(), key_rect.top()), QPointF(key_rect.right(), key_rect.center().y()), QPointF(key_rect.center().x(), key_rect.bottom()), QPointF(key_rect.left(), key_rect.center().y()) }; painter->drawPolygon(points, 4); break; } case NodeKeyframe::kBezier: painter->drawEllipse(key_rect); break; case NodeKeyframe::kHold: painter->drawRect(key_rect); break; } } void KeyframeView::ScaleChangedEvent(const double &scale) { super::ScaleChangedEvent(scale); Redraw(); } void KeyframeView::TimeTargetChangedEvent(ViewerOutput *v) { Redraw(); } void KeyframeView::TimebaseChangedEvent(const rational &timebase) { super::TimebaseChangedEvent(timebase); selection_manager_.SetTimebase(timebase); } void KeyframeView::ContextMenuEvent(Menu& m) { Q_UNUSED(m) } void KeyframeView::SelectKeyframe(NodeKeyframe *key) { if (selection_manager_.Select(key)) { Redraw(); emit SelectionChanged(); } } void KeyframeView::DeselectKeyframe(NodeKeyframe *key) { if (selection_manager_.Deselect(key)) { Redraw(); emit SelectionChanged(); } } rational KeyframeView::GetUnadjustedKeyframeTime(NodeKeyframe *key, const rational &time) { return GetAdjustedTime(GetTimeTarget(), key->parent(), time, Node::kTransformTowardsInput); } rational KeyframeView::GetAdjustedKeyframeTime(NodeKeyframe *key) { return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), Node::kTransformTowardsOutput); } double KeyframeView::GetKeyframeSceneX(NodeKeyframe *key) { return TimeToScene(GetAdjustedKeyframeTime(key)); } qreal KeyframeView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) { return mapFromGlobal(QPoint(0, track->GetKeyframeY())).y(); } void KeyframeView::SceneRectUpdateEvent(QRectF &rect) { rect.setY(0); rect.setHeight(max_scroll_); } rational KeyframeView::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) { return rational::fromDouble(old_time.toDouble() + cursor_diff); } void KeyframeView::ShowContextMenu() { Menu m; MenuShared::instance()->AddItemsForEditMenu(&m, false); QAction* linear_key_action = nullptr; QAction* bezier_key_action = nullptr; QAction* hold_key_action = nullptr; if (!GetSelectedKeyframes().empty()) { bool all_keys_are_same_type = true; NodeKeyframe::Type type = GetSelectedKeyframes().front()->type(); for (size_t i=1;itype() != prev_item->type()) { all_keys_are_same_type = false; break; } } m.addSeparator(); linear_key_action = m.addAction(tr("Linear")); bezier_key_action = m.addAction(tr("Bezier")); hold_key_action = m.addAction(tr("Hold")); if (all_keys_are_same_type) { switch (type) { case NodeKeyframe::kInvalid: break; case NodeKeyframe::kLinear: linear_key_action->setChecked(true); break; case NodeKeyframe::kBezier: bezier_key_action->setChecked(true); break; case NodeKeyframe::kHold: hold_key_action->setChecked(true); break; } } } m.addSeparator(); ContextMenuEvent(m); if (!GetSelectedKeyframes().empty()) { m.addSeparator(); QAction* properties_action = m.addAction(tr("P&roperties")); connect(properties_action, &QAction::triggered, this, &KeyframeView::ShowKeyframePropertiesDialog); } QAction* selected = m.exec(QCursor::pos()); // Process keyframe type changes if (selected) { if (selected == linear_key_action || selected == bezier_key_action || selected == hold_key_action) { NodeKeyframe::Type new_type; if (selected == hold_key_action) { new_type = NodeKeyframe::kHold; } else if (selected == bezier_key_action) { new_type = NodeKeyframe::kBezier; } else { new_type = NodeKeyframe::kLinear; } MultiUndoCommand* command = new MultiUndoCommand(); foreach (NodeKeyframe* item, GetSelectedKeyframes()) { command->add_child(new KeyframeSetTypeCommand(item, new_type)); } Core::instance()->undo_stack()->push(command, tr("Set Type of %1 Keyframe(s)").arg(GetSelectedKeyframes().size())); } } } void KeyframeView::ShowKeyframePropertiesDialog() { if (!GetSelectedKeyframes().empty()) { KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this); kd.exec(); } } void KeyframeView::UpdateRubberBandForScroll() { this->selection_manager_.ForceDragUpdate(); } void KeyframeView::Redraw() { viewport()->update(); } }