Merge branch 'node-view-show-ctx'

This commit is contained in:
itsmattkc
2022-01-02 12:15:55 -08:00
152 changed files with 7409 additions and 5465 deletions
+3
View File
@@ -55,6 +55,9 @@ else()
endif()
set(OLIVE_DEFINITIONS -DQT_DEPRECATED_WARNINGS)
if (WIN32)
list(APPEND OLIVE_DEFINITIONS -DUNICODE -D_UNICODE)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
+4
View File
@@ -58,6 +58,10 @@ double Bezier::CalculateTFromX(bool cubic, double x, double a, double b, double
double top = 1.0;
while (true) {
if (bottom == top) {
return bottom;
}
double mid = (bottom + top) * 0.5;
double test = cubic ? CubicTtoY(a, b, c, d, mid) : QuadraticTtoY(a, b, c, mid);
+53 -31
View File
@@ -24,37 +24,10 @@
#include "node/factory.h"
#include "widget/nodeparamview/nodeparamviewundo.h"
#include "widget/nodeview/nodeviewundo.h"
//#include "widget/timelinewidget/undo/timelineundogeneral.h"
namespace olive {
void XMLConnectNodes(const XMLNodeData &xml_node_data, uint version, MultiUndoCommand *command)
{
foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) {
Node *out = xml_node_data.node_ptrs.value(con.output_node);
if (out) {
// Use output param as hint tag since we grandfathered those in
Node::ValueHint hint(con.output_param);
if (command) {
command->add_child(new NodeEdgeAddCommand(out, con.input));
if (version < 210907) {
/// Deprecated: backwards compatibility only
command->add_child(new NodeSetValueHintCommand(con.input, hint));
}
} else {
Node::ConnectEdge(out, con.input);
if (version < 210907) {
/// Deprecated: backwards compatibility only
con.input.node()->SetValueHintForInput(con.input.input(), hint, con.input.element());
}
}
}
}
}
bool XMLReadNextStartElement(QXmlStreamReader *reader)
{
QXmlStreamReader::TokenType token;
@@ -71,10 +44,59 @@ bool XMLReadNextStartElement(QXmlStreamReader *reader)
return false;
}
void XMLLinkBlocks(const XMLNodeData &xml_node_data)
void XMLNodeData::PostConnect(uint version, MultiUndoCommand *command) const
{
foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) {
Block::Link(l.block, static_cast<Block*>(xml_node_data.node_ptrs.value(l.link)));
foreach (const XMLNodeData::SerializedConnection& con, desired_connections) {
if (Node *out = node_ptrs.value(con.output_node)) {
// Use output param as hint tag since we grandfathered those in
Node::ValueHint hint(con.output_param);
if (command) {
command->add_child(new NodeEdgeAddCommand(out, con.input));
} else {
Node::ConnectEdge(out, con.input);
}
if (version < 210907) {
/// Deprecated: backwards compatibility only
if (command) {
command->add_child(new NodeSetValueHintCommand(con.input, hint));
} else {
con.input.node()->SetValueHintForInput(con.input.input(), hint, con.input.element());
}
}
}
}
foreach (const XMLNodeData::BlockLink& l, block_links) {
Node *a = l.block;
Node *b = node_ptrs.value(l.link);
if (command) {
command->add_child(new NodeLinkCommand(a, b, true));
} else {
Node::Link(a, b);
}
}
foreach (const XMLNodeData::GroupLink &l, group_input_links) {
if (Node *input_node = node_ptrs.value(l.input_node)) {
NodeInput resolved(input_node, l.input_id, l.input_element);
if (command) {
command->add_child(new NodeGroupAddInputPassthrough(l.group, resolved));
} else {
l.group->AddInputPassthrough(resolved);
}
}
}
for (auto it=group_output_links.cbegin(); it!=group_output_links.cend(); it++) {
if (Node *output_node = node_ptrs.value(it.value())) {
if (command) {
command->add_child(new NodeGroupSetOutputPassthrough(it.key(), output_node));
} else {
it.key()->SetOutputPassthrough(output_node);
}
}
}
}
+12 -4
View File
@@ -31,6 +31,7 @@ namespace olive {
class Block;
class Node;
class NodeInput;
class NodeGroup;
#define XMLAttributeLoop(reader, item) \
foreach (const QXmlStreamAttribute& item, reader->attributes())
@@ -49,14 +50,23 @@ struct XMLNodeData {
quintptr link;
};
struct GroupLink {
NodeGroup *group;
quintptr input_node;
QString input_id;
int input_element;
};
QHash<quintptr, Node*> node_ptrs;
QList<SerializedConnection> desired_connections;
QList<BlockLink> block_links;
QVector<GroupLink> group_input_links;
QHash<NodeGroup*, quintptr> group_output_links;
void PostConnect(uint version, MultiUndoCommand *command = nullptr) const;
};
void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCommand *command = nullptr);
/**
* @brief Workaround for QXmlStreamReader::readNextStartElement not detecting the end of a document
*
@@ -68,8 +78,6 @@ void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCo
*/
bool XMLReadNextStartElement(QXmlStreamReader* reader);
void XMLLinkBlocks(const XMLNodeData& xml_node_data);
}
#endif // XMLREADLOOP_H
+13 -5
View File
@@ -75,7 +75,7 @@
namespace olive {
Core* Core::instance_ = nullptr;
const uint Core::kProjectVersion = 210907;
const uint Core::kProjectVersion = 211228;
Core::Core(const CoreParams& params) :
main_window_(nullptr),
@@ -434,7 +434,7 @@ void Core::CreateNewSequence()
command->add_child(new NodeAddCommand(active_project, new_sequence));
command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence));
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false));
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, Node::Position()));
// Create and connect default nodes to new sequence
new_sequence->add_default_nodes(command);
@@ -1354,10 +1354,10 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref
Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value;
}
void Core::LabelNodes(const QVector<Node *> &nodes)
bool Core::LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent)
{
if (nodes.isEmpty()) {
return;
return false;
}
bool ok;
@@ -1386,8 +1386,16 @@ void Core::LabelNodes(const QVector<Node *> &nodes)
rename_command->AddNode(n, s);
}
undo_stack_.push(rename_command);
if (parent) {
parent->add_child(rename_command);
} else {
undo_stack_.push(rename_command);
}
return true;
}
return false;
}
Sequence *Core::CreateNewSequenceForProject(Project* project) const
+1 -1
View File
@@ -253,7 +253,7 @@ public:
/**
* @brief Show a dialog to the user to rename a set of nodes
*/
void LabelNodes(const QVector<Node *> &nodes);
bool LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent = nullptr);
/**
* @brief Create a new sequence named appropriately for the active project
+1 -1
View File
@@ -21,9 +21,9 @@ add_subdirectory(color)
add_subdirectory(configbase)
add_subdirectory(diskcache)
add_subdirectory(export)
add_subdirectory(footageproperties)
add_subdirectory(footagerelink)
add_subdirectory(keyframeproperties)
add_subdirectory(nodeproperties)
add_subdirectory(preferences)
add_subdirectory(progress)
add_subdirectory(rendercancel)
@@ -1,5 +1,5 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2021 Olive Team
# 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
@@ -14,9 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(streamproperties)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
dialog/nodeproperties/nodepropertiesdialog.cpp
dialog/nodeproperties/nodepropertiesdialog.h
dialog/footageproperties/footageproperties.cpp
dialog/footageproperties/footageproperties.h
PARENT_SCOPE
)
@@ -0,0 +1,251 @@
/***
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 "footageproperties.h"
#include <QGridLayout>
#include <QLabel>
#include <QComboBox>
#include <QLineEdit>
#include <QDialogButtonBox>
#include <QTreeWidgetItem>
#include <QGroupBox>
#include <QListWidget>
#include <QCheckBox>
#include <QSpinBox>
#include "core.h"
#include "streamproperties/audiostreamproperties.h"
#include "streamproperties/videostreamproperties.h"
#include "widget/nodeview/nodeviewundo.h"
namespace olive {
FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *footage) :
QDialog(parent),
footage_(footage)
{
QGridLayout* layout = new QGridLayout(this);
setWindowTitle(tr("\"%1\" Properties").arg(footage_->GetLabelOrName()));
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
int row = 0;
layout->addWidget(new QLabel(tr("Name:")), row, 0);
footage_name_field_ = new QLineEdit(footage_->GetLabel());
layout->addWidget(footage_name_field_, row, 1);
row++;
layout->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2);
row++;
track_list = new QListWidget();
layout->addWidget(track_list, row, 0, 1, 2);
row++;
stacked_widget_ = new QStackedWidget();
layout->addWidget(stacked_widget_, row, 0, 1, 2);
int first_usable_stream = -1;
for (int i=0; i<footage_->GetTotalStreamCount(); i++) {
Track::Reference reference = footage_->GetReferenceFromRealIndex(i);
QString description;
bool is_enabled = false;
switch (reference.type()) {
case Track::kVideo:
{
stacked_widget_->addWidget(new VideoStreamProperties(footage_, reference.index()));
VideoParams vp = footage_->GetVideoParams(reference.index());
is_enabled = vp.enabled();
description = tr("%1x%2 %3 FPS").arg(QString::number(vp.width()), QString::number(vp.height()), QString::number(vp.frame_rate().toDouble()));
break;
}
case Track::kAudio:
{
stacked_widget_->addWidget(new AudioStreamProperties(footage_, reference.index()));
AudioParams ap = footage_->GetAudioParams(reference.index());
is_enabled = ap.enabled();
description = tr("%1 Hz %2 channels").arg(QString::number(ap.sample_rate()), QString::number(ap.channel_count()));
break;
}
default:
stacked_widget_->addWidget(new StreamProperties());
description = tr("Unknown");
break;
}
QListWidgetItem* item = new QListWidgetItem(description, track_list);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(is_enabled ? Qt::Checked : Qt::Unchecked);
track_list->addItem(item);
if (first_usable_stream == -1
&& (reference.type() == Track::kVideo
|| reference.type() == Track::kAudio)) {
first_usable_stream = i;
}
}
row++;
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttons->setCenterButtons(true);
layout->addWidget(buttons, row, 0, 1, 2);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(track_list, &QListWidget::currentRowChanged, stacked_widget_, &QStackedWidget::setCurrentIndex);
// Auto-select first item that actually has properties
if (first_usable_stream >= 0) {
track_list->setCurrentRow(first_usable_stream);
}
track_list->setFocus();
}
void FootagePropertiesDialog::accept()
{
// Perform sanity check on all pages
for (int i=0;i<stacked_widget_->count();i++) {
if (!static_cast<StreamProperties*>(stacked_widget_->widget(i))->SanityCheck()) {
// Switch to the failed panel in question
stacked_widget_->setCurrentIndex(i);
// Do nothing (it's up to the property panel itself to throw the error message)
return;
}
}
MultiUndoCommand* command = new MultiUndoCommand();
if (footage_->GetLabel() != footage_name_field_->text()) {
NodeRenameCommand *nrc = new NodeRenameCommand();
nrc->AddNode(footage_, footage_name_field_->text());
command->add_child(nrc);
}
for (int i=0; i<footage_->GetTotalStreamCount(); i++) {
Track::Reference reference = footage_->GetReferenceFromRealIndex(i);
bool new_stream_enabled = (track_list->item(i)->checkState() == Qt::Checked);
bool old_stream_enabled = new_stream_enabled;
switch (reference.type()) {
case Track::kVideo:
old_stream_enabled = footage_->GetVideoParams(reference.index()).enabled();
break;
case Track::kAudio:
old_stream_enabled = footage_->GetAudioParams(reference.index()).enabled();
break;
case Track::kSubtitle:
case Track::kNone:
case Track::kCount:
break;
}
if (old_stream_enabled != new_stream_enabled) {
command->add_child(new StreamEnableChangeCommand(footage_,
reference.type(),
reference.index(),
new_stream_enabled));
}
}
for (int i=0;i<stacked_widget_->count();i++) {
static_cast<StreamProperties*>(stacked_widget_->widget(i))->Accept(command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
QDialog::accept();
}
FootagePropertiesDialog::StreamEnableChangeCommand::StreamEnableChangeCommand(Footage *footage, Track::Type type, int index_in_type, bool enabled) :
footage_(footage),
type_(type),
index_(index_in_type),
new_enabled_(enabled)
{
}
Project *FootagePropertiesDialog::StreamEnableChangeCommand::GetRelevantProject() const
{
return footage_->project();
}
void FootagePropertiesDialog::StreamEnableChangeCommand::redo()
{
switch (type_) {
case Track::kVideo:
{
VideoParams vp = footage_->GetVideoParams(index_);
old_enabled_ = vp.enabled();
vp.set_enabled(new_enabled_);
footage_->SetVideoParams(vp, index_);
break;
}
case Track::kAudio:
{
AudioParams ap = footage_->GetAudioParams(index_);
old_enabled_ = ap.enabled();
ap.set_enabled(new_enabled_);
footage_->SetAudioParams(ap, index_);
break;
}
case Track::kSubtitle:
case Track::kNone:
case Track::kCount:
break;
}
}
void FootagePropertiesDialog::StreamEnableChangeCommand::undo()
{
switch (type_) {
case Track::kVideo:
{
VideoParams vp = footage_->GetVideoParams(index_);
vp.set_enabled(old_enabled_);
footage_->SetVideoParams(vp, index_);
break;
}
case Track::kAudio:
{
AudioParams ap = footage_->GetAudioParams(index_);
ap.set_enabled(old_enabled_);
footage_->SetAudioParams(ap, index_);
break;
}
case Track::kSubtitle:
case Track::kNone:
case Track::kCount:
break;
}
}
}
@@ -0,0 +1,120 @@
/***
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/>.
***/
#ifndef MEDIAPROPERTIESDIALOG_H
#define MEDIAPROPERTIESDIALOG_H
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QDoubleSpinBox>
#include <QLineEdit>
#include <QListWidget>
#include <QStackedWidget>
#include "node/project/footage/footage.h"
#include "undo/undocommand.h"
namespace olive {
/**
* @brief The MediaPropertiesDialog class
*
* A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given
* a valid Media object.
*/
class FootagePropertiesDialog : public QDialog {
Q_OBJECT
public:
/**
* @brief MediaPropertiesDialog Constructor
*
* @param parent
*
* QWidget parent. Usually MainWindow or Project panel.
*
* @param i
*
* Media object to set properties for.
*/
FootagePropertiesDialog(QWidget *parent, Footage* footage);
private:
class StreamEnableChangeCommand : public UndoCommand {
public:
StreamEnableChangeCommand(Footage *footage,
Track::Type type,
int index_in_type,
bool enabled);
virtual Project* GetRelevantProject() const override;
virtual void redo() override;
virtual void undo() override;
private:
Footage *footage_;
Track::Type type_;
int index_;
bool old_enabled_;
bool new_enabled_;
};
/**
* @brief Stack of widgets that changes based on whether the stream is a video or audio stream
*/
QStackedWidget* stacked_widget_;
/**
* @brief ComboBox for interlacing setting
*/
QComboBox* interlacing_box;
/**
* @brief Media name text field
*/
QLineEdit* footage_name_field_;
/**
* @brief Internal pointer to Media object (set in constructor)
*/
Footage* footage_;
/**
* @brief A list widget for listing the tracks in Media
*/
QListWidget* track_list;
/**
* @brief Frame rate to conform to
*/
QDoubleSpinBox* conform_fr;
private slots:
/**
* @brief Overridden accept function for saving the properties back to the Media class
*/
void accept();
};
}
#endif // MEDIAPROPERTIESDIALOG_H
@@ -0,0 +1,26 @@
# 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
dialog/footageproperties/streamproperties/streamproperties.h
dialog/footageproperties/streamproperties/streamproperties.cpp
dialog/footageproperties/streamproperties/audiostreamproperties.h
dialog/footageproperties/streamproperties/audiostreamproperties.cpp
dialog/footageproperties/streamproperties/videostreamproperties.h
dialog/footageproperties/streamproperties/videostreamproperties.cpp
PARENT_SCOPE
)
@@ -0,0 +1,37 @@
/***
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 "audiostreamproperties.h"
namespace olive {
AudioStreamProperties::AudioStreamProperties(Footage *footage, int audio_index) :
footage_(footage),
audio_index_(audio_index)
{
}
void AudioStreamProperties::Accept(MultiUndoCommand*)
{
Q_UNUSED(footage_)
Q_UNUSED(audio_index_)
}
}
@@ -0,0 +1,45 @@
/***
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/>.
***/
#ifndef AUDIOSTREAMPROPERTIES_H
#define AUDIOSTREAMPROPERTIES_H
#include "node/project/footage/footage.h"
#include "streamproperties.h"
namespace olive {
class AudioStreamProperties : public StreamProperties
{
public:
AudioStreamProperties(Footage *footage, int audio_index);
virtual void Accept(MultiUndoCommand* parent) override;
private:
Footage *footage_;
int audio_index_;
};
}
#endif // AUDIOSTREAMPROPERTIES_H
@@ -0,0 +1,30 @@
/***
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 "streamproperties.h"
namespace olive {
StreamProperties::StreamProperties(QWidget *parent) :
QWidget(parent)
{
}
}
@@ -0,0 +1,44 @@
/***
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/>.
***/
#ifndef STREAMPROPERTIES_H
#define STREAMPROPERTIES_H
#include <QWidget>
#include "common/define.h"
#include "undo/undocommand.h"
namespace olive {
class StreamProperties : public QWidget
{
public:
StreamProperties(QWidget* parent = nullptr);
virtual void Accept(MultiUndoCommand*){}
virtual bool SanityCheck(){return true;}
};
}
#endif // STREAMPROPERTIES_H
@@ -0,0 +1,271 @@
/***
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 "videostreamproperties.h"
#include <QGridLayout>
#include <QGroupBox>
#include <QInputDialog>
#include <QLabel>
#include <QMessageBox>
#include "common/ocioutils.h"
#include "core.h"
#include "undo/undostack.h"
namespace olive {
VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index) :
footage_(footage),
video_index_(video_index),
video_premultiply_alpha_(nullptr)
{
QGridLayout* video_layout = new QGridLayout(this);
video_layout->setMargin(0);
int row = 0;
video_layout->addWidget(new QLabel(tr("Pixel Aspect:")), row, 0);
VideoParams vp = footage_->GetVideoParams(video_index_);
pixel_aspect_combo_ = new PixelAspectRatioComboBox();
pixel_aspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio());
video_layout->addWidget(pixel_aspect_combo_, row, 1);
row++;
video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
video_interlace_combo_ = new InterlacedComboBox();
video_interlace_combo_->SetInterlaceMode(vp.interlacing());
video_layout->addWidget(video_interlace_combo_, row, 1);
row++;
video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0);
video_color_space_ = new QComboBox();
OCIO::ConstConfigRcPtr config = footage_->project()->color_manager()->GetConfig();
int number_of_colorspaces = config->getNumColorSpaces();
video_color_space_->addItem(tr("Default (%1)").arg(footage_->project()->color_manager()->GetDefaultInputColorSpace()));
for (int i=0;i<number_of_colorspaces;i++) {
QString colorspace = config->getColorSpaceNameByIndex(i);
video_color_space_->addItem(colorspace);
}
video_color_space_->setCurrentText(vp.colorspace());
video_layout->addWidget(video_color_space_, row, 1);
if (vp.channel_count() == VideoParams::kRGBAChannelCount) {
row++;
video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha"));
video_premultiply_alpha_->setChecked(vp.premultiplied_alpha());
video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2);
}
row++;
if (vp.video_type() == VideoParams::kVideoTypeImageSequence) {
QGroupBox* imgseq_group = new QGroupBox(tr("Image Sequence"));
QGridLayout* imgseq_layout = new QGridLayout(imgseq_group);
int imgseq_row = 0;
imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0);
imgseq_start_time_ = new IntegerSlider();
imgseq_start_time_->SetMinimum(0);
imgseq_start_time_->SetValue(vp.start_time());
imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1);
imgseq_row++;
imgseq_layout->addWidget(new QLabel(tr("End Index:")), imgseq_row, 0);
imgseq_end_time_ = new IntegerSlider();
imgseq_end_time_->SetMinimum(0);
imgseq_end_time_->SetValue(vp.start_time() + vp.duration() - 1);
imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1);
imgseq_row++;
imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0);
imgseq_frame_rate_ = new FrameRateComboBox();
imgseq_frame_rate_->SetFrameRate(vp.frame_rate());
imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1);
video_layout->addWidget(imgseq_group, row, 0, 1, 2);
}
}
void VideoStreamProperties::Accept(MultiUndoCommand *parent)
{
QString set_colorspace;
if (video_color_space_->currentIndex() > 0) {
set_colorspace = video_color_space_->currentText();
}
VideoParams vp = footage_->GetVideoParams(video_index_);
if ((video_premultiply_alpha_ && video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha())
|| set_colorspace != vp.colorspace()
|| static_cast<VideoParams::Interlacing>(video_interlace_combo_->currentIndex()) != vp.interlacing()
|| pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio()) {
parent->add_child(new VideoStreamChangeCommand(footage_,
video_index_,
video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() : vp.premultiplied_alpha(),
set_colorspace,
static_cast<VideoParams::Interlacing>(video_interlace_combo_->currentIndex()),
pixel_aspect_combo_->GetPixelAspectRatio()));
}
if (vp.video_type() == VideoParams::kVideoTypeImageSequence) {
int64_t new_dur = imgseq_end_time_->GetValue() - imgseq_start_time_->GetValue() + 1;
if (vp.start_time() != imgseq_start_time_->GetValue()
|| vp.duration() != new_dur
|| vp.frame_rate() != imgseq_frame_rate_->GetFrameRate()) {
parent->add_child(new ImageSequenceChangeCommand(footage_,
video_index_,
imgseq_start_time_->GetValue(),
new_dur,
imgseq_frame_rate_->GetFrameRate()));
}
}
}
bool VideoStreamProperties::SanityCheck()
{
if (footage_->GetVideoParams(video_index_).video_type() == VideoParams::kVideoTypeImageSequence) {
if (imgseq_start_time_->GetValue() >= imgseq_end_time_->GetValue()) {
QMessageBox::critical(this,
tr("Invalid Configuration"),
tr("Image sequence end index must be a value higher than the start index."),
QMessageBox::Ok);
return false;
}
}
return true;
}
VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(Footage *footage,
int video_index,
bool premultiplied,
QString colorspace,
VideoParams::Interlacing interlacing,
const rational &pixel_ar) :
footage_(footage),
video_index_(video_index),
new_premultiplied_(premultiplied),
new_colorspace_(colorspace),
new_interlacing_(interlacing),
new_pixel_ar_(pixel_ar)
{
}
Project *VideoStreamProperties::VideoStreamChangeCommand::GetRelevantProject() const
{
return footage_->project();
}
void VideoStreamProperties::VideoStreamChangeCommand::redo()
{
VideoParams vp = footage_->GetVideoParams(video_index_);
old_premultiplied_ = vp.premultiplied_alpha();
old_colorspace_ = vp.colorspace();
old_interlacing_ = vp.interlacing();
old_pixel_ar_ = vp.pixel_aspect_ratio();
vp.set_premultiplied_alpha(new_premultiplied_);
vp.set_colorspace(new_colorspace_);
vp.set_interlacing(new_interlacing_);
vp.set_pixel_aspect_ratio(new_pixel_ar_);
footage_->SetVideoParams(vp, video_index_);
}
void VideoStreamProperties::VideoStreamChangeCommand::undo()
{
VideoParams vp = footage_->GetVideoParams(video_index_);
vp.set_premultiplied_alpha(old_premultiplied_);
vp.set_colorspace(old_colorspace_);
vp.set_interlacing(old_interlacing_);
vp.set_pixel_aspect_ratio(old_pixel_ar_);
footage_->SetVideoParams(vp, video_index_);
}
VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(Footage *footage, int video_index, int64_t start_index, int64_t duration, const rational &frame_rate) :
footage_(footage),
video_index_(video_index),
new_start_index_(start_index),
new_duration_(duration),
new_frame_rate_(frame_rate)
{
}
Project *VideoStreamProperties::ImageSequenceChangeCommand::GetRelevantProject() const
{
return footage_->project();
}
void VideoStreamProperties::ImageSequenceChangeCommand::redo()
{
VideoParams vp = footage_->GetVideoParams(video_index_);
old_start_index_ = vp.start_time();
vp.set_start_time(new_start_index_);
old_duration_ = vp.duration();
vp.set_duration(new_duration_);
old_frame_rate_ = vp.frame_rate();
vp.set_frame_rate(new_frame_rate_);
vp.set_time_base(new_frame_rate_.flipped());
footage_->SetVideoParams(vp, video_index_);
}
void VideoStreamProperties::ImageSequenceChangeCommand::undo()
{
VideoParams vp = footage_->GetVideoParams(video_index_);
vp.set_start_time(old_start_index_);
vp.set_duration(old_duration_);
vp.set_frame_rate(old_frame_rate_);
vp.set_time_base(old_frame_rate_.flipped());
footage_->SetVideoParams(vp, video_index_);
}
}
@@ -0,0 +1,146 @@
/***
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/>.
***/
#ifndef VIDEOSTREAMPROPERTIES_H
#define VIDEOSTREAMPROPERTIES_H
#include <QCheckBox>
#include <QComboBox>
#include "node/project/footage/footage.h"
#include "streamproperties.h"
#include "widget/slider/integerslider.h"
#include "widget/standardcombos/standardcombos.h"
namespace olive {
class VideoStreamProperties : public StreamProperties
{
Q_OBJECT
public:
VideoStreamProperties(Footage *footage, int video_index);
virtual void Accept(MultiUndoCommand *parent) override;
virtual bool SanityCheck() override;
private:
Footage *footage_;
int video_index_;
/**
* @brief Setting for associated/premultiplied alpha
*/
QCheckBox* video_premultiply_alpha_;
/**
* @brief Setting for this media's color space
*/
QComboBox* video_color_space_;
/**
* @brief Setting for video interlacing
*/
InterlacedComboBox* video_interlace_combo_;
/**
* @brief Sets the start index for image sequences
*/
IntegerSlider* imgseq_start_time_;
/**
* @brief Sets the end index for image sequences
*/
IntegerSlider* imgseq_end_time_;
/**
* @brief Sets the frame rate for image sequences
*/
FrameRateComboBox* imgseq_frame_rate_;
/**
* @brief Sets the pixel aspect ratio of the stream
*/
PixelAspectRatioComboBox* pixel_aspect_combo_;
class VideoStreamChangeCommand : public UndoCommand {
public:
VideoStreamChangeCommand(Footage *footage,
int video_index,
bool premultiplied,
QString colorspace,
VideoParams::Interlacing interlacing,
const rational& pixel_ar);
virtual Project* GetRelevantProject() const override;
virtual void redo() override;
virtual void undo() override;
private:
Footage *footage_;
int video_index_;
bool new_premultiplied_;
QString new_colorspace_;
VideoParams::Interlacing new_interlacing_;
rational new_pixel_ar_;
bool old_premultiplied_;
QString old_colorspace_;
VideoParams::Interlacing old_interlacing_;
rational old_pixel_ar_;
};
class ImageSequenceChangeCommand : public UndoCommand {
public:
ImageSequenceChangeCommand(Footage *footage,
int video_index,
int64_t start_index,
int64_t duration,
const rational& frame_rate);
virtual Project* GetRelevantProject() const override;
virtual void redo() override;
virtual void undo() override;
private:
Footage *footage_;
int video_index_;
int64_t new_start_index_;
int64_t old_start_index_;
int64_t new_duration_;
int64_t old_duration_;
rational new_frame_rate_;
rational old_frame_rate_;
};
};
}
#endif // VIDEOSTREAMPROPERTIES_H
@@ -1,74 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "nodepropertiesdialog.h"
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include "core.h"
#include "widget/nodeview/nodeviewundo.h"
namespace olive {
NodePropertiesDialog::NodePropertiesDialog(Node *node, const rational &timebase, QWidget *parent) :
QDialog(parent),
node_(node)
{
setWindowTitle(tr("Node Properties"));
QVBoxLayout *layout = new QVBoxLayout(this);
QHBoxLayout *label_layout = new QHBoxLayout();
label_layout->setMargin(0);
layout->addLayout(label_layout);
label_layout->addWidget(new QLabel(tr("Name:")));
label_edit_ = new QLineEdit();
label_edit_->setText(node->GetLabel());
label_layout->addWidget(label_edit_);
NodeParamViewItem *item = new NodeParamViewItem(node);
item->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
item->SetTimebase(timebase);
item->setTitleBarWidget(new QWidget());
layout->addWidget(item);
layout->addStretch();
QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(btns, &QDialogButtonBox::accepted, this, &NodePropertiesDialog::accept);
connect(btns, &QDialogButtonBox::rejected, this, &NodePropertiesDialog::reject);
layout->addWidget(btns);
}
void NodePropertiesDialog::accept()
{
if (label_edit_->text() != node_->GetLabel()) {
NodeRenameCommand* rename_command = new NodeRenameCommand();
rename_command->AddNode(node_, label_edit_->text());
Core::instance()->undo_stack()->push(rename_command);
}
QDialog::accept();
}
}
@@ -19,11 +19,28 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
// Set up video section
QGroupBox* video_group = new QGroupBox();
video_group->setTitle(tr("Video"));
QHBoxLayout* video_layout = new QHBoxLayout(video_group);
video_section_ = new VideoParamEdit();
video_section_->SetParameterMask(Sequence::kVideoParamEditMask);
connect(video_section_, &VideoParamEdit::Changed, this, &SequenceDialogParameterTab::UpdatePreviewResolutionLabel);
video_layout->addWidget(video_section_);
QGridLayout *video_layout = new QGridLayout(video_group);
video_layout->addWidget(new QLabel(tr("Width:")), row, 0);
width_slider_ = new IntegerSlider();
width_slider_->SetMinimum(0);
video_layout->addWidget(width_slider_, row, 1);
row++;
video_layout->addWidget(new QLabel(tr("Height:")), row, 0);
height_slider_ = new IntegerSlider();
height_slider_->SetMinimum(0);
video_layout->addWidget(height_slider_, row, 1);
row++;
video_layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0);
framerate_combo_ = new FrameRateComboBox();
video_layout->addWidget(framerate_combo_, row, 1);
row++;
video_layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0);
pixelaspect_combo_ = new PixelAspectRatioComboBox();
video_layout->addWidget(pixelaspect_combo_, row, 1);
row++;
video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
interlacing_combo_ = new InterlacedComboBox();
video_layout->addWidget(interlacing_combo_, row, 1);
layout->addWidget(video_group);
row = 0;
@@ -65,7 +82,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
// Set values based on input sequence
VideoParams vp = sequence->GetVideoParams();
AudioParams ap = sequence->GetAudioParams();
video_section_->SetVideoParams(vp);
width_slider_->SetValue(vp.width());
height_slider_->SetValue(vp.height());
framerate_combo_->SetFrameRate(vp.time_base().flipped());
pixelaspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio());
interlacing_combo_->SetInterlaceMode(vp.interlacing());
preview_resolution_field_->SetDivider(vp.divider());
preview_format_field_->SetPixelFormat(vp.format());
preview_autocache_field_->setChecked(sequence->GetVideoAutoCacheEnabled());
@@ -86,11 +107,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
void SequenceDialogParameterTab::PresetChanged(const SequencePreset &preset)
{
video_section_->SetWidth(preset.width());
video_section_->SetHeight(preset.height());
video_section_->SetFrameRate(preset.frame_rate());
video_section_->SetPixelAspectRatio(preset.pixel_aspect());
video_section_->SetInterlaceMode(preset.interlacing());
width_slider_->SetValue(preset.width());
height_slider_->SetValue(preset.height());
framerate_combo_->SetFrameRate(preset.frame_rate());
pixelaspect_combo_->SetPixelAspectRatio(preset.pixel_aspect());
interlacing_combo_->SetInterlaceMode(preset.interlacing());
audio_sample_rate_field_->SetSampleRate(preset.sample_rate());
audio_channels_field_->SetChannelLayout(preset.channel_layout());
preview_resolution_field_->SetDivider(preset.preview_divider());
@@ -115,8 +136,8 @@ void SequenceDialogParameterTab::SavePresetClicked()
void SequenceDialogParameterTab::UpdatePreviewResolutionLabel()
{
VideoParams test_param(video_section_->GetWidth(),
video_section_->GetHeight(),
VideoParams test_param(GetSelectedVideoWidth(),
GetSelectedVideoHeight(),
VideoParams::kFormatInvalid,
VideoParams::kInternalChannelCount,
rational(1),
@@ -1,6 +1,7 @@
#ifndef SEQUENCEDIALOGPARAMETERTAB_H
#define SEQUENCEDIALOGPARAMETERTAB_H
#include <QCheckBox>
#include <QComboBox>
#include <QList>
#include <QSpinBox>
@@ -9,7 +10,6 @@
#include "sequencepreset.h"
#include "widget/slider/integerslider.h"
#include "widget/standardcombos/standardcombos.h"
#include "widget/videoparamedit/videoparamedit.h"
namespace olive {
@@ -21,27 +21,27 @@ public:
int GetSelectedVideoWidth() const
{
return video_section_->GetWidth();
return width_slider_->GetValue();
}
int GetSelectedVideoHeight() const
{
return video_section_->GetHeight();
return height_slider_->GetValue();
}
rational GetSelectedVideoFrameRate() const
{
return video_section_->GetFrameRate();
return framerate_combo_->GetFrameRate();
}
rational GetSelectedVideoPixelAspect() const
{
return video_section_->GetPixelAspectRatio();
return pixelaspect_combo_->GetPixelAspectRatio();
}
VideoParams::Interlacing GetSelectedVideoInterlacingMode() const
{
return video_section_->GetInterlaceMode();
return interlacing_combo_->GetInterlaceMode();
}
int GetSelectedAudioSampleRate() const
@@ -76,7 +76,15 @@ signals:
void SaveParametersAsPreset(const SequencePreset& preset);
private:
VideoParamEdit* video_section_;
IntegerSlider *width_slider_;
IntegerSlider *height_slider_;
FrameRateComboBox *framerate_combo_;
PixelAspectRatioComboBox *pixelaspect_combo_;
InterlacedComboBox *interlacing_combo_;
SampleRateComboBox* audio_sample_rate_field_;
+1
View File
@@ -20,6 +20,7 @@ add_subdirectory(color)
add_subdirectory(distort)
add_subdirectory(filter)
add_subdirectory(generator)
add_subdirectory(group)
add_subdirectory(input)
add_subdirectory(math)
add_subdirectory(output)
+2
View File
@@ -47,6 +47,8 @@ Block::Block() :
IgnoreHashingFrom(kLengthInput);
AddInput(kEnabledInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
SetFlags(kDontShowInParamView);
}
QVector<Node::CategoryID> Block::Category() const
+1 -3
View File
@@ -233,13 +233,11 @@ void CropDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt
}
}
void CropDistortNode::GizmoRelease()
void CropDistortNode::GizmoRelease(MultiUndoCommand *command)
{
MultiUndoCommand *command = new MultiUndoCommand();
for (NodeInputDragger& i : gizmo_dragger_) {
i.End(command);
}
Core::instance()->undo_stack()->push(command);
gizmo_dragger_.clear();
gizmo_start_.clear();
+1 -1
View File
@@ -76,7 +76,7 @@ public:
virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override;
virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
virtual void GizmoRelease() override;
virtual void GizmoRelease(MultiUndoCommand *command) override;
static const QString kTextureInput;
static const QString kLeftInput;
@@ -301,13 +301,11 @@ void TransformDistortNode::GizmoMove(const QPointF &p, const rational &time, con
}
}
void TransformDistortNode::GizmoRelease()
void TransformDistortNode::GizmoRelease(MultiUndoCommand *command)
{
MultiUndoCommand *command = new MultiUndoCommand();
for (NodeInputDragger& i : gizmo_dragger_) {
i.End(command);
}
Core::instance()->undo_stack()->push(command);
gizmo_dragger_.clear();
gizmo_start_.clear();
@@ -78,7 +78,7 @@ public:
virtual bool GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) override;
virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
virtual void GizmoRelease() override;
virtual void GizmoRelease(MultiUndoCommand *command) override;
enum AutoScaleType {
kAutoScaleNone,
+11
View File
@@ -54,6 +54,7 @@
namespace olive {
QList<Node*> NodeFactory::library_;
QVector<int> NodeFactory::hidden_;
void NodeFactory::Initialize()
{
@@ -65,6 +66,9 @@ void NodeFactory::Initialize()
library_.append(created_node);
}
hidden_.append(kTextGeneratorLegacy);
hidden_.append(kGroupNode);
}
void NodeFactory::Destroy()
@@ -86,6 +90,11 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate
continue;
}
if (hidden_.contains(i)) {
// Skip this node
continue;
}
// Make sure nodes are up-to-date with the current translation
n->Retranslate();
@@ -241,6 +250,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new SubtitleBlock();
case kShapeGenerator:
return new ShapeNode();
case kGroupNode:
return new NodeGroup();
case kInternalNodeCount:
break;
+3
View File
@@ -61,6 +61,7 @@ public:
kTimeRemapNode,
kSubtitleBlock,
kShapeGenerator,
kGroupNode,
// Count value
kInternalNodeCount
@@ -87,6 +88,8 @@ public:
private:
static QList<Node*> library_;
static QVector<int> hidden_;
};
}
+1 -3
View File
@@ -297,13 +297,11 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt::
}
}
void ShapeNodeBase::GizmoRelease()
void ShapeNodeBase::GizmoRelease(MultiUndoCommand *command)
{
MultiUndoCommand *command = new MultiUndoCommand();
for (NodeInputDragger& i : gizmo_dragger_) {
i.End(command);
}
Core::instance()->undo_stack()->push(command);
gizmo_dragger_.clear();
}
+1 -1
View File
@@ -49,7 +49,7 @@ public:
virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override;
virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
virtual void GizmoRelease() override;
virtual void GizmoRelease(MultiUndoCommand *command) override;
private:
static QVector2D GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, int drag, QVector2D *pt);
+18 -40
View File
@@ -44,26 +44,12 @@ void NodeGraph::Clear()
}
}
qreal NodeGraph::GetNodeContextHeight(Node *context)
{
const PositionMap &map = position_map_.value(context);
qreal top = 0, bottom = 0;
foreach (const QPointF &pt, map) {
top = qMin(pt.y(), top);
bottom = qMax(pt.y(), bottom);
}
return bottom - top;
}
int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node) const
int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node, bool except_itself) const
{
int count = 0;
for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) {
if (it.value().contains(node)) {
foreach (Node *ctx, node_children_) {
if (ctx->ContextContainsNode(node) && (!except_itself || ctx != node)) {
count++;
}
}
@@ -71,18 +57,6 @@ int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node) const
return count;
}
bool NodeGraph::NodeOutputsToContext(Node *node) const
{
for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) {
const PositionMap &pm = it.value();
if (pm.contains(node) && node->OutputsTo(it.key(), true)) {
return true;
}
}
return false;
}
void NodeGraph::childEvent(QChildEvent *event)
{
super::childEvent(event);
@@ -100,6 +74,12 @@ void NodeGraph::childEvent(QChildEvent *event)
connect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged, Qt::DirectConnection);
connect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged, Qt::DirectConnection);
if (NodeGroup *group = dynamic_cast<NodeGroup*>(node)) {
connect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough, Qt::DirectConnection);
connect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough, Qt::DirectConnection);
connect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough, Qt::DirectConnection);
}
emit NodeAdded(node);
emit node->AddedToGraph(this);
@@ -113,21 +93,19 @@ void NodeGraph::childEvent(QChildEvent *event)
disconnect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged);
disconnect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged);
if (NodeGroup *group = dynamic_cast<NodeGroup*>(node)) {
disconnect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough);
disconnect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough);
disconnect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough);
}
emit NodeRemoved(node);
emit node->RemovedFromGraph(this);
for (auto it=position_map_.begin(); it!=position_map_.end(); it++) {
PositionMap &map = it.value();
for (auto jt=map.begin(); jt!=map.end(); ) {
if (jt.key() == node) {
jt = map.erase(jt);
emit NodePositionRemoved(node, it.key());
} else {
jt++;
}
}
// Remove from any contexts
foreach (Node *context, node_children_) {
context->RemoveNodeFromContext(node);
}
}
}
}
+6 -54
View File
@@ -21,6 +21,7 @@
#ifndef NODEGRAPH_H
#define NODEGRAPH_H
#include "node/group/group.h"
#include "node/node.h"
namespace olive {
@@ -63,54 +64,7 @@ public:
return default_nodes_;
}
bool NodeMapContainsNode(Node* node, Node* context) const
{
return position_map_.value(context).contains(node);
}
QPointF GetNodePosition(Node* node, Node* context)
{
return position_map_.value(context).value(node);
}
void SetNodePosition(Node* node, Node* context, const QPointF& pos)
{
position_map_[context].insert(node, pos);
emit NodePositionAdded(node, context, pos);
}
void RemoveNodePosition(Node* node, Node* context)
{
PositionMap& map = position_map_[context];
map.remove(node);
if (map.isEmpty()) {
position_map_.remove(context);
}
emit NodePositionRemoved(node, context);
}
bool ContextContainsNode(Node *node, Node *context)
{
return position_map_[context].contains(node);
}
qreal GetNodeContextHeight(Node *context);
using PositionMap = QHash<Node*, QPointF>;
const PositionMap &GetNodesForContext(Node *context)
{
return position_map_[context];
}
const QMap<Node *, PositionMap> &GetPositionMap() const
{
return position_map_;
}
int GetNumberOfContextsNodeIsIn(Node *node) const;
bool NodeOutputsToContext(Node *node) const;
int GetNumberOfContextsNodeIsIn(Node *node, bool except_itself = false) const;
signals:
/**
@@ -131,9 +85,11 @@ signals:
void InputValueHintChanged(const NodeInput& input);
void NodePositionAdded(Node *node, Node *relative, const QPointF &position);
void GroupAddedInputPassthrough(NodeGroup *group, const NodeInput &input);
void NodePositionRemoved(Node *node, Node *relative);
void GroupRemovedInputPassthrough(NodeGroup *group, const NodeInput &input);
void GroupChangedOutputPassthrough(NodeGroup *group, Node *output);
protected:
void AddDefaultNode(Node* n)
@@ -148,10 +104,6 @@ private:
QVector<Node*> default_nodes_;
QMap<Node *, PositionMap> position_map_;
PositionMap root_position_map_;
};
}
@@ -16,7 +16,7 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/videoparamedit/videoparamedit.cpp
widget/videoparamedit/videoparamedit.h
node/group/group.cpp
node/group/group.h
PARENT_SCOPE
)
+215
View File
@@ -0,0 +1,215 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "group.h"
#include "node/graph.h"
namespace olive {
#define super Node
NodeGroup::NodeGroup() :
output_passthrough_(nullptr)
{
}
QString NodeGroup::Name() const
{
return tr("Group");
}
QString NodeGroup::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.group");
}
QVector<Node::CategoryID> NodeGroup::Category() const
{
return {kCategoryGeneral};
}
QString NodeGroup::Description() const
{
return tr("A group of nodes that is represented as a single node.");
}
void NodeGroup::Retranslate()
{
for (auto it=GetContextPositions().cbegin(); it!=GetContextPositions().cend(); it++) {
it.key()->Retranslate();
}
}
void NodeGroup::AddInputPassthrough(const NodeInput &input)
{
Q_ASSERT(ContextContainsNode(input.node()));
for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
if (it.value() == input) {
// Already passing this input through
return;
}
}
// Add input
QString id = GetGroupInputIDFromInput(input);
AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags());
input_passthroughs_.insert(id, input);
emit InputPassthroughAdded(this, input);
}
void NodeGroup::RemoveInputPassthrough(const NodeInput &input)
{
for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
if (it.value() == input) {
RemoveInput(it.key());
input_passthroughs_.erase(it);
emit InputPassthroughRemoved(this, it.value());
break;
}
}
}
void NodeGroup::SetOutputPassthrough(Node *node)
{
Q_ASSERT(!node || ContextContainsNode(node));
output_passthrough_ = node;
emit OutputPassthroughChanged(this, output_passthrough_);
}
QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input)
{
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(input.node()->GetUUID().toByteArray());
hash.addData(input.input().toUtf8());
hash.addData((const char*) &input.element(), sizeof(input.element()));
return QString::fromLatin1(hash.result().toHex());
}
bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const
{
for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
if (it.value() == input) {
return true;
}
}
return false;
}
QString NodeGroup::GetInputName(const QString &id) const
{
return input_passthroughs_.value(id).name();
}
bool NodeGroup::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled)
{
if (reader->name() == QStringLiteral("inputpassthroughs")) {
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("inputpassthrough")) {
XMLNodeData::GroupLink link;
link.group = this;
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("node")) {
link.input_node = reader->readElementText().toULongLong();
} else if (reader->name() == QStringLiteral("input")) {
link.input_id = reader->readElementText();
} else if (reader->name() == QStringLiteral("element")) {
link.input_element = reader->readElementText().toInt();
} else {
reader->skipCurrentElement();
}
}
xml_node_data.group_input_links.append(link);
} else {
reader->skipCurrentElement();
}
}
return true;
} else if (reader->name() == QStringLiteral("outputpassthrough")) {
xml_node_data.group_output_links.insert(this, reader->readElementText().toULongLong());
return true;
} else {
return super::LoadCustom(reader, xml_node_data, version, cancelled);
}
}
void NodeGroup::SaveCustom(QXmlStreamWriter *writer) const
{
super::SaveCustom(writer);
writer->writeStartElement(QStringLiteral("inputpassthroughs"));
foreach (const NodeInput &ip, input_passthroughs_) {
writer->writeStartElement(QStringLiteral("inputpassthrough"));
writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast<quintptr>(ip.node())));
writer->writeTextElement(QStringLiteral("input"), ip.input());
writer->writeTextElement(QStringLiteral("element"), QString::number(ip.element()));
writer->writeEndElement(); // input
}
writer->writeEndElement(); // inputpassthroughs
writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast<quintptr>(output_passthrough_)));
}
void NodeGroupAddInputPassthrough::redo()
{
if (!group_->ContainsInputPassthrough(input_)) {
group_->AddInputPassthrough(input_);
actually_added_ = true;
} else {
actually_added_ = false;
}
}
void NodeGroupAddInputPassthrough::undo()
{
if (actually_added_) {
group_->RemoveInputPassthrough(input_);
}
}
void NodeGroupSetOutputPassthrough::redo()
{
old_output_ = group_->GetOutputPassthrough();
group_->SetOutputPassthrough(new_output_);
}
void NodeGroupSetOutputPassthrough::undo()
{
group_->SetOutputPassthrough(old_output_);
}
}
+141
View File
@@ -0,0 +1,141 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEGROUP_H
#define NODEGROUP_H
#include "node/node.h"
namespace olive {
class NodeGroup : public Node
{
Q_OBJECT
public:
NodeGroup();
NODE_DEFAULT_DESTRUCTOR(NodeGroup)
NODE_COPY_FUNCTION(NodeGroup)
virtual QString Name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
void AddInputPassthrough(const NodeInput &input);
void RemoveInputPassthrough(const NodeInput &input);
Node *GetOutputPassthrough() const
{
return output_passthrough_;
}
void SetOutputPassthrough(Node *node);
static QString GetGroupInputIDFromInput(const NodeInput &input);
const QHash<QString, NodeInput> &GetInputPassthroughs() const
{
return input_passthroughs_;
}
bool ContainsInputPassthrough(const NodeInput &input) const;
virtual QString GetInputName(const QString& id) const override;
signals:
void InputPassthroughAdded(NodeGroup *group, const NodeInput &input);
void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input);
void OutputPassthroughChanged(NodeGroup *group, Node *output);
protected:
virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) override;
virtual void SaveCustom(QXmlStreamWriter* writer) const override;
private:
QHash<QString, NodeInput> input_passthroughs_;
Node *output_passthrough_;
};
class NodeGroupAddInputPassthrough : public UndoCommand
{
public:
NodeGroupAddInputPassthrough(NodeGroup *group, const NodeInput &input) :
group_(group),
input_(input),
actually_added_(false)
{}
virtual Project * GetRelevantProject() const override
{
return group_->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
NodeGroup *group_;
NodeInput input_;
bool actually_added_;
};
class NodeGroupSetOutputPassthrough : public UndoCommand
{
public:
NodeGroupSetOutputPassthrough(NodeGroup *group, Node *output) :
group_(group),
new_output_(output)
{}
virtual Project * GetRelevantProject() const override
{
return group_->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
NodeGroup *group_;
Node *new_output_;
Node *old_output_;
};
}
#endif // NODEGROUP_H
+1
View File
@@ -43,6 +43,7 @@ public:
* @brief Methods of interpolation to use with this keyframe
*/
enum Type {
kInvalid = -1,
kLinear,
kHold,
kBezier
+2
View File
@@ -32,6 +32,8 @@ MergeNode::MergeNode()
AddInput(kBaseIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
AddInput(kBlendIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
SetFlags(kDontShowInParamView);
}
Node *MergeNode::copy() const
+130 -130
View File
@@ -47,8 +47,10 @@ Node::Node() :
override_color_(-1),
folder_(nullptr),
operation_stack_(0),
cache_result_(false)
cache_result_(false),
flags_(kNone)
{
uuid_ = QUuid::createUuid();
}
Node::~Node()
@@ -88,6 +90,8 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint versi
xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this);
} else if (reader->name() == QStringLiteral("label")) {
SetLabel(reader->readElementText());
} else if (reader->name() == QStringLiteral("uuid")) {
SetUUID(QUuid::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("color")) {
override_color_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("links")) {
@@ -169,6 +173,7 @@ void Node::Save(QXmlStreamWriter *writer) const
{
writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
writer->writeTextElement(QStringLiteral("uuid"), uuid_.toString());
writer->writeTextElement(QStringLiteral("label"), GetLabel());
writer->writeTextElement(QStringLiteral("color"), QString::number(override_color_));
@@ -219,7 +224,16 @@ void Node::Save(QXmlStreamWriter *writer) const
Project* Node::project() const
{
return dynamic_cast<Project*>(parent());
QObject *t = this->parent();
while (t) {
if (Project *p = dynamic_cast<Project*>(t)) {
return p;
}
t = t->parent();
}
return nullptr;
}
QString Node::ShortName() const
@@ -243,6 +257,40 @@ QIcon Node::icon() const
return icon::New;
}
bool Node::SetNodePositionInContext(Node *node, const QPointF &pos)
{
Position p = context_positions_.value(node);
p.position = pos;
return SetNodePositionInContext(node, p);
}
bool Node::SetNodePositionInContext(Node *node, const Position &pos)
{
bool added = !ContextContainsNode(node);
context_positions_.insert(node, pos);
if (added) {
emit NodeAddedToContext(node);
}
emit NodePositionInContextChanged(node, pos.position);
return added;
}
bool Node::RemoveNodeFromContext(Node *node)
{
if (ContextContainsNode(node)) {
context_positions_.remove(node);
emit NodeRemovedFromContext(node);
return true;
} else {
return false;
}
}
Color Node::color() const
{
int c;
@@ -429,6 +477,11 @@ void Node::SaveInput(QXmlStreamWriter *writer, const QString &id) const
writer->writeEndElement(); // subelements
}
bool Node::IsInputHidden(const QString &input) const
{
return (GetInputFlags(input) & kInputFlagHidden);
}
bool Node::IsInputConnectable(const QString &input) const
{
return !(GetInputFlags(input) & kInputFlagNotConnectable);
@@ -1050,7 +1103,7 @@ NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const
return nullptr;
}
Node::InputFlags Node::GetInputFlags(const QString &input) const
InputFlags Node::GetInputFlags(const QString &input) const
{
const Input* i = GetInternalInputData(input);
@@ -1196,13 +1249,10 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap<Node*, Node*>& cre
command->add_child(new NodeSetValueHintCommand(copied_input, node->GetValueHintForInput(input.input(), input.element())));
}
if (node->parent()->GetPositionMap().contains(node)) {
// This node is a context, copy the context
const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node);
for (auto it=map.cbegin(); it!=map.cend(); it++) {
// Add either the copy (if it exists) or the original node to the context
command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value(), false));
}
const PositionMap &map = node->GetContextPositions();
for (auto it=map.cbegin(); it!=map.cend(); it++) {
// Add either the copy (if it exists) or the original node to the context
command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value()));
}
return copy;
@@ -1229,13 +1279,10 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command)
command->add_child(new NodeCopyInputsCommand(node, copy, true));
if (node->parent()->GetPositionMap().contains(node)) {
// This node is a context, copy the context
const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node);
for (auto it=map.cbegin(); it!=map.cend(); it++) {
// Add to the context
command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value(), false));
}
const PositionMap &map = node->GetContextPositions();
for (auto it=map.cbegin(); it!=map.cend(); it++) {
// Add to the context
command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value()));
}
}
@@ -1310,7 +1357,7 @@ void Node::HashAddNodeSignature(QCryptographicHash &hash) const
hash.addData(id().toUtf8());
}
void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, Node::InputFlags flags, int index)
void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, InputFlags flags, int index)
{
if (id.isEmpty()) {
qWarning() << "Rejected adding input with an empty ID on node" << this->id();
@@ -1457,7 +1504,7 @@ void Node::GizmoMove(const QPointF &, const rational&, const Qt::KeyboardModifie
{
}
void Node::GizmoRelease()
void Node::GizmoRelease(MultiUndoCommand *)
{
}
@@ -1564,7 +1611,9 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input,
dst->SetSplitStandardValue(input, src->GetSplitStandardValue(input, src_element), dst_element);
// Copy keyframes
dst->GetImmediate(input, dst_element)->delete_all_keyframes();
if (NodeInputImmediate *immediate = dst->GetImmediate(input, dst_element)) {
immediate->delete_all_keyframes();
}
foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) {
foreach (NodeKeyframe* key, track) {
key->copy(dst_element, dst);
@@ -2198,7 +2247,6 @@ void Node::childEvent(QChildEvent *event)
GetImmediate(key->input(), key->element())->insert_keyframe(key);
connect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange);
connect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged);
connect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange);
connect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged);
connect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange);
@@ -2210,15 +2258,14 @@ void Node::childEvent(QChildEvent *event)
TimeRange time_affected = GetRangeAffectedByKeyframe(key);
disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange);
disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged);
disconnect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange);
disconnect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged);
disconnect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange);
disconnect(key, &NodeKeyframe::BezierControlOutChanged, this, &Node::InvalidateFromKeyframeBezierOutChange);
GetImmediate(key->input(), key->element())->remove_keyframe(key);
emit KeyframeRemoved(key);
GetImmediate(key->input(), key->element())->remove_keyframe(key);
ParameterValueChanged(i, time_affected);
}
}
@@ -2281,12 +2328,16 @@ void Node::InvalidateFromKeyframeTimeChange()
foreach (const TimeRange& r, invalidate_range) {
ParameterValueChanged(key->key_track_ref().input(), r);
}
emit KeyframeTimeChanged(key);
}
void Node::InvalidateFromKeyframeValueChange()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
ParameterValueChanged(key->key_track_ref().input(), GetRangeAffectedByKeyframe(key));
emit KeyframeValueChanged(key);
}
void Node::InvalidateFromKeyframeTypeChanged()
@@ -2301,6 +2352,8 @@ void Node::InvalidateFromKeyframeTypeChanged()
// Invalidate entire range
ParameterValueChanged(key->key_track_ref().input(), GetRangeAroundIndex(key->input(), track.indexOf(key), key->track(), key->element()));
emit KeyframeTypeChanged(key);
}
Project *Node::ArrayInsertCommand::GetRelevantProject() const
@@ -2318,119 +2371,40 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const
return node_->project();
}
void NodeSetPositionAndShiftSurroundingsCommand::redo()
{
if (commands_.isEmpty()) {
// Move first node
NodeSetPositionCommand* set_pos_command = new NodeSetPositionCommand(node_, relative_, position_, move_dependencies_);
set_pos_command->redo_now();
commands_.append(set_pos_command);
// Get bounding rect
qreal bounding_rect_sz = 1.0;
qreal bounding_rect_half_sz = bounding_rect_sz * 0.5;
QRectF bounding_rect(position_.x() - bounding_rect_half_sz, position_.y() - bounding_rect_half_sz, bounding_rect_sz, bounding_rect_sz);
// Start moving other nodes
foreach (Node* surrounding, node_->parent()->nodes()) {
if (surrounding != node_) {
QPointF surrounding_position = node_->parent()->GetNodePosition(surrounding, relative_);
if (bounding_rect.contains(surrounding_position)) {
QPointF new_pos = surrounding_position;
qreal move_rate = 0.50;
if (surrounding_position.y() < position_.y()) {
move_rate = -move_rate;
}
new_pos.setY(new_pos.y() + move_rate);
auto sur_command = new NodeSetPositionAndShiftSurroundingsCommand(surrounding, relative_, new_pos, true);
sur_command->redo();
commands_.append(sur_command);
}
}
}
} else {
for (int i=0; i<commands_.size(); i++) {
commands_.at(i)->redo_now();
}
}
}
void NodeSetPositionCommand::redo()
{
graph_ = node_->parent();
if (!(added_ = !graph_->NodeMapContainsNode(node_, relevant_))) {
old_pos_ = graph_->GetNodePosition(node_, relevant_);
added_ = !context_->ContextContainsNode(node_);
if (!added_) {
old_pos_ = context_->GetNodePositionDataInContext(node_);
}
graph_->SetNodePosition(node_, relevant_, pos_);
context_->SetNodePositionInContext(node_, pos_);
}
void NodeSetPositionCommand::undo()
{
if (added_) {
graph_->RemoveNodePosition(node_, relevant_);
context_->RemoveNodeFromContext(node_);
} else {
graph_->SetNodePosition(node_, relevant_, old_pos_);
context_->SetNodePositionInContext(node_, old_pos_);
}
}
void NodeSetPositionAsChildCommand::redo()
{
if (!sub_command_) {
// Calculate position of node
NodeGraph *graph = parent_->parent();
QPointF pos = graph->GetNodePosition(parent_, relative_);
// This is a dependency, so we'll place it one X before
pos.setX(pos.x() - 1);
// The Y will be calculated using the index and child count
pos.setY(pos.y() - (double(child_count_)*0.5) + this_index_ + 0.5);
sub_command_ = new MultiUndoCommand();
if (shift_surroundings_) {
sub_command_->add_child(new NodeSetPositionAndShiftSurroundingsCommand(node_, relative_, pos, true));
} else {
sub_command_->add_child(new NodeSetPositionCommand(node_, relative_, pos, true));
}
}
sub_command_->redo_now();
}
void NodeSetPositionToOffsetOfAnotherNodeCommand::redo()
{
NodeGraph *graph = node_->parent();
old_pos_ = graph->GetNodePosition(node_, relative_);
graph->SetNodePosition(node_, relative_, graph->GetNodePosition(other_node_, relative_) + offset_);
}
void NodeSetPositionToOffsetOfAnotherNodeCommand::undo()
{
NodeGraph *graph = node_->parent();
graph->SetNodePosition(node_, relative_, old_pos_);
}
void NodeRemovePositionFromContextCommand::redo()
{
NodeGraph *graph = node_->parent();
contained_ = graph->ContextContainsNode(node_, context_);
contained_ = context_->ContextContainsNode(node_);
if (contained_) {
old_pos_ = graph->GetNodePosition(node_, context_);
graph->RemoveNodePosition(node_, context_);
old_pos_ = context_->GetNodePositionDataInContext(node_);
context_->RemoveNodeFromContext(node_);
}
}
void NodeRemovePositionFromContextCommand::undo()
{
if (contained_) {
NodeGraph *graph = node_->parent();
graph->SetNodePosition(node_, context_, old_pos_);
context_->SetNodePositionInContext(node_, old_pos_);
}
}
@@ -2438,28 +2412,21 @@ void NodeRemovePositionFromAllContextsCommand::redo()
{
NodeGraph *graph = node_->parent();
if (points_.empty()) {
// No points yet, let's see what points we should remove
auto map = graph->GetPositionMap();
for (auto it=map.cbegin(); it!=map.cend(); it++) {
if (it.value().contains(node_)) {
points_.insert({it.key(), it.value().value(node_)});
}
foreach (Node* context, graph->nodes()) {
if (context->ContextContainsNode(node_)) {
contexts_.insert({context, context->GetNodePositionInContext(node_)});
context->RemoveNodeFromContext(node_);
}
}
for (auto it=points_.cbegin(); it!=points_.cend(); it++) {
graph->RemoveNodePosition(node_, it->first);
}
}
void NodeRemovePositionFromAllContextsCommand::undo()
{
NodeGraph *graph = node_->parent();
for (auto it=points_.crbegin(); it!=points_.crend(); it++) {
graph->SetNodePosition(node_, it->first, it->second);
for (auto it = contexts_.crbegin(); it != contexts_.crend(); it++) {
it->first->SetNodePositionInContext(node_, it->second);
}
contexts_.clear();
}
void Node::ValueHint::Hash(QCryptographicHash &hash) const
@@ -2508,4 +2475,37 @@ void Node::ValueHint::Save(QXmlStreamWriter *writer) const
writer->writeTextElement(QStringLiteral("tag"), tag_);
}
void NodeSetPositionAndDependenciesRecursivelyCommand::prepare()
{
move_recursively(node_, pos_.position - context_->GetNodePositionDataInContext(node_).position);
}
void NodeSetPositionAndDependenciesRecursivelyCommand::redo()
{
for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) {
(*it)->redo_now();
}
}
void NodeSetPositionAndDependenciesRecursivelyCommand::undo()
{
for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) {
(*it)->undo_now();
}
}
void NodeSetPositionAndDependenciesRecursivelyCommand::move_recursively(Node *node, const QPointF &diff)
{
Node::Position pos = context_->GetNodePositionDataInContext(node);
pos += diff;
commands_.append(new NodeSetPositionCommand(node_, context_, pos));
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
Node *output = it->second;
if (context_->ContextContainsNode(output)) {
move_recursively(output, diff);
}
}
}
}
+133 -170
View File
@@ -27,6 +27,7 @@
#include <QObject>
#include <QPainter>
#include <QPointF>
#include <QUuid>
#include <QXmlStreamWriter>
#include "codec/frame.h"
@@ -93,6 +94,11 @@ public:
kCategoryCount
};
enum Flag {
kNone = 0,
kDontShowInParamView = 0x1
};
Node();
virtual ~Node() override;
@@ -112,6 +118,14 @@ public:
Project* project() const;
const QUuid &GetUUID() const {return uuid_;}
void SetUUID(const QUuid &uuid) {uuid_ = uuid;}
const uint64_t &GetFlags() const
{
return flags_;
}
/**
* @brief Clear current node variables and replace them with
*/
@@ -208,6 +222,79 @@ public:
return HasInputWithID(id);
}
struct Position
{
Position(const QPointF &p = QPointF(0, 0), bool e = false)
{
position = p;
expanded = e;
}
QPointF position;
bool expanded;
inline Position &operator+=(const Position &p)
{
position += p.position;
return *this;
}
inline Position &operator-=(const Position &p)
{
position -= p.position;
return *this;
}
friend inline const Position operator+(Position a, const Position &b)
{
a += b;
return a;
}
friend inline const Position operator-(Position a, const Position &b)
{
a -= b;
return a;
}
};
using PositionMap = QHash<Node*, Position>;
const PositionMap &GetContextPositions() const
{
return context_positions_;
}
bool IsNodeExpandedInContext(Node *node) const
{
return context_positions_.value(node).expanded;
}
bool ContextContainsNode(Node *node) const
{
return context_positions_.contains(node);
}
Position GetNodePositionDataInContext(Node *node)
{
return context_positions_.value(node);
}
QPointF GetNodePositionInContext(Node *node)
{
return GetNodePositionDataInContext(node).position;
}
bool SetNodePositionInContext(Node *node, const QPointF &pos);
bool SetNodePositionInContext(Node *node, const Position &pos);
void SetNodeExpandedInContext(Node *node, bool e)
{
context_positions_[node].expanded = e;
}
bool RemoveNodeFromContext(Node *node);
/**
* @brief Retrieve the color of this node
*/
@@ -243,11 +330,12 @@ public:
static void DisconnectEdge(Node *output, const NodeInput& input);
QString GetInputName(const QString& id) const;
virtual QString GetInputName(const QString& id) const;
void LoadInput(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled);
void SaveInput(QXmlStreamWriter* writer, const QString& id) const;
bool IsInputHidden(const QString& input) const;
bool IsInputConnectable(const QString& input) const;
bool IsInputKeyframable(const QString& input) const;
@@ -767,7 +855,7 @@ public:
virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF& p);
virtual void GizmoMove(const QPointF& p, const rational &time, const Qt::KeyboardModifiers &modifiers);
virtual void GizmoRelease();
virtual void GizmoRelease(MultiUndoCommand *command);
const QString& GetLabel() const;
void SetLabel(const QString& s);
@@ -861,37 +949,9 @@ public:
};
InputFlags GetInputFlags(const QString& input) const;
protected:
enum InputFlag {
/// By default, inputs are keyframable, connectable, and NOT arrays
kInputFlagNormal = 0x0,
kInputFlagArray = 0x1,
kInputFlagNotKeyframable = 0x2,
kInputFlagNotConnectable = 0x4
};
class InputFlags {
public:
explicit InputFlags()
{
f_ = kInputFlagNormal;
}
explicit InputFlags(uint64_t flags)
{
f_ = flags;
}
bool operator&(const InputFlag& f) const
{
return f_ & f;
}
private:
uint64_t f_;
};
virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const;
void HashAddNodeSignature(QCryptographicHash &hash) const;
@@ -986,12 +1046,12 @@ protected:
tooltip_ = s;
}
signals:
/**
* @brief Signal emitted whenever the position is set through SetPosition()
*/
void PositionChanged(const QPointF& pos);
void SetFlags(const uint64_t &f)
{
flags_ = f;
}
signals:
/**
* @brief Signal emitted when SetLabel() is called
*/
@@ -1021,7 +1081,11 @@ signals:
void KeyframeRemoved(NodeKeyframe* key);
void KeyframeTimeChanged();
void KeyframeTimeChanged(NodeKeyframe* key);
void KeyframeTypeChanged(NodeKeyframe* key);
void KeyframeValueChanged(NodeKeyframe* key);
void KeyframeEnableChanged(const NodeInput& input, bool enabled);
@@ -1037,6 +1101,12 @@ signals:
void RemovedFromGraph(NodeGraph* graph);
void NodeAddedToContext(Node *node);
void NodePositionInContextChanged(Node *node, const QPointF &pos);
void NodeRemovedFromContext(Node *node);
private:
class ArrayInsertCommand : public UndoCommand
{
@@ -1140,8 +1210,6 @@ private:
return input_ids_.indexOf(input);
}
InputFlags GetInputFlags(const QString& input) const;
Input* GetInternalInputData(const QString& input)
{
int i = GetInternalInputIndex(input);
@@ -1258,6 +1326,12 @@ private:
QMap<InputElementPair, ValueHint> value_hints_;
PositionMap context_positions_;
QUuid uuid_;
uint64_t flags_;
private slots:
/**
* @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time
@@ -1367,12 +1441,11 @@ using NodePtr = std::shared_ptr<Node>;
class NodeSetPositionCommand : public UndoCommand
{
public:
NodeSetPositionCommand(Node* node, Node* relevant, const QPointF& pos, bool move_dependencies_relatively)
NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos)
{
node_ = node;
relevant_ = relevant;
context_ = context;
pos_ = pos;
move_deps_ = move_dependencies_relatively;
}
virtual Project* GetRelevantProject() const override
@@ -1387,153 +1460,43 @@ protected:
private:
Node* node_;
Node* relevant_;
QPointF pos_;
QPointF old_pos_;
Node* context_;
Node::Position pos_;
Node::Position old_pos_;
bool added_;
bool move_deps_;
NodeGraph *graph_;
};
class NodeSetPositionAndShiftSurroundingsCommand : public UndoCommand
{
class NodeSetPositionAndDependenciesRecursivelyCommand : public UndoCommand{
public:
NodeSetPositionAndShiftSurroundingsCommand(Node* node, Node *relative, const QPointF& pos, bool move_dependencies_relatively) :
NodeSetPositionAndDependenciesRecursivelyCommand(Node* node, Node* context, const Node::Position& pos) :
node_(node),
relative_(relative),
position_(pos),
move_dependencies_(move_dependencies_relatively)
context_(context),
pos_(pos)
{}
virtual ~NodeSetPositionAndShiftSurroundingsCommand() override
{
qDeleteAll(commands_);
}
virtual Project * GetRelevantProject() const override
virtual Project* GetRelevantProject() const override
{
return node_->project();
}
protected:
virtual void prepare() override;
virtual void redo() override;
virtual void undo() override
{
for (int i=commands_.size()-1; i>=0; i--) {
commands_.at(i)->undo_now();
}
}
virtual void undo() override;
private:
void move_recursively(Node *node, const QPointF &diff);
Node* node_;
Node *relative_;
QPointF position_;
bool move_dependencies_;
Node* context_;
Node::Position pos_;
QVector<UndoCommand*> commands_;
};
class NodeSetPositionAsChildCommand : public UndoCommand
{
public:
NodeSetPositionAsChildCommand(Node* node, Node* parent, Node *relative, double this_index, int child_count, bool shift_surroundings) :
node_(node),
parent_(parent),
relative_(relative),
this_index_(this_index),
child_count_(child_count),
shift_surroundings_(shift_surroundings),
sub_command_(nullptr)
{
}
virtual ~NodeSetPositionAsChildCommand() override
{
delete sub_command_;
}
virtual Project * GetRelevantProject() const override
{
return node_->project();
}
protected:
virtual void redo() override;
virtual void undo() override
{
sub_command_->undo_now();
}
private:
Node* node_;
Node* parent_;
Node *relative_;
double this_index_;
int child_count_;
bool shift_surroundings_;
MultiUndoCommand* sub_command_;
};
class NodePositionCloseChildGapCommand : public UndoCommand
{
public:
NodePositionCloseChildGapCommand(Node *parent, void *relative, int remove_index, int child_count, bool shift_surroundings);
virtual Project * GetRelevantProject() const override
{
return parent_->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
Node *parent_;
};
class NodeSetPositionToOffsetOfAnotherNodeCommand : public UndoCommand
{
public:
NodeSetPositionToOffsetOfAnotherNodeCommand(Node* node, Node* other_node, Node *relative, const QPointF& offset) :
node_(node),
other_node_(other_node),
relative_(relative),
offset_(offset)
{}
virtual Project * GetRelevantProject() const override
{
return node_->project();
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
Node* node_;
Node* other_node_;
Node *relative_;
QPointF offset_;
QPointF old_pos_;
};
class NodeRemovePositionFromContextCommand : public UndoCommand
{
public:
@@ -1558,7 +1521,7 @@ private:
Node *context_;
QPointF old_pos_;
Node::Position old_pos_;
bool contained_;
@@ -1585,7 +1548,7 @@ protected:
private:
Node *node_;
std::map<Node *, QPointF> points_;
std::map<Node *, QPointF> contexts_;
};
+32 -18
View File
@@ -29,7 +29,7 @@
namespace olive {
void NodeCopyPasteService::CopyNodesToClipboard(const QVector<Node *> &nodes, void *userdata)
void NodeCopyPasteService::CopyNodesToClipboard(QVector<Node *> nodes, void *userdata)
{
QString copy_str;
@@ -42,22 +42,33 @@ void NodeCopyPasteService::CopyNodesToClipboard(const QVector<Node *> &nodes, vo
writer.writeTextElement(QStringLiteral("version"), QString::number(Core::kProjectVersion));
writer.writeStartElement(QStringLiteral("nodes"));
foreach (Node* n, nodes) {
for (int i=0; i<nodes.size(); i++) {
Node *n = nodes.at(i);
writer.writeStartElement(QStringLiteral("node"));
writer.writeAttribute(QStringLiteral("id"), n->id());
n->Save(&writer);
writer.writeEndElement(); // node
// If this is a group, add the child nodes too
if (NodeGroup *g = dynamic_cast<NodeGroup*>(n)) {
for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) {
if (!nodes.contains(it.key())) {
nodes.append(it.key());
}
}
}
}
writer.writeEndElement(); // nodes
writer.writeStartElement(QStringLiteral("contexts"));
foreach (Node* n, nodes) {
// Determine if this node is a context
if (n->parent()->GetPositionMap().contains(n)) {
if (!n->GetContextPositions().isEmpty()) {
writer.writeStartElement(QStringLiteral("context"));
writer.writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(n)));
const NodeGraph::PositionMap &map = n->parent()->GetNodesForContext(n);
const Node::PositionMap &map = n->GetContextPositions();
for (auto it=map.cbegin(); it!=map.cend(); it++) {
writer.writeStartElement(QStringLiteral("node"));
Project::SavePosition(&writer, it.key(), it.value());
@@ -92,7 +103,7 @@ QVector<Node *> NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph,
QVector<Node*> pasted_nodes;
XMLNodeData xml_node_data;
QMap<quintptr, QMap<quintptr, QPointF> > pasted_contexts;
QMap<quintptr, QMap<quintptr, Node::Position> > pasted_contexts;
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("olive")) {
@@ -131,7 +142,7 @@ QVector<Node *> NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph,
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("context")) {
// Get context ptr
QMap<quintptr, QPointF> map;
QMap<quintptr, Node::Position> map;
quintptr context_ptr = 0;
XMLAttributeLoop((&reader), attr) {
if (attr.name() == QStringLiteral("ptr")) {
@@ -144,7 +155,7 @@ QVector<Node *> NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph,
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("node")) {
quintptr node_ptr;
QPointF node_pos;
Node::Position node_pos;
if (Project::LoadPosition(&reader, &node_ptr, &node_pos)) {
map.insert(node_ptr, node_pos);
@@ -201,31 +212,34 @@ QVector<Node *> NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph,
// Add all nodes to graph
foreach (Node* n, pasted_nodes) {
command->add_child(new NodeAddCommand(graph, n));
if (command) {
command->add_child(new NodeAddCommand(graph, n));
} else {
n->setParent(graph);
}
}
// Make connections
if (!xml_node_data.desired_connections.isEmpty()) {
XMLConnectNodes(xml_node_data, data_version, command);
}
// Link blocks
XMLLinkBlocks(xml_node_data);
// Process contexts
for (auto it=pasted_contexts.cbegin(); it!=pasted_contexts.cend(); it++) {
Node *context = xml_node_data.node_ptrs.value(it.key());
if (context) {
const QMap<quintptr, QPointF> &map = it.value();
auto map = it.value();
for (auto jt=map.cbegin(); jt!=map.cend(); jt++) {
Node *subnode = xml_node_data.node_ptrs.value(jt.key());
if (subnode) {
command->add_child(new NodeSetPositionCommand(subnode, context, jt.value(), false));
if (command) {
command->add_child(new NodeSetPositionCommand(subnode, context, jt.value()));
} else {
context->SetNodePositionInContext(subnode, jt.value());
}
}
}
}
}
// Make connections
xml_node_data.PostConnect(data_version, command);
return pasted_nodes;
}
+1 -1
View File
@@ -35,7 +35,7 @@ public:
NodeCopyPasteService() = default;
protected:
void CopyNodesToClipboard(const QVector<Node *> &nodes, void* userdata = nullptr);
void CopyNodesToClipboard(QVector<Node *> nodes, void* userdata = nullptr);
QVector<Node*> PasteNodesFromClipboard(NodeGraph *graph, MultiUndoCommand *command, void* userdata = nullptr);
+3 -2
View File
@@ -41,9 +41,10 @@ const QString Track::kMutedInput = QStringLiteral("muted_in");
Track::Track() :
track_type_(Track::kNone),
index_(-1),
locked_(false)
locked_(false),
sequence_(nullptr)
{
AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable));
AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable | kInputFlagHidden));
// Since blocks are time based, we can handle the invalidate timing a little more intelligently
// on our end
+53 -9
View File
@@ -26,6 +26,8 @@
namespace olive {
class Sequence;
/**
* @brief A time traversal Node for sorting through one channel/track of Blocks
*/
@@ -168,17 +170,48 @@ public:
QString ToString() const
{
QString type_string;
if (type_ == Track::kVideo) {
type_string = QStringLiteral("v");
} else if (type_ == Track::kAudio) {
type_string = QStringLiteral("a");
} else {
QString type_string = TypeToString(type_);
if (type_string.isEmpty()) {
return QString();
} else {
return QStringLiteral("%1:%2").arg(type_string, QString::number(index_));
}
}
/// For IDs that shouldn't change between localizations
static QString TypeToString(Type type)
{
switch (type) {
case kVideo:
return QStringLiteral("v");
case kAudio:
return QStringLiteral("a");
case kSubtitle:
return QStringLiteral("s");
case kCount:
case kNone:
break;
}
return QStringLiteral("%1:%2").arg(type_string, QString::number(index_));
return QString();
}
/// For human-facing strings
static QString TypeToTranslatedString(Type type)
{
switch (type) {
case kVideo:
return tr("V");
case kAudio:
return tr("A");
case kSubtitle:
return tr("S");
case kCount:
case kNone:
break;
}
return QString();
}
static Type TypeFromString(const QString& s)
@@ -359,9 +392,18 @@ public:
bool IsLocked() const;
int GetArrayIndexFromBlock(Block* block) const;
Sequence *sequence() const
{
return sequence_;
}
void set_sequence(Sequence *sequence)
{
sequence_ = sequence;
}
static const double kTrackHeightDefault;
static const double kTrackHeightMinimum;
static const double kTrackHeightInterval;
@@ -443,6 +485,8 @@ private:
bool locked_;
Sequence *sequence_;
private slots:
void BlockLengthChanged();
+2
View File
@@ -83,6 +83,7 @@ void TrackList::TrackConnected(Node *node, int element)
connect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength);
track->set_type(type_);
track->set_sequence(parent());
emit TrackListChanged();
@@ -121,6 +122,7 @@ void TrackList::TrackDisconnected(Node *node, int element)
track->SetIndex(-1);
track->set_type(Track::kNone);
track->set_sequence(nullptr);
disconnect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength);
+4 -6
View File
@@ -23,7 +23,6 @@
#include "config/config.h"
#include "core.h"
#include "node/traverser.h"
#include "widget/videoparamedit/videoparamedit.h"
namespace olive {
@@ -34,8 +33,6 @@ const QString ViewerOutput::kSamplesInput = QStringLiteral("samples_in");
const QString ViewerOutput::kVideoAutoCacheInput = QStringLiteral("video_autocache_in");
const QString ViewerOutput::kAudioAutoCacheInput = QStringLiteral("audio_autocache_in");
const uint64_t ViewerOutput::kVideoParamEditMask = VideoParamEdit::kWidthHeight | VideoParamEdit::kInterlacing | VideoParamEdit::kFrameRate | VideoParamEdit::kPixelAspect;
#define super Node
ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_streams) :
@@ -47,10 +44,9 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream
video_cache_enabled_(true),
audio_cache_enabled_(true)
{
AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray));
SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(kVideoParamEditMask));
AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden));
AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray));
AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden));
if (create_buffer_inputs) {
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
@@ -70,6 +66,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream
AddStream(Track::kAudio, QVariant());
set_default_parameters();
}
SetFlags(kDontShowInParamView);
}
Node *ViewerOutput::copy() const
-4
View File
@@ -200,8 +200,6 @@ public:
static const QString kVideoAutoCacheInput;
static const QString kAudioAutoCacheInput;
static const uint64_t kVideoParamEditMask;
signals:
void FrameRateChanged(const rational&);
@@ -254,8 +252,6 @@ private:
AudioPlaybackCache audio_playback_cache_;
int operation_stack_;
VideoParams cached_video_params_;
AudioParams cached_audio_params_;
+36
View File
@@ -33,6 +33,15 @@ QString NodeInput::name() const
}
}
bool NodeInput::IsHidden() const
{
if (IsValid()) {
return node_->IsInputHidden(input_);
} else {
return false;
}
}
bool NodeInput::IsConnected() const
{
if (IsValid()) {
@@ -60,6 +69,15 @@ bool NodeInput::IsArray() const
}
}
InputFlags NodeInput::GetFlags() const
{
if (IsValid()) {
return node_->GetInputFlags(input_);
} else {
return InputFlags(kInputFlagNormal);
}
}
Node *NodeInput::GetConnectedOutput() const
{
if (IsValid()) {
@@ -78,6 +96,15 @@ NodeValue::Type NodeInput::GetDataType() const
}
}
QVariant NodeInput::GetDefaultValue() const
{
if (IsValid()) {
return node_->GetDefaultValue(input_);
} else {
return QVariant();
}
}
QStringList NodeInput::GetComboBoxStrings() const
{
if (IsValid()) {
@@ -123,6 +150,15 @@ QVariant NodeInput::GetSplitDefaultValueForTrack(int track) const
}
}
int NodeInput::GetArraySize() const
{
if (IsValid() && element_ == -1) {
return node_->InputArraySize(input_);
} else {
return 0;
}
}
uint qHash(const NodeInput &i)
{
return qHash(i.node()) ^ qHash(i.input()) ^ qHash(i.element());
+50 -1
View File
@@ -31,6 +31,37 @@ namespace olive {
class Node;
class NodeKeyframe;
enum InputFlag {
/// By default, inputs are keyframable, connectable, and NOT arrays
kInputFlagNormal = 0x0,
kInputFlagArray = 0x1,
kInputFlagNotKeyframable = 0x2,
kInputFlagNotConnectable = 0x4,
kInputFlagHidden = 0x8
};
class InputFlags {
public:
explicit InputFlags()
{
f_ = kInputFlagNormal;
}
explicit InputFlags(uint64_t flags)
{
f_ = flags;
}
bool operator&(const InputFlag& f) const
{
return f_ & f;
}
private:
uint64_t f_;
};
struct NodeInputPair {
bool operator==(const NodeInputPair& rhs) const
{
@@ -98,11 +129,21 @@ public:
return input_;
}
int element() const
const int &element() const
{
return element_;
}
void set_node(Node *node)
{
node_ = node;
}
void set_input(const QString &input)
{
input_ = input;
}
void set_element(int e)
{
element_ = e;
@@ -115,16 +156,22 @@ public:
return node_ && !input_.isEmpty() && element_ >= -1;
}
bool IsHidden() const;
bool IsConnected() const;
bool IsKeyframing() const;
bool IsArray() const;
InputFlags GetFlags() const;
Node *GetConnectedOutput() const;
NodeValue::Type GetDataType() const;
QVariant GetDefaultValue() const;
QStringList GetComboBoxStrings() const;
QVariant GetProperty(const QString& key) const;
@@ -135,6 +182,8 @@ public:
QVariant GetSplitDefaultValueForTrack(int track) const;
int GetArraySize() const;
void Reset()
{
*this = NodeInput();
+2 -20
View File
@@ -113,19 +113,12 @@ void Folder::InputDisconnectedEvent(const QString &input, int element, Node *out
}
}
FolderAddChild::FolderAddChild(Folder *folder, Node *child, bool autoposition) :
FolderAddChild::FolderAddChild(Folder *folder, Node *child) :
folder_(folder),
child_(child),
autoposition_(autoposition),
position_command_(nullptr)
child_(child)
{
}
FolderAddChild::~FolderAddChild()
{
delete position_command_;
}
Project *FolderAddChild::GetRelevantProject() const
{
return folder_->project();
@@ -136,21 +129,10 @@ void FolderAddChild::redo()
int array_index = folder_->InputArraySize(Folder::kChildInput);
folder_->InputArrayAppend(Folder::kChildInput, false);
Node::ConnectEdge(child_, NodeInput(folder_, Folder::kChildInput, array_index));
if (autoposition_) {
if (!position_command_) {
position_command_ = new NodeSetPositionAsChildCommand(child_, folder_, folder_->project()->root(), array_index, array_index+1, true);
}
position_command_->redo_now();
}
}
void FolderAddChild::undo()
{
if (position_command_) {
position_command_->undo_now();
}
Node::DisconnectEdge(child_, NodeInput(folder_, Folder::kChildInput, folder_->InputArraySize(Folder::kChildInput)-1));
folder_->InputArrayRemoveLast(Folder::kChildInput);
}
+1 -7
View File
@@ -208,9 +208,7 @@ private:
class FolderAddChild : public UndoCommand
{
public:
FolderAddChild(Folder* folder, Node* child, bool autoposition = true);
virtual ~FolderAddChild() override;
FolderAddChild(Folder* folder, Node* child);
virtual Project * GetRelevantProject() const override;
@@ -224,10 +222,6 @@ private:
Node* child_;
bool autoposition_;
NodeSetPositionAsChildCommand* position_command_;
};
}
-34
View File
@@ -33,7 +33,6 @@
#include "core.h"
#include "render/job/footagejob.h"
#include "ui/icons/icons.h"
#include "widget/videoparamedit/videoparamedit.h"
namespace olive {
@@ -144,39 +143,6 @@ void Footage::InputValueChangedEvent(const QString &input, int element)
AddStream(Track::kVideo, QVariant::fromValue(vp));
}
if (!footage_info.GetVideoStreams().isEmpty()) {
// FIXME: This will break on multiple video streams. Currently we don't have
// infrastructure for different properties per element. We'll see if this becomes
// a problem.
VideoParams vp = footage_info.GetVideoStreams().first();
uint64_t video_param_mask = 0;
video_param_mask |= VideoParamEdit::kEnabled;
video_param_mask |= VideoParamEdit::kColorspace;
video_param_mask |= VideoParamEdit::kPixelAspect;
video_param_mask |= VideoParamEdit::kInterlacing;
video_param_mask |= VideoParamEdit::kFrameRateIsArbitrary;
if (vp.channel_count() == VideoParams::kRGBAChannelCount) {
// Add premultiplied setting if this footage has an alpha channel
video_param_mask |= VideoParamEdit::kPremultipliedAlpha;
}
if (vp.video_type() == VideoParams::kVideoTypeVideo) {
// This is video, ensure that the frame rate does not overwrite the timebase
video_param_mask |= VideoParamEdit::kFrameRateIsNotTimebase;
} else {
// This is not a video, so it's either a still image or an image sequence
video_param_mask |= VideoParamEdit::kIsImageSequence;
video_param_mask |= VideoParamEdit::kStartTime;
video_param_mask |= VideoParamEdit::kEndTime;
video_param_mask |= VideoParamEdit::kFrameRate;
}
SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(video_param_mask));
}
for (int i=0; i<footage_info.GetAudioStreams().size(); i++) {
AddStream(Track::kAudio, QVariant::fromValue(footage_info.GetAudioStreams().at(i)));
}
+24 -25
View File
@@ -44,19 +44,16 @@ Project::Project() :
root_->setParent(this);
root_->SetLabel(tr("Root"));
root_->SetCanBeDeleted(false);
SetNodePosition(root_, root_, QPointF(0, 0));
// Adds a color manager "node" to this project so that it synchronizes
color_manager_ = new ColorManager();
color_manager_->setParent(this);
SetNodePosition(color_manager_, root_, QPointF(1, 0));
color_manager_->SetCanBeDeleted(false);
AddDefaultNode(color_manager_);
// Same with project settings
settings_ = new ProjectSettingsNode();
settings_->setParent(this);
SetNodePosition(settings_, root_, QPointF(2, 0));
settings_->SetCanBeDeleted(false);
AddDefaultNode(settings_);
@@ -161,13 +158,13 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("node")) {
quintptr node_ptr;
QPointF node_pos;
Node::Position node_pos;
if (LoadPosition(reader, &node_ptr, &node_pos)) {
Node *node = xml_node_data.node_ptrs.value(node_ptr);
if (node) {
SetNodePosition(node, context, node_pos);
context->SetNodePositionInContext(node, node_pos);
} else {
qWarning() << "Failed to find pointer for node position";
reader->skipCurrentElement();
@@ -196,10 +193,7 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint
}
// Make connections
XMLConnectNodes(xml_node_data, version);
// Link blocks
XMLLinkBlocks(xml_node_data);
xml_node_data.PostConnect(version);
}
void Project::Save(QXmlStreamWriter *writer) const
@@ -230,20 +224,22 @@ void Project::Save(QXmlStreamWriter *writer) const
writer->writeStartElement(QStringLiteral("positions"));
for (auto it=GetPositionMap().cbegin(); it!=GetPositionMap().cend(); it++) {
writer->writeStartElement(QStringLiteral("context"));
foreach (Node* context, nodes()) {
const Node::PositionMap &map = context->GetContextPositions();
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(it.key())));
if (!map.isEmpty()) {
writer->writeStartElement(QStringLiteral("context"));
const PositionMap &map = it.value();
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(context)));
for (auto jt=map.cbegin(); jt!=map.cend(); jt++) {
writer->writeStartElement(QStringLiteral("node"));
SavePosition(writer, jt.key(), jt.value());
writer->writeEndElement(); // node
for (auto jt=map.cbegin(); jt!=map.cend(); jt++) {
writer->writeStartElement(QStringLiteral("node"));
SavePosition(writer, jt.key(), jt.value());
writer->writeEndElement(); // node
}
writer->writeEndElement(); // context
}
writer->writeEndElement(); // context
}
writer->writeEndElement(); // positions
@@ -351,7 +347,7 @@ void Project::RegenerateUuid()
uuid_ = QUuid::createUuid();
}
bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF *pos)
bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos)
{
bool got_node_ptr = false;
bool got_pos_x = false;
@@ -367,11 +363,13 @@ bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("x")) {
pos->setX(reader->readElementText().toDouble());
pos->position.setX(reader->readElementText().toDouble());
got_pos_x = true;
} else if (reader->name() == QStringLiteral("y")) {
pos->setY(reader->readElementText().toDouble());
pos->position.setY(reader->readElementText().toDouble());
got_pos_y = true;
} else if (reader->name() == QStringLiteral("expanded")) {
pos->expanded = reader->readElementText().toInt();
} else {
reader->skipCurrentElement();
}
@@ -380,12 +378,13 @@ bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF
return got_node_ptr && got_pos_x && got_pos_y;
}
void Project::SavePosition(QXmlStreamWriter *writer, Node *node, const QPointF &pos)
void Project::SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos)
{
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(node)));
writer->writeTextElement(QStringLiteral("x"), QString::number(pos.x()));
writer->writeTextElement(QStringLiteral("y"), QString::number(pos.y()));
writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x()));
writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y()));
writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded));
}
void Project::ColorManagerValueChanged(const NodeInput &input, const TimeRange &range)
+2 -2
View File
@@ -82,8 +82,8 @@ public:
void RegenerateUuid();
static bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF *pos);
static void SavePosition(QXmlStreamWriter *writer, Node *node, const QPointF &pos);
static bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos);
static void SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos);
signals:
void NameChanged();
+1 -1
View File
@@ -41,7 +41,7 @@ Sequence::Sequence()
// Create track input
QString track_input_id = kTrackInputFormat.arg(i);
AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray));
AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden));
IgnoreInvalidationsFrom(track_input_id);
+12
View File
@@ -39,6 +39,18 @@ public:
virtual void DeselectAll() override;
public slots:
void SetNode(Node *node)
{
// Convert single pointer to either an empty vector or a vector of one
QVector<Node *> nodes;
if (node) {
nodes.append(node);
}
SetNodes(nodes);
}
void SetNodes(const QVector<Node *> &nodes);
virtual void IncreaseTrackHeight() override;
+7 -25
View File
@@ -20,39 +20,21 @@
#include "node.h"
#include <QVBoxLayout>
namespace olive {
NodePanel::NodePanel(QWidget *parent) :
PanelWidget(QStringLiteral("NodePanel"), parent)
{
QWidget *outer_widget = new QWidget(this);
node_widget_ = new NodeWidget();
connect(this, &NodePanel::visibilityChanged, node_widget_->view(), &NodeView::CenterOnItemsBoundingRect);
QVBoxLayout *outer_layout = new QVBoxLayout(outer_widget);
outer_layout->setMargin(0);
toolbar_ = new NodeViewToolBar();
outer_layout->addWidget(toolbar_);
// Create NodeView widget
node_view_ = new NodeView(this);
outer_layout->addWidget(node_view_);
// Connect toolbar to NodeView
connect(toolbar_, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled);
connect(toolbar_, &NodeViewToolBar::AddNodeClicked, node_view_, &NodeView::ShowAddMenu);
// Set defaults
toolbar_->SetMiniMapEnabled(true);
node_view_->SetMiniMapEnabled(true);
// Connect node view signals to this panel
connect(node_view_, &NodeView::NodesSelected, this, &NodePanel::NodesSelected);
connect(node_view_, &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected);
// Connect node view signals to this panel - MAY REMOVE
connect(node_widget_->view(), &NodeView::NodesSelected, this, &NodePanel::NodesSelected);
connect(node_widget_->view(), &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected);
connect(node_widget_->view(), &NodeView::NodeGroupOpenRequested, this, &NodePanel::NodeGroupOpenRequested);
// Set it as the main widget of this panel
SetWidgetWithPadding(outer_widget);
SetWidgetWithPadding(node_widget_);
// Set strings
Retranslate();
+28 -29
View File
@@ -21,8 +21,7 @@
#ifndef NODEPANEL_H
#define NODEPANEL_H
#include "widget/nodeview/nodeview.h"
#include "widget/nodeview/nodeviewtoolbar.h"
#include "widget/nodeview/nodewidget.h"
#include "widget/panel/panel.h"
namespace olive {
@@ -36,86 +35,86 @@ class NodePanel : public PanelWidget
public:
NodePanel(QWidget* parent);
NodeGraph* GetGraph() const
NodeWidget *GetNodeWidget() const
{
return node_view_->GetGraph();
return node_widget_;
}
void SetGraph(NodeGraph *graph, const QVector<Node*> &nodes)
const QVector<Node*> &GetContexts() const
{
node_view_->SetGraph(graph, nodes);
toolbar_->setEnabled(graph);
return node_widget_->view()->GetContexts();
}
void ClearGraph()
void SetContexts(const QVector<Node*> &nodes)
{
node_view_->ClearGraph();
node_widget_->SetContexts(nodes);
}
void CloseContextsBelongingToProject(Project *project)
{
node_widget_->view()->CloseContextsBelongingToProject(project);
}
const QVector<Node*> &GetCurrentContexts() const
{
return node_view_->GetCurrentContexts();
return node_widget_->view()->GetCurrentContexts();
}
virtual void SelectAll() override
{
node_view_->SelectAll();
node_widget_->view()->SelectAll();
}
virtual void DeselectAll() override
{
node_view_->DeselectAll();
node_widget_->view()->DeselectAll();
}
virtual void DeleteSelected() override
{
node_view_->DeleteSelected();
node_widget_->view()->DeleteSelected();
}
virtual void CutSelected() override
{
node_view_->CopySelected(true);
node_widget_->view()->CopySelected(true);
}
virtual void CopySelected() override
{
node_view_->CopySelected(false);
node_widget_->view()->CopySelected(false);
}
virtual void Paste() override
{
node_view_->Paste();
node_widget_->view()->Paste();
}
virtual void Duplicate() override
{
node_view_->Duplicate();
node_widget_->view()->Duplicate();
}
virtual void SetColorLabel(int index) override
{
node_view_->SetColorLabel(index);
node_widget_->view()->SetColorLabel(index);
}
virtual void ZoomIn() override
{
node_view_->ZoomIn();
node_widget_->view()->ZoomIn();
}
virtual void ZoomOut() override
{
node_view_->ZoomOut();
node_widget_->view()->ZoomOut();
}
public slots:
void Select(const QVector<Node*>& nodes, bool center_view_on_item)
{
node_view_->Select(nodes, center_view_on_item);
}
void SelectWithDependencies(const QVector<Node*>& nodes, bool center_view_on_item)
{
node_view_->SelectWithDependencies(nodes, center_view_on_item);
node_widget_->view()->Select(nodes, center_view_on_item);
this->raise();
}
signals:
@@ -123,15 +122,15 @@ signals:
void NodesDeselected(const QVector<Node*>& nodes);
void NodeGroupOpenRequested(NodeGroup *group);
private:
virtual void Retranslate() override
{
SetTitle(tr("Node Editor"));
}
NodeView* node_view_;
NodeViewToolBar *toolbar_;
NodeWidget *node_widget_;
};
+34 -7
View File
@@ -95,6 +95,40 @@ PanelManager *PanelManager::instance()
return instance_;
}
void PanelManager::RegisterPanel(PanelWidget *panel)
{
// Add panel to the bottom of the focus history
focus_history_.append(panel);
panel->SetMovementLocked(locked_);
// Get panel parent (it's assumed it has one)
QWidget *parent = panel->parentWidget();
// Sane default for panel size
panel->resize(parent->size() / 3);
// We're about to center the panel relative to the parent (usually the main window), but for some
// reason this requires the panel to be shown first.
panel->show();
// Center the panel relative to the parent
QPoint parent_center = panel->mapFromGlobal(parent->mapToGlobal(parent->rect().center()));
QPoint panel_center = panel->rect().center();
panel->move(parent_center - panel_center);
if (focus_history_.size() == 1) {
// This is the first panel, focus it
panel->SetBorderVisible(true);
emit FocusedPanelChanged(panel);
}
}
void PanelManager::UnregisterPanel(PanelWidget *panel)
{
focus_history_.removeOne(panel);
}
void PanelManager::FocusChanged(QWidget *old, QWidget *now)
{
Q_UNUSED(old)
@@ -151,11 +185,4 @@ void PanelManager::SetPanelsLocked(bool locked)
locked_ = locked;
}
void PanelManager::PanelDestroyed()
{
PanelWidget* panel = static_cast<PanelWidget*>(sender());
focus_history_.removeOne(panel);
}
}
+10 -46
View File
@@ -84,12 +84,6 @@ public:
*/
T* MostRecentlyFocused();
template<class T>
/**
* @brief Create a panel
*/
T* CreatePanel(QWidget* parent);
/**
* @brief Get whether panels are currently prevented from moving
*/
@@ -118,6 +112,16 @@ public:
*/
QList<T*> GetPanelsOfType();
/**
* @brief Panel should call this upon construction so it can be kept track of
*/
void RegisterPanel(PanelWidget *panel);
/**
* @brief Panel should call this upon destruction so no invalid pointers will be kept for it
*/
void UnregisterPanel(PanelWidget *panel);
public slots:
/**
* @brief Connect this to a QApplication's SIGNAL(focusChanged())
@@ -153,48 +157,8 @@ private:
*/
static PanelManager* instance_;
private slots:
/**
* @brief Processing if a panel gets deleted
*/
void PanelDestroyed();
};
template<class T>
T *PanelManager::CreatePanel(QWidget *parent)
{
T* panel = new T(parent);
// Add panel to the bottom of the focus history
focus_history_.append(panel);
panel->SetMovementLocked(locked_);
// Sane default for panel size
panel->resize(parent->size() / 3);
// We're about to center the panel relative to the parent (usually the main window), but for some
// reason this requires the panel to be shown first.
panel->show();
// Center the panel relative to the parent
QPoint parent_center = panel->mapFromGlobal(parent->mapToGlobal(parent->rect().center()));
QPoint panel_center = panel->rect().center();
panel->move(parent_center - panel_center);
// Connect destroy signal so we can remove it from focus history
connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed, Qt::DirectConnection);
if (focus_history_.size() == 1) {
// This is the first panel, focus it
panel->SetBorderVisible(true);
emit FocusedPanelChanged(panel);
}
return panel;
}
template<class T>
T* PanelManager::MostRecentlyFocused()
{
+5 -15
View File
@@ -29,7 +29,6 @@ ParamPanel::ParamPanel(QWidget* parent) :
{
NodeParamView* view = new NodeParamView();
connect(view, &NodeParamView::RequestSelectNode, this, &ParamPanel::RequestSelectNode);
connect(view, &NodeParamView::NodeOrderChanged, this, &ParamPanel::NodeOrderChanged);
connect(view, &NodeParamView::FocusedNodeChanged, this, &ParamPanel::FocusedNodeChanged);
SetTimeBasedWidget(view);
@@ -39,15 +38,11 @@ ParamPanel::ParamPanel(QWidget* parent) :
void ParamPanel::SelectNodes(const QVector<Node *> &nodes)
{
static_cast<NodeParamView*>(GetTimeBasedWidget())->SelectNodes(nodes);
Retranslate();
}
void ParamPanel::DeselectNodes(const QVector<Node *> &nodes)
{
static_cast<NodeParamView*>(GetTimeBasedWidget())->DeselectNodes(nodes);
Retranslate();
}
void ParamPanel::DeleteSelected()
@@ -65,19 +60,14 @@ void ParamPanel::DeselectAll()
static_cast<NodeParamView*>(GetTimeBasedWidget())->DeselectAll();
}
void ParamPanel::SetContexts(const QVector<Node *> &contexts)
{
static_cast<NodeParamView*>(GetTimeBasedWidget())->SetContexts(contexts);
}
void ParamPanel::Retranslate()
{
SetTitle(tr("Parameter Editor"));
NodeParamView* view = static_cast<NodeParamView*>(GetTimeBasedWidget());
if (view->GetItemMap().isEmpty()) {
SetSubtitle(tr("(none)"));
} else if (view->GetItemMap().size() == 1) {
SetSubtitle(view->GetItemMap().firstKey()->Name());
} else {
SetSubtitle(tr("(multiple)"));
}
}
}
+22 -2
View File
@@ -33,6 +33,26 @@ class ParamPanel : public TimeBasedPanel
public:
ParamPanel(QWidget* parent);
NodeParamView *GetParamView() const
{
return static_cast<NodeParamView *>(GetTimeBasedWidget());
}
const QVector<Node*> &GetContexts() const
{
return GetParamView()->GetContexts();
}
void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e)
{
GetParamView()->SetCreateCheckBoxes(e);
}
void SetIgnoreNodeFlags(bool e)
{
GetParamView()->SetIgnoreNodeFlags(e);
}
public slots:
void SelectNodes(const QVector<Node*>& nodes);
void DeselectNodes(const QVector<Node*>& nodes);
@@ -43,11 +63,11 @@ public slots:
virtual void DeselectAll() override;
void SetContexts(const QVector<Node*> &contexts);
signals:
void RequestSelectNode(const QVector<Node*>& target);
void NodeOrderChanged(const QVector<Node*>& nodes);
void FocusedNodeChanged(Node* n);
protected:
+1 -1
View File
@@ -25,7 +25,7 @@
namespace olive {
PixelSamplerPanel::PixelSamplerPanel(QWidget *parent) :
PanelWidget(QStringLiteral("ProjectPanel"), parent)
PanelWidget(QStringLiteral("PixelSamplerPanel"), parent)
{
sampler_widget_ = new ManagedPixelSamplerWidget();
SetWidgetWithPadding(sampler_widget_);
+7 -18
View File
@@ -24,24 +24,6 @@ namespace olive {
ViewerPanel::ViewerPanel(const QString &object_name, QWidget *parent) :
ViewerPanelBase(object_name, parent)
{
Init();
}
ViewerPanel::ViewerPanel(QWidget *parent) :
ViewerPanelBase(QStringLiteral("ViewerPanel"), parent)
{
Init();
}
void ViewerPanel::Retranslate()
{
ViewerPanelBase::Retranslate();
SetTitle(tr("Viewer"));
}
void ViewerPanel::Init()
{
// Set ViewerWidget as the central widget
ViewerWidget* vw = new ViewerWidget();
@@ -52,4 +34,11 @@ void ViewerPanel::Init()
Retranslate();
}
void ViewerPanel::Retranslate()
{
ViewerPanelBase::Retranslate();
SetTitle(tr("Viewer"));
}
}
+4 -4
View File
@@ -34,14 +34,14 @@ class ViewerPanel : public ViewerPanelBase {
Q_OBJECT
public:
ViewerPanel(const QString& object_name, QWidget* parent);
ViewerPanel(QWidget* parent);
ViewerPanel(QWidget *parent) :
ViewerPanel(QStringLiteral("ViewerPanel"), parent)
{
}
protected:
virtual void Retranslate() override;
private:
void Init();
};
}
+8
View File
@@ -367,12 +367,20 @@ void PreviewAutoCacher::ProcessUpdateQueue()
void PreviewAutoCacher::AddNode(Node *node)
{
if (dynamic_cast<NodeGroup*>(node)) {
// Group nodes are just dummy nodes, no need to copy them
return;
}
// Copy node
Node* copy = node->copy();
// Add to project
copy->setParent(&copied_project_);
// Copy UUID
copy->SetUUID(node->GetUUID());
// Insert into map
InsertIntoCopyMap(node, copy);
+3 -1
View File
@@ -24,8 +24,9 @@
#include <QtConcurrent/QtConcurrent>
#include "config/config.h"
#include "node/graph.h"
#include "node/color/colormanager/colormanager.h"
#include "node/graph.h"
#include "node/group/group.h"
#include "node/node.h"
#include "node/output/viewer/viewer.h"
#include "node/project/project.h"
@@ -172,6 +173,7 @@ private:
QVector<QueuedJob> graph_update_queue_;
QHash<Node*, Node*> copy_map_;
QHash<NodeGraph*, NodeGraph*> graph_map_;
ViewerOutput* copied_viewer_node_;
ColorManager* copied_color_manager_;
QVector<Node*> created_nodes_;
+7 -7
View File
@@ -210,7 +210,7 @@ bool LoadOTIOTask::Run()
block->setParent(sequence->parent());
// Position transition in its own context
sequence->parent()->SetNodePosition(block, block, QPointF(0, 0));
block->SetNodePositionInContext(block, QPointF(0, 0));
}
if (otio_block->schema_name() == "Gap") {
@@ -218,7 +218,7 @@ bool LoadOTIOTask::Run()
block->setParent(sequence->parent());
// Position transition in its own context
sequence->parent()->SetNodePosition(block, block, QPointF(0, 0));
block->SetNodePositionInContext(block, QPointF(0, 0));
}
// Update this after it's used but before any continue statements
@@ -246,7 +246,7 @@ bool LoadOTIOTask::Run()
QFileInfo info(probed_item->filename());
probed_item->SetLabel(info.fileName());
FolderAddChild add(sequence_footage, probed_item, true);
FolderAddChild add(sequence_footage, probed_item);
add.redo_now();
}
@@ -254,10 +254,10 @@ bool LoadOTIOTask::Run()
block->setParent(sequence->parent());
// Position clip in its own context
sequence->parent()->SetNodePosition(block, block, QPointF(0, 0));
block->SetNodePositionInContext(block, QPointF(0, 0));
// Position footage in its context
sequence->parent()->SetNodePosition(probed_item, block, QPointF(-2, 0));
block->SetNodePositionInContext(probed_item, QPointF(-2, 0));
if (track->type() == Track::kVideo) {
@@ -266,14 +266,14 @@ bool LoadOTIOTask::Run()
Node::ConnectEdge(probed_item, NodeInput(transform, TransformDistortNode::kTextureInput));
Node::ConnectEdge(transform, NodeInput(block, ClipBlock::kBufferIn));
sequence->parent()->SetNodePosition(transform, block, QPointF(-1, 0));
block->SetNodePositionInContext(transform, QPointF(-1, 0));
} else {
VolumeNode* volume_node = new VolumeNode();
volume_node->setParent(sequence->parent());
Node::ConnectEdge(probed_item, NodeInput(volume_node, VolumeNode::kSamplesInput));
Node::ConnectEdge(volume_node, NodeInput(block, ClipBlock::kBufferIn));
sequence->parent()->SetNodePosition(volume_node, block, QPointF(-1, 0));
block->SetNodePositionInContext(volume_node, QPointF(-1, 0));
}
}
}
+2
View File
@@ -76,6 +76,7 @@ QIcon icon::Clock;
QIcon icon::Diamond;
QIcon icon::Plus;
QIcon icon::Minus;
QIcon icon::AddEffect;
void icon::LoadAll(const QString& theme)
{
@@ -129,6 +130,7 @@ void icon::LoadAll(const QString& theme)
Diamond = Create(theme, "diamond");
Plus = Create(theme, "plus");
Minus = Create(theme, "minus");
AddEffect = Create(theme, "add-effect");
}
QIcon icon::Create(const QString& theme, const QString &name)
+1
View File
@@ -86,6 +86,7 @@ extern QIcon Clock;
extern QIcon Diamond;
extern QIcon Plus;
extern QIcon Minus;
extern QIcon AddEffect;
/**
* @brief Create an icon object loaded from file
+17 -21
View File
@@ -24,34 +24,24 @@
namespace olive {
MultiUndoCommand::MultiUndoCommand() :
done_(false)
{
}
void MultiUndoCommand::redo()
{
if (!done_) {
for (auto it=children_.cbegin(); it!=children_.cend(); it++) {
(*it)->redo_and_set_modified();
}
done_ = true;
for (auto it=children_.cbegin(); it!=children_.cend(); it++) {
(*it)->redo_and_set_modified();
}
}
void MultiUndoCommand::undo()
{
if (done_) {
for (auto it=children_.crbegin(); it!=children_.crend(); it++) {
(*it)->undo_and_set_modified();
}
done_ = false;
for (auto it=children_.crbegin(); it!=children_.crend(); it++) {
(*it)->undo_and_set_modified();
}
}
UndoCommand::UndoCommand()
{
prepared_ = false;
done_ = false;
}
void UndoCommand::redo_and_set_modified()
@@ -76,17 +66,23 @@ void UndoCommand::undo_and_set_modified()
void UndoCommand::redo_now()
{
if (!prepared_) {
prepare();
prepared_ = true;
}
if (!done_) {
if (!prepared_) {
prepare();
prepared_ = true;
}
redo();
redo();
done_ = true;
}
}
void UndoCommand::undo_now()
{
undo();
if (done_) {
undo();
done_ = false;
}
}
}
+3 -3
View File
@@ -75,12 +75,14 @@ private:
bool prepared_;
bool done_;
};
class MultiUndoCommand : public UndoCommand
{
public:
MultiUndoCommand();
MultiUndoCommand() = default;
virtual Project* GetRelevantProject() const override
{
@@ -109,8 +111,6 @@ protected:
private:
std::vector<UndoCommand*> children_;
bool done_;
};
}
-1
View File
@@ -51,7 +51,6 @@ add_subdirectory(timelinewidget)
add_subdirectory(timeruler)
add_subdirectory(timetarget)
add_subdirectory(toolbar)
add_subdirectory(videoparamedit)
add_subdirectory(viewer)
set(OLIVE_SOURCES
+2 -4
View File
@@ -16,11 +16,9 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/curvewidget/beziercontrolpointitem.h
widget/curvewidget/beziercontrolpointitem.cpp
widget/curvewidget/curveview.h
widget/curvewidget/curveview.cpp
widget/curvewidget/curvewidget.h
widget/curvewidget/curveview.h
widget/curvewidget/curvewidget.cpp
widget/curvewidget/curvewidget.h
PARENT_SCOPE
)
@@ -1,106 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "beziercontrolpointitem.h"
#include <QApplication>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QWidget>
#include "common/qtutils.h"
namespace olive {
BezierControlPointItem::BezierControlPointItem(NodeKeyframe* key, NodeKeyframe::BezierType mode, QGraphicsItem *parent) :
QGraphicsRectItem(parent),
key_(key),
mode_(mode),
x_scale_(1.0),
y_scale_(1.0)
{
setFlag(QGraphicsItem::ItemIsMovable);
connect(key, &NodeKeyframe::TimeChanged, this, &BezierControlPointItem::UpdatePos);
if (mode_ == NodeKeyframe::kInHandle) {
connect(key, &NodeKeyframe::BezierControlInChanged, this, &BezierControlPointItem::UpdatePos);
} else {
connect(key, &NodeKeyframe::BezierControlOutChanged, this, &BezierControlPointItem::UpdatePos);
}
int control_point_size = QtUtils::QFontMetricsWidth(qApp->fontMetrics(), "o");
int half_sz = control_point_size / 2;
setRect(-half_sz, -half_sz, control_point_size, control_point_size);
}
void BezierControlPointItem::SetXScale(double scale)
{
x_scale_ = scale;
UpdatePos();
}
void BezierControlPointItem::SetYScale(double scale)
{
y_scale_ = scale;
UpdatePos();
}
NodeKeyframe* BezierControlPointItem::key() const
{
return key_;
}
const NodeKeyframe::BezierType &BezierControlPointItem::mode() const
{
return mode_;
}
QPointF BezierControlPointItem::GetCorrespondingKeyframeHandle() const
{
return key_->bezier_control(mode_);
}
void BezierControlPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
if (option->state & QStyle::State_Selected) {
painter->setPen(widget->palette().highlight().color());
} else {
painter->setPen(widget->palette().text().color());
}
painter->drawEllipse(rect());
}
void BezierControlPointItem::UpdatePos()
{
QPointF handle_offset = GetCorrespondingKeyframeHandle();
// Scale handle offset
handle_offset.setX(handle_offset.x() * x_scale_);
// Flip the Y coordinate because bezier curves are drawn bottom to top
handle_offset.setY(-handle_offset.y() * y_scale_);
setPos(handle_offset - rect().center());
}
}
@@ -1,68 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef BEZIERCONTROLPOINTITEM_H
#define BEZIERCONTROLPOINTITEM_H
#include <QGraphicsRectItem>
#include "node/keyframe.h"
namespace olive {
class BezierControlPointItem : public QObject, public QGraphicsRectItem
{
public:
BezierControlPointItem(NodeKeyframe* key, NodeKeyframe::BezierType mode, QGraphicsItem* parent = nullptr);
void SetXScale(double scale);
void SetYScale(double scale);
NodeKeyframe* key() const;
const NodeKeyframe::BezierType& mode() const;
QPointF GetCorrespondingKeyframeHandle() const;
void SetCorrespondingKeyframeHandle(const QPointF& handle);
void SetOpposingKeyframeHandle(const QPointF& handle);
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
private:
NodeKeyframe* key_;
NodeKeyframe::BezierType mode_;
double x_scale_;
double y_scale_;
private slots:
void UpdatePos();
};
}
#endif // BEZIERCONTROLPOINTITEM_H
+371 -203
View File
@@ -20,47 +20,32 @@
#include "curveview.h"
#include <cfloat>
#include <QHash>
#include <QMouseEvent>
#include <QPainterPath>
#include <QScrollBar>
#include <QtMath>
#include <cfloat>
#include "common/qtutils.h"
#include "widget/keyframeview/keyframeviewundo.h"
#include "widget/nodeparamview/nodeparamviewundo.h"
namespace olive {
#define super KeyframeViewBase
#define super KeyframeView
CurveView::CurveView(QWidget *parent) :
KeyframeViewBase(parent)
KeyframeView(parent),
dragging_bezier_pt_(nullptr)
{
setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
setDragMode(RubberBandDrag);
setViewportUpdateMode(FullViewportUpdate);
SetYAxisEnabled(true);
SetAutoSelectSiblings(false);
text_padding_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("i"));
minimum_grid_space_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("00000"));
connect(scene(), &QGraphicsScene::selectionChanged, this, &CurveView::SelectionChanged);
}
CurveView::~CurveView()
{
// Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other members have been destroyed
Clear();
}
void CurveView::Clear()
{
KeyframeViewBase::Clear();
foreach (QGraphicsLineItem* line, lines_) {
delete line;
}
lines_.clear();
}
void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref)
@@ -71,7 +56,12 @@ void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref)
}
// Add keyframes from track
AddKeyframesOfTrack(ref);
KeyframeViewInputConnection *track_con = AddKeyframesOfTrack(ref);
track_con->SetBrush(keyframe_colors_.value(ref));
track_connections_.insert(ref, track_con);
// Signal to CurveWidget to update its bezier/linear/hold buttons if a key type changes
connect(track_con, &KeyframeViewInputConnection::TypeChanged, this, &CurveView::SelectionChanged);
// Append to the list
connected_inputs_.append(ref);
@@ -85,7 +75,7 @@ void CurveView::DisconnectInput(const NodeKeyframeTrackReference& ref)
}
// Remove keyframes belonging to this element and track
RemoveKeyframesOfTrack(ref);
RemoveKeyframesOfTrack(track_connections_.take(ref));
// Remove from the list
connected_inputs_.removeOne(ref);
@@ -95,36 +85,21 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref)
{
DeselectAll();
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
if (it.key()->key_track_ref() == ref) {
it.value()->setSelected(true);
foreach (KeyframeViewInputConnection *con, track_connections_) {
foreach (NodeKeyframe *key, con->GetKeyframes()) {
SelectKeyframe(key);
}
}
}
void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref)
{
QList<NodeKeyframe*> keys;
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
if (it.key()->key_track_ref() == ref) {
keys.append(it.key());
}
}
ZoomToFitInternal(keys);
}
void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color)
{
// Insert color into hashmap
keyframe_colors_.insert(ref, color);
// Update all keyframes
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
if (it.key()->key_track_ref() == ref) {
it.value()->SetOverrideBrush(color);
}
if (KeyframeViewInputConnection *con = track_connections_.value(ref)) {
// Update all keyframes
con->SetBrush(color);
}
}
@@ -196,7 +171,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
QPainterPath path;
// Draw straight line leading to first keyframe
QPointF first_key_pos = item_map().value(track.first())->pos();
QPointF first_key_pos = GetKeyframePosition(track.first());
path.moveTo(QPointF(scene_bottom_left.x(), first_key_pos.y()));
path.lineTo(first_key_pos);
@@ -205,24 +180,24 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
NodeKeyframe* before = track.at(i-1);
NodeKeyframe* after = track.at(i);
KeyframeViewItem* before_item = item_map().value(before);
KeyframeViewItem* after_item = item_map().value(after);
QPointF before_pos = GetKeyframePosition(before);
QPointF after_pos = GetKeyframePosition(after);
if (before->type() == NodeKeyframe::kHold) {
// Draw a hold keyframe (basically a right angle)
path.lineTo(after_item->pos().x(), before_item->pos().y());
path.lineTo(after_item->pos().x(), after_item->pos().y());
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_item->pos() + ScalePoint(before->valid_bezier_control_out());
QPointF after_control_point = after_item->pos() + ScalePoint(after->valid_bezier_control_in());
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_item->pos());
path.cubicTo(before_control_point, after_control_point, after_pos);
} else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) {
// Draw a quadratic bezier
@@ -232,10 +207,10 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
QPointF control_point;
if (before->type() == NodeKeyframe::kBezier) {
key_anchor = before_item->pos();
key_anchor = before_pos;
control_point = before->valid_bezier_control_out();
} else {
key_anchor = after_item->pos();
key_anchor = after_pos;
control_point = after->valid_bezier_control_in();
}
@@ -243,66 +218,31 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
control_point = key_anchor + ScalePoint(control_point);
// Create the path from both keyframes
path.quadTo(control_point, after_item->pos());
path.quadTo(control_point, after_pos);
} else {
// Linear to linear
path.lineTo(after_item->pos());
path.lineTo(after_pos);
}
}
// Draw straight line leading from end keyframe
QPointF last_key_pos = item_map().value(track.last())->pos();
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<QLineF> bezier_lines;
foreach (BezierControlPointItem* item, bezier_control_points_) {
// All BezierControlPointItems should be children of a KeyframeViewItem
KeyframeViewItem* par = static_cast<KeyframeViewItem*>(item->parentItem());
bezier_lines.append(QLineF(par->pos(), par->pos() + item->pos()));
}
painter->drawLines(bezier_lines);
}
}
void CurveView::KeyframeAboutToBeRemoved(NodeKeyframe *key)
void CurveView::drawForeground(QPainter *painter, const QRectF &rect)
{
disconnect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged);
disconnect(key, &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged);
}
bezier_pts_.clear();
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)
for (auto iterator=item_map().begin();iterator!=item_map().end();iterator++) {
SetItemYFromKeyframeValue(iterator.value()->key(), iterator.value());
}
foreach (BezierControlPointItem* item, bezier_control_points_) {
item->SetYScale(scale);
}
super::drawForeground(painter, rect);
}
void CurveView::ContextMenuEvent(Menu &m)
@@ -320,62 +260,334 @@ void CurveView::ContextMenuEvent(Menu &m)
void CurveView::SceneRectUpdateEvent(QRectF &r)
{
r.setTop(r.top() - this->height());
r.setBottom(r.bottom() + this->height());
double min_val, max_val;
bool got_val = false;
foreach (KeyframeViewInputConnection *con, track_connections_) {
foreach (NodeKeyframe *key, con->GetKeyframes()) {
qreal key_y = GetItemYFromKeyframeValue(key);
if (got_val) {
min_val = qMin(key_y, min_val);
max_val = qMax(key_y, max_val);
} else {
min_val = key_y;
max_val = key_y;
got_val = true;
}
}
}
if (got_val) {
r.setTop(min_val - this->height());
r.setBottom(max_val + this->height());
}
}
void CurveView::ZoomToFitInternal(const QList<NodeKeyframe *> &keys)
qreal CurveView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key)
{
if (keys.isEmpty()) {
// Prevent scaling to DBL_MIN/DBL_MAX
return GetItemYFromKeyframeValue(key);
}
void CurveView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect)
{
if (IsKeyframeSelected(key) && key->type() == NodeKeyframe::kBezier) {
// Draw bezier control points if keyframe is selected
int control_point_size = QtUtils::QFontMetricsWidth(fontMetrics(), "o");
int half_sz = control_point_size / 2;
QRectF control_point_rect(-half_sz, -half_sz, control_point_size, control_point_size);
painter->setPen(palette().text().color());
painter->setBrush(Qt::NoBrush);
QRectF cp_in = control_point_rect.translated(key_rect.center() + ScalePoint(key->bezier_control_in()));
QRectF cp_out = control_point_rect.translated(key_rect.center() + ScalePoint(key->bezier_control_out()));
painter->drawLine(key_rect.center(), cp_in.center());
painter->drawLine(key_rect.center(), cp_out.center());
painter->drawEllipse(cp_in);
painter->drawEllipse(cp_out);
bezier_pts_.append({cp_in, key, NodeKeyframe::kInHandle});
bezier_pts_.append({cp_out, key, NodeKeyframe::kOutHandle});
}
super::DrawKeyframe(painter, key, track, key_rect);
}
bool CurveView::FirstChanceMousePress(QMouseEvent *event)
{
dragging_bezier_pt_ = nullptr;
QPointF scene_pt = mapToScene(event->pos());
foreach (const BezierPoint &b, bezier_pts_) {
if (b.rect.contains(scene_pt)) {
dragging_bezier_pt_ = &b;
break;
}
}
if (dragging_bezier_pt_) {
NodeKeyframe *key = dragging_bezier_pt_->keyframe;
dragging_bezier_point_start_ = (dragging_bezier_pt_->type == NodeKeyframe::kInHandle) ? key->bezier_control_in() : key->bezier_control_out();
dragging_bezier_point_opposing_start_ = (dragging_bezier_pt_->type == NodeKeyframe::kInHandle) ? key->bezier_control_out() : key->bezier_control_in();
drag_start_ = mapToScene(event->pos());
return true;
} else {
return false;
}
}
void CurveView::FirstChanceMouseMove(QMouseEvent *event)
{
// 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);
}
// 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_pt_->type,
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_pt_->type);
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_pt_->keyframe->set_bezier_control(dragging_bezier_pt_->type,
new_bezier_pos);
dragging_bezier_pt_->keyframe->set_bezier_control(opposing_type,
new_opposing_pos);
Redraw();
}
void CurveView::FirstChanceMouseRelease(QMouseEvent *event)
{
MultiUndoCommand* command = new MultiUndoCommand();
// Create undo command with the current bezier point and the old one
command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_pt_->keyframe,
dragging_bezier_pt_->type,
dragging_bezier_pt_->keyframe->bezier_control(dragging_bezier_pt_->type),
dragging_bezier_point_start_));
if (!(event->modifiers() & Qt::ControlModifier)) {
auto opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type);
command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_pt_->keyframe,
opposing_type,
dragging_bezier_pt_->keyframe->bezier_control(opposing_type),
dragging_bezier_point_opposing_start_));
}
dragging_bezier_pt_ = nullptr;
Core::instance()->undo_stack()->push(command);
}
void CurveView::KeyframeDragStart(QMouseEvent *event)
{
drag_keyframe_values_.resize(GetSelectedKeyframes().size());
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
NodeKeyframe *key = GetSelectedKeyframes().at(i);
drag_keyframe_values_[i] = key->value();
}
drag_start_ = mapToScene(event->pos());
}
void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip)
{
if (event->modifiers() & Qt::ShiftModifier) {
// Lock to X axis only and set original values on all keys
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
NodeKeyframe *key = GetSelectedKeyframes().at(i);
key->set_value(drag_keyframe_values_.at(i));
}
return;
}
rational min_time = RATIONAL_MAX;
rational max_time = RATIONAL_MIN;
// Calculate cursor difference
double scaled_diff = (mapToScene(event->pos()).y() - drag_start_.y()) / GetYScale();
double min_val = DBL_MAX;
double max_val = DBL_MIN;
// Validate movement - ensure no keyframe goes above its max point or below its min point
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
NodeKeyframe *key = GetSelectedKeyframes().at(i);
foreach (NodeKeyframe* key, keys) {
rational transformed_time = GetAdjustedTime(key->parent(),
GetTimeTarget(),
key->time(),
false);
FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key);
Node* node = key->parent();
double original_val = FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display);
const QString& input = key->input();
double new_val = FloatSlider::TransformDisplayToValue(original_val - scaled_diff, display);
double limited = new_val;
min_time = qMin(transformed_time, min_time);
max_time = qMax(transformed_time, max_time);
if (node->HasInputProperty(input, QStringLiteral("min"))) {
limited = qMax(limited, node->GetInputProperty(input, QStringLiteral("min")).toDouble());
}
min_val = qMin(key->value().toDouble(), min_val);
max_val = qMax(key->value().toDouble(), max_val);
if (node->HasInputProperty(input, QStringLiteral("max"))) {
limited = qMin(limited, node->GetInputProperty(input, QStringLiteral("max")).toDouble());
}
if (limited != new_val) {
scaled_diff = original_val - limited;
}
}
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);
// Set values
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
NodeKeyframe *key = GetSelectedKeyframes().at(i);
FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key);
key->set_value(FloatSlider::TransformDisplayToValue(FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display) - scaled_diff, display));
}
emit ScaleChanged(new_x_scale);
SetYScale(new_y_scale);
NodeKeyframe *tip_item = GetSelectedKeyframes().first();
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())));
bool ok;
double num_value = tip_item->value().toDouble(&ok);
if (ok) {
tip = QStringLiteral("%1\n");
tip.append(FloatSlider::ValueToString(num_value + GetOffsetFromKeyframe(tip_item), GetFloatDisplayTypeFromKeyframe(tip_item), 2, true));
}
}
void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command)
{
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
NodeKeyframe *k = GetSelectedKeyframes().at(i);
if (!qFuzzyCompare(k->value().toDouble(), drag_keyframe_values_.at(i).toDouble())) {
command->add_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i)));
}
}
}
QPointF CurveView::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 CurveView::GetScaledCursorPos(const QPointF &cursor_pos)
{
return QPointF(cursor_pos.x() / GetScale(),
cursor_pos.y() / GetYScale());
}
void CurveView::ZoomToFitInternal(bool selected_only)
{
bool got_val = false;
rational min_time, max_time;
double min_val, max_val;
foreach (KeyframeViewInputConnection *con, track_connections_) {
foreach (NodeKeyframe *key, con->GetKeyframes()) {
if (!selected_only || IsKeyframeSelected(key)) {
rational transformed_time = GetAdjustedTime(key->parent(),
GetTimeTarget(),
key->time(),
false);
qreal key_y = GetUnscaledItemYFromKeyframeValue(key);
if (got_val) {
min_time = qMin(transformed_time, min_time);
max_time = qMax(transformed_time, max_time);
min_val = qMin(key_y, min_val);
max_val = qMax(key_y, max_val);
} else {
min_time = transformed_time;
max_time = transformed_time;
min_val = key_y;
max_val = key_y;
got_val = true;
}
}
}
}
// Prevent scaling if no keyframes were found
if (got_val) {
QRectF desired(QPointF(min_time.toDouble(), min_val), QPointF(max_time.toDouble(), max_val));
const double scale_divider = 0.5;
double scale_half_divider = scale_divider*0.5;
double new_x_scale = viewport()->width() / desired.width() * scale_divider;
double new_y_scale;
if (qFuzzyIsNull(desired.height())) {
// Catch divide by zero
new_y_scale = 1.0;
scale_half_divider = 0.5;
} else {
// Use height as normal
new_y_scale = viewport()->height() / desired.height() * scale_divider;
}
emit ScaleChanged(new_x_scale);
SetYScale(new_y_scale);
UpdateSceneRect();
int sb_x = desired.left() * new_x_scale - viewport()->width() * scale_half_divider;
QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_x));
int sb_y = desired.top() * new_y_scale - viewport()->height() * scale_half_divider;
QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_y));
}
}
qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key)
{
return GetItemYFromKeyframeValue(key->value().toDouble());
return GetUnscaledItemYFromKeyframeValue(key) * GetYScale();
}
qreal CurveView::GetItemYFromKeyframeValue(double value)
qreal CurveView::GetUnscaledItemYFromKeyframeValue(NodeKeyframe *key)
{
return -value * GetYScale();
}
double val = key->value().toDouble();
void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *item)
{
item->SetOverrideY(GetItemYFromKeyframeValue(key));
val = FloatSlider::TransformValueToDisplay(val, GetFloatDisplayTypeFromKeyframe(key));
val += GetOffsetFromKeyframe(key);
return -val;
}
QPointF CurveView::ScalePoint(const QPointF &point)
@@ -384,80 +596,48 @@ QPointF CurveView::ScalePoint(const QPointF &point)
return QPointF(point.x() * GetScale(), - point.y() * GetYScale());
}
void CurveView::CreateBezierControlPoints(KeyframeViewItem* item)
FloatSlider::DisplayType CurveView::GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key)
{
BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item->key(), 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->key(), 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);
}
void CurveView::KeyframeValueChanged()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
KeyframeViewItem* item = item_map().value(key);
SetItemYFromKeyframeValue(key, item);
}
void CurveView::KeyframeTypeChanged()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
KeyframeViewItem* item = item_map().value(key);
if (item->isSelected()) {
item->setSelected(false);
item->setSelected(true);
}
}
void CurveView::SelectionChanged()
{
// Clear current bezier handles
while (!bezier_control_points_.isEmpty()) {
delete bezier_control_points_.first();
Node* node = key->parent();
const QString& input = key->input();
if (node->HasInputProperty(input, QStringLiteral("view"))) {
// Try to get view from input (which will be normal if unset)
return static_cast<FloatSlider::DisplayType>(node->GetInputProperty(input, QStringLiteral("view")).toInt());
}
QList<QGraphicsItem*> selected = scene()->selectedItems();
foreach (QGraphicsItem* item, selected) {
KeyframeViewItem* this_item = static_cast<KeyframeViewItem*>(item);
if (this_item->key()->type() == NodeKeyframe::kBezier) {
CreateBezierControlPoints(this_item);
}
}
// Fallback to normal
return FloatSlider::kNormal;
}
void CurveView::BezierControlPointDestroyed()
double CurveView::GetOffsetFromKeyframe(NodeKeyframe *key)
{
BezierControlPointItem* item = static_cast<BezierControlPointItem*>(sender());
bezier_control_points_.removeOne(item);
Node *node = key->parent();
const QString &input = key->input();
if (node->HasInputProperty(input, QStringLiteral("offset"))) {
QVariant v = node->GetInputProperty(input, QStringLiteral("offset"));
// NOTE: Implement getting correct offset for the track based on the data type
QVector<QVariant> track_vals = NodeValue::split_normal_value_into_track_values(node->GetInputDataType(input), v);
return track_vals.at(key->track()).toDouble();
}
return 0;
}
QPointF CurveView::GetKeyframePosition(NodeKeyframe *key)
{
return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key));
}
void CurveView::ZoomToFit()
{
ZoomToFitInternal(item_map().keys());
ZoomToFitInternal(false);
}
void CurveView::ZoomToFitSelected()
{
QList<NodeKeyframe*> selected_keys;
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
if (it.value()->isSelected()) {
selected_keys.append(it.key());
}
}
ZoomToFitInternal(selected_keys);
ZoomToFitInternal(true);
}
void CurveView::ResetZoom()
@@ -466,16 +646,4 @@ void CurveView::ResetZoom()
SetYScale(1.0);
}
KeyframeViewItem* CurveView::AddKeyframe(NodeKeyframe* key)
{
KeyframeViewItem* item = super::AddKeyframe(key);
SetItemYFromKeyframeValue(key, item);
item->SetOverrideBrush(keyframe_colors_.value(key->key_track_ref()));
connect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged);
connect(key, &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged);
return item;
}
}
+41 -31
View File
@@ -21,36 +21,27 @@
#ifndef CURVEVIEW_H
#define CURVEVIEW_H
#include "beziercontrolpointitem.h"
#include "node/keyframe.h"
#include "widget/keyframeview/keyframeview.h"
#include "widget/keyframeview/keyframeviewitem.h"
#include "widget/slider/floatslider.h"
namespace olive {
class CurveView : public KeyframeViewBase
class CurveView : public KeyframeView
{
Q_OBJECT
public:
CurveView(QWidget* parent = nullptr);
virtual ~CurveView() override;
virtual void Clear() override;
void ConnectInput(const NodeKeyframeTrackReference &ref);
void DisconnectInput(const NodeKeyframeTrackReference &ref);
void SelectKeyframesOfInput(const NodeKeyframeTrackReference &ref);
void ZoomToFitInput(const NodeKeyframeTrackReference &ref);
void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color);
public slots:
virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key) override;
void ZoomToFit();
void ZoomToFitSelected();
@@ -59,51 +50,70 @@ public slots:
protected:
virtual void drawBackground(QPainter* painter, const QRectF& rect) override;
virtual void KeyframeAboutToBeRemoved(NodeKeyframe *key) override;
virtual void ScaleChangedEvent(const double &scale) override;
virtual void VerticalScaleChangedEvent(double scale) override;
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
virtual void ContextMenuEvent(Menu &m) override;
virtual void SceneRectUpdateEvent(QRectF &r) override;
virtual qreal GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) override;
virtual void DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) override;
virtual bool FirstChanceMousePress(QMouseEvent *event) override;
virtual void FirstChanceMouseMove(QMouseEvent *event) override;
virtual void FirstChanceMouseRelease(QMouseEvent *event) override;
virtual void KeyframeDragStart(QMouseEvent *event) override;
virtual void KeyframeDragMove(QMouseEvent *event, QString &tip) override;
virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) override;
private:
void ZoomToFitInternal(const QList<NodeKeyframe *> &keys);
void ZoomToFitInternal(bool selected_only);
qreal GetItemYFromKeyframeValue(NodeKeyframe* key);
qreal GetItemYFromKeyframeValue(double value);
void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item);
qreal GetUnscaledItemYFromKeyframeValue(NodeKeyframe* key);
QPointF ScalePoint(const QPointF& point);
static FloatSlider::DisplayType GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key);
static double GetOffsetFromKeyframe(NodeKeyframe *key);
void AdjustLines();
void CreateBezierControlPoints(KeyframeViewItem *item);
QPointF GetKeyframePosition(NodeKeyframe *key);
static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode,
const QPointF& start_point,
const QPointF& scaled_cursor_diff);
QPointF GetScaledCursorPos(const QPointF &cursor_pos);
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
QHash<NodeKeyframeTrackReference, KeyframeViewInputConnection*> track_connections_;
int text_padding_;
int minimum_grid_space_;
QVector<QGraphicsLineItem*> lines_;
QVector<BezierControlPointItem*> bezier_control_points_;
QVector<NodeKeyframeTrackReference> connected_inputs_;
private slots:
void KeyframeValueChanged();
struct BezierPoint
{
QRectF rect;
NodeKeyframe *keyframe;
NodeKeyframe::BezierType type;
};
void KeyframeTypeChanged();
QVector<BezierPoint> bezier_pts_;
const BezierPoint *dragging_bezier_pt_;
void SelectionChanged();
QPointF dragging_bezier_point_start_;
QPointF dragging_bezier_point_opposing_start_;
QPointF drag_start_;
void BezierControlPointDestroyed();
QVector<QVariant> drag_keyframe_values_;
};
+80 -127
View File
@@ -34,8 +34,10 @@
namespace olive {
#define super TimeBasedWidget
CurveWidget::CurveWidget(QWidget *parent) :
TimeBasedWidget(parent)
super(parent)
{
QHBoxLayout* outer_layout = new QHBoxLayout(this);
@@ -45,10 +47,7 @@ CurveWidget::CurveWidget(QWidget *parent) :
tree_view_ = new NodeTreeView();
tree_view_->SetOnlyShowKeyframable(true);
tree_view_->SetShowKeyframeTracksAsRows(true);
connect(tree_view_, &NodeTreeView::NodeEnableChanged, this, &CurveWidget::NodeEnabledChanged);
connect(tree_view_, &NodeTreeView::InputEnableChanged, this, &CurveWidget::InputEnabledChanged);
connect(tree_view_, &NodeTreeView::InputSelectionChanged, this, &CurveWidget::InputSelectionChanged);
connect(tree_view_, &NodeTreeView::InputDoubleClicked, this, &CurveWidget::InputDoubleClicked);
splitter->addWidget(tree_view_);
QWidget* workarea = new QWidget();
@@ -99,7 +98,7 @@ CurveWidget::CurveWidget(QWidget *parent) :
// Connect ruler and view together
connect(view_, &CurveView::TimeChanged, this, &CurveWidget::SetTimeAndSignal);
connect(view_->scene(), &QGraphicsScene::selectionChanged, this, &CurveWidget::SelectionChanged);
connect(view_, &CurveView::SelectionChanged, this, &CurveWidget::SelectionChanged);
connect(view_, &CurveView::ScaleChanged, this, &CurveWidget::SetScale);
connect(view_, &CurveView::Dragged, this, &CurveWidget::KeyframeViewDragged);
@@ -113,12 +112,6 @@ CurveWidget::CurveWidget(QWidget *parent) :
SetScale(120.0);
}
CurveWidget::~CurveWidget()
{
// Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other members have been destroyed
view_->Clear();
}
const double &CurveWidget::GetVerticalScale()
{
return view_->GetYScale();
@@ -138,27 +131,38 @@ void CurveWidget::SetNodes(const QVector<Node *> &nodes)
{
tree_view_->SetNodes(nodes);
// Detect removed nodes
foreach (Node* n, nodes_) {
if (!nodes.contains(n)) {
ConnectNode(n, false);
}
}
// Detect added nodes
foreach (Node* n, nodes) {
if (tree_view_->IsNodeEnabled(n) && !nodes_.contains(n)) {
ConnectNode(n, true);
}
}
// Save new node list
nodes_ = nodes;
// Generate colors
foreach (Node *node, nodes_) {
foreach (const QString& input, node->inputs()) {
if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) {
int arr_sz = node->InputArraySize(input);
for (int i=-1; i<arr_sz; i++) {
// Generate a random color for this input
const QVector<NodeKeyframeTrack>& tracks = node->GetKeyframeTracks(input, i);
for (int j=0; j<tracks.size(); j++) {
NodeKeyframeTrackReference ref(NodeInput(node, input, i), j);
if (!keyframe_colors_.contains(ref)) {
QColor c = QColor::fromHsl(std::rand()%360, 255, 160);
keyframe_colors_.insert(ref, c);
tree_view_->SetKeyframeTrackColor(ref, c);
view_->SetKeyframeTrackColor(ref, c);
}
}
}
}
}
}
}
void CurveWidget::TimeChangedEvent(const rational &time)
{
TimeBasedWidget::TimeChangedEvent(time);
super::TimeChangedEvent(time);
view_->SetTime(time);
UpdateBridgeTime(time);
@@ -166,20 +170,22 @@ void CurveWidget::TimeChangedEvent(const rational &time)
void CurveWidget::TimebaseChangedEvent(const rational &timebase)
{
TimeBasedWidget::TimebaseChangedEvent(timebase);
super::TimebaseChangedEvent(timebase);
view_->SetTimebase(timebase);
}
void CurveWidget::ScaleChangedEvent(const double &scale)
{
TimeBasedWidget::ScaleChangedEvent(scale);
super::ScaleChangedEvent(scale);
view_->SetScale(scale);
}
void CurveWidget::TimeTargetChangedEvent(Node *target)
{
TimeTargetObject::TimeTargetChangedEvent(target);
key_control_->SetTimeTarget(target);
view_->SetTimeTarget(target);
@@ -187,6 +193,8 @@ void CurveWidget::TimeTargetChangedEvent(Node *target)
void CurveWidget::ConnectedNodeChangeEvent(ViewerOutput *n)
{
super::ConnectedNodeChangeEvent(n);
SetTimeTarget(n);
}
@@ -216,91 +224,47 @@ void CurveWidget::UpdateBridgeTime(const rational &time)
key_control_->SetTime(time);
}
void CurveWidget::ConnectNode(Node *node, bool connect)
void CurveWidget::ConnectInput(Node *node, const QString &input, int element)
{
foreach (const QString& input, node->inputs()) {
if (node->IsInputKeyframable(input)) {
ConnectInput(node, input, connect);
if (element == -1 && node->InputIsArray(input)) {
// This is the root element, connect all elements (if applicable)
int arr_sz = node->InputArraySize(input);
for (int i=-1; i<arr_sz; i++) {
ConnectInputInternal(node, input, i);
}
}
// Connect add/remove signals
if (connect) {
QObject::connect(node, &Node::KeyframeAdded, this, &CurveWidget::AddKeyframe);
QObject::connect(node, &Node::KeyframeRemoved, this, &CurveWidget::RemoveKeyframe);
} else {
QObject::disconnect(node, &Node::KeyframeAdded, this, &CurveWidget::AddKeyframe);
QObject::disconnect(node, &Node::KeyframeRemoved, this, &CurveWidget::RemoveKeyframe);
// This is a single element, just connect it as-is
ConnectInputInternal(node, input, element);
}
}
void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect)
void CurveWidget::ConnectInputInternal(Node *node, const QString &input, int element)
{
if (!node->IsInputKeyframable(input)) {
qWarning() << "Tried to connect input that isn't keyframable";
return;
}
int track_count = NodeValue::get_number_of_keyframe_tracks(node->GetInputDataType(input));
bool multiple_tracks = track_count > 1;
int arr_sz = node->InputArraySize(input);
for (int i=-1; i<arr_sz; i++) {
// Generate a random color for this input
const QVector<NodeKeyframeTrack>& tracks = node->GetKeyframeTracks(input, i);
for (int j=0; j<tracks.size(); j++) {
NodeKeyframeTrackReference ref(NodeInput(node, input, i), j);
if (!keyframe_colors_.contains(ref)) {
QColor c = QColor::fromHsv(std::rand()%360, std::rand()%255, 255);
keyframe_colors_.insert(ref, c);
tree_view_->SetKeyframeTrackColor(ref, c);
view_->SetKeyframeTrackColor(ref, c);
}
}
if (tree_view_->IsInputEnabled(NodeKeyframeTrackReference(NodeInput(node, input, i), multiple_tracks ? -1 : 0))) {
if (multiple_tracks) {
for (int j=0; j<track_count; j++) {
NodeKeyframeTrackReference ref(NodeInput(node, input, i), j);
if (tree_view_->IsInputEnabled(ref)) {
if (connect) {
view_->ConnectInput(ref);
} else {
view_->DisconnectInput(ref);
}
}
}
} else {
NodeKeyframeTrackReference ref(NodeInput(node, input, i), 0);
if (connect) {
view_->ConnectInput(ref);
} else {
view_->DisconnectInput(ref);
}
}
}
NodeInput input_ref(node, input, element);
int track_count = NodeValue::get_number_of_keyframe_tracks(input_ref.GetDataType());
for (int i=0; i<track_count; i++) {
NodeKeyframeTrackReference track_ref(input_ref, i);
view_->ConnectInput(track_ref);
selected_tracks_.append(track_ref);
}
}
void CurveWidget::SelectionChanged()
{
QList<QGraphicsItem*> selected = view_->scene()->selectedItems();
const QVector<NodeKeyframe*> &selected = view_->GetSelectedKeyframes();
SetKeyframeButtonChecked(false);
SetKeyframeButtonEnabled(!selected.isEmpty());
if (!selected.isEmpty()) {
bool all_same_type = true;
NodeKeyframe::Type type = static_cast<KeyframeViewItem*>(selected.first())->key()->type();
NodeKeyframe::Type type = selected.first()->type();
for (int i=1;i<selected.size();i++) {
KeyframeViewItem* prev_item = static_cast<KeyframeViewItem*>(selected.at(i-1));
KeyframeViewItem* this_item = static_cast<KeyframeViewItem*>(selected.at(i));
NodeKeyframe* prev_item = selected.at(i-1);
NodeKeyframe* this_item = selected.at(i);
if (prev_item->key()->type() != this_item->key()->type()) {
if (prev_item->type() != this_item->type()) {
all_same_type = false;
break;
}
@@ -323,7 +287,7 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked)
}
// Get selected items and do nothing if there are none
QList<QGraphicsItem*> selected = view_->scene()->selectedItems();
const QVector<NodeKeyframe*> &selected = view_->GetSelectedKeyframes();
if (selected.isEmpty()) {
return;
}
@@ -345,51 +309,40 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked)
MultiUndoCommand* command = new MultiUndoCommand();
foreach (QGraphicsItem* item, selected) {
KeyframeViewItem* key_item = static_cast<KeyframeViewItem*>(item);
command->add_child(new KeyframeSetTypeCommand(key_item->key(), new_type));
foreach (NodeKeyframe* item, selected) {
command->add_child(new KeyframeSetTypeCommand(item, new_type));
}
Core::instance()->undo_stack()->push(command);
}
void CurveWidget::NodeEnabledChanged(Node* n, bool e)
{
ConnectNode(n, e);
}
void CurveWidget::InputEnabledChanged(const NodeKeyframeTrackReference& ref, bool e)
{
if (e) {
view_->ConnectInput(ref);
} else {
view_->DisconnectInput(ref);
}
}
void CurveWidget::AddKeyframe(NodeKeyframe *key)
{
view_->AddKeyframe(key);
}
void CurveWidget::RemoveKeyframe(NodeKeyframe *key)
{
view_->RemoveKeyframe(key);
}
void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref)
{
key_control_->SetInput(ref.input());
if (ref.IsValid()) {
view_->SelectKeyframesOfInput(ref);
foreach (const NodeKeyframeTrackReference &c, selected_tracks_) {
view_->DisconnectInput(c);
}
}
void CurveWidget::InputDoubleClicked(const NodeKeyframeTrackReference& ref)
{
view_->ZoomToFitInput(ref);
selected_tracks_.clear();
if (ref.IsValid() && !ref.input().IsArray()) {
// This reference is a track, connect it only
view_->ConnectInput(ref);
selected_tracks_.append(ref);
} else if (ref.input().IsValid()) {
// This reference is a input, connect all tracks
ConnectInput(ref.input().node(), ref.input().input(), ref.input().element());
} else if (Node *node = ref.input().node()) {
// This is a node, add all inputs
foreach (const QString &input, node->inputs()) {
if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) {
ConnectInput(node, input, -1);
}
}
}
view_->ZoomToFit();
}
void CurveWidget::KeyframeViewDragged(int x, int y)
+4 -14
View File
@@ -40,8 +40,6 @@ class CurveWidget : public TimeBasedWidget, public TimeTargetObject
public:
CurveWidget(QWidget* parent = nullptr);
virtual ~CurveWidget() override;
const double& GetVerticalScale();
void SetVerticalScale(const double& vscale);
@@ -78,9 +76,9 @@ private:
void UpdateBridgeTime(const rational &time);
void ConnectNode(Node* node, bool connect);
void ConnectInput(Node *node, const QString &input, int element);
void ConnectInput(Node* node, const QString& input, bool connect);
void ConnectInputInternal(Node *node, const QString &input, int element);
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
@@ -98,23 +96,15 @@ private:
QVector<Node*> nodes_;
QVector<NodeKeyframeTrackReference> selected_tracks_;
private slots:
void SelectionChanged();
void KeyframeTypeButtonTriggered(bool checked);
void NodeEnabledChanged(Node* n, bool e);
void InputEnabledChanged(const NodeKeyframeTrackReference &ref, bool e);
void AddKeyframe(NodeKeyframe* key);
void RemoveKeyframe(NodeKeyframe* key);
void InputSelectionChanged(const NodeKeyframeTrackReference& ref);
void InputDoubleClicked(const NodeKeyframeTrackReference& ref);
void KeyframeViewDragged(int x, int y);
void CatchUpYScrollToPoint(int point);
+4 -6
View File
@@ -16,13 +16,11 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/keyframeview/keyframeview.h
widget/keyframeview/keyframeview.cpp
widget/keyframeview/keyframeviewbase.h
widget/keyframeview/keyframeviewbase.cpp
widget/keyframeview/keyframeviewitem.h
widget/keyframeview/keyframeviewitem.cpp
widget/keyframeview/keyframeviewundo.h
widget/keyframeview/keyframeview.h
widget/keyframeview/keyframeviewinputconnection.cpp
widget/keyframeview/keyframeviewinputconnection.h
widget/keyframeview/keyframeviewundo.cpp
widget/keyframeview/keyframeviewundo.h
PARENT_SCOPE
)
+501 -13
View File
@@ -20,40 +20,528 @@
#include "keyframeview.h"
#include <QMouseEvent>
#include <QToolTip>
#include <QVBoxLayout>
#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 KeyframeViewBase
#define super TimeBasedView
KeyframeView::KeyframeView(QWidget *parent) :
KeyframeViewBase(parent),
max_scroll_(0)
super(parent),
selection_manager_(this),
autoselect_siblings_(true),
max_scroll_(0),
first_chance_mouse_event_(false)
{
setAlignment(Qt::AlignLeft | Qt::AlignTop);
SetDefaultDragMode(RubberBandDrag);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &KeyframeView::customContextMenuRequested, this, &KeyframeView::ShowContextMenu);
}
void KeyframeView::SetElementY(const NodeInput &c, int y)
void KeyframeView::DeleteSelected()
{
qreal scene_y = mapToScene(mapFromGlobal(QPoint(0, y))).y();
MultiUndoCommand* command = new MultiUndoCommand();
element_y_.insert(c, scene_y);
foreach (NodeKeyframe *key, GetSelectedKeyframes()) {
command->add_child(new NodeParamRemoveKeyframeCommand(key));
}
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
if (it.key()->key_track_ref().input() == c) {
it.value()->SetOverrideY(scene_y);
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
KeyframeView::NodeConnections KeyframeView::AddKeyframesOfNode(Node *n)
{
NodeConnections map;
foreach (const QString& i, n->inputs()) {
map.insert(i, AddKeyframesOfInput(n, i));
}
return map;
}
KeyframeView::InputConnections KeyframeView::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<arr_sz; i++) {
vec[i+1] = AddKeyframesOfElement(NodeInput(n, input, i));
}
}
return vec;
}
KeyframeView::ElementConnections KeyframeView::AddKeyframesOfElement(const NodeInput& input)
{
const QVector<NodeKeyframeTrack>& tracks = input.node()->GetKeyframeTracks(input);
ElementConnections vec(tracks.size());
for (int i=0; i<tracks.size(); i++) {
vec[i] = AddKeyframesOfTrack(NodeKeyframeTrackReference(input, i));
}
return vec;
}
KeyframeViewInputConnection *KeyframeView::AddKeyframesOfTrack(const NodeKeyframeTrackReference& ref)
{
KeyframeViewInputConnection *track = new KeyframeViewInputConnection(ref, this);
connect(track, &KeyframeViewInputConnection::RequireUpdate, this, &KeyframeView::Redraw);
tracks_.append(track);
Redraw();
return track;
}
void KeyframeView::RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection)
{
if (tracks_.removeOne(connection)) {
foreach (NodeKeyframe *key, connection->GetKeyframes()) {
selection_manager_.Deselect(key);
}
delete connection;
Redraw();
emit SelectionChanged();
}
}
void KeyframeView::SelectAll()
{
foreach (KeyframeViewInputConnection *track, tracks_) {
foreach (NodeKeyframe *key, track->GetKeyframes()) {
SelectKeyframe(key);
}
}
}
void KeyframeView::DeselectAll()
{
selection_manager_.ClearSelection();
Redraw();
}
void KeyframeView::Clear()
{
if (!tracks_.isEmpty()) {
qDeleteAll(tracks_);
tracks_.clear();
Redraw();
}
selection_manager_.ClearSelection();
}
void KeyframeView::SelectionManagerSelectEvent(void *obj)
{
if (autoselect_siblings_) {
NodeKeyframe *key = static_cast<NodeKeyframe*>(obj);
QVector<NodeKeyframe*> keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element());
foreach (NodeKeyframe* k, keys) {
if (k != key) {
SelectKeyframe(k);
}
}
}
emit SelectionChanged();
}
void KeyframeView::SelectionManagerDeselectEvent(void *obj)
{
if (autoselect_siblings_) {
NodeKeyframe *key = static_cast<NodeKeyframe*>(obj);
QVector<NodeKeyframe*> keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element());
foreach (NodeKeyframe* k, keys) {
if (k != key) {
DeselectKeyframe(k);
}
}
}
emit SelectionChanged();
}
void KeyframeView::mousePressEvent(QMouseEvent *event)
{
NodeKeyframe *key_under_cursor = selection_manager_.GetObjectAtPoint(event->pos());
if (HandPress(event) || (!key_under_cursor && PlayheadPress(event))) {
return;
}
// Do mouse press things
if (FirstChanceMousePress(event)) {
first_chance_mouse_event_ = true;
} else if (NodeKeyframe *initial_key = selection_manager_.MousePress(event)) {
selection_manager_.DragStart(initial_key, event);
KeyframeDragStart(event);
} else {
selection_manager_.RubberBandStart(event);
}
// Update view
Redraw();
}
void KeyframeView::mouseMoveEvent(QMouseEvent *event)
{
if (HandMove(event) || PlayheadMove(event)) {
return;
}
if (first_chance_mouse_event_) {
FirstChanceMouseMove(event);
} else if (selection_manager_.IsDragging()) {
QString tip;
KeyframeDragMove(event, tip);
selection_manager_.DragMove(event, tip);
} else if (selection_manager_.IsRubberBanding()) {
selection_manager_.RubberBandMove(event);
Redraw();
}
if (event->buttons()) {
// Signal cursor pos in case we should scroll to catch up to it
QPointF scene_pos = mapToScene(event->pos());
emit Dragged(scene_pos.x(), scene_pos.y());
}
}
void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
{
if (HandRelease(event) || PlayheadRelease(event)) {
return;
}
if (first_chance_mouse_event_) {
FirstChanceMouseRelease(event);
first_chance_mouse_event_ = false;
} else if (selection_manager_.IsDragging()) {
MultiUndoCommand* command = new MultiUndoCommand();
selection_manager_.DragStop(command);
KeyframeDragRelease(event, command);
Core::instance()->undo_stack()->push(command);
} else if (selection_manager_.IsRubberBanding()) {
selection_manager_.RubberBandStop();
Redraw();
emit SelectionChanged();
}
}
int BinarySearchFirstKeyframeAfterOrAt(const QVector<NodeKeyframe*> &keys, const rational &time)
{
int low = 0;
int high = keys.size()-1;
while (low <= high) {
int mid = low + (high-low)/2;
NodeKeyframe *test_key = keys.at(mid);
if (test_key->time() == time || (test_key->time() > time && (mid == 0 || keys.at(mid-1)->time() < time))) {
return mid;
} else if (test_key->time() < time) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
void KeyframeView::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);
foreach (KeyframeViewInputConnection *track, tracks_) {
const QVector<NodeKeyframe*> &keys = track->GetKeyframes();
if (keys.isEmpty()) {
continue;
}
if (!IsYAxisEnabled()) {
// Filter out if the keyframes are offscreen Y
qreal y = GetKeyframeSceneY(track, keys.first());
if (y + key_rad < rect.top() || y - key_rad >= rect.bottom()) {
continue;
}
}
// Find first keyframe to show with binary search
rational left_time = SceneToTime(rect.left() - key_sz);
int using_index = BinarySearchFirstKeyframeAfterOrAt(keys, left_time);
rational next_key = RATIONAL_MIN;
NodeKeyframe::Type last_type = NodeKeyframe::kInvalid;
for (int i=using_index; i<keys.size(); i++) {
NodeKeyframe *key = keys.at(i);
if (key->time() < next_key && key->type() == last_type) {
// This key will be drawn at exactly the same location as the last one and therefore
// doesn't need to be drawn. See if the next one will be drawn.
i++;
if (i == keys.size()) {
break;
}
key = keys.at(i);
if (key->time() < next_key) {
// Next key still won't be drawn, so we'll switch to a binary search
i = BinarySearchFirstKeyframeAfterOrAt(keys, next_key);
if (i == -1) {
break;
}
key = keys.at(i);
}
}
QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz);
qreal key_x = GetKeyframeSceneX(key);
key_rect.translate(key_x, GetKeyframeSceneY(track, key));
if (key_rect.left() >= rect.right()) {
// Break after last keyframe
break;
}
DrawKeyframe(painter, key, track, key_rect);
next_key = SceneToTime(key_x + 1);
last_type = key->type();
}
}
super::drawForeground(painter, rect);
}
void KeyframeView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect)
{
painter->setPen(Qt::black);
if (IsKeyframeSelected(key)) {
painter->setBrush(palette().highlight());
} else {
painter->setBrush(track->GetBrush());
}
selection_manager_.DeclareDrawnObject(key, key_rect);
switch (key->type()) {
case NodeKeyframe::kInvalid:
break;
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;
}
}
void KeyframeView::ScaleChangedEvent(const double &scale)
{
super::ScaleChangedEvent(scale);
Redraw();
}
void KeyframeView::TimeTargetChangedEvent(Node *target)
{
Redraw();
}
void KeyframeView::TimebaseChangedEvent(const rational &timebase)
{
super::TimebaseChangedEvent(timebase);
selection_manager_.SetTimebase(timebase);
}
void KeyframeView::ContextMenuEvent(Menu& m)
{
Q_UNUSED(m)
}
void KeyframeView::SelectKeyframe(NodeKeyframe *key)
{
if (selection_manager_.Select(key)) {
Redraw();
emit SelectionChanged();
}
}
void KeyframeView::DeselectKeyframe(NodeKeyframe *key)
{
if (selection_manager_.Deselect(key)) {
Redraw();
emit SelectionChanged();
}
}
rational KeyframeView::GetAdjustedKeyframeTime(NodeKeyframe *key)
{
return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false);
}
double KeyframeView::GetKeyframeSceneX(NodeKeyframe *key)
{
return TimeToScene(GetAdjustedKeyframeTime(key));
}
qreal KeyframeView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key)
{
return mapFromGlobal(QPoint(0, track->GetKeyframeY())).y();
}
void KeyframeView::SceneRectUpdateEvent(QRectF &rect)
{
rect.setY(0);
rect.setHeight(max_scroll_);
}
KeyframeViewItem* KeyframeView::AddKeyframe(NodeKeyframe* key)
rational KeyframeView::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff)
{
KeyframeViewItem* item = super::AddKeyframe(key);
item->SetOverrideY(element_y_.value(key->key_track_ref().input()));
return item;
return rational::fromDouble(old_time.toDouble() + cursor_diff);
}
void KeyframeView::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;i<GetSelectedKeyframes().size();i++) {
NodeKeyframe* key_item = GetSelectedKeyframes().at(i);
NodeKeyframe* prev_item = GetSelectedKeyframes().at(i-1);
if (key_item->type() != 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::kInvalid:
break;
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, &KeyframeView::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 KeyframeView::ShowKeyframePropertiesDialog()
{
if (!GetSelectedKeyframes().isEmpty()) {
KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this);
kd.exec();
}
}
void KeyframeView::Redraw()
{
viewport()->update();
}
}
+105 -9
View File
@@ -18,39 +18,135 @@
***/
#ifndef KEYFRAMEVIEW_H
#define KEYFRAMEVIEW_H
#ifndef KEYFRAMEVIEWBASE_H
#define KEYFRAMEVIEWBASE_H
#include "keyframeviewbase.h"
#include "keyframeviewinputconnection.h"
#include "node/keyframe.h"
#include "widget/menu/menu.h"
#include "widget/timebased/timebasedview.h"
#include "widget/timebased/timebasedviewselectionmanager.h"
#include "widget/timetarget/timetarget.h"
namespace olive {
class KeyframeView : public KeyframeViewBase
class KeyframeView : public TimeBasedView, public TimeTargetObject
{
Q_OBJECT
public:
KeyframeView(QWidget* parent = nullptr);
void DeleteSelected();
using ElementConnections = QVector<KeyframeViewInputConnection *>;
using InputConnections = QVector<ElementConnections>;
using NodeConnections = QMap<QString, InputConnections>;
NodeConnections AddKeyframesOfNode(Node* n);
InputConnections AddKeyframesOfInput(Node *n, const QString &input);
ElementConnections AddKeyframesOfElement(const NodeInput &input);
KeyframeViewInputConnection *AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref);
void RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection);
void SelectAll();
void DeselectAll();
void Clear();
const QVector<NodeKeyframe*> &GetSelectedKeyframes() const
{
return selection_manager_.GetSelectedObjects();
}
virtual void SelectionManagerSelectEvent(void *obj) override;
virtual void SelectionManagerDeselectEvent(void *obj) override;
void SetMaxScroll(int i)
{
max_scroll_ = i;
UpdateSceneRect();
}
void SetElementY(const NodeInput& c, int y);
signals:
void Dragged(int current_x, int current_y);
void SelectionChanged();
protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
virtual void DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect);
virtual void ScaleChangedEvent(const double& scale) override;
virtual void TimeTargetChangedEvent(Node*) override;
virtual void TimebaseChangedEvent(const rational &timebase) override;
virtual void ContextMenuEvent(Menu &m);
virtual bool FirstChanceMousePress(QMouseEvent *event){return false;}
virtual void FirstChanceMouseMove(QMouseEvent *event){}
virtual void FirstChanceMouseRelease(QMouseEvent *event){}
virtual void KeyframeDragStart(QMouseEvent *event){}
virtual void KeyframeDragMove(QMouseEvent *event, QString &tip){}
virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command){}
void SelectKeyframe(NodeKeyframe *key);
void DeselectKeyframe(NodeKeyframe *key);
bool IsKeyframeSelected(NodeKeyframe *key) const
{
return selection_manager_.IsSelected(key);
}
rational GetAdjustedKeyframeTime(NodeKeyframe *key);
double GetKeyframeSceneX(NodeKeyframe *key);
virtual qreal GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key);
void SetAutoSelectSiblings(bool e)
{
autoselect_siblings_ = e;
}
virtual void SceneRectUpdateEvent(QRectF& rect) override;
public slots:
virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key) override;
protected slots:
void Redraw();
private:
QHash<NodeInput, qreal> element_y_;
rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff);
QVector<KeyframeViewInputConnection*> tracks_;
TimeBasedViewSelectionManager<NodeKeyframe> selection_manager_;
bool autoselect_siblings_;
int max_scroll_;
bool first_chance_mouse_event_;
private slots:
void ShowContextMenu();
void ShowKeyframePropertiesDialog();
};
}
#endif // KEYFRAMEVIEW_H
#endif // KEYFRAMEVIEWBASE_H
@@ -1,623 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "keyframeviewbase.h"
#include <QMouseEvent>
#include <QToolTip>
#include <QVBoxLayout>
#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 {
KeyframeViewBase::KeyframeViewBase(QWidget *parent) :
TimeBasedView(parent),
dragging_bezier_point_(nullptr),
currently_autoselecting_(false),
dragging_(false)
{
SetDefaultDragMode(RubberBandDrag);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &KeyframeViewBase::customContextMenuRequested, this, &KeyframeViewBase::ShowContextMenu);
connect(scene(), &QGraphicsScene::selectionChanged, this, &KeyframeViewBase::AutoSelectKeyTimeNeighbors);
}
void KeyframeViewBase::Clear()
{
QMap<NodeKeyframe*, KeyframeViewItem*>::iterator iterator;
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
delete iterator.value();
}
item_map_.clear();
}
void KeyframeViewBase::DeleteSelected()
{
MultiUndoCommand* command = new MultiUndoCommand();
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator i;
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
if (i.value()->isSelected()) {
command->add_child(new NodeParamRemoveKeyframeCommand(i.key()));
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
void KeyframeViewBase::AddKeyframesOfNode(Node *n)
{
foreach (const QString& i, n->inputs()) {
AddKeyframesOfInput(n, i);
}
}
void KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input)
{
if (!n->IsInputKeyframable(input)) {
return;
}
int arr_sz = n->InputArraySize(input);
for (int i=-1; i<arr_sz; i++) {
AddKeyframesOfElement(NodeInput(n, input, i));
}
}
void KeyframeViewBase::AddKeyframesOfElement(const NodeInput& input)
{
const QVector<NodeKeyframeTrack>& tracks = input.node()->GetKeyframeTracks(input);
for (int i=0; i<tracks.size(); i++) {
AddKeyframesOfTrack(NodeKeyframeTrackReference(input, i));
}
}
void KeyframeViewBase::AddKeyframesOfTrack(const NodeKeyframeTrackReference& ref)
{
const QVector<NodeKeyframeTrack>& tracks = ref.input().node()->GetKeyframeTracks(ref.input());
const NodeKeyframeTrack& t = tracks.at(ref.track());
foreach (NodeKeyframe* key, t) {
AddKeyframe(key);
}
}
void KeyframeViewBase::RemoveKeyframesOfNode(Node *n)
{
foreach (const QString& i, n->inputs()) {
RemoveKeyframesOfInput(n, i);
}
}
void KeyframeViewBase::RemoveKeyframesOfInput(Node* n, const QString& input)
{
if (!n->IsInputKeyframable(input)) {
return;
}
int arr_sz = n->InputArraySize(input);
for (int i=-1; i<arr_sz; i++) {
RemoveKeyframesOfElement(NodeInput(n, input, i));
}
}
void KeyframeViewBase::RemoveKeyframesOfElement(const NodeInput& input)
{
const QVector<NodeKeyframeTrack>& tracks = input.node()->GetKeyframeTracks(input);
for (int i=0; i<tracks.size(); i++) {
RemoveKeyframesOfTrack(NodeKeyframeTrackReference(input, i));
}
}
void KeyframeViewBase::RemoveKeyframesOfTrack(const NodeKeyframeTrackReference& ref)
{
const QVector<NodeKeyframeTrack>& tracks = ref.input().node()->GetKeyframeTracks(ref.input());
const NodeKeyframeTrack& t = tracks.at(ref.track());
foreach (NodeKeyframe* key, t) {
RemoveKeyframe(key);
}
}
void KeyframeViewBase::SelectAll()
{
for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) {
it.value()->setSelected(true);
}
}
void KeyframeViewBase::DeselectAll()
{
for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) {
it.value()->setSelected(false);
}
}
void KeyframeViewBase::RemoveKeyframe(NodeKeyframe* key)
{
KeyframeAboutToBeRemoved(key);
delete item_map_.take(key);
}
KeyframeViewItem *KeyframeViewBase::AddKeyframe(NodeKeyframe* key)
{
KeyframeViewItem* item = item_map_.value(key);
if (!item) {
item = new KeyframeViewItem(key);
item->SetTimeTarget(GetTimeTarget());
item->SetScale(GetScale());
item_map_.insert(key, item);
scene()->addItem(item);
}
return item;
}
void KeyframeViewBase::mousePressEvent(QMouseEvent *event)
{
QGraphicsItem* item_under_cursor = itemAt(event->pos());
if (HandPress(event) || (!item_under_cursor && PlayheadPress(event))) {
return;
}
active_tool_ = Core::instance()->tool();
if (event->button() == Qt::LeftButton) {
QGraphicsView::mousePressEvent(event);
if (active_tool_ == Tool::kPointer) {
if (item_under_cursor) {
dragging_ = true;
drag_start_ = mapToScene(event->pos());
// Determine what type of item is under the cursor
dragging_bezier_point_ = dynamic_cast<BezierControlPointItem*>(item_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 {
QList<QGraphicsItem*> selected_items = scene()->selectedItems();
selected_keys_.resize(selected_items.size());
initial_drag_item_ = static_cast<KeyframeViewItem*>(item_under_cursor);
for (int i=0;i<selected_items.size();i++) {
KeyframeViewItem* key = static_cast<KeyframeViewItem*>(selected_items.at(i));
selected_keys_.replace(i, {key,
key->x(),
GetAdjustedTime(key->key()->parent(), GetTimeTarget(), key->key()->time(), false),
key->key()->value().toDouble()});
}
}
}
}
}
}
void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event)
{
if (HandMove(event) || PlayheadMove(event)) {
return;
}
if (event->buttons() & Qt::LeftButton) {
QGraphicsView::mouseMoveEvent(event);
if (dragging_) {
// Calculate cursor difference and scale it
QPointF mouse_diff_scaled = GetScaledCursorPos(mapToScene(event->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 (!selected_keys_.isEmpty()) {
// 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, selected_keys_) {
Node* node = keypair.key->key()->parent();
const QString& input = keypair.key->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_->key()->parent();
const QString& initial_drag_input_id = initial_drag_item_->key()->input();
if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) {
display_type = static_cast<FloatSlider::DisplayType>(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt());
}
}
foreach (const KeyframeItemAndTime& keypair, selected_keys_) {
rational node_time = GetAdjustedTime(GetTimeTarget(),
keypair.key->key()->parent(),
CalculateNewTimeFromScreen(keypair.time, mouse_diff_scaled.x()),
true);
keypair.key->key()->set_time(node_time);
if (IsYAxisEnabled()) {
keypair.key->key()->set_value(keypair.value - mouse_diff_scaled.y());
}
}
// Show information about this keyframe
QString tip = Timecode::time_to_timecode(initial_drag_item_->key()->time(), timebase(),
Core::instance()->GetTimecodeDisplay(), false);
if (IsYAxisEnabled()) {
bool ok;
double num_value = initial_drag_item_->key()->value().toDouble(&ok);
if (ok) {
tip.append('\n');
tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true));
}
// Force viewport to update since Qt might try to optimize it out if the keyframe is
// offscreen
viewport()->update();
}
QToolTip::hideText();
QToolTip::showText(QCursor::pos(), tip);
emit Dragged(qRound(initial_drag_item_->x()), qRound(initial_drag_item_->y()));
}
}
}
}
void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event)
{
if (HandRelease(event) || PlayheadRelease(event)) {
return;
}
if (event->button() == Qt::LeftButton) {
QGraphicsView::mouseReleaseEvent(event);
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 (!selected_keys_.isEmpty()) {
MultiUndoCommand* command = new MultiUndoCommand();
foreach (const KeyframeItemAndTime& keypair, selected_keys_) {
NodeKeyframe* item = keypair.key->key();
// Commit movement
command->add_child(new NodeParamSetKeyframeTimeCommand(item,
item->time(),
keypair.time));
// Commit value if we're setting a value
if (IsYAxisEnabled()) {
command->add_child(new NodeParamSetKeyframeValueCommand(item,
item->value(),
keypair.value));
}
}
Core::instance()->undo_stack()->push(command);
}
selected_keys_.clear();
dragging_ = false;
QToolTip::hideText();
}
}
}
void KeyframeViewBase::ScaleChangedEvent(const double &scale)
{
TimeBasedView::ScaleChangedEvent(scale);
for (auto iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
iterator.value()->SetScale(scale);
}
}
const QMap<NodeKeyframe *, KeyframeViewItem *> &KeyframeViewBase::item_map() const
{
return item_map_;
}
void KeyframeViewBase::KeyframeAboutToBeRemoved(NodeKeyframe *)
{
}
void KeyframeViewBase::TimeTargetChangedEvent(Node *target)
{
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator i;
for (i=item_map_.begin();i!=item_map_.end();i++) {
i.value()->SetTimeTarget(target);
}
}
void KeyframeViewBase::ContextMenuEvent(Menu& m)
{
Q_UNUSED(m)
}
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;
QList<QGraphicsItem*> items = scene()->selectedItems();
if (!items.isEmpty()) {
bool all_keys_are_same_type = true;
NodeKeyframe::Type type = static_cast<KeyframeViewItem*>(items.first())->key()->type();
for (int i=1;i<items.size();i++) {
KeyframeViewItem* key_item = static_cast<KeyframeViewItem*>(items.at(i));
KeyframeViewItem* prev_item = static_cast<KeyframeViewItem*>(items.at(i-1));
if (key_item->key()->type() != prev_item->key()->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 (!items.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 (!items.isEmpty()) {
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 (QGraphicsItem* item, items) {
command->add_child(new KeyframeSetTypeCommand(static_cast<KeyframeViewItem*>(item)->key(),
new_type));
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
}
}
void KeyframeViewBase::ShowKeyframePropertiesDialog()
{
QList<QGraphicsItem*> items = scene()->selectedItems();
QVector<NodeKeyframe*> keys;
foreach (QGraphicsItem* item, items) {
keys.append(static_cast<KeyframeViewItem*>(item)->key());
}
if (!keys.isEmpty()) {
KeyframePropertiesDialog kd(keys, timebase(), this);
kd.exec();
}
}
void KeyframeViewBase::AutoSelectKeyTimeNeighbors()
{
if (currently_autoselecting_ || IsYAxisEnabled()) {
return;
}
// Prevents infinite loop
currently_autoselecting_ = true;
QList<QGraphicsItem*> selected_items = scene()->selectedItems();
foreach (QGraphicsItem* g, selected_items) {
KeyframeViewItem* key_item = static_cast<KeyframeViewItem*>(g);
rational key_time = key_item->key()->time();
QVector<NodeKeyframe*> keys = key_item->key()->parent()->GetKeyframesAtTime(key_item->key()->input(), key_time, key_item->key()->element());
foreach (NodeKeyframe* k, keys) {
if (k == key_item->key()) {
continue;
}
// Ensure this key is not already selected
KeyframeViewItem* item = item_map_.value(k);
if (item) {
item->setSelected(true);
}
}
}
currently_autoselecting_ = false;
}
}
-136
View File
@@ -1,136 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef KEYFRAMEVIEWBASE_H
#define KEYFRAMEVIEWBASE_H
#include "keyframeviewitem.h"
#include "node/keyframe.h"
#include "widget/curvewidget/beziercontrolpointitem.h"
#include "widget/menu/menu.h"
#include "widget/timebased/timebasedview.h"
#include "widget/timetarget/timetarget.h"
namespace olive {
class KeyframeViewBase : public TimeBasedView, public TimeTargetObject
{
Q_OBJECT
public:
KeyframeViewBase(QWidget* parent = nullptr);
virtual void Clear();
void DeleteSelected();
void AddKeyframesOfNode(Node* n);
void AddKeyframesOfInput(Node *n, const QString &input);
void AddKeyframesOfElement(const NodeInput &input);
void AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref);
void RemoveKeyframesOfNode(Node* n);
void RemoveKeyframesOfInput(Node *n, const QString &input);
void RemoveKeyframesOfElement(const NodeInput &input);
void RemoveKeyframesOfTrack(const NodeKeyframeTrackReference &ref);
void SelectAll();
void DeselectAll();
signals:
void Dragged(int current_x, int current_y);
public slots:
virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key);
void RemoveKeyframe(NodeKeyframe* key);
protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void ScaleChangedEvent(const double& scale) override;
const QMap<NodeKeyframe*, KeyframeViewItem*>& item_map() const;
virtual void KeyframeAboutToBeRemoved(NodeKeyframe* key);
virtual void TimeTargetChangedEvent(Node*) override;
virtual void ContextMenuEvent(Menu &m);
bool IsDragging() const
{
return dragging_;
}
private:
rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff);
static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode,
const QPointF& start_point,
const QPointF& scaled_cursor_diff);
QPointF GetScaledCursorPos(const QPointF &cursor_pos);
struct KeyframeItemAndTime {
KeyframeViewItem* key;
qreal item_x;
rational time;
double value;
};
QMap<NodeKeyframe*, KeyframeViewItem*> item_map_;
Tool::Item active_tool_;
QPointF drag_start_;
BezierControlPointItem* dragging_bezier_point_;
QPointF dragging_bezier_point_start_;
QPointF dragging_bezier_point_opposing_start_;
KeyframeViewItem* initial_drag_item_;
QVector<KeyframeItemAndTime> selected_keys_;
bool currently_autoselecting_;
bool dragging_;
private slots:
void ShowContextMenu();
void ShowKeyframePropertiesDialog();
void AutoSelectKeyTimeNeighbors();
};
}
#endif // KEYFRAMEVIEWBASE_H
@@ -0,0 +1,100 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "keyframeviewinputconnection.h"
#include "keyframeview.h"
namespace olive {
KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeView *parent) :
QObject(parent),
keyframe_view_(parent),
input_(input),
y_(0),
y_behavior_(kSingleRow),
brush_(Qt::white)
{
Node *n = input.input().node();
connect(n, &Node::KeyframeAdded, this, &KeyframeViewInputConnection::AddKeyframe);
connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe);
connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeTypeChanged);
connect(n, &Node::KeyframeValueChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
}
void KeyframeViewInputConnection::SetKeyframeY(int y)
{
if (y_ != y) {
y_ = y;
emit RequireUpdate();
}
}
void KeyframeViewInputConnection::SetYBehavior(YBehavior e)
{
if (y_behavior_ != e) {
y_behavior_ = e;
emit RequireUpdate();
}
}
void KeyframeViewInputConnection::SetBrush(const QBrush &brush)
{
if (brush_ != brush) {
brush_ = brush;
emit RequireUpdate();
}
}
void KeyframeViewInputConnection::AddKeyframe(NodeKeyframe *key)
{
if (key->key_track_ref() == input_) {
emit RequireUpdate();
}
}
void KeyframeViewInputConnection::RemoveKeyframe(NodeKeyframe *key)
{
if (key->key_track_ref() == input_) {
emit RequireUpdate();
}
}
void KeyframeViewInputConnection::KeyframeChanged(NodeKeyframe *key)
{
if (key->key_track_ref() == input_) {
emit RequireUpdate();
}
}
void KeyframeViewInputConnection::KeyframeTypeChanged(NodeKeyframe *key)
{
if (key->key_track_ref() == input_) {
emit TypeChanged();
}
}
}
@@ -0,0 +1,94 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef KEYFRAMEVIEWINPUTCONNECTION_H
#define KEYFRAMEVIEWINPUTCONNECTION_H
#include <QObject>
#include "node/node.h"
#include "node/param.h"
namespace olive {
class KeyframeView;
class KeyframeViewInputConnection : public QObject
{
Q_OBJECT
public:
KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeView *parent);
const int &GetKeyframeY() const
{
return y_;
}
void SetKeyframeY(int y);
enum YBehavior {
kSingleRow,
kValueIsHeight
};
void SetYBehavior(YBehavior e);
const QVector<NodeKeyframe*> GetKeyframes() const
{
return input_.input().node()->GetKeyframeTracks(input_.input()).at(input_.track());
}
const QBrush &GetBrush() const
{
return brush_;
}
void SetBrush(const QBrush &brush);
signals:
void RequireUpdate();
void TypeChanged();
private:
KeyframeView *keyframe_view_;
NodeKeyframeTrackReference input_;
int y_;
YBehavior y_behavior_;
QBrush brush_;
private slots:
void AddKeyframe(NodeKeyframe *key);
void RemoveKeyframe(NodeKeyframe *key);
void KeyframeChanged(NodeKeyframe *key);
void KeyframeTypeChanged(NodeKeyframe *key);
};
}
#endif // KEYFRAMEVIEWINPUTCONNECTION_H
@@ -1,129 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "keyframeviewitem.h"
#include <QApplication>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QWidget>
#include "common/qtutils.h"
namespace olive {
KeyframeViewItem::KeyframeViewItem(NodeKeyframe* key, QGraphicsItem *parent) :
QGraphicsRectItem(parent),
key_(key),
scale_(1.0),
vert_center_(0),
use_custom_brush_(false)
{
setFlag(QGraphicsItem::ItemIsSelectable);
connect(key, &NodeKeyframe::TimeChanged, this, &KeyframeViewItem::UpdatePos);
connect(key, &NodeKeyframe::TypeChanged, this, &KeyframeViewItem::Redraw);
int keyframe_size = QtUtils::QFontMetricsWidth(qApp->fontMetrics(), "Oi");
int half_sz = keyframe_size/2;
setRect(-half_sz, -half_sz, keyframe_size, keyframe_size);
UpdatePos();
// Set default brush
}
void KeyframeViewItem::SetOverrideY(qreal vertical_center)
{
vert_center_ = vertical_center;
UpdatePos();
}
void KeyframeViewItem::SetScale(double scale)
{
scale_ = scale;
UpdatePos();
}
void KeyframeViewItem::SetOverrideBrush(const QBrush &b)
{
use_custom_brush_ = true;
setBrush(b);
}
NodeKeyframe* KeyframeViewItem::key() const
{
return key_;
}
void KeyframeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(Qt::black);
if (option->state & QStyle::State_Selected) {
painter->setBrush(widget->palette().highlight());
} else if (use_custom_brush_) {
painter->setBrush(brush());
} else {
painter->setBrush(widget->palette().text());
}
switch (key_->type()) {
case NodeKeyframe::kLinear:
{
QPointF points[] = {
QPointF(rect().center().x(), rect().top()),
QPointF(rect().right(), rect().center().y()),
QPointF(rect().center().x(), rect().bottom()),
QPointF(rect().left(), rect().center().y())
};
painter->drawPolygon(points, 4);
break;
}
case NodeKeyframe::kBezier:
painter->drawEllipse(rect());
break;
case NodeKeyframe::kHold:
painter->drawRect(rect());
break;
}
}
void KeyframeViewItem::TimeTargetChangedEvent(Node *)
{
UpdatePos();
}
void KeyframeViewItem::UpdatePos()
{
rational adjusted = GetAdjustedTime(key_->parent(), GetTimeTarget(), key_->time(), false);
setPos(adjusted.toDouble() * scale_, vert_center_);
}
void KeyframeViewItem::Redraw()
{
QGraphicsItem::update();
}
}
@@ -1,68 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef KEYFRAMEVIEWITEM_H
#define KEYFRAMEVIEWITEM_H
#include <QGraphicsRectItem>
#include "node/keyframe.h"
#include "widget/timetarget/timetarget.h"
namespace olive {
class KeyframeViewItem : public QObject, public QGraphicsRectItem, public TimeTargetObject
{
Q_OBJECT
public:
KeyframeViewItem(NodeKeyframe* key, QGraphicsItem *parent = nullptr);
void SetOverrideY(qreal vertical_center);
void SetScale(double scale);
void SetOverrideBrush(const QBrush& b);
NodeKeyframe* key() const;
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
virtual void TimeTargetChangedEvent(Node* ) override;
private:
NodeKeyframe* key_;
double scale_;
qreal vert_center_;
bool use_custom_brush_;
private slots:
void UpdatePos();
void Redraw();
};
}
#endif // KEYFRAMEVIEWITEM_H
+15 -9
View File
@@ -16,23 +16,29 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/nodeparamview/nodeparamview.h
widget/nodeparamview/nodeparamview.cpp
widget/nodeparamview/nodeparamviewarraywidget.h
widget/nodeparamview/nodeparamview.h
widget/nodeparamview/nodeparamviewarraywidget.cpp
widget/nodeparamview/nodeparamviewconnectedlabel.h
widget/nodeparamview/nodeparamviewarraywidget.h
widget/nodeparamview/nodeparamviewconnectedlabel.cpp
widget/nodeparamview/nodeparamviewdockarea.h
widget/nodeparamview/nodeparamviewconnectedlabel.h
widget/nodeparamview/nodeparamviewcontext.cpp
widget/nodeparamview/nodeparamviewcontext.h
widget/nodeparamview/nodeparamviewdockarea.cpp
widget/nodeparamview/nodeparamviewitem.h
widget/nodeparamview/nodeparamviewdockarea.h
widget/nodeparamview/nodeparamviewitem.cpp
widget/nodeparamview/nodeparamviewkeyframecontrol.h
widget/nodeparamview/nodeparamviewitem.h
widget/nodeparamview/nodeparamviewitembase.cpp
widget/nodeparamview/nodeparamviewitembase.h
widget/nodeparamview/nodeparamviewitemtitlebar.cpp
widget/nodeparamview/nodeparamviewitemtitlebar.h
widget/nodeparamview/nodeparamviewkeyframecontrol.cpp
widget/nodeparamview/nodeparamviewtextedit.h
widget/nodeparamview/nodeparamviewkeyframecontrol.h
widget/nodeparamview/nodeparamviewtextedit.cpp
widget/nodeparamview/nodeparamviewundo.h
widget/nodeparamview/nodeparamviewtextedit.h
widget/nodeparamview/nodeparamviewundo.cpp
widget/nodeparamview/nodeparamviewwidgetbridge.h
widget/nodeparamview/nodeparamviewundo.h
widget/nodeparamview/nodeparamviewwidgetbridge.cpp
widget/nodeparamview/nodeparamviewwidgetbridge.h
PARENT_SCOPE
)
+308 -169
View File
@@ -25,6 +25,7 @@
#include <QScrollBar>
#include <QSplitter>
#include "common/functiontimer.h"
#include "common/timecodefunctions.h"
#include "node/output/viewer/viewer.h"
@@ -32,10 +33,13 @@ namespace olive {
#define super TimeBasedWidget
NodeParamView::NodeParamView(QWidget *parent) :
NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
super(true, false, parent),
last_scroll_val_(0),
focused_node_(nullptr)
focused_node_(nullptr),
create_checkboxes_(kNoCheckBoxes),
time_target_(nullptr),
ignore_flags_(false)
{
// Create horizontal layout to place scroll area in (and keyframe editing eventually)
QHBoxLayout* layout = new QHBoxLayout(this);
@@ -46,25 +50,18 @@ NodeParamView::NodeParamView(QWidget *parent) :
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_scroll_area_ = new QScrollArea();
param_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
param_scroll_area_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
param_scroll_area_->setWidgetResizable(true);
splitter->addWidget(param_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_container_ = new QWidget();
param_scroll_area_->setWidget(param_widget_container_);
param_widget_area_ = new NodeParamViewDockArea();
// 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());
@@ -74,34 +71,24 @@ NodeParamView::NodeParamView(QWidget *parent) :
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 contexts for three different types
context_items_.resize(Track::kCount + 1);
for (int i=0; i<context_items_.size(); i++) {
NodeParamViewContext *c = new NodeParamViewContext;
c->setVisible(false);
// Create ruler object
keyframe_area_layout->addWidget(ruler());
NodeParamViewItemTitleBar *title_bar = static_cast<NodeParamViewItemTitleBar*>(c->titleBarWidget());
// Create keyframe view
keyframe_view_ = new KeyframeView();
keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ConnectTimelineView(keyframe_view_);
keyframe_area_layout->addWidget(keyframe_view_);
if (i == Track::kVideo || i == Track::kAudio) {
title_bar->SetAddEffectButtonVisible(true);
title_bar->SetText(tr("%1 Nodes").arg(Footage::GetStreamTypeName(static_cast<Track::Type>(i))));
} else {
title_bar->SetText(tr("Other"));
}
// 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::SetTime);
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});
context_items_[i] = c;
param_widget_area_->AddItem(c);
}
// Disable collapsing param view (but collapsing keyframe view is permitted)
splitter->setCollapsible(0, false);
@@ -112,18 +99,54 @@ NodeParamView::NodeParamView(QWidget *parent) :
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);
connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange);
connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::UpdateGlobalScrollBar);
connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue);
// TimeBasedWidget's scrollbar has extra functionality that we can take advantage of
keyframe_view_->setHorizontalScrollBar(scrollbar());
keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
if (create_keyframe_view) {
// 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);
connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
// 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::SetTime);
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});
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue);
connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->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);
} else {
keyframe_view_ = nullptr;
}
// Set a default scale - FIXME: Hardcoded
SetScale(120);
@@ -135,8 +158,14 @@ NodeParamView::NodeParamView(QWidget *parent) :
&NodeParamView::FocusChanged);
}
void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
NodeParamView::~NodeParamView()
{
qDeleteAll(context_items_);
}
/*void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
{
return;
int original_node_count = items_.size();
foreach (Node* n, nodes) {
@@ -149,7 +178,7 @@ void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
active_nodes_.append(n);
// Create node UI
AddNode(n);
AddNode(n, param_widget_area_);
}
if (items_.size() > original_node_count ) {
@@ -164,6 +193,7 @@ void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
{
return;
// Remove item from map and delete the widget
int original_node_count = items_.size();
@@ -190,6 +220,69 @@ void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
SignalNodeOrder();
}
}*/
void NodeParamView::SetInputChecked(const NodeInput &input, bool e)
{
input_checked_.insert(input, e);
foreach (NodeParamViewContext *ctx, context_items_) {
ctx->SetInputChecked(input, e);
}
}
void NodeParamView::SetContexts(const QVector<Node *> &contexts)
{
TIME_THIS_FUNCTION;
foreach (NodeParamViewContext *ctx, context_items_) {
ctx->Clear();
ctx->setVisible(false);
}
contexts_ = contexts;
if (keyframe_view_) {
keyframe_view_->Clear();
}
if (focused_node_) {
focused_node_ = nullptr;
emit FocusedNodeChanged(nullptr);
}
foreach (Node *ctx, contexts) {
Track::Type ctx_type = Track::kCount;
if (ClipBlock *clip = dynamic_cast<ClipBlock*>(ctx)) {
if (clip->track()) {
if (clip->track()->type() != Track::kNone) {
ctx_type = clip->track()->type();
}
}
} else if (Track *track = dynamic_cast<Track*>(ctx)) {
if (track->type() != Track::kNone) {
ctx_type = track->type();
}
}
NodeParamViewContext *item = context_items_.at(ctx_type);
item->AddContext(ctx);
item->setVisible(true);
for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) {
if (!(it.key()->GetFlags() & Node::kDontShowInParamView) || ignore_flags_) {
AddNode(it.key(), item);
}
}
}
foreach (NodeParamViewContext *ctx, context_items_) {
SortItemsInContext(ctx);
}
if (keyframe_view_) {
QueueKeyframePositionUpdate();
}
}
void NodeParamView::resizeEvent(QResizeEvent *event)
@@ -197,25 +290,27 @@ void NodeParamView::resizeEvent(QResizeEvent *event)
super::resizeEvent(event);
vertical_scrollbar_->setPageStep(vertical_scrollbar_->height());
UpdateGlobalScrollBar();
}
void NodeParamView::ScaleChangedEvent(const double &scale)
{
super::ScaleChangedEvent(scale);
keyframe_view_->SetScale(scale);
if (keyframe_view_) {
keyframe_view_->SetScale(scale);
}
}
void NodeParamView::TimebaseChangedEvent(const rational &timebase)
{
super::TimebaseChangedEvent(timebase);
keyframe_view_->SetTimebase(timebase);
if (keyframe_view_) {
keyframe_view_->SetTimebase(timebase);
}
foreach (NodeParamViewItem* item, items_) {
item->SetTimebase(timebase);
foreach (NodeParamViewContext* ctx, context_items_) {
ctx->SetTimebase(timebase);
}
UpdateItemTime(GetTime());
@@ -225,34 +320,52 @@ void NodeParamView::TimeChangedEvent(const rational &time)
{
super::TimeChangedEvent(time);
keyframe_view_->SetTime(time);
if (keyframe_view_) {
keyframe_view_->SetTime(time);
}
UpdateItemTime(time);
}
void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n)
{
// Set viewer as a time target
keyframe_view_->SetTimeTarget(n);
if (keyframe_view_) {
// Set viewer as a time target
keyframe_view_->SetTimeTarget(n);
}
foreach (NodeParamViewItem* item, items_) {
foreach (NodeParamViewContext* item, context_items_) {
item->SetTimeTarget(n);
}
time_target_ = n;
}
Node *NodeParamView::GetTimeTarget() const
{
return keyframe_view_->GetTimeTarget();
return time_target_;
}
void NodeParamView::DeleteSelected()
{
keyframe_view_->DeleteSelected();
if (keyframe_view_) {
keyframe_view_->DeleteSelected();
}
}
void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
{
// Do nothing, this is a placeholder if we ever need this to do anything in the future
}
void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
{
// Do nothing, this is a placeholder if we ever need this to do anything in the future
}
void NodeParamView::UpdateItemTime(const rational &time)
{
foreach (NodeParamViewItem* item, items_) {
foreach (NodeParamViewContext* item, context_items_) {
item->SetTime(time);
}
}
@@ -262,104 +375,102 @@ void NodeParamView::QueueKeyframePositionUpdate()
QMetaObject::invokeMethod(this, &NodeParamView::UpdateElementY, Qt::QueuedConnection);
}
void NodeParamView::SignalNodeOrder()
void NodeParamView::AddNode(Node *n, NodeParamViewContext *context)
{
// 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;
NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, context);
for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
int item_y = it.value()->pos().y();
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal);
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode);
connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked);
if (create_checkboxes_) {
for (auto it=input_checked_.cbegin(); it!=input_checked_.cend(); it++) {
if (it.key().node() == n) {
item->SetInputChecked(it.key(), it.value());
}
}
}
item->SetTimeTarget(GetTimeTarget());
item->SetTimebase(timebase());
item->SetTime(GetTime());
context->AddNode(item);
if (!focused_node_ && n->HasGizmos()) {
// We'll focus this node now
item->SetHighlighted(true);
focused_node_ = item;
emit FocusedNodeChanged(n);
}
if (keyframe_view_) {
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate);
item->SetKeyframeConnections(keyframe_view_->AddKeyframesOfNode(n));
}
}
int GetDistanceBetweenNodes(Node *start, Node *end)
{
if (start == end) {
return 0;
}
for (auto it=start->input_connections().cbegin(); it!=start->input_connections().cend(); it++) {
int this_node_dist = GetDistanceBetweenNodes(it->second, end);
if (this_node_dist != -1) {
return 1 + this_node_dist;
}
}
return -1;
}
void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item)
{
QVector<QPair<NodeParamViewItem*, int> > distances;
for (auto it=context_item->GetItems().cbegin(); it!=context_item->GetItems().cend(); it++) {
int distance = -1;
foreach (Node *ctx, context_item->GetContexts()) {
distance = qMax(distance, GetDistanceBetweenNodes(ctx, it.key()));
}
if (distance == -1) {
distance = INT_MAX;
}
bool inserted = false;
QPair<NodeParamViewItem*, int> dist(it.value(), distance);
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());
for (int i=0; i<distances.size(); i++) {
if (distances.at(i).second < distance) {
distances.insert(i, dist);
inserted = true;
break;
}
}
if (!inserted) {
item_ys.append(item_y);
nodes.append(it.key());
distances.append(dist);
}
}
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::SetTimeAndSignal);
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::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate);
// Set time target
item->SetTimeTarget(GetTimeTarget());
// Set the timebase
item->SetTimebase(timebase());
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) {
// Try to find new node with gizmos to focus
focused_node_ = nullptr;
for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
if (it.key()->HasGizmos()) {
focused_node_ = it.key();
it.value()->SetHighlighted(true);
break;
}
}
emit FocusedNodeChanged(focused_node_);
foreach (auto info, distances) {
context_item->GetDockArea()->AddItem(info.first);
}
}
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());
if (keyframe_view_) {
keyframe_view_->SetMaxScroll(param_widget_container_->height() - ruler()->height());
}
}
void NodeParamView::PinNode(bool pin)
@@ -373,8 +484,7 @@ void NodeParamView::PinNode(bool pin)
pinned_nodes_.removeOne(node);
if (!active_nodes_.contains(node)) {
RemoveNode(node);
SignalNodeOrder();
//RemoveNode(node);
}
}
}
@@ -384,26 +494,36 @@ void NodeParamView::FocusChanged(QWidget* old, QWidget* now)
Q_UNUSED(old)
QObject* parent = now;
NodeParamViewItem* item;
while (parent) {
item = dynamic_cast<NodeParamViewItem*>(parent);
if (NodeParamViewItem* item = dynamic_cast<NodeParamViewItem*>(parent)) {
if (item != focused_node_) {
// Found a NodeParamViewItem that isn't already focused, see if it belongs to us
bool ours = false;
if (item) {
// Found it!
if (item->GetNode() != focused_node_) {
if (focused_node_) {
// De-focus current node
items_.value(focused_node_)->SetHighlighted(false);
do {
parent = parent->parent();
if (parent == this) {
ours = true;
break;
}
} while (parent);
if (ours) {
// This item is ours,
if (focused_node_) {
// De-focus current node
focused_node_->SetHighlighted(false);
}
focused_node_ = item;
item->SetHighlighted(true);
emit FocusedNodeChanged(item->GetNode());
}
focused_node_ = item->GetNode();
item->SetHighlighted(true);
emit FocusedNodeChanged(focused_node_);
}
break;
}
@@ -421,15 +541,34 @@ void NodeParamView::KeyframeViewDragged(int x, int y)
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);
foreach (NodeParamViewContext *ctx, context_items_) {
for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) {
const KeyframeView::NodeConnections &connections = it.value()->GetKeyframeConnections();
for (int i=-1; i<arr_sz; i++) {
NodeInput ic = {it.key(), input, i};
if (!connections.isEmpty()) {
foreach (const QString& input, it.key()->inputs()) {
if (!(it.key()->GetInputFlags(input) & kInputFlagHidden)) {
int arr_sz = it.key()->InputArraySize(input);
int y = it.value()->GetElementY(ic);
keyframe_view_->SetElementY(ic, y);
for (int i=-1; i<arr_sz; i++) {
NodeInput ic = {it.key(), input, i};
int y = it.value()->GetElementY(ic);
// For some reason Qt's mapToGlobal doesn't seem to handle this, so we offset here
y += vertical_scrollbar_->value();
const KeyframeView::InputConnections &input_con = connections.value(input);
int use_index = i + 1;
if (use_index < input_con.size()) {
const KeyframeView::ElementConnections &ele_con = input_con.at(ic.element()+1);
foreach (KeyframeViewInputConnection *track, ele_con) {
track->SetKeyframeY(y);
}
}
}
}
}
}
}
}
+50 -41
View File
@@ -25,6 +25,7 @@
#include <QWidget>
#include "node/node.h"
#include "nodeparamviewcontext.h"
#include "nodeparamviewdockarea.h"
#include "nodeparamviewitem.h"
#include "widget/keyframeview/keyframeview.h"
@@ -32,40 +33,26 @@
namespace olive {
class NodeParamViewParamContainer : public QWidget
{
Q_OBJECT
public:
NodeParamViewParamContainer(QWidget* parent = nullptr) :
QWidget(parent)
{
}
protected:
virtual void resizeEvent(QResizeEvent *event) override
{
QWidget::resizeEvent(event);
emit Resized(event->size().height());
}
signals:
void Resized(int new_height);
};
class NodeParamView : public TimeBasedWidget
{
Q_OBJECT
public:
NodeParamView(QWidget* parent = nullptr);
void SelectNodes(const QVector<Node *> &nodes);
void DeselectNodes(const QVector<Node*>& nodes);
const QMap<Node*, NodeParamViewItem*>& GetItemMap() const
NodeParamView(bool create_keyframe_view, QWidget* parent = nullptr);
NodeParamView(QWidget* parent = nullptr) :
NodeParamView(true, parent)
{
return items_;
}
virtual ~NodeParamView() override;
void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e)
{
create_checkboxes_ = e;
}
bool IsInputChecked(const NodeInput &input) const
{
return input_checked_.value(input);
}
Node* GetTimeTarget() const;
@@ -82,11 +69,27 @@ public:
keyframe_view_->DeselectAll();
}
void SetIgnoreNodeFlags(bool e)
{
ignore_flags_ = e;
}
void SelectNodes(const QVector<Node*> &nodes);
void DeselectNodes(const QVector<Node*> &nodes);
const QVector<Node*> &GetContexts() const
{
return contexts_;
}
public slots:
void SetInputChecked(const NodeInput &input, bool e);
void SetContexts(const QVector<Node*> &contexts);
signals:
void RequestSelectNode(const QVector<Node*>& target);
void NodeOrderChanged(const QVector<Node*>& nodes);
void FocusedNodeChanged(Node* n);
protected:
@@ -103,33 +106,39 @@ private:
void QueueKeyframePositionUpdate();
void SignalNodeOrder();
void AddNode(Node* n, NodeParamViewContext *context);
void AddNode(Node* n);
void RemoveNode(Node* n);
void SortItemsInContext(NodeParamViewContext *context);
KeyframeView* keyframe_view_;
QMap<Node*, NodeParamViewItem*> items_;
QVector<NodeParamViewContext*> context_items_;
QScrollBar* vertical_scrollbar_;
int last_scroll_val_;
NodeParamViewParamContainer* param_widget_container_;
QScrollArea* param_scroll_area_;
QWidget* param_widget_container_;
// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows
// docking windows
NodeParamViewDockArea* param_widget_area_;
QVector<Node*> pinned_nodes_;
QVector<Node*> active_nodes_;
QMap<Node*, bool> node_expanded_state_;
NodeParamViewItem* focused_node_;
Node* focused_node_;
NodeParamViewCheckBoxBehavior create_checkboxes_;
Node *time_target_;
QHash<NodeInput, bool> input_checked_;
bool ignore_flags_;
QVector<Node*> contexts_;
private slots:
void UpdateGlobalScrollBar();
@@ -0,0 +1,101 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "nodeparamviewcontext.h"
#include <QMessageBox>
#include "node/block/clip/clip.h"
namespace olive {
#define super NodeParamViewItemBase
NodeParamViewContext::NodeParamViewContext(QWidget *parent) :
super(parent)
{
QWidget *body = new QWidget();
QHBoxLayout *body_layout = new QHBoxLayout(body);
SetBody(body);
dock_area_ = new NodeParamViewDockArea();
body_layout->addWidget(dock_area_);
setBackgroundRole(QPalette::Base);
Retranslate();
connect(title_bar(), &NodeParamViewItemTitleBar::AddEffectButtonClicked, this, &NodeParamViewContext::AddEffectButtonClicked);
}
void NodeParamViewContext::AddNode(NodeParamViewItem *item)
{
items_.insert(item->GetNode(), item);
dock_area_->AddItem(item);
}
void NodeParamViewContext::RemoveNode(Node *node)
{
}
void NodeParamViewContext::Clear()
{
qDeleteAll(items_);
items_.clear();
}
void NodeParamViewContext::SetInputChecked(const NodeInput &input, bool e)
{
if (NodeParamViewItem *item = items_.value(input.node())) {
item->SetInputChecked(input, e);
}
}
void NodeParamViewContext::SetTimebase(const rational &timebase)
{
foreach (NodeParamViewItem* item, items_) {
item->SetTimebase(timebase);
}
}
void NodeParamViewContext::SetTimeTarget(Node *n)
{
foreach (NodeParamViewItem* item, items_) {
item->SetTimeTarget(n);
}
}
void NodeParamViewContext::SetTime(const rational &time)
{
foreach (NodeParamViewItem* item, items_) {
item->SetTime(time);
}
}
void NodeParamViewContext::Retranslate()
{
}
void NodeParamViewContext::AddEffectButtonClicked()
{
QMessageBox::information(this, tr("STUB"), tr("This feature is coming soon. Thanks for testing development builds of Olive :)"));
}
}
@@ -0,0 +1,93 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEPARAMVIEWCONTEXT_H
#define NODEPARAMVIEWCONTEXT_H
#include "nodeparamviewdockarea.h"
#include "nodeparamviewitembase.h"
#include "nodeparamviewitem.h"
namespace olive {
class NodeParamViewContext : public NodeParamViewItemBase
{
Q_OBJECT
public:
NodeParamViewContext(QWidget *parent = nullptr);
NodeParamViewDockArea *GetDockArea() const
{
return dock_area_;
}
const QVector<Node*> &GetContexts() const
{
return contexts_;
}
const QMap<Node*, NodeParamViewItem*> &GetItems() const
{
return items_;
}
void AddNode(NodeParamViewItem *item);
void RemoveNode(Node *node);
void Clear();
void SetInputChecked(const NodeInput &input, bool e);
void SetTimebase(const rational &timebase);
void SetTimeTarget(Node *n);
void SetTime(const rational &time);
public slots:
void AddContext(Node *node)
{
contexts_.append(node);
}
void RemoveContext(Node *node)
{
contexts_.removeOne(node);
}
protected slots:
virtual void Retranslate() override;
private:
NodeParamViewDockArea *dock_area_;
QVector<Node*> contexts_;
QMap<Node*, NodeParamViewItem*> items_;
private slots:
void AddEffectButtonClicked();
};
}
#endif // NODEPARAMVIEWCONTEXT_H
@@ -20,11 +20,18 @@
#include "nodeparamviewdockarea.h"
#include <QDockWidget>
namespace olive {
NodeParamViewDockArea::NodeParamViewDockArea(QWidget *parent) :
QMainWindow(parent)
{
// Disable dock widgets from tabbing and disable glitchy animations
setDockOptions(static_cast<QMainWindow::DockOption>(0));
// HACK: Hide the main window separators (unfortunately the cursors still appear)
setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}"));
}
QMenu *NodeParamViewDockArea::createPopupMenu()
@@ -32,4 +39,11 @@ QMenu *NodeParamViewDockArea::createPopupMenu()
return nullptr;
}
void NodeParamViewDockArea::AddItem(QDockWidget *item)
{
item->setAllowedAreas(Qt::LeftDockWidgetArea);
item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
addDockWidget(Qt::LeftDockWidgetArea, item);
}
}
@@ -25,6 +25,8 @@
namespace olive {
// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows
// for docking QDockWidgets
class NodeParamViewDockArea : public QMainWindow
{
Q_OBJECT
@@ -33,6 +35,8 @@ public:
virtual QMenu *createPopupMenu() override;
void AddItem(QDockWidget *item);
};
}
+109 -182
View File
@@ -22,8 +22,6 @@
#include <QCheckBox>
#include <QDebug>
#include <QEvent>
#include <QPainter>
#include "common/qtutils.h"
#include "core.h"
@@ -38,227 +36,105 @@ const int NodeParamViewItemBody::kArrayInsertColumn = kKeyControlColumn-1;
const int NodeParamViewItemBody::kArrayRemoveColumn = kArrayInsertColumn-1;
const int NodeParamViewItemBody::kExtraButtonColumn = kKeyControlColumn-1;
// 0 is for the array collapse button, 1 is for the main label, widgets start at 2
const int NodeParamViewItemBody::kWidgetStartColumn = 2;
const int NodeParamViewItemBody::kOptionalCheckBox = 0;
const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1;
const int NodeParamViewItemBody::kLabelColumn = 2;
const int NodeParamViewItemBody::kWidgetStartColumn = 3;
#define super QDockWidget
#define super NodeParamViewItemBase
NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) :
super(parent),
node_(node),
highlighted_(false)
node_(node)
{
// Create title bar widget
title_bar_ = new NodeParamViewItemTitleBar(this);
// Add title bar to widget
this->setTitleBarWidget(title_bar_);
node_->Retranslate();
// Create and add contents widget
body_ = new NodeParamViewItemBody(node_);
body_ = new NodeParamViewItemBody(node_, create_checkboxes);
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_);
// Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar
// size hints and will shrink as small as possible if the body is hidden)
hidden_body_ = new QWidget(this);
connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged);
SetBody(body_);
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
setFocusPolicy(Qt::ClickFocus);
setBackgroundRole(QPalette::Window);
Retranslate();
}
void NodeParamViewItem::SetTimeTarget(Node *target)
{
body_->SetTimeTarget(target);
}
void NodeParamViewItem::SetTime(const rational &time)
{
time_ = time;
body_->SetTime(time_);
}
void NodeParamViewItem::SetTimebase(const rational& timebase)
{
body_->SetTimebase(timebase);
}
Node *NodeParamViewItem::GetNode() const
{
return node_;
}
void NodeParamViewItem::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
super::changeEvent(e);
}
void NodeParamViewItem::paintEvent(QPaintEvent *event)
{
super::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::moveEvent(QMoveEvent *event)
{
super::moveEvent(event);
emit Moved();
}
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()));
}
title_bar()->SetText(GetTitleBarTextFromNode(node_));
body_->Retranslate();
}
void NodeParamViewItem::SetExpanded(bool e)
{
setWidget(e ? body_ : hidden_body_);
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();
return mapToGlobal(title_bar()->rect().center()).y();
}
}
void NodeParamViewItem::ToggleExpanded()
void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e)
{
SetExpanded(!IsExpanded());
body_->SetInputChecked(input, e);
}
NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, 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),
node_(node)
node_(node),
create_checkboxes_(create_checkboxes)
{
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);
foreach (QString input, node->inputs()) {
Node *n = node;
while (NodeGroup *g = dynamic_cast<NodeGroup*>(n)) {
const NodeInput &ni = g->GetInputPassthroughs().value(input);
n = ni.node();
input = ni.input();
}
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);
// Start with zero elements for efficiency. We will make the widgets for them if the user
// requests the array UI to be expanded
int arr_sz = 0;
// 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});
if (!(n->GetInputFlags(input) & kInputFlagHidden)) {
CreateWidgets(root_layout, n, input, -1, insert_row);
insert_row++;
if (n->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);
// Start with zero elements for efficiency. We will make the widgets for them if the user
// requests the array UI to be expanded
int arr_sz = 0;
// 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({n, input}, {array_widget, arr_sz, append_btn});
insert_row++;
}
}
}
@@ -277,11 +153,22 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
ui_objects.layout = layout;
ui_objects.row = row;
// Create optional checkbox if requested
if (create_checkboxes_) {
ui_objects.optional_checkbox = new QCheckBox();
connect(ui_objects.optional_checkbox, &QCheckBox::clicked, this, &NodeParamViewItemBody::OptionalCheckBoxClicked);
layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox);
if (create_checkboxes_ == kCheckBoxesOnNonConnected && input_ref.IsConnected()) {
ui_objects.optional_checkbox->setVisible(false);
}
}
// 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);
// Create input label
layout->addWidget(ui_objects.main_label, row, kLabelColumn);
if (node->InputIsArray(input)) {
if (element == -1) {
@@ -292,8 +179,8 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
// Default to collapsed
array_collapse_btn->setChecked(false);
// Collapse button always goes into column 0
layout->addWidget(array_collapse_btn, row, 0);
// Add collapse button to layout
layout->addWidget(array_collapse_btn, row, kArrayCollapseBtnColumn);
// Connect signal to show/hide array params when toggled
connect(array_collapse_btn, &CollapseButton::toggled, this, &NodeParamViewItemBody::ArrayCollapseBtnPressed);
@@ -412,6 +299,12 @@ int NodeParamViewItemBody::GetElementY(NodeInput c) const
c.set_element(-1);
}
while (NodeGroup *g = dynamic_cast<NodeGroup*>(c.node())) {
const NodeInput &passthrough = g->GetInputPassthroughs().value(c.input());
c.set_node(passthrough.node());
c.set_input(passthrough.input());
}
// Find its row in the parameters
QLabel* lbl = input_ui_map_.value(c).main_label;
@@ -448,6 +341,11 @@ void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input)
if (ui_objects.key_control) {
ui_objects.key_control->setVisible(!input.IsConnected());
}
// Show/hide optional checkbox if requested
if (create_checkboxes_ == kCheckBoxesOnNonConnected) {
ui_objects.optional_checkbox->setVisible(!input.IsConnected());
}
}
}
@@ -463,7 +361,13 @@ void NodeParamViewItemBody::PlaceWidgetsFromBridge(QGridLayout* layout, NodePara
void NodeParamViewItemBody::InputArraySizeChangedInternal(Node *node, const QString &input, int size)
{
ArrayUI& array_ui = array_ui_[{node, input}];
NodeInputPair nip = {node, input};
if (!array_ui_.contains(nip)) {
return;
}
ArrayUI& array_ui = array_ui_[nip];
if (size != array_ui.count) {
QGridLayout* grid = static_cast<QGridLayout*>(array_ui.widget->layout());
@@ -575,6 +479,16 @@ void NodeParamViewItemBody::SetTimebase(const rational& timebase)
}
}
void NodeParamViewItemBody::SetInputChecked(const NodeInput &input, bool e)
{
if (input_ui_map_.contains(input)) {
QCheckBox *cb = input_ui_map_.value(input).optional_checkbox;
if (cb) {
cb->setChecked(e);
}
}
}
void NodeParamViewItemBody::ReplaceWidgets(const NodeInput &input)
{
InputUI ui = input_ui_map_.value(input);
@@ -588,12 +502,25 @@ void NodeParamViewItemBody::ShowSpeedDurationDialogForNode()
sdd.exec();
}
void NodeParamViewItemBody::OptionalCheckBoxClicked(bool e)
{
QCheckBox *cb = static_cast<QCheckBox*>(sender());
for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) {
if (it.value().optional_checkbox == cb) {
emit InputCheckedChanged(it.key(), e);
break;
}
}
}
NodeParamViewItemBody::InputUI::InputUI() :
main_label(nullptr),
widget_bridge(nullptr),
connected_label(nullptr),
key_control(nullptr),
extra_btn(nullptr),
optional_checkbox(nullptr),
array_insert_btn(nullptr),
array_remove_btn(nullptr)
{
+55 -73
View File
@@ -21,7 +21,7 @@
#ifndef NODEPARAMVIEWITEM_H
#define NODEPARAMVIEWITEM_H
#include <QDockWidget>
#include <QCheckBox>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
@@ -32,50 +32,24 @@
#include "nodeparamviewarraywidget.h"
#include "nodeparamviewconnectedlabel.h"
#include "nodeparamviewkeyframecontrol.h"
#include "nodeparamviewitembase.h"
#include "nodeparamviewwidgetbridge.h"
#include "widget/clickablelabel/clickablelabel.h"
#include "widget/collapsebutton/collapsebutton.h"
#include "widget/keyframeview/keyframeview.h"
namespace olive {
class NodeParamViewItemTitleBar : public QWidget
{
Q_OBJECT
public:
NodeParamViewItemTitleBar(QWidget* parent = nullptr);
void SetExpanded(bool e);
void SetText(const QString& s)
{
lbl_->setText(s);
lbl_->setToolTip(s);
lbl_->setMinimumWidth(1);
}
signals:
void ExpandedStateChanged(bool e);
void PinToggled(bool e);
protected:
virtual void paintEvent(QPaintEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
private:
bool draw_border_;
QLabel* lbl_;
CollapseButton* collapse_btn_;
enum NodeParamViewCheckBoxBehavior {
kNoCheckBoxes,
kCheckBoxesOn,
kCheckBoxesOnNonConnected
};
class NodeParamViewItemBody : public QWidget {
Q_OBJECT
public:
NodeParamViewItemBody(Node* node, QWidget* parent = nullptr);
NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr);
void SetTimeTarget(Node* target);
@@ -86,7 +60,9 @@ public:
int GetElementY(NodeInput c) const;
// Set the timebase of any timebased widgets contained here
void SetTimebase(const rational& timebase);
void SetTimebase(const rational& timebase);
void SetInputChecked(const NodeInput &input, bool e);
signals:
void RequestSetTime(const rational& time);
@@ -95,6 +71,8 @@ signals:
void ArrayExpandedChanged(bool e);
void InputCheckedChanged(const NodeInput &input, bool e);
private:
void CreateWidgets(QGridLayout *layout, Node* node, const QString& input, int element, int row_index);
@@ -114,6 +92,7 @@ private:
QGridLayout* layout;
int row;
QPushButton *extra_btn;
QCheckBox *optional_checkbox;
NodeParamViewArrayButton* array_insert_btn;
NodeParamViewArrayButton* array_remove_btn;
@@ -135,6 +114,8 @@ private:
rational timebase_;
NodeParamViewCheckBoxBehavior create_checkboxes_;
/**
* @brief The column to place the keyframe controls in
*
@@ -147,6 +128,9 @@ private:
static const int kArrayRemoveColumn;
static const int kExtraButtonColumn;
static const int kOptionalCheckBox;
static const int kArrayCollapseBtnColumn;
static const int kLabelColumn;
static const int kWidgetStartColumn;
private slots:
@@ -168,74 +152,72 @@ private slots:
void ShowSpeedDurationDialogForNode();
void OptionalCheckBoxClicked(bool e);
};
class NodeParamViewItem : public QDockWidget
class NodeParamViewItem : public NodeParamViewItemBase
{
Q_OBJECT
public:
NodeParamViewItem(Node* node, QWidget* parent = nullptr);
NodeParamViewItem(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr);
void SetTimeTarget(Node* target);
void SetTime(const rational& time);
// Set the timebase of the NodeParamViewItemBody
void SetTimebase(const rational& timebase);
Node* GetNode() const;
bool IsExpanded() const;
void SetHighlighted(bool e)
void SetTimeTarget(Node* target)
{
highlighted_ = e;
body_->SetTimeTarget(target);
}
update();
void SetTime(const rational& time)
{
time_ = time;
body_->SetTime(time_);
}
void SetTimebase(const rational& timebase)
{
body_->SetTimebase(timebase);
}
Node* GetNode() const
{
return node_;
}
int GetElementY(const NodeInput& c) const;
public slots:
void SetExpanded(bool e);
void SetInputChecked(const NodeInput &input, bool e);
void ToggleExpanded();
const KeyframeView::NodeConnections &GetKeyframeConnections() const
{
return keyframe_connections_;
}
void SetKeyframeConnections(const KeyframeView::NodeConnections &c)
{
keyframe_connections_ = c;
}
signals:
void RequestSetTime(const rational& time);
void RequestSelectNode(const QVector<Node*>& node);
void PinToggled(bool e);
void ExpandedChanged(bool e);
void ArrayExpandedChanged(bool e);
void Moved();
void InputCheckedChanged(const NodeInput &input, bool e);
protected:
virtual void changeEvent(QEvent *e) override;
virtual void paintEvent(QPaintEvent *event) override;
virtual void moveEvent(QMoveEvent *event) override;
protected slots:
virtual void Retranslate() override;
private:
NodeParamViewItemTitleBar* title_bar_;
NodeParamViewItemBody* body_;
QWidget *hidden_body_;
Node* node_;
rational time_;
bool highlighted_;
private slots:
void Retranslate();
KeyframeView::NodeConnections keyframe_connections_;
};
@@ -0,0 +1,114 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "nodeparamviewitembase.h"
#include <QEvent>
#include <QPainter>
namespace olive {
#define super QDockWidget
NodeParamViewItemBase::NodeParamViewItemBase(QWidget *parent) :
super(parent),
highlighted_(false)
{
// Create title bar widget
title_bar_ = new NodeParamViewItemTitleBar(this);
// Add title bar to widget
this->setTitleBarWidget(title_bar_);
// Connect title bar to this
connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItemBase::SetExpanded);
connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItemBase::PinToggled);
// Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar
// size hints and will shrink as small as possible if the body is hidden)
hidden_body_ = new QWidget(this);
setAutoFillBackground(true);
setFocusPolicy(Qt::ClickFocus);
}
bool NodeParamViewItemBase::IsExpanded() const
{
return title_bar_->IsExpanded();
}
QString NodeParamViewItemBase::GetTitleBarTextFromNode(Node *n)
{
if (n->GetLabel().isEmpty()) {
return n->Name();
} else {
return tr("%1 (%2)").arg(n->GetLabel(), n->Name());
}
}
void NodeParamViewItemBase::SetBody(QWidget *body)
{
body_ = body;
body_->setParent(this);
if (title_bar_->IsExpanded()) {
setWidget(body_);
}
}
void NodeParamViewItemBase::paintEvent(QPaintEvent *event)
{
super::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 NodeParamViewItemBase::SetExpanded(bool e)
{
setWidget(e ? body_ : hidden_body_);
title_bar_->SetExpanded(e);
emit ExpandedChanged(e);
}
void NodeParamViewItemBase::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
super::changeEvent(e);
}
void NodeParamViewItemBase::moveEvent(QMoveEvent *event)
{
super::moveEvent(event);
emit Moved();
}
}
@@ -0,0 +1,93 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEPARAMVIEWITEMBASE_H
#define NODEPARAMVIEWITEMBASE_H
#include <QDockWidget>
#include "nodeparamviewitemtitlebar.h"
#include "node/node.h"
namespace olive {
class NodeParamViewItemBase : public QDockWidget
{
Q_OBJECT
public:
NodeParamViewItemBase(QWidget* parent = nullptr);
void SetHighlighted(bool e)
{
highlighted_ = e;
update();
}
bool IsExpanded() const;
static QString GetTitleBarTextFromNode(Node *n);
public slots:
void SetExpanded(bool e);
void ToggleExpanded()
{
SetExpanded(!IsExpanded());
}
signals:
void PinToggled(bool e);
void ExpandedChanged(bool e);
void Moved();
protected:
void SetBody(QWidget *body);
virtual void paintEvent(QPaintEvent *event) override;
NodeParamViewItemTitleBar* title_bar() const
{
return title_bar_;
}
virtual void changeEvent(QEvent *e) override;
virtual void moveEvent(QMoveEvent *event) override;
protected slots:
virtual void Retranslate(){}
private:
NodeParamViewItemTitleBar* title_bar_;
QWidget *body_;
QWidget *hidden_body_;
bool highlighted_;
};
}
#endif // NODEPARAMVIEWITEMBASE_H
@@ -0,0 +1,90 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "nodeparamviewitemtitlebar.h"
#include <QHBoxLayout>
#include <QPainter>
#include "ui/icons/icons.h"
namespace olive {
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();
add_fx_btn_ = new QPushButton();
add_fx_btn_->setIcon(icon::AddEffect);
add_fx_btn_->setFixedSize(add_fx_btn_->sizeHint().height(), add_fx_btn_->sizeHint().height());
add_fx_btn_->setVisible(false);
layout->addWidget(add_fx_btn_);
connect(add_fx_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::AddEffectButtonClicked);
pin_btn_ = new QPushButton(QStringLiteral("P"));
pin_btn_->setCheckable(true);
pin_btn_->setFixedSize(pin_btn_->sizeHint().height(), pin_btn_->sizeHint().height());
pin_btn_->setVisible(false);
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();
}
}

Some files were not shown because too many files have changed in this diff Show More