Files
oak-editor/app/widget/nodeparamview/nodeparamviewitem.cpp
T
itsmattkc 81c1922911 use strings to connect nodes
This makes the node system somewhat more high-level with the intent of making
working with them far more flexible and stable. By making the architecture more
abstracted, it becomes far less rigid which should allow us to do even more
with it and make it much less crash prone.
2021-02-09 15:48:37 +11:00

515 lines
15 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 "nodeparamviewitem.h"
#include <QCheckBox>
#include <QDebug>
#include <QEvent>
#include <QPainter>
#include "common/qtutils.h"
#include "core.h"
#include "nodeparamviewundo.h"
#include "project/item/sequence/sequence.h"
namespace olive {
const int NodeParamViewItemBody::kKeyControlColumn = 10;
const int NodeParamViewItemBody::kArrayInsertColumn = kKeyControlColumn-1;
const int NodeParamViewItemBody::kArrayRemoveColumn = kArrayInsertColumn-1;
NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
QDockWidget(parent),
node_(node),
highlighted_(false)
{
// Create title bar widget
title_bar_ = new NodeParamViewItemTitleBar(this);
// Add title bar to widget
this->setTitleBarWidget(title_bar_);
// Create and add contents widget
body_ = new NodeParamViewItemBody(node_);
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode);
connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime);
connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged);
connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded);
connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled);
this->setWidget(body_);
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
setFocusPolicy(Qt::ClickFocus);
Retranslate();
}
void NodeParamViewItem::SetTimeTarget(Node *target)
{
body_->SetTimeTarget(target);
}
void NodeParamViewItem::SetTime(const rational &time)
{
time_ = time;
body_->SetTime(time_);
}
Node *NodeParamViewItem::GetNode() const
{
return node_;
}
void NodeParamViewItem::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
QWidget::changeEvent(e);
}
void NodeParamViewItem::paintEvent(QPaintEvent *event)
{
QDockWidget::paintEvent(event);
// Draw border if focused
if (highlighted_) {
QPainter p(this);
p.setBrush(Qt::NoBrush);
p.setPen(palette().highlight().color());
p.drawRect(rect().adjusted(0, 0, -1, -1));
}
}
void NodeParamViewItem::Retranslate()
{
node_->Retranslate();
if (node_->GetLabel().isEmpty()) {
title_bar_->SetText(node_->Name());
} else {
title_bar_->SetText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name()));
}
body_->Retranslate();
}
void NodeParamViewItem::SetExpanded(bool e)
{
body_->setVisible(e);
title_bar_->SetExpanded(e);
emit ExpandedChanged(e);
}
bool NodeParamViewItem::IsExpanded() const
{
return body_->isVisible();
}
int NodeParamViewItem::GetElementY(const NodeInput &c) const
{
if (IsExpanded()) {
return body_->GetElementY(c);
} else {
// Not expanded, put keyframes at the titlebar Y
return mapToGlobal(title_bar_->rect().center()).y();
}
}
void NodeParamViewItem::ToggleExpanded()
{
SetExpanded(!IsExpanded());
}
NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
QWidget(parent),
draw_border_(true)
{
QHBoxLayout* layout = new QHBoxLayout(this);
collapse_btn_ = new CollapseButton();
connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged);
layout->addWidget(collapse_btn_);
lbl_ = new QLabel();
layout->addWidget(lbl_);
// Place next buttons on the far side
layout->addStretch();
QPushButton* pin_btn = new QPushButton(QStringLiteral("P"));
pin_btn->setCheckable(true);
pin_btn->setFixedSize(pin_btn->sizeHint().height(), pin_btn->sizeHint().height());
layout->addWidget(pin_btn);
connect(pin_btn, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
}
void NodeParamViewItemTitleBar::SetExpanded(bool e)
{
draw_border_ = e;
collapse_btn_->setChecked(e);
update();
}
void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (draw_border_) {
QPainter p(this);
// Draw bottom border using text color
int bottom = height() - 1;
p.setPen(palette().text().color());
p.drawLine(0, bottom, width(), bottom);
}
}
void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{
QWidget::mouseDoubleClickEvent(event);
collapse_btn_->click();
}
NodeParamViewItemBody::NodeParamViewItemBody(Node* node, QWidget *parent) :
QWidget(parent)
{
QGridLayout* root_layout = new QGridLayout(this);
int insert_row = 0;
// Create widgets all root level components
foreach (const QString& input, node->inputs()) {
CreateWidgets(root_layout, node, input, -1, insert_row);
insert_row++;
if (node->InputIsArray(input)) {
// Insert here
QWidget* array_widget = new QWidget();
QGridLayout* array_layout = new QGridLayout(array_widget);
array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0);
root_layout->addWidget(array_widget, insert_row, 1, 1, 10);
int arr_sz = node->InputArraySize(input);
for (int j=0; j<arr_sz; j++) {
CreateWidgets(array_layout, node, input, j, j);
}
// Add one last add button for appending to the array
NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd);
connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked);
array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn);
array_widget->setVisible(false);
array_ui_.insert({node, input}, {array_widget, arr_sz, append_btn});
insert_row++;
}
}
connect(node, &Node::InputArraySizeChanged, this, &NodeParamViewItemBody::InputArraySizeChanged);
connect(node, &Node::InputConnected, this, &NodeParamViewItemBody::EdgeChanged);
connect(node, &Node::InputDisconnected, this, &NodeParamViewItemBody::EdgeChanged);
}
void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const QString &input, int element, int row)
{
NodeInput input_ref(node, input, element);
InputUI ui_objects;
// Add descriptor label
ui_objects.main_label = new QLabel();
// Label always goes into column 1 (array collapse button goes into 0 if applicable)
layout->addWidget(ui_objects.main_label, row, 1);
if (node->InputIsArray(input)) {
if (element == -1) {
// Create a collapse toggle for expanding/collapsing the array
CollapseButton* array_collapse_btn = new CollapseButton();
// Default to collapsed
array_collapse_btn->setChecked(false);
// Collapse button always goes into column 0
layout->addWidget(array_collapse_btn, row, 0);
// Connect signal to show/hide array params when toggled
connect(array_collapse_btn, &CollapseButton::toggled, this, &NodeParamViewItemBody::ArrayCollapseBtnPressed);
array_collapse_buttons_.insert({node, input}, array_collapse_btn);
} else {
NodeParamViewArrayButton* insert_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd);
NodeParamViewArrayButton* remove_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kRemove);
layout->addWidget(insert_element_btn, row, kArrayInsertColumn);
layout->addWidget(remove_element_btn, row, kArrayRemoveColumn);
ui_objects.array_insert_btn = insert_element_btn;
ui_objects.array_remove_btn = remove_element_btn;
connect(insert_element_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayInsertClicked);
connect(remove_element_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayRemoveClicked);
}
}
// Create a widget/input bridge for this input
ui_objects.widget_bridge = new NodeParamViewWidgetBridge(NodeInput(node, input, element), this);
connect(ui_objects.widget_bridge, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked, this, &NodeParamViewItemBody::ToggleArrayExpanded);
// 0 is for the array collapse button, 1 is for the main label, widgets start at 2
const int widget_start = 2;
// Add widgets for this parameter to the layout
for (int i=0; i<ui_objects.widget_bridge->widgets().size(); i++) {
QWidget* w = ui_objects.widget_bridge->widgets().at(i);
layout->addWidget(w, row, i+widget_start);
}
if (node->IsInputConnectable(input)) {
// Create clickable label used when an input is connected
ui_objects.connected_label = new NodeParamViewConnectedLabel(input_ref);
connect(ui_objects.connected_label, &NodeParamViewConnectedLabel::RequestSelectNode, this, &NodeParamViewItemBody::RequestSelectNode);
layout->addWidget(ui_objects.connected_label, row, widget_start);
}
// Add keyframe control to this layout if parameter is keyframable
if (node->IsInputKeyframable(input)) {
ui_objects.key_control = new NodeParamViewKeyframeControl();
ui_objects.key_control->SetInput(input_ref);
layout->addWidget(ui_objects.key_control, row, kKeyControlColumn);
connect(ui_objects.key_control, &NodeParamViewKeyframeControl::RequestSetTime, this, &NodeParamViewItemBody::RequestSetTime);
}
input_ui_map_.insert(input_ref, ui_objects);
if (node->IsInputConnectable(input)) {
UpdateUIForEdgeConnection(input_ref);
}
}
void NodeParamViewItemBody::SetTimeTarget(Node *target)
{
foreach (const InputUI& ui_obj, input_ui_map_) {
// Only keyframable inputs have a key control widget
if (ui_obj.key_control) {
ui_obj.key_control->SetTimeTarget(target);
}
ui_obj.widget_bridge->SetTimeTarget(target);
}
}
void NodeParamViewItemBody::SetTime(const rational &time)
{
foreach (const InputUI& ui_obj, input_ui_map_) {
// Only keyframable inputs have a key control widget
if (ui_obj.key_control) {
ui_obj.key_control->SetTime(time);
}
ui_obj.widget_bridge->SetTime(time);
}
}
void NodeParamViewItemBody::Retranslate()
{
for (auto i=input_ui_map_.begin(); i!=input_ui_map_.end(); i++) {
const NodeInput& ic = i.key();
if (ic.IsArray() && ic.element() >= 0) {
// Make the label the array index
i.value().main_label->setText(tr("%n:", nullptr, ic.element()));
} else {
// Set to the input's name
i.value().main_label->setText(tr("%1:").arg(ic.name()));
}
}
}
int NodeParamViewItemBody::GetElementY(NodeInput c) const
{
if (c.IsArray() && !array_ui_.value(c.input_pair()).widget->isVisible()) {
// Array is collapsed, so we'll return the Y of its root
c.set_element(-1);
}
// Find its row in the parameters
QLabel* lbl = input_ui_map_.value(c).main_label;
// Find label's Y position
QPoint lbl_center = lbl->rect().center();
// Find global position
lbl_center = lbl->mapToGlobal(lbl_center);
// Return Y
return lbl_center.y();
}
void NodeParamViewItemBody::EdgeChanged(const NodeOutput& output, const NodeInput& input)
{
Q_UNUSED(output)
UpdateUIForEdgeConnection(input);
}
void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input)
{
// Show/hide bridge widgets
const InputUI& ui_objects = input_ui_map_[input];
foreach (QWidget* w, ui_objects.widget_bridge->widgets()) {
w->setVisible(!input.IsConnected());
}
// Show/hide connection label
ui_objects.connected_label->setVisible(input.IsConnected());
}
void NodeParamViewItemBody::ArrayCollapseBtnPressed(bool checked)
{
const NodeInputPair& input = array_collapse_buttons_.key(static_cast<CollapseButton*>(sender()));
array_ui_.value(input).widget->setVisible(checked);
emit ArrayExpandedChanged(checked);
}
void NodeParamViewItemBody::InputArraySizeChanged(const QString& input, int size)
{
Node* node = static_cast<Node*>(sender());
ArrayUI& array_ui = array_ui_[{node, input}];
if (size != array_ui.count) {
QGridLayout* grid = static_cast<QGridLayout*>(array_ui.widget->layout());
if (array_ui.count < size) {
// Our UI count is smaller than the size, create more
grid->addWidget(array_ui.append_btn, size, kArrayInsertColumn);
for (int i=array_ui.count; i<size; i++) {
CreateWidgets(grid, node, input, i, i);
}
} else {
for (int i=array_ui.count-1; i>=size; i--) {
// Our UI count is larger than the size, delete
InputUI input_ui = input_ui_map_.take({node, input, i});
delete input_ui.main_label;
qDeleteAll(input_ui.widget_bridge->widgets());
delete input_ui.widget_bridge;
delete input_ui.connected_label;
delete input_ui.key_control;
delete input_ui.array_insert_btn;
delete input_ui.array_remove_btn;
}
grid->addWidget(array_ui.append_btn, size, kArrayInsertColumn);
}
array_ui.count = size;
}
Retranslate();
}
void NodeParamViewItemBody::ArrayAppendClicked()
{
for (auto it=array_ui_.cbegin(); it!=array_ui_.cend(); it++) {
if (it.value().append_btn == sender()) {
it.key().node->InputArrayAppend(it.key().input, true);
break;
}
}
}
void NodeParamViewItemBody::ArrayInsertClicked()
{
for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) {
if (it.value().array_insert_btn == sender()) {
// Found our input and element
const NodeInput& ic = it.key();
ic.node()->InputArrayInsert(ic.input(), ic.element(), true);
break;
}
}
}
void NodeParamViewItemBody::ArrayRemoveClicked()
{
for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) {
if (it.value().array_remove_btn == sender()) {
// Found our input and element
const NodeInput& ic = it.key();
ic.node()->InputArrayRemove(ic.input(), ic.element(), true);
break;
}
}
}
void NodeParamViewItemBody::ToggleArrayExpanded()
{
NodeParamViewWidgetBridge* bridge = static_cast<NodeParamViewWidgetBridge*>(sender());
for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) {
if (it.value().widget_bridge == bridge) {
CollapseButton* b = array_collapse_buttons_.value(it.key().input_pair());
b->setChecked(!b->isChecked());
return;
}
}
}
NodeParamViewItemBody::InputUI::InputUI() :
main_label(nullptr),
widget_bridge(nullptr),
connected_label(nullptr),
key_control(nullptr),
array_insert_btn(nullptr),
array_remove_btn(nullptr)
{
}
}