/*** Olive - Non-Linear Video Editor Copyright (C) 2020 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 "nodeparamview.h" #include #include #include #include #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" namespace olive { NodeParamView::NodeParamView(QWidget *parent) : TimeBasedWidget(true, false, parent), last_scroll_val_(0), focused_node_(nullptr) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); QSplitter* splitter = new QSplitter(Qt::Horizontal); layout->addWidget(splitter); // Set up scroll area for params QScrollArea* scroll_area = new QScrollArea(); scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll_area->setWidgetResizable(true); splitter->addWidget(scroll_area); // Param widget param_widget_container_ = new NodeParamViewParamContainer(); connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar); scroll_area->setWidget(param_widget_container_); param_widget_area_ = new QMainWindow(); // Disable dock widgets from tabbing and disable glitchy animations param_widget_area_->setDockOptions(static_cast(0)); // HACK: Hide the main window separators (unfortunately the cursors still appear) param_widget_area_->setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}")); QVBoxLayout* param_widget_container_layout = new QVBoxLayout(param_widget_container_); QMargins param_widget_margin = param_widget_container_layout->contentsMargins(); param_widget_margin.setTop(ruler()->height()); param_widget_container_layout->setContentsMargins(param_widget_margin); param_widget_container_layout->setSpacing(0); param_widget_container_layout->addWidget(param_widget_area_); param_widget_container_layout->addStretch(INT_MAX); // Set up keyframe view QWidget* keyframe_area = new QWidget(); QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area); keyframe_area_layout->setSpacing(0); keyframe_area_layout->setMargin(0); // Create ruler object keyframe_area_layout->addWidget(ruler()); // Create keyframe view keyframe_view_ = new KeyframeView(); keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ConnectTimelineView(keyframe_view_); keyframe_area_layout->addWidget(keyframe_view_); // Connect ruler and keyframe view together connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime); connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime); connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTimestamp); connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged); // Connect keyframe view scaling to this connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale); splitter->addWidget(keyframe_area); // Set both widgets to 50/50 splitter->setSizes({INT_MAX, INT_MAX}); // Disable collapsing param view (but collapsing keyframe view is permitted) splitter->setCollapsible(0, false); // Create global vertical scrollbar on the right vertical_scrollbar_ = new QScrollBar(); vertical_scrollbar_->setMaximum(0); layout->addWidget(vertical_scrollbar_); // Connect scrollbars together connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); connect(vertical_scrollbar_, &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of keyframe_view_->setHorizontalScrollBar(scrollbar()); keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); // Set a default scale - FIXME: Hardcoded SetScale(120); SetMaximumScale(TimeBasedView::kMaximumScale); // Pickup on widget focus changes connect(qApp, &QApplication::focusChanged, this, &NodeParamView::FocusChanged); } void NodeParamView::SelectNodes(const QVector &nodes) { active_nodes_.append(nodes); bool changes_made = false; foreach (Node* n, nodes) { if (!pinned_nodes_.contains(n)) { AddNode(n); changes_made = true; } } if (changes_made) { UpdateItemTime(GetTimestamp()); // Re-arrange keyframes QueueKeyframePositionUpdate(); SignalNodeOrder(); } } void NodeParamView::DeselectNodes(const QVector &nodes) { // Remove item from map and delete the widget bool changes_made = false; foreach (Node* n, nodes) { if (!pinned_nodes_.contains(n)) { // Store expanded state node_expanded_state_.insert(n, items_.value(n)->IsExpanded()); // Remove all keyframes from this node RemoveNode(n); changes_made = true; } active_nodes_.removeOne(n); } if (changes_made) { // Re-arrange keyframes QueueKeyframePositionUpdate(); SignalNodeOrder(); } } void NodeParamView::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); vertical_scrollbar_->setPageStep(vertical_scrollbar_->height()); UpdateGlobalScrollBar(); } void NodeParamView::ScaleChangedEvent(const double &scale) { TimeBasedWidget::ScaleChangedEvent(scale); keyframe_view_->SetScale(scale); } void NodeParamView::TimebaseChangedEvent(const rational &timebase) { TimeBasedWidget::TimebaseChangedEvent(timebase); keyframe_view_->SetTimebase(timebase); UpdateItemTime(GetTimestamp()); } void NodeParamView::TimeChangedEvent(const int64_t ×tamp) { TimeBasedWidget::TimeChangedEvent(timestamp); keyframe_view_->SetTime(timestamp); UpdateItemTime(timestamp); } void NodeParamView::ConnectedNodeChanged(Sequence *n) { // Set viewer as a time target keyframe_view_->SetTimeTarget(n); foreach (NodeParamViewItem* item, items_) { item->SetTimeTarget(n); } } Node *NodeParamView::GetTimeTarget() const { return keyframe_view_->GetTimeTarget(); } void NodeParamView::DeleteSelected() { keyframe_view_->DeleteSelected(); } void NodeParamView::UpdateItemTime(const int64_t ×tamp) { rational time = Timecode::timestamp_to_time(timestamp, timebase()); foreach (NodeParamViewItem* item, items_) { item->SetTime(time); } } void NodeParamView::QueueKeyframePositionUpdate() { QMetaObject::invokeMethod(this, "UpdateElementY", Qt::QueuedConnection); } void NodeParamView::SignalNodeOrder() { // Sort by item Y (apparently there's no way in Qt to get the order of dock widgets) QVector nodes; QVector item_ys; for (auto it=items_.cbegin(); it!=items_.cend(); it++) { int item_y = it.value()->pos().y(); bool inserted = false; for (int i=0; i item_y) { item_ys.insert(i, item_y); nodes.insert(i, it.key()); inserted = true; break; } } if (!inserted) { item_ys.append(item_y); nodes.append(it.key()); } } emit NodeOrderChanged(nodes); } void NodeParamView::AddNode(Node *n) { NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_); item->setAllowedAreas(Qt::LeftDockWidgetArea); item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); item->SetExpanded(node_expanded_state_.value(n, true)); connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder); connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode); connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::UpdateElementY); connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::UpdateElementY); // Set time target item->SetTimeTarget(GetTimeTarget()); items_.insert(n, item); param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item); if (!focused_node_ && n->HasGizmos()) { // We'll focus this node now item->SetHighlighted(true); focused_node_ = n; emit FocusedNodeChanged(focused_node_); } keyframe_view_->AddKeyframesOfNode(n); } void NodeParamView::RemoveNode(Node *n) { keyframe_view_->RemoveKeyframesOfNode(n); disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); delete items_.take(n); if (focused_node_ == n) { focused_node_ = nullptr; emit FocusedNodeChanged(nullptr); } } void NodeParamView::ItemRequestedTimeChanged(const rational &time) { SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase())); } void NodeParamView::UpdateGlobalScrollBar() { int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height(); keyframe_view_->SetMaxScroll(height_offscreen); vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height()); } void NodeParamView::PinNode(bool pin) { NodeParamViewItem* item = static_cast(sender()); Node* node = item->GetNode(); if (pin) { pinned_nodes_.append(node); } else { pinned_nodes_.removeOne(node); if (!active_nodes_.contains(node)) { RemoveNode(node); SignalNodeOrder(); } } } void NodeParamView::FocusChanged(QWidget* old, QWidget* now) { Q_UNUSED(old) QObject* parent = now; NodeParamViewItem* item; while (parent) { item = dynamic_cast(parent); if (item) { // Found it! if (item->GetNode() != focused_node_) { if (focused_node_) { // De-focus current node items_.value(focused_node_)->SetHighlighted(false); } focused_node_ = item->GetNode(); item->SetHighlighted(true); emit FocusedNodeChanged(focused_node_); } break; } parent = parent->parent(); } } void NodeParamView::KeyframeViewDragged(int x, int y) { Q_UNUSED(y) QMetaObject::invokeMethod(this, "CatchUpScrollToPoint", Qt::QueuedConnection, Q_ARG(int, x)); } void NodeParamView::UpdateElementY() { for (auto it=items_.cbegin(); it!=items_.cend(); it++) { foreach (const QString& input, it.key()->inputs()) { int arr_sz = it.key()->InputArraySize(input); for (int i=-1; iGetElementY(ic); keyframe_view_->SetElementY(ic, y); } } } } }