diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp index 951c45339..90742f176 100644 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -23,10 +23,12 @@ #include #include #include +#include #include -#include "widget/nodeparamview/nodeparamview.h" -#include "widget/nodeview/nodeview.h" +#include "panel/node/node.h" +#include "panel/panelmanager.h" +#include "panel/param/param.h" namespace olive { @@ -35,7 +37,7 @@ namespace olive { NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : super(parent), group_(group), - parent_undo_(nullptr) + prepend_undo_(nullptr) { QGridLayout *layout = new QGridLayout(this); @@ -44,29 +46,26 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : layout->addWidget(new QLabel(tr("Name:")), row, 0); name_edit_ = new QLineEdit(); + name_edit_->setText(group->GetLabel()); layout->addWidget(name_edit_, row, 1); row++; - QSplitter *splitter = new QSplitter(Qt::Horizontal); + QMainWindow *splitter = new QMainWindow(); + QWidget *cw = new QWidget(); + cw->setFixedSize(0, 0); + splitter->setCentralWidget(cw); layout->addWidget(splitter, row, 0, 1, 2); - NodeParamView *param_view = new NodeParamView(false); - param_view->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); - splitter->addWidget(param_view); + ParamPanel *param_view = PanelManager::instance()->CreatePanel(splitter); + param_view->GetParamView()->SetIgnoreNodeFlags(true); + param_view->GetParamView()->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); + splitter->addDockWidget(Qt::LeftDockWidgetArea, param_view); - NodeView *node_view = new NodeView(); - node_view->SetContexts({group}); - QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); - splitter->addWidget(node_view); - - splitter->setSizes({splitter->width() / 4, splitter->width() / 4 * 3}); - - for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) { - param_view->SetInputChecked(it.value(), true); - } - - param_view->SetContexts({group}); + NodePanel *node_view = PanelManager::instance()->CreatePanel(splitter); + node_view->GetNodeWidget()->view()->OverrideUndoStack(&undo_stack_); + QMetaObject::invokeMethod(node_view->GetNodeWidget()->view(), &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); + splitter->addDockWidget(Qt::RightDockWidgetArea, node_view); row++; @@ -76,24 +75,245 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : connect(btns, &QDialogButtonBox::rejected, this, &NodeGroupDialog::reject); layout->addWidget(btns, row, 0, 1, 2); - setWindowTitle(tr("Group Editor")); + setWindowTitle(tr("Group Editor - %1").arg(group->GetLabelOrName())); + + // Create a project + copied_project_ = new Project(); + copied_project_->setParent(this); + + // Copy project nodes + // NOTE: I hate this. What might be better is to fold the "ColorManager" and "ProjectSettings" + // nodes into "Project" inputs and make "Project" a node too. + for (int i=0; inodes().size(); i++) { + Node *ours = copied_project_->nodes().at(i); + Node *theirs = group_->project()->nodes().at(i); + + copy_subnodes_.insert(theirs, ours); + Node::CopyInputs(ours, theirs, false); + } + + // Copy group and nodes + copy_group_ = static_cast(group_->copy()); + copy_group_->setParent(copied_project_); + + // Copy subnodes with positions + const Node::PositionMap &map = group_->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + Node *copy = it.key()->copy(); + copy->SetUUID(it.key()->GetUUID()); + copy->setParent(copied_project_); + + copy_group_->SetNodePositionInContext(copy, it.value()); + copy_subnodes_.insert(it.key(), copy); + } + + for (auto it=map.cbegin(); it!=map.cend(); it++) { + Node::CopyInputs(it.key(), copy_subnodes_.value(it.key()), false); + } + + // Copy edges + for (auto it=copy_subnodes_.cbegin(); it!=copy_subnodes_.cend(); it++) { + Node *src = it.key(); + Node *cpy = it.value(); + + for (auto jt=src->input_connections().cbegin(); jt!=src->input_connections().cend(); jt++) { + if (Node *cpy_output = copy_subnodes_.value(jt->second)) { + Node::ConnectEdge(cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())); + copied_edges_.append({cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())}); + } + } + } + + node_view->SetContexts({copy_group_}); + + for (auto it=group_->GetInputPassthroughs().cbegin(); it!=group_->GetInputPassthroughs().cend(); it++) { + const NodeInput &src_input = it.value(); + NodeInput copy_input(copy_subnodes_.value(src_input.node()), src_input.input(), src_input.element()); + param_view->GetParamView()->SetInputChecked(copy_input, true); + } + + param_view->SetContexts({copy_group_}); } void NodeGroupDialog::accept() { + // First, validate if a connection, deleted node, or disabled passthrough is going to disconnect + // something elsewhere in the group. Ask the user to confirm if so. + + // Detect removed nodes + QVector nodes_to_delete = GetNodesToDelete(); + + // Detect new connections + QVector edges_to_connect = GetNewConnections(); + + // Warn user if operation will disconnect a node outside the group + if (OperationWillAffectOutsideGroup(nodes_to_delete, edges_to_connect)) { + if (QMessageBox::question(this, QString(), tr("This operation will disconnect nodes outside of this group. Do you wish to continue?"), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { + return; + } + } + + NodeViewDeleteCommand *delete_command = new NodeViewDeleteCommand(); + + // Remove deleted nodes + foreach (Node *n, nodes_to_delete) { + delete_command->AddNode(n, group_); + } + + // Disconnect old edges + foreach (const Node::OutputConnection &edge, copied_edges_) { + bool found = false; + const NodeInput ©_input = edge.second; + + for (auto it=edge.first->output_connections().cbegin(); it!=edge.first->output_connections().cend(); it++) { + if (it->second == copy_input) { + found = true; + break; + } + } + + if (!found) { + // Edge has been disconnected + delete_command->AddEdge(copy_subnodes_.key(edge.first), NodeInput(copy_subnodes_.key(copy_input.node()), copy_input.input(), copy_input.element())); + } + } + + delete_command->redo_now(); + + // Add new nodes + MultiUndoCommand *add_command = new MultiUndoCommand(); + for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { + Node *n = it.key(); + Node *original = copy_subnodes_.key(n); + if (!original) { + // If it's a new node that isn't even in the project yet, add it + original = n->copy(); + Node::CopyInputs(n, original, false); + add_command->add_child(new NodeAddCommand(group_->parent(), original)); + copy_subnodes_.insert(original, n); + } + + // Update position in context + add_command->add_child(new NodeSetPositionCommand(original, group_, it.value())); + } + add_command->redo_now(); + + // Connect new edges + MultiUndoCommand *connect_command = new MultiUndoCommand(); + edges_to_connect = GetNewConnections(); // Update list with new nodes made above if necessary + foreach (const Node::OutputConnection &edge, edges_to_connect) { + connect_command->add_child(new NodeEdgeAddCommand(edge.first, edge.second)); + } + connect_command->redo_now(); + MultiUndoCommand *command = new MultiUndoCommand(); + if (prepend_undo_) { + command->add_child(prepend_undo_); + } + + command->add_child(delete_command); + command->add_child(add_command); + command->add_child(connect_command); + + // Update group name if (name_edit_->text() != group_->GetCustomName()) { command->add_child(new NodeGroupSetCustomNameCommand(group_, name_edit_->text())); } - if (parent_undo_) { - parent_undo_->add_child(command); - } else { - Core::instance()->undo_stack()->push(command); - } + Core::instance()->undo_stack()->push(command); super::accept(); } +void NodeGroupDialog::reject() +{ + if (prepend_undo_) { + prepend_undo_->undo_now(); + delete prepend_undo_; + } + + super::reject(); +} + +QVector NodeGroupDialog::GetNodesToDelete() +{ + QVector nodes_to_delete; + + for (auto it=group_->GetContextPositions().cbegin(); it!=group_->GetContextPositions().cend(); it++) { + Node *original = it.key(); + Node *copy = copy_subnodes_.value(original); + + if (!copy_group_->ContextContainsNode(copy)) { + nodes_to_delete.append(original); + } + } + + return nodes_to_delete; +} + +QVector NodeGroupDialog::GetNewConnections() +{ + QVector edges_to_connect; + + for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { + const Node::InputConnections &ic = it.key()->input_connections(); + + for (auto jt=ic.cbegin(); jt!=ic.cend(); jt++) { + // All connections inside this dialog will be valid and inside the group + const NodeInput &copied_input = jt->first; + Node *copied_output = jt->second; + + NodeInput original_input(copy_subnodes_.key(copied_input.node()), copied_input.input(), copied_input.element()); + Node *original_output = copy_subnodes_.key(copied_output); + + bool found = false; + + if (original_output) { + for (auto kt=original_output->output_connections().cbegin(); kt!=original_output->output_connections().cend(); kt++) { + if (kt->second == original_input) { + found = true; + break; + } + } + } + + if (!found) { + edges_to_connect.append({original_output, original_input}); + } + } + } + + return edges_to_connect; +} + +bool NodeGroupDialog::OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections) +{ + // Check if a node to be deleted inputs from a node outside the group + foreach (Node *n, deleted) { + for (auto jt=n->input_connections().cbegin(); jt!=n->input_connections().cend(); jt++) { + if (!group_->ContextContainsNode(jt->second)) { + return true; + } + } + + for (auto jt=n->output_connections().cbegin(); jt!=n->output_connections().cend(); jt++) { + if (!group_->ContextContainsNode(jt->second.node())) { + return true; + } + } + } + + // Check if a new connection will overwrite a connection from outside the group + foreach (const Node::OutputConnection &edge, connections) { + if (Node *current_conn = edge.second.GetConnectedOutput()) { + if (!group_->ContextContainsNode(current_conn)) { + return true; + } + } + } + + return false; +} + } diff --git a/app/dialog/nodegroup/nodegroupdialog.h b/app/dialog/nodegroup/nodegroupdialog.h index 741f0ac55..d18b7fd10 100644 --- a/app/dialog/nodegroup/nodegroupdialog.h +++ b/app/dialog/nodegroup/nodegroupdialog.h @@ -23,8 +23,10 @@ #include #include +#include #include "node/group/group.h" +#include "undo/undostack.h" namespace olive { @@ -34,22 +36,37 @@ class NodeGroupDialog : public QDialog public: explicit NodeGroupDialog(NodeGroup *group, QWidget *parent = nullptr); - void SetParentUndoCommand(MultiUndoCommand *c) + void PrependUndoCommand(MultiUndoCommand *c) { - parent_undo_ = c; + prepend_undo_ = c; } public slots: virtual void accept() override; + virtual void reject() override; + signals: private: + QVector GetNodesToDelete(); + + QVector GetNewConnections(); + + bool OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections); + NodeGroup *group_; QLineEdit *name_edit_; - MultiUndoCommand *parent_undo_; + MultiUndoCommand *prepend_undo_; + + Project *copied_project_; + NodeGroup *copy_group_; + QMap copy_subnodes_; + QVector copied_edges_; + + UndoStack undo_stack_; }; diff --git a/app/node/graph.cpp b/app/node/graph.cpp index 024cebf98..f9a68c500 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -105,10 +105,6 @@ void NodeGraph::childEvent(QChildEvent *event) // Remove from any contexts foreach (Node *context, node_children_) { context->RemoveNodeFromContext(node); - - if (NodeGroup *group = dynamic_cast(context)) { - group->RemoveNode(node); - } } } } diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index f8e44790f..be92d9be8 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -55,28 +55,14 @@ QString NodeGroup::Description() const void NodeGroup::Retranslate() { - foreach (Node *n, nodes_) { - n->Retranslate(); - } -} - -void NodeGroup::AddNode(Node *node) -{ - nodes_.append(node); - - emit NodeAddedToGroup(node); -} - -void NodeGroup::RemoveNode(Node *node) -{ - if (nodes_.removeOne(node)) { - emit NodeRemovedFromGroup(node); + for (auto it=GetContextPositions().cbegin(); it!=GetContextPositions().cend(); it++) { + it.key()->Retranslate(); } } void NodeGroup::AddInputPassthrough(const NodeInput &input) { - Q_ASSERT(nodes_.contains(input.node())); + Q_ASSERT(ContextContainsNode(input.node())); for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { if (it.value() == input) { @@ -109,7 +95,7 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) void NodeGroup::SetOutputPassthrough(Node *node) { - Q_ASSERT(!node || nodes_.contains(node)); + Q_ASSERT(!node || ContextContainsNode(node)); output_passthrough_ = node; @@ -145,16 +131,6 @@ QString NodeGroup::GetInputName(const QString &id) const return input_passthroughs_.value(id).name(); } -void NodeAddToGroupCommand::redo() -{ - group_->AddNode(node_); -} - -void NodeAddToGroupCommand::undo() -{ - group_->RemoveNode(node_); -} - void NodeGroupSetCustomNameCommand::redo() { old_name_ = group_->GetCustomName(); @@ -183,16 +159,6 @@ void NodeGroupAddInputPassthrough::undo() } } -void NodeRemoveFromGroupCommand::redo() -{ - group_->RemoveNode(node_); -} - -void NodeRemoveFromGroupCommand::undo() -{ - group_->AddNode(node_); -} - void NodeGroupSetOutputPassthrough::redo() { old_output_ = group_->GetOutputPassthrough(); diff --git a/app/node/group/group.h b/app/node/group/group.h index 31e07ab07..d023a7416 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -41,20 +41,6 @@ public: virtual void Retranslate() override; - void AddNode(Node *node); - - void RemoveNode(Node *node); - - bool ContainsNode(Node *node) const - { - return nodes_.contains(node); - } - - const QVector &GetNodes() const - { - return nodes_; - } - void AddInputPassthrough(const NodeInput &input); void RemoveInputPassthrough(const NodeInput &input); @@ -96,10 +82,6 @@ public: virtual QString GetInputName(const QString& id) const override; signals: - void NodeAddedToGroup(Node *node); - - void NodeRemovedFromGroup(Node *node); - void InputPassthroughAdded(NodeGroup *group, const NodeInput &input); void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input); @@ -107,8 +89,6 @@ signals: void OutputPassthroughChanged(NodeGroup *group, Node *output); private: - QVector nodes_; - QHash input_passthroughs_; Node *output_passthrough_; @@ -117,56 +97,6 @@ private: }; -class NodeAddToGroupCommand : public UndoCommand -{ -public: - NodeAddToGroupCommand(Node *node, NodeGroup *group) : - node_(node), - group_(group) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node *node_; - - NodeGroup *group_; - -}; - -class NodeRemoveFromGroupCommand : public UndoCommand -{ -public: - NodeRemoveFromGroupCommand(Node *node, NodeGroup *group) : - node_(node), - group_(group) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node *node_; - - NodeGroup *group_; - -}; - class NodeGroupSetCustomNameCommand : public UndoCommand { public: diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 3f0e3a4a2..6e4b842e6 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -20,40 +20,20 @@ #include "node.h" -#include - 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(this, &NodePanel::visibilityChanged, node_view_, &NodeView::CenterOnItemsBoundingRect); - - // 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); // Set it as the main widget of this panel - SetWidgetWithPadding(outer_widget); + SetWidgetWithPadding(node_widget_); // Set strings Retranslate(); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index 20eccc79a..083445029 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -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,76 +35,80 @@ class NodePanel : public PanelWidget public: NodePanel(QWidget* parent); + NodeWidget *GetNodeWidget() const + { + return node_widget_; + } + void SetContexts(const QVector &nodes) { - node_view_->SetContexts(nodes); - toolbar_->setEnabled(!nodes.isEmpty()); + node_widget_->SetContexts(nodes); } void CloseContextsBelongingToProject(Project *project) { - node_view_->CloseContextsBelongingToProject(project); + node_widget_->view()->CloseContextsBelongingToProject(project); } const QVector &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& nodes, bool center_view_on_item) { - node_view_->Select(nodes, center_view_on_item); + node_widget_->view()->Select(nodes, center_view_on_item); } signals: @@ -119,9 +122,7 @@ private: SetTitle(tr("Node Editor")); } - NodeView* node_view_; - - NodeViewToolBar *toolbar_; + NodeWidget *node_widget_; }; diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index bf44d5db3..90c2c1983 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -37,12 +37,12 @@ ParamPanel::ParamPanel(QWidget* parent) : void ParamPanel::SelectNodes(const QVector &nodes) { - //static_cast(GetTimeBasedWidget())->SelectNodes(nodes); + static_cast(GetTimeBasedWidget())->SelectNodes(nodes); } void ParamPanel::DeselectNodes(const QVector &nodes) { - //static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); + static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); } void ParamPanel::DeleteSelected() diff --git a/app/panel/param/param.h b/app/panel/param/param.h index e298ccf3e..4389bc15f 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -33,6 +33,11 @@ class ParamPanel : public TimeBasedPanel public: ParamPanel(QWidget* parent); + NodeParamView *GetParamView() const + { + return static_cast(GetTimeBasedWidget()); + } + public slots: void SelectNodes(const QVector& nodes); void DeselectNodes(const QVector& nodes); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 75b78c3c5..887c8b578 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -367,6 +367,11 @@ void PreviewAutoCacher::ProcessUpdateQueue() void PreviewAutoCacher::AddNode(Node *node) { + if (dynamic_cast(node)) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + // Copy node Node* copy = node->copy(); diff --git a/app/undo/undocommand.cpp b/app/undo/undocommand.cpp index 075348c15..f182f86f4 100644 --- a/app/undo/undocommand.cpp +++ b/app/undo/undocommand.cpp @@ -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; + } } } diff --git a/app/undo/undocommand.h b/app/undo/undocommand.h index 94d4d3e8f..bd6032da2 100644 --- a/app/undo/undocommand.h +++ b/app/undo/undocommand.h @@ -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 children_; - bool done_; - }; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b04f41011..86732d37c 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -38,7 +38,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : last_scroll_val_(0), focused_node_(nullptr), create_checkboxes_(kNoCheckBoxes), - time_target_(nullptr) + time_target_(nullptr), + ignore_flags_(false) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); @@ -268,7 +269,7 @@ void NodeParamView::SetContexts(const QVector &contexts) item->setVisible(true); for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) { - if (!(it.key()->GetFlags() & Node::kDontShowInParamView)) { + if (!(it.key()->GetFlags() & Node::kDontShowInParamView) || ignore_flags_) { AddNode(it.key(), item); } } @@ -351,6 +352,16 @@ void NodeParamView::DeleteSelected() } } +void NodeParamView::SelectNodes(const QVector &nodes) +{ + // Do nothing, this is a placeholder if we ever need this to do anything in the future +} + +void NodeParamView::DeselectNodes(const QVector &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 (NodeParamViewContext* item, context_items_) { @@ -543,6 +554,9 @@ void NodeParamView::UpdateElementY() 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()) { diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index d9f7985de..29110deca 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -69,6 +69,14 @@ public: keyframe_view_->DeselectAll(); } + void SetIgnoreNodeFlags(bool e) + { + ignore_flags_ = e; + } + + void SelectNodes(const QVector &nodes); + void DeselectNodes(const QVector &nodes); + public slots: void SetInputChecked(const NodeInput &input, bool e); @@ -123,6 +131,8 @@ private: QHash input_checked_; + bool ignore_flags_; + private slots: void UpdateGlobalScrollBar(); diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index 12512695f..cf3a2375e 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -35,5 +35,7 @@ set(OLIVE_SOURCES widget/nodeview/nodeviewtoolbar.h widget/nodeview/nodeviewundo.cpp widget/nodeview/nodeviewundo.h + widget/nodeview/nodewidget.cpp + widget/nodeview/nodewidget.h PARENT_SCOPE ) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index c6a3c9746..d0e85fc37 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -26,7 +26,6 @@ #include #include -#include "core.h" #include "dialog/nodegroup/nodegroupdialog.h" #include "nodeviewundo.h" #include "node/audio/volume/volume.h" @@ -50,6 +49,7 @@ NodeView::NodeView(QWidget *parent) : create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), paste_command_(nullptr), + undo_stack_(Core::instance()->undo_stack()), scale_(1.0) { setScene(&scene_); @@ -127,7 +127,13 @@ void NodeView::ClearGraph() void NodeView::DeleteSelected() { - scene_.DeleteSelected(); + NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); + + foreach (NodeViewContext *ctx, scene_.context_map()) { + ctx->DeleteSelected(command); + } + + undo_stack_->push(command); } void NodeView::SelectAll() @@ -221,7 +227,7 @@ void NodeView::SetColorLabel(int index) command->add_child(new NodeOverrideColorCommand(node, index)); } - Core::instance()->undo_stack()->push(command); + undo_stack_->push(command); } void NodeView::ZoomIn() @@ -276,7 +282,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) } } } - Core::instance()->undo_stack()->pushIfHasChildren(pos_command); + undo_stack_->pushIfHasChildren(pos_command); break; } case Qt::Key_Escape: @@ -555,7 +561,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } create_edge_expanded_items_.clear(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + undo_stack_->pushIfHasChildren(command); } MultiUndoCommand* command = new MultiUndoCommand(); @@ -639,7 +645,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } dragging_items_.clear(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + undo_stack_->pushIfHasChildren(command); super::mouseReleaseEvent(event); } @@ -1199,7 +1205,6 @@ void NodeView::GroupNodes() DeselectAll(); foreach (Node *n, nodes_to_group) { command->add_child(new NodeRemovePositionFromContextCommand(n, context)); - command->add_child(new NodeAddToGroupCommand(n, group)); command->add_child(new NodeSetPositionCommand(n, group, context->GetNodePositionDataInContext(n))); for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) { @@ -1232,14 +1237,8 @@ void NodeView::GroupNodes() command->redo_now(); NodeGroupDialog ngd(group, this); - if (ngd.exec() == QDialog::Accepted) { - // Push to stack so it can be undone (MultiUndoCommand will ignore the request to redo again) - Core::instance()->undo_stack()->push(command); - } else { - // Undo command and delete - command->undo_now(); - delete command; - } + ngd.PrependUndoCommand(command); + ngd.exec(); } void NodeView::UngroupNodes() @@ -1269,13 +1268,12 @@ void NodeView::UngroupNodes() command->add_child(new NodeRemovePositionFromContextCommand(group, context)); command->add_child(new NodeRemoveAndDisconnectCommand(group)); - foreach (Node *n, group->GetNodes()) { - command->add_child(new NodeRemovePositionFromContextCommand(n, group)); - command->add_child(new NodeRemoveFromGroupCommand(n, group)); - command->add_child(new NodeSetPositionCommand(n, context, group->GetNodePositionDataInContext(n))); + for (auto it=group->GetContextPositions().cbegin(); it!=group->GetContextPositions().cend(); it++) { + command->add_child(new NodeRemovePositionFromContextCommand(it.key(), group)); + command->add_child(new NodeSetPositionCommand(it.key(), context, group->GetNodePositionDataInContext(it.key()))); } - Core::instance()->undo_stack()->push(command); + undo_stack_->push(command); } void NodeView::ShowNodeProperties() diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 8e34bba99..02118ba45 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -24,6 +24,7 @@ #include #include +#include "core.h" #include "node/graph.h" #include "node/nodecopypaste.h" #include "nodeviewedge.h" @@ -76,6 +77,11 @@ public: void ZoomOut(); + void OverrideUndoStack(UndoStack *stack) + { + undo_stack_ = stack; + } + const QVector &GetCurrentContexts() const { return contexts_; @@ -189,6 +195,8 @@ private: QMap dragging_items_; + UndoStack *undo_stack_; + double scale_; static const double kMinimumScale; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 59c640634..8ded8fdc3 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -56,13 +56,13 @@ void NodeViewContext::AddChild(Node *node) AddNodeInternal(node, item); if (NodeGroup *group = dynamic_cast(node)) { - foreach (Node *n, group->GetNodes()) { + for (auto it=group->GetContextPositions().cbegin(); it!=group->GetContextPositions().cend(); it++) { // Use this item as the representative for all of these nodes too - AddNodeInternal(n, item); + AddNodeInternal(it.key(), item); } - connect(group, &NodeGroup::NodeAddedToGroup, this, &NodeViewContext::GroupAddedNode); - connect(group, &NodeGroup::NodeRemovedFromGroup, this, &NodeViewContext::GroupRemovedNode); + connect(group, &NodeGroup::NodeAddedToContext, this, &NodeViewContext::GroupAddedNode); + connect(group, &NodeGroup::NodeRemovedFromContext, this, &NodeViewContext::GroupRemovedNode); } UpdateRect(); @@ -78,6 +78,11 @@ void NodeViewContext::RemoveChild(Node *node) disconnect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); disconnect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + if (NodeGroup *group = dynamic_cast(node)) { + disconnect(group, &NodeGroup::NodeAddedToContext, this, &NodeViewContext::GroupAddedNode); + disconnect(group, &NodeGroup::NodeRemovedFromContext, this, &NodeViewContext::GroupRemovedNode); + } + NodeViewItem *item = item_map_.take(node); // Delete edges first because the edge destructor will try to reference item (maybe that should diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 37a226710..dc76509cc 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -58,17 +58,6 @@ void NodeViewScene::DeselectAll() } } -void NodeViewScene::DeleteSelected() -{ - NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); - - foreach (NodeViewContext *ctx, context_map_) { - ctx->DeleteSelected(command); - } - - Core::instance()->undo_stack()->push(command); -} - QVector NodeViewScene::GetSelectedItems() const { QVector items; diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 467109923..64a149bd3 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -28,6 +28,7 @@ #include "nodeviewcontext.h" #include "nodeviewedge.h" #include "nodeviewitem.h" +#include "undo/undostack.h" namespace olive { @@ -40,8 +41,6 @@ public: void SelectAll(); void DeselectAll(); - void DeleteSelected(); - QVector GetSelectedItems() const; const QHash &context_map() const diff --git a/app/widget/nodeview/nodewidget.cpp b/app/widget/nodeview/nodewidget.cpp new file mode 100644 index 000000000..4fdb2dd06 --- /dev/null +++ b/app/widget/nodeview/nodewidget.cpp @@ -0,0 +1,51 @@ +/*** + + 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 . + +***/ + +#include "nodewidget.h" + +#include + +namespace olive { + +NodeWidget::NodeWidget(QWidget *parent) : + QWidget(parent) +{ + QVBoxLayout *outer_layout = new QVBoxLayout(this); + 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); + + setSizePolicy(node_view_->sizePolicy()); +} + +} diff --git a/app/widget/nodeview/nodewidget.h b/app/widget/nodeview/nodewidget.h new file mode 100644 index 000000000..47762f499 --- /dev/null +++ b/app/widget/nodeview/nodewidget.h @@ -0,0 +1,57 @@ +/*** + + 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 . + +***/ + +#ifndef NODEWIDGET_H +#define NODEWIDGET_H + +#include + +#include "nodeview.h" +#include "nodeviewtoolbar.h" + +namespace olive { + +class NodeWidget : public QWidget +{ + Q_OBJECT +public: + NodeWidget(QWidget *parent = nullptr); + + NodeView *view() const + { + return node_view_; + } + + void SetContexts(const QVector &nodes) + { + node_view_->SetContexts(nodes); + toolbar_->setEnabled(!nodes.isEmpty()); + } + +private: + NodeView *node_view_; + + NodeViewToolBar *toolbar_; + +}; + +} + +#endif // NODEWIDGET_H diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index bceb6a935..61eb7ef92 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -220,11 +220,6 @@ void MainWindow::FolderOpen(Project* p, Folder *i, bool floating) { ProjectPanel* panel = PanelManager::instance()->CreatePanel(this); - // Set custom name to distinguish it from regular ProjectPanels - panel->setObjectName(QStringLiteral("FolderPanel")); - - SetUniquePanelID(panel, folder_panels_); - panel->set_project(p); panel->set_root(i); @@ -823,8 +818,6 @@ T *MainWindow::AppendPanelInternal(QList& list) { T* panel = PanelManager::instance()->CreatePanel(this); - SetUniquePanelID(panel, list); - if (!list.isEmpty()) { tabifyDockWidget(list.last(), panel); } @@ -841,20 +834,11 @@ T *MainWindow::AppendPanelInternal(QList& list) return panel; } -template -void MainWindow::SetUniquePanelID(T *panel, const QList &list) -{ - // Set unique object name so it can be identified by QMainWindow's save and restore state functions - panel->setObjectName(panel->objectName().append(QString::number(list.size()))); -} - template T *MainWindow::AppendFloatingPanelInternal(QList &list) { T* panel = PanelManager::instance()->CreatePanel(this); - SetUniquePanelID(panel, list); - panel->setFloating(true); panel->show();