/*** Olive - Non-Linear Video Editor Copyright (C) 2021 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 "keyframeviewbase.h" #include #include #include #include "common/qtutils.h" #include "dialog/keyframeproperties/keyframeproperties.h" #include "keyframeviewundo.h" #include "node/node.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "widget/nodeparamview/nodeparamviewundo.h" namespace olive { #define super TimeBasedView KeyframeViewBase::KeyframeViewBase(QWidget *parent) : super(parent), dragging_bezier_point_(nullptr), currently_autoselecting_(false), dragging_(false), selection_manager_(this) { SetDefaultDragMode(RubberBandDrag); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &KeyframeViewBase::customContextMenuRequested, this, &KeyframeViewBase::ShowContextMenu); } void KeyframeViewBase::DeleteSelected() { MultiUndoCommand* command = new MultiUndoCommand(); foreach (NodeKeyframe *key, GetSelectedKeyframes()) { command->add_child(new NodeParamRemoveKeyframeCommand(key)); } Core::instance()->undo_stack()->pushIfHasChildren(command); } KeyframeViewBase::NodeConnections KeyframeViewBase::AddKeyframesOfNode(Node *n) { NodeConnections map; foreach (const QString& i, n->inputs()) { map.insert(i, AddKeyframesOfInput(n, i)); } return map; } KeyframeViewBase::InputConnections KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input) { InputConnections vec; 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()) { SelectKeyframe(key); } } } void KeyframeViewBase::DeselectAll() { selection_manager_.ClearSelection(); Redraw(); } void KeyframeViewBase::Clear() { if (!tracks_.isEmpty()) { qDeleteAll(tracks_); tracks_.clear(); Redraw(); } } void KeyframeViewBase::mousePressEvent(QMouseEvent *event) { NodeKeyframe *key_under_cursor = selection_manager_.MousePress(event); if (key_under_cursor) { AutoSelectKeyTimeNeighbors(); } BezierControlPointItem *bezier_under_cursor = dynamic_cast(itemAt(event->pos())); Redraw(); if (HandPress(event) || (!bezier_under_cursor && !key_under_cursor && PlayheadPress(event))) { return; } if (event->button() == Qt::LeftButton) { if (key_under_cursor || bezier_under_cursor) { dragging_ = true; drag_start_ = mapToScene(event->pos()); // Determine what type of item is under the cursor dragging_bezier_point_ = bezier_under_cursor; if (dragging_bezier_point_) { dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle(); dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode())); } else { selection_manager_.DragStart(key_under_cursor, event); } } } } void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) { if (HandMove(event) || PlayheadMove(event)) { return; } if (event->buttons() & Qt::LeftButton) { if (dragging_) { // Calculate cursor difference and scale it QPointF scene_pos = mapToScene(event->pos()); QPointF mouse_diff_scaled = GetScaledCursorPos(scene_pos - drag_start_); if (event->modifiers() & Qt::ShiftModifier) { // If holding shift, only move one axis mouse_diff_scaled.setY(0); } if (dragging_bezier_point_) { // Flip the mouse Y because bezier control points are drawn bottom to top, not top to bottom mouse_diff_scaled.setY(-mouse_diff_scaled.y()); QPointF new_bezier_pos = GenerateBezierControlPosition(dragging_bezier_point_->mode(), dragging_bezier_point_start_, mouse_diff_scaled); // If the user is NOT holding control, we set the other handle to the exact negative of this handle QPointF new_opposing_pos; NodeKeyframe::BezierType opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode()); if (!(event->modifiers() & Qt::ControlModifier)) { new_opposing_pos = GenerateBezierControlPosition(opposing_type, dragging_bezier_point_opposing_start_, -mouse_diff_scaled); } else { new_opposing_pos = dragging_bezier_point_opposing_start_; } dragging_bezier_point_->key()->set_bezier_control(dragging_bezier_point_->mode(), new_bezier_pos); dragging_bezier_point_->key()->set_bezier_control(opposing_type, new_opposing_pos); // Bezier control points are parented to keyframe items making their positions relative // to those items. We need to map them to the scene coordinates for this to work properly. QPointF bezier_pos = dragging_bezier_point_->pos() + dragging_bezier_point_->parentItem()->pos(); emit Dragged(qRound(bezier_pos.x()), qRound(bezier_pos.y())); } else if (selection_manager_.IsDragging()) { QString tip; /* // Validate movement - ensure no keyframe goes above its max point or below its min point FloatSlider::DisplayType display_type = FloatSlider::kNormal; if (IsYAxisEnabled()) { foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { NodeKeyframe *key = keypair.key; Node* node = key->parent(); const QString& input = key->input(); double new_val = keypair.value - mouse_diff_scaled.y(); double limited = new_val; if (node->HasInputProperty(input, QStringLiteral("min"))) { limited = qMax(limited, node->GetInputProperty(input, QStringLiteral("min")).toDouble()); } if (node->HasInputProperty(input, QStringLiteral("max"))) { limited = qMin(limited, node->GetInputProperty(input, QStringLiteral("max")).toDouble()); } if (limited != new_val) { mouse_diff_scaled.setY(keypair.value - limited); } } Node* initial_drag_input = initial_drag_item_->parent(); const QString& initial_drag_input_id = initial_drag_item_->input(); if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); } } foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { if (IsYAxisEnabled()) { key->set_value(keypair.value - mouse_diff_scaled.y()); } } if (IsYAxisEnabled()) { bool ok; double num_value = initial_drag_item_->value().toDouble(&ok); if (ok) { tip = QStringLiteral("%1\n"); tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); } } */ selection_manager_.DragMove(event, tip); Redraw(); emit Dragged(scene_pos.x(), scene_pos.y()); } } } } void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) { if (HandRelease(event) || PlayheadRelease(event)) { return; } if (event->button() == Qt::LeftButton) { if (dragging_) { if (dragging_bezier_point_) { MultiUndoCommand* command = new MultiUndoCommand(); // Create undo command with the current bezier point and the old one command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), dragging_bezier_point_->mode(), dragging_bezier_point_->key()->bezier_control(dragging_bezier_point_->mode()), dragging_bezier_point_start_)); if (!(event->modifiers() & Qt::ControlModifier)) { auto opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode()); command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), opposing_type, dragging_bezier_point_->key()->bezier_control(opposing_type), dragging_bezier_point_opposing_start_)); } dragging_bezier_point_ = nullptr; Core::instance()->undo_stack()->push(command); } else if (selection_manager_.IsDragging()) { MultiUndoCommand* command = new MultiUndoCommand(); selection_manager_.DragStop(command); /*if (IsYAxisEnabled()) { command->add_child(new NodeParamSetKeyframeValueCommand(item, item->value(), keypair.value)); }*/ Core::instance()->undo_stack()->push(command); } dragging_ = false; QToolTip::hideText(); } } } void KeyframeViewBase::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); painter->setPen(Qt::black); foreach (KeyframeViewInputConnection *track, tracks_) { foreach (NodeKeyframe *key, track->GetKeyframes()) { QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); key_rect.translate(GetKeyframeSceneX(key), mapFromGlobal(QPoint(0, track->GetKeyframeY())).y()); if (!rect.intersects(key_rect)) { continue; } if (IsKeyframeSelected(key)) { painter->setBrush(palette().highlight()); } else { painter->setBrush(track->GetBrush()); } selection_manager_.DeclareDrawnObject(key, key_rect); switch (key->type()) { 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; } } } super::drawForeground(painter, rect); } void KeyframeViewBase::ScaleChangedEvent(const double &scale) { super::ScaleChangedEvent(scale); Redraw(); } void KeyframeViewBase::TimeTargetChangedEvent(Node *target) { Redraw(); } void KeyframeViewBase::TimebaseChangedEvent(const rational &timebase) { super::TimebaseChangedEvent(timebase); selection_manager_.SetTimebase(timebase); } void KeyframeViewBase::ContextMenuEvent(Menu& m) { Q_UNUSED(m) } void KeyframeViewBase::SelectKeyframe(NodeKeyframe *key) { if (selection_manager_.Select(key)) { Redraw(); } } void KeyframeViewBase::DeselectKeyframe(NodeKeyframe *key) { if (selection_manager_.Deselect(key)) { Redraw(); } } rational KeyframeViewBase::GetAdjustedKeyframeTime(NodeKeyframe *key) { return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); } double KeyframeViewBase::GetKeyframeSceneX(NodeKeyframe *key) { return TimeToScene(GetAdjustedKeyframeTime(key)); } rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) { return rational::fromDouble(old_time.toDouble() + cursor_diff); } QPointF KeyframeViewBase::GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, const QPointF &start_point, const QPointF &scaled_cursor_diff) { QPointF new_bezier_pos = start_point; new_bezier_pos += scaled_cursor_diff; // LIMIT bezier handles from overlapping each other if (mode == NodeKeyframe::kInHandle) { if (new_bezier_pos.x() > 0) { new_bezier_pos.setX(0); } } else { if (new_bezier_pos.x() < 0) { new_bezier_pos.setX(0); } } return new_bezier_pos; } QPointF KeyframeViewBase::GetScaledCursorPos(const QPointF &cursor_pos) { return QPointF(cursor_pos.x() / GetScale(), cursor_pos.y() / GetYScale()); } void KeyframeViewBase::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().isEmpty()) { bool all_keys_are_same_type = true; NodeKeyframe::Type type = GetSelectedKeyframes().first()->type(); for (int 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::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(); AddSetScrollZoomsByDefaultActionToMenu(&m); m.addSeparator(); ContextMenuEvent(m); if (!GetSelectedKeyframes().isEmpty()) { m.addSeparator(); QAction* properties_action = m.addAction(tr("P&roperties")); connect(properties_action, &QAction::triggered, this, &KeyframeViewBase::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); } } } void KeyframeViewBase::ShowKeyframePropertiesDialog() { if (!GetSelectedKeyframes().isEmpty()) { KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this); kd.exec(); } } void KeyframeViewBase::AutoSelectKeyTimeNeighbors() { if (currently_autoselecting_ || IsYAxisEnabled()) { return; } // Prevents infinite loop currently_autoselecting_ = true; QVector copy = GetSelectedKeyframes(); foreach (NodeKeyframe *key, copy) { rational key_time = key->time(); QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key_time, key->element()); foreach (NodeKeyframe* k, keys) { if (k != key) { SelectKeyframe(k); } } } currently_autoselecting_ = false; } void KeyframeViewBase::Redraw() { viewport()->update(); } }