NodePtr AnimationModulePrivate::getNearestTimeNodeFromOutputs_recursive(const NodePtr& node, const NodeCollectionPtr& collection, std::list<NodePtr>& markedNodes) const { const NodesWList &outputs = node->getOutputs(); if ( std::find(markedNodes.begin(), markedNodes.end(), node) != markedNodes.end() ) { return NodePtr(); } // If we go outside of the initial node group scope, return if (node->getGroup() != collection) { return NodePtr(); } markedNodes.push_back(node); for (NodesWList::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { NodePtr output = it->lock(); std::string pluginID = output->getPluginID(); if ( (pluginID == PLUGINID_OFX_RETIME) || ( pluginID == PLUGINID_OFX_TIMEOFFSET) || ( pluginID == PLUGINID_OFX_FRAMERANGE) ) { return output; } else { NodePtr ret = getNearestTimeNodeFromOutputs_recursive(output, collection, markedNodes); if (ret) { return ret; } } } return NodePtr(); }
void NodeGraph::moveNodesForIdealPosition(const NodeGuiPtr &node, const NodeGuiPtr &selected, bool autoConnect) { BackdropGuiPtr isBd = toBackdropGui(node); if (isBd) { return; } QRectF viewPos = visibleSceneRect(); ///3 possible values: /// 0 = default , i.e: we pop the node in the middle of the graph's current view /// 1 = pop the node above the selected node and move the inputs of the selected node a little /// 2 = pop the node below the selected node and move the outputs of the selected node a little int behavior = 0; if (!selected || !autoConnect) { behavior = 0; } else { ///this function is redundant with Project::autoConnect, depending on the node selected ///and this node we make some assumptions on to where we could put the node. // 1) selected is output // a) created is output --> fail // b) created is input --> connect input // c) created is regular --> connect input // 2) selected is input // a) created is output --> connect output // b) created is input --> fail // c) created is regular --> connect output // 3) selected is regular // a) created is output--> connect output // b) created is input --> connect input // c) created is regular --> connect output ///1) if ( selected->getNode()->isOutputNode() ) { ///case 1-a) just do default we don't know what else to do if ( node->getNode()->isOutputNode() ) { behavior = 0; } else { ///for either cases 1-b) or 1-c) we just connect the created node as input of the selected node. behavior = 1; } } ///2) and 3) are similar except for case b) else { ///case 2 or 3- a): connect the created node as output of the selected node. if ( node->getNode()->isOutputNode() ) { behavior = 2; } ///case b) else if ( node->getNode()->isInputNode() ) { if ( selected->getNode()->getEffectInstance()->isReader() ) { ///case 2-b) just do default we don't know what else to do behavior = 0; } else { ///case 3-b): connect the created node as input of the selected node behavior = 1; } } ///case c) connect created as output of the selected node else { behavior = 2; } } } NodePtr createdNodeInternal = node->getNode(); NodePtr selectedNodeInternal; if (selected) { selectedNodeInternal = selected->getNode(); } ///if behaviour is 1 , just check that we can effectively connect the node to avoid moving them for nothing ///otherwise fallback on behaviour 0 if (behavior == 1) { const std::vector<NodeWPtr > & inputs = selected->getNode()->getGuiInputs(); bool oneInputEmpty = false; for (std::size_t i = 0; i < inputs.size(); ++i) { if ( !inputs[i].lock() ) { oneInputEmpty = true; break; } } if (!oneInputEmpty) { behavior = 0; } } ProjectPtr proj = getGui()->getApp()->getProject(); ///default QPointF position; if (behavior == 0) { position.setX( ( viewPos.bottomRight().x() + viewPos.topLeft().x() ) / 2. ); position.setY( ( viewPos.topLeft().y() + viewPos.bottomRight().y() ) / 2. ); } else if (behavior == 1) { ///pop it above the selected node ///If this is the first connected input, insert it in a "linear" way so the tree remains vertical int nbConnectedInput = 0; const std::vector<Edge*> & selectedNodeInputs = selected->getInputsArrows(); for (std::vector<Edge*>::const_iterator it = selectedNodeInputs.begin(); it != selectedNodeInputs.end(); ++it) { NodeGuiPtr input; if (*it) { input = (*it)->getSource(); } if (input) { ++nbConnectedInput; } } ///connect it to the first input QSize selectedNodeSize = selected->getSize(); QSize createdNodeSize = node->getSize(); if (nbConnectedInput == 0) { QPointF selectedNodeMiddlePos = selected->scenePos() + QPointF(selectedNodeSize.width() / 2, selectedNodeSize.height() / 2); position.setX(selectedNodeMiddlePos.x() - createdNodeSize.width() / 2); position.setY( selectedNodeMiddlePos.y() - selectedNodeSize.height() / 2 - NodeGui::DEFAULT_OFFSET_BETWEEN_NODES - createdNodeSize.height() ); QRectF createdNodeRect( position.x(), position.y(), createdNodeSize.width(), createdNodeSize.height() ); ///now that we have the position of the node, move the inputs of the selected node to make some space for this node for (std::vector<Edge*>::const_iterator it = selectedNodeInputs.begin(); it != selectedNodeInputs.end(); ++it) { if ( (*it)->hasSource() ) { (*it)->getSource()->moveAbovePositionRecursively(createdNodeRect); } } int selectedInput = selectedNodeInternal->getPreferredInputForConnection(); if (selectedInput != -1) { bool ok = proj->connectNodes(selectedInput, createdNodeInternal, selectedNodeInternal, true); Q_UNUSED(ok); } } else { //ViewerInstancePtr isSelectedViewer = selectedNodeInternal->isEffectViewerInstance(); //Don't pop a dot, it will most likely annoy the user, just fallback on behavior 0 /* position.setX( ( viewPos.bottomRight().x() + viewPos.topLeft().x() ) / 2. ); position.setY( ( viewPos.topLeft().y() + viewPos.bottomRight().y() ) / 2. ); } else { */ QRectF selectedBbox = selected->mapToScene( selected->boundingRect() ).boundingRect(); QPointF selectedCenter = selectedBbox.center(); double y = selectedCenter.y() - selectedNodeSize.height() / 2. - NodeGui::DEFAULT_OFFSET_BETWEEN_NODES - createdNodeSize.height(); double x = selectedCenter.x() + nbConnectedInput * 150; position.setX(x - createdNodeSize.width() / 2.); position.setY(y); int index = selectedNodeInternal->getPreferredInputForConnection(); if (index != -1) { bool ok = proj->connectNodes(index, createdNodeInternal, selectedNodeInternal, true); Q_UNUSED(ok); } //} // if (isSelectedViewer) {*/ } // if (nbConnectedInput == 0) { } else { ///pop it below the selected node const NodesWList& outputs = selectedNodeInternal->getGuiOutputs(); if ( !createdNodeInternal->isOutputNode() || outputs.empty() ) { QSize selectedNodeSize = selected->getSize(); QSize createdNodeSize = node->getSize(); QPointF selectedNodeMiddlePos = selected->scenePos() + QPointF(selectedNodeSize.width() / 2, selectedNodeSize.height() / 2); ///actually move the created node where the selected node is position.setX(selectedNodeMiddlePos.x() - createdNodeSize.width() / 2); position.setY(selectedNodeMiddlePos.y() + (selectedNodeSize.height() / 2) + NodeGui::DEFAULT_OFFSET_BETWEEN_NODES); QRectF createdNodeRect( position.x(), position.y(), createdNodeSize.width(), createdNodeSize.height() ); ///and move the selected node below recusively for (NodesWList::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { NodePtr output = it->lock(); if (!output) { continue; } NodeGuiIPtr output_i = output->getNodeGui(); if (!output_i) { continue; } NodeGuiPtr outputGui = toNodeGui( output_i ); assert(outputGui); if (outputGui) { outputGui->moveBelowPositionRecursively(createdNodeRect); } } ///Connect the created node to the selected node ///finally we connect the created node to the selected node int createdInput = createdNodeInternal->getPreferredInputForConnection(); if (createdInput != -1) { ignore_result( createdNodeInternal->connectInput(selectedNodeInternal, createdInput) ); } if ( !createdNodeInternal->isOutputNode() ) { ///we find all the nodes that were previously connected to the selected node, ///and connect them to the created node instead. std::map<NodePtr, int> outputsConnectedToSelectedNode; selectedNodeInternal->getOutputsConnectedToThisNode(&outputsConnectedToSelectedNode); for (std::map<NodePtr, int>::iterator it = outputsConnectedToSelectedNode.begin(); it != outputsConnectedToSelectedNode.end(); ++it) { if ( it->first->getParentMultiInstanceName().empty() && (it->first != createdNodeInternal) ) { /* Internal rotopaint nodes are connecting to the Rotopaint itself... make sure not to connect internal nodes of the tree */ RotoDrawableItemPtr stroke = it->first->getAttachedRotoItem(); if ( stroke && (stroke->getContext()->getNode() == selectedNodeInternal) ) { continue; } ignore_result( it->first->replaceInput(createdNodeInternal, it->second) ); // bool ok = proj->disconnectNodes(selectedNodeInternal.get(), it->first); // if (ok) { // ignore_result(proj->connectNodes(it->second, createdNodeInternal, it->first)); // } //assert(ok); Might not be ok if the disconnectNodes() action above was queued } } } } else { ///the created node is an output node and the selected node already has several outputs, create it aside QSize createdNodeSize = node->getSize(); QRectF selectedBbox = selected->mapToScene( selected->boundingRect() ).boundingRect(); QPointF selectedCenter = selectedBbox.center(); double y = selectedCenter.y() + selectedBbox.height() / 2. + NodeGui::DEFAULT_OFFSET_BETWEEN_NODES; double x = selectedCenter.x() + (int)outputs.size() * 150; position.setX(x - createdNodeSize.width() / 2.); position.setY(y); //Don't pop a dot, it will most likely annoy the user, just fallback on behavior 0 int index = createdNodeInternal->getPreferredInputForConnection(); bool ok = proj->connectNodes(index, selectedNodeInternal, createdNodeInternal, true); Q_UNUSED(ok); } } position = node->mapFromScene(position); position = node->mapToParent(position); node->setPosition( position.x(), position.y() ); } // moveNodesForIdealPosition
void DSMoveKeysAndNodesCommand::moveSelection(double dt) { /* The view is going to get MANY update() calls since every knob change will trigger a computeNodeRange() in DopeSheetView We explicitly disable update on the dopesheet view and re-enable it afterwards. */ DopeSheetView* view = _model->getDopesheetView(); view->setUpdatesEnabled(false); for (NodesWList::iterator khIt = _allDifferentNodes.begin(); khIt != _allDifferentNodes.end(); ++khIt) { NodePtr node = khIt->lock(); if (!node) { continue; } node->getEffectInstance()->beginChanges(); } //////////Handle selected keyframes for (DSKeyPtrList::iterator it = _keys.begin(); it != _keys.end(); ++it) { const DSKeyPtr& selectedKey = (*it); DSKnobPtr knobContext = selectedKey->context.lock(); if (!knobContext) { continue; } KnobIPtr knob = knobContext->getKnobGui()->getKnob(); knob->moveValueAtTime(eCurveChangeReasonDopeSheet, selectedKey->key.getTime(), ViewIdx(0), knobContext->getDimension(), dt, 0, &selectedKey->key); } ////////////Handle selected nodes for (std::vector<DSNodePtr >::iterator it = _nodes.begin(); it != _nodes.end(); ++it) { DopeSheetItemType type = (*it)->getItemType(); if (type == eDopeSheetItemTypeReader) { moveReader( (*it)->getInternalNode(), dt ); } else if (type == eDopeSheetItemTypeFrameRange) { moveFrameRange( (*it)->getInternalNode(), dt ); } else if (type == eDopeSheetItemTypeTimeOffset) { moveTimeOffset( (*it)->getInternalNode(), dt ); } else if (type == eDopeSheetItemTypeGroup) { moveGroupNode(_model, (*it)->getInternalNode(), dt); } } for (NodesWList::const_iterator khIt = _allDifferentNodes.begin(); khIt != _allDifferentNodes.end(); ++khIt) { NodePtr node = khIt->lock(); if (!node) { continue; } node->getEffectInstance()->endChanges(); } view->setUpdatesEnabled(true); _model->refreshSelectionBboxAndRedrawView(); } // DSMoveKeysAndNodesCommand::moveSelection
void BaseTest::disconnectNodes(const NodePtr& input, const NodePtr& output, bool expectedReturnvalue) { if (expectedReturnvalue) { ///check that the connections are internally all set as "expected" ///the input must have in its output the node 'output' EXPECT_TRUE( input->hasOutputConnected() ); const NodesWList & outputs = input->getGuiOutputs(); bool foundOutput = false; for (NodesWList::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { if (it->lock() == output) { foundOutput = true; break; } } ///the output must have in its inputs the node 'input' const std::vector<NodeWPtr> & inputs = output->getGuiInputs(); int inputIndex = 0; bool foundInput = false; for (U32 i = 0; i < inputs.size(); ++i) { if (inputs[i].lock() == input) { foundInput = true; break; } ++inputIndex; } EXPECT_TRUE(foundInput); EXPECT_TRUE(foundOutput); EXPECT_EQ(output->getInput(inputIndex), input); EXPECT_TRUE( output->isInputConnected(inputIndex) ); } ///call disconnect bool ret = getApp()->getProject()->disconnectNodes(input, output); EXPECT_EQ(expectedReturnvalue, ret); if (expectedReturnvalue) { ///check that the disconnection went OK const NodesWList & outputs = input->getGuiOutputs(); bool foundOutput = false; for (NodesWList::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { if (it->lock() == output) { foundOutput = true; break; } } ///the output must have in its inputs the node 'input' const std::vector<NodeWPtr> & inputs = output->getGuiInputs(); int inputIndex = 0; bool foundInput = false; for (U32 i = 0; i < inputs.size(); ++i) { if (inputs[i].lock() == input) { foundInput = true; break; } ++inputIndex; } EXPECT_FALSE(foundOutput); EXPECT_FALSE(foundInput); EXPECT_EQ( (Node*)NULL, output->getInput(inputIndex).get() ); EXPECT_FALSE( output->isInputConnected(inputIndex) ); } } // disconnectNodes