GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Engine/EffectInstance.h" #include "Engine/Node.h" #include "Engine/NodeGroup.h" #include "Engine/Settings.h" #include "Engine/Utils.h" // convertFromPlainText #include "Gui/BackdropGui.h" #include "Gui/Edge.h" #include "Gui/GuiApplicationManager.h" #include "Gui/GuiMacros.h" #include "Gui/NodeGui.h" #include "Gui/TabWidget.h" #include "Global/QtCompat.h" NATRON_NAMESPACE_ENTER void NodeGraph::checkForHints(bool shiftdown, bool controlDown, const NodeGuiPtr& selectedNode, const QRectF& visibleSceneR) { NodePtr internalNode = selectedNode->getNode(); if (!internalNode) { return; } bool doMergeHints = shiftdown && controlDown; bool doConnectionHints = controlDown; //Ignore hints for backdrops BackdropGuiPtr isBd = toBackdropGui( selectedNode ); if (isBd) { return; } if (!doMergeHints) { ///for nodes already connected don't show hint if ( ( internalNode->getNInputs() == 0) && internalNode->hasOutputConnected() ) { doConnectionHints = false; } else if ( ( internalNode->getNInputs() > 0) && internalNode->hasAllInputsConnected() && internalNode->hasOutputConnected() ) { doConnectionHints = false; } } if (!doConnectionHints) { return; } QRectF selectedNodeBbox = selectedNode->boundingRectWithEdges(); //selectedNode->mapToParent( selectedNode->boundingRect() ).boundingRect(); double tolerance = 10; selectedNodeBbox.adjust(-tolerance, -tolerance, tolerance, tolerance); NodeGuiPtr nodeToShowMergeRect; NodePtr selectedNodeInternalNode = selectedNode->getNode(); bool selectedNodeIsReader = selectedNodeInternalNode->getEffectInstance()->isReader() || selectedNodeInternalNode->getNInputs() == 0; Edge* edge = 0; std::set<NodeGuiPtr> nodesWithinRect; getNodesWithinViewportRect(visibleWidgetRect(), &nodesWithinRect); { for (std::set<NodeGuiPtr>::iterator it = nodesWithinRect.begin(); it != nodesWithinRect.end(); ++it) { OutputNodesMap outputs; internalNode->getOutputs(outputs); OutputNodesMap::const_iterator foundAsOutput = outputs.find((*it)->getNode()); if (foundAsOutput != outputs.end()) { continue; } QRectF nodeBbox = (*it)->boundingRectWithEdges(); if ( ( (*it) != selectedNode ) && (*it)->isVisible() && nodeBbox.intersects(visibleSceneR) ) { if (doMergeHints) { //QRectF nodeRect = (*it)->mapToParent((*it)->boundingRect()).boundingRect(); NodePtr internalNode = (*it)->getNode(); if ( !internalNode->isOutputNode() && nodeBbox.intersects(selectedNodeBbox) ) { bool nHasInput = internalNode->hasInputConnected(); int nMaxInput = internalNode->getNInputs(); bool selectedHasInput = selectedNodeInternalNode->hasInputConnected(); int selectedMaxInput = selectedNodeInternalNode->getNInputs(); double nPAR = internalNode->getEffectInstance()->getAspectRatio(-1); double selectedPAR = selectedNodeInternalNode->getEffectInstance()->getAspectRatio(-1); double nFPS = internalNode->getEffectInstance()->getFrameRate(); double selectedFPS = selectedNodeInternalNode->getEffectInstance()->getFrameRate(); bool isValid = true; if ( (selectedPAR != nPAR) || (std::abs(nFPS - selectedFPS) > 0.01) ) { if (nHasInput || selectedHasInput) { isValid = false; } else if ( !nHasInput && (nMaxInput == 0) && !selectedHasInput && (selectedMaxInput == 0) ) { isValid = false; } } if (isValid) { nodeToShowMergeRect = (*it)->shared_from_this(); } } else { (*it)->setMergeHintActive(false); } } else { //!doMergeHints edge = (*it)->hasEdgeNearbyRect(selectedNodeBbox); ///if the edge input is the selected node don't continue if ( edge && ( edge->getSource() == selectedNode) ) { edge = 0; } if ( edge && edge->isOutputEdge() ) { if (selectedNodeIsReader) { continue; } int prefInput = selectedNodeInternalNode->getPreferredInputForConnection(); if (prefInput == -1) { edge = 0; } else { Node::CanConnectInputReturnValue ret = selectedNodeInternalNode->canConnectInput(edge->getSource()->getNode(), prefInput); if (ret != Node::eCanConnectInput_ok) { edge = 0; } } } if ( edge && !edge->isOutputEdge() ) { if ( (*it)->getNode()->getEffectInstance()->isReader() || ( (*it)->getNode()->getNInputs() == 0 ) ) { edge = 0; continue; } //Check that the edge can connect to the selected node { Node::CanConnectInputReturnValue ret = edge->getDest()->getNode()->canConnectInput( selectedNodeInternalNode, edge->getInputNumber() ); if ( (ret == Node::eCanConnectInput_inputAlreadyConnected) && !selectedNodeIsReader ) { ret = Node::eCanConnectInput_ok; } if (ret != Node::eCanConnectInput_ok) { edge = 0; } } //Check that the selected node can connect to the input of the edge if (edge) { NodeGuiPtr edgeHasSource = edge->getSource(); if (edgeHasSource) { int prefInput = selectedNodeInternalNode->getPreferredInputForConnection(); if (prefInput != -1) { Node::CanConnectInputReturnValue ret = selectedNodeInternalNode->canConnectInput(edgeHasSource->getNode(), prefInput); if ( (ret == Node::eCanConnectInput_inputAlreadyConnected) && !selectedNodeIsReader ) { ret = Node::eCanConnectInput_ok; } if (ret != Node::eCanConnectInput_ok) { edge = 0; } } } } } if (edge) { edge->setUseHighlight(true); break; } } // doMergeHints } } } // QMutexLocker l(&_imp->_nodesMutex); if ( _imp->_highLightedEdge && ( _imp->_highLightedEdge != edge) ) { _imp->_highLightedEdge->setUseHighlight(false); _imp->_hintInputEdge->hide(); _imp->_hintOutputEdge->hide(); } _imp->_highLightedEdge = edge; if ( edge && edge->getSource() && edge->getDest() ) { ///setup the hints edge ///find out if the node is already connected to what the edge is connected bool alreadyConnected = false; const std::vector<NodeWPtr> & inpNodes = selectedNode->getNode()->getInputs(); for (std::size_t i = 0; i < inpNodes.size(); ++i) { if ( inpNodes[i].lock() == edge->getSource()->getNode() ) { alreadyConnected = true; break; } } if ( !_imp->_hintInputEdge->isVisible() ) { if (!alreadyConnected) { int prefInput = selectedNode->getNode()->getPreferredInputForConnection(); _imp->_hintInputEdge->setInputNumber(prefInput != -1 ? prefInput : 0); _imp->_hintInputEdge->setSourceAndDestination(edge->getSource(), selectedNode); _imp->_hintInputEdge->setVisible(true); } _imp->_hintOutputEdge->setInputNumber( edge->getInputNumber() ); _imp->_hintOutputEdge->setSourceAndDestination( selectedNode, edge->getDest() ); _imp->_hintOutputEdge->setVisible(true); } else { if (!alreadyConnected) { _imp->_hintInputEdge->initLine(); } _imp->_hintOutputEdge->initLine(); } } else if (edge) { ///setup only 1 of the hints edge if ( _imp->_highLightedEdge && !_imp->_hintInputEdge->isVisible() ) { if ( edge->isOutputEdge() ) { int prefInput = selectedNode->getNode()->getPreferredInputForConnection(); if (prefInput != -1) { _imp->_hintInputEdge->setInputNumber(prefInput); _imp->_hintInputEdge->setSourceAndDestination(edge->getSource(), selectedNode); _imp->_hintInputEdge->setVisible(true); } } else { _imp->_hintInputEdge->setInputNumber( edge->getInputNumber() ); _imp->_hintInputEdge->setSourceAndDestination( selectedNode, edge->getDest() ); _imp->_hintInputEdge->setVisible(true); } } else if ( _imp->_highLightedEdge && _imp->_hintInputEdge->isVisible() ) { _imp->_hintInputEdge->initLine(); } } else if (nodeToShowMergeRect) { nodeToShowMergeRect->setMergeHintActive(true); selectedNode->setMergeHintActive(true); _imp->_mergeHintNode = nodeToShowMergeRect; } else { selectedNode->setMergeHintActive(false); _imp->_mergeHintNode.reset(); } } // NodeGraph::checkForHints
void NodeAnimPrivate::computeGroupRange() { NodeGuiPtr nodeUI = nodeGui.lock(); NodePtr node = nodeUI->getNode(); if (!node) { return; } AnimationModulePtr isAnimModel = toAnimationModule(model.lock()); if (!isAnimModel) { return; } NodeGroupPtr nodegroup = node->isEffectNodeGroup(); assert(nodegroup); if (!nodegroup) { return; } AnimationModuleTreeView* treeView = isAnimModel->getEditor()->getTreeView(); NodesList nodes = nodegroup->getNodes(); std::set<double> times; for (NodesList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { NodeAnimPtr childAnim = isAnimModel->findNodeAnim(*it); if (!childAnim) { continue; } if (!treeView->isItemVisibleRecursive(childAnim->getTreeItem())) { continue; } childAnim->refreshFrameRange(); RangeD childRange = childAnim->getFrameRange(); times.insert(childRange.min); times.insert(childRange.max); // Also check the child knobs keyframes NodeGuiPtr childGui = childAnim->getNodeGui(); const KnobsVec &knobs = childGui->getNode()->getKnobs(); for (KnobsVec::const_iterator it2 = knobs.begin(); it2 != knobs.end(); ++it2) { if ( !(*it2)->isAnimationEnabled() || !(*it2)->hasAnimation() ) { continue; } else { // For each dimension and for each split view get the first/last keyframe (if any) int nDims = (*it2)->getNDimensions(); std::list<ViewIdx> views = (*it2)->getViewsList(); for (std::list<ViewIdx>::const_iterator it3 = views.begin(); it3 != views.end(); ++it3) { for (int i = 0; i < nDims; ++i) { CurvePtr curve = (*it2)->getAnimationCurve(*it3, DimIdx(i)); if (!curve) { continue; } int nKeys = curve->getKeyFramesCount(); if (nKeys > 0) { KeyFrame k; if (curve->getKeyFrameWithIndex(0, &k)) { times.insert( k.getTime() ); } if (curve->getKeyFrameWithIndex(nKeys - 1, &k)) { times.insert( k.getTime() ); } } } } } } // for all knobs } // for all children nodes if (times.size() <= 1) { frameRange.min = 0; frameRange.max = 0; } else { frameRange.min = *times.begin(); frameRange.max = *times.rbegin(); } } // computeGroupRange
void NodeAnimPrivate::computeRetimeRange() { NodeGuiPtr nodeUI = nodeGui.lock(); NodePtr node = nodeUI->getNode(); if (!node) { return; } NodePtr input = node->getInput(0); if (!input) { return; } if (input) { RangeD inputRange = {0, 0}; { GetFrameRangeResultsPtr results; ActionRetCodeEnum stat = input->getEffectInstance()->getFrameRange_public(&results); if (!isFailureRetCode(stat)) { results->getFrameRangeResults(&inputRange); } } FramesNeededMap framesFirst, framesLast; { GetFramesNeededResultsPtr results; ActionRetCodeEnum stat = node->getEffectInstance()->getFramesNeeded_public(TimeValue(inputRange.min), ViewIdx(0), &results); if (!isFailureRetCode(stat)) { results->getFramesNeeded(&framesFirst); } stat = node->getEffectInstance()->getFramesNeeded_public(TimeValue(inputRange.max), ViewIdx(0), &results); if (!isFailureRetCode(stat)) { results->getFramesNeeded(&framesLast); } } assert( !framesFirst.empty() && !framesLast.empty() ); if ( framesFirst.empty() || framesLast.empty() ) { return; } { const FrameRangesMap& rangeFirst = framesFirst[0]; assert( !rangeFirst.empty() ); if ( rangeFirst.empty() ) { return; } FrameRangesMap::const_iterator it = rangeFirst.find( ViewIdx(0) ); assert( it != rangeFirst.end() ); if ( it == rangeFirst.end() ) { return; } const std::vector<OfxRangeD>& frames = it->second; assert( !frames.empty() ); if ( frames.empty() ) { return; } frameRange.min = (frames.front().min); } { FrameRangesMap& rangeLast = framesLast[0]; assert( !rangeLast.empty() ); if ( rangeLast.empty() ) { return; } FrameRangesMap::const_iterator it = rangeLast.find( ViewIdx(0) ); assert( it != rangeLast.end() ); if ( it == rangeLast.end() ) { return; } const std::vector<OfxRangeD>& frames = it->second; assert( !frames.empty() ); if ( frames.empty() ) { return; } frameRange.max = (frames.front().min); } } } // computeRetimeRange
NATRON_NAMESPACE_ENTER ViewerTab::ViewerTab(const std::string& scriptName, const std::list<NodeGuiPtr> & existingNodesContext, const std::list<NodeGuiPtr>& activePluginsContext, Gui* gui, const NodeGuiPtr& node_ui, QWidget* parent) : QWidget(parent) , PanelWidget(scriptName, this, gui) , _imp( new ViewerTabPrivate(this, node_ui) ) { ViewerNodePtr node = node_ui->getNode()->isEffectViewerNode(); installEventFilter(this); setMouseTracking(true); NodePtr internalNode = node->getNode(); QObject::connect( internalNode.get(), SIGNAL(scriptNameChanged(QString)), this, SLOT(onInternalNodeScriptNameChanged(QString)) ); QObject::connect( internalNode.get(), SIGNAL(labelChanged(QString,QString)), this, SLOT(onInternalNodeLabelChanged(QString,QString)) ); QObject::connect( node.get(), SIGNAL(internalViewerCreated()), this, SLOT(onInternalViewerCreated())); _imp->mainLayout = new QVBoxLayout(this); setLayout(_imp->mainLayout); _imp->mainLayout->setSpacing(0); _imp->mainLayout->setContentsMargins(0, 0, 0, 0); QFontMetrics fm(font(), 0); _imp->viewerContainer = new QWidget(this); _imp->viewerLayout = new QHBoxLayout(_imp->viewerContainer); _imp->viewerLayout->setContentsMargins(0, 0, 0, 0); _imp->viewerLayout->setSpacing(0); _imp->viewerSubContainer = new QWidget(_imp->viewerContainer); _imp->viewerSubContainerLayout = new QVBoxLayout(_imp->viewerSubContainer); _imp->viewerSubContainerLayout->setContentsMargins(0, 0, 0, 0); _imp->viewerSubContainerLayout->setSpacing(1); // Info bars QString inputNames[2] = { QString::fromUtf8("A:"), QString::fromUtf8("B:") }; bool infobarvisible = node->isInfoBarVisible(); for (int i = 0; i < 2; ++i) { _imp->infoWidget[i] = new InfoViewerWidget(inputNames[i], this); } // Viewer _imp->viewer = new ViewerGL(this); GuiAppInstancePtr app = gui->getApp(); // Init viewer to project format { Format projectFormat; app->getProject()->getProjectDefaultFormat(&projectFormat); RectD canonicalFormat = projectFormat.toCanonicalFormat(); for (int i = 0; i < 2; ++i) { _imp->viewer->setInfoViewer(_imp->infoWidget[i], i); _imp->viewer->setRegionOfDefinition(canonicalFormat, projectFormat.getPixelAspectRatio(), i); setInfoBarAndViewerResolution(projectFormat, canonicalFormat, projectFormat.getPixelAspectRatio(), i); } _imp->viewer->resetWipeControls(); } _imp->viewerSubContainerLayout->addWidget(_imp->viewer); for (int i = 0; i < 2; ++i) { _imp->viewerSubContainerLayout->addWidget(_imp->infoWidget[i]); _imp->viewer->setInfoViewer(_imp->infoWidget[i], i); if (i == 1 || !infobarvisible) { _imp->infoWidget[i]->hide(); } } manageSlotsForInfoWidget(0, true); _imp->viewerLayout->addWidget(_imp->viewerSubContainer); _imp->mainLayout->addWidget(_imp->viewerContainer); TimeLinePtr timeline = app->getTimeLine(); _imp->timeLineGui = new TimeLineGui(node, timeline, getGui(), this); QObject::connect( _imp->timeLineGui, SIGNAL(boundariesChanged(SequenceTime,SequenceTime)), this, SLOT(onTimelineBoundariesChanged(SequenceTime,SequenceTime)) ); QObject::connect( app->getProject().get(), SIGNAL(frameRangeChanged(int,int)), _imp->timeLineGui, SLOT(onProjectFrameRangeChanged(int,int)) ); _imp->timeLineGui->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); if (!node->isTimelineVisible()) { _imp->timeLineGui->hide(); } //Add some spacing because the timeline might be black as the info _imp->mainLayout->addSpacing( TO_DPIY(5) ); _imp->mainLayout->addWidget(_imp->timeLineGui); double leftBound, rightBound; leftBound = node->getPlaybackInPointKnob()->getValue(); rightBound = node->getPlaybackOutPointKnob()->getValue(); TimeValue projectLeft, projectRight; app->getProject()->getFrameRange(&projectLeft, &projectRight); _imp->timeLineGui->setBoundaries(leftBound, rightBound); onTimelineBoundariesChanged(leftBound, rightBound); _imp->timeLineGui->setFrameRangeEdited(projectLeft != leftBound || projectRight != rightBound);; manageTimelineSlot(false, timeline); QObject::connect( node.get(), SIGNAL(renderStatsAvailable(int,double,RenderStatsMap)), this, SLOT(onRenderStatsAvailable(int,double,RenderStatsMap)) ); QObject::connect( _imp->viewer, SIGNAL(zoomChanged(int)), this, SLOT(updateZoomComboBox(int)) ); QObject::connect( node.get(), SIGNAL(viewerDisconnected()), this, SLOT(disconnectViewer()) ); createNodeViewerInterface(node_ui); setPluginViewerInterface(node_ui); for (std::list<NodeGuiPtr>::const_iterator it = existingNodesContext.begin(); it != existingNodesContext.end(); ++it) { ViewerNodePtr isViewerNode = (*it)->getNode()->isEffectViewerNode(); // For viewers, create the viewer interface separately if (!isViewerNode) { createNodeViewerInterface(*it); } } for (std::list<NodeGuiPtr>::const_iterator it = activePluginsContext.begin(); it != activePluginsContext.end(); ++it) { ViewerNodePtr isViewerNode = (*it)->getNode()->isEffectViewerNode(); // For viewers, create the viewer interface separately if (!isViewerNode) { setPluginViewerInterface(*it); } } setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); _imp->viewerNode.lock()->setUiContext( getViewer() ); QTimer::singleShot( 25, _imp->timeLineGui, SLOT(recenterOnBounds()) ); //Refresh the viewport lock state const std::list<ViewerTab*>& viewers = getGui()->getViewersList(); if ( !viewers.empty() ) { ViewerTab* other = viewers.front(); if ( other->getInternalNode()->isViewersSynchroEnabled() ) { double left, bottom, factor, par; other->getViewer()->getProjection(&left, &bottom, &factor, &par); _imp->viewer->setProjection(left, bottom, factor, par); node->setViewersSynchroEnabled(true); } } _imp->cachedFramesThread.reset(new CachedFramesThread(this)); _imp->cachedFramesThread->start(); }
void ProjectGuiSerialization::initialize(const ProjectGui* projectGui) { NodesList activeNodes; projectGui->getInternalProject()->getActiveNodesExpandGroups(&activeNodes); _serializedNodes.clear(); for (NodesList::iterator it = activeNodes.begin(); it != activeNodes.end(); ++it) { NodeGuiIPtr nodegui_i = (*it)->getNodeGui(); if (!nodegui_i) { continue; } NodeGuiPtr nodegui = boost::dynamic_pointer_cast<NodeGui>(nodegui_i); if ( nodegui->isVisible() ) { NodeCollectionPtr isInCollection = (*it)->getGroup(); NodeGroupPtr isCollectionAGroup = toNodeGroup( isInCollection ); if (!isCollectionAGroup) { ///Nodes within a group will be serialized recursively in the node group serialization NodeGuiSerialization state; nodegui->serialize(&state); _serializedNodes.push_back(state); } ViewerInstancePtr viewer = (*it)->isEffectViewerInstance(); if (viewer) { ViewerTab* tab = projectGui->getGui()->getViewerTabForInstance(viewer); assert(tab); ViewerData viewerData; double zoompar; tab->getViewer()->getProjection(&viewerData.zoomLeft, &viewerData.zoomBottom, &viewerData.zoomFactor, &zoompar); viewerData.userRoI = tab->getViewer()->getUserRegionOfInterest(); viewerData.userRoIenabled = tab->getViewer()->isUserRegionOfInterestEnabled(); viewerData.isClippedToProject = tab->isClippedToProject(); viewerData.autoContrastEnabled = tab->isAutoContrastEnabled(); viewerData.gain = tab->getGain(); viewerData.gamma = tab->getGamma(); viewerData.colorSpace = tab->getColorSpace(); viewerData.channels = tab->getChannelsString(); viewerData.renderScaleActivated = tab->getRenderScaleActivated(); viewerData.mipMapLevel = tab->getMipMapLevel(); viewerData.zoomOrPanSinceLastFit = tab->getZoomOrPannedSinceLastFit(); viewerData.wipeCompositingOp = (int)tab->getCompositingOperator(); viewerData.leftToolbarVisible = tab->isLeftToolbarVisible(); viewerData.rightToolbarVisible = tab->isRightToolbarVisible(); viewerData.topToolbarVisible = tab->isTopToolbarVisible(); viewerData.infobarVisible = tab->isInfobarVisible(); viewerData.playerVisible = tab->isPlayerVisible(); viewerData.timelineVisible = tab->isTimelineVisible(); viewerData.checkerboardEnabled = tab->isCheckerboardEnabled(); viewerData.isFullFrameProcessEnabled = tab->isFullFrameProcessingEnabled(); viewerData.fps = tab->getDesiredFps(); viewerData.fpsLocked = tab->isFPSLocked(); viewerData.isPauseEnabled[0] = tab->isViewerPaused(0); viewerData.isPauseEnabled[1] = tab->isViewerPaused(1); viewerData.layerName = tab->getCurrentLayerName().toStdString(); viewerData.alphaLayerName = tab->getCurrentAlphaLayerName().toStdString(); tab->getTimelineBounds(&viewerData.leftBound, &viewerData.rightBound); tab->getActiveInputs(&viewerData.aChoice, &viewerData.bChoice); viewerData.version = VIEWER_DATA_SERIALIZATION_VERSION; _viewersData.insert( std::make_pair(viewer->getNode()->getScriptName_mt_safe(), viewerData) ); } } } ///Init windows _layoutSerialization.initialize( projectGui->getGui() ); ///save histograms std::list<Histogram*> histograms = projectGui->getGui()->getHistograms_mt_safe(); for (std::list<Histogram*>::const_iterator it = histograms.begin(); it != histograms.end(); ++it) { _histograms.push_back( (*it)->objectName().toStdString() ); } ///save opened panels by order std::list<DockablePanel*> panels = projectGui->getGui()->getVisiblePanels_mt_safe(); for (std::list<DockablePanel*>::iterator it = panels.begin(); it != panels.end(); ++it) { if ( (*it)->isVisible() ) { KnobHolderPtr holder = (*it)->getHolder(); assert(holder); EffectInstancePtr isEffect = toEffectInstance(holder); ProjectPtr isProj = toProject(holder); if (isProj) { _openedPanelsOrdered.push_back(kNatronProjectSettingsPanelSerializationName); } else if (isEffect) { _openedPanelsOrdered.push_back( isEffect->getNode()->getFullyQualifiedName() ); } } } _scriptEditorInput = projectGui->getGui()->getScriptEditor()->getAutoSavedScript().toStdString(); std::map<NATRON_PYTHON_NAMESPACE::PyPanel*, std::string> pythonPanels = projectGui->getGui()->getPythonPanels(); for (std::map<NATRON_PYTHON_NAMESPACE::PyPanel*, std::string>::iterator it = pythonPanels.begin(); it != pythonPanels.end(); ++it) { boost::shared_ptr<PythonPanelSerialization> s(new PythonPanelSerialization); s->initialize(it->first, it->second); _pythonPanels.push_back(s); } } // initialize
void NodeGraph::connectCurrentViewerToSelection(int inputNB) { ViewerTab* lastUsedViewer = getLastSelectedViewer(); if (lastUsedViewer) { boost::shared_ptr<NodeCollection> collection = lastUsedViewer->getInternalNode()->getNode()->getGroup(); if (collection && collection->getNodeGraph() != this) { //somehow the group doesn't belong to this nodegraph , pick another one const std::list<ViewerTab*>& tabs = getGui()->getViewersList(); lastUsedViewer = 0; for (std::list<ViewerTab*>::const_iterator it = tabs.begin(); it!=tabs.end(); ++it) { boost::shared_ptr<NodeCollection> otherCollection = (*it)->getInternalNode()->getNode()->getGroup(); if (otherCollection && otherCollection->getNodeGraph() == this) { lastUsedViewer = *it; break; } } } } boost::shared_ptr<InspectorNode> v; if ( lastUsedViewer ) { v = boost::dynamic_pointer_cast<InspectorNode>( lastUsedViewer-> getInternalNode()->getNode() ); } else { CreateNodeArgs args(QString::fromUtf8(PLUGINID_NATRON_VIEWER), eCreateNodeReasonUserCreate, getGroup()); NodePtr viewerNode = getGui()->getApp()->createNode(args); if (!viewerNode) { return; } v = boost::dynamic_pointer_cast<InspectorNode>(viewerNode); } if (!v) { return; } ///if the node is no longer active (i.e: it was deleted by the user), don't do anything. if ( !v->isActivated() ) { return; } ///get a ptr to the NodeGui boost::shared_ptr<NodeGuiI> gui_i = v->getNodeGui(); NodeGuiPtr gui = boost::dynamic_pointer_cast<NodeGui>(gui_i); assert(gui); ///if there's no selected node or the viewer is selected, then try refreshing that input nb if it is connected. bool viewerAlreadySelected = std::find(_imp->_selection.begin(),_imp->_selection.end(),gui) != _imp->_selection.end(); if (_imp->_selection.empty() || (_imp->_selection.size() > 1) || viewerAlreadySelected) { v->setActiveInputAndRefresh(inputNB, false); gui->refreshEdges(); return; } NodeGuiPtr selected = _imp->_selection.front(); if ( !selected->getNode()->canOthersConnectToThisNode() ) { return; } ///if the node doesn't have the input 'inputNb' created yet, populate enough input ///so it can be created. Edge* foundInput = gui->getInputArrow(inputNB); assert(foundInput); ///and push a connect command to the selected node. pushUndoCommand( new ConnectCommand(this,foundInput,foundInput->getSource(),selected) ); ///Set the viewer as the selected node (also wipe the current selection) selectNode(gui,false); } // connectCurrentViewerToSelection