diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 8111d8ad9..dfd2fef61 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -62,7 +62,7 @@ void NodeFactory::Destroy() library_.clear(); } -Menu *NodeFactory::CreateMenu(QWidget* parent) +Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item) { Menu* menu = new Menu(parent); menu->setToolTipsVisible(true); @@ -100,18 +100,57 @@ Menu *NodeFactory::CreateMenu(QWidget* parent) a->setToolTip(n->Description()); } + if (create_none_item) { + + QAction* none_item = new QAction(QCoreApplication::translate("NodeFactory", "None"), + menu); + + none_item->setData(-1); + + if (menu->actions().isEmpty()) { + menu->addAction(none_item); + } else { + QAction* separator = menu->insertSeparator(menu->actions().first()); + menu->insertAction(separator, none_item); + } + } + return menu; } Node* NodeFactory::CreateFromMenuAction(QAction *action) { - int library_index = action->data().toInt(); + int index = action->data().toInt(); - if (library_index >= 0 && library_index < library_.size()) { - return library_.at(library_index)->copy(); + if (index == -1) { + return nullptr; } - return nullptr; + return library_.at(index)->copy(); +} + +QString NodeFactory::GetIDFromMenuAction(QAction *action) +{ + int index = action->data().toInt(); + + if (index == -1) { + return QString(); + } + + return library_.at(action->data().toInt())->id(); +} + +QString NodeFactory::GetNameFromID(const QString &id) +{ + if (!id.isEmpty()) { + foreach (Node* n, library_) { + if (n->id() == id) { + return n->Name(); + } + } + } + + return QString(); } Node *NodeFactory::CreateFromID(const QString &id) diff --git a/app/node/factory.h b/app/node/factory.h index 6b17bd792..c82b2b63e 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -60,10 +60,14 @@ public: static void Destroy(); - static Menu* CreateMenu(QWidget *parent); + static Menu* CreateMenu(QWidget *parent, bool create_none_item = false); static Node* CreateFromMenuAction(QAction* action); + static QString GetIDFromMenuAction(QAction* action); + + static QString GetNameFromID(const QString& id); + static Node* CreateFromID(const QString& id); private: diff --git a/app/node/node.cpp b/app/node/node.cpp index 7f27ffb71..7f525b2a3 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -371,7 +371,7 @@ void Node::Hash(QCryptographicHash &hash, const rational& time) const // Footage timestamp if (stream->type() == Stream::kVideo) { hash.addData(QStringLiteral("%1/%2").arg(QString::number(input_time.numerator()), - QString::number(input_time.denominator())).toUtf8()); + QString::number(input_time.denominator())).toUtf8()); hash.addData(QString::number(static_cast(stream.get())->start_time()).toUtf8()); @@ -532,16 +532,75 @@ NodeOutput *Node::GetOutputWithID(const QString &id) const return nullptr; } -bool Node::OutputsTo(Node *n) const +bool Node::OutputsTo(Node *n, bool recursively) const { - foreach (NodeParam* param, params_) { - if (param->type() == NodeParam::kOutput) { - QVector edges = param->edges(); + QList outputs = GetOutputs(); - foreach (NodeEdgePtr edge, edges) { - if (edge->input()->parent() == n) { - return true; - } + foreach (NodeOutput* output, outputs) { + foreach (NodeEdgePtr edge, output->edges()) { + Node* connected = edge->input()->parentNode(); + + if (connected == n) { + return true; + } else if (recursively && connected->OutputsTo(n, recursively)) { + return true; + } + } + } + + return false; +} + +bool Node::OutputsTo(const QString &id, bool recursively) const +{ + QList outputs = GetOutputs(); + + foreach (NodeOutput* output, outputs) { + foreach (NodeEdgePtr edge, output->edges()) { + Node* connected = edge->input()->parentNode(); + + if (connected->id() == id) { + return true; + } else if (recursively && connected->OutputsTo(id, recursively)) { + return true; + } + } + } + + return false; +} + +bool Node::InputsFrom(Node *n, bool recursively) const +{ + QList inputs = GetInputsIncludingArrays(); + + foreach (NodeInput* input, inputs) { + foreach (NodeEdgePtr edge, input->edges()) { + Node* connected = edge->output()->parentNode(); + + if (connected == n) { + return true; + } else if (recursively && connected->InputsFrom(n, recursively)) { + return true; + } + } + } + + return false; +} + +bool Node::InputsFrom(const QString &id, bool recursively) const +{ + QList inputs = GetInputsIncludingArrays(); + + foreach (NodeInput* input, inputs) { + foreach (NodeEdgePtr edge, input->edges()) { + Node* connected = edge->output()->parentNode(); + + if (connected->id() == id) { + return true; + } else if (recursively && connected->InputsFrom(id, recursively)) { + return true; } } } diff --git a/app/node/node.h b/app/node/node.h index 58f5533f1..5427f1643 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -229,9 +229,33 @@ public: NodeOutput* GetOutputWithID(const QString& id) const; /** - * @brief Returns whether this Node outputs directly to `n` + * @brief Returns whether this Node outputs to `n` + * + * @param n + * + * The node instance to check. + * + * @param recursively + * + * Whether to keep traversing down outputs to find this node (TRUE) or stick to immediate outputs + * (FALSE). */ - bool OutputsTo(Node* n) const; + bool OutputsTo(Node* n, bool recursively) const; + + /** + * @brief Same as OutputsTo(Node*), but for a node ID rather than a specific instance. + */ + bool OutputsTo(const QString& id, bool recursively) const; + + /** + * @brief Returns whether this node ever receives an input from a particular node instance + */ + bool InputsFrom(Node* n, bool recursively) const; + + /** + * @brief Returns whether this node ever receives an input from a node with a particular ID + */ + bool InputsFrom(const QString& id, bool recursively) const; /** * @brief Determines how many paths go from this node out to another node diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index ddc6dfb72..dc068e82b 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -27,6 +27,7 @@ add_subdirectory(footagecombobox) add_subdirectory(keyframeview) add_subdirectory(manageddisplay) add_subdirectory(menu) +add_subdirectory(nodecombobox) add_subdirectory(nodecopypaste) add_subdirectory(nodeview) add_subdirectory(nodeparamview) diff --git a/app/widget/nodecombobox/CMakeLists.txt b/app/widget/nodecombobox/CMakeLists.txt new file mode 100644 index 000000000..56a597688 --- /dev/null +++ b/app/widget/nodecombobox/CMakeLists.txt @@ -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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/nodecombobox/nodecombobox.h + widget/nodecombobox/nodecombobox.cpp + PARENT_SCOPE +) diff --git a/app/widget/nodecombobox/nodecombobox.cpp b/app/widget/nodecombobox/nodecombobox.cpp new file mode 100644 index 000000000..89e1dfeda --- /dev/null +++ b/app/widget/nodecombobox/nodecombobox.cpp @@ -0,0 +1,93 @@ +/*** + + 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 . + +***/ + +#include "nodecombobox.h" + +#include +#include + +#include "node/factory.h" +#include "ui/icons/icons.h" +#include "widget/menu/menu.h" + +OLIVE_NAMESPACE_ENTER + +NodeComboBox::NodeComboBox(QWidget *parent) : + QComboBox(parent) +{ +} + +void NodeComboBox::showPopup() +{ + Menu* m = NodeFactory::CreateMenu(this, true); + + QAction* selected = m->exec(parentWidget()->mapToGlobal(pos())); + + if (selected) { + QString new_id = NodeFactory::GetIDFromMenuAction(selected); + + SetNodeInternal(new_id, true); + } + + delete m; +} + +const QString &NodeComboBox::GetSelectedNode() const +{ + return selected_id_; +} + +void NodeComboBox::SetNode(const QString &id) +{ + SetNodeInternal(id, false); +} + +void NodeComboBox::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + UpdateText(); + } + + QComboBox::changeEvent(e); +} + +void NodeComboBox::UpdateText() +{ + clear(); + + if (!selected_id_.isEmpty()) { + addItem(NodeFactory::GetNameFromID(selected_id_)); + } +} + +void NodeComboBox::SetNodeInternal(const QString &id, bool emit_signal) +{ + if (selected_id_ != id) { + selected_id_ = id; + + UpdateText(); + + if (emit_signal) { + emit NodeChanged(selected_id_); + } + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodecombobox/nodecombobox.h b/app/widget/nodecombobox/nodecombobox.h new file mode 100644 index 000000000..cadad971c --- /dev/null +++ b/app/widget/nodecombobox/nodecombobox.h @@ -0,0 +1,60 @@ +/*** + + 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 . + +***/ + +#ifndef NODECOMBOBOX_H +#define NODECOMBOBOX_H + +#include + +#include "node/node.h" + +OLIVE_NAMESPACE_ENTER + +class NodeComboBox : public QComboBox +{ + Q_OBJECT +public: + NodeComboBox(QWidget* parent = nullptr); + + virtual void showPopup() override; + + const QString& GetSelectedNode() const; + +public slots: + void SetNode(const QString& id); + +protected: + virtual void changeEvent(QEvent *e) override; + +signals: + void NodeChanged(const QString& id); + +private: + void UpdateText(); + + void SetNodeInternal(const QString& id, bool emit_signal); + + QString selected_id_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // FOOTAGECOMBOBOX_H diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 763b10281..f24a402a1 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -467,7 +467,7 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (cached_drop_item_->GetInputRect(i).contains(cached_drop_item_->mapFromScene(event->scenePos()))) { // See if we're dragging inside the hitbox // Attempt to prevent circular dependency - ensure the receiving node doesn't output to the outputting node - if (!cached_drop_item_->GetNode()->OutputsTo(node_)) { + if (!cached_drop_item_->GetNode()->OutputsTo(node_, true)) { drag_dest_param_ = comp_param; highlight_their_index = i;