/*** 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 "curveview.h" #include #include #include #include #include #include "common/qtutils.h" namespace olive { #define super KeyframeViewBase CurveView::CurveView(QWidget *parent) : KeyframeViewBase(parent) { setAlignment(Qt::AlignLeft | Qt::AlignVCenter); setDragMode(RubberBandDrag); setViewportUpdateMode(FullViewportUpdate); SetYAxisEnabled(true); text_padding_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("i")); minimum_grid_space_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("00000")); connect(scene(), &QGraphicsScene::selectionChanged, this, &CurveView::SelectionChanged); } void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) { if (connected_inputs_.contains(ref)) { // Input wasn't connected, do nothing return; } // Add keyframes from track KeyframeViewInputConnection *track_con = AddKeyframesOfTrack(ref); track_con->SetBrush(keyframe_colors_.value(ref)); track_connections_.insert(ref, track_con); // Append to the list connected_inputs_.append(ref); } void CurveView::DisconnectInput(const NodeKeyframeTrackReference& ref) { if (!connected_inputs_.contains(ref)) { // Input wasn't connected, do nothing return; } // Remove keyframes belonging to this element and track RemoveKeyframesOfTrack(track_connections_.take(ref)); // Remove from the list connected_inputs_.removeOne(ref); } void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref) { DeselectAll(); foreach (KeyframeViewInputConnection *con, track_connections_) { foreach (NodeKeyframe *key, con->GetKeyframes()) { SelectKeyframe(key); } } } void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref) { ZoomToFitInternal(track_connections_.value(ref)->GetKeyframes()); } void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color) { // Insert color into hashmap keyframe_colors_.insert(ref, color); // Update all keyframes track_connections_.value(ref)->SetBrush(color); } void CurveView::drawBackground(QPainter *painter, const QRectF &rect) { if (timebase().isNull()) { return; } painter->setRenderHint(QPainter::Antialiasing); QVector lines; double x_interval = timebase().flipped().toDouble(); double y_interval = 100.0; int x_grid_interval, y_grid_interval; painter->setPen(QPen(palette().window().color(), 1)); do { x_grid_interval = qRound(x_interval * GetScale() * timebase_dbl()); x_interval *= 2.0; } while (x_grid_interval < minimum_grid_space_); do { y_grid_interval = qRound(y_interval * GetYScale()); y_interval *= 2.0; } while (y_grid_interval < minimum_grid_space_); int x_start = qCeil(rect.left() / x_grid_interval) * x_grid_interval; int y_start = qCeil(rect.top() / y_grid_interval) * y_grid_interval; QPointF scene_bottom_left = mapToScene(QPoint(0, qRound(rect.height()))); QPointF scene_top_right = mapToScene(QPoint(qRound(rect.width()), 0)); // Add vertical lines for (int i=x_start;i(i) / GetScale() / timebase_dbl()); painter->drawText(i + text_padding_, qRound(scene_bottom_left.y()) - text_padding_, QString::number(value)); lines.append(QLine(i, qRound(rect.top()), i, qRound(rect.bottom()))); } // Add horizontal lines for (int i=y_start;i(i) / GetYScale()); painter->drawText(qRound(scene_bottom_left.x()) + text_padding_, i - text_padding_, QString::number(-value)); lines.append(QLine(qRound(rect.left()), i, qRound(rect.right()), i)); } // Draw grid painter->drawLines(lines); // Draw keyframe lines foreach (const NodeKeyframeTrackReference& ref, connected_inputs_) { Node* node = ref.input().node(); const QString& input = ref.input().input(); if (node->IsInputKeyframing(input, ref.input().element())) { const QVector& tracks = node->GetKeyframeTracks(ref.input()); const NodeKeyframeTrack& track = tracks.at(ref.track()); if (!track.isEmpty()) { painter->setPen(QPen(keyframe_colors_.value(ref), qMax(1, fontMetrics().height() / 4))); // Create a path QPainterPath path; // Draw straight line leading to first keyframe QPointF first_key_pos = GetKeyframePosition(track.first()); path.moveTo(QPointF(scene_bottom_left.x(), first_key_pos.y())); path.lineTo(first_key_pos); // Draw lines between each keyframe for (int i=1;itype() == NodeKeyframe::kHold) { // Draw a hold keyframe (basically a right angle) path.lineTo(after_pos.x(), before_pos.y()); path.lineTo(after_pos.x(), after_pos.y()); } else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { // Draw a cubic bezier // Cubic beziers have two control points, so we can just use both QPointF before_control_point = before_pos + ScalePoint(before->valid_bezier_control_out()); QPointF after_control_point = after_pos + ScalePoint(after->valid_bezier_control_in()); path.cubicTo(before_control_point, after_control_point, after_pos); } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { // Draw a quadratic bezier // Quadratic beziers have a single control point, we just have to determine which it is QPointF key_anchor; QPointF control_point; if (before->type() == NodeKeyframe::kBezier) { key_anchor = before_pos; control_point = before->valid_bezier_control_out(); } else { key_anchor = after_pos; control_point = after->valid_bezier_control_in(); } // Scale control point control_point = key_anchor + ScalePoint(control_point); // Create the path from both keyframes path.quadTo(control_point, after_pos); } else { // Linear to linear path.lineTo(after_pos); } } // Draw straight line leading from end keyframe QPointF last_key_pos = GetKeyframePosition(track.last()); path.lineTo(QPointF(scene_top_right.x(), last_key_pos.y())); painter->drawPath(path); } } } // Draw bezier control point lines /*if (!bezier_control_points_.isEmpty()) { painter->setPen(QPen(palette().text().color(), 1)); QVector bezier_lines; foreach (BezierControlPointItem* item, bezier_control_points_) { // All BezierControlPointItems should be children of a KeyframeViewItem KeyframeViewItem* par = static_cast(item->parentItem()); bezier_lines.append(QLineF(par->pos(), par->pos() + item->pos())); } painter->drawLines(bezier_lines); }*/ } void CurveView::ScaleChangedEvent(const double& scale) { KeyframeViewBase::ScaleChangedEvent(scale); foreach (BezierControlPointItem* item, bezier_control_points_) { item->SetXScale(scale); } } void CurveView::VerticalScaleChangedEvent(double scale) { Q_UNUSED(scale) foreach (BezierControlPointItem* item, bezier_control_points_) { item->SetYScale(scale); } viewport()->update(); } void CurveView::ContextMenuEvent(Menu &m) { // View settings QAction* zoom_fit_action = m.addAction(tr("Zoom to Fit")); connect(zoom_fit_action, &QAction::triggered, this, &CurveView::ZoomToFit); QAction* zoom_fit_selected_action = m.addAction(tr("Zoom to Fit Selected")); connect(zoom_fit_selected_action, &QAction::triggered, this, &CurveView::ZoomToFitSelected); QAction* reset_zoom_action = m.addAction(tr("Reset Zoom")); connect(reset_zoom_action, &QAction::triggered, this, &CurveView::ResetZoom); } void CurveView::SceneRectUpdateEvent(QRectF &r) { r.setTop(r.top() - this->height()); r.setBottom(r.bottom() + this->height()); } void CurveView::ZoomToFitInternal(const QVector &keys) { if (keys.isEmpty()) { // Prevent scaling to DBL_MIN/DBL_MAX return; } rational min_time = RATIONAL_MAX; rational max_time = RATIONAL_MIN; double min_val = DBL_MAX; double max_val = DBL_MIN; foreach (NodeKeyframe* key, keys) { rational transformed_time = GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); min_time = qMin(transformed_time, min_time); max_time = qMax(transformed_time, max_time); min_val = qMin(key->value().toDouble(), min_val); max_val = qMax(key->value().toDouble(), max_val); } double time_range = max_time.toDouble() - min_time.toDouble(); double new_x_scale = CalculateScaleFromDimensions(this->width(), time_range); double new_y_scale = CalculateScaleFromDimensions(this->height(), max_val - min_val); emit ScaleChanged(new_x_scale); SetYScale(new_y_scale); QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, TimeToScene(min_time) - CalculatePaddingFromDimensionScale(this->width()))); QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, GetItemYFromKeyframeValue(max_val) - CalculatePaddingFromDimensionScale(this->height()))); } qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key) { return GetItemYFromKeyframeValue(key->value().toDouble()); } qreal CurveView::GetItemYFromKeyframeValue(double value) { return -value * GetYScale(); } QPointF CurveView::ScalePoint(const QPointF &point) { // Flips Y coordinate because curves are drawn bottom to top return QPointF(point.x() * GetScale(), - point.y() * GetYScale()); } void CurveView::CreateBezierControlPoints(NodeKeyframe* item) { qDebug() << "STUB!"; /*BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item, NodeKeyframe::kInHandle, item); bezier_in_pt->SetXScale(GetScale()); bezier_in_pt->SetYScale(GetYScale()); bezier_control_points_.append(bezier_in_pt); connect(bezier_in_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(item, NodeKeyframe::kOutHandle, item); bezier_out_pt->SetXScale(GetScale()); bezier_out_pt->SetYScale(GetYScale()); bezier_control_points_.append(bezier_out_pt); connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);*/ } QPointF CurveView::GetKeyframePosition(NodeKeyframe *key) { return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key)); } void CurveView::KeyframeValueChanged() { qDebug() << "STUB!"; /*NodeKeyframe* key = static_cast(sender()); KeyframeViewItem* item = item_map().value(key); SetItemYFromKeyframeValue(key, item);*/ } void CurveView::KeyframeTypeChanged() { qDebug() << "STUB!"; /*NodeKeyframe* key = static_cast(sender()); KeyframeViewItem* item = item_map().value(key); if (item->isSelected()) { item->setSelected(false); item->setSelected(true); }*/ } void CurveView::SelectionChanged() { qDebug() << "STUB!"; /* // Clear current bezier handles while (!bezier_control_points_.isEmpty()) { delete bezier_control_points_.first(); } QList selected = scene()->selectedItems(); foreach (QGraphicsItem* item, selected) { KeyframeViewItem* this_item = static_cast(item); if (this_item->key()->type() == NodeKeyframe::kBezier) { CreateBezierControlPoints(this_item); } } */ } void CurveView::BezierControlPointDestroyed() { BezierControlPointItem* item = static_cast(sender()); bezier_control_points_.removeOne(item); } void CurveView::ZoomToFit() { QVector keys; foreach (KeyframeViewInputConnection *con, track_connections_) { foreach (NodeKeyframe *k, con->GetKeyframes()) { if (!keys.contains(k)) { keys.append(k); } } } ZoomToFitInternal(keys); } void CurveView::ZoomToFitSelected() { ZoomToFitInternal(GetSelectedKeyframes()); } void CurveView::ResetZoom() { emit ScaleChanged(1.0); SetYScale(1.0); } }