Merge branch 'master' into mathnode
This commit is contained in:
+29
-9
@@ -1,16 +1,18 @@
|
||||
#include "xmlutils.h"
|
||||
|
||||
#include "node/block/block.h"
|
||||
#include "node/factory.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
|
||||
Node* XMLLoadNode(QXmlStreamReader* reader) {
|
||||
QString node_id;
|
||||
quintptr node_ptr = 0;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == "id") {
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
node_id = attr.value().toString();
|
||||
|
||||
// Currently the only thing we need
|
||||
break;
|
||||
} else if (attr.name() == QStringLiteral("ptr")) {
|
||||
node_ptr = attr.value().toULongLong();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,20 +23,26 @@ Node* XMLLoadNode(QXmlStreamReader* reader) {
|
||||
|
||||
Node* node = NodeFactory::CreateFromID(node_id);
|
||||
|
||||
if (!node) {
|
||||
if (node) {
|
||||
node->setProperty("xml_ptr", node_ptr);
|
||||
} else {
|
||||
qWarning() << "Failed to load" << node_id << "- no node with that ID is installed";
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void XMLConnectNodes(const QHash<quintptr, NodeOutput*>& output_ptrs, const QList<NodeParam::SerializedConnection>& desired_connections)
|
||||
void XMLConnectNodes(const XMLNodeData &xml_node_data, QUndoCommand *command)
|
||||
{
|
||||
foreach (const NodeParam::SerializedConnection& con, desired_connections) {
|
||||
NodeOutput* out = output_ptrs.value(con.output);
|
||||
foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) {
|
||||
NodeOutput* out = xml_node_data.output_ptrs.value(con.output);
|
||||
|
||||
if (out) {
|
||||
NodeParam::ConnectEdge(out, con.input);
|
||||
if (command) {
|
||||
new NodeEdgeAddCommand(out, con.input, command);
|
||||
} else {
|
||||
NodeParam::ConnectEdge(out, con.input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,3 +62,15 @@ bool XMLReadNextStartElement(QXmlStreamReader *reader)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void XMLLinkBlocks(const XMLNodeData &xml_node_data)
|
||||
{
|
||||
foreach (const XMLNodeData::BlockLink& l1, xml_node_data.block_links) {
|
||||
foreach (const XMLNodeData::BlockLink& l2, xml_node_data.block_links) {
|
||||
if (l1.link == l2.block->property("xml_ptr")) {
|
||||
Block::Link(l1.block, l2.block);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-2
@@ -1,9 +1,16 @@
|
||||
#ifndef XMLREADLOOP_H
|
||||
#define XMLREADLOOP_H
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "project/item/footage/stream.h"
|
||||
|
||||
class Block;
|
||||
class Node;
|
||||
class NodeParam;
|
||||
class NodeInput;
|
||||
class NodeOutput;
|
||||
|
||||
#define XMLAttributeLoop(reader, item) \
|
||||
QXmlStreamAttributes __attributes = reader->attributes(); \
|
||||
@@ -11,8 +18,34 @@
|
||||
|
||||
Node *XMLLoadNode(QXmlStreamReader* reader);
|
||||
|
||||
void XMLConnectNodes(const QHash<quintptr, NodeOutput *> &output_ptrs, const QList<NodeParam::SerializedConnection> &desired_connections);
|
||||
struct XMLNodeData {
|
||||
struct SerializedConnection {
|
||||
NodeInput* input;
|
||||
quintptr output;
|
||||
};
|
||||
|
||||
struct FootageConnection {
|
||||
NodeInput* input;
|
||||
quintptr footage;
|
||||
};
|
||||
|
||||
struct BlockLink {
|
||||
Block* block;
|
||||
quintptr link;
|
||||
};
|
||||
|
||||
QHash<quintptr, NodeOutput*> output_ptrs;
|
||||
QList<SerializedConnection> desired_connections;
|
||||
QHash<quintptr, StreamPtr> footage_ptrs;
|
||||
QList<FootageConnection> footage_connections;
|
||||
QList<BlockLink> block_links;
|
||||
|
||||
};
|
||||
|
||||
void XMLConnectNodes(const XMLNodeData& xml_node_data, QUndoCommand* command = nullptr);
|
||||
|
||||
bool XMLReadNextStartElement(QXmlStreamReader* reader);
|
||||
|
||||
void XMLLinkBlocks(const XMLNodeData& xml_node_data);
|
||||
|
||||
#endif // XMLREADLOOP_H
|
||||
|
||||
+4
-93
@@ -513,103 +513,14 @@ void Core::SetAutorecoveryInterval(int minutes)
|
||||
autorecovery_timer_.setInterval(minutes * 60000);
|
||||
}
|
||||
|
||||
void Core::CopyNodesToClipboard(const QList<Node *> &nodes)
|
||||
void Core::CopyStringToClipboard(const QString &s)
|
||||
{
|
||||
QString copy_str;
|
||||
|
||||
QXmlStreamWriter writer(©_str);
|
||||
writer.setAutoFormatting(true);
|
||||
|
||||
writer.writeStartDocument();
|
||||
writer.writeStartElement(QStringLiteral("olive"));
|
||||
|
||||
foreach (Node* n, nodes) {
|
||||
n->Save(&writer);
|
||||
}
|
||||
|
||||
writer.writeEndElement(); // clipboard
|
||||
writer.writeEndDocument();
|
||||
|
||||
QGuiApplication::clipboard()->setText(copy_str);
|
||||
QGuiApplication::clipboard()->setText(s);
|
||||
}
|
||||
|
||||
QList<Node*> Core::PasteNodesFromClipboard(Sequence *graph)
|
||||
QString Core::PasteStringFromClipboard()
|
||||
{
|
||||
QString clipboard = QGuiApplication::clipboard()->text();
|
||||
|
||||
if (clipboard.isEmpty()) {
|
||||
return QList<Node*>();
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(clipboard);
|
||||
|
||||
QList<Node*> pasted_nodes;
|
||||
QHash<quintptr, NodeOutput*> output_ptrs;
|
||||
QList<NodeParam::SerializedConnection> desired_connections;
|
||||
QList<NodeInput::FootageConnection> footage_connections;
|
||||
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("olive")) {
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("node")) {
|
||||
Node* node = XMLLoadNode(&reader);
|
||||
|
||||
if (node) {
|
||||
node->Load(&reader, output_ptrs, desired_connections, footage_connections, nullptr);
|
||||
|
||||
graph->AddNode(node);
|
||||
|
||||
pasted_nodes.append(node);
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Make connections
|
||||
if (!desired_connections.isEmpty()) {
|
||||
XMLConnectNodes(output_ptrs, desired_connections);
|
||||
}
|
||||
|
||||
// Connect footage to existing footage if it exists
|
||||
if (!footage_connections.isEmpty()) {
|
||||
// Get list of all footage from project
|
||||
// FIXME: Assumes sequence
|
||||
QList<ItemPtr> footage = graph->project()->get_items_of_type(Item::kFootage);
|
||||
|
||||
if (!footage.isEmpty()) {
|
||||
foreach (const NodeInput::FootageConnection& con, footage_connections) {
|
||||
if (con.footage) {
|
||||
// Assume this is a pointer to a Stream*
|
||||
Stream* loaded_stream = reinterpret_cast<Stream*>(con.footage);
|
||||
|
||||
bool found = false;
|
||||
|
||||
foreach (ItemPtr item, footage) {
|
||||
const QList<StreamPtr>& streams = std::static_pointer_cast<Footage>(item)->streams();
|
||||
|
||||
foreach (StreamPtr s, streams) {
|
||||
if (s.get() == loaded_stream) {
|
||||
con.input->set_standard_value(QVariant::fromValue(s));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pasted_nodes;
|
||||
return QGuiApplication::clipboard()->text();
|
||||
}
|
||||
|
||||
bool Core::SaveActiveProject()
|
||||
|
||||
+2
-2
@@ -151,9 +151,9 @@ public:
|
||||
*/
|
||||
void SetAutorecoveryInterval(int minutes);
|
||||
|
||||
void CopyNodesToClipboard(const QList<Node*>& nodes);
|
||||
static void CopyStringToClipboard(const QString& s);
|
||||
|
||||
QList<Node*> PasteNodesFromClipboard(Sequence *graph);
|
||||
static QString PasteStringFromClipboard();
|
||||
|
||||
/**
|
||||
* @brief Return a list of supported frame rates in rational form
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "node/output/track/track.h"
|
||||
#include "transition/transition.h"
|
||||
|
||||
Block::Block() :
|
||||
@@ -197,6 +198,22 @@ rational Block::MediaToSequenceTime(const rational &media_time) const
|
||||
return (media_time - media_in()) / speed() + in();
|
||||
}
|
||||
|
||||
void Block::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data)
|
||||
{
|
||||
if (reader->name() == QStringLiteral("link")) {
|
||||
xml_node_data.block_links.append({this, reader->readElementText().toULongLong()});
|
||||
} else {
|
||||
Node::LoadInternal(reader, xml_node_data);
|
||||
}
|
||||
}
|
||||
|
||||
void Block::SaveInternal(QXmlStreamWriter *writer) const
|
||||
{
|
||||
foreach (Block* link, linked_clips_) {
|
||||
writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast<quintptr>(link)));
|
||||
}
|
||||
}
|
||||
|
||||
void Block::LengthInputChanged()
|
||||
{
|
||||
emit LengthChanged(length());
|
||||
@@ -303,3 +320,13 @@ NodeInput *Block::speed_input() const
|
||||
return speed_input_;
|
||||
}
|
||||
|
||||
void Block::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from)
|
||||
{
|
||||
// We ignore length changes since they don't have an effect on our frames
|
||||
if (from == length_input_) {
|
||||
return;
|
||||
}
|
||||
|
||||
Node::InvalidateCache(start_range, end_range, from);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ public:
|
||||
NodeInput* media_in_input() const;
|
||||
NodeInput* speed_input() const;
|
||||
|
||||
virtual void InvalidateCache(const rational& start_range, const rational& end_range, NodeInput* from = nullptr) override;
|
||||
|
||||
public slots:
|
||||
|
||||
signals:
|
||||
@@ -111,6 +113,10 @@ protected:
|
||||
|
||||
rational MediaToSequenceTime(const rational& media_time) const;
|
||||
|
||||
virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data) override;
|
||||
|
||||
virtual void SaveInternal(QXmlStreamWriter* writer) const override;
|
||||
|
||||
Block* previous_;
|
||||
Block* next_;
|
||||
|
||||
|
||||
+18
-8
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "input.h"
|
||||
|
||||
#include <QMatrix4x4>
|
||||
#include <QVector2D>
|
||||
#include <QVector3D>
|
||||
#include <QVector4D>
|
||||
@@ -84,7 +85,7 @@ QString NodeInput::name()
|
||||
return NodeParam::name();
|
||||
}
|
||||
|
||||
void NodeInput::Load(QXmlStreamReader *reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<SerializedConnection> &input_connections, QList<FootageConnection>& footage_connections, const QAtomicInt *cancelled)
|
||||
void NodeInput::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled)
|
||||
{
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (cancelled && *cancelled) {
|
||||
@@ -116,7 +117,7 @@ void NodeInput::Load(QXmlStreamReader *reader, QHash<quintptr, NodeOutput*>& par
|
||||
if (value_text.isEmpty()) {
|
||||
standard_value_.replace(val_index, QVariant());
|
||||
} else {
|
||||
standard_value_.replace(val_index, StringToValue(value_text, footage_connections));
|
||||
standard_value_.replace(val_index, StringToValue(value_text, xml_node_data.footage_connections));
|
||||
}
|
||||
|
||||
val_index++;
|
||||
@@ -165,7 +166,7 @@ void NodeInput::Load(QXmlStreamReader *reader, QHash<quintptr, NodeOutput*>& par
|
||||
}
|
||||
}
|
||||
|
||||
key_value = StringToValue(reader->readElementText(), footage_connections);
|
||||
key_value = StringToValue(reader->readElementText(), xml_node_data.footage_connections);
|
||||
|
||||
NodeKeyframePtr key = NodeKeyframe::Create(key_time, key_value, key_type, track);
|
||||
key->set_bezier_control_in(key_in_handle);
|
||||
@@ -189,13 +190,13 @@ void NodeInput::Load(QXmlStreamReader *reader, QHash<quintptr, NodeOutput*>& par
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("connection")) {
|
||||
input_connections.append({this, reader->readElementText().toULongLong()});
|
||||
xml_node_data.desired_connections.append({this, reader->readElementText().toULongLong()});
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LoadInternal(reader, param_ptrs, input_connections, footage_connections, cancelled);
|
||||
LoadInternal(reader, xml_node_data, cancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,7 +269,7 @@ const NodeParam::DataType &NodeInput::data_type() const
|
||||
return data_type_;
|
||||
}
|
||||
|
||||
void NodeInput::LoadInternal(QXmlStreamReader* reader, QHash<quintptr, NodeOutput *>&, QList<SerializedConnection>&, QList<FootageConnection>&, const QAtomicInt*)
|
||||
void NodeInput::LoadInternal(QXmlStreamReader* reader, XMLNodeData &, const QAtomicInt*)
|
||||
{
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
@@ -289,12 +290,21 @@ QString NodeInput::ValueToString(const QVariant &value) const
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
qWarning() << "Failed to convert type" << data_type_ << "to string";
|
||||
if (!value.isNull()) {
|
||||
qWarning() << "Failed to convert type" << QStringLiteral("%1").arg(data_type_, 0, 16) << "to string";
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
// These data types need no XML representation
|
||||
case kTexture:
|
||||
case kSamples:
|
||||
case kBuffer:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant NodeInput::StringToValue(const QString &string, QList<NodeInput::FootageConnection>& footage_connections)
|
||||
QVariant NodeInput::StringToValue(const QString &string, QList<XMLNodeData::FootageConnection>& footage_connections)
|
||||
{
|
||||
switch (data_type_) {
|
||||
case kRational:
|
||||
|
||||
+3
-3
@@ -54,7 +54,7 @@ public:
|
||||
|
||||
virtual QString name() override;
|
||||
|
||||
virtual void Load(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<SerializedConnection> &input_connections, QList<FootageConnection>& footage_connections, const QAtomicInt* cancelled) override;
|
||||
virtual void Load(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled) override;
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const override;
|
||||
|
||||
@@ -270,14 +270,14 @@ signals:
|
||||
void PropertyChanged(const QString& s, const QVariant& v);
|
||||
|
||||
protected:
|
||||
virtual void LoadInternal(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<SerializedConnection> &input_connections, QList<FootageConnection>& footage_connections, const QAtomicInt* cancelled);
|
||||
virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled);
|
||||
|
||||
virtual void SaveInternal(QXmlStreamWriter* writer) const;
|
||||
|
||||
private:
|
||||
QString ValueToString(const QVariant& value) const;
|
||||
|
||||
QVariant StringToValue(const QString &string, QList<FootageConnection> &footage_connections);
|
||||
QVariant StringToValue(const QString &string, QList<XMLNodeData::FootageConnection> &footage_connections);
|
||||
|
||||
void SaveConnections(QXmlStreamWriter* writer) const;
|
||||
|
||||
|
||||
@@ -164,19 +164,19 @@ void NodeInputArray::RemoveAt(int index)
|
||||
RemoveLast();
|
||||
}
|
||||
|
||||
void NodeInputArray::LoadInternal(QXmlStreamReader *reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<SerializedConnection> &input_connections, QList<FootageConnection>& footage_connections, const QAtomicInt* cancelled)
|
||||
void NodeInputArray::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled)
|
||||
{
|
||||
if (reader->name() == QStringLiteral("subparameters")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
Append();
|
||||
At(GetSize() - 1)->Load(reader, param_ptrs, input_connections, footage_connections, cancelled);
|
||||
At(GetSize() - 1)->Load(reader, xml_node_data, cancelled);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NodeInput::Load(reader, param_ptrs, input_connections, footage_connections, cancelled);
|
||||
NodeInput::Load(reader, xml_node_data, cancelled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ signals:
|
||||
void SizeChanged(int size);
|
||||
|
||||
protected:
|
||||
virtual void LoadInternal(QXmlStreamReader* reader, QHash<quintptr, NodeOutput *> ¶m_ptrs, QList<SerializedConnection> &input_connections, QList<FootageConnection>& footage_connections, const QAtomicInt *cancelled) override;
|
||||
virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt *cancelled) override;
|
||||
|
||||
virtual void SaveInternal(QXmlStreamWriter* writer) const override;
|
||||
|
||||
|
||||
+18
-5
@@ -50,7 +50,7 @@ Node::~Node()
|
||||
}
|
||||
}
|
||||
|
||||
void Node::Load(QXmlStreamReader *reader, QHash<quintptr, NodeOutput *> &output_ptrs, QList<NodeInput::SerializedConnection>& input_connections, QList<NodeParam::FootageConnection>& footage_connections, const QAtomicInt* cancelled)
|
||||
void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled)
|
||||
{
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (cancelled && *cancelled) {
|
||||
@@ -80,23 +80,27 @@ void Node::Load(QXmlStreamReader *reader, QHash<quintptr, NodeOutput *> &output_
|
||||
continue;
|
||||
}
|
||||
|
||||
param->Load(reader, output_ptrs, input_connections, footage_connections, cancelled);
|
||||
param->Load(reader, xml_node_data, cancelled);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
LoadInternal(reader, xml_node_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::Save(QXmlStreamWriter *writer, const QString &custom_name) const
|
||||
{
|
||||
writer->writeStartElement(custom_name.isEmpty() ? "node" : custom_name);
|
||||
writer->writeStartElement(custom_name.isEmpty() ? QStringLiteral("node") : custom_name);
|
||||
|
||||
writer->writeAttribute("id", id());
|
||||
writer->writeAttribute(QStringLiteral("id"), id());
|
||||
|
||||
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
|
||||
|
||||
foreach (NodeParam* param, parameters()) {
|
||||
param->Save(writer);
|
||||
}
|
||||
|
||||
SaveInternal(writer);
|
||||
|
||||
writer->writeEndElement(); // node
|
||||
}
|
||||
|
||||
@@ -217,6 +221,15 @@ void Node::DependentEdgeChanged(NodeInput *from)
|
||||
}
|
||||
}
|
||||
|
||||
void Node::LoadInternal(QXmlStreamReader *reader, XMLNodeData &)
|
||||
{
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
|
||||
void Node::SaveInternal(QXmlStreamWriter *) const
|
||||
{
|
||||
}
|
||||
|
||||
QString Node::ReadFileAsString(const QString &filename)
|
||||
{
|
||||
QFile f(filename);
|
||||
|
||||
+6
-1
@@ -27,6 +27,7 @@
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "node/dependency.h"
|
||||
#include "node/input.h"
|
||||
#include "node/inputarray.h"
|
||||
@@ -73,7 +74,7 @@ public:
|
||||
/**
|
||||
* @brief Clear current node variables and replace them with
|
||||
*/
|
||||
void Load(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<NodeInput::SerializedConnection> &input_connections, QList<NodeParam::FootageConnection>& footage_connections, const QAtomicInt *cancelled);
|
||||
void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled);
|
||||
|
||||
/**
|
||||
* @brief Save this node into a text/XML format
|
||||
@@ -358,6 +359,10 @@ protected:
|
||||
|
||||
virtual void DependentEdgeChanged(NodeInput* from);
|
||||
|
||||
virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data);
|
||||
|
||||
virtual void SaveInternal(QXmlStreamWriter* writer) const;
|
||||
|
||||
public slots:
|
||||
|
||||
signals:
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ QString NodeOutput::name()
|
||||
return NodeParam::name();
|
||||
}
|
||||
|
||||
void NodeOutput::Load(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<SerializedConnection>&, QList<FootageConnection>&, const QAtomicInt *cancelled)
|
||||
void NodeOutput::Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled)
|
||||
{
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (cancelled && *cancelled) {
|
||||
@@ -52,7 +52,7 @@ void NodeOutput::Load(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& pa
|
||||
if (attr.name() == "ptr") {
|
||||
quintptr saved_ptr = attr.value().toULongLong();
|
||||
|
||||
param_ptrs.insert(saved_ptr, this);
|
||||
xml_node_data.output_ptrs.insert(saved_ptr, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
virtual QString name() override;
|
||||
|
||||
virtual void Load(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<SerializedConnection> &input_connections, QList<FootageConnection>& footage_connections, const QAtomicInt* cancelled) override;
|
||||
virtual void Load(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled) override;
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const override;
|
||||
|
||||
|
||||
@@ -331,7 +331,7 @@ void TrackOutput::ReplaceBlock(Block *old, Block *replace)
|
||||
}
|
||||
}
|
||||
|
||||
TrackOutput *TrackOutput::TrackFromBlock(Block *block)
|
||||
TrackOutput *TrackOutput::TrackFromBlock(const Block *block)
|
||||
{
|
||||
NodeOutput* output = block->output();
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ public:
|
||||
|
||||
void UnblockInvalidateCache();
|
||||
|
||||
static TrackOutput* TrackFromBlock(Block* block);
|
||||
static TrackOutput* TrackFromBlock(const Block *block);
|
||||
|
||||
const rational& track_length() const;
|
||||
|
||||
|
||||
+2
-11
@@ -27,6 +27,7 @@
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "node/edge.h"
|
||||
|
||||
class Node;
|
||||
@@ -222,20 +223,10 @@ public:
|
||||
|
||||
virtual ~NodeParam() override;
|
||||
|
||||
struct SerializedConnection {
|
||||
NodeInput* input;
|
||||
quintptr output;
|
||||
};
|
||||
|
||||
struct FootageConnection {
|
||||
NodeInput* input;
|
||||
quintptr footage;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Load function
|
||||
*/
|
||||
virtual void Load(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<SerializedConnection> &input_connections, QList<FootageConnection>& footage_connections, const QAtomicInt* cancelled) = 0;
|
||||
virtual void Load(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled) = 0;
|
||||
|
||||
/**
|
||||
* @brief Save function
|
||||
|
||||
@@ -44,7 +44,7 @@ QIcon Folder::icon()
|
||||
return icon::Folder;
|
||||
}
|
||||
|
||||
void Folder::Load(QXmlStreamReader *reader, QHash<quintptr, StreamPtr> &footage_ptrs, QList<NodeParam::FootageConnection>& footage_connections, const QAtomicInt *cancelled)
|
||||
void Folder::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const QAtomicInt *cancelled)
|
||||
{
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (cancelled && *cancelled) {
|
||||
@@ -75,7 +75,7 @@ void Folder::Load(QXmlStreamReader *reader, QHash<quintptr, StreamPtr> &footage_
|
||||
}
|
||||
|
||||
add_child(child);
|
||||
child->Load(reader, footage_ptrs, footage_connections, cancelled);
|
||||
child->Load(reader, xml_node_data, cancelled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
virtual QIcon icon() override;
|
||||
|
||||
virtual void Load(QXmlStreamReader* reader, QHash<quintptr, StreamPtr> &footage_ptrs, QList<NodeParam::FootageConnection> &footage_connections, const QAtomicInt *cancelled) override;
|
||||
virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) override;
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const override;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ Footage::~Footage()
|
||||
ClearStreams();
|
||||
}
|
||||
|
||||
void Footage::Load(QXmlStreamReader *reader, QHash<quintptr, StreamPtr>& footage_ptrs, QList<NodeParam::FootageConnection>&, const QAtomicInt* cancelled)
|
||||
void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled)
|
||||
{
|
||||
QXmlStreamAttributes attributes = reader->attributes();
|
||||
|
||||
@@ -74,7 +74,7 @@ void Footage::Load(QXmlStreamReader *reader, QHash<quintptr, StreamPtr>& footage
|
||||
}
|
||||
|
||||
if (stream_index > -1 && stream_ptr > 0) {
|
||||
footage_ptrs.insert(stream_ptr, stream(stream_index));
|
||||
xml_node_data.footage_ptrs.insert(stream_ptr, stream(stream_index));
|
||||
|
||||
stream(stream_index)->Load(reader);
|
||||
} else {
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
/**
|
||||
* @brief Load function
|
||||
*/
|
||||
virtual void Load(QXmlStreamReader* reader, QHash<quintptr, StreamPtr> &footage_ptrs, QList<NodeParam::FootageConnection> &footage_connections, const QAtomicInt *cancelled) override;
|
||||
virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) override;
|
||||
|
||||
/**
|
||||
* @brief Save function
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "common/constructors.h"
|
||||
#include "common/threadedobject.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "node/param.h"
|
||||
#include "project/item/footage/stream.h"
|
||||
|
||||
@@ -65,7 +66,7 @@ public:
|
||||
|
||||
DISABLE_COPY_MOVE(Item)
|
||||
|
||||
virtual void Load(QXmlStreamReader* reader, QHash<quintptr, StreamPtr> &footage_ptrs, QList<NodeParam::FootageConnection> &footage_connections, const QAtomicInt *cancelled) = 0;
|
||||
virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) = 0;
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const = 0;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ Sequence::Sequence()
|
||||
AddNode(viewer_output_);
|
||||
}
|
||||
|
||||
void Sequence::Load(QXmlStreamReader *reader, QHash<quintptr, StreamPtr> &, QList<NodeInput::FootageConnection>& footage_connections, const QAtomicInt *cancelled)
|
||||
void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const QAtomicInt *cancelled)
|
||||
{
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (cancelled && *cancelled) {
|
||||
@@ -56,9 +56,6 @@ void Sequence::Load(QXmlStreamReader *reader, QHash<quintptr, StreamPtr> &, QLis
|
||||
}
|
||||
}
|
||||
|
||||
QHash<quintptr, NodeOutput*> output_ptrs;
|
||||
QList<NodeParam::SerializedConnection> desired_connections;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
@@ -110,7 +107,7 @@ void Sequence::Load(QXmlStreamReader *reader, QHash<quintptr, StreamPtr> &, QLis
|
||||
}
|
||||
|
||||
if (node) {
|
||||
node->Load(reader, output_ptrs, desired_connections, footage_connections, cancelled);
|
||||
node->Load(reader, xml_node_data, cancelled);
|
||||
|
||||
AddNode(node);
|
||||
}
|
||||
@@ -120,7 +117,10 @@ void Sequence::Load(QXmlStreamReader *reader, QHash<quintptr, StreamPtr> &, QLis
|
||||
}
|
||||
|
||||
// Make connections
|
||||
XMLConnectNodes(output_ptrs, desired_connections);
|
||||
XMLConnectNodes(xml_node_data);
|
||||
|
||||
// Link blocks
|
||||
XMLLinkBlocks(xml_node_data);
|
||||
|
||||
// Ensure this and all children are in the main thread
|
||||
// (FIXME: Weird place for this? This should probably be in ProjectLoadManager somehow)
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
/**
|
||||
* @brief Load function
|
||||
*/
|
||||
virtual void Load(QXmlStreamReader* reader, QHash<quintptr, StreamPtr> &footage_ptrs, QList<NodeParam::FootageConnection> &footage_connections, const QAtomicInt* cancelled) override;
|
||||
virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled) override;
|
||||
|
||||
/**
|
||||
* @brief Save function
|
||||
|
||||
@@ -35,14 +35,13 @@ Project::Project()
|
||||
|
||||
void Project::Load(QXmlStreamReader *reader, const QAtomicInt* cancelled)
|
||||
{
|
||||
QHash<quintptr, StreamPtr> footage_ptrs;
|
||||
QList<NodeInput::FootageConnection> footage_connections;
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("folder")) {
|
||||
|
||||
// Assume this folder is our root
|
||||
root_.Load(reader, footage_ptrs, footage_connections, cancelled);
|
||||
root_.Load(reader, xml_node_data, cancelled);
|
||||
|
||||
} else if (reader->name() == QStringLiteral("colormanagement")) {
|
||||
|
||||
@@ -62,9 +61,9 @@ void Project::Load(QXmlStreamReader *reader, const QAtomicInt* cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const NodeInput::FootageConnection& con, footage_connections) {
|
||||
foreach (const XMLNodeData::FootageConnection& con, xml_node_data.footage_connections) {
|
||||
if (con.footage) {
|
||||
con.input->set_standard_value(QVariant::fromValue(footage_ptrs.value(con.footage)));
|
||||
con.input->set_standard_value(QVariant::fromValue(xml_node_data.footage_ptrs.value(con.footage)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ AudioRenderBackend::AudioRenderBackend(QObject *parent) :
|
||||
|
||||
void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
CancelQueue();
|
||||
|
||||
// Set new parameters
|
||||
params_ = params;
|
||||
|
||||
// Set params on all processors
|
||||
// FIXME: Undefined behavior if the processors are currently working, this may need to be delayed like the
|
||||
// recompile signal
|
||||
foreach (RenderWorker* worker, processors_) {
|
||||
static_cast<AudioRenderWorker*>(worker)->SetParameters(params_);
|
||||
}
|
||||
|
||||
@@ -85,10 +85,7 @@ void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
|
||||
|
||||
void VideoRenderBackend::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
|
||||
{
|
||||
if (!AllProcessorsAreAvailable()) {
|
||||
qCritical() << "Attempted to set operating mode on a backend whose workers are still busy";
|
||||
return;
|
||||
}
|
||||
CancelQueue();
|
||||
|
||||
operating_mode_ = mode;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ add_subdirectory(focusablelineedit)
|
||||
add_subdirectory(footagecombobox)
|
||||
add_subdirectory(keyframeview)
|
||||
add_subdirectory(menu)
|
||||
add_subdirectory(nodecopypaste)
|
||||
add_subdirectory(nodeview)
|
||||
add_subdirectory(nodeparamview)
|
||||
add_subdirectory(panel)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 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}
|
||||
widget/nodecopypaste/nodecopypaste.h
|
||||
widget/nodecopypaste/nodecopypaste.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "nodecopypaste.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "core.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
|
||||
void NodeCopyPasteWidget::CopyNodesToClipboard(const QList<Node *> &nodes, void *userdata)
|
||||
{
|
||||
QString copy_str;
|
||||
|
||||
QXmlStreamWriter writer(©_str);
|
||||
writer.setAutoFormatting(true);
|
||||
|
||||
writer.writeStartDocument();
|
||||
writer.writeStartElement(QStringLiteral("olive"));
|
||||
|
||||
foreach (Node* n, nodes) {
|
||||
n->Save(&writer);
|
||||
}
|
||||
|
||||
CopyNodesToClipboardInternal(&writer, userdata);
|
||||
|
||||
writer.writeEndElement(); // olive
|
||||
writer.writeEndDocument();
|
||||
|
||||
Core::CopyStringToClipboard(copy_str);
|
||||
}
|
||||
|
||||
QList<Node *> NodeCopyPasteWidget::PasteNodesFromClipboard(Sequence *graph, QUndoCommand* command, void *userdata)
|
||||
{
|
||||
QString clipboard = Core::PasteStringFromClipboard();
|
||||
|
||||
if (clipboard.isEmpty()) {
|
||||
return QList<Node*>();
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(clipboard);
|
||||
|
||||
QList<Node*> pasted_nodes;
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("olive")) {
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("node")) {
|
||||
Node* node = XMLLoadNode(&reader);
|
||||
|
||||
if (node) {
|
||||
node->Load(&reader, xml_node_data, nullptr);
|
||||
|
||||
pasted_nodes.append(node);
|
||||
}
|
||||
} else {
|
||||
PasteNodesFromClipboardInternal(&reader, userdata);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (pasted_nodes.isEmpty()) {
|
||||
// If we passed through the whole string and there were no nodes, it must not be data for us after all
|
||||
return QList<Node*>();
|
||||
}
|
||||
|
||||
// If we have some nodes AND the XML data was malformed, the user should probably know
|
||||
if (reader.hasError()) {
|
||||
// Delete all nodes so this is a no-op
|
||||
foreach (Node* n, pasted_nodes) {
|
||||
delete n;
|
||||
}
|
||||
|
||||
// If this was NOT an internal error, we assume it's an XML error that the user needs to know about
|
||||
QMessageBox::critical(Core::instance()->main_window(),
|
||||
QCoreApplication::translate("NodeCopyPasteWidget", "Error pasting nodes"),
|
||||
QCoreApplication::translate("NodeCopyPasteWidget", "Failed to paste nodes: %1").arg(reader.errorString()),
|
||||
QMessageBox::Ok);
|
||||
|
||||
return QList<Node*>();
|
||||
}
|
||||
|
||||
// Add all nodes to graph
|
||||
foreach (Node* n, pasted_nodes) {
|
||||
new NodeAddCommand(graph, n, command);
|
||||
}
|
||||
|
||||
// Make connections
|
||||
if (!xml_node_data.desired_connections.isEmpty()) {
|
||||
XMLConnectNodes(xml_node_data, command);
|
||||
}
|
||||
|
||||
// Link blocks
|
||||
XMLLinkBlocks(xml_node_data);
|
||||
|
||||
// Connect footage to existing footage if it exists
|
||||
if (!xml_node_data.footage_connections.isEmpty()) {
|
||||
// Get list of all footage from project
|
||||
QList<ItemPtr> footage = graph->project()->get_items_of_type(Item::kFootage);
|
||||
|
||||
if (!footage.isEmpty()) {
|
||||
foreach (const XMLNodeData::FootageConnection& con, xml_node_data.footage_connections) {
|
||||
if (con.footage) {
|
||||
// Assume this is a pointer to a Stream*
|
||||
Stream* loaded_stream = reinterpret_cast<Stream*>(con.footage);
|
||||
|
||||
bool found = false;
|
||||
|
||||
foreach (ItemPtr item, footage) {
|
||||
const QList<StreamPtr>& streams = std::static_pointer_cast<Footage>(item)->streams();
|
||||
|
||||
foreach (StreamPtr s, streams) {
|
||||
if (s.get() == loaded_stream) {
|
||||
con.input->set_standard_value(QVariant::fromValue(s));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pasted_nodes;
|
||||
}
|
||||
|
||||
void NodeCopyPasteWidget::CopyNodesToClipboardInternal(QXmlStreamWriter*, void*)
|
||||
{
|
||||
}
|
||||
|
||||
void NodeCopyPasteWidget::PasteNodesFromClipboardInternal(QXmlStreamReader* reader, void*)
|
||||
{
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef NODECOPYPASTEWIDGET_H
|
||||
#define NODECOPYPASTEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "project/item/sequence/sequence.h"
|
||||
|
||||
class NodeCopyPasteWidget
|
||||
{
|
||||
public:
|
||||
NodeCopyPasteWidget() = default;
|
||||
|
||||
protected:
|
||||
void CopyNodesToClipboard(const QList<Node*>& nodes, void* userdata = nullptr);
|
||||
|
||||
QList<Node*> PasteNodesFromClipboard(Sequence *graph, QUndoCommand *command, void* userdata = nullptr);
|
||||
|
||||
virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void* userdata);
|
||||
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, void* userdata);
|
||||
|
||||
};
|
||||
|
||||
#endif // NODECOPYPASTEWIDGET_H
|
||||
@@ -143,7 +143,7 @@ void NodeView::CopySelected(bool cut)
|
||||
return;
|
||||
}
|
||||
|
||||
Core::instance()->CopyNodesToClipboard(selected);
|
||||
CopyNodesToClipboard(selected);
|
||||
|
||||
if (cut) {
|
||||
DeleteSelected();
|
||||
@@ -156,7 +156,11 @@ void NodeView::Paste()
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Node*> pasted_nodes = Core::instance()->PasteNodesFromClipboard(static_cast<Sequence*>(graph_));
|
||||
QUndoCommand* command = new QUndoCommand();
|
||||
|
||||
QList<Node*> pasted_nodes = PasteNodesFromClipboard(static_cast<Sequence*>(graph_), command);
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
if (!pasted_nodes.isEmpty()) {
|
||||
// FIXME: Attach to cursor so user can drop in place
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "node/graph.h"
|
||||
#include "nodeviewscene.h"
|
||||
#include "widget/nodecopypaste/nodecopypaste.h"
|
||||
|
||||
/**
|
||||
* @brief A widget for viewing and editing node graphs
|
||||
@@ -33,7 +34,7 @@
|
||||
* This widget takes a NodeGraph object and constructs a QGraphicsScene representing its data, viewing and allowing
|
||||
* the user to make modifications to it.
|
||||
*/
|
||||
class NodeView : public QGraphicsView
|
||||
class NodeView : public QGraphicsView, public NodeCopyPasteWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
@@ -135,11 +135,8 @@ void TimelineWidget::Clear()
|
||||
QMap<Block*, TimelineViewBlockItem*>::iterator iterator = block_items_.begin();
|
||||
|
||||
while (iterator != block_items_.end()) {
|
||||
TimelineViewBlockItem* item = iterator.value();
|
||||
|
||||
delete iterator.value();
|
||||
iterator = block_items_.erase(iterator);
|
||||
|
||||
delete item;
|
||||
}
|
||||
|
||||
block_items_.clear();
|
||||
@@ -254,6 +251,72 @@ void TimelineWidget::DisconnectNodeInternal(ViewerOutput *n)
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void* userdata)
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("timeline"));
|
||||
|
||||
// Cache the earliest in point so all copied clips have a "relative" in point that can be pasted anywhere
|
||||
QList<TimelineViewBlockItem*>& selected = *static_cast<QList<TimelineViewBlockItem*>*>(userdata);
|
||||
rational earliest_in = RATIONAL_MAX;
|
||||
|
||||
foreach (TimelineViewBlockItem* item, selected) {
|
||||
Block* block = item->block();
|
||||
|
||||
earliest_in = qMin(earliest_in, block->in());
|
||||
}
|
||||
|
||||
foreach (TimelineViewBlockItem* item, selected) {
|
||||
Block* block = item->block();
|
||||
|
||||
writer->writeStartElement(QStringLiteral("block"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(block)));
|
||||
writer->writeAttribute(QStringLiteral("in"), (block->in() - earliest_in).toString());
|
||||
|
||||
TrackOutput* track = TrackOutput::TrackFromBlock(block);
|
||||
|
||||
if (track) {
|
||||
writer->writeAttribute(QStringLiteral("tracktype"), QString::number(track->track_type()));
|
||||
writer->writeAttribute(QStringLiteral("trackindex"), QString::number(track->Index()));
|
||||
}
|
||||
|
||||
writer->writeEndElement();
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // timeline
|
||||
}
|
||||
|
||||
void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, void *userdata)
|
||||
{
|
||||
if (reader->name() == QStringLiteral("timeline")) {
|
||||
QList<BlockPasteData>& paste_data = *static_cast<QList<BlockPasteData>*>(userdata);
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("block")) {
|
||||
BlockPasteData bpd;
|
||||
|
||||
foreach (QXmlStreamAttribute attr, reader->attributes()) {
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
bpd.ptr = attr.value().toULongLong();
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
bpd.in = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("tracktype")) {
|
||||
bpd.track_type = static_cast<Timeline::TrackType>(attr.value().toInt());
|
||||
} else if (attr.name() == QStringLiteral("trackindex")) {
|
||||
bpd.track_index = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
paste_data.append(bpd);
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NodeCopyPasteWidget::PasteNodesFromClipboardInternal(reader, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
TimelineWidget::DraggedFootage TimelineWidget::FootageToDraggedFootage(Footage *f)
|
||||
{
|
||||
return DraggedFootage(f, f->get_enabled_stream_flags());
|
||||
@@ -546,7 +609,7 @@ void TimelineWidget::CopySelected(bool cut)
|
||||
}
|
||||
}
|
||||
|
||||
Core::instance()->CopyNodesToClipboard(selected_nodes);
|
||||
CopyNodesToClipboard(selected_nodes, &selected);
|
||||
|
||||
if (cut) {
|
||||
DeleteSelected();
|
||||
@@ -559,18 +622,45 @@ void TimelineWidget::Paste(bool insert)
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Node*> pasted = Core::instance()->PasteNodesFromClipboard(static_cast<Sequence*>(GetConnectedNode()->parent()));
|
||||
QUndoCommand* command = new QUndoCommand();
|
||||
|
||||
QList<BlockPasteData> paste_data;
|
||||
QList<Node*> pasted = PasteNodesFromClipboard(static_cast<Sequence*>(GetConnectedNode()->parent()), command, &paste_data);
|
||||
|
||||
rational paste_start = GetTime();
|
||||
|
||||
if (insert) {
|
||||
// FIXME: Implement this
|
||||
}
|
||||
rational paste_end = GetTime();
|
||||
|
||||
foreach (Node* n, pasted) {
|
||||
// See if this block is a node and is a top level node
|
||||
if (n->IsBlock() && !n->output()->IsConnected()) {
|
||||
foreach (const BlockPasteData& bpd, paste_data) {
|
||||
foreach (Node* n, pasted) {
|
||||
if (n->property("xml_ptr") == bpd.ptr) {
|
||||
paste_end = qMax(paste_end, paste_start + bpd.in + static_cast<Block*>(n)->length());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (paste_end != paste_start) {
|
||||
InsertGapsAt(paste_start, paste_end - paste_start, command);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const BlockPasteData& bpd, paste_data) {
|
||||
foreach (Node* n, pasted) {
|
||||
if (n->property("xml_ptr") == bpd.ptr) {
|
||||
qDebug() << "Placing" << n;
|
||||
new TrackPlaceBlockCommand(GetConnectedNode()->track_list(bpd.track_type),
|
||||
bpd.track_index,
|
||||
static_cast<Block*>(n),
|
||||
paste_start + bpd.in,
|
||||
command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
QList<TimelineViewBlockItem *> TimelineWidget::GetSelectedBlocks()
|
||||
@@ -657,6 +747,55 @@ void TimelineWidget::RippleEditTo(Timeline::MovementMode mode, bool insert_gaps)
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, QUndoCommand *command)
|
||||
{
|
||||
QVector<Block*> blocks_to_split;
|
||||
QList<Block*> blocks_to_append_gap_to;
|
||||
QList<Block*> gaps_to_extend;
|
||||
|
||||
foreach (TrackOutput* track, GetConnectedNode()->Tracks()) {
|
||||
if (track->IsLocked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Block* b, track->Blocks()) {
|
||||
if (b->out() >= earliest_point) {
|
||||
if (b->type() == Block::kClip) {
|
||||
|
||||
if (b->out() > earliest_point) {
|
||||
blocks_to_split.append(b);
|
||||
}
|
||||
|
||||
blocks_to_append_gap_to.append(b);
|
||||
|
||||
} else if (b->type() == Block::kGap) {
|
||||
|
||||
gaps_to_extend.append(b);
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extend gaps that already exist
|
||||
foreach (Block* gap, gaps_to_extend) {
|
||||
new BlockResizeCommand(gap, gap->length() + insert_length, command);
|
||||
}
|
||||
|
||||
// Split clips here
|
||||
new BlockSplitPreservingLinksCommand(blocks_to_split, {earliest_point}, command);
|
||||
|
||||
// Insert gaps that don't exist yet
|
||||
foreach (Block* b, blocks_to_append_gap_to) {
|
||||
GapBlock* gap = new GapBlock();
|
||||
gap->set_length_and_media_out(insert_length);
|
||||
new NodeAddCommand(static_cast<NodeGraph*>(GetConnectedNode()->parent()), gap, command);
|
||||
new TrackInsertBlockAfterCommand(TrackOutput::TrackFromBlock(b), gap, b, command);
|
||||
}
|
||||
}
|
||||
|
||||
TrackOutput *TimelineWidget::GetTrackFromReference(const TrackReference &ref)
|
||||
{
|
||||
return GetConnectedNode()->track_list(ref.type())->TrackAt(ref.index());
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "core.h"
|
||||
#include "timelineandtrackview.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "widget/nodecopypaste/nodecopypaste.h"
|
||||
#include "widget/slider/timeslider.h"
|
||||
#include "widget/timebased/timebased.h"
|
||||
|
||||
@@ -16,7 +17,7 @@
|
||||
*
|
||||
* Encapsulates TimelineViews, TimeRulers, and scrollbars for a complete widget to manipulate Timelines
|
||||
*/
|
||||
class TimelineWidget : public TimeBasedWidget
|
||||
class TimelineWidget : public TimeBasedWidget, public NodeCopyPasteWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -78,6 +79,16 @@ protected:
|
||||
virtual void ConnectNodeInternal(ViewerOutput* n) override;
|
||||
virtual void DisconnectNodeInternal(ViewerOutput* n) override;
|
||||
|
||||
virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void* userdata) override;
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, void* userdata) override;
|
||||
|
||||
struct BlockPasteData {
|
||||
quintptr ptr;
|
||||
rational in;
|
||||
Timeline::TrackType track_type;
|
||||
int track_index;
|
||||
};
|
||||
|
||||
private:
|
||||
class DraggedFootage {
|
||||
public:
|
||||
@@ -162,8 +173,6 @@ private:
|
||||
*/
|
||||
bool SnapPoint(QList<rational> start_times, rational *movement, int snap_points = kSnapAll);
|
||||
|
||||
void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command);
|
||||
|
||||
void GetGhostData(const QVector<TimelineViewGhostItem*>& ghosts, rational *earliest_point, rational *latest_point);
|
||||
|
||||
void InsertGapsAtGhostDestination(const QVector<TimelineViewGhostItem*>& ghosts, QUndoCommand* command);
|
||||
@@ -382,6 +391,8 @@ private:
|
||||
bool dual_transition_;
|
||||
};
|
||||
|
||||
void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command);
|
||||
|
||||
void DeleteSelectedInternal(const QList<Block *>& blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command);
|
||||
|
||||
void SetBlockLinksSelected(Block *block, bool selected);
|
||||
|
||||
@@ -197,55 +197,6 @@ bool TimelineWidget::Tool::SnapPoint(QList<rational> start_times, rational* move
|
||||
return (diff < DBL_MAX);
|
||||
}
|
||||
|
||||
void TimelineWidget::Tool::InsertGapsAt(const rational &earliest_point, const rational &insert_length, QUndoCommand *command)
|
||||
{
|
||||
QVector<Block*> blocks_to_split;
|
||||
QList<Block*> blocks_to_append_gap_to;
|
||||
QList<Block*> gaps_to_extend;
|
||||
|
||||
foreach (TrackOutput* track, parent()->GetConnectedNode()->Tracks()) {
|
||||
if (track->IsLocked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Block* b, track->Blocks()) {
|
||||
if (b->out() >= earliest_point) {
|
||||
if (b->type() == Block::kClip) {
|
||||
|
||||
if (b->out() > earliest_point) {
|
||||
blocks_to_split.append(b);
|
||||
}
|
||||
|
||||
blocks_to_append_gap_to.append(b);
|
||||
|
||||
} else if (b->type() == Block::kGap) {
|
||||
|
||||
gaps_to_extend.append(b);
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extend gaps that already exist
|
||||
foreach (Block* gap, gaps_to_extend) {
|
||||
new BlockResizeCommand(gap, gap->length() + insert_length, command);
|
||||
}
|
||||
|
||||
// Split clips here
|
||||
new BlockSplitPreservingLinksCommand(blocks_to_split, {earliest_point}, command);
|
||||
|
||||
// Insert gaps that don't exist yet
|
||||
foreach (Block* b, blocks_to_append_gap_to) {
|
||||
GapBlock* gap = new GapBlock();
|
||||
gap->set_length_and_media_out(insert_length);
|
||||
new NodeAddCommand(static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent()), gap, command);
|
||||
new TrackInsertBlockAfterCommand(TrackOutput::TrackFromBlock(b), gap, b, command);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::Tool::GetGhostData(const QVector<TimelineViewGhostItem *> &ghosts, rational *earliest_point, rational *latest_point)
|
||||
{
|
||||
rational ep = RATIONAL_MAX;
|
||||
@@ -271,5 +222,5 @@ void TimelineWidget::Tool::InsertGapsAtGhostDestination(const QVector<TimelineVi
|
||||
|
||||
GetGhostData(ghosts, &earliest_point, &latest_point);
|
||||
|
||||
InsertGapsAt(earliest_point, latest_point - earliest_point, command);
|
||||
parent()->InsertGapsAt(earliest_point, latest_point - earliest_point, command);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "core.h"
|
||||
#include "node/graph.h"
|
||||
#include "node/block/transition/transition.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
|
||||
Node* TakeNodeFromParentGraph(Node* n, QObject* new_parent = nullptr)
|
||||
{
|
||||
@@ -209,7 +210,9 @@ void TrackRippleRemoveAreaCommand::redo_internal()
|
||||
foreach (Block* remove_block, removed_blocks_) {
|
||||
track_->RippleRemoveBlock(remove_block);
|
||||
|
||||
// FIXME: Delete blocks from graph and restore them in undo
|
||||
NodeRemoveWithExclusiveDeps* command = new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(remove_block->parent()), remove_block);
|
||||
command->redo();
|
||||
remove_block_commands_.append(command);
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the out point of
|
||||
@@ -261,12 +264,21 @@ void TrackRippleRemoveAreaCommand::undo_internal()
|
||||
trim_out_->set_length_and_media_out(trim_out_old_length_);
|
||||
}
|
||||
|
||||
// Remove all blocks that are flagged for removal
|
||||
foreach (Block* remove_block, removed_blocks_) {
|
||||
if (trim_in_ == nullptr) {
|
||||
track_->AppendBlock(remove_block);
|
||||
} else {
|
||||
// Restore blocks that were removed
|
||||
for (int i=remove_block_commands_.size()-1;i>=0;i--) {
|
||||
UndoCommand* command = remove_block_commands_.at(i);
|
||||
command->undo();
|
||||
delete command;
|
||||
}
|
||||
remove_block_commands_.clear();
|
||||
|
||||
for (int i=removed_blocks_.size()-1;i>=0;i--) {
|
||||
Block* remove_block = removed_blocks_.at(i);
|
||||
|
||||
if (trim_in_) {
|
||||
track_->InsertBlockBefore(remove_block, trim_in_);
|
||||
} else {
|
||||
track_->AppendBlock(remove_block);
|
||||
}
|
||||
}
|
||||
removed_blocks_.clear();
|
||||
@@ -370,7 +382,8 @@ BlockSplitCommand::BlockSplitCommand(TrackOutput* track, Block *block, rational
|
||||
|
||||
void BlockSplitCommand::redo_internal()
|
||||
{
|
||||
track_->BlockInvalidateCache();
|
||||
// FIXME: Reintroduce this optimization when block waveforms update automatically
|
||||
// track_->BlockInvalidateCache();
|
||||
|
||||
static_cast<NodeGraph*>(block_->parent())->AddNode(new_block_);
|
||||
Node::CopyInputs(block_, new_block_);
|
||||
@@ -388,12 +401,12 @@ void BlockSplitCommand::redo_internal()
|
||||
NodeParam::ConnectEdge(new_block_->output(), transition);
|
||||
}
|
||||
|
||||
track_->UnblockInvalidateCache();
|
||||
// track_->UnblockInvalidateCache();
|
||||
}
|
||||
|
||||
void BlockSplitCommand::undo_internal()
|
||||
{
|
||||
track_->BlockInvalidateCache();
|
||||
// track_->BlockInvalidateCache();
|
||||
|
||||
block_->set_length_and_media_out(old_length_);
|
||||
track_->RippleRemoveBlock(new_block_);
|
||||
@@ -405,7 +418,7 @@ void BlockSplitCommand::undo_internal()
|
||||
NodeParam::ConnectEdge(block_->output(), transition);
|
||||
}
|
||||
|
||||
track_->UnblockInvalidateCache();
|
||||
// track_->UnblockInvalidateCache();
|
||||
}
|
||||
|
||||
Block *BlockSplitCommand::new_block()
|
||||
|
||||
@@ -169,6 +169,9 @@ protected:
|
||||
Block* insert_;
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
QList<UndoCommand*> remove_block_commands_;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,9 +65,9 @@ void TimelineViewBlockItem::UpdateRect()
|
||||
|
||||
setToolTip(QCoreApplication::translate("TimelineViewBlockItem",
|
||||
"%1\n\nIn: %2\nOut: %3\nLength: %4").arg(block_->Name(),
|
||||
Timecode::time_to_timecode(block_->in(), timebase(), Core::instance()->GetTimecodeDisplay()),
|
||||
Timecode::time_to_timecode(block_->out(), timebase(), Core::instance()->GetTimecodeDisplay()),
|
||||
Timecode::time_to_timecode(block_->out() - block_->in(), timebase(), Core::instance()->GetTimecodeDisplay())));
|
||||
Timecode::time_to_timecode(block_->in(), timebase(), Core::instance()->GetTimecodeDisplay()),
|
||||
Timecode::time_to_timecode(block_->out(), timebase(), Core::instance()->GetTimecodeDisplay()),
|
||||
Timecode::time_to_timecode(block_->out() - block_->in(), timebase(), Core::instance()->GetTimecodeDisplay())));
|
||||
}
|
||||
|
||||
void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget */*widget*/)
|
||||
@@ -95,11 +95,11 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI
|
||||
|
||||
// FIXME: Hardcoded channel count
|
||||
AudioWaveformView::DrawWaveform(painter,
|
||||
rect().toRect(),
|
||||
this->GetScale(),
|
||||
reinterpret_cast<const SampleSummer::Sum*>(w.constData()),
|
||||
w.size() / sizeof(SampleSummer::Sum),
|
||||
2);
|
||||
rect().toRect(),
|
||||
this->GetScale(),
|
||||
reinterpret_cast<const SampleSummer::Sum*>(w.constData()),
|
||||
w.size() / sizeof(SampleSummer::Sum),
|
||||
2);
|
||||
|
||||
wave_file.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user