392 lines
11 KiB
C++
392 lines
11 KiB
C++
/***
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
#include "nodeparamview.h"
|
|
|
|
#include <QApplication>
|
|
#include <QScrollArea>
|
|
#include <QScrollBar>
|
|
#include <QSplitter>
|
|
|
|
#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<QMainWindow::DockOption>(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 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<Node *> &nodes)
|
|
{
|
|
active_nodes_.append(nodes);
|
|
|
|
bool changes_made = false;
|
|
|
|
foreach (Node* n, nodes) {
|
|
if (!pinned_nodes_.contains(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(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
|
|
connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
|
|
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged);
|
|
connect(item, &NodeParamViewItem::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked);
|
|
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);
|
|
|
|
// Set time target
|
|
item->SetTimeTarget(GetTimeTarget());
|
|
|
|
items_.insert(n, item);
|
|
param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item);
|
|
|
|
changes_made = true;
|
|
|
|
if (!focused_node_ && n->HasGizmos()) {
|
|
// We'll focus this node now
|
|
item->SetHighlighted(true);
|
|
focused_node_ = n;
|
|
emit FocusedNodeChanged(focused_node_);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changes_made) {
|
|
UpdateItemTime(GetTimestamp());
|
|
|
|
// Re-arrange keyframes
|
|
QueueKeyframePositionUpdate();
|
|
|
|
SignalNodeOrder();
|
|
}
|
|
}
|
|
|
|
void NodeParamView::DeselectNodes(const QVector<Node *> &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(ViewerOutput *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, "PlaceKeyframesOnView", Qt::QueuedConnection);
|
|
}
|
|
|
|
void NodeParamView::SignalNodeOrder()
|
|
{
|
|
// Sort by item Y (apparently there's no way in Qt to get the order of dock widgets)
|
|
QVector<Node*> nodes;
|
|
QVector<int> 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_ys.size(); i++) {
|
|
if (item_ys.at(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::RemoveNode(Node *n)
|
|
{
|
|
keyframe_view_->RemoveKeyframesOfNode(n);
|
|
|
|
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::PlaceKeyframesOnView()
|
|
{
|
|
foreach (NodeParamViewItem* item, items_) {
|
|
QMetaObject::invokeMethod(item, "SignalAllKeyframes", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
void NodeParamView::PinNode(bool pin)
|
|
{
|
|
NodeParamViewItem* item = static_cast<NodeParamViewItem*>(sender());
|
|
Node* node = item->GetNode();
|
|
|
|
if (pin) {
|
|
pinned_nodes_.append(node);
|
|
} else {
|
|
pinned_nodes_.removeOne(node);
|
|
|
|
if (!active_nodes_.contains(node)) {
|
|
RemoveNode(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeParamView::FocusChanged(QWidget* old, QWidget* now)
|
|
{
|
|
Q_UNUSED(old)
|
|
|
|
QObject* parent = now;
|
|
NodeParamViewItem* item;
|
|
|
|
while (parent) {
|
|
item = dynamic_cast<NodeParamViewItem*>(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();
|
|
}
|
|
}
|
|
|
|
}
|