diff --git a/.travis.yml b/.travis.yml index 0267a6a839..15f782494a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,8 @@ addons: # see http://stackoverflow.com/questions/11302758/error-while-copy-constructing-boostshared-ptr-using-c11 # we use the irie/boost ppa for that purpose - irie-boost + # kubuntu-ppa/backports contains OpenCV + - kubuntu-ppa-backports # the PPA xorg-edgers contains cairo 1.12 (required for rotoscoping) - xorg-edgers-ppa # ubuntu-toolchain-r/test contains recent versions of gcc @@ -48,6 +50,23 @@ addons: - python-pyside - libpyside-dev - libshiboken-dev + coverity_scan: + # customized build script URL + # TRAVIS_REPO_SLUG: owner_name/repo_name of repository currently being built + # TRAVIS_BRANCH: name of the branch currently being built + build_script_url: https://raw.githubusercontent.com/$TRAVIS_REPO_SLUG/$TRAVIS_BRANCH/.travis-coverity-scan-build.sh + # project metadata + project: + name: $TRAVIS_REPO_SLUG + # Where email notification of build analysis results will be sent + notification_email: frederic.devernay@m4x.org + # Commands to prepare for build_command + #build_command_prepend: ./configure + # This command will be added as an argument to "cov-build" to compile + # the project for analysis + build_command: "tools/travis/build.sh" + # Pattern to match selecting branches that will run analysis + branch_pattern: coverity_scan matrix: exclude: @@ -97,21 +116,3 @@ after_success: after_failure: - cat install_dependencies.log || true -addons: - coverity_scan: - # customized build script URL - # TRAVIS_REPO_SLUG: owner_name/repo_name of repository currently being built - # TRAVIS_BRANCH: name of the branch currently being built - build_script_url: https://raw.githubusercontent.com/$TRAVIS_REPO_SLUG/$TRAVIS_BRANCH/.travis-coverity-scan-build.sh - # project metadata - project: - name: $TRAVIS_REPO_SLUG - # Where email notification of build analysis results will be sent - notification_email: frederic.devernay@m4x.org - # Commands to prepare for build_command - #build_command_prepend: ./configure - # This command will be added as an argument to "cov-build" to compile - # the project for analysis - build_command: "tools/travis/build.sh" - # Pattern to match selecting branches that will run analysis - branch_pattern: coverity_scan diff --git a/App/App.pro b/App/App.pro index 7a4cc3b846..cff83d33c2 100644 --- a/App/App.pro +++ b/App/App.pro @@ -26,10 +26,12 @@ win32 { CONFIG += app } CONFIG += moc -CONFIG += boost glew opengl qt expat cairo python shiboken pyside +CONFIG += boost glew opengl qt cairo python shiboken pyside QT += gui core opengl network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets concurrent +!noexpat: CONFIG += expat + macx { ### custom variables for the Info.plist file # use a custom Info.plist template diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ce373719f..efb80ce273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Nodes group to have cleaner graphs with hidden sub-nodegraphs - PyPlug: You can export a group as a Python plug-in and it be re-used in any other project as a single node as you would use any other plug-in - SeExpr integration within a node: http://www.disneyanimation.com/technology/seexpr.html +- New SeNoise and SeGrain nodes based on SeExpr - RotoPaint node with Wacom tablets support - DopeSheet editor: This is where you can control easily keyframes and clips in time for motion graphics purposes - Render statistics: Available in the Render menu, use this to debug complex compositions diff --git a/CrashReporter/CrashDialog.cpp b/CrashReporter/CrashDialog.cpp index 6b8bd85422..e102133bc1 100644 --- a/CrashReporter/CrashDialog.cpp +++ b/CrashReporter/CrashDialog.cpp @@ -119,7 +119,8 @@ CrashDialog::CrashDialog(const QString &filePath) .arg("rgb(21,97,248)") // %7: keyframe value color .arg("rgb(200,200,200)") // %8: disabled editable text .arg("rgb(180, 200, 100)") // %9: expression background color - .arg("rgb(150,150,50")); // *10: altered text color + .arg("rgb(150,150,50") // %10: altered text color + .arg("rgb(255,195,120)")); // %11: mouse over selection color } setWindowTitle(tr("Natron Issue Reporter")); diff --git a/Documentation/source/PythonReference/NatronEngine/BooleanParam.rst b/Documentation/source/PythonReference/NatronEngine/BooleanParam.rst index 3cb834c401..bfc601a5fa 100644 --- a/Documentation/source/PythonReference/NatronEngine/BooleanParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/BooleanParam.rst @@ -53,7 +53,7 @@ Returns the value of the parameter at the current timeline's time. .. method:: NatronEngine.BooleanParam.get(frame) - :param frame: :class:`int` + :param frame: :class:`float` :rtype: :class:`bool` Returns the value of the parameter at the given *frame*. This value may be interpolated @@ -85,7 +85,7 @@ Same as :func:`get()` .. method:: NatronEngine.BooleanParam.getValueAtTime(time) - :param time: :class:`int` + :param time: :class:`float` :rtype: :class:`bool` Same as :func:`get(frame)` diff --git a/Documentation/source/PythonReference/NatronEngine/ButtonParam.rst b/Documentation/source/PythonReference/NatronEngine/ButtonParam.rst index b7e15b6553..a77118f1ac 100644 --- a/Documentation/source/PythonReference/NatronEngine/ButtonParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/ButtonParam.rst @@ -20,6 +20,7 @@ Functions ^^^^^^^^^ * def :meth:`setIconFilePath` (icon) +* def :meth:`trigger` () Member functions description @@ -37,5 +38,7 @@ a file-path relative to a path in the NATRON_PLUGIN_PATH. +.. method:: NatronEngine.ButtonParam.trigger() +Triggers the button action as though the user had pressed it. diff --git a/Documentation/source/PythonReference/NatronEngine/ChoiceParam.rst b/Documentation/source/PythonReference/NatronEngine/ChoiceParam.rst index 9c38528148..69d80e4d50 100644 --- a/Documentation/source/PythonReference/NatronEngine/ChoiceParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/ChoiceParam.rst @@ -67,7 +67,7 @@ hovers the entry with the mouse. .. method:: NatronEngine.ChoiceParam.get(frame) - :param frame: :class:`int` + :param frame: :class:`float` :rtype: :class:`int` Get the value of the parameter at the given *frame*. @@ -130,7 +130,7 @@ Same as :func:`get()` .. method:: NatronEngine.ChoiceParam.getValueAtTime(time) - :param time: :class:`int` + :param time: :class:`float` :rtype: :class:`int` Same as :func:`get(frame)` diff --git a/Documentation/source/PythonReference/NatronEngine/ColorParam.rst b/Documentation/source/PythonReference/NatronEngine/ColorParam.rst index 1f1cb0462b..b507db9463 100644 --- a/Documentation/source/PythonReference/NatronEngine/ColorParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/ColorParam.rst @@ -57,7 +57,7 @@ Member functions description .. method:: NatronEngine.ColorParam.get(frame) - :param frame: :class:`int` + :param frame: :class:`float` :rtype: :class:`ColorTuple` Returns a :doc:`ColorTuple` of the color held by the parameter at the given *frame*. @@ -150,7 +150,7 @@ Returns the value of this parameter at the given *dimension* at the current time .. method:: NatronEngine.ColorParam.getValueAtTime(time[, dimension=0]) - :param time: :class:`int` + :param time: :class:`float` :param dimension: :class:`int` :rtype: :class:`float` diff --git a/Documentation/source/PythonReference/NatronEngine/Double2DParam.rst b/Documentation/source/PythonReference/NatronEngine/Double2DParam.rst index 8bc5a53b60..38a42f7d36 100644 --- a/Documentation/source/PythonReference/NatronEngine/Double2DParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/Double2DParam.rst @@ -48,6 +48,7 @@ timeline's time. .. method:: NatronEngine.Double2DParam.get(frame) + :param frame: :class:`float` :rtype: :class:`Double2DTuple` Returns a :doc:`Double2DTuple` with the [x,y] values for this parameter at the given *frame*. diff --git a/Documentation/source/PythonReference/NatronEngine/Double3DParam.rst b/Documentation/source/PythonReference/NatronEngine/Double3DParam.rst index f8f9799f38..bfce69558f 100644 --- a/Documentation/source/PythonReference/NatronEngine/Double3DParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/Double3DParam.rst @@ -34,6 +34,7 @@ timeline's time. .. method:: NatronEngine.Double3DParam.get(frame) + :param frame: :class:`float` :rtype: :class:`Double3DTuple` Returns a :doc:`Double3DTuple` with the [x,y,z] values for this parameter at the given *frame*. diff --git a/Documentation/source/PythonReference/NatronEngine/DoubleParam.rst b/Documentation/source/PythonReference/NatronEngine/DoubleParam.rst index eca0ef7166..bb883bf319 100644 --- a/Documentation/source/PythonReference/NatronEngine/DoubleParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/DoubleParam.rst @@ -69,7 +69,7 @@ Member functions description .. method:: NatronEngine.DoubleParam.get(frame) - :param frame: :class:`int` + :param frame: :class:`float` :rtype: :class:`float` Returns the value of this parameter at the given *frame*. If the animation curve has an @@ -165,7 +165,7 @@ Returns the value of this parameter at the given *dimension* at the current time .. method:: NatronEngine.DoubleParam.getValueAtTime(time[, dimension=0]) - :param time: :class:`int` + :param time: :class:`float` :param dimension: :class:`int` :rtype: :class:`float` diff --git a/Documentation/source/PythonReference/NatronEngine/Effect.rst b/Documentation/source/PythonReference/NatronEngine/Effect.rst index f799015da7..44d0d1b0a0 100644 --- a/Documentation/source/PythonReference/NatronEngine/Effect.rst +++ b/Documentation/source/PythonReference/NatronEngine/Effect.rst @@ -117,6 +117,8 @@ Member functions description Starts a begin/End bracket, blocking all evaluation (=renders and callback onParamChanged) that would be issued due to a call to :func:`setValue` on any parameter of the Effect. + Similarly all input changes will not be evaluated until endChanges() is called. + Typically to change several values at once we bracket the changes like this:: node.beginChanges() @@ -124,9 +126,15 @@ Member functions description param2.setValue(...) param3.setValue(...) param4.setValue(...) - node.endChanges() # This triggers a new render and a call to the onParamChanged callback - + node.endChanges() # This triggers a new render + A more complex call: + + node.beginChanges() + node.connectInput(0,otherNode) + node.connectInput(1,thirdNode) + param1.setValue(...) + node.endChanges() # This triggers a new render .. method:: NatronEngine.Effect.endChanges() @@ -465,5 +473,7 @@ should not be used. Can be called to disable editing of the group via Natron's graphical user interface. This is handy to prevent users from accidentally breaking the sub-graph. This can always be reverted by editing the python script associated. +The user will still be able to see the internal node graph but will not be able to +unlock it. diff --git a/Documentation/source/PythonReference/NatronEngine/Int2DParam.rst b/Documentation/source/PythonReference/NatronEngine/Int2DParam.rst index ed3cfe28a5..bf33b63278 100644 --- a/Documentation/source/PythonReference/NatronEngine/Int2DParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/Int2DParam.rst @@ -36,7 +36,7 @@ current time. .. method:: NatronEngine.Int2DParam.get(frame) - + :param: :class:`float` :rtype: :class: `Int2DTuple` Returns a :doc:`Int2DTuple` containing the [x,y] value of this parameter at diff --git a/Documentation/source/PythonReference/NatronEngine/Int3DParam.rst b/Documentation/source/PythonReference/NatronEngine/Int3DParam.rst index 32e7cf59b2..cb0951f5fb 100644 --- a/Documentation/source/PythonReference/NatronEngine/Int3DParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/Int3DParam.rst @@ -35,7 +35,7 @@ current time. .. method:: NatronEngine.Int3DParam.get(frame) - :param frame: :class:`int` + :param frame: :class:`float` :rtype: :class:`` diff --git a/Documentation/source/PythonReference/NatronEngine/IntParam.rst b/Documentation/source/PythonReference/NatronEngine/IntParam.rst index 7e22703848..383c88d8ac 100644 --- a/Documentation/source/PythonReference/NatronEngine/IntParam.rst +++ b/Documentation/source/PythonReference/NatronEngine/IntParam.rst @@ -71,7 +71,7 @@ Member functions description .. method:: NatronEngine.IntParam.get(frame) - :param frame: :class:`int` + :param frame: :class:`float` :rtype: :class:`int` @@ -173,7 +173,7 @@ Returns the value of this parameter at the given *dimension* at the current time .. method:: NatronEngine.IntParam.getValueAtTime(time[, dimension=0]) - :param time: :class:`int` + :param time: :class:`float` :param dimension: :class:`int` :rtype: :class:`int` diff --git a/Documentation/source/PythonReference/NatronEngine/StringParamBase.rst b/Documentation/source/PythonReference/NatronEngine/StringParamBase.rst index 736d1496eb..60ec39c374 100644 --- a/Documentation/source/PythonReference/NatronEngine/StringParamBase.rst +++ b/Documentation/source/PythonReference/NatronEngine/StringParamBase.rst @@ -60,7 +60,7 @@ Get the value of the parameter at the current timeline's time .. method:: NatronEngine.StringParamBase.get(frame) - :param frame: :class:`int` + :param frame: :class:`float` :rtype: :class:`str` @@ -94,7 +94,7 @@ Same as :func:`get()` .. method:: NatronEngine.StringParamBase.getValueAtTime(time) - :param time: :class:`int` + :param time: :class:`float` :rtype: :class:`str` Same as :func:`get(frame)` diff --git a/Documentation/source/PythonReference/NatronGui/PyModalDialog.rst b/Documentation/source/PythonReference/NatronGui/PyModalDialog.rst index 8f976be4f5..5ca1ce6075 100644 --- a/Documentation/source/PythonReference/NatronGui/PyModalDialog.rst +++ b/Documentation/source/PythonReference/NatronGui/PyModalDialog.rst @@ -40,15 +40,15 @@ To create a new :doc:`PyModalDialog`, just use the :func:`createModalDialog()` function inherited from :class:`QDialog` :: +To show the dialog to the user, use the :func:`exec_()<>` function inherited from :class:`QDialog` :: - dialog.exec() + dialog.exec_() -Note that once :func:`exec()<>` is called, no instruction will be executed until the user closed the dialog. +Note that once :func:`exec_()<>` is called, no instruction will be executed until the user closed the dialog. -The modal dialog always has *OK* and *Cancel* buttons. To query which button the user pressed, inspect the return value of the :func:`exec()<>` call:: +The modal dialog always has *OK* and *Cancel* buttons. To query which button the user pressed, inspect the return value of the :func:`exec_()<>` call:: - if dialog.exec(): + if dialog.exec_(): #The user pressed OK ... else: @@ -71,11 +71,11 @@ Once all your parameters are created, create the GUI for them using the :func:`r dialog.refreshUserParamsGUI() - dialog.exec() + dialog.exec_() You can then retrieve the value of a parameter once the dialog is finished using the :func:`getParam(scriptName)` function:: - if dialog.exec(): + if dialog.exec_(): intValue = dialog.getParam("myInt").get() boolValue = dialog.getParam("myBool").get() diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index 418eac6ec3..e6f10c266d 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -130,12 +130,8 @@ AppInstance::AppInstance(int appID) AppInstance::~AppInstance() { - appPTR->removeInstance(_imp->_appID); - ///Clear nodes now, not in the destructor of the project as - ///deleting nodes might reference the project. - _imp->_currentProject->clearNodes(false); - _imp->_currentProject->discardAppPointer(); + appPTR->removeInstance(_imp->_appID); } void @@ -464,12 +460,9 @@ AppInstance::load(const CLArgs& cl) } } - try { - startWritersRendering(cl.areRenderStatsEnabled(),writersWork); - } catch (const std::exception& e) { - getProject()->removeLockFile(); - throw e; - } + + startWritersRendering(cl.areRenderStatsEnabled(),writersWork); + @@ -635,25 +628,38 @@ AppInstance::createNodeFromPythonModule(Natron::Plugin* plugin, { FlagSetter fs(true,&_imp->_creatingGroup,&_imp->creatingGroupMutex); - CreateNodeArgs groupArgs(PLUGINID_NATRON_GROUP, - "", - -1,-1, - true, //< autoconnect - INT_MIN,INT_MIN, - true, //< push undo/redo command - true, // add to project - true, - QString(), - CreateNodeArgs::DefaultValuesList(), - group); - NodePtr containerNode = createNode(groupArgs); + NodePtr containerNode; + if (!requestedByLoad) { + CreateNodeArgs groupArgs(PLUGINID_NATRON_GROUP, + "", + -1,-1, + true, //< autoconnect + INT_MIN,INT_MIN, + true, //< push undo/redo command + true, // add to project + true, + QString(), + CreateNodeArgs::DefaultValuesList(), + group); + containerNode = createNode(groupArgs); + std::string containerName; + group->initNodeName(plugin->getLabelWithoutSuffix().toStdString(),&containerName); + containerNode->setScriptName(containerName); + + + } else { + + LoadNodeArgs groupArgs(PLUGINID_NATRON_GROUP, + "", + -1,-1, + &serialization, + false, + group); + containerNode = loadNode(groupArgs); + } if (!containerNode) { return containerNode; } - std::string containerName; - group->initNodeName(plugin->getLabelWithoutSuffix().toStdString(),&containerName); - containerNode->setScriptName(containerName); - std::string containerFullySpecifiedName = containerNode->getFullyQualifiedName(); @@ -677,22 +683,16 @@ AppInstance::createNodeFromPythonModule(Natron::Plugin* plugin, } node = containerNode; } - if (requestedByLoad) { - containerNode->loadKnobs(serialization); - if (!serialization.isNull() && !serialization.getUserPages().empty()) { - containerNode->getLiveInstance()->refreshKnobs(); - } - node = containerNode; - } if (!moduleName.isEmpty()) { setGroupLabelIDAndVersion(node,modulePath, moduleName); } + } //FlagSetter fs(true,&_imp->_creatingGroup,&_imp->creatingGroupMutex); ///Now that the group is created and all nodes loaded, autoconnect the group like other nodes. - onGroupCreationFinished(node); + onGroupCreationFinished(node, requestedByLoad); return node; } @@ -776,16 +776,17 @@ AppInstance::createNodeInternal(const QString & pluginID, boost::shared_ptr node; Natron::Plugin* plugin = 0; - QString findId; //Roto has moved to a built-in plugin - if (userEdited && pluginID == PLUGINID_OFX_ROTO) { + if ((userEdited || requestedByLoad) && + ((!_imp->_projectCreatedWithLowerCaseIDs && pluginID == PLUGINID_OFX_ROTO) || (_imp->_projectCreatedWithLowerCaseIDs && pluginID == QString(PLUGINID_OFX_ROTO).toLower()))) { findId = PLUGINID_NATRON_ROTO; } else { findId = pluginID; } + try { - plugin = appPTR->getPluginBinary(findId,majorVersion,minorVersion,_imp->_projectCreatedWithLowerCaseIDs); + plugin = appPTR->getPluginBinary(findId,majorVersion,minorVersion,_imp->_projectCreatedWithLowerCaseIDs && requestedByLoad); } catch (const std::exception & e1) { ///Ok try with the old Ids we had in Natron prior to 1.0 @@ -959,7 +960,7 @@ AppInstance::createNodeInternal(const QString & pluginID, } ///Now that the group is created and all nodes loaded, autoconnect the group like other nodes. - onGroupCreationFinished(node); + onGroupCreationFinished(node, false); } } @@ -999,7 +1000,7 @@ AppInstance::loadNode(const LoadNodeArgs & args) INT_MIN,INT_MIN, false, true, - true, + false, true, QString(), CreateNodeArgs::DefaultValuesList(), @@ -1114,7 +1115,7 @@ AppInstance::startWritersRendering(bool enableRenderStats,const std::listisOutputNode() ) { @@ -1233,7 +1234,15 @@ AppInstance::clearAllLastRenderedImages() } } - +void +AppInstance::aboutToQuit() +{ + ///Clear nodes now, not in the destructor of the project as + ///deleting nodes might reference the project. + _imp->_currentProject->closeProject(true); + _imp->_currentProject->discardAppPointer(); + +} void AppInstance::quit() @@ -1387,17 +1396,17 @@ AppInstance::getAppIDString() const } void -AppInstance::onGroupCreationFinished(const boost::shared_ptr& /*node*/) +AppInstance::onGroupCreationFinished(const boost::shared_ptr& node,bool requestedByLoad) { -// assert(node); -// if (!_imp->_currentProject->isLoadingProject()) { -// NodeGroup* isGrp = dynamic_cast(node->getLiveInstance()); -// assert(isGrp); -// if (!isGrp) { -// return; -// } -// isGrp->forceGetClipPreferencesOnAllTrees(); -// } + assert(node); + if (!_imp->_currentProject->isLoadingProject() && !requestedByLoad) { + NodeGroup* isGrp = dynamic_cast(node->getLiveInstance()); + assert(isGrp); + if (!isGrp) { + return; + } + isGrp->forceComputeInputDependentDataOnAllTrees(); + } } bool diff --git a/Engine/AppInstance.h b/Engine/AppInstance.h index 1efd38c1da..380d6c1305 100644 --- a/Engine/AppInstance.h +++ b/Engine/AppInstance.h @@ -175,10 +175,8 @@ class AppInstance virtual ~AppInstance(); - virtual void aboutToQuit() - { - } - + virtual void aboutToQuit(); + struct RenderRequest { QString writerName; int firstFrame,lastFrame; @@ -217,7 +215,6 @@ class AppInstance ///Same as createNode but used when loading a project boost::shared_ptr loadNode(const LoadNodeArgs & args); - boost::shared_ptr getNodeByFullySpecifiedName(const std::string & name) const; @@ -374,7 +371,7 @@ class AppInstance int firstFrame = INT_MIN, int lastFrame = INT_MAX); virtual void queueRedrawForAllViewers() {} - virtual void renderAllViewers() {} + virtual void renderAllViewers(bool /* canAbort*/) {} virtual void declareCurrentAppVariable_Python(); @@ -434,7 +431,7 @@ public Q_SLOTS: protected: - virtual void onGroupCreationFinished(const boost::shared_ptr& node); + virtual void onGroupCreationFinished(const boost::shared_ptr& node, bool requestedByLoad); virtual void createNodeGui(const boost::shared_ptr& /*node*/, const boost::shared_ptr& /*parentmultiinstance*/, diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 2eea4cf740..9c6a966c57 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -149,23 +149,8 @@ AppManager::load(int &argc, } initializeQApp(argc, argv); -#if defined(__NATRON_OSX__) + // set fontconfig path on all platforms if (qgetenv("FONTCONFIG_PATH").isNull()) { - // set FONTCONFIG_PATH to Natron.app/Contents/Resources/etc/fonts (required by plugins using fontconfig) - QString path = QCoreApplication::applicationDirPath() + "/../Resources/etc/fonts"; - QString pathcfg = path + "/fonts.conf"; - // Note: - // a minimalist fonts.conf file for OS X is: - // /System/Library/Fonts/Library/Fonts~/Library/Fonts - if (!QFile(pathcfg).exists()) { - qWarning() << "Fontconfig configuration file" << pathcfg << "does not exist, not setting FONTCONFIG_PATH"; - } else { - qDebug() << "Setting FONTCONFIG_PATH to" << path; - qputenv("FONTCONFIG_PATH", path.toUtf8()); - } - } -#elif defined(__NATRON_WIN32__) - if (qgetenv("FONTCONFIG_PATH").isNull()) { // set FONTCONFIG_PATH to Natron/Resources/etc/fonts (required by plugins using fontconfig) QString path = QCoreApplication::applicationDirPath() + "/../Resources/etc/fonts"; QString pathcfg = path + "/fonts.conf"; @@ -176,10 +161,13 @@ AppManager::load(int &argc, qputenv("FONTCONFIG_PATH", path.toUtf8()); } } -#endif - - initPython(argc, argv); + try { + initPython(argc, argv); + } catch (const std::runtime_error& e) { + std::cerr << e.what() << std::endl; + return false; + } _imp->idealThreadCount = QThread::idealThreadCount(); QThreadPool::globalInstance()->setExpiryTimeout(-1); //< make threads never exit on their own @@ -251,6 +239,8 @@ AppManager::~AppManager() delete qApp; } + + void AppManager::quit(AppInstance* instance) { @@ -594,7 +584,7 @@ AppManager::clearAllCaches() } for (std::map::iterator it = _imp->_appInstances.begin(); it != _imp->_appInstances.end(); ++it) { - it->second.app->renderAllViewers(); + it->second.app->renderAllViewers(true); } Project::clearAutoSavesDir(); @@ -729,59 +719,6 @@ AppManager::loadAllPlugins() /*loading ofx plugins*/ _imp->ofxHost->loadOFXPlugins( &readersMap, &writersMap); - std::vector ignoredPlugins; - _imp->_settings->populatePluginsTab(ignoredPlugins); - - ///Remove from the plug-ins the ignore plug-ins - for (std::vector::iterator it = ignoredPlugins.begin(); it != ignoredPlugins.end(); ++it) { - (*it)->setIsUserCreatable(false); - - /*PluginsMap::iterator foundId = _imp->_plugins.find((*it)->getPluginID().toStdString()); - if (foundId != _imp->_plugins.end()) { - PluginMajorsOrdered::iterator found = foundId->second.end(); - - for (PluginMajorsOrdered::iterator it2 = foundId->second.begin() ; it2 != foundId->second.end() ; ++it2) { - if (*it2 == *it) { - found = it2; - break; - } - } - if (found != foundId->second.end()) { - - ignorePlugin(*it); - ///Remove it from the readersMap and writersMap - - std::string pluginId = (*it)->getPluginID().toStdString(); - if ((*it)->isReader()) { - for (std::map > >::iterator it2 = readersMap.begin(); it2 != readersMap.end(); ++it2) { - for (std::vector< std::pair >::iterator it3 = it2->second.begin(); it3 != it2->second.end(); ++it3) { - if (it3->first == pluginId) { - it2->second.erase(it3); - break; - } - } - } - } - if ((*it)->isWriter()) { - for (std::map > >::iterator it2 = writersMap.begin(); it2 != writersMap.end(); ++it2) { - for (std::vector< std::pair >::iterator it3 = it2->second.begin(); it3 != it2->second.end(); ++it3) { - if (it3->first == pluginId) { - it2->second.erase(it3); - break; - } - } - } - - } - delete *found; - foundId->second.erase(found); - if (foundId->second.empty()) { - _imp->_plugins.erase(foundId); - } - } - }*/ - } - _imp->_settings->populateReaderPluginsAndFormats(readersMap); _imp->_settings->populateWriterPluginsAndFormats(writersMap); @@ -791,6 +728,9 @@ AppManager::loadAllPlugins() //Should be done after settings are declared loadPythonGroups(); + _imp->_settings->populatePluginsTab(); + + onAllPluginsLoaded(); } @@ -813,7 +753,7 @@ AppManager::onAllPluginsLoaded() assert(!it->second.empty()); PluginMajorsOrdered::iterator first = it->second.begin(); - if (!(*first)->getIsUserCreatable() || (*first)->getIsForInternalUseOnly()) { + if (!(*first)->getIsUserCreatable()) { continue; } @@ -825,7 +765,7 @@ AppManager::onAllPluginsLoaded() continue; } PluginMajorsOrdered::iterator other = it2->second.begin(); - if (!(*other)->getIsUserCreatable() || (*other)->getIsForInternalUseOnly()) { + if (!(*other)->getIsUserCreatable()) { continue; } @@ -870,7 +810,7 @@ AppManager::loadBuiltinNodePlugins(std::map::iterator it = grouping.begin(); it != grouping.end(); ++it) { qgrouping.push_back( it->c_str() ); } - registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), NATRON_IMAGES_PATH "backdrop_icon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), true); + registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), NATRON_IMAGES_PATH "backdrop_icon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), false); } { boost::shared_ptr node( GroupOutput::BuildEffect( boost::shared_ptr() ) ); @@ -886,7 +826,7 @@ AppManager::loadBuiltinNodePlugins(std::map::iterator it = grouping.begin(); it != grouping.end(); ++it) { qgrouping.push_back( it->c_str() ); } - registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), NATRON_IMAGES_PATH "output_icon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), true); + registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), NATRON_IMAGES_PATH "output_icon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), false); } { boost::shared_ptr node( GroupInput::BuildEffect( boost::shared_ptr() ) ); @@ -902,7 +842,7 @@ AppManager::loadBuiltinNodePlugins(std::map::iterator it = grouping.begin(); it != grouping.end(); ++it) { qgrouping.push_back( it->c_str() ); } - registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), NATRON_IMAGES_PATH "input_icon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), true); + registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), NATRON_IMAGES_PATH "input_icon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), false); } { boost::shared_ptr groupNode( NodeGroup::BuildEffect( boost::shared_ptr() ) ); @@ -918,7 +858,7 @@ AppManager::loadBuiltinNodePlugins(std::map::iterator it = grouping.begin(); it != grouping.end(); ++it) { qgrouping.push_back( it->c_str() ); } - registerPlugin(qgrouping, groupNode->getPluginID().c_str(), groupNode->getPluginLabel().c_str(), NATRON_IMAGES_PATH "group_icon.png", "", false, false, binary, false, groupNode->getMajorVersion(), groupNode->getMinorVersion(), true); + registerPlugin(qgrouping, groupNode->getPluginID().c_str(), groupNode->getPluginLabel().c_str(), NATRON_IMAGES_PATH "group_icon.png", "", false, false, binary, false, groupNode->getMajorVersion(), groupNode->getMinorVersion(), false); } { boost::shared_ptr dotNode( Dot::BuildEffect( boost::shared_ptr() ) ); @@ -934,7 +874,7 @@ AppManager::loadBuiltinNodePlugins(std::map::iterator it = grouping.begin(); it != grouping.end(); ++it) { qgrouping.push_back( it->c_str() ); } - registerPlugin(qgrouping, dotNode->getPluginID().c_str(), dotNode->getPluginLabel().c_str(), NATRON_IMAGES_PATH "dot_icon.png", "", false, false, binary, false, dotNode->getMajorVersion(), dotNode->getMinorVersion(), true); + registerPlugin(qgrouping, dotNode->getPluginID().c_str(), dotNode->getPluginLabel().c_str(), NATRON_IMAGES_PATH "dot_icon.png", "", false, false, binary, false, dotNode->getMajorVersion(), dotNode->getMinorVersion(), false); } { boost::shared_ptr node( DiskCacheNode::BuildEffect( boost::shared_ptr() ) ); @@ -950,7 +890,7 @@ AppManager::loadBuiltinNodePlugins(std::map::iterator it = grouping.begin(); it != grouping.end(); ++it) { qgrouping.push_back( it->c_str() ); } - registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), NATRON_IMAGES_PATH "diskcache_icon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), true); + registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), NATRON_IMAGES_PATH "diskcache_icon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), false); } { boost::shared_ptr node( RotoPaint::BuildEffect( boost::shared_ptr() ) ); @@ -967,7 +907,7 @@ AppManager::loadBuiltinNodePlugins(std::mapc_str() ); } registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), - NATRON_IMAGES_PATH "GroupingIcons/Set2/paint_grouping_2.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), true); + NATRON_IMAGES_PATH "GroupingIcons/Set2/paint_grouping_2.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), false); } { boost::shared_ptr node( RotoNode::BuildEffect( boost::shared_ptr() ) ); @@ -984,7 +924,7 @@ AppManager::loadBuiltinNodePlugins(std::mapc_str() ); } registerPlugin(qgrouping, node->getPluginID().c_str(), node->getPluginLabel().c_str(), - NATRON_IMAGES_PATH "rotoNodeIcon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), true); + NATRON_IMAGES_PATH "rotoNodeIcon.png", "", false, false, binary, false, node->getMajorVersion(), node->getMinorVersion(), false); } { boost::shared_ptr node( RotoSmear::BuildEffect( boost::shared_ptr() ) ); @@ -1007,7 +947,7 @@ AppManager::loadBuiltinNodePlugins(std::mapwriteToOfxLog_mt_safe(message); std::cerr << message.toStdString() << std::endl; + return false; } - + return true; } break; } } + return false; } QString @@ -1117,6 +1059,77 @@ AppManager::getAllNonOFXPluginsPaths() const return templatesSearchPath; } +typedef void (*NatronPathFunctor)(const QDir&); + +static void operateOnPathRecursive(NatronPathFunctor functor, const QDir& directory) +{ + if (!directory.exists()) { + return; + } + + functor(directory); + + QStringList subDirs = directory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (int i = 0; i < subDirs.size(); ++i) { + QDir d(directory.absolutePath() + "/" + subDirs[i]); + operateOnPathRecursive(functor,d); + } +} + +static void addToPythonPathFunctor(const QDir& directory) +{ + std::string addToPythonPath("sys.path.append(\""); + addToPythonPath += directory.absolutePath().toStdString(); + addToPythonPath += "\")\n"; + + std::string err; + bool ok = Natron::interpretPythonScript(addToPythonPath, &err, 0); + if (!ok) { + std::string message = QObject::tr("Could not add").toStdString() + ' ' + directory.absolutePath().toStdString() + ' ' + + QObject::tr("to python path").toStdString() + ": " + err; + std::cerr << message << std::endl; + appPTR->writeToOfxLog_mt_safe(message.c_str()); + } + +} + +static void findAllScriptsRecursive(const QDir& directory, + QStringList& allPlugins, + bool *foundInit, + bool *foundInitGui) +{ + if (!directory.exists()) { + return; + } + + QStringList filters; + filters << "*.py"; + QStringList files = directory.entryList(filters,QDir::Files | QDir::NoDotAndDotDot); + bool ok = findAndRunScriptFile(directory.absolutePath() + '/', files,"init.py"); + if (ok) { + *foundInit = true; + } + if (!appPTR->isBackground()) { + ok = findAndRunScriptFile(directory.absolutePath() + '/',files,"initGui.py"); + if (ok) { + *foundInitGui = true; + } + } + + for (QStringList::iterator it = files.begin(); it != files.end(); ++it) { + if (it->endsWith(".py") && *it != QString("init.py") && *it != QString("initGui.py")) { + allPlugins.push_back(directory.absolutePath() + "/" + *it); + } + } + + QStringList subDirs = directory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (int i = 0; i < subDirs.size(); ++i) { + QDir d(directory.absolutePath() + "/" + subDirs[i]); + findAllScriptsRecursive(d,allPlugins, foundInit, foundInitGui); + } + +} + void AppManager::loadPythonGroups() { @@ -1127,67 +1140,67 @@ AppManager::loadPythonGroups() QStringList templatesSearchPath = getAllNonOFXPluginsPaths(); - - - QStringList filters; - filters << "*.py"; - std::string err; QStringList allPlugins; ///For all search paths, first add the path to the python path, then run in order the init.py and initGui.py for (int i = 0; i < templatesSearchPath.size(); ++i) { - - std::string addToPythonPath("sys.path.append(\""); - addToPythonPath += templatesSearchPath[i].toStdString(); - addToPythonPath += "\")\n"; - - bool ok = interpretPythonScript(addToPythonPath, &err, 0); - assert(ok); - if (!ok) { - throw std::runtime_error("AppManager::loadPythonGroups(): interpretPythonScript("+addToPythonPath+") failed!"); - } + QDir d(templatesSearchPath[i]); + operateOnPathRecursive(&addToPythonPathFunctor,d); } - ///Also import Pyside.QtCore and Pyside.QtGui (the later only in non background mode + ///Also import Pyside.QtCore and Pyside.QtGui (the later only in non background mode) { std::string s = "import PySide.QtCore as QtCore"; bool ok = interpretPythonScript(s, &err, 0); - assert(ok); if (!ok) { - throw std::runtime_error("AppManager::loadPythonGroups(): interpretPythonScript("+s+") failed!"); + std::string message = QObject::tr("Failed to import PySide.QtCore").toStdString(); + std::cerr << message << std::endl; + appPTR->writeToOfxLog_mt_safe(message.c_str()); } } if (!isBackground()) { std::string s = "import PySide.QtGui as QtGui"; bool ok = interpretPythonScript(s, &err, 0); - assert(ok); if (!ok) { - throw std::runtime_error("AppManager::loadPythonGroups(): interpretPythonScript("+s+") failed!"); + std::string message = QObject::tr("Failed to import PySide.QtGui").toStdString(); + std::cerr << message << std::endl; + appPTR->writeToOfxLog_mt_safe(message.c_str()); } } + bool foundInit = false; + bool foundInitGui = false; for (int i = 0; i < templatesSearchPath.size(); ++i) { QDir d(templatesSearchPath[i]); - if (d.exists()) { - - QStringList files = d.entryList(filters,QDir::Files | QDir::NoDotAndDotDot); - findAndRunScriptFile(d.absolutePath() + '/', files,"init.py"); - if (!isBackground()) { - findAndRunScriptFile(d.absolutePath() + '/',files,"initGui.py"); - } - - QStringList scripts = d.entryList(QStringList(QString("*.py")),QDir::Files | QDir::NoDotAndDotDot); + findAllScriptsRecursive(d,allPlugins,&foundInit, &foundInitGui); + } + if (!foundInit) { + QString message = QObject::tr("init.py script not loaded"); + appPTR->setLoadingStatus(message); + std::cout << message.toStdString() << std::endl; + } else { + QString message = QObject::tr("init.py script loaded"); + appPTR->setLoadingStatus(message); + std::cout << message.toStdString() << std::endl; + } + + if (!appPTR->isBackground()) { + + if (!foundInitGui) { + QString message = QObject::tr("initGui.py script not loaded"); + appPTR->setLoadingStatus(message); + std::cout << message.toStdString() << std::endl; - for (QStringList::iterator it = scripts.begin(); it != scripts.end(); ++it) { - if (it->endsWith(".py") && *it != QString("init.py") && *it != QString("initGui.py")) { - allPlugins.push_back(d.absolutePath() + "/" + *it); - } - } + } else { + QString message = QObject::tr("initGui.py script loaded"); + appPTR->setLoadingStatus(message); + std::cout << message.toStdString() << std::endl; } + } // Now that init.py and InitGui.py have run, we need to set the search path again for the PyPlug @@ -1204,16 +1217,8 @@ AppManager::loadPythonGroups() //Add only paths that did not exist so far for (int i = 0; i < diffSearch.size(); ++i) { - - std::string addToPythonPath("sys.path.append(\""); - addToPythonPath += diffSearch[i].toStdString(); - addToPythonPath += "\")\n"; - - bool ok = interpretPythonScript(addToPythonPath, &err, 0); - assert(ok); - if (!ok) { - throw std::runtime_error("AppManager::loadPythonGroups(): interpretPythonScript("+addToPythonPath+") failed!"); - } + QDir d(diffSearch[i]); + operateOnPathRecursive(&addToPythonPathFunctor,d); } } @@ -1239,7 +1244,7 @@ AppManager::loadPythonGroups() if (getGroupInfos(modulePath.toStdString(),moduleName.toStdString(), &pluginID, &pluginLabel, &iconFilePath, &pluginGrouping, &pluginDescription, &version)) { qDebug() << "Loading " << moduleName; QStringList grouping = QString(pluginGrouping.c_str()).split(QChar('/')); - Natron::Plugin* p = registerPlugin(grouping, pluginID.c_str(), pluginLabel.c_str(), iconFilePath.c_str(), QString(), false, false, 0, false, version, 0, true); + Natron::Plugin* p = registerPlugin(grouping, pluginID.c_str(), pluginLabel.c_str(), iconFilePath.c_str(), QString(), false, false, 0, false, version, 0, false); p->setPythonModule(modulePath + moduleName); @@ -1260,7 +1265,7 @@ AppManager::registerPlugin(const QStringList & groups, bool mustCreateMutex, int major, int minor, - bool canBeUserCreated) + bool isDeprecated) { QMutex* pluginMutex = 0; @@ -1268,7 +1273,7 @@ AppManager::registerPlugin(const QStringList & groups, pluginMutex = new QMutex(QMutex::Recursive); } Natron::Plugin* plugin = new Natron::Plugin(binary,pluginID,pluginLabel,pluginIconPath,groupIconPath,groups,pluginMutex,major,minor, - isReader,isWriter, canBeUserCreated); + isReader,isWriter, isDeprecated); std::string stdID = pluginID.toStdString(); PluginsMap::iterator found = _imp->_plugins.find(stdID); if (found != _imp->_plugins.end()) { @@ -1476,9 +1481,10 @@ AppManager::createOFXEffect(boost::shared_ptr node, const NodeSerialization* serialization, const std::list >& paramValues, bool allowFileDialogs, - bool disableRenderScaleSupport) const + bool disableRenderScaleSupport, + bool *hasUsedFileDialog) const { - return _imp->ofxHost->createOfxEffect(node,serialization,paramValues,allowFileDialogs,disableRenderScaleSupport); + return _imp->ofxHost->createOfxEffect(node,serialization,paramValues,allowFileDialogs,disableRenderScaleSupport,hasUsedFileDialog); } void diff --git a/Engine/AppManager.h b/Engine/AppManager.h index d3ede33601..0b340fde9f 100644 --- a/Engine/AppManager.h +++ b/Engine/AppManager.h @@ -166,7 +166,8 @@ class AppManager const NodeSerialization* serialization, const std::list >& paramValues, bool allowFileDialogs, - bool disableRenderScaleSupport) const; + bool disableRenderScaleSupport, + bool *hasUsedFileDialog) const; void registerAppInstance(AppInstance* app); @@ -305,7 +306,7 @@ class AppManager bool mustCreateMutex, int major, int minor, - bool canBeUserCreated); + bool isDeprecated); bool isNCacheFilesOpenedCapped() const; size_t getNCacheFilesOpened() const; diff --git a/Engine/AppManagerPrivate.cpp b/Engine/AppManagerPrivate.cpp index 85841d4348..93a8ce512b 100644 --- a/Engine/AppManagerPrivate.cpp +++ b/Engine/AppManagerPrivate.cpp @@ -373,7 +373,6 @@ AppManagerPrivate::checkForCacheDiskStructure(const QString & cachePath) QString settingsFilePath(cachePath + QDir::separator() + "restoreFile." NATRON_CACHE_FILE_EXT); if ( !QFile::exists(settingsFilePath) ) { - qDebug() << "Disk cache empty."; cleanUpCacheDiskStructure(cachePath); return false; diff --git a/Engine/Bezier.cpp b/Engine/Bezier.cpp index dc03b91c8d..e7a661e5e5 100644 --- a/Engine/Bezier.cpp +++ b/Engine/Bezier.cpp @@ -2158,6 +2158,8 @@ Bezier::removeAnimation() } _imp->isClockwiseOriented.clear(); + + copyInternalPointsToGuiPoints(); } incrementNodesAge(); @@ -2209,6 +2211,8 @@ Bezier::moveKeyframe(int oldTime,int newTime) oldValue = 0; } _imp->isClockwiseOriented[newTime] = oldValue; + + copyInternalPointsToGuiPoints(); } incrementNodesAge(); diff --git a/Engine/Cache.h b/Engine/Cache.h index 95b79cd9f2..aa964d6315 100644 --- a/Engine/Cache.h +++ b/Engine/Cache.h @@ -657,6 +657,44 @@ class Cache } // createInternal public: + + void swapOrInsert(const EntryTypePtr& entryToBeEvicted, + const EntryTypePtr& newEntry) + { + QMutexLocker locker(&_lock); + + const typename EntryType::key_type& key = entryToBeEvicted->getKey(); + typename EntryType::hash_type hash = entryToBeEvicted->getHashKey(); + ///find a matching value in the internal memory container + CacheIterator memoryCached = _memoryCache(hash); + if (memoryCached != _memoryCache.end()) { + std::list & ret = getValueFromIterator(memoryCached); + for (typename std::list::iterator it = ret.begin(); it != ret.end(); ++it) { + if ( (*it)->getKey() == key && (*it)->getParams() == entryToBeEvicted->getParams()) { + ret.erase(it); + break; + } + } + ///Append it + ret.push_back(newEntry); + } else { + ///Look in disk cache + CacheIterator diskCached = _diskCache(hash); + if (diskCached != _diskCache.end()) { + ///Remove the old entry + std::list & ret = getValueFromIterator(diskCached); + for (typename std::list::iterator it = ret.begin(); it != ret.end(); ++it) { + if ( (*it)->getKey() == key && (*it)->getParams() == entryToBeEvicted->getParams()) { + ret.erase(it); + break; + } + } + + } + ///Insert in mem cache + _memoryCache.insert(hash, newEntry); + } + } /** diff --git a/Engine/CacheEntry.h b/Engine/CacheEntry.h index c14a1cc09e..f47f6d67b3 100644 --- a/Engine/CacheEntry.h +++ b/Engine/CacheEntry.h @@ -34,6 +34,7 @@ #include #include +#include #include #include #if !defined(Q_MOC_RUN) && !defined(SBK_RUN) @@ -505,7 +506,7 @@ class CacheEntryHelper , _cache() , _removeBackingFileBeforeDestruction(false) , _requestedStorage(eStorageModeNone) - , _entryLock(QMutex::Recursive) + , _entryLock(QReadWriteLock::Recursive) { } @@ -527,7 +528,7 @@ class CacheEntryHelper , _removeBackingFileBeforeDestruction(false) , _requestedPath(path) , _requestedStorage(storage) - , _entryLock(QMutex::Recursive) + , _entryLock(QReadWriteLock::Recursive) { } @@ -538,6 +539,11 @@ class CacheEntryHelper } deallocate(); } + + const CacheAPI* getCacheAPI() const + { + return _cache; + } void setCacheEntry(const KeyType & key, const boost::shared_ptr & params, @@ -567,12 +573,12 @@ class CacheEntryHelper { { - QMutexLocker k(&_entryLock); + QReadLocker k(&_entryLock); if (_data.isAllocated()) { return; } } - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); allocate(_params->getElementsCount(),_requestedStorage,_requestedPath); onMemoryAllocated(false); } @@ -595,7 +601,7 @@ class CacheEntryHelper assert(!_requestedPath.empty()); { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); restoreBufferFromFile(_requestedPath); @@ -670,7 +676,7 @@ class CacheEntryHelper void reOpenFileMapping() const { { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); _data.reOpenFileMapping(); } if (_cache) { @@ -687,7 +693,7 @@ class CacheEntryHelper bool dataAllocated = _data.isAllocated(); int time = getTime(); { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); _data.deallocate(); } @@ -721,7 +727,7 @@ class CacheEntryHelper **/ size_t dataSize() const { - bool got = _entryLock.tryLock(); + bool got = _entryLock.tryLockForRead(); std::size_t r = _data.size(); if (got) { _entryLock.unlock(); @@ -736,7 +742,7 @@ class CacheEntryHelper bool isAllocated() const { - QMutexLocker k(&_entryLock); + QReadLocker k(&_entryLock); return _data.isAllocated(); } @@ -752,7 +758,7 @@ class CacheEntryHelper bool isAlloc = _data.isAllocated(); bool hasRemovedFile; { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); hasRemovedFile = _data.removeAnyBackingFile(); } @@ -771,7 +777,7 @@ class CacheEntryHelper * @brief To be called when an entry is going to be removed from the cache entirely. **/ void scheduleForDestruction() { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); _removeBackingFileBeforeDestruction = true; } @@ -884,7 +890,7 @@ class CacheEntryHelper bool _removeBackingFileBeforeDestruction; std::string _requestedPath; Natron::StorageModeEnum _requestedStorage; - mutable QMutex _entryLock; + mutable QReadWriteLock _entryLock; }; } diff --git a/Engine/DiskCacheNode.cpp b/Engine/DiskCacheNode.cpp index eadd033639..2a71b1a5fa 100644 --- a/Engine/DiskCacheNode.cpp +++ b/Engine/DiskCacheNode.cpp @@ -69,7 +69,7 @@ DiskCacheNode::addSupportedBitDepth(std::list* depths } bool -DiskCacheNode::shouldCacheOutput(bool /*isFrameVaryingOrAnimated*/) const +DiskCacheNode::shouldCacheOutput(bool /*isFrameVaryingOrAnimated*/,double /*time*/, int /*view*/) const { return true; } diff --git a/Engine/DiskCacheNode.h b/Engine/DiskCacheNode.h index 7a62a86438..967331ff91 100644 --- a/Engine/DiskCacheNode.h +++ b/Engine/DiskCacheNode.h @@ -135,7 +135,7 @@ class DiskCacheNode : public Natron::OutputEffectInstance virtual Natron::StatusEnum render(const RenderActionArgs& args) OVERRIDE WARN_UNUSED_RETURN; - virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated) const OVERRIDE FINAL WARN_UNUSED_RETURN; + virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const OVERRIDE FINAL WARN_UNUSED_RETURN; boost::scoped_ptr _imp; }; diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 501ff7ad6e..4492f15648 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -299,7 +299,7 @@ EffectInstance::getRenderHash() const if ( !_imp->frameRenderArgs.hasLocalData() ) { return getHash(); } else { - ParallelRenderArgs & args = _imp->frameRenderArgs.localData(); + const ParallelRenderArgs & args = _imp->frameRenderArgs.localData(); if (!args.validArgs) { return getHash(); } else { @@ -319,7 +319,7 @@ EffectInstance::aborted() const ///No local data, we're either not rendering or calling this from a thread not controlled by Natron return false; } else { - ParallelRenderArgs & args = _imp->frameRenderArgs.localData(); + const ParallelRenderArgs & args = _imp->frameRenderArgs.localData(); if (!args.validArgs) { ///No valid args, probably not rendering return false; @@ -369,11 +369,11 @@ EffectInstance::aborted() const } // EffectInstance::aborted bool -EffectInstance::shouldCacheOutput(bool isFrameVaryingOrAnimated) const +EffectInstance::shouldCacheOutput(bool isFrameVaryingOrAnimated,double time, int view) const { boost::shared_ptr n = _node.lock(); - return n->shouldCacheOutput(isFrameVaryingOrAnimated); + return n->shouldCacheOutput(isFrameVaryingOrAnimated, time ,view); } U64 @@ -545,12 +545,17 @@ EffectInstance::getImage(int inputNb, const RenderScale & scale, const int view, const RectD *optionalBoundsParam, //!< optional region in canonical coordinates - const ImageComponents & comp, + const ImageComponents & requestComps, const Natron::ImageBitDepthEnum depth, const double par, const bool dontUpscale, RectI* roiPixel) { + if (time != time) { + // time is NaN + + return ImagePtr(); + } ///The input we want the image from EffectInstance* n = getInput(inputNb); @@ -599,11 +604,11 @@ EffectInstance::getImage(int inputNb, return ImagePtr(); } - std::list outputClipPrefComps; + /*std::list outputClipPrefComps; ImageBitDepthEnum outputDepth; getPreferredDepthAndComponents(inputNb, &outputClipPrefComps, &outputDepth); assert(outputClipPrefComps.size() >= 1); - const ImageComponents & prefComps = outputClipPrefComps.front(); + const ImageComponents & prefComps = outputClipPrefComps.front();*/ ///If optionalBounds have been set, use this for the RoI instead of the data int the TLS RectD optionalBounds; @@ -642,7 +647,7 @@ EffectInstance::getImage(int inputNb, } } else { RenderArgs & renderArgs = _imp->renderArgs.localData(); - ParallelRenderArgs & frameRenderArgs = _imp->frameRenderArgs.localData(); + const ParallelRenderArgs & frameRenderArgs = _imp->frameRenderArgs.localData(); if (!renderArgs._validArgs || !frameRenderArgs.validArgs) { if ( !retrieveGetImageDataUponFailure(time, view, scale, optionalBoundsParam, &nodeHash, &isIdentity, &inputIdentityTime, &inputNbIdentity, &duringPaintStroke, &rod, &inputsRoI, &optionalBounds) ) { @@ -743,9 +748,9 @@ EffectInstance::getImage(int inputNb, assert(attachedStroke); if (attachedStroke) { if (duringPaintStroke) { - inputImg = getNode()->getOrRenderLastStrokeImage(mipMapLevel, pixelRoI, par, prefComps, depth); + inputImg = getNode()->getOrRenderLastStrokeImage(mipMapLevel, pixelRoI, par, requestComps, depth); } else { - inputImg = roto->renderMaskFromStroke(attachedStroke, pixelRoI, prefComps, + inputImg = roto->renderMaskFromStroke(attachedStroke, pixelRoI, requestComps, time, view, depth, mipMapLevel); if ( roto->isDoingNeatRender() ) { getNode()->updateStrokeImage(inputImg); @@ -778,7 +783,7 @@ EffectInstance::getImage(int inputNb, assert(n); std::list requestedComps; - requestedComps.push_back(isMask ? maskComps : comp); + requestedComps.push_back(isMask ? maskComps : requestComps); ImageList inputImages; RenderRoIRetCode retCode = n->renderRoI(RenderRoIArgs(time, scale, @@ -789,6 +794,7 @@ EffectInstance::getImage(int inputNb, RectD(), requestedComps, depth, + true, this, inputImagesThreadLocal), &inputImages); @@ -814,9 +820,26 @@ EffectInstance::getImage(int inputNb, * instantaneous thanks to the image cache. */ +#ifdef DEBUG ///Check that the rendered image contains what we requested. - assert( (!isMask && inputImg->getComponents() == comp) || (isMask && inputImg->getComponents() == maskComps) ); + if ((!isMask && inputImg->getComponents() != requestComps) || (isMask && inputImg->getComponents() != maskComps)) { + ImageComponents cc; + if (isMask) { + cc = maskComps; + } else { + cc = requestComps; + } + qDebug() << "WARNING:"<< getNode()->getScriptName_mt_safe().c_str() << "requested" << cc.getComponentsGlobalName().c_str() << "but" << n->getScriptName_mt_safe().c_str() << "returned an image with" + << inputImg->getComponents().getComponentsGlobalName().c_str(); + std::list prefComps; + ImageBitDepthEnum depth; + n->getPreferredDepthAndComponents(-1, &prefComps, &depth); + assert(!prefComps.empty()); + qDebug() << n->getScriptName_mt_safe().c_str() << "output clip preference is" << prefComps.front().getComponentsGlobalName().c_str(); + } +#endif + if (roiPixel) { *roiPixel = pixelRoI; } @@ -841,27 +864,25 @@ EffectInstance::getImage(int inputNb, inputImg = rescaledImg; } - - - if ( prefComps.getNumComponents() != inputImg->getComponents().getNumComponents() ) { - ImagePtr remappedImg; - { - Image::ReadAccess acc = inputImg->getReadRights(); - - remappedImg.reset( new Image(prefComps, inputImg->getRoD(), inputImg->getBounds(), inputImg->getMipMapLevel(), inputImg->getPixelAspectRatio(), inputImg->getBitDepth(), false) ); - - Natron::ViewerColorSpaceEnum colorspace = getApp()->getDefaultColorSpaceForBitDepth( inputImg->getBitDepth() ); - bool unPremultIfNeeded = getOutputPremultiplication() == eImagePremultiplicationPremultiplied && - inputImg->getComponents().getNumComponents() == 4 && prefComps.getNumComponents() == 3; - inputImg->convertToFormat( inputImg->getBounds(), - colorspace, colorspace, - channelForMask, false, unPremultIfNeeded, remappedImg.get() ); - } - inputImg = remappedImg; + + + //Remap if needed + ImagePremultiplicationEnum outputPremult; + if (requestComps.isColorPlane()) { + outputPremult = n->getOutputPremultiplication(); + } else { + outputPremult = eImagePremultiplicationOpaque; } + - - if ( inputImagesThreadLocal.empty() ) { + std::list prefComps; + ImageBitDepthEnum prefDepth; + getPreferredDepthAndComponents(inputNb, &prefComps, &prefDepth); + assert(!prefComps.empty()); + + inputImg = convertPlanesFormatsIfNeeded(getApp(), inputImg, pixelRoI, prefComps.front(), prefDepth, getNode()->usesAlpha0ToConvertFromRGBToRGBA(), outputPremult, channelForMask); + + if (inputImagesThreadLocal.empty()) { ///If the effect is analysis (e.g: Tracker) there's no input images in the tread local storage, hence add it _imp->addInputImageTempPointer(inputNb, inputImg); } @@ -1476,7 +1497,6 @@ EffectInstance::allocateImagePlane(const ImageKey & key, double par, unsigned int mipmapLevel, bool renderFullScaleThenDownscale, - bool renderScaleOneUpstreamIfRenderScaleSupportDisabled, bool useDiskCache, bool createInCache, boost::shared_ptr* fullScaleImage, @@ -1487,9 +1507,29 @@ EffectInstance::allocateImagePlane(const ImageKey & key, //If we're rendering full scale and with input images at full scale, don't cache the downscale image since it is cheap to //recreate, instead cache the full-scale image - if (renderFullScaleThenDownscale && renderScaleOneUpstreamIfRenderScaleSupportDisabled) { + if (renderFullScaleThenDownscale) { downscaleImage->reset( new Natron::Image(components, rod, downscaleImageBounds, mipmapLevel, par, depth, true) ); + boost::shared_ptr upscaledImageParams = Natron::Image::makeParams(cost, + rod, + fullScaleImageBounds, + par, + 0, + isProjectFormat, + components, + depth, + framesNeeded); + + //The upscaled image will be rendered with input images at full def, it is then the best possibly rendered image so cache it! + + fullScaleImage->reset(); + getOrCreateFromCacheInternal(key, upscaledImageParams, createInCache, useDiskCache, fullScaleImage); + + if (!*fullScaleImage) { + return false; + } + } else { + ///Cache the image with the requested components instead of the remapped ones boost::shared_ptr cachedImgParams = Natron::Image::makeParams(cost, rod, @@ -1506,39 +1546,11 @@ EffectInstance::allocateImagePlane(const ImageKey & key, ///When calling allocateMemory() on the image, the cache already has the lock since it added it ///so taking this lock now ensures the image will be allocated completetly - getOrCreateFromCacheInternal(key, cachedImgParams, createInCache, useDiskCache, fullScaleImage); - if (!*fullScaleImage) { + getOrCreateFromCacheInternal(key, cachedImgParams, createInCache, useDiskCache, downscaleImage); + if (!*downscaleImage) { return false; } - - - *downscaleImage = *fullScaleImage; - } - - if (renderFullScaleThenDownscale) { - if (!renderScaleOneUpstreamIfRenderScaleSupportDisabled) { - ///The upscaled image will be rendered using input images at lower def... which means really crappy results, don't cache this image! - fullScaleImage->reset( new Natron::Image(components, rod, fullScaleImageBounds, 0, par, depth, true) ); - } else { - boost::shared_ptr upscaledImageParams = Natron::Image::makeParams(cost, - rod, - fullScaleImageBounds, - par, - 0, - isProjectFormat, - components, - depth, - framesNeeded); - - //The upscaled image will be rendered with input images at full def, it is then the best possibly rendered image so cache it! - - fullScaleImage->reset(); - getOrCreateFromCacheInternal(key, upscaledImageParams, createInCache, useDiskCache, fullScaleImage); - - if (!*fullScaleImage) { - return false; - } - } + *fullScaleImage = *downscaleImage; } return true; @@ -1650,7 +1662,6 @@ EffectInstance::tiledRenderingFunctor( TiledRenderingFunctorArgs & args, specificData, args.frameTLS, args.renderFullScaleThenDownscale, - args.renderUseScaleOneInputs, args.isSequentialRender, args.isRenderResponseToUserInteraction, args.firstFrame, @@ -1676,7 +1687,6 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, const RectToRender & rectToRender, const std::map, ParallelRenderArgs > & frameTLS, bool renderFullScaleThenDownscale, - bool renderUseScaleOneInputs, bool isSequentialRender, bool isRenderResponseToUserInteraction, int firstFrame, @@ -1697,8 +1707,6 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, { assert( !rectToRender.rect.isNull() ); - bool outputUseImage = renderFullScaleThenDownscale && renderUseScaleOneInputs; - ///Make the thread-storage live as long as the render action is called if we're in a newly launched thread in eRenderSafetyFullySafeFrame mode boost::shared_ptr scopedFrameArgs; if ( !frameTLS.empty() && ( callingThread != QThread::currentThread() ) ) { @@ -1725,7 +1733,8 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, ///Upscale the RoI to a region in the full scale image so it is in canonical coordinates RectD canonicalRectToRender; downscaledRectToRender.toCanonical(mipMapLevel, par, rod, &canonicalRectToRender); - if ( !outputUseImage && (mipMapLevel > 0) && (renderMappedMipMapLevel != mipMapLevel) ) { + if (renderFullScaleThenDownscale) { + assert(mipMapLevel > 0 && renderMappedMipMapLevel != mipMapLevel); canonicalRectToRender.toPixelEnclosing(renderMappedMipMapLevel, par, &renderMappedRectToRender); } @@ -1744,8 +1753,10 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, ///now make the preliminaries call to handle that region (getRoI etc...) so just stick with the old rect to render // check the bitmap! + + bool bitmapMarkedForRendering = false; if (frameArgs.tilesSupported) { - if (outputUseImage) { + if (renderFullScaleThenDownscale) { //The renderMappedImage is cached , read bitmap from it canonicalRectToRender.toPixelEnclosing(0, par, &downscaledRectToRender); downscaledRectToRender.intersect(firstPlaneToRender.renderMappedImage->getBounds(), &downscaledRectToRender); @@ -1754,7 +1765,8 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, #if NATRON_ENABLE_TRIMAP if (!frameArgs.canAbort && frameArgs.isRenderResponseToUserInteraction) { - downscaledRectToRender = firstPlaneToRender.renderMappedImage->getMinimalRect_trimap(downscaledRectToRender, &isBeingRenderedElseWhere); + bitmapMarkedForRendering = true; + downscaledRectToRender = firstPlaneToRender.renderMappedImage->getMinimalRectAndMarkForRendering_trimap(downscaledRectToRender, &isBeingRenderedElseWhere); } else { downscaledRectToRender = firstPlaneToRender.renderMappedImage->getMinimalRect(downscaledRectToRender); } @@ -1776,7 +1788,8 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, #if NATRON_ENABLE_TRIMAP RectI downscaledRectToRenderMinimal; if (!frameArgs.canAbort && frameArgs.isRenderResponseToUserInteraction) { - downscaledRectToRenderMinimal = firstPlaneToRender.downscaleImage->getMinimalRect_trimap(downscaledRectToRender, &isBeingRenderedElseWhere); + bitmapMarkedForRendering = true; + downscaledRectToRenderMinimal = firstPlaneToRender.downscaleImage->getMinimalRectAndMarkForRendering_trimap(downscaledRectToRender, &isBeingRenderedElseWhere); } else { downscaledRectToRenderMinimal = firstPlaneToRender.downscaleImage->getMinimalRect(downscaledRectToRender); } @@ -1786,28 +1799,15 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, assert( downscaledRectToRenderMinimal.isNull() || (renderBounds.x1 <= downscaledRectToRenderMinimal.x1 && downscaledRectToRenderMinimal.x2 <= renderBounds.x2 && renderBounds.y1 <= downscaledRectToRenderMinimal.y1 && downscaledRectToRenderMinimal.y2 <= renderBounds.y2) ); - - - if (renderFullScaleThenDownscale) { - ///If the new rect after getMinimalRect is bigger (maybe because another thread as grown the image) - ///we stick to what was requested - RectD canonicalrenderRectToRender; - if ( downscaledRectToRender.contains(downscaledRectToRenderMinimal) ) { - downscaledRectToRenderMinimal.toCanonical(mipMapLevel, par, rod, &canonicalrenderRectToRender); - downscaledRectToRender = downscaledRectToRenderMinimal; - } else { - downscaledRectToRender.toCanonical(mipMapLevel, par, rod, &canonicalrenderRectToRender); - } - canonicalrenderRectToRender.toPixelEnclosing(0, par, &renderMappedRectToRender); - renderMappedRectToRender.intersect(firstPlaneToRender.renderMappedImage->getBounds(), &renderMappedRectToRender); - } else { - ///If the new rect after getMinimalRect is bigger (maybe because another thread as grown the image) - ///we stick to what was requested - if ( downscaledRectToRender.contains(downscaledRectToRenderMinimal) ) { - downscaledRectToRender = downscaledRectToRenderMinimal; - renderMappedRectToRender = downscaledRectToRender; - } + + + ///If the new rect after getMinimalRect is bigger (maybe because another thread as grown the image) + ///we stick to what was requested + if ( downscaledRectToRender.contains(downscaledRectToRenderMinimal) ) { + downscaledRectToRender = downscaledRectToRenderMinimal; + renderMappedRectToRender = downscaledRectToRender; } + } } // tilesSupported ///It might have been already rendered now @@ -1871,7 +1871,6 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, it != rectToRender.imgs.end(); ++it) { for (ImageList::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { - assert(outputUseImage || (*it2)->getMipMapLevel() == mipMapLevel); const RectD & srcRodCanonical = (*it2)->getRoD(); RectI srcBounds; srcRodCanonical.toPixelEnclosing( (*it2)->getMipMapLevel(), (*it2)->getPixelAspectRatio(), &srcBounds ); // compute srcRod at level 0 @@ -1935,12 +1934,12 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, rectToRender.identityTime, rectToRender.identityInput, renderFullScaleThenDownscale, - renderUseScaleOneInputs, isSequentialRender, isRenderResponseToUserInteraction, renderMappedRectToRender, downscaledRectToRender, byPassCache, + bitmapMarkedForRendering, outputClipPrefDepth, outputClipPrefsComps, processChannels, @@ -1967,12 +1966,12 @@ EffectInstance::renderHandler(RenderArgs & args, double identityTime, Natron::EffectInstance* identityInput, bool renderFullScaleThenDownscale, - bool renderUseScaleOneInputs, bool isSequentialRender, bool isRenderResponseToUserInteraction, const RectI & renderMappedRectToRender, const RectI & downscaledRectToRender, bool byPassCache, + bool bitmapMarkedForRendering, Natron::ImageBitDepthEnum outputClipPrefDepth, const std::list & outputClipPrefsComps, bool* processChannels, @@ -1999,7 +1998,6 @@ EffectInstance::renderHandler(RenderArgs & args, renderBounds.y1 <= renderMappedRectToRender.y1 && renderMappedRectToRender.y2 <= renderBounds.y2); # endif - bool outputUseImage = renderUseScaleOneInputs && renderFullScaleThenDownscale; RenderActionArgs actionArgs; actionArgs.byPassCache = byPassCache; for (int i = 0; i < 4; ++i) { @@ -2034,10 +2032,11 @@ EffectInstance::renderHandler(RenderArgs & args, RectD(), comps, outputClipPrefDepth, + false, this); if (!identityInput) { for (std::map::iterator it = planes.planes.begin(); it != planes.planes.end(); ++it) { - if (outputUseImage) { + if (renderFullScaleThenDownscale) { it->second.fullscaleImage->fillZero(downscaledRectToRender); it->second.fullscaleImage->markForRendered(downscaledRectToRender); } else { @@ -2058,7 +2057,7 @@ EffectInstance::renderHandler(RenderArgs & args, return eRenderingFunctorRetFailed; } else if ( identityPlanes.empty() ) { for (std::map::iterator it = planes.planes.begin(); it != planes.planes.end(); ++it) { - if (outputUseImage) { + if (renderFullScaleThenDownscale) { it->second.fullscaleImage->fillZero(downscaledRectToRender); it->second.fullscaleImage->markForRendered(downscaledRectToRender); } else { @@ -2076,7 +2075,7 @@ EffectInstance::renderHandler(RenderArgs & args, ImageList::iterator idIt = identityPlanes.begin(); for (std::map::iterator it = planes.planes.begin(); it != planes.planes.end(); ++it, ++idIt) { - if ( outputUseImage && ( (*idIt)->getMipMapLevel() > it->second.fullscaleImage->getMipMapLevel() ) ) { + if ( renderFullScaleThenDownscale && ( (*idIt)->getMipMapLevel() > it->second.fullscaleImage->getMipMapLevel() ) ) { if ( !(*idIt)->getBounds().contains(downscaledRectToRender) ) { ///Fill the RoI with 0's as the identity input image might have bounds contained into the RoI it->second.fullscaleImage->fillZero(downscaledRectToRender); @@ -2163,9 +2162,9 @@ EffectInstance::renderHandler(RenderArgs & args, #if NATRON_ENABLE_TRIMAP - if (!frameArgs.canAbort && frameArgs.isRenderResponseToUserInteraction) { + if (!bitmapMarkedForRendering && !frameArgs.canAbort && frameArgs.isRenderResponseToUserInteraction) { for (std::map::iterator it = args._outputPlanes.begin(); it != args._outputPlanes.end(); ++it) { - if (outputUseImage) { + if (renderFullScaleThenDownscale) { it->second.fullscaleImage->markForRendering(downscaledRectToRender); } else { it->second.downscaleImage->markForRendering(downscaledRectToRender); @@ -2225,7 +2224,7 @@ EffectInstance::renderHandler(RenderArgs & args, using it while it has still pixels marked to PIXEL_UNAVAILABLE, hence clear the bitmap */ for (std::map::const_iterator it = outputPlanes.begin(); it != outputPlanes.end(); ++it) { - if (outputUseImage) { + if (renderFullScaleThenDownscale) { it->second.fullscaleImage->clearBitmap(downscaledRectToRender); } else { it->second.downscaleImage->clearBitmap(downscaledRectToRender); @@ -2283,86 +2282,40 @@ EffectInstance::renderHandler(RenderArgs & args, } else { if (renderFullScaleThenDownscale) { ///copy the rectangle rendered in the full scale image to the downscaled output - - /* If we're using renderUseScaleOneInputs, the full scale image is cached. - We might have been asked to render only a portion. - Hence we're not sure that the whole part of the image will be downscaled. - Instead we do all the downscale at once at the end of renderRoI(). - If !renderUseScaleOneInputs the image is not cached. - Hence we know it will be rendered completly so it is safe to do this here and take advantage of the multi-threading.*/ - if ( (mipMapLevel != 0) && !renderUseScaleOneInputs ) { - assert(it->second.fullscaleImage != it->second.downscaleImage && it->second.renderMappedImage == it->second.fullscaleImage); - - - if ( ( it->second.downscaleImage->getComponents() != it->second.tmpImage->getComponents() ) || - ( it->second.downscaleImage->getBitDepth() != it->second.tmpImage->getBitDepth() ) ) { - /* - * BitDepth/Components conversion required as well as downscaling, do conversion to a tmp buffer - */ - ImagePtr tmp( new Image(it->second.downscaleImage->getComponents(), it->second.tmpImage->getRoD(), it->second.tmpImage->getBounds(), mipMapLevel, it->second.tmpImage->getPixelAspectRatio(), it->second.downscaleImage->getBitDepth(), false) ); - - it->second.tmpImage->convertToFormat( it->second.tmpImage->getBounds(), - getApp()->getDefaultColorSpaceForBitDepth( it->second.tmpImage->getBitDepth() ), - getApp()->getDefaultColorSpaceForBitDepth( it->second.downscaleImage->getBitDepth() ), - -1, false, unPremultRequired, tmp.get() ); - tmp->downscaleMipMap( it->second.tmpImage->getRoD(), - actionArgs.roi, 0, mipMapLevel, false, it->second.downscaleImage.get() ); - } else { - /* - * Downscaling required only - */ - it->second.tmpImage->downscaleMipMap( it->second.tmpImage->getRoD(), - actionArgs.roi, 0, mipMapLevel, false, it->second.downscaleImage.get() ); - } - - it->second.downscaleImage->copyUnProcessedChannels(downscaledRectToRender, planes.outputPremult, originalImagePremultiplication, processChannels, originalInputImage); - if (useMaskMix) { - it->second.downscaleImage->applyMaskMix(downscaledRectToRender, maskImage.get(), originalInputImage.get(), doMask, false, mix); - } - it->second.downscaleImage->markForRendered(downscaledRectToRender); - } else { // if (mipMapLevel != 0 && !renderUseScaleOneInputs) { - assert(it->second.renderMappedImage == it->second.fullscaleImage); - if (it->second.tmpImage != it->second.renderMappedImage) { - if ( ( it->second.fullscaleImage->getComponents() != it->second.tmpImage->getComponents() ) || - ( it->second.fullscaleImage->getBitDepth() != it->second.tmpImage->getBitDepth() ) ) { - /* - * BitDepth/Components conversion required - */ - - it->second.tmpImage->copyUnProcessedChannels(it->second.tmpImage->getBounds(), planes.outputPremult, originalImagePremultiplication, processChannels, originalInputImage); - if (useMaskMix) { - it->second.tmpImage->applyMaskMix(actionArgs.roi, maskImage.get(), originalInputImage.get(), doMask, false, mix); - } - it->second.tmpImage->convertToFormat( it->second.tmpImage->getBounds(), - getApp()->getDefaultColorSpaceForBitDepth( it->second.tmpImage->getBitDepth() ), - getApp()->getDefaultColorSpaceForBitDepth( it->second.fullscaleImage->getBitDepth() ), - -1, false, unPremultRequired, it->second.fullscaleImage.get() ); - } else { - /* - * No conversion required, copy to output - */ - int prefInput = getNode()->getPreferredInput(); - RectI roiPixel; - ImagePtr originalInputImageFullScale; - if (prefInput != -1) { - originalInputImageFullScale = getImage(prefInput, time, actionArgs.mappedScale, view, NULL, originalInputImage->getComponents(), originalInputImage->getBitDepth(), originalInputImage->getPixelAspectRatio(), false, &roiPixel); - } - - - if (originalInputImageFullScale) { - it->second.fullscaleImage->copyUnProcessedChannels(actionArgs.roi, planes.outputPremult, originalImagePremultiplication, processChannels, originalInputImageFullScale); - if (useMaskMix) { - ImagePtr originalMaskFullScale = getImage(getMaxInputCount() - 1, time, actionArgs.mappedScale, view, NULL, ImageComponents::getAlphaComponents(), originalInputImage->getBitDepth(), originalInputImage->getPixelAspectRatio(), false, &roiPixel); - if (originalMaskFullScale) { - it->second.fullscaleImage->applyMaskMix(actionArgs.roi, originalMaskFullScale.get(), originalInputImageFullScale.get(), doMask, false, mix); - } - } - } - it->second.fullscaleImage->pasteFrom(*it->second.tmpImage, actionArgs.roi, false); - } - } - it->second.fullscaleImage->markForRendered(actionArgs.roi); + assert(mipMapLevel != 0); + + assert(it->second.fullscaleImage != it->second.downscaleImage && it->second.renderMappedImage == it->second.fullscaleImage); + + it->second.tmpImage->copyUnProcessedChannels(renderMappedRectToRender, planes.outputPremult, originalImagePremultiplication, processChannels, originalInputImage); + if (useMaskMix) { + it->second.tmpImage->applyMaskMix(renderMappedRectToRender, maskImage.get(), originalInputImage.get(), doMask, false, mix); + } + if ( ( it->second.fullscaleImage->getComponents() != it->second.tmpImage->getComponents() ) || + ( it->second.fullscaleImage->getBitDepth() != it->second.tmpImage->getBitDepth() ) ) { + /* + * BitDepth/Components conversion required as well as downscaling, do conversion to a tmp buffer + */ + ImagePtr tmp( new Image(it->second.fullscaleImage->getComponents(), it->second.tmpImage->getRoD(), it->second.tmpImage->getBounds(), mipMapLevel, it->second.tmpImage->getPixelAspectRatio(), it->second.fullscaleImage->getBitDepth(), false) ); + + it->second.tmpImage->convertToFormat( it->second.tmpImage->getBounds(), + getApp()->getDefaultColorSpaceForBitDepth( it->second.tmpImage->getBitDepth() ), + getApp()->getDefaultColorSpaceForBitDepth( it->second.fullscaleImage->getBitDepth() ), + -1, false, unPremultRequired, tmp.get() ); + tmp->downscaleMipMap( it->second.tmpImage->getRoD(), + actionArgs.roi, 0, mipMapLevel, false, it->second.downscaleImage.get() ); + it->second.fullscaleImage->pasteFrom(*tmp, it->second.fullscaleImage->getBounds(), false); + } else { + /* + * Downscaling required only + */ + it->second.tmpImage->downscaleMipMap( it->second.tmpImage->getRoD(), + actionArgs.roi, 0, mipMapLevel, false, it->second.downscaleImage.get() ); + it->second.fullscaleImage->pasteFrom(*(it->second.tmpImage), it->second.fullscaleImage->getBounds(), false); } + + + it->second.fullscaleImage->markForRendered(renderMappedRectToRender); + } else { // if (renderFullScaleThenDownscale) { ///Copy the rectangle rendered in the downscaled image if (it->second.tmpImage != it->second.downscaleImage) { @@ -2425,10 +2378,14 @@ EffectInstance::allocateImagePlaneAndSetInThreadLocalStorage(const Natron::Image const PlaneToRender & firstPlane = args._outputPlanes.begin()->second; bool useCache = firstPlane.fullscaleImage->usesBitMap() || firstPlane.downscaleImage->usesBitMap(); + if (getNode()->getPluginID().find("uk.co.thefoundry.furnace") != std::string::npos) { + //Furnace plug-ins are bugged and do not render properly both planes, just wipe the image. + useCache = false; + } const ImagePtr & img = firstPlane.fullscaleImage->usesBitMap() ? firstPlane.fullscaleImage : firstPlane.downscaleImage; boost::shared_ptr params = img->getParams(); PlaneToRender p; - bool ok = allocateImagePlane(img->getKey(), img->getRoD(), img->getBounds(), img->getBounds(), false, params->getFramesNeeded(), plane, img->getBitDepth(), img->getPixelAspectRatio(), img->getMipMapLevel(), false, false, false, useCache, &p.fullscaleImage, &p.downscaleImage); + bool ok = allocateImagePlane(img->getKey(), args._rod, args._renderWindowPixel, args._renderWindowPixel, false, params->getFramesNeeded(), plane, img->getBitDepth(), img->getPixelAspectRatio(), img->getMipMapLevel(), false, false, useCache, &p.fullscaleImage, &p.downscaleImage); if (!ok) { return ImagePtr(); } else { @@ -2496,6 +2453,12 @@ EffectInstance::evaluate(KnobI* knob, bool isSignificant, Natron::ValueChangedReasonEnum /*reason*/) { + KnobPage* isPage = dynamic_cast(knob); + KnobGroup* isGrp = dynamic_cast(knob); + if (isGrp || isPage) { + return; + } + ////If the node is currently modifying its input, to ask for a render ////because at then end of the inputChanged handler, it will ask for a refresh ////and a rebuild of the inputs tree. @@ -2508,8 +2471,9 @@ EffectInstance::evaluate(KnobI* knob, if ( getApp()->getProject()->isLoadingProject() ) { return; } - - + + + KnobButton* button = dynamic_cast(knob); /*if this is a writer (openfx or built-in writer)*/ @@ -2524,10 +2488,10 @@ EffectInstance::evaluate(KnobI* knob, Natron::questionDialog( QObject::tr("Render").toStdString(), sequentialNode + QObject::tr(" can only " "render in sequential mode. Due to limitations in the " - "OpenFX standard that means that %1" + "OpenFX standard, %1" " will not be able " "to render all the views of the project. " - "Only the main view of the project will be rendered, you can " + "Only the main view of the project will be rendered. You can " "change the main view in the project settings. Would you like " "to continue ?").arg(NATRON_APPLICATION_NAME).toStdString(), false ); if (answer != Natron::eStandardButtonYes) { @@ -2584,6 +2548,12 @@ EffectInstance::setPersistentMessage(Natron::MessageTypeEnum type, getNode()->setPersistentMessage(type, content); } +bool +EffectInstance::hasPersistentMessage() +{ + return getNode()->hasPersistentMessage(); +} + void EffectInstance::clearPersistentMessage(bool recurse) { @@ -3077,11 +3047,7 @@ EffectInstance::isIdentity_public(bool useIdentityCache, // only set to true whe void EffectInstance::onInputChanged(int /*inputNo*/) { - if ( !getApp()->getProject()->isLoadingProject() ) { - RenderScale s; - s.x = s.y = 1.; - checkOFXClipPreferences_public(getCurrentTime(), s, kOfxChangeUserEdited, true, true); - } + } Natron::StatusEnum @@ -3230,7 +3196,13 @@ EffectInstance::getFramesNeeded_public(U64 hash, return framesNeeded; } - framesNeeded = getFramesNeeded(time, view); + try { + framesNeeded = getFramesNeeded(time, view); + } catch (std::exception &e) { + if (!hasPersistentMessage()) { // plugin may already have set a message + setPersistentMessage(Natron::eMessageTypeError, e.what()); + } + } _imp->actionsCache.setFramesNeededResult(hash, time, view, mipMapLevel, framesNeeded); return framesNeeded; @@ -3707,31 +3679,45 @@ EffectInstance::getComponentsNeededAndProduced_public(double time, ImageComponents layer; std::vector compVec; bool ok = getNode()->getUserComponents(-1, processChannels, processAllRequested, &layer); - if (ok) { - if ( !layer.isColorPlane() && (layer.getNumComponents() != 0) ) { - compVec.push_back(layer); + ImageBitDepthEnum depth; + std::list clipPrefsComps; + getPreferredDepthAndComponents(-1, &clipPrefsComps, &depth); + + if (ok && layer.getNumComponents() != 0) { + if (!layer.isColorPlane()) { + + bool found = false; + for (std::size_t i = 0; i < compVec.size(); ++i) { + if (compVec[i] == layer) { + found = true; + break; + } + } + if (!found) { + compVec.push_back(layer); + } + for (std::list::iterator it = clipPrefsComps.begin(); it != clipPrefsComps.end(); ++it) { + if (!(*it).isColorPlane()) { + compVec.push_back(*it); + } + } } else { - //Use regular clip preferences - ImageBitDepthEnum depth; - std::list components; - getPreferredDepthAndComponents(-1, &components, &depth); - for (std::list::iterator it = components.begin(); it != components.end(); ++it) { - //if (it->isColorPlane()) { + for (std::list::iterator it = clipPrefsComps.begin(); it != clipPrefsComps.end(); ++it) { compVec.push_back(*it); - //} } } + } else { - //Use regular clip preferences - ImageBitDepthEnum depth; - std::list components; - getPreferredDepthAndComponents(-1, &components, &depth); - for (std::list::iterator it = components.begin(); it != components.end(); ++it) { - //if (it->isColorPlane()) { + for (std::list::iterator it = clipPrefsComps.begin(); it != clipPrefsComps.end(); ++it) { compVec.push_back(*it); - //} } } + + + + + + comps->insert( std::make_pair(-1, compVec) ); } @@ -4192,11 +4178,6 @@ EffectInstance::getNearestNonIdentity(double time) } } -void -EffectInstance::restoreClipPreferences() -{ - setSupportsRenderScaleMaybe(eSupportsYes); -} void EffectInstance::onNodeHashChanged(U64 hash) @@ -4228,7 +4209,17 @@ EffectInstance::abortAnyEvaluation() assert(node); node->incrementKnobsAge(); std::list outputNodes; - node->hasOutputNodesConnected(&outputNodes); + + NodeGroup* isGroup = dynamic_cast(this); + if (isGroup) { + std::list inputOutputs; + isGroup->getInputsOutputs(&inputOutputs); + for (std::list::iterator it = inputOutputs.begin(); it!=inputOutputs.end();++it) { + (*it)->hasOutputNodesConnected(&outputNodes); + } + } else { + node->hasOutputNodesConnected(&outputNodes); + } for (std::list::const_iterator it = outputNodes.begin(); it != outputNodes.end(); ++it) { ViewerInstance* isViewer = dynamic_cast(*it); if (isViewer) { @@ -4351,33 +4342,6 @@ EffectInstance::getPreferredFrameRate() const return getApp()->getProjectFrameRate(); } -void -EffectInstance::refreshChannelSelectors_recursiveInternal(Natron::Node* node, - std::list & markedNodes) -{ - std::list::iterator found = std::find(markedNodes.begin(), markedNodes.end(), node); - - if ( found != markedNodes.end() ) { - return; - } - node->refreshChannelSelectors(false); - - markedNodes.push_back(node); - std::list outputs; - node->getOutputsWithGroupRedirection(outputs); - for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { - refreshChannelSelectors_recursiveInternal( (*it), markedNodes ); - } -} - -void -EffectInstance::refreshChannelSelectors_recursive() -{ - std::list markedNodes; - - refreshChannelSelectors_recursiveInternal(getNode().get(), markedNodes); -} - void EffectInstance::checkOFXClipPreferences_recursive(double time, const RenderScale & scale, diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index d471d61239..fbb198335c 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -142,45 +142,49 @@ class EffectInstance ///When called from getImage() the calling node will have already computed input images, hence the image of this node ///might already be in this list EffectInstance::InputImagesMap inputImagesList; + bool calledFromGetImage; const EffectInstance* caller; RenderRoIArgs() - : time(0) - , scale() - , mipMapLevel(0) - , view(0) - , byPassCache(false) - , roi() - , preComputedRoD() - , components() - , bitdepth(eImageBitDepthFloat) - , inputImagesList() - , caller(0) + : time(0) + , scale() + , mipMapLevel(0) + , view(0) + , byPassCache(false) + , roi() + , preComputedRoD() + , components() + , bitdepth(eImageBitDepthFloat) + , inputImagesList() + , calledFromGetImage(false) + , caller(0) { } - - RenderRoIArgs( double time_, - const RenderScale & scale_, - unsigned int mipMapLevel_, - int view_, - bool byPassCache_, - const RectI & roi_, - const RectD & preComputedRoD_, - const std::list & components_, - Natron::ImageBitDepthEnum bitdepth_, - const EffectInstance* caller, - const EffectInstance::InputImagesMap & inputImages = EffectInstance::InputImagesMap() ) - : time(time_) - , scale(scale_) - , mipMapLevel(mipMapLevel_) - , view(view_) - , byPassCache(byPassCache_) - , roi(roi_) - , preComputedRoD(preComputedRoD_) - , components(components_) - , bitdepth(bitdepth_) - , inputImagesList(inputImages) - , caller(caller) + + RenderRoIArgs(double time_, + const RenderScale & scale_, + unsigned int mipMapLevel_, + int view_, + bool byPassCache_, + const RectI & roi_, + const RectD & preComputedRoD_, + const std::list & components_, + Natron::ImageBitDepthEnum bitdepth_, + bool calledFromGetImage, + const EffectInstance* caller, + const EffectInstance::InputImagesMap & inputImages = EffectInstance::InputImagesMap() ) + : time(time_) + , scale(scale_) + , mipMapLevel(mipMapLevel_) + , view(view_) + , byPassCache(byPassCache_) + , roi(roi_) + , preComputedRoD(preComputedRoD_) + , components(components_) + , bitdepth(bitdepth_) + , inputImagesList(inputImages) + , calledFromGetImage(calledFromGetImage) + , caller(caller) { } }; @@ -673,11 +677,17 @@ class EffectInstance bool forceGetClipPrefAction, bool recurse); - void refreshChannelSelectors_recursive(); - virtual void onChannelsSelectorRefreshed() {} + virtual bool checkOFXClipPreferences(double /*time*/, + const RenderScale & /*scale*/, + const std::string & /*reason*/, + bool /*forceGetClipPrefAction*/) + { + return false; + } + protected: void checkOFXClipPreferences_recursive(double time, @@ -686,12 +696,6 @@ class EffectInstance bool forceGetClipPrefAction, std::list & markedNodes); - virtual void checkOFXClipPreferences(double /*time*/, - const RenderScale & /*scale*/, - const std::string & /*reason*/, - bool /*forceGetClipPrefAction*/) - { - } public: @@ -899,16 +903,13 @@ class EffectInstance const RenderScale & scale, const int view, const RectD *optionalBounds, //!< optional region in canonical coordinates - const Natron::ImageComponents & comp, + const Natron::ImageComponents & requestComps, const Natron::ImageBitDepthEnum depth, const double par, const bool dontUpscale, RectI* roiPixel) WARN_UNUSED_RETURN; virtual void aboutToRestoreDefaultValues() OVERRIDE FINAL; - virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated) const; - -protected: - + virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const; /** * @brief Can be derived to get the region that the plugin is capable of filling. @@ -917,8 +918,13 @@ class EffectInstance * @param isProjectFormat[out] If set to true, then rod is taken to be equal to the current project format. * In case of failure the plugin should return eStatusFailed. * @returns eStatusOK, eStatusReplyDefault, or eStatusFailed. rod is set except if return value is eStatusOK or eStatusReplyDefault. - **/ + **/ virtual Natron::StatusEnum getRegionOfDefinition(U64 hash, double time, const RenderScale & scale, int view, RectD* rod) WARN_UNUSED_RETURN; + +protected: + + + virtual void calcDefaultRegionOfDefinition(U64 hash, double time, int view, const RenderScale & scale, RectD *rod); /** @@ -1086,6 +1092,11 @@ class EffectInstance **/ void setPersistentMessage(Natron::MessageTypeEnum type, const std::string & content); + /** + * @brief Test if a persistent message is set. + **/ + bool hasPersistentMessage(); + /** * @brief Clears any message posted previously by setPersistentMessage. **/ @@ -1127,6 +1138,8 @@ class EffectInstance /// should be set during effect initialization, but may also be set by the first getRegionOfDefinition with scale != 1 that succeeds void setSupportsRenderScaleMaybe(EffectInstance::SupportsEnum s) const; + virtual bool supportsRenderQuality() const { return false; } + /** * @brief Does this effect can support multiple clips PAR ? * http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#kOfxImageEffectPropSupportsMultipleClipPARs @@ -1187,11 +1200,6 @@ class EffectInstance **/ virtual void onInputChanged(int inputNo); - /** - * @brief Called after the project has restored all nodes and their links, to set clip preferences. - **/ - virtual void restoreClipPreferences(); - /** * @brief If the plug-in calls timelineGoTo and we're during a render/instance changed action, * then all the knobs will retrieve the current time as being the one in the last render args thread-storage. @@ -1214,6 +1222,13 @@ class EffectInstance //Points to a temporary image that the plug-in will render boost::shared_ptr tmpImage; + + /* + In the event where the fullScaleImage is in the cache but we must resize it to render a portion unallocated yet and + if the render is issues directly from getImage() we swap image in cache instead of taking the write lock of fullScaleImage + */ + boost::shared_ptr cacheSwapImage; + void* originalCachedImage; /** @@ -1226,6 +1241,7 @@ class EffectInstance , downscaleImage() , renderMappedImage() , tmpImage() + , cacheSwapImage() , originalCachedImage(0) , isAllocatedOnTheFly(false) { @@ -1636,7 +1652,6 @@ class EffectInstance bool isRenderMadeInResponseToUserInteraction, U64 nodeHash, bool renderFullScaleThenDownscale, - bool useScaleOneInputImages, bool byPassCache, Natron::ImageBitDepthEnum outputClipPrefDepth, const std::list & outputClipPrefsComps, @@ -1663,6 +1678,15 @@ class EffectInstance InputImagesMap *inputImages, RoIMap* inputsRoI); + static boost::shared_ptr convertPlanesFormatsIfNeeded(const AppInstance* app, + const boost::shared_ptr& inputImage, + const RectI& roi, + const ImageComponents& targetComponents, + ImageBitDepthEnum targetDepth, + bool useAlpha0ForRGBToRGBAConversion, + ImagePremultiplicationEnum outputPremult, + int channelForAlpha); + /** * @brief Called by getImage when the thread-storage was not set by the caller thread (mostly because this is a thread that is not @@ -1694,7 +1718,6 @@ class EffectInstance double par, unsigned int mipmapLevel, bool renderFullScaleThenDownscale, - bool renderScaleOneUpstreamIfRenderScaleSupportDisabled, bool useDiskCache, bool createInCache, boost::shared_ptr* fullScaleImage, @@ -1725,7 +1748,6 @@ class EffectInstance ParallelRenderArgs frameArgs; std::map, ParallelRenderArgs > frameTLS; bool renderFullScaleThenDownscale; - bool renderUseScaleOneInputs; bool isSequentialRender; bool isRenderResponseToUserInteraction; int firstFrame; @@ -1753,7 +1775,6 @@ class EffectInstance const RectToRender & rectToRender, const std::map, ParallelRenderArgs > & frameTls, bool renderFullScaleThenDownscale, - bool renderUseScaleOneInputs, bool isSequentialRender, bool isRenderResponseToUserInteraction, int firstFrame, int lastFrame, @@ -1800,12 +1821,12 @@ class EffectInstance double identityTime, Natron::EffectInstance* identityInput, bool renderFullScaleThenDownscale, - bool renderUseScaleOneInputs, bool isSequentialRender, bool isRenderResponseToUserInteraction, const RectI & renderMappedRectToRender, const RectI & downscaledRectToRender, bool byPassCache, + bool bitmapMarkedForRendering, Natron::ImageBitDepthEnum outputClipPrefDepth, const std::list & outputClipPrefsComps, bool* processChannels, @@ -1819,10 +1840,7 @@ class EffectInstance **/ int getInputNumber(Natron::EffectInstance* inputEffect) const; -protected: - static void - refreshChannelSelectors_recursiveInternal(Natron::Node* node, - std::list & markedNodes); + }; diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 31dab7baba..19e25e1a5c 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -50,6 +50,7 @@ GCC_DIAG_UNUSED_LOCAL_TYPEDEFS_ON #include "Engine/AppManager.h" #include "Engine/BlockingBackgroundRender.h" #include "Engine/DiskCacheNode.h" +#include "Engine/Cache.h" #include "Engine/Image.h" #include "Engine/ImageParams.h" #include "Engine/KnobFile.h" @@ -185,6 +186,50 @@ optimizeRectsToRender(Natron::EffectInstance* self, } } // optimizeRectsToRender +ImagePtr +EffectInstance::convertPlanesFormatsIfNeeded(const AppInstance* app, + const ImagePtr& inputImage, + const RectI& roi, + const ImageComponents& targetComponents, + ImageBitDepthEnum targetDepth, + bool useAlpha0ForRGBToRGBAConversion, + ImagePremultiplicationEnum outputPremult, + int channelForAlpha) +{ + bool imageConversionNeeded = targetComponents.getNumComponents() != inputImage->getComponents().getNumComponents() || targetDepth != inputImage->getBitDepth(); + if (!imageConversionNeeded) { + return inputImage; + } else { + /** + * Lock the downscaled image so it cannot be resized while creating the temp image and calling convertToFormat. + **/ + Image::ReadAccess acc = inputImage->getReadRights(); + RectI bounds = inputImage->getBounds(); + + ImagePtr tmp(new Image(targetComponents, inputImage->getRoD(), bounds, inputImage->getMipMapLevel(), inputImage->getPixelAspectRatio(), targetDepth, false)); + + RectI clippedRoi; + roi.intersect(bounds, &clippedRoi); + + bool unPremultIfNeeded = outputPremult == eImagePremultiplicationPremultiplied && inputImage->getComponentsCount() == 4 && tmp->getComponentsCount() == 3; + + if (useAlpha0ForRGBToRGBAConversion) { + inputImage->convertToFormatAlpha0( clippedRoi, + app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), + app->getDefaultColorSpaceForBitDepth(targetDepth), + channelForAlpha, false, unPremultIfNeeded, tmp.get() ); + } else { + inputImage->convertToFormat( clippedRoi, + app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), + app->getDefaultColorSpaceForBitDepth(targetDepth), + channelForAlpha, false, unPremultIfNeeded, tmp.get() ); + } + + return tmp; + } + +} + EffectInstance::RenderRoIRetCode EffectInstance::renderRoI(const RenderRoIArgs & args, ImageList* outputPlanes) @@ -291,37 +336,16 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////// End get RoD /////////////////////////////////////////////////////////////// + RectI roi; - - /*We pass the 2 images (image & downscaledImage). Depending on the context we want to render in one or the other one: - If (renderFullScaleThenDownscale and renderScaleOneUpstreamIfRenderScaleSupportDisabled) - the image that is held by the cache will be 'image' and it will then be downscaled if needed. - However if the render scale is not supported but input images are not rendered at full-scale , - we don't want to cache the full-scale image because it will be low res. Instead in that case we cache the downscaled image - */ - bool useImageAsOutput; - RectI roi,upscaledRoi; - - if (renderFullScaleThenDownscale && renderScaleOneUpstreamIfRenderScaleSupportDisabled) { + if (renderFullScaleThenDownscale) { //We cache 'image', hence the RoI should be expressed in its coordinates //renderRoIInternal should check the bitmap of 'image' and not downscaledImage! RectD canonicalRoI; args.roi.toCanonical(args.mipMapLevel, par, rod, &canonicalRoI); canonicalRoI.toPixelEnclosing(0, par, &roi); - upscaledRoi = roi; - useImageAsOutput = true; } else { - //In that case the plug-in either supports render scale or doesn't support render scale but uses downscaled inputs - //renderRoIInternal should check the bitmap of downscaledImage and not 'image'! roi = args.roi; - if (!renderFullScaleThenDownscale) { - upscaledRoi = roi; - } else { - RectD canonicalRoI; - args.roi.toCanonical(args.mipMapLevel, par, rod, &canonicalRoI); - canonicalRoI.toPixelEnclosing(0, par, &upscaledRoi); - } - useImageAsOutput = false; } @@ -464,7 +488,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } } - if ( (supportsRS == eSupportsMaybe) && (renderMappedMipMapLevel != 0) ) { + if ( (supportsRS == eSupportsMaybe) && (mipMapLevel != 0) ) { // supportsRenderScaleMaybe may have changed, update it supportsRS = supportsRenderScaleMaybe(); renderFullScaleThenDownscale = true; @@ -534,7 +558,33 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, - return inputEffectIdentity->renderRoI(inputArgs, outputPlanes); + RenderRoIRetCode ret = inputEffectIdentity->renderRoI(inputArgs, outputPlanes); + if (ret == eRenderRoIRetCodeOk) { + ImageList convertedPlanes; + AppInstance* app = getApp(); + assert(inputArgs.components.size() == outputPlanes->size() || outputPlanes->empty()); + bool useAlpha0ForRGBToRGBAConversion = args.caller ? args.caller->getNode()->usesAlpha0ToConvertFromRGBToRGBA() : false; + + std::list::const_iterator compIt = args.components.begin(); + + for (ImageList::iterator it = outputPlanes->begin(); it!=outputPlanes->end(); ++it,++compIt) { + + ImagePremultiplicationEnum premult; + const ImageComponents & outComp = outputComponents.front(); + if ( outComp.isColorPlane() ) { + premult = getOutputPremultiplication(); + } else { + premult = eImagePremultiplicationOpaque; + } + + ImagePtr tmp = convertPlanesFormatsIfNeeded(app, *it, args.roi, *compIt, inputArgs.bitdepth, useAlpha0ForRGBToRGBAConversion, premult, -1); + assert(tmp); + convertedPlanes.push_back(tmp); + } + *outputPlanes = convertedPlanes; + } else { + return ret; + } } else { assert( outputPlanes->empty() ); } @@ -594,7 +644,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, ///Make sure the RoI falls within the image bounds ///Intersection will be in pixel coordinates if (frameRenderArgs.tilesSupported) { - if (useImageAsOutput) { + if (renderFullScaleThenDownscale) { if ( !roi.intersect(upscaledImageBoundsNc, &roi) ) { return eRenderRoIRetCodeOk; } @@ -609,28 +659,44 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } #ifndef NATRON_ALWAYS_ALLOCATE_FULL_IMAGE_BOUNDS ///just allocate the roi - upscaledImageBoundsNc.intersect(upscaledRoi, &upscaledImageBoundsNc); + upscaledImageBoundsNc.intersect(roi, &upscaledImageBoundsNc); downscaledImageBoundsNc.intersect(args.roi, &downscaledImageBoundsNc); #endif } else { - roi = useImageAsOutput ? upscaledImageBoundsNc : downscaledImageBoundsNc; + roi = renderFullScaleThenDownscale ? upscaledImageBoundsNc : downscaledImageBoundsNc; } const RectI & downscaledImageBounds = downscaledImageBoundsNc; const RectI & upscaledImageBounds = upscaledImageBoundsNc; RectD canonicalRoI; - if (useImageAsOutput) { + if (renderFullScaleThenDownscale) { roi.toCanonical(0, par, rod, &canonicalRoI); } else { roi.toCanonical(args.mipMapLevel, par, rod, &canonicalRoI); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////// End Compute RoI ///////////////////////////////////////////////////////////////////////// - + bool draftModeSupported = getNode()->isDraftModeUsed(); bool isFrameVaryingOrAnimated = isFrameVaryingOrAnimated_Recursive(); - bool createInCache = shouldCacheOutput(isFrameVaryingOrAnimated); - Natron::ImageKey key = Natron::Image::makeKey(getNode().get(),nodeHash, isFrameVaryingOrAnimated, args.time, args.view, frameRenderArgs.draftMode); + bool createInCache = shouldCacheOutput(isFrameVaryingOrAnimated, args.time, args.view); + Natron::ImageKey key(getNode().get(), + nodeHash, + isFrameVaryingOrAnimated, + args.time, + args.view, + 1., + draftModeSupported && frameRenderArgs.draftMode, + renderMappedMipMapLevel == 0 && args.mipMapLevel != 0 && !renderScaleOneUpstreamIfRenderScaleSupportDisabled); + Natron::ImageKey nonDraftKey(getNode().get(), + nodeHash, + isFrameVaryingOrAnimated, + args.time, + args.view, + 1., + false, + renderMappedMipMapLevel == 0 && args.mipMapLevel != 0 && !renderScaleOneUpstreamIfRenderScaleSupportDisabled); + bool useDiskCacheNode = dynamic_cast(this) != NULL; @@ -675,17 +741,25 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } } assert(components); - getImageFromCacheAndConvertIfNeeded(createInCache, useDiskCacheNode, key, renderMappedMipMapLevel, - useImageAsOutput ? &upscaledImageBounds : &downscaledImageBounds, - &rod, - args.bitdepth, *it, - outputDepth, - *components, - args.inputImagesList, - frameRenderArgs.stats, - &plane.fullscaleImage); - - + + int nLookups = draftModeSupported && frameRenderArgs.draftMode ? 2 : 1; + + for (int n = 0; n < nLookups; ++n) { + getImageFromCacheAndConvertIfNeeded(createInCache, useDiskCacheNode, n == 0 ? nonDraftKey : key, renderMappedMipMapLevel, + renderFullScaleThenDownscale ? &upscaledImageBounds : &downscaledImageBounds, + &rod, + args.bitdepth, *it, + outputDepth, + *components, + args.inputImagesList, + frameRenderArgs.stats, + &plane.fullscaleImage); + if (plane.fullscaleImage) { + break; + } + } + + if (byPassCache) { if (plane.fullscaleImage) { appPTR->removeFromNodeCache( key.getHash() ); @@ -891,13 +965,13 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, if (!frameRenderArgs.tilesSupported && !rectsLeftToRender.empty() && isPlaneCached) { ///if the effect doesn't support tiles, just render the whole rod again even though rectsLeftToRender.clear(); - rectsLeftToRender.push_back(useImageAsOutput ? upscaledImageBounds : downscaledImageBounds); + rectsLeftToRender.push_back(renderFullScaleThenDownscale ? upscaledImageBounds : downscaledImageBounds); } } else { if (frameRenderArgs.tilesSupported) { rectsLeftToRender.push_back(roi); } else { - rectsLeftToRender.push_back(useImageAsOutput ? upscaledImageBounds : downscaledImageBounds); + rectsLeftToRender.push_back(renderFullScaleThenDownscale ? upscaledImageBounds : downscaledImageBounds); } } @@ -998,7 +1072,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } RectD canonicalRoI; - if (useImageAsOutput) { + if (renderFullScaleThenDownscale) { it->rect.toCanonical(0, par, rod, &canonicalRoI); } else { it->rect.toCanonical(args.mipMapLevel, par, rod, &canonicalRoI); @@ -1074,7 +1148,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, assert(components); getImageFromCacheAndConvertIfNeeded(createInCache, useDiskCacheNode, key, renderMappedMipMapLevel, - useImageAsOutput ? &upscaledImageBounds : &downscaledImageBounds, + renderFullScaleThenDownscale ? &upscaledImageBounds : &downscaledImageBounds, &rod, args.bitdepth, it->first, outputDepth, *components, @@ -1101,7 +1175,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, if (frameRenderArgs.tilesSupported) { rectsLeftToRender.push_back(roi); } else { - rectsLeftToRender.push_back(useImageAsOutput ? upscaledImageBounds : downscaledImageBounds); + rectsLeftToRender.push_back(renderFullScaleThenDownscale ? upscaledImageBounds : downscaledImageBounds); } @@ -1125,7 +1199,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } RectD canonicalRoI; - if (useImageAsOutput) { + if (renderFullScaleThenDownscale) { it->rect.toCanonical(0, par, rod, &canonicalRoI); } else { it->rect.toCanonical(args.mipMapLevel, par, rod, &canonicalRoI); @@ -1185,7 +1259,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, if (!it->second.fullscaleImage) { ///The image is not cached - allocateImagePlane(key, rod, downscaledImageBounds, upscaledImageBounds, isProjectFormat, framesNeeded, *components, args.bitdepth, par, args.mipMapLevel, renderFullScaleThenDownscale, renderScaleOneUpstreamIfRenderScaleSupportDisabled, useDiskCacheNode, createInCache, &it->second.fullscaleImage, &it->second.downscaleImage); + allocateImagePlane(key, rod, downscaledImageBounds, upscaledImageBounds, isProjectFormat, framesNeeded, *components, args.bitdepth, par, args.mipMapLevel, renderFullScaleThenDownscale, useDiskCacheNode, createInCache, &it->second.fullscaleImage, &it->second.downscaleImage); } else { /* * There might be a situation where the RoD of the cached image @@ -1204,10 +1278,31 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, * Another thread might have allocated the same image in the cache but with another RoI, make sure * it is big enough for us, or resize it to our needs. */ - bool hasResized = it->second.fullscaleImage->ensureBounds(useImageAsOutput ? upscaledImageBounds : downscaledImageBounds, - fillGrownBoundsWithZeroes, fillGrownBoundsWithZeroes); - - + bool hasResized; + + if (args.calledFromGetImage) { + /* + * When called from EffectInstance::getImage() we must prevent from taking any write lock because + * this image probably already has a lock for read on it. To overcome the write lock, we resize in a + * separate image and then we swap the images in the cache directly, without taking the image write lock. + */ + + hasResized = it->second.fullscaleImage->copyAndResizeIfNeeded(renderFullScaleThenDownscale ? upscaledImageBounds : downscaledImageBounds,fillGrownBoundsWithZeroes, fillGrownBoundsWithZeroes,&it->second.cacheSwapImage); + if (hasResized) { + ///Work on the swapImg and then swap in the cache + ImagePtr swapImg = it->second.cacheSwapImage; + it->second.cacheSwapImage = it->second.fullscaleImage; + it->second.fullscaleImage = swapImg; + if (!renderFullScaleThenDownscale) { + it->second.downscaleImage = it->second.fullscaleImage; + } + } + } else { + hasResized = it->second.fullscaleImage->ensureBounds(renderFullScaleThenDownscale ? upscaledImageBounds : downscaledImageBounds, + fillGrownBoundsWithZeroes, fillGrownBoundsWithZeroes); + } + + /* * Note that the image has been resized and the bitmap explicitly set to 1 in the newly allocated portions (for rotopaint purpose). * We must reset it back to 0 in the last stroke tick RoD. @@ -1217,11 +1312,10 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } if ( renderFullScaleThenDownscale && (it->second.fullscaleImage->getMipMapLevel() == 0) ) { - //Allocate a downscale image that will be cheap to create - ///The upscaled image will be rendered using input images at lower def... which means really crappy results, don't cache this image! RectI bounds; rod.toPixelEnclosing(args.mipMapLevel, par, &bounds); it->second.downscaleImage.reset( new Natron::Image(*components, rod, downscaledImageBounds, args.mipMapLevel, it->second.fullscaleImage->getPixelAspectRatio(), outputDepth, true) ); + it->second.fullscaleImage->downscaleMipMap( rod, it->second.fullscaleImage->getBounds(), 0, args.mipMapLevel, true, it->second.downscaleImage.get() ); } } @@ -1254,7 +1348,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, ///Only use trimap system if the render cannot be aborted. if (!frameRenderArgs.canAbort && frameRenderArgs.isRenderResponseToUserInteraction) { for (std::map::iterator it = planesToRender.planes.begin(); it != planesToRender.planes.end(); ++it) { - _imp->markImageAsBeingRendered(useImageAsOutput ? it->second.fullscaleImage : it->second.downscaleImage); + _imp->markImageAsBeingRendered(renderFullScaleThenDownscale ? it->second.fullscaleImage : it->second.downscaleImage); } } #endif @@ -1314,7 +1408,6 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, frameRenderArgs.isRenderResponseToUserInteraction, nodeHash, renderFullScaleThenDownscale, - renderScaleOneUpstreamIfRenderScaleSupportDisabled, byPassCache, outputDepth, outputClipPrefComps, @@ -1332,17 +1425,17 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, for (std::map::iterator it = planesToRender.planes.begin(); it != planesToRender.planes.end(); ++it) { if (!renderAborted) { if ( (renderRetCode == eRenderRoIStatusRenderFailed) || !planesToRender.isBeingRenderedElsewhere ) { - _imp->unmarkImageAsBeingRendered(useImageAsOutput ? it->second.fullscaleImage : it->second.downscaleImage, + _imp->unmarkImageAsBeingRendered(renderFullScaleThenDownscale ? it->second.fullscaleImage : it->second.downscaleImage, renderRetCode == eRenderRoIStatusRenderFailed); } else { if ( !_imp->waitForImageBeingRenderedElsewhereAndUnmark(roi, - useImageAsOutput ? it->second.fullscaleImage : it->second.downscaleImage) ) { + renderFullScaleThenDownscale ? it->second.fullscaleImage : it->second.downscaleImage) ) { renderAborted = true; } } } else { - appPTR->removeFromNodeCache(useImageAsOutput ? it->second.fullscaleImage : it->second.downscaleImage); - _imp->unmarkImageAsBeingRendered(useImageAsOutput ? it->second.fullscaleImage : it->second.downscaleImage, true); + appPTR->removeFromNodeCache(renderFullScaleThenDownscale ? it->second.fullscaleImage : it->second.downscaleImage); + _imp->unmarkImageAsBeingRendered(renderFullScaleThenDownscale ? it->second.fullscaleImage : it->second.downscaleImage, true); return eRenderRoIRetCodeAborted; } @@ -1376,10 +1469,10 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, for (std::map::iterator it = planesToRender.planes.begin(); it != planesToRender.planes.end(); ++it) { if (!frameRenderArgs.tilesSupported) { //assert that bounds are consistent with the RoD if tiles are not supported - const RectD & srcRodCanonical = useImageAsOutput ? it->second.fullscaleImage->getRoD() : it->second.downscaleImage->getRoD(); + const RectD & srcRodCanonical = renderFullScaleThenDownscale ? it->second.fullscaleImage->getRoD() : it->second.downscaleImage->getRoD(); RectI srcBounds; - srcRodCanonical.toPixelEnclosing(useImageAsOutput ? it->second.fullscaleImage->getMipMapLevel() : it->second.downscaleImage->getMipMapLevel(), par, &srcBounds); - RectI srcRealBounds = useImageAsOutput ? it->second.fullscaleImage->getBounds() : it->second.downscaleImage->getBounds(); + srcRodCanonical.toPixelEnclosing(renderFullScaleThenDownscale ? it->second.fullscaleImage->getMipMapLevel() : it->second.downscaleImage->getMipMapLevel(), par, &srcBounds); + RectI srcRealBounds = renderFullScaleThenDownscale ? it->second.fullscaleImage->getBounds() : it->second.downscaleImage->getBounds(); assert(srcRealBounds.x1 == srcBounds.x1); assert(srcRealBounds.x2 == srcBounds.x2); assert(srcRealBounds.y1 == srcBounds.y1); @@ -1387,11 +1480,20 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } std::list restToRender; - if (useImageAsOutput) { + if (renderFullScaleThenDownscale) { it->second.fullscaleImage->getRestToRender(roi, restToRender); } else { it->second.downscaleImage->getRestToRender(roi, restToRender); } + /* + If crashing on this assert this is likely due to a bug of the Trimap system. + Most likely another thread started rendering the portion that is in restToRender but did not fill the bitmap with 1 + yet. Do not remove this assert, there should never be 2 threads running concurrently renderHandler for the same roi + on the same image. + */ + if (!restToRender.empty()) { + it->second.downscaleImage->printUnrenderedPixels(roi); + } assert( restToRender.empty() ); } } @@ -1403,10 +1505,24 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, bool useAlpha0ForRGBToRGBAConversion = args.caller ? args.caller->getNode()->usesAlpha0ToConvertFromRGBToRGBA() : false; for (std::map::iterator it = planesToRender.planes.begin(); it != planesToRender.planes.end(); ++it) { + + //If we have worked on a local swaped image, swap it in the cache + if (it->second.cacheSwapImage) { + const CacheAPI* cache = it->second.cacheSwapImage->getCacheAPI(); + const Cache* imgCache = dynamic_cast*>(cache); + if (imgCache) { + Cache* ccImgCache = const_cast*>(imgCache); + assert(ccImgCache); + ccImgCache->swapOrInsert(it->second.cacheSwapImage, it->second.fullscaleImage); + } + } + //We have to return the downscale image, so make sure it has been computed - if ( (renderRetCode != eRenderRoIStatusRenderFailed) && renderFullScaleThenDownscale && renderScaleOneUpstreamIfRenderScaleSupportDisabled ) { + if ( (renderRetCode != eRenderRoIStatusRenderFailed) && + renderFullScaleThenDownscale && + it->second.fullscaleImage->getMipMapLevel() != mipMapLevel && + !hasSomethingToRender) { assert(it->second.fullscaleImage->getMipMapLevel() == 0); - roi.intersect(it->second.fullscaleImage->getBounds(), &roi); if (it->second.downscaleImage == it->second.fullscaleImage) { it->second.downscaleImage.reset( new Image(it->second.fullscaleImage->getComponents(), it->second.fullscaleImage->getRoD(), @@ -1419,36 +1535,10 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, it->second.fullscaleImage->downscaleMipMap( it->second.fullscaleImage->getRoD(), roi, 0, args.mipMapLevel, false, it->second.downscaleImage.get() ); } + ///The image might need to be converted to fit the original requested format - bool imageConversionNeeded = it->first != it->second.downscaleImage->getComponents() || args.bitdepth != it->second.downscaleImage->getBitDepth(); - - if ( imageConversionNeeded && (renderRetCode != eRenderRoIStatusRenderFailed) ) { - /** - * Lock the downscaled image so it cannot be resized while creating the temp image and calling convertToFormat. - **/ - boost::shared_ptr tmp; - { - Image::ReadAccess acc = it->second.downscaleImage->getReadRights(); - RectI bounds = it->second.downscaleImage->getBounds(); - - tmp.reset( new Image(it->first, it->second.downscaleImage->getRoD(), bounds, mipMapLevel, it->second.downscaleImage->getPixelAspectRatio(), args.bitdepth, false) ); - - bool unPremultIfNeeded = planesToRender.outputPremult == eImagePremultiplicationPremultiplied && it->second.downscaleImage->getComponentsCount() == 4 && tmp->getComponentsCount() == 3; - - if (useAlpha0ForRGBToRGBAConversion) { - it->second.downscaleImage->convertToFormatAlpha0( roi, - getApp()->getDefaultColorSpaceForBitDepth( it->second.downscaleImage->getBitDepth() ), - getApp()->getDefaultColorSpaceForBitDepth(args.bitdepth), - -1, false, unPremultIfNeeded, tmp.get() ); - } else { - it->second.downscaleImage->convertToFormat( roi, - getApp()->getDefaultColorSpaceForBitDepth( it->second.downscaleImage->getBitDepth() ), - getApp()->getDefaultColorSpaceForBitDepth(args.bitdepth), - -1, false, unPremultIfNeeded, tmp.get() ); - } - } - it->second.downscaleImage = tmp; - } + it->second.downscaleImage = convertPlanesFormatsIfNeeded(getApp(), it->second.downscaleImage, roi, it->first, args.bitdepth, useAlpha0ForRGBToRGBAConversion, planesToRender.outputPremult, -1); + assert(it->second.downscaleImage->getComponents() == it->first && it->second.downscaleImage->getBitDepth() == args.bitdepth); outputPlanes->push_back(it->second.downscaleImage); } @@ -1476,7 +1566,6 @@ EffectInstance::renderRoIInternal(double time, bool isRenderMadeInResponseToUserInteraction, U64 nodeHash, bool renderFullScaleThenDownscale, - bool useScaleOneInputImages, bool byPassCache, Natron::ImageBitDepthEnum outputClipPrefDepth, const std::list & outputClipPrefsComps, @@ -1598,7 +1687,6 @@ EffectInstance::renderRoIInternal(double time, tiledArgs.frameArgs = frameArgs; tiledArgs.frameTLS = tlsCopy; tiledArgs.renderFullScaleThenDownscale = renderFullScaleThenDownscale; - tiledArgs.renderUseScaleOneInputs = useScaleOneInputImages; tiledArgs.isRenderResponseToUserInteraction = isRenderMadeInResponseToUserInteraction; tiledArgs.firstFrame = firstFrame; tiledArgs.lastFrame = lastFrame; @@ -1657,7 +1745,7 @@ EffectInstance::renderRoIInternal(double time, } } else { for (std::list::const_iterator it = planesToRender.rectsToRender.begin(); it != planesToRender.rectsToRender.end(); ++it) { - RenderingFunctorRetEnum functorRet = tiledRenderingFunctor(currentThread, frameArgs, *it, tlsCopy, renderFullScaleThenDownscale, useScaleOneInputImages, isSequentialRender, isRenderMadeInResponseToUserInteraction, firstFrame, lastFrame, preferredInput, mipMapLevel, renderMappedMipMapLevel, rod, time, view, par, byPassCache, outputClipPrefDepth, outputClipPrefsComps, compsNeeded, processChannels, planesToRender); + RenderingFunctorRetEnum functorRet = tiledRenderingFunctor(currentThread, frameArgs, *it, tlsCopy, renderFullScaleThenDownscale, isSequentialRender, isRenderMadeInResponseToUserInteraction, firstFrame, lastFrame, preferredInput, mipMapLevel, renderMappedMipMapLevel, rod, time, view, par, byPassCache, outputClipPrefDepth, outputClipPrefsComps, compsNeeded, processChannels, planesToRender); if ( (functorRet == eRenderingFunctorRetFailed) || (functorRet == eRenderingFunctorRetAborted) ) { renderStatus = functorRet; diff --git a/Engine/Engine.pro b/Engine/Engine.pro index 04fdd8d013..e35b8238ba 100644 --- a/Engine/Engine.pro +++ b/Engine/Engine.pro @@ -20,10 +20,12 @@ TARGET = Engine TEMPLATE = lib CONFIG += staticlib CONFIG += moc -CONFIG += boost qt expat cairo python shiboken pyside +CONFIG += boost qt cairo python shiboken pyside QT += core network greaterThan(QT_MAJOR_VERSION, 4): QT += concurrent +!noexpat: CONFIG += expat + # Don't uncomment the following: pyside requires QtGui, because PySide/QtCore/pyside_qtcore_python.h includes qtextdocument.h #QT -= gui diff --git a/Engine/FrameEntry.h b/Engine/FrameEntry.h index b8c6dde267..201d5b88f5 100644 --- a/Engine/FrameEntry.h +++ b/Engine/FrameEntry.h @@ -98,13 +98,13 @@ class FrameEntry void getOriginalTiles(std::list >* ret) const { - QMutexLocker k(&_entryLock); + QReadLocker k(&_entryLock); _params->getOriginalTiles(ret); } void addOriginalTile(const boost::shared_ptr& image) { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); _params->addOriginalTile(image); } diff --git a/Engine/Image.cpp b/Engine/Image.cpp index b28f02338c..129af29a6b 100644 --- a/Engine/Image.cpp +++ b/Engine/Image.cpp @@ -591,6 +591,32 @@ Natron::Bitmap::getBitmapAt(int x, } } +#ifdef DEBUG +void +Image::printUnrenderedPixels(const RectI& roi) const +{ + if (!_useBitmap) { + return; + } + QReadLocker k(&_entryLock); + + const char* bm = _bitmap.getBitmapAt(roi.x1, roi.y1); + int roiw = roi.x2 - roi.x1; + int boundsW = _bitmap.getBounds().width(); + + for (int y = roi.y1; y < roi.y2; ++y, + bm += (boundsW - roiw)) { + for (int x = roi.x1; x < roi.x2; ++x,++bm) { + if (*bm == 0) { + qDebug() << '(' << x << ',' << y << ") = 0"; + } else if (*bm == PIXEL_UNAVAILABLE) { + qDebug() << '(' << x << ',' << y << ") = PIXEL_UNAVAILABLE"; + } + } + } +} +#endif + Image::Image(const ImageKey & key, const boost::shared_ptr& params, const Natron::CacheAPI* cache, @@ -634,7 +660,7 @@ Image::Image(const ImageComponents& components, , _useBitmap(useBitmap) { - setCacheEntry(makeKey(0,0,false,0,0, false), + setCacheEntry(makeKey(0, 0, false, 0, 0, false, false), boost::shared_ptr( new ImageParams( mipMapLevel, regionOfDefinition, par, @@ -685,7 +711,7 @@ Image::onMemoryAllocated(bool diskRestoration) void Image::setBitmapDirtyZone(const RectI& zone) { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); _bitmap.setDirtyZone(zone); } @@ -695,9 +721,10 @@ Image::makeKey(const CacheEntryHolder* holder, bool frameVaryingOrAnimated, double time, int view, - bool draftMode) + bool draftMode, + bool fullScaleWithDownscaleInputs) { - return ImageKey(holder,nodeHashKey,frameVaryingOrAnimated,time,view, 1., draftMode); + return ImageKey(holder,nodeHashKey,frameVaryingOrAnimated,time,view, 1., draftMode, fullScaleWithDownscaleInputs); } boost::shared_ptr @@ -770,11 +797,11 @@ Image::pasteFromForDepth(const Natron::Image & srcImg, assert( (getBitDepth() == eImageBitDepthByte && sizeof(PIX) == 1) || (getBitDepth() == eImageBitDepthShort && sizeof(PIX) == 2) || (getBitDepth() == eImageBitDepthFloat && sizeof(PIX) == 4) ); // NOTE: before removing the following asserts, please explain why an empty image may happen - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); - boost::shared_ptr k2; + boost::shared_ptr k2; if (takeSrcLock) { - k2.reset(new QMutexLocker(&srcImg._entryLock)); + k2.reset(new QReadLocker(&srcImg._entryLock)); } const RectI & bounds = _bounds; @@ -823,104 +850,104 @@ Image::pasteFromForDepth(const Natron::Image & srcImg, void Image::setRoD(const RectD& rod) { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); _rod = rod; _params->setRoD(rod); } -bool -Image::ensureBounds(const RectI& newBounds, bool fillWithBlackAndTransparant, bool setBitmapTo1) +void +Image::resizeInternal(const Image* srcImg, + const RectI& srcBounds, + const RectI& merge, + bool fillWithBlackAndTransparant, + bool setBitmapTo1, + bool createInCache, + boost::shared_ptr* outputImage) { - - - if (getBounds().contains(newBounds)) { - return false; + ///Allocate to resized image + if (!createInCache) { + outputImage->reset(new Image(srcImg->getComponents(), + srcImg->getRoD(), + merge, + srcImg->getMipMapLevel(), + srcImg->getPixelAspectRatio(), + srcImg->getBitDepth(), + srcImg->usesBitMap())); + } else { + boost::shared_ptr params(new ImageParams(*srcImg->getParams())); + params->setBounds(merge); + outputImage->reset(new Image(srcImg->getKey(), params, srcImg->getCacheAPI(), Natron::eStorageModeRAM,std::string())); + (*outputImage)->allocateMemory(); } + Natron::ImageBitDepthEnum depth = srcImg->getBitDepth(); - QMutexLocker k(&_entryLock); - - RectI merge = newBounds; - merge.merge(_bounds); - - - - ///Copy to a temp buffer of the good size - boost::scoped_ptr tmpImg(new Image(getComponents(), - getRoD(), - merge, - getMipMapLevel(), - getPixelAspectRatio(), - getBitDepth(), - usesBitMap())); - Natron::ImageBitDepthEnum depth = getBitDepth(); - if (fillWithBlackAndTransparant) { /* Compute the rectangles (A,B,C,D) where to set the image to 0 - AAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAA - DDDDDXXXXXXXXXXXXXXXXXXBBBBB - DDDDDXXXXXXXXXXXXXXXXXXBBBBB - DDDDDXXXXXXXXXXXXXXXXXXBBBBB - DDDDDXXXXXXXXXXXXXXXXXXBBBBB - CCCCCCCCCCCCCCCCCCCCCCCCCCCC - CCCCCCCCCCCCCCCCCCCCCCCCCCCC + AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAA + DDDDDXXXXXXXXXXXXXXXXXXBBBBB + DDDDDXXXXXXXXXXXXXXXXXXBBBBB + DDDDDXXXXXXXXXXXXXXXXXXBBBBB + DDDDDXXXXXXXXXXXXXXXXXXBBBBB + CCCCCCCCCCCCCCCCCCCCCCCCCCCC + CCCCCCCCCCCCCCCCCCCCCCCCCCCC */ RectI aRect; aRect.x1 = merge.x1; - aRect.y1 = _bounds.y2; + aRect.y1 = srcBounds.y2; aRect.y2 = merge.y2; aRect.x2 = merge.x2; RectI bRect; - bRect.x1 = _bounds.x2; - bRect.y1 = _bounds.y1; + bRect.x1 = srcBounds.x2; + bRect.y1 = srcBounds.y1; bRect.x2 = merge.x2; - bRect.y2 = _bounds.y2; + bRect.y2 = srcBounds.y2; RectI cRect; cRect.x1 = merge.x1; cRect.y1 = merge.y1; cRect.x2 = merge.x2; - cRect.y2 = _bounds.y1; + cRect.y2 = srcBounds.y1; RectI dRect; dRect.x1 = merge.x1; - dRect.y1 = _bounds.y1; - dRect.x2 = _bounds.x1; - dRect.y2 = _bounds.y2; + dRect.y1 = srcBounds.y1; + dRect.x2 = srcBounds.x1; + dRect.y2 = srcBounds.y2; - WriteAccess wacc(tmpImg.get()); - std::size_t pixelSize = getComponentsCount() * getSizeOfForBitDepth(depth); + Image::WriteAccess wacc(outputImage->get()); + std::size_t pixelSize = srcImg->getComponentsCount() * getSizeOfForBitDepth(depth); if (!aRect.isNull()) { - char* pix = (char*)tmpImg->pixelAt(aRect.x1, aRect.y1); + char* pix = (char*)wacc.pixelAt(aRect.x1, aRect.y1); assert(pix); double a = aRect.area(); std::size_t memsize = a * pixelSize; memset(pix, 0, memsize); - if (setBitmapTo1 && tmpImg->usesBitMap()) { - char* bm = (char*)tmpImg->getBitmapAt(aRect.x1, aRect.y1); + if (setBitmapTo1 && (*outputImage)->usesBitMap()) { + char* bm = wacc.bitmapAt(aRect.x1, aRect.y1); assert(bm); memset(bm, 1, a); } } if (!cRect.isNull()) { - char* pix = (char*)tmpImg->pixelAt(cRect.x1, cRect.y1); + char* pix = (char*)wacc.pixelAt(cRect.x1, cRect.y1); assert(pix); double a = cRect.area(); std::size_t memsize = a * pixelSize; memset(pix, 0, memsize); - if (setBitmapTo1 && tmpImg->usesBitMap()) { - char* bm = (char*)tmpImg->getBitmapAt(cRect.x1, cRect.y1); + if (setBitmapTo1 && (*outputImage)->usesBitMap()) { + char* bm = (char*)wacc.bitmapAt(cRect.x1, cRect.y1); assert(bm); memset(bm, 1, a); } } if (!bRect.isNull()) { - char* pix = (char*)tmpImg->pixelAt(bRect.x1, bRect.y1); + char* pix = (char*)wacc.pixelAt(bRect.x1, bRect.y1); assert(pix); int mw = merge.width(); std::size_t rowsize = mw * pixelSize; @@ -928,7 +955,7 @@ Image::ensureBounds(const RectI& newBounds, bool fillWithBlackAndTransparant, bo int bw = bRect.width(); std::size_t rectRowSize = bw * pixelSize; - char* bm = (setBitmapTo1 && tmpImg->usesBitMap()) ? tmpImg->getBitmapAt(bRect.x1, bRect.y1) : 0; + char* bm = (setBitmapTo1 && (*outputImage)->usesBitMap()) ? wacc.bitmapAt(bRect.x1, bRect.y1) : 0; for (int y = bRect.y1; y < bRect.y2; ++y, pix += rowsize) { memset(pix, 0, rectRowSize); if (bm) { @@ -938,7 +965,7 @@ Image::ensureBounds(const RectI& newBounds, bool fillWithBlackAndTransparant, bo } } if (!dRect.isNull()) { - char* pix = (char*)tmpImg->pixelAt(dRect.x1, dRect.y1); + char* pix = (char*)wacc.pixelAt(dRect.x1, dRect.y1); assert(pix); int mw = merge.width(); std::size_t rowsize = mw * pixelSize; @@ -946,7 +973,7 @@ Image::ensureBounds(const RectI& newBounds, bool fillWithBlackAndTransparant, bo int dw = dRect.width(); std::size_t rectRowSize = dw * pixelSize; - char* bm = (setBitmapTo1 && tmpImg->usesBitMap()) ? tmpImg->getBitmapAt(dRect.x1, dRect.y1) : 0; + char* bm = (setBitmapTo1 && (*outputImage)->usesBitMap()) ? wacc.bitmapAt(dRect.x1, dRect.y1) : 0; for (int y = dRect.y1; y < dRect.y2; ++y, pix += rowsize) { memset(pix, 0, rectRowSize); if (bm) { @@ -962,22 +989,59 @@ Image::ensureBounds(const RectI& newBounds, bool fillWithBlackAndTransparant, bo switch (depth) { case eImageBitDepthByte: - tmpImg->pasteFromForDepth(*this, _bounds, usesBitMap(), false); + (*outputImage)->pasteFromForDepth(*srcImg, srcBounds, srcImg->usesBitMap(), false); break; case eImageBitDepthShort: - tmpImg->pasteFromForDepth(*this, _bounds, usesBitMap(), false); + (*outputImage)->pasteFromForDepth(*srcImg, srcBounds, srcImg->usesBitMap(), false); break; case eImageBitDepthHalf: assert(false); break; case eImageBitDepthFloat: - tmpImg->pasteFromForDepth(*this, _bounds, usesBitMap(), false); + (*outputImage)->pasteFromForDepth(*srcImg, srcBounds, srcImg->usesBitMap(), false); break; case eImageBitDepthNone: break; } - + +} + +bool +Image::copyAndResizeIfNeeded(const RectI& newBounds, bool fillWithBlackAndTransparant, bool setBitmapTo1, boost::shared_ptr* output) +{ + if (getBounds().contains(newBounds)) { + return false; + } + assert(output); + + QReadLocker k(&_entryLock); + + RectI merge = newBounds; + merge.merge(_bounds); + + resizeInternal(this, _bounds, merge, fillWithBlackAndTransparant, setBitmapTo1, usesBitMap(), output); + return true; +} + +bool +Image::ensureBounds(const RectI& newBounds, bool fillWithBlackAndTransparant, bool setBitmapTo1) +{ + + + if (getBounds().contains(newBounds)) { + return false; + } + + QWriteLocker k(&_entryLock); + + RectI merge = newBounds; + merge.merge(_bounds); + + ImagePtr tmpImg; + resizeInternal(this, _bounds, merge, fillWithBlackAndTransparant, setBitmapTo1, false, &tmpImg); + + ///Change the size of the current buffer _bounds = merge; _params->setBounds(merge); @@ -1096,7 +1160,7 @@ Image::fill(const RectI & roi, float a) { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); switch ( getBitDepth() ) { case eImageBitDepthByte: @@ -1119,7 +1183,7 @@ Image::fill(const RectI & roi, void Image::fillZero(const RectI& roi) { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); RectI intersection; if (!roi.intersect(_bounds, &intersection)) { return; @@ -1155,7 +1219,7 @@ Image::fillZero(const RectI& roi) void Image::fillBoundsZero() { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); std::size_t rowSize = getComponents().getNumComponents(); switch ( getBitDepth() ) { @@ -1334,7 +1398,7 @@ Image::getPixelAspectRatio() const unsigned int Image::getRowElements() const { - QMutexLocker k(&_entryLock); + QReadLocker k(&_entryLock); return getComponentsCount() * _bounds.width(); } @@ -1358,8 +1422,8 @@ Image::halveRoIForDepth(const RectI & roi, } /// Take the lock for both bitmaps since we're about to read/write from them! - QMutexLocker k1(&output->_entryLock); - QMutexLocker k2(&_entryLock); + QWriteLocker k1(&output->_entryLock); + QReadLocker k2(&_entryLock); ///The source rectangle, intersected to this image region of definition in pixels const RectI &srcBounds = _bounds; @@ -1541,8 +1605,8 @@ Image::halve1DImageForDepth(const RectI & roi, assert( output->getComponents() == getComponents() ); /// Take the lock for both bitmaps since we're about to read/write from them! - QMutexLocker k1(&output->_entryLock); - QMutexLocker k2(&_entryLock); + QWriteLocker k1(&output->_entryLock); + QReadLocker k2(&_entryLock); const RectI & srcBounds = _bounds; @@ -1658,7 +1722,7 @@ Image::checkForNaNs(const RectI& roi) return false; } - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); unsigned int compsCount = getComponentsCount(); @@ -1715,8 +1779,8 @@ Image::upscaleMipMapForDepth(const RectI & roi, return; } - QMutexLocker k1(&output->_entryLock); - QMutexLocker k2(&_entryLock); + QWriteLocker k1(&output->_entryLock); + QReadLocker k2(&_entryLock); int srcRowSize = _bounds.width() * components; int dstRowSize = output->_bounds.width() * components; @@ -1740,13 +1804,15 @@ Image::upscaleMipMapForDepth(const RectI & roi, PIX * dstPixFirst = dstLineBatchStart; // fill the first line for (int xo = dstRoi.x1; xo < dstRoi.x2; ++xi, srcPix += components, xo += xcount, dstPixFirst += xcount * components) { - xcount = scale + xo - xi * scale; + xcount = scale - (xo - xi * scale); + xcount = std::min(xcount, dstRoi.x2 - xo); //assert(0 < xcount && xcount <= scale); // replicate srcPix as many times as necessary PIX * dstPix = dstPixFirst; //assert((srcPix-(PIX*)pixelAt(srcRoi.x1, srcRoi.y1)) % components == 0); for (int i = 0; i < xcount; ++i, dstPix += components) { assert( ( dstPix - (PIX*)output->pixelAt(dstRoi.x1, dstRoi.y1) ) % components == 0 ); + assert(dstPix >= (PIX*)output->pixelAt(xo, yo) && dstPix < (PIX*)output->pixelAt(xo, yo) + xcount * components); for (int c = 0; c < components; ++c) { dstPix[c] = srcPix[c]; } @@ -1798,8 +1864,8 @@ Image::scaleBoxForDepth(const RectI & roi, assert( (getBitDepth() == eImageBitDepthByte && sizeof(PIX) == 1) || (getBitDepth() == eImageBitDepthShort && sizeof(PIX) == 2) || (getBitDepth() == eImageBitDepthFloat && sizeof(PIX) == 4) ); - QMutexLocker k1(&output->_entryLock); - QMutexLocker k2(&_entryLock); + QWriteLocker k1(&output->_entryLock); + QReadLocker k2(&_entryLock); ///The destination rectangle const RectI & dstBounds = output->_bounds; diff --git a/Engine/Image.h b/Engine/Image.h index 73bd83ba7f..3594747409 100644 --- a/Engine/Image.h +++ b/Engine/Image.h @@ -203,7 +203,8 @@ namespace Natron { bool frameVaryingOrAnimated, double time, int view, - bool draftMode); + bool draftMode, + bool fullScaleWithDownscaleInputs); static boost::shared_ptr makeParams(int cost, const RectD & rod, // the image rod in canonical coordinates const double par, @@ -222,22 +223,41 @@ namespace Natron { const ImageComponents& components, Natron::ImageBitDepthEnum bitdepth, const std::map > >& framesNeeded); - - - // boost::shared_ptr getParams() const WARN_UNUSED_RETURN; - + + + // boost::shared_ptr getParams() const WARN_UNUSED_RETURN; + /** * @brief Resizes this image so it contains newBounds, copying all the content of the current bounds of the image into - * a new buffer. This is not thread-safe and should be called only while under an ImageLocker + * a new buffer. This is not thread-safe and should be called only while under an ImageLocker **/ bool ensureBounds(const RectI& newBounds, bool fillWithBlackAndTransparant = false, bool setBitmapTo1 = false); /** - * @brief Returns the region of definition of the image in canonical coordinates. It doesn't have any - * scale applied to it. In order to return the true pixel data window you must call getBounds() - * WARNING: this is NOT the same definition as in OpenFX, where the Image RoD is always in pixels. - **/ + * @brief Same as ensureBounds() except that if a resize is needed, it will do the resize in the output image instead to avoid taking the + * write lock from this image. + **/ + bool copyAndResizeIfNeeded(const RectI& newBounds, bool fillWithBlackAndTransparant, bool setBitmapTo1, boost::shared_ptr* output); + + private: + + static void resizeInternal(const Image* srcImg, + const RectI& srcBounds, + const RectI& merge, + bool fillWithBlackAndTransparant, + bool setBitmapTo1, + bool createInCache, + boost::shared_ptr* outputImage); + + + public: + + /** + * @brief Returns the region of definition of the image in canonical coordinates. It doesn't have any + * scale applied to it. In order to return the true pixel data window you must call getBounds() + * WARNING: this is NOT the same definition as in OpenFX, where the Image RoD is always in pixels. + **/ const RectD & getRoD() const { return _rod; @@ -248,22 +268,22 @@ namespace Natron { * to prevent an assert from triggering. **/ void setRoD(const RectD& rod); - + /** - * @brief Returns the bounds where data is in the image. - * This is equivalent to calling getRoD().mipMapLevel(getMipMapLevel()); - * but slightly faster since it is stored as a member of the image. - **/ + * @brief Returns the bounds where data is in the image. + * This is equivalent to calling getRoD().mipMapLevel(getMipMapLevel()); + * but slightly faster since it is stored as a member of the image. + **/ RectI getBounds() const { - QMutexLocker k(&_entryLock); + QReadLocker k(&_entryLock); return _bounds; }; virtual size_t size() const OVERRIDE FINAL { std::size_t dt = dataSize(); - bool got = _entryLock.tryLock(); + bool got = _entryLock.tryLockForRead(); dt += _bitmap.getBounds().area(); if (got) { _entryLock.unlock(); @@ -365,6 +385,12 @@ namespace Natron { assert(img); return img->pixelAt(x, y); } + + const char* bitmapAt(int x, int y) const + { + assert(img); + return img->getBitmapAt(x, y); + } }; /** @@ -404,6 +430,12 @@ namespace Natron { { return img->pixelAt(x, y); } + + char* bitmapAt(int x, int y) const + { + assert(img); + return img->getBitmapAt(x, y); + } }; ReadAccess getReadRights() const @@ -445,18 +477,29 @@ namespace Natron { const unsigned char* pixelAt(int x,int y) const; /** - * @brief Locks the image for read/write access. In the past we used a ReadWriteLock, however we cannot use this - * anymore: The lock for read is taken when a plugin attempts to fetch an image from a source clip. But if the plug-ins + * @brief Locks the image for read/write access. + * There can be a deadlock situation in the following situation: + * The lock for read is taken when a plugin attempts to + * fetch an image from a source clip. But if the plug-in * fetches twice the very same image (likely if this is a tracker on the last frame for example) then it will deadlock - * if a clip is not asking for the exact same region and thus there is something left to render. + * if a clip is not asking for the exact same region because it is likely there is something left to render. + * + * We detect such calls and ensure that there are no deadlock. + * + * + * NB: We cannot also rely on a mutex based implementation since it can lead to a deadlock in the following situation: + * Thread A requests image at time T and locks it + * Thread B requests image at time T+1 and locks it + * Thread A requests image at time T+1 and hangs + * Thread B requests image at time T and hangs **/ void lockForRead() const { - _entryLock.lock(); + _entryLock.lockForRead(); } void lockForWrite() const { - _entryLock.lock(); + _entryLock.lockForWrite(); } void unlock() const @@ -546,7 +589,7 @@ namespace Natron { if (!_useBitmap) { return; } - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); _bitmap.minimalNonMarkedRects_trimap(regionOfInterest, ret, isBeingRenderedElsewhere); } #endif @@ -555,7 +598,7 @@ namespace Natron { if (!_useBitmap) { return ; } - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); _bitmap.minimalNonMarkedRects(regionOfInterest,ret); } @@ -565,7 +608,7 @@ namespace Natron { if (!_useBitmap) { return regionOfInterest; } - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); return _bitmap.minimalNonMarkedBbox_trimap(regionOfInterest,isBeingRenderedElsewhere); } #endif @@ -574,16 +617,32 @@ namespace Natron { if (!_useBitmap) { return regionOfInterest; } - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); return _bitmap.minimalNonMarkedBbox(regionOfInterest); } - + +#if NATRON_ENABLE_TRIMAP + RectI getMinimalRectAndMarkForRendering_trimap(const RectI & regionOfInterest,bool* isBeingRenderedElsewhere) + { + if (!_useBitmap) { + return regionOfInterest; + } + RectI ret; + { + QReadLocker locker(&_entryLock); + ret = _bitmap.minimalNonMarkedBbox_trimap(regionOfInterest,isBeingRenderedElsewhere); + } + markForRendering(ret); + return ret; + } +#endif + void markForRendered(const RectI & roi) { if (!_useBitmap) { return; } - QMutexLocker locker(&_entryLock); + QWriteLocker locker(&_entryLock); RectI intersection; _bounds.intersect(roi, &intersection); _bitmap.markForRendered(intersection); @@ -596,7 +655,7 @@ namespace Natron { if (!_useBitmap) { return; } - QMutexLocker locker(&_entryLock); + QWriteLocker locker(&_entryLock); RectI intersection; _bounds.intersect(roi, &intersection); _bitmap.markForRendering(intersection); @@ -608,12 +667,16 @@ namespace Natron { if (!_useBitmap) { return; } - QMutexLocker locker(&_entryLock); + QWriteLocker locker(&_entryLock); RectI intersection; _bounds.intersect(roi, &intersection); _bitmap.clear(intersection); } +#ifdef DEBUG + void printUnrenderedPixels(const RectI& roi) const; +#endif + /** * @brief Fills the image with the given colour. If the image components * are not RGBA it will ignore the unsupported components. diff --git a/Engine/ImageConvert.cpp b/Engine/ImageConvert.cpp index 0bedcc0184..7e4b0b8ed5 100644 --- a/Engine/ImageConvert.cpp +++ b/Engine/ImageConvert.cpp @@ -698,8 +698,8 @@ Image::convertToFormatCommon(const RectI & renderWindow, Natron::Image* dstImg) const { - QMutexLocker k(&dstImg->_entryLock); - QMutexLocker k2(&_entryLock); + QWriteLocker k(&dstImg->_entryLock); + QReadLocker k2(&_entryLock); assert( _bounds.contains(renderWindow) && dstImg->_bounds.contains(renderWindow) ); diff --git a/Engine/ImageCopyChannels.cpp b/Engine/ImageCopyChannels.cpp index a0e851611d..5651569951 100644 --- a/Engine/ImageCopyChannels.cpp +++ b/Engine/ImageCopyChannels.cpp @@ -411,7 +411,7 @@ Image::copyUnProcessedChannels(const RectI& roi, return; } - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); assert(!originalImage || getBitDepth() == originalImage->getBitDepth()); diff --git a/Engine/ImageKey.cpp b/Engine/ImageKey.cpp index 507716859d..3d13528ca1 100644 --- a/Engine/ImageKey.cpp +++ b/Engine/ImageKey.cpp @@ -35,6 +35,7 @@ ImageKey::ImageKey() , _view(0) , _pixelAspect(1) , _draftMode(false) +, _fullScaleWithDownscaleInputs(false) { } @@ -45,7 +46,8 @@ ImageKey::ImageKey(const CacheEntryHolder* holder, //unsigned int mipMapLevel, //< Store different mipmapLevels under the same key int view, double pixelAspect, - bool draftMode) + bool draftMode, + bool fullScaleWithDownscaleInputs) : KeyHelper(holder) , _nodeHashKey(nodeHashKey) , _frameVaryingOrAnimated(frameVaryingOrAnimated) @@ -53,6 +55,7 @@ ImageKey::ImageKey(const CacheEntryHolder* holder, , _view(view) , _pixelAspect(pixelAspect) , _draftMode(draftMode) +, _fullScaleWithDownscaleInputs(fullScaleWithDownscaleInputs) { } @@ -66,6 +69,7 @@ ImageKey::fillHash(Hash64* hash) const hash->append(_view); hash->append(_pixelAspect); hash->append(_draftMode); + hash->append(_fullScaleWithDownscaleInputs); } bool @@ -76,12 +80,14 @@ ImageKey::operator==(const ImageKey & other) const _time == other._time && _view == other._view && _pixelAspect == other._pixelAspect && - _draftMode == other._draftMode; + _draftMode == other._draftMode && + _fullScaleWithDownscaleInputs == other._fullScaleWithDownscaleInputs; } else { return _nodeHashKey == other._nodeHashKey && _view == other._view && _pixelAspect == other._pixelAspect && - _draftMode == other._draftMode; + _draftMode == other._draftMode && + _fullScaleWithDownscaleInputs == other._fullScaleWithDownscaleInputs; } } diff --git a/Engine/ImageKey.h b/Engine/ImageKey.h index 749e676f5e..61cb467d0d 100644 --- a/Engine/ImageKey.h +++ b/Engine/ImageKey.h @@ -39,6 +39,10 @@ class ImageKey int _view; double _pixelAspect; bool _draftMode; + + //When true that means the image has been computed based on inputs using a mipmaplevel != 0 + //hence it is probably not very high quality, even though the mipmap level is 0 + bool _fullScaleWithDownscaleInputs; ImageKey(); @@ -48,7 +52,8 @@ class ImageKey double time, int view, double pixelAspect, - bool draftMode); + bool draftMode, + bool fullScaleWithDownscaleInputs); void fillHash(Hash64* hash) const; diff --git a/Engine/ImageMaskMix.cpp b/Engine/ImageMaskMix.cpp index 3ea5e862b8..c83009bfb4 100644 --- a/Engine/ImageMaskMix.cpp +++ b/Engine/ImageMaskMix.cpp @@ -204,14 +204,14 @@ Image::applyMaskMix(const RectI& roi, return; } - QMutexLocker k(&_entryLock); - boost::shared_ptr originalLock; - boost::shared_ptr maskLock; + QWriteLocker k(&_entryLock); + boost::shared_ptr originalLock; + boost::shared_ptr maskLock; if (originalImg) { - originalLock.reset(new QMutexLocker(&originalImg->_entryLock)); + originalLock.reset(new QReadLocker(&originalImg->_entryLock)); } if (maskImg) { - maskLock.reset(new QMutexLocker(&maskImg->_entryLock)); + maskLock.reset(new QReadLocker(&maskImg->_entryLock)); } RectI realRoI; roi.intersect(_bounds, &realRoI); diff --git a/Engine/Knob.cpp b/Engine/Knob.cpp index fd5a14ee88..cbef12fbf7 100644 --- a/Engine/Knob.cpp +++ b/Engine/Knob.cpp @@ -698,7 +698,7 @@ KnobHelper::deleteValueAtTime(Natron::CurveChangeReason curveChangeReason, } checkAnimationLevel(dimension); guiCurveCloneInternalCurve(curveChangeReason,dimension, reason); - evaluateValueChange(dimension,reason); + evaluateValueChange(dimension,time,reason); } else { if (_signalSlotHandler) { _signalSlotHandler->s_redrawGuiCurve(curveChangeReason, dimension); @@ -788,7 +788,7 @@ KnobHelper::moveValueAtTime(Natron::CurveChangeReason reason, int time,int dimen } if (!useGuiCurve) { - evaluateValueChange(dimension, Natron::eValueChangedReasonPluginEdited); + evaluateValueChange(dimension, newX, Natron::eValueChangedReasonPluginEdited); //We've set the internal curve, so synchronize the gui curve to the internal curve //the s_redrawGuiCurve signal will be emitted @@ -881,7 +881,7 @@ KnobHelper::transformValueAtTime(Natron::CurveChangeReason curveChangeReason, in } if (!useGuiCurve) { - evaluateValueChange(dimension, Natron::eValueChangedReasonPluginEdited); + evaluateValueChange(dimension,p.x, Natron::eValueChangedReasonPluginEdited); guiCurveCloneInternalCurve(curveChangeReason, dimension , eValueChangedReasonPluginEdited); } return true; @@ -913,7 +913,7 @@ KnobHelper::cloneCurve(int dimension,const Curve& curve) animationRemoved_virtual(dimension); thisCurve->clone(curve); if (!useGuiCurve) { - evaluateValueChange(dimension, Natron::eValueChangedReasonPluginEdited); + evaluateValueChange(dimension, getCurrentTime(), Natron::eValueChangedReasonPluginEdited); guiCurveCloneInternalCurve(Natron::eCurveChangeReasonInternal,dimension, eValueChangedReasonPluginEdited); } @@ -963,7 +963,7 @@ KnobHelper::setInterpolationAtTime(Natron::CurveChangeReason reason,int dimensio *newKey = curve->setKeyFrameInterpolation(interpolation, keyIndex); if (!useGuiCurve) { - evaluateValueChange(dimension, Natron::eValueChangedReasonPluginEdited); + evaluateValueChange(dimension, time, Natron::eValueChangedReasonPluginEdited); guiCurveCloneInternalCurve(reason, dimension, eValueChangedReasonPluginEdited); } else { if (_signalSlotHandler) { @@ -1014,7 +1014,7 @@ KnobHelper::moveDerivativesAtTime(Natron::CurveChangeReason reason,int dimension curve->setKeyFrameDerivatives(left, right, keyIndex); if (!useGuiCurve) { - evaluateValueChange(dimension, Natron::eValueChangedReasonPluginEdited); + evaluateValueChange(dimension, time, Natron::eValueChangedReasonPluginEdited); guiCurveCloneInternalCurve(reason, dimension, eValueChangedReasonPluginEdited); } else { if (_signalSlotHandler) { @@ -1068,7 +1068,7 @@ KnobHelper::moveDerivativeAtTime(Natron::CurveChangeReason reason,int dimension, } if (!useGuiCurve) { - evaluateValueChange(dimension, Natron::eValueChangedReasonPluginEdited); + evaluateValueChange(dimension, time, Natron::eValueChangedReasonPluginEdited); guiCurveCloneInternalCurve(reason, dimension, eValueChangedReasonPluginEdited); } else { if (_signalSlotHandler) { @@ -1134,7 +1134,7 @@ KnobHelper::removeAnimation(int dimension, if (!useGuiCurve) { //virtual portion - evaluateValueChange(dimension, reason); + evaluateValueChange(dimension, getCurrentTime(), reason); guiCurveCloneInternalCurve(Natron::eCurveChangeReasonInternal, dimension, reason); } else { if (_signalSlotHandler) { @@ -1324,6 +1324,7 @@ KnobHelper::isValueChangesBlocked() const void KnobHelper::evaluateValueChange(int dimension, + int time, Natron::ValueChangedReasonEnum reason) { @@ -1341,10 +1342,10 @@ KnobHelper::evaluateValueChange(int dimension, if ( ( app && !app->getProject()->isLoadingProject() /*&& !app->isCreatingPythonGroup()*/) || !app ) { if (_imp->holder->isEvaluationBlocked()) { - _imp->holder->appendValueChange(this,reason); + _imp->holder->appendValueChange(this, time, reason); } else { _imp->holder->beginChanges(); - _imp->holder->appendValueChange(this,reason); + _imp->holder->appendValueChange(this,time, reason); _imp->holder->endChanges(); } @@ -1353,8 +1354,10 @@ KnobHelper::evaluateValueChange(int dimension, if (!guiFrozen && _signalSlotHandler) { computeHasModifications(); - _signalSlotHandler->s_valueChanged(dimension,(int)reason); - _signalSlotHandler->s_updateDependencies(dimension,(int)reason); + if (!app || time == app->getTimeLine()->currentFrame()) { + _signalSlotHandler->s_valueChanged(dimension,(int)reason); + _signalSlotHandler->s_updateDependencies(dimension,(int)reason); + } checkAnimationLevel(dimension); } } @@ -2271,6 +2274,19 @@ KnobHelper::getIsSecret() const return _imp->IsSecret; } +bool +KnobHelper::getIsSecretRecursive() const +{ + if (getIsSecret()) { + return true; + } + boost::shared_ptr parent = getParentKnob(); + if (parent) { + return parent->getIsSecretRecursive(); + } + return false; +} + bool KnobHelper::getDefaultIsSecret() const { @@ -2313,8 +2329,13 @@ KnobHelper::setDirty(bool d) void KnobHelper::setEvaluateOnChange(bool b) { - QMutexLocker k(&_imp->evaluateOnChangeMutex); - _imp->evaluateOnChange = b; + { + QMutexLocker k(&_imp->evaluateOnChangeMutex); + _imp->evaluateOnChange = b; + } + if (_signalSlotHandler) { + _signalSlotHandler->s_evaluateOnChangeChanged(b); + } } bool @@ -2531,7 +2552,7 @@ KnobHelper::slaveTo(int dimension, } if (hasChanged) { - evaluateValueChange(dimension, reason); + evaluateValueChange(dimension, getCurrentTime(), reason); } ///Register this as a listener of the master @@ -2603,12 +2624,16 @@ void KnobHelper::setAnimationLevel(int dimension, Natron::AnimationLevelEnum level) { + bool changed = false; { QMutexLocker l(&_imp->animationLevelMutex); assert( dimension < (int)_imp->animationLevel.size() ); - _imp->animationLevel[dimension] = level; + if (_imp->animationLevel[dimension] != level) { + changed = true; + _imp->animationLevel[dimension] = level; + } } - if ( _signalSlotHandler && _imp->gui && !_imp->gui->isGuiFrozenForPlayback() ) { + if ( changed && _signalSlotHandler && _imp->gui && !_imp->gui->isGuiFrozenForPlayback() ) { if (getExpression(dimension).empty()) { _signalSlotHandler->s_animationLevelChanged( dimension,(int)level ); } @@ -2670,7 +2695,7 @@ KnobHelper::deleteAnimationConditional(int time,int dimension,Natron::ValueChang } checkAnimationLevel(dimension); guiCurveCloneInternalCurve(Natron::eCurveChangeReasonInternal,dimension, reason); - evaluateValueChange(dimension,reason); + evaluateValueChange(dimension, time ,reason); } if (holder && holder->getApp()) { @@ -2808,7 +2833,7 @@ KnobHelper::onMasterChanged(KnobI* master, ///For example we use it for roto knobs where selected beziers have their knobs slaved to the gui knobs clone(master,i); - evaluateValueChange(i, Natron::eValueChangedReasonSlaveRefresh); + evaluateValueChange(i, getCurrentTime(),Natron::eValueChangedReasonSlaveRefresh); } return; @@ -2834,6 +2859,7 @@ KnobHelper::onExprDependencyChanged(KnobI* knob,int dimensionChanged) } KnobHolder* holder = getHolder(); + int time = getCurrentTime(); for (std::set::const_iterator it = dimensionsToEvaluate.begin(); it != dimensionsToEvaluate.end(); ++it) { if (holder && !holder->isSetValueCurrentlyPossible()) { holder->abortAnyEvaluation(); @@ -2841,7 +2867,7 @@ KnobHelper::onExprDependencyChanged(KnobI* knob,int dimensionChanged) _imp->mustClearExprResults[*it] = true; } else { clearExpressionsResults(*it); - evaluateValueChange(*it, Natron::eValueChangedReasonSlaveRefresh); + evaluateValueChange(*it, time, Natron::eValueChangedReasonSlaveRefresh); } } } @@ -2941,7 +2967,7 @@ KnobHelper::removeListener(KnobI* knob) KnobHelper* other = dynamic_cast(knob); assert(other); if (other && other->_signalSlotHandler && _signalSlotHandler) { - QObject::disconnect( other->_signalSlotHandler.get(), SIGNAL( updateSlaves(int,int) ), _signalSlotHandler.get(), SLOT( onMasterChanged(int,int) ) ); + QObject::disconnect( _signalSlotHandler.get(), SIGNAL( updateSlaves(int,int) ), other->_signalSlotHandler.get(), SLOT( onMasterChanged(int,int) ) ); } QWriteLocker l(&_imp->mastersMutex); @@ -3666,6 +3692,13 @@ KnobHolder::onDoEndChangesOnMainThreadTriggered() endChanges(); } +ChangesList +KnobHolder::getKnobChanges() const +{ + QMutexLocker l(&_imp->evaluationBlockedMutex); + return _imp->knobChanged; +} + void KnobHolder::endChanges(bool discardEverything) { @@ -3722,7 +3755,7 @@ KnobHolder::onDoValueChangeOnMainThread(KnobI* knob, int reason, int time, bool } void -KnobHolder::appendValueChange(KnobI* knob,Natron::ValueChangedReasonEnum reason) +KnobHolder::appendValueChange(KnobI* knob,int time, Natron::ValueChangedReasonEnum reason) { { QMutexLocker l(&_imp->evaluationBlockedMutex); @@ -3734,9 +3767,9 @@ KnobHolder::appendValueChange(KnobI* knob,Natron::ValueChangedReasonEnum reason) if (knob && !knob->isValueChangesBlocked()) { if (!k.originatedFromMainThread && !canHandleEvaluateOnChangeInOtherThread()) { - Q_EMIT doValueChangeOnMainThread(knob, reason, getCurrentTime(), k.originatedFromMainThread); + Q_EMIT doValueChangeOnMainThread(knob, reason, time, k.originatedFromMainThread); } else { - onKnobValueChanged_public(knob, reason, getCurrentTime(), k.originatedFromMainThread); + onKnobValueChanged_public(knob, reason, time, k.originatedFromMainThread); } } @@ -3795,25 +3828,28 @@ void KnobHolder::setMultipleParamsEditLevel(KnobHolder::MultipleParamsEditEnum level) { QMutexLocker l(&_imp->paramsEditLevelMutex); - - if (level == KnobHolder::eMultipleParamsEditOff) { - if (_imp->paramsEditRecursionLevel > 0) { - --_imp->paramsEditRecursionLevel; - } - if (_imp->paramsEditRecursionLevel == 0) { - _imp->paramsEditLevel = KnobHolder::eMultipleParamsEditOff; - } - endChanges(); - - } else if (level == KnobHolder::eMultipleParamsEditOn) { - _imp->paramsEditLevel = level; + if (appPTR->isBackground()) { + _imp->paramsEditLevel = KnobHolder::eMultipleParamsEditOff; } else { - assert(level == KnobHolder::eMultipleParamsEditOnCreateNewCommand); - beginChanges(); - if (_imp->paramsEditLevel == KnobHolder::eMultipleParamsEditOff) { - _imp->paramsEditLevel = KnobHolder::eMultipleParamsEditOnCreateNewCommand; + if (level == KnobHolder::eMultipleParamsEditOff) { + if (_imp->paramsEditRecursionLevel > 0) { + --_imp->paramsEditRecursionLevel; + } + if (_imp->paramsEditRecursionLevel == 0) { + _imp->paramsEditLevel = KnobHolder::eMultipleParamsEditOff; + } + endChanges(); + + } else if (level == KnobHolder::eMultipleParamsEditOn) { + _imp->paramsEditLevel = level; + } else { + assert(level == KnobHolder::eMultipleParamsEditOnCreateNewCommand); + beginChanges(); + if (_imp->paramsEditLevel == KnobHolder::eMultipleParamsEditOff) { + _imp->paramsEditLevel = KnobHolder::eMultipleParamsEditOnCreateNewCommand; + } + ++_imp->paramsEditRecursionLevel; } - ++_imp->paramsEditRecursionLevel; } } diff --git a/Engine/Knob.h b/Engine/Knob.h index 4ce9f952bf..2d8ebd0b9c 100644 --- a/Engine/Knob.h +++ b/Engine/Knob.h @@ -217,6 +217,11 @@ class KnobSignalSlotHandler Q_EMIT descriptionChanged(); } + void s_evaluateOnChangeChanged(bool value) + { + Q_EMIT evaluateOnChangeChanged(value); + } + public Q_SLOTS: /** @@ -242,6 +247,8 @@ public Q_SLOTS: Q_SIGNALS: + void evaluateOnChangeChanged(bool value); + ///emitted whenever setAnimationLevel is called. It is meant to notify ///openfx params whether it is auto-keying or not. void animationLevelChanged(int,int); @@ -427,7 +434,7 @@ class KnobI * @brief Called by setValue to refresh the GUI, call the instanceChanged action on the plugin and * evaluate the new value (cause a render). **/ - virtual void evaluateValueChange(int dimension,Natron::ValueChangedReasonEnum reason) = 0; + virtual void evaluateValueChange(int dimension, int time, Natron::ValueChangedReasonEnum reason) = 0; /** * @brief Copies all the values, animations and extra data the other knob might have @@ -629,6 +636,7 @@ class KnobI **/ virtual bool onKeyFrameSet(SequenceTime time,int dimension) = 0; virtual bool onKeyFrameSet(SequenceTime time,const KeyFrame& key,int dimension) = 0; + virtual bool setKeyFrame(const KeyFrame& key,int dimension,Natron::ValueChangedReasonEnum reason) = 0; /** * @brief Called when the current time of the timeline changes. @@ -786,6 +794,11 @@ class KnobI **/ virtual bool getIsSecret() const = 0; virtual bool getDefaultIsSecret() const = 0; + + /** + * @brief Returns true if a knob is secret because it is either itself secret or one of its parent, recursively + **/ + virtual bool getIsSecretRecursive() const = 0; /** * @biref This is called to notify the gui that the knob shouldn't be editable. @@ -945,7 +958,10 @@ class KnobI * is listening to the values/keyframes of "this". It could be call addSlave but it will also be use for expressions. **/ virtual void addListener(bool isExpression,int fromExprDimension, int thisDimension, const boost::shared_ptr& knob) = 0; + +private: virtual void removeListener(KnobI* knob) = 0; +public: virtual bool useNativeOverlayHandle() const { return false; } @@ -1116,7 +1132,7 @@ class KnobHelper virtual void blockValueChanges() OVERRIDE FINAL; virtual void unblockValueChanges() OVERRIDE FINAL; virtual bool isValueChangesBlocked() const OVERRIDE FINAL WARN_UNUSED_RETURN; - virtual void evaluateValueChange(int dimension,Natron::ValueChangedReasonEnum reason) OVERRIDE FINAL; + virtual void evaluateValueChange(int dimension,int time, Natron::ValueChangedReasonEnum reason) OVERRIDE FINAL; virtual double random(double time,unsigned int seed) const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual double random(double min = 0., double max = 1.) const OVERRIDE FINAL WARN_UNUSED_RETURN; @@ -1192,6 +1208,7 @@ class KnobHelper virtual void setSecret(bool b) OVERRIDE FINAL; virtual void setSecretByDefault(bool b) OVERRIDE FINAL; virtual bool getIsSecret() const OVERRIDE FINAL WARN_UNUSED_RETURN; + virtual bool getIsSecretRecursive() const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual bool getDefaultIsSecret() const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual void setIsFrozen(bool frozen) OVERRIDE FINAL; virtual void setDirty(bool d) OVERRIDE FINAL; @@ -1441,8 +1458,12 @@ class Knob Natron::ValueChangedReasonEnum reason, bool copyState) OVERRIDE FINAL; + + +public: - /** + + /** * @brief Set the value of the knob at the given time and for the given dimension with the given reason. * @param newKey[out] The keyframe that was added if the return value is true. * @returns True if a keyframe was successfully added, false otherwise. @@ -1453,8 +1474,8 @@ class Knob Natron::ValueChangedReasonEnum reason, KeyFrame* newKey); -public: - + virtual bool setKeyFrame(const KeyFrame& key,int dimension,Natron::ValueChangedReasonEnum reason) OVERRIDE FINAL; + /** * @brief Set the value of the knob in the given dimension with the given reason. * @param newKey If not NULL and the animation level of the knob is Natron::eAnimationLevelInterpolatedValue @@ -1973,7 +1994,7 @@ class KnobHolder : public QObject bool isEvaluationBlocked() const; - void appendValueChange(KnobI* knob,Natron::ValueChangedReasonEnum reason); + void appendValueChange(KnobI* knob,int time, Natron::ValueChangedReasonEnum reason); bool isSetValueCurrentlyPossible() const; @@ -2053,6 +2074,8 @@ class KnobHolder : public QObject **/ void beginChanges(); void endChanges(bool discardEverything = false); + + ChangesList getKnobChanges() const; /** * @brief The virtual portion of notifyProjectBeginValuesChanged(). This is called by the project diff --git a/Engine/KnobImpl.h b/Engine/KnobImpl.h index 94619a23db..ba2250e6c4 100644 --- a/Engine/KnobImpl.h +++ b/Engine/KnobImpl.h @@ -883,7 +883,7 @@ Knob::setValue(const T & v, } break; } - } + } // switch (paramEditLevel) { ///basically if we enter this if condition, for each dimension the undo stack will create a new command. ///the caller should have tested this prior to calling this function and correctly called editBegin() editEnd() @@ -897,7 +897,7 @@ Knob::setValue(const T & v, return ret; } } - } + } // if ( holder && (reason == Natron::eValueChangedReasonPluginEdited) && getKnobGuiPointer() ) { @@ -979,6 +979,9 @@ Knob::setValue(const T & v, _guiValues[dimension] = v; } + double time; + bool timeSet = false; + ///Add automatically a new keyframe if (isAnimationEnabled() && getAnimationLevel(dimension) != Natron::eAnimationLevelNone && //< if the knob is animated @@ -991,8 +994,8 @@ Knob::setValue(const T & v, reason == Natron::eValueChangedReasonNatronInternalEdited ) && //< the change was made by the user or plugin ( newKey != NULL) ) { //< the keyframe to set is not null - double time = getCurrentTime(); - + time = getCurrentTime(); + timeSet = true; bool addedKeyFrame = setValueAtTime(time, v, dimension,reason,newKey); if (addedKeyFrame) { ret = eValueChangedReturnCodeKeyframeAdded; @@ -1003,7 +1006,10 @@ Knob::setValue(const T & v, } if (hasChanged && ret == eValueChangedReturnCodeNoKeyframeAdded) { //the other cases already called this in setValueAtTime() - evaluateValueChange(dimension,reason); + if (!timeSet) { + time = getCurrentTime(); + } + evaluateValueChange(dimension, time, reason); } { QMutexLocker l(&_setValueRecursionLevelMutex); @@ -1173,10 +1179,19 @@ Knob::setValueAtTime(int time, break; case KnobHolder::eMultipleParamsEditOnCreateNewCommand: { if ( !get_SetValueRecursionLevel() ) { + { + QMutexLocker l(&_setValueRecursionLevelMutex); + ++_setValueRecursionLevel; + } + Variant vari; valueToVariant(v, &vari); holder->setMultipleParamsEditLevel(KnobHolder::eMultipleParamsEditOn); _signalSlotHandler->s_appendParamEditChange(reason, vari, dimension, time, true,true); + { + QMutexLocker l(&_setValueRecursionLevelMutex); + --_setValueRecursionLevel; + } return true; } @@ -1184,15 +1199,24 @@ Knob::setValueAtTime(int time, } case KnobHolder::eMultipleParamsEditOn: { if ( !get_SetValueRecursionLevel() ) { + { + QMutexLocker l(&_setValueRecursionLevelMutex); + ++_setValueRecursionLevel; + } + Variant vari; valueToVariant(v, &vari); _signalSlotHandler->s_appendParamEditChange(reason, vari, dimension,time, false,true); - + { + QMutexLocker l(&_setValueRecursionLevelMutex); + --_setValueRecursionLevel; + } + return true; } break; } - } + } // switch (paramEditLevel) { } @@ -1248,7 +1272,7 @@ Knob::setValueAtTime(int time, _signalSlotHandler->s_keyFrameSet(time,dimension,(int)reason,ret); } if (hasChanged) { - evaluateValueChange(dimension, reason); + evaluateValueChange(dimension, time, reason); } else { return eValueChangedReturnCodeNothingChanged; } @@ -1391,79 +1415,15 @@ Knob::unSlave(int dimension, if (getHolder() && _signalSlotHandler) { getHolder()->onKnobSlaved( this, master.second.get(),dimension,false ); } - if (hasChanged) { - evaluateValueChange(dimension, reason); - } -} - -template<> -void -Knob::unSlave(int dimension, - Natron::ValueChangedReasonEnum reason, - bool copyState) -{ - assert( isSlave(dimension) ); - - std::pair > master = getMaster(dimension); - - if (copyState) { - ///clone the master - { - Knob* isString = dynamic_cast* >( master.second.get() ); - assert(isString); //< other data types aren't supported - if (isString) { - QMutexLocker l1(&_valueMutex); - _values[dimension] = isString->getValue(master.first); - _guiValues[dimension] = _values[dimension]; - } - } - boost::shared_ptr curve = getCurve(dimension); - boost::shared_ptr mastercurve = master.second->getCurve(master.first); - if (curve && mastercurve) { - curve->clone(*mastercurve); - } - - cloneExtraData( master.second.get() ); - } - KnobHelper* helper = dynamic_cast( master.second.get() ); - assert(helper); - if (helper) { - QObject::disconnect( helper->getSignalSlotHandler().get(), - SIGNAL( updateSlaves(int,int) ), - _signalSlotHandler.get(), - SLOT( onMasterChanged(int,int) ) ); - QObject::disconnect( helper->getSignalSlotHandler().get(), - SIGNAL( keyFrameSet(SequenceTime,int,int,bool) ), - _signalSlotHandler.get(), - SLOT( onMasterKeyFrameSet(SequenceTime,int,int,bool) ) ); - QObject::disconnect( helper->getSignalSlotHandler().get(), - SIGNAL( keyFrameRemoved(SequenceTime,int,int) ), - _signalSlotHandler.get(), - SLOT( onMasterKeyFrameRemoved(SequenceTime,int,int)) ); - - QObject::disconnect( helper->getSignalSlotHandler().get(), - SIGNAL( keyFrameMoved(int,int,int) ), - _signalSlotHandler.get(), - SLOT( onMasterKeyFrameMoved(int,int,int) ) ); - QObject::disconnect( helper->getSignalSlotHandler().get(), - SIGNAL(animationRemoved(int) ), - _signalSlotHandler.get(), - SLOT(onMasterAnimationRemoved(int)) ); - } - resetMaster(dimension); - setEnabled(dimension, true); - _signalSlotHandler->s_valueChanged(dimension,reason); - if (getHolder() && _signalSlotHandler) { - getHolder()->onKnobSlaved( this,master.second.get(),dimension,false ); - } - if (reason == Natron::eValueChangedReasonPluginEdited) { - _signalSlotHandler->s_knobSlaved(dimension, false); - } if (helper) { helper->removeListener(this); } + if (hasChanged) { + evaluateValueChange(dimension, getCurrentTime(), reason); + } } + template KnobHelper::ValueChangedReturnCodeEnum Knob::setValue(const T & value, @@ -1714,7 +1674,7 @@ bool Knob::onKeyFrameSet(SequenceTime time, int dimension) { - KeyFrame k; + KeyFrame key; boost::shared_ptr curve; KnobHolder* holder = getHolder(); bool useGuiCurve = (!holder || !holder->isSetValueCurrentlyPossible()) && getKnobGuiPointer(); @@ -1729,21 +1689,14 @@ Knob::onKeyFrameSet(SequenceTime time, curve = getGuiCurve(dimension); setGuiCurveHasChanged(dimension,true); } - - makeKeyFrame(curve.get(), time, getValueAtTime(time,dimension), &k); - - bool ret = curve->addKeyFrame(k); - if (!useGuiCurve) { - guiCurveCloneInternalCurve(Natron::eCurveChangeReasonInternal,dimension, Natron::eValueChangedReasonUserEdited); - evaluateValueChange(dimension, Natron::eValueChangedReasonUserEdited); - } - return ret; + makeKeyFrame(curve.get(), time, getValueAtTime(time,dimension), &key); + return setKeyFrame(key, dimension, Natron::eValueChangedReasonUserEdited); } template bool -Knob::onKeyFrameSet(SequenceTime /*time*/,const KeyFrame& key,int dimension) +Knob::setKeyFrame(const KeyFrame& key,int dimension,Natron::ValueChangedReasonEnum reason) { boost::shared_ptr curve; KnobHolder* holder = getHolder(); @@ -1763,12 +1716,19 @@ Knob::onKeyFrameSet(SequenceTime /*time*/,const KeyFrame& key,int dimension) bool ret = curve->addKeyFrame(key); if (!useGuiCurve) { - guiCurveCloneInternalCurve(Natron::eCurveChangeReasonInternal,dimension, Natron::eValueChangedReasonUserEdited); - evaluateValueChange(dimension, Natron::eValueChangedReasonUserEdited); + guiCurveCloneInternalCurve(Natron::eCurveChangeReasonInternal,dimension, reason); + evaluateValueChange(dimension, key.getTime(), reason); } return ret; } +template +bool +Knob::onKeyFrameSet(SequenceTime /*time*/,const KeyFrame& key,int dimension) +{ + return setKeyFrame(key, dimension, Natron::eValueChangedReasonUserEdited); +} + template void Knob::onTimeChanged(SequenceTime /*time*/) @@ -2549,8 +2509,9 @@ Knob::dequeueValuesSet(bool disableEvaluation) if (!disableEvaluation && !dimensionChanged.empty()) { beginChanges(); + int time = getCurrentTime(); for (std::map::iterator it = dimensionChanged.begin(); it != dimensionChanged.end(); ++it) { - evaluateValueChange(it->first, it->second); + evaluateValueChange(it->first, time, it->second); } endChanges(); } diff --git a/Engine/KnobTypes.cpp b/Engine/KnobTypes.cpp index 79d582ca5f..43913f2daf 100644 --- a/Engine/KnobTypes.cpp +++ b/Engine/KnobTypes.cpp @@ -378,14 +378,7 @@ KnobDouble::restoreTracks(const std::list & tracks, { ///get a shared_ptr to this assert( getHolder() ); - boost::shared_ptr thisShared; - const std::vector > & knobs = getHolder()->getKnobs(); - for (U32 i = 0; i < knobs.size(); ++i) { - if (knobs[i].get() == this) { - thisShared = boost::dynamic_pointer_cast(knobs[i]); - break; - } - } + boost::shared_ptr thisShared = boost::dynamic_pointer_cast(shared_from_this()); assert(thisShared); std::string lastNodeName; @@ -567,6 +560,12 @@ KnobButton::typeName() const return typeNameStatic(); } +void +KnobButton::trigger() +{ + evaluateValueChange(0, getCurrentTime(), Natron::eValueChangedReasonUserEdited); +} + /******************************KnobChoice**************************************/ KnobChoice::KnobChoice(KnobHolder* holder, diff --git a/Engine/KnobTypes.h b/Engine/KnobTypes.h index 6df9596d3c..13cb58c1f2 100644 --- a/Engine/KnobTypes.h +++ b/Engine/KnobTypes.h @@ -384,6 +384,8 @@ class KnobButton { return _iconFilePath; } + + void trigger(); private: diff --git a/Engine/NatronEngine/booleanparam_wrapper.cpp b/Engine/NatronEngine/booleanparam_wrapper.cpp index 4942c3083d..ecb31f0ba8 100644 --- a/Engine/NatronEngine/booleanparam_wrapper.cpp +++ b/Engine/NatronEngine/booleanparam_wrapper.cpp @@ -120,12 +120,12 @@ static PyObject* Sbk_BooleanParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -143,13 +143,13 @@ static PyObject* Sbk_BooleanParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const bool cppResult = const_cast(cppSelf)->get(cppArg0); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -164,7 +164,7 @@ static PyObject* Sbk_BooleanParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_BooleanParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.BooleanParam.get", overloads); return 0; } @@ -234,9 +234,9 @@ static PyObject* Sbk_BooleanParamFunc_getValueAtTime(PyObject* self, PyObject* p SBK_UNUSED(pythonToCpp) // Overloaded function decisor - // 0: getValueAtTime(int)const - if ((pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArg)))) { - overloadId = 0; // getValueAtTime(int)const + // 0: getValueAtTime(double)const + if ((pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArg)))) { + overloadId = 0; // getValueAtTime(double)const } // Function signature not found. @@ -244,11 +244,11 @@ static PyObject* Sbk_BooleanParamFunc_getValueAtTime(PyObject* self, PyObject* p // Call function/method { - int cppArg0; + double cppArg0; pythonToCpp(pyArg, &cppArg0); if (!PyErr_Occurred()) { - // getValueAtTime(int)const + // getValueAtTime(double)const bool cppResult = const_cast(cppSelf)->getValueAtTime(cppArg0); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -261,7 +261,7 @@ static PyObject* Sbk_BooleanParamFunc_getValueAtTime(PyObject* self, PyObject* p return pyResult; Sbk_BooleanParamFunc_getValueAtTime_TypeError: - const char* overloads[] = {"int", 0}; + const char* overloads[] = {"float", 0}; Shiboken::setErrorAboutWrongArguments(pyArg, "NatronEngine.BooleanParam.getValueAtTime", overloads); return 0; } diff --git a/Engine/NatronEngine/buttonparam_wrapper.cpp b/Engine/NatronEngine/buttonparam_wrapper.cpp index eaff10cf0c..5ccb828b69 100644 --- a/Engine/NatronEngine/buttonparam_wrapper.cpp +++ b/Engine/NatronEngine/buttonparam_wrapper.cpp @@ -76,8 +76,32 @@ static PyObject* Sbk_ButtonParamFunc_setIconFilePath(PyObject* self, PyObject* p return 0; } +static PyObject* Sbk_ButtonParamFunc_trigger(PyObject* self) +{ + ButtonParamWrapper* cppSelf = 0; + SBK_UNUSED(cppSelf) + if (!Shiboken::Object::isValid(self)) + return 0; + cppSelf = (ButtonParamWrapper*)((::ButtonParam*)Shiboken::Conversions::cppPointer(SbkNatronEngineTypes[SBK_BUTTONPARAM_IDX], (SbkObject*)self)); + + // Call function/method + { + + if (!PyErr_Occurred()) { + // trigger() + cppSelf->trigger(); + } + } + + if (PyErr_Occurred()) { + return 0; + } + Py_RETURN_NONE; +} + static PyMethodDef Sbk_ButtonParam_methods[] = { {"setIconFilePath", (PyCFunction)Sbk_ButtonParamFunc_setIconFilePath, METH_O}, + {"trigger", (PyCFunction)Sbk_ButtonParamFunc_trigger, METH_NOARGS}, {0} // Sentinel }; diff --git a/Engine/NatronEngine/choiceparam_wrapper.cpp b/Engine/NatronEngine/choiceparam_wrapper.cpp index c87055fe4b..25e68b8810 100644 --- a/Engine/NatronEngine/choiceparam_wrapper.cpp +++ b/Engine/NatronEngine/choiceparam_wrapper.cpp @@ -178,12 +178,12 @@ static PyObject* Sbk_ChoiceParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -201,13 +201,13 @@ static PyObject* Sbk_ChoiceParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const int cppResult = const_cast(cppSelf)->get(cppArg0); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -222,7 +222,7 @@ static PyObject* Sbk_ChoiceParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_ChoiceParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.ChoiceParam.get", overloads); return 0; } @@ -389,9 +389,9 @@ static PyObject* Sbk_ChoiceParamFunc_getValueAtTime(PyObject* self, PyObject* py SBK_UNUSED(pythonToCpp) // Overloaded function decisor - // 0: getValueAtTime(int)const - if ((pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArg)))) { - overloadId = 0; // getValueAtTime(int)const + // 0: getValueAtTime(double)const + if ((pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArg)))) { + overloadId = 0; // getValueAtTime(double)const } // Function signature not found. @@ -399,11 +399,11 @@ static PyObject* Sbk_ChoiceParamFunc_getValueAtTime(PyObject* self, PyObject* py // Call function/method { - int cppArg0; + double cppArg0; pythonToCpp(pyArg, &cppArg0); if (!PyErr_Occurred()) { - // getValueAtTime(int)const + // getValueAtTime(double)const int cppResult = const_cast(cppSelf)->getValueAtTime(cppArg0); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -416,7 +416,7 @@ static PyObject* Sbk_ChoiceParamFunc_getValueAtTime(PyObject* self, PyObject* py return pyResult; Sbk_ChoiceParamFunc_getValueAtTime_TypeError: - const char* overloads[] = {"int", 0}; + const char* overloads[] = {"float", 0}; Shiboken::setErrorAboutWrongArguments(pyArg, "NatronEngine.ChoiceParam.getValueAtTime", overloads); return 0; } diff --git a/Engine/NatronEngine/colorparam_wrapper.cpp b/Engine/NatronEngine/colorparam_wrapper.cpp index f268b711de..b4b2d7aa84 100644 --- a/Engine/NatronEngine/colorparam_wrapper.cpp +++ b/Engine/NatronEngine/colorparam_wrapper.cpp @@ -120,12 +120,12 @@ static PyObject* Sbk_ColorParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -143,13 +143,13 @@ static PyObject* Sbk_ColorParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const ColorTuple* cppResult = new ColorTuple(const_cast(cppSelf)->get(cppArg0)); pyResult = Shiboken::Object::newObject((SbkObjectType*)SbkNatronEngineTypes[SBK_COLORTUPLE_IDX], cppResult, true, true); } @@ -164,7 +164,7 @@ static PyObject* Sbk_ColorParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_ColorParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.ColorParam.get", overloads); return 0; } @@ -572,12 +572,12 @@ static PyObject* Sbk_ColorParamFunc_getValueAtTime(PyObject* self, PyObject* arg // Overloaded function decisor - // 0: getValueAtTime(int,int)const - if ((pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + // 0: getValueAtTime(double,int)const + if ((pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { if (numArgs == 1) { - overloadId = 0; // getValueAtTime(int,int)const + overloadId = 0; // getValueAtTime(double,int)const } else if ((pythonToCpp[1] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[1])))) { - overloadId = 0; // getValueAtTime(int,int)const + overloadId = 0; // getValueAtTime(double,int)const } } @@ -597,13 +597,13 @@ static PyObject* Sbk_ColorParamFunc_getValueAtTime(PyObject* self, PyObject* arg goto Sbk_ColorParamFunc_getValueAtTime_TypeError; } } - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); int cppArg1 = 0; if (pythonToCpp[1]) pythonToCpp[1](pyArgs[1], &cppArg1); if (!PyErr_Occurred()) { - // getValueAtTime(int,int)const + // getValueAtTime(double,int)const double cppResult = const_cast(cppSelf)->getValueAtTime(cppArg0, cppArg1); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -616,7 +616,7 @@ static PyObject* Sbk_ColorParamFunc_getValueAtTime(PyObject* self, PyObject* arg return pyResult; Sbk_ColorParamFunc_getValueAtTime_TypeError: - const char* overloads[] = {"int, int = 0", 0}; + const char* overloads[] = {"float, int = 0", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.ColorParam.getValueAtTime", overloads); return 0; } diff --git a/Engine/NatronEngine/double2dparam_wrapper.cpp b/Engine/NatronEngine/double2dparam_wrapper.cpp index 64285b1d43..c83a7dc908 100644 --- a/Engine/NatronEngine/double2dparam_wrapper.cpp +++ b/Engine/NatronEngine/double2dparam_wrapper.cpp @@ -57,12 +57,12 @@ static PyObject* Sbk_Double2DParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -80,13 +80,13 @@ static PyObject* Sbk_Double2DParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const Double2DTuple* cppResult = new Double2DTuple(const_cast(cppSelf)->get(cppArg0)); pyResult = Shiboken::Object::newObject((SbkObjectType*)SbkNatronEngineTypes[SBK_DOUBLE2DTUPLE_IDX], cppResult, true, true); } @@ -101,7 +101,7 @@ static PyObject* Sbk_Double2DParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_Double2DParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.Double2DParam.get", overloads); return 0; } diff --git a/Engine/NatronEngine/double3dparam_wrapper.cpp b/Engine/NatronEngine/double3dparam_wrapper.cpp index 17d98441c2..964fd42bc4 100644 --- a/Engine/NatronEngine/double3dparam_wrapper.cpp +++ b/Engine/NatronEngine/double3dparam_wrapper.cpp @@ -57,12 +57,12 @@ static PyObject* Sbk_Double3DParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -80,13 +80,13 @@ static PyObject* Sbk_Double3DParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const Double3DTuple* cppResult = new Double3DTuple(const_cast(cppSelf)->get(cppArg0)); pyResult = Shiboken::Object::newObject((SbkObjectType*)SbkNatronEngineTypes[SBK_DOUBLE3DTUPLE_IDX], cppResult, true, true); } @@ -101,7 +101,7 @@ static PyObject* Sbk_Double3DParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_Double3DParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.Double3DParam.get", overloads); return 0; } diff --git a/Engine/NatronEngine/doubleparam_wrapper.cpp b/Engine/NatronEngine/doubleparam_wrapper.cpp index d6e9cb04e0..03b3fd607a 100644 --- a/Engine/NatronEngine/doubleparam_wrapper.cpp +++ b/Engine/NatronEngine/doubleparam_wrapper.cpp @@ -120,12 +120,12 @@ static PyObject* Sbk_DoubleParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -143,13 +143,13 @@ static PyObject* Sbk_DoubleParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const double cppResult = const_cast(cppSelf)->get(cppArg0); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -164,7 +164,7 @@ static PyObject* Sbk_DoubleParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_DoubleParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.DoubleParam.get", overloads); return 0; } @@ -572,12 +572,12 @@ static PyObject* Sbk_DoubleParamFunc_getValueAtTime(PyObject* self, PyObject* ar // Overloaded function decisor - // 0: getValueAtTime(int,int)const - if ((pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + // 0: getValueAtTime(double,int)const + if ((pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { if (numArgs == 1) { - overloadId = 0; // getValueAtTime(int,int)const + overloadId = 0; // getValueAtTime(double,int)const } else if ((pythonToCpp[1] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[1])))) { - overloadId = 0; // getValueAtTime(int,int)const + overloadId = 0; // getValueAtTime(double,int)const } } @@ -597,13 +597,13 @@ static PyObject* Sbk_DoubleParamFunc_getValueAtTime(PyObject* self, PyObject* ar goto Sbk_DoubleParamFunc_getValueAtTime_TypeError; } } - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); int cppArg1 = 0; if (pythonToCpp[1]) pythonToCpp[1](pyArgs[1], &cppArg1); if (!PyErr_Occurred()) { - // getValueAtTime(int,int)const + // getValueAtTime(double,int)const double cppResult = const_cast(cppSelf)->getValueAtTime(cppArg0, cppArg1); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -616,7 +616,7 @@ static PyObject* Sbk_DoubleParamFunc_getValueAtTime(PyObject* self, PyObject* ar return pyResult; Sbk_DoubleParamFunc_getValueAtTime_TypeError: - const char* overloads[] = {"int, int = 0", 0}; + const char* overloads[] = {"float, int = 0", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.DoubleParam.getValueAtTime", overloads); return 0; } diff --git a/Engine/NatronEngine/int2dparam_wrapper.cpp b/Engine/NatronEngine/int2dparam_wrapper.cpp index 9d7d91bd3c..5f5a94bfe0 100644 --- a/Engine/NatronEngine/int2dparam_wrapper.cpp +++ b/Engine/NatronEngine/int2dparam_wrapper.cpp @@ -57,12 +57,12 @@ static PyObject* Sbk_Int2DParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -80,13 +80,13 @@ static PyObject* Sbk_Int2DParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const Int2DTuple* cppResult = new Int2DTuple(const_cast(cppSelf)->get(cppArg0)); pyResult = Shiboken::Object::newObject((SbkObjectType*)SbkNatronEngineTypes[SBK_INT2DTUPLE_IDX], cppResult, true, true); } @@ -101,7 +101,7 @@ static PyObject* Sbk_Int2DParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_Int2DParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.Int2DParam.get", overloads); return 0; } diff --git a/Engine/NatronEngine/int3dparam_wrapper.cpp b/Engine/NatronEngine/int3dparam_wrapper.cpp index e1d146023e..020c98669b 100644 --- a/Engine/NatronEngine/int3dparam_wrapper.cpp +++ b/Engine/NatronEngine/int3dparam_wrapper.cpp @@ -57,12 +57,12 @@ static PyObject* Sbk_Int3DParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -80,13 +80,13 @@ static PyObject* Sbk_Int3DParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const Int3DTuple* cppResult = new Int3DTuple(const_cast(cppSelf)->get(cppArg0)); pyResult = Shiboken::Object::newObject((SbkObjectType*)SbkNatronEngineTypes[SBK_INT3DTUPLE_IDX], cppResult, true, true); } @@ -101,7 +101,7 @@ static PyObject* Sbk_Int3DParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_Int3DParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.Int3DParam.get", overloads); return 0; } diff --git a/Engine/NatronEngine/intparam_wrapper.cpp b/Engine/NatronEngine/intparam_wrapper.cpp index a06e5b5468..52f6e20148 100644 --- a/Engine/NatronEngine/intparam_wrapper.cpp +++ b/Engine/NatronEngine/intparam_wrapper.cpp @@ -120,12 +120,12 @@ static PyObject* Sbk_IntParamFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -143,13 +143,13 @@ static PyObject* Sbk_IntParamFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const int cppResult = const_cast(cppSelf)->get(cppArg0); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -164,7 +164,7 @@ static PyObject* Sbk_IntParamFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_IntParamFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.IntParam.get", overloads); return 0; } @@ -572,12 +572,12 @@ static PyObject* Sbk_IntParamFunc_getValueAtTime(PyObject* self, PyObject* args, // Overloaded function decisor - // 0: getValueAtTime(int,int)const - if ((pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + // 0: getValueAtTime(double,int)const + if ((pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { if (numArgs == 1) { - overloadId = 0; // getValueAtTime(int,int)const + overloadId = 0; // getValueAtTime(double,int)const } else if ((pythonToCpp[1] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[1])))) { - overloadId = 0; // getValueAtTime(int,int)const + overloadId = 0; // getValueAtTime(double,int)const } } @@ -597,13 +597,13 @@ static PyObject* Sbk_IntParamFunc_getValueAtTime(PyObject* self, PyObject* args, goto Sbk_IntParamFunc_getValueAtTime_TypeError; } } - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); int cppArg1 = 0; if (pythonToCpp[1]) pythonToCpp[1](pyArgs[1], &cppArg1); if (!PyErr_Occurred()) { - // getValueAtTime(int,int)const + // getValueAtTime(double,int)const int cppResult = const_cast(cppSelf)->getValueAtTime(cppArg0, cppArg1); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -616,7 +616,7 @@ static PyObject* Sbk_IntParamFunc_getValueAtTime(PyObject* self, PyObject* args, return pyResult; Sbk_IntParamFunc_getValueAtTime_TypeError: - const char* overloads[] = {"int, int = 0", 0}; + const char* overloads[] = {"float, int = 0", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.IntParam.getValueAtTime", overloads); return 0; } diff --git a/Engine/NatronEngine/natron_wrapper.cpp b/Engine/NatronEngine/natron_wrapper.cpp index 106a76ccda..6ffb29dc8c 100644 --- a/Engine/NatronEngine/natron_wrapper.cpp +++ b/Engine/NatronEngine/natron_wrapper.cpp @@ -1107,6 +1107,12 @@ void init_Natron(PyObject* module) if (!Shiboken::Enum::createScopedEnumItem(SbkNatronEngineTypes[SBK_NATRON_PIXMAPENUM_IDX], &Sbk_Natron_Type, "NATRON_PIXMAP_VIEWER_RENDER_SCALE_CHECKED", (long) Natron::NATRON_PIXMAP_VIEWER_RENDER_SCALE_CHECKED)) return ; + if (!Shiboken::Enum::createScopedEnumItem(SbkNatronEngineTypes[SBK_NATRON_PIXMAPENUM_IDX], + &Sbk_Natron_Type, "NATRON_PIXMAP_VIEWER_AUTOCONTRAST_ENABLED", (long) Natron::NATRON_PIXMAP_VIEWER_AUTOCONTRAST_ENABLED)) + return ; + if (!Shiboken::Enum::createScopedEnumItem(SbkNatronEngineTypes[SBK_NATRON_PIXMAPENUM_IDX], + &Sbk_Natron_Type, "NATRON_PIXMAP_VIEWER_AUTOCONTRAST_DISABLED", (long) Natron::NATRON_PIXMAP_VIEWER_AUTOCONTRAST_DISABLED)) + return ; if (!Shiboken::Enum::createScopedEnumItem(SbkNatronEngineTypes[SBK_NATRON_PIXMAPENUM_IDX], &Sbk_Natron_Type, "NATRON_PIXMAP_OPEN_FILE", (long) Natron::NATRON_PIXMAP_OPEN_FILE)) return ; diff --git a/Engine/NatronEngine/natronengine_module_wrapper.cpp b/Engine/NatronEngine/natronengine_module_wrapper.cpp index 874e4deb9c..0f21370339 100644 --- a/Engine/NatronEngine/natronengine_module_wrapper.cpp +++ b/Engine/NatronEngine/natronengine_module_wrapper.cpp @@ -32,21 +32,18 @@ static PyMethodDef NatronEngine_methods[] = { }; // Classes initialization functions ------------------------------------------------------------ -void init_RectD(PyObject* module); void init_PyCoreApplication(PyObject* module); void init_Group(PyObject* module); void init_App(PyObject* module); void init_AppSettings(PyObject* module); void init_ItemBase(PyObject* module); -void init_Layer(PyObject* module); void init_BezierCurve(PyObject* module); +void init_Layer(PyObject* module); void init_Roto(PyObject* module); void init_UserParamHolder(PyObject* module); void init_Effect(PyObject* module); void init_Param(PyObject* module); void init_AnimatedParam(PyObject* module); -void init_ColorParam(PyObject* module); -void init_ChoiceParam(PyObject* module); void init_BooleanParam(PyObject* module); void init_StringParamBase(PyObject* module); void init_StringParam(PyObject* module); @@ -59,6 +56,8 @@ void init_Int3DParam(PyObject* module); void init_DoubleParam(PyObject* module); void init_Double2DParam(PyObject* module); void init_Double3DParam(PyObject* module); +void init_ColorParam(PyObject* module); +void init_ChoiceParam(PyObject* module); void init_ButtonParam(PyObject* module); void init_GroupParam(PyObject* module); void init_PageParam(PyObject* module); @@ -69,6 +68,7 @@ void init_Double2DTuple(PyObject* module); void init_Double3DTuple(PyObject* module); void init_ColorTuple(PyObject* module); void init_RectI(PyObject* module); +void init_RectD(PyObject* module); void init_Natron(PyObject* module); // Required modules' type and converter arrays. @@ -558,21 +558,18 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) #endif // Initialize classes in the type system - init_RectD(module); init_PyCoreApplication(module); init_Group(module); init_App(module); init_AppSettings(module); init_ItemBase(module); - init_Layer(module); init_BezierCurve(module); + init_Layer(module); init_Roto(module); init_UserParamHolder(module); init_Effect(module); init_Param(module); init_AnimatedParam(module); - init_ColorParam(module); - init_ChoiceParam(module); init_BooleanParam(module); init_StringParamBase(module); init_StringParam(module); @@ -585,6 +582,8 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) init_DoubleParam(module); init_Double2DParam(module); init_Double3DParam(module); + init_ColorParam(module); + init_ChoiceParam(module); init_ButtonParam(module); init_GroupParam(module); init_PageParam(module); @@ -595,6 +594,7 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) init_Double3DTuple(module); init_ColorTuple(module); init_RectI(module); + init_RectD(module); init_Natron(module); // Register converter for type 'NatronEngine.std::size_t'. diff --git a/Engine/NatronEngine/natronengine_python.h b/Engine/NatronEngine/natronengine_python.h index 37d0bdfbfb..97df07ca3d 100644 --- a/Engine/NatronEngine/natronengine_python.h +++ b/Engine/NatronEngine/natronengine_python.h @@ -72,6 +72,7 @@ CLANG_DIAG_ON(uninitialized) #define SBK_NATRON_PLAYBACKMODEENUM_IDX 35 #define SBK_NATRON_PIXMAPENUM_IDX 34 #define SBK_NATRON_VIEWERCOLORSPACEENUM_IDX 39 +#define SBK_RECTD_IDX 48 #define SBK_RECTI_IDX 49 #define SBK_COLORTUPLE_IDX 9 #define SBK_DOUBLE3DTUPLE_IDX 13 @@ -84,6 +85,7 @@ CLANG_DIAG_ON(uninitialized) #define SBK_GROUPPARAM_IDX 18 #define SBK_BUTTONPARAM_IDX 6 #define SBK_ANIMATEDPARAM_IDX 0 +#define SBK_BOOLEANPARAM_IDX 5 #define SBK_CHOICEPARAM_IDX 7 #define SBK_COLORPARAM_IDX 8 #define SBK_DOUBLEPARAM_IDX 14 @@ -98,7 +100,6 @@ CLANG_DIAG_ON(uninitialized) #define SBK_FILEPARAM_IDX 16 #define SBK_STRINGPARAM_IDX 51 #define SBK_STRINGPARAM_TYPEENUM_IDX 52 -#define SBK_BOOLEANPARAM_IDX 5 #define SBK_USERPARAMHOLDER_IDX 54 #define SBK_ROTO_IDX 50 #define SBK_ITEMBASE_IDX 24 @@ -110,7 +111,6 @@ CLANG_DIAG_ON(uninitialized) #define SBK_APP_IDX 1 #define SBK_EFFECT_IDX 15 #define SBK_PYCOREAPPLICATION_IDX 46 -#define SBK_RECTD_IDX 48 #define SBK_NatronEngine_IDX_COUNT 55 // This variable stores all Python types exported by this module. @@ -156,6 +156,7 @@ template<> inline PyTypeObject* SbkType< ::Natron::ViewerCompositingOperatorEnum template<> inline PyTypeObject* SbkType< ::Natron::PlaybackModeEnum >() { return SbkNatronEngineTypes[SBK_NATRON_PLAYBACKMODEENUM_IDX]; } template<> inline PyTypeObject* SbkType< ::Natron::PixmapEnum >() { return SbkNatronEngineTypes[SBK_NATRON_PIXMAPENUM_IDX]; } template<> inline PyTypeObject* SbkType< ::Natron::ViewerColorSpaceEnum >() { return SbkNatronEngineTypes[SBK_NATRON_VIEWERCOLORSPACEENUM_IDX]; } +template<> inline PyTypeObject* SbkType< ::RectD >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_RECTD_IDX]); } template<> inline PyTypeObject* SbkType< ::RectI >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_RECTI_IDX]); } template<> inline PyTypeObject* SbkType< ::ColorTuple >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_COLORTUPLE_IDX]); } template<> inline PyTypeObject* SbkType< ::Double3DTuple >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_DOUBLE3DTUPLE_IDX]); } @@ -168,6 +169,7 @@ template<> inline PyTypeObject* SbkType< ::PageParam >() { return reinterpret_ca template<> inline PyTypeObject* SbkType< ::GroupParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_GROUPPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::ButtonParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_BUTTONPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::AnimatedParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_ANIMATEDPARAM_IDX]); } +template<> inline PyTypeObject* SbkType< ::BooleanParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_BOOLEANPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::ChoiceParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_CHOICEPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::ColorParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_COLORPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::DoubleParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_DOUBLEPARAM_IDX]); } @@ -182,7 +184,6 @@ template<> inline PyTypeObject* SbkType< ::OutputFileParam >() { return reinterp template<> inline PyTypeObject* SbkType< ::FileParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_FILEPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::StringParam::TypeEnum >() { return SbkNatronEngineTypes[SBK_STRINGPARAM_TYPEENUM_IDX]; } template<> inline PyTypeObject* SbkType< ::StringParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_STRINGPARAM_IDX]); } -template<> inline PyTypeObject* SbkType< ::BooleanParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_BOOLEANPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::UserParamHolder >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_USERPARAMHOLDER_IDX]); } template<> inline PyTypeObject* SbkType< ::Roto >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_ROTO_IDX]); } template<> inline PyTypeObject* SbkType< ::ItemBase >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_ITEMBASE_IDX]); } @@ -194,7 +195,6 @@ template<> inline PyTypeObject* SbkType< ::Group >() { return reinterpret_cast

inline PyTypeObject* SbkType< ::App >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_APP_IDX]); } template<> inline PyTypeObject* SbkType< ::Effect >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_EFFECT_IDX]); } template<> inline PyTypeObject* SbkType< ::PyCoreApplication >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PYCOREAPPLICATION_IDX]); } -template<> inline PyTypeObject* SbkType< ::RectD >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_RECTD_IDX]); } } // namespace Shiboken diff --git a/Engine/NatronEngine/stringparambase_wrapper.cpp b/Engine/NatronEngine/stringparambase_wrapper.cpp index 7ab9cc8936..b4471dadda 100644 --- a/Engine/NatronEngine/stringparambase_wrapper.cpp +++ b/Engine/NatronEngine/stringparambase_wrapper.cpp @@ -120,12 +120,12 @@ static PyObject* Sbk_StringParamBaseFunc_get(PyObject* self, PyObject* args) // Overloaded function decisor // 0: get()const - // 1: get(int)const + // 1: get(double)const if (numArgs == 0) { overloadId = 0; // get()const } else if (numArgs == 1 - && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { - overloadId = 1; // get(int)const + && (pythonToCpp[0] = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArgs[0])))) { + overloadId = 1; // get(double)const } // Function signature not found. @@ -143,13 +143,13 @@ static PyObject* Sbk_StringParamBaseFunc_get(PyObject* self, PyObject* args) } break; } - case 1: // get(int frame) const + case 1: // get(double frame) const { - int cppArg0; + double cppArg0; pythonToCpp[0](pyArgs[0], &cppArg0); if (!PyErr_Occurred()) { - // get(int)const + // get(double)const std::string cppResult = const_cast(cppSelf)->get(cppArg0); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -164,7 +164,7 @@ static PyObject* Sbk_StringParamBaseFunc_get(PyObject* self, PyObject* args) return pyResult; Sbk_StringParamBaseFunc_get_TypeError: - const char* overloads[] = {"", "int", 0}; + const char* overloads[] = {"", "float", 0}; Shiboken::setErrorAboutWrongArguments(args, "NatronEngine.StringParamBase.get", overloads); return 0; } @@ -234,9 +234,9 @@ static PyObject* Sbk_StringParamBaseFunc_getValueAtTime(PyObject* self, PyObject SBK_UNUSED(pythonToCpp) // Overloaded function decisor - // 0: getValueAtTime(int)const - if ((pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArg)))) { - overloadId = 0; // getValueAtTime(int)const + // 0: getValueAtTime(double)const + if ((pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(Shiboken::Conversions::PrimitiveTypeConverter(), (pyArg)))) { + overloadId = 0; // getValueAtTime(double)const } // Function signature not found. @@ -244,11 +244,11 @@ static PyObject* Sbk_StringParamBaseFunc_getValueAtTime(PyObject* self, PyObject // Call function/method { - int cppArg0; + double cppArg0; pythonToCpp(pyArg, &cppArg0); if (!PyErr_Occurred()) { - // getValueAtTime(int)const + // getValueAtTime(double)const std::string cppResult = const_cast(cppSelf)->getValueAtTime(cppArg0); pyResult = Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppResult); } @@ -261,7 +261,7 @@ static PyObject* Sbk_StringParamBaseFunc_getValueAtTime(PyObject* self, PyObject return pyResult; Sbk_StringParamBaseFunc_getValueAtTime_TypeError: - const char* overloads[] = {"int", 0}; + const char* overloads[] = {"float", 0}; Shiboken::setErrorAboutWrongArguments(pyArg, "NatronEngine.StringParamBase.getValueAtTime", overloads); return 0; } diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 0a22ea783d..f4b8394217 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -198,6 +198,8 @@ struct Node::Implementation , activatedMutex() , activated(true) , plugin(plugin_) + , pyPlugID() + , pyPlugVersion(0) , computingPreview(false) , computingPreviewMutex() , pluginInstanceMemoryUsed(0) @@ -239,7 +241,6 @@ struct Node::Implementation , childrenMutex() , children() , multiInstanceParentName() - , duringInputChangedAction(false) , keyframesDisplayedOnTimeline(false) , timersMutex() , lastRenderStartedSlotCallTime() @@ -259,6 +260,7 @@ struct Node::Implementation , pluginPythonModuleMutex() , pluginPythonModule() , pluginPythonModuleVersion(0) + , pyplugChangedSinceScript(false) , nodeCreated(false) , createdComponentsMutex() , createdComponents() @@ -269,17 +271,22 @@ struct Node::Implementation , currentSupportTiles(false) , currentSupportOpenGLRender(Natron::ePluginOpenGLRenderSupportNone) , currentSupportSequentialRender(Natron::eSequentialPreferenceNotSequential) + , draftModeUsed(false) + , mustComputeInputRelatedData(true) , duringPaintStrokeCreation(false) , lastStrokeMovementMutex() , lastStrokeMovementBbox() , strokeBitmapCleared(false) , lastStrokeIndex(-1) + , multiStrokeIndex(0) , strokeImage() , lastStrokePoints() , distToNextIn(0.) , distToNextOut(0.) , useAlpha0ToConvertFromRGBToRGBA(false) , isBeingDestroyed(false) + , inputModifiedRecursion(0) + , inputsModified() { ///Initialize timers gettimeofday(&lastRenderStartedSlotCallTime, 0); @@ -325,6 +332,7 @@ struct Node::Implementation bool getSelectedLayer(int inputNb,const ChannelSelector& selector, ImageComponents* comp) const; + Node* _publicInterface; boost::weak_ptr group; @@ -370,6 +378,8 @@ struct Node::Implementation bool activated; Natron::Plugin* plugin; //< the plugin which stores the function to instantiate the effect + std::string pyPlugID; //< if this is a pyplug, this is the ID of the Plug-in. This is because the plugin handle will be the one of the Group + int pyPlugVersion; bool computingPreview; mutable QMutex computingPreviewMutex; @@ -439,7 +449,6 @@ struct Node::Implementation ///the name of the parent at the time this node was created std::string multiInstanceParentName; - bool duringInputChangedAction; //< true if we're during onInputChanged(...). MT-safe since only modified by the main thread bool keyframesDisplayedOnTimeline; ///This is to avoid the slots connected to the main-thread to be called too much @@ -471,6 +480,9 @@ struct Node::Implementation std::string pluginPythonModule; unsigned int pluginPythonModuleVersion; + //Set to true when the user has edited a PyPlug + bool pyplugChangedSinceScript; + bool nodeCreated; mutable QMutex createdComponentsMutex; @@ -483,12 +495,14 @@ struct Node::Implementation bool currentSupportTiles; Natron::PluginOpenGLRenderSupport currentSupportOpenGLRender; Natron::SequentialPreferenceEnum currentSupportSequentialRender; + bool draftModeUsed,mustComputeInputRelatedData; + bool duringPaintStrokeCreation; // protected by lastStrokeMovementMutex mutable QMutex lastStrokeMovementMutex; RectD lastStrokeMovementBbox; bool strokeBitmapCleared; - int lastStrokeIndex; + int lastStrokeIndex,multiStrokeIndex; ImagePtr strokeImage; std::list > lastStrokePoints; double distToNextIn,distToNextOut; @@ -499,6 +513,14 @@ struct Node::Implementation bool useAlpha0ToConvertFromRGBToRGBA; bool isBeingDestroyed; + + /* + Used to block render emitions while modifying nodes links + MT-safe: only accessed/used on main thread + */ + int inputModifiedRecursion; + std::set inputsModified; + }; /** @@ -584,7 +606,7 @@ Node::load(const std::string & parentMultiInstanceName, boost::shared_ptr thisShared = shared_from_this(); - int renderScaleSupportPreference = appPTR->getCurrentSettings()->getRenderScaleSupportPreference(getPluginID()); + int renderScaleSupportPreference = appPTR->getCurrentSettings()->getRenderScaleSupportPreference(_imp->plugin); LibraryBinary* binary = _imp->plugin->getLibraryBinary(); std::pair func; @@ -594,19 +616,9 @@ Node::load(const std::string & parentMultiInstanceName, bool isFileDialogPreviewReader = fixedName.contains(NATRON_FILE_DIALOG_PREVIEW_READER_NAME); bool nameSet = false; - if (!serialization.isNull()) { - { - QMutexLocker k(&_imp->nameMutex); - _imp->cacheID = serialization.getCacheID(); - } - if (!dontLoadName && !nameSet && fixedName.isEmpty()) { - setScriptName_no_error_check(serialization.getNodeScriptName()); - setLabel(serialization.getNodeLabel()); - nameSet = true; - } - } + - + bool hasUsedFileDialog = false; if (func.first) { _imp->liveInstance.reset(func.second(thisShared)); assert(_imp->liveInstance); @@ -631,6 +643,7 @@ Node::load(const std::string & parentMultiInstanceName, images = getApp()->saveImageFileDialog(); } if (!images.empty()) { + hasUsedFileDialog = true; boost::shared_ptr defaultFile = createDefaultValueForParam(kOfxImageEffectFileParamName, images); CreateNodeArgs::DefaultValuesList list; list.push_back(defaultFile); @@ -638,7 +651,7 @@ Node::load(const std::string & parentMultiInstanceName, } } else { //ofx plugin - _imp->liveInstance = appPTR->createOFXEffect(thisShared,&serialization,paramValues,!isFileDialogPreviewReader && userEdited,renderScaleSupportPreference == 1); + _imp->liveInstance = appPTR->createOFXEffect(thisShared,&serialization,paramValues,!isFileDialogPreviewReader && userEdited,renderScaleSupportPreference == 1, &hasUsedFileDialog); assert(_imp->liveInstance); _imp->liveInstance->initializeOverlayInteract(); } @@ -660,7 +673,17 @@ Node::load(const std::string & parentMultiInstanceName, _imp->isMultiInstance = true; } - + if (!serialization.isNull()) { + { + QMutexLocker k(&_imp->nameMutex); + _imp->cacheID = serialization.getCacheID(); + } + if (!dontLoadName && !nameSet && fixedName.isEmpty()) { + setScriptName_no_error_check(serialization.getNodeScriptName()); + setLabel(serialization.getNodeLabel()); + nameSet = true; + } + } if (!nameSet) { if (fixedName.isEmpty()) { @@ -717,9 +740,35 @@ Node::load(const std::string & parentMultiInstanceName, _imp->nodeCreated = true; - refreshChannelSelectors(serialization.isNull()); + bool isLoadingPyPlug = getApp()->isCreatingPythonGroup(); + + if (!getApp()->getProject()->isLoadingProject() && !isLoadingPyPlug) { + refreshAllInputRelatedData(serialization.isNull()); + } - _imp->runOnNodeCreatedCB(serialization.isNull()); + _imp->runOnNodeCreatedCB(serialization.isNull() && !isLoadingPyPlug); + + + ///Now that the instance is created, make sure instanceChangedActino is called for all extra default values + ///that we set + int time = getLiveInstance()->getCurrentTime(); + for (std::list >::const_iterator it = paramValues.begin(); it != paramValues.end(); ++it) { + boost::shared_ptr knob = getKnobByName((*it)->getName()); + if (knob) { + for (int i = 0; i < knob->getDimension(); ++i) { + knob->evaluateValueChange(i, time, Natron::eValueChangedReasonUserEdited); + } + } else { + qDebug() << "WARNING: No such parameter " << (*it)->getName().c_str(); + } + } + + if (hasUsedFileDialog) { + boost::shared_ptr fileNameKnob = getKnobByName(kOfxImageEffectFileParamName); + if (fileNameKnob) { + fileNameKnob->evaluateValueChange(0, time, Natron::eValueChangedReasonUserEdited); + } + } } // load @@ -824,7 +873,8 @@ Node::refreshDynamicProperties() void Node::updateLastPaintStrokeData(int newAge,const std::list >& points, - const RectD& lastPointsBbox) + const RectD& lastPointsBbox, + int strokeIndex) { { @@ -834,6 +884,7 @@ Node::updateLastPaintStrokeData(int newAge,const std::listlastStrokeIndex = newAge; _imp->distToNextIn = _imp->distToNextOut; _imp->strokeBitmapCleared = false; + _imp->multiStrokeIndex = strokeIndex; } _imp->liveInstance->clearActionsCache(); } @@ -908,16 +959,18 @@ Node::clearLastPaintStrokeRoD() } void -Node::getLastPaintStrokePoints(int time,std::list > >* strokes) const +Node::getLastPaintStrokePoints(int time,std::list > >* strokes,int* strokeIndex) const { QMutexLocker k(&_imp->lastStrokeMovementMutex); if (_imp->duringPaintStrokeCreation) { strokes->push_back(_imp->lastStrokePoints); + *strokeIndex = _imp->multiStrokeIndex; } else { boost::shared_ptr item = _imp->paintStroke.lock(); RotoStrokeItem* stroke = dynamic_cast(item.get()); assert(stroke); stroke->evaluateStroke(0, time, strokes); + *strokeIndex = 0; } } @@ -1813,6 +1866,13 @@ Node::hasOverlay() const if (!_imp->liveInstance) { return false; } + + boost::shared_ptr nodeGui = getNodeGui(); + if (nodeGui) { + if (nodeGui->hasDefaultOverlay()) { + return true; + } + } return _imp->liveInstance->hasOverlay(); } @@ -2362,11 +2422,14 @@ Node::makeCacheInfo() const std::string Node::makeInfoForInput(int inputNumber) const { + if (inputNumber < -1 || inputNumber >= getMaxInputCount()) { + return ""; + } const Natron::Node* inputNode = 0; std::string inputName ; if (inputNumber != -1) { inputNode = getInput(inputNumber).get(); - inputName = _imp->liveInstance->getInputLabel(inputNumber); + inputName = getInputLabel(inputNumber); } else { inputNode = this; inputName = "Output"; @@ -2475,81 +2538,6 @@ Node::initializeKnobs(int renderScaleSupportPref) _imp->nodeSettingsPage = Natron::createKnob(_imp->liveInstance.get(), NATRON_PARAMETER_PAGE_NAME_EXTRA,1,false); if (!isBd) { - - std::vector inputLabels(inputsCount); - std::vector hasMaskChannelSelector(inputsCount); - - for (int i = 0; i < inputsCount; ++i) { - - inputLabels[i] = _imp->liveInstance->getInputLabel(i); - - assert(i < (int)_imp->inputsComponents.size()); - const std::list& inputSupportedComps = _imp->inputsComponents[i]; - - bool isMask = _imp->liveInstance->isInputMask(i); - bool supportsOnlyAlpha = inputSupportedComps.size() == 1 && inputSupportedComps.front().getNumComponents() == 1; - - hasMaskChannelSelector[i] = false; - - if ((isMask || supportsOnlyAlpha) && - !_imp->liveInstance->isInputRotoBrush(i) ) { - - hasMaskChannelSelector[i] = true; - - MaskSelector sel; - boost::shared_ptr enabled = Natron::createKnob(_imp->liveInstance.get(), inputLabels[i],1,false); - - enabled->setDefaultValue(false, 0); - enabled->setAddNewLine(false); - if (isMask) { - std::string enableMaskName(std::string(kEnableMaskKnobName) + "_" + inputLabels[i]); - enabled->setName(enableMaskName); - enabled->setHintToolTip(tr("Enable the mask to come from the channel named by the choice parameter on the right. " - "Turning this off will act as though the mask was disconnected.").toStdString()); - } else { - std::string enableMaskName(std::string(kEnableInputKnobName) + "_" + inputLabels[i]); - enabled->setName(enableMaskName); - enabled->setHintToolTip(tr("Enable the image to come from the channel named by the choice parameter on the right. " - "Turning this off will act as though the input was disconnected.").toStdString()); - } - enabled->setAnimationEnabled(false); - - - sel.enabled = enabled; - - boost::shared_ptr channel = Natron::createKnob(_imp->liveInstance.get(), "",1,false); - - std::vector choices; - choices.push_back("None"); - const ImageComponents& rgba = ImageComponents::getRGBAComponents(); - const std::vector& channels = rgba.getComponentsNames(); - const std::string& layerName = rgba.getComponentsGlobalName(); - for (std::size_t c = 0; c < channels.size(); ++c) { - choices.push_back(layerName + "." + channels[c]); - } - - channel->populateChoices(choices); - channel->setDefaultValue(choices.size() - 1, 0); - channel->setAnimationEnabled(false); - channel->setHintToolTip(tr("Use this channel from the original input to mix the output with the original input. " - "Setting this to None is the same as disconnecting the input.").toStdString()); - if (isMask) { - std::string channelMaskName(std::string(kMaskChannelKnobName) + "_" + inputLabels[i]); - channel->setName(channelMaskName); - } else { - std::string channelMaskName(std::string(kInputChannelKnobName) + "_" + inputLabels[i]); - channel->setName(channelMaskName); - } - sel.channel = channel; - - boost::shared_ptr channelName = Natron::createKnob(_imp->liveInstance.get(), "",1,false); - channelName->setSecretByDefault(true); - channelName->setEvaluateOnChange(false); - sel.channelName = channelName; - _imp->maskSelectors[i] = sel; - - } - } bool isReaderOrWriterOrTrackerOrGroup = _imp->liveInstance->isReader() || _imp->liveInstance->isWriter() || _imp->liveInstance->isTrackerNode() || dynamic_cast(_imp->liveInstance.get()); @@ -2559,7 +2547,7 @@ Node::initializeKnobs(int renderScaleSupportPref) ///find in all knobs a page param to set this param into boost::shared_ptr mainPage; const std::vector< boost::shared_ptr > & knobs = _imp->liveInstance->getKnobs(); - + if (!isReaderOrWriterOrTrackerOrGroup) { for (U32 i = 0; i < knobs.size(); ++i) { boost::shared_ptr p = boost::dynamic_pointer_cast(knobs[i]); @@ -2574,29 +2562,61 @@ Node::initializeKnobs(int renderScaleSupportPref) } assert(mainPage); } + + ///Pair hasMaskChannelSelector, isMask + std::vector > hasMaskChannelSelector(inputsCount); + std::vector inputLabels(inputsCount); + for (int i = 0; i < inputsCount; ++i) { + inputLabels[i] = _imp->liveInstance->getInputLabel(i); + + assert(i < (int)_imp->inputsComponents.size()); + const std::list& inputSupportedComps = _imp->inputsComponents[i]; + + bool isMask = _imp->liveInstance->isInputMask(i); + bool supportsOnlyAlpha = inputSupportedComps.size() == 1 && inputSupportedComps.front().getNumComponents() == 1; + + hasMaskChannelSelector[i].first = false; + hasMaskChannelSelector[i].second = isMask; + + if ((isMask || supportsOnlyAlpha) && + !_imp->liveInstance->isInputRotoBrush(i) ) { + hasMaskChannelSelector[i].first = true; + } + } + + if (useChannels) { + + bool useSelectors = !dynamic_cast(_imp->liveInstance.get()); + if (useSelectors) { - //There are a A and B inputs and the plug-in is not multi-planar, propose 2 layer selectors for the inputs. + boost::shared_ptr sep = Natron::createKnob(_imp->liveInstance.get(), "Advanced", 1, false); + mainPage->addKnob(sep); + + ///Create input layer selectors for (int i = 0; i < inputsCount; ++i) { - if (!hasMaskChannelSelector[i]) { + if (!hasMaskChannelSelector[i].first) { _imp->createChannelSelector(i,inputLabels[i], false, mainPage); } } + ///Create output layer selectors _imp->createChannelSelector(-1, "Output", true, mainPage); } - - //Try to find R,G,B,A parameters on the plug-in, if found, use them, otherwise create them - std::string channelLabels[4] = {kNatronOfxParamProcessRLabel, kNatronOfxParamProcessGLabel, kNatronOfxParamProcessBLabel, kNatronOfxParamProcessALabel}; - std::string channelNames[4] = {kNatronOfxParamProcessR, kNatronOfxParamProcessG, kNatronOfxParamProcessB, kNatronOfxParamProcessA}; - std::string channelHints[4] = {kNatronOfxParamProcessRHint, kNatronOfxParamProcessGHint, kNatronOfxParamProcessBHint, kNatronOfxParamProcessAHint}; - - - bool pluginDefaultPref[4]; + + + + //Try to find R,G,B,A parameters on the plug-in, if found, use them, otherwise create them + std::string channelLabels[4] = {kNatronOfxParamProcessRLabel, kNatronOfxParamProcessGLabel, kNatronOfxParamProcessBLabel, kNatronOfxParamProcessALabel}; + std::string channelNames[4] = {kNatronOfxParamProcessR, kNatronOfxParamProcessG, kNatronOfxParamProcessB, kNatronOfxParamProcessA}; + std::string channelHints[4] = {kNatronOfxParamProcessRHint, kNatronOfxParamProcessGHint, kNatronOfxParamProcessBHint, kNatronOfxParamProcessAHint}; + + + bool pluginDefaultPref[4]; bool useRGBACheckbox = _imp->liveInstance->isHostChannelSelectorSupported(&pluginDefaultPref[0], &pluginDefaultPref[1], &pluginDefaultPref[2], &pluginDefaultPref[3]); boost::shared_ptr foundEnabled[4]; for (int i = 0; i < 4; ++i) { @@ -2608,11 +2628,15 @@ Node::initializeKnobs(int renderScaleSupportPref) } } } + if (foundEnabled[0] && foundEnabled[1] && foundEnabled[2] && foundEnabled[3]) { - _imp->enabledChan[0] = foundEnabled[0]; - _imp->enabledChan[1] = foundEnabled[1]; - _imp->enabledChan[2] = foundEnabled[2]; - _imp->enabledChan[3] = foundEnabled[3]; + for (int i = 0; i < 4; ++i) { + if (foundEnabled[i]->getParentKnob() == mainPage) { + mainPage->removeKnob(foundEnabled[i].get()); + mainPage->addKnob(foundEnabled[i]); + } + _imp->enabledChan[i] = foundEnabled[i]; + } } #ifdef DEBUG if (foundEnabled[0] && foundEnabled[1] && foundEnabled[2] && foundEnabled[3] && useRGBACheckbox) { @@ -2627,13 +2651,110 @@ Node::initializeKnobs(int renderScaleSupportPref) foundEnabled[i]->setAddNewLine(i == 3); foundEnabled[i]->setDefaultValue(pluginDefaultPref[i]); foundEnabled[i]->setHintToolTip(channelHints[i]); - mainPage->insertKnob(i,foundEnabled[i]); + mainPage->addKnob(foundEnabled[i]); _imp->enabledChan[i] = foundEnabled[i]; } } + } // useChannels - //Create the mix + ///Find in the plug-in the Mask/Mix related parameter to re-order them so it is consistent across nodes + std::vector > > foundPluginDefaultKnobsToReorder; + foundPluginDefaultKnobsToReorder.push_back(std::make_pair(kOfxMaskInvertParamName, boost::shared_ptr())); + foundPluginDefaultKnobsToReorder.push_back(std::make_pair(kOfxMixParamName, boost::shared_ptr())); + if (mainPage) { + ///Insert auto-added knobs before mask invert if found + for (std::size_t i = 0; i < knobs.size(); ++i) { + for (std::size_t j = 0; j < foundPluginDefaultKnobsToReorder.size(); ++j) { + if (knobs[i]->getName() == foundPluginDefaultKnobsToReorder[j].first) { + foundPluginDefaultKnobsToReorder[j].second = knobs[i]; + } + } + } + } + + ///Create mask selectors + for (int i = 0; i < inputsCount; ++i) { + + if (!hasMaskChannelSelector[i].first) { + continue; + } + + + MaskSelector sel; + boost::shared_ptr enabled = Natron::createKnob(_imp->liveInstance.get(), inputLabels[i],1,false); + + enabled->setDefaultValue(false, 0); + enabled->setAddNewLine(false); + if (hasMaskChannelSelector[i].second) { + std::string enableMaskName(std::string(kEnableMaskKnobName) + "_" + inputLabels[i]); + enabled->setName(enableMaskName); + enabled->setHintToolTip(tr("Enable the mask to come from the channel named by the choice parameter on the right. " + "Turning this off will act as though the mask was disconnected.").toStdString()); + } else { + std::string enableMaskName(std::string(kEnableInputKnobName) + "_" + inputLabels[i]); + enabled->setName(enableMaskName); + enabled->setHintToolTip(tr("Enable the image to come from the channel named by the choice parameter on the right. " + "Turning this off will act as though the input was disconnected.").toStdString()); + } + enabled->setAnimationEnabled(false); + if (mainPage) { + mainPage->addKnob(enabled); + } + + + sel.enabled = enabled; + + boost::shared_ptr channel = Natron::createKnob(_imp->liveInstance.get(), "",1,false); + + std::vector choices; + choices.push_back("None"); + const ImageComponents& rgba = ImageComponents::getRGBAComponents(); + const std::vector& channels = rgba.getComponentsNames(); + const std::string& layerName = rgba.getComponentsGlobalName(); + for (std::size_t c = 0; c < channels.size(); ++c) { + choices.push_back(layerName + "." + channels[c]); + } + + channel->populateChoices(choices); + channel->setDefaultValue(choices.size() - 1, 0); + channel->setAnimationEnabled(false); + channel->setHintToolTip(tr("Use this channel from the original input to mix the output with the original input. " + "Setting this to None is the same as disconnecting the input.").toStdString()); + if (hasMaskChannelSelector[i].second) { + std::string channelMaskName(std::string(kMaskChannelKnobName) + "_" + inputLabels[i]); + channel->setName(channelMaskName); + } else { + std::string channelMaskName(std::string(kInputChannelKnobName) + "_" + inputLabels[i]); + channel->setName(channelMaskName); + } + sel.channel = channel; + channel->setAddNewLine(false); + if (mainPage) { + mainPage->addKnob(channel); + } + + boost::shared_ptr channelName = Natron::createKnob(_imp->liveInstance.get(), "",1,false); + channelName->setSecretByDefault(true); + channelName->setEvaluateOnChange(false); + if (mainPage) { + mainPage->addKnob(channelName); + } + sel.channelName = channelName; + + //Make sure the first default param in the vector is MaskInvert + assert(foundPluginDefaultKnobsToReorder.size() > 0 && foundPluginDefaultKnobsToReorder[0].first == kOfxMaskInvertParamName); + if (foundPluginDefaultKnobsToReorder[0].second) { + //If there is a MaskInvert parameter, make it on the same line as the Mask channel parameter + channelName->setAddNewLine(false); + } + + + _imp->maskSelectors[i] = sel; + + } // for (int i = 0; i < inputsCount; ++i) { + + //Create the host mix if needed if (!isReaderOrWriterOrTrackerOrGroup && _imp->liveInstance->isHostMixingEnabled()) { boost::shared_ptr mixKnob = Natron::createKnob(_imp->liveInstance.get(), "Mix", 1, false); mixKnob->setName("hostMix"); @@ -2641,9 +2762,25 @@ Node::initializeKnobs(int renderScaleSupportPref) mixKnob->setMinimum(0.); mixKnob->setMaximum(1.); mixKnob->setDefaultValue(1.); - mainPage->addKnob(mixKnob); + if (mainPage) { + mainPage->addKnob(mixKnob); + } _imp->mixWithSource = mixKnob; } + + + /* + * Reposition the MaskInvert and Mix parameters declared by the plug-in + */ + if (mainPage) { + for (std::size_t i = 0; i < foundPluginDefaultKnobsToReorder.size(); ++i) { + if (foundPluginDefaultKnobsToReorder[i].second && foundPluginDefaultKnobsToReorder[i].second->getParentKnob() == mainPage) { + mainPage->removeKnob(foundPluginDefaultKnobsToReorder[i].second.get()); + mainPage->addKnob(foundPluginDefaultKnobsToReorder[i].second); + } + } + } + } // !isBd boost::shared_ptr nodeLabel = Natron::createKnob(_imp->liveInstance.get(), isBd ? tr("Name label").toStdString() : tr("Label").toStdString(),1,false); @@ -3052,7 +3189,7 @@ Node::hasOutputNodesConnected(std::list* writers { Natron::OutputEffectInstance* thisWriter = dynamic_cast(_imp->liveInstance.get()); - if (thisWriter) { + if (thisWriter && thisWriter->isOutput() && !dynamic_cast(thisWriter)) { std::list::const_iterator alreadyExists = std::find(writers->begin(), writers->end(), thisWriter); if ( alreadyExists == writers->end() ) { writers->push_back(thisWriter); @@ -3072,14 +3209,23 @@ int Node::getMajorVersion() const { ///Thread safe as it never changes - return _imp->liveInstance->getMajorVersion(); + if (!_imp->pyPlugID.empty()) { + return _imp->pyPlugVersion; + } + if (!_imp->plugin) { + return 0; + } + return _imp->plugin->getMajorVersion(); } int Node::getMinorVersion() const { ///Thread safe as it never changes - return _imp->liveInstance->getMinorVersion(); + if (!_imp->plugin) { + return 0; + } + return _imp->plugin->getMinorVersion(); } void @@ -3093,6 +3239,10 @@ Node::initializeInputs() { QMutexLocker l(&_imp->inputsMutex); oldInputs = _imp->inputs; + if ((int)oldInputs.size() == inputCount) { + _imp->inputsInitialized = true; + return; + } _imp->inputs.resize(inputCount); _imp->guiInputs.resize(inputCount); _imp->inputLabels.resize(inputCount); @@ -3119,6 +3269,27 @@ Node::initializeInputs() _imp->liveInstance->addAcceptedComponents(-1, &_imp->outputComponents); } _imp->inputsInitialized = true; + boost::shared_ptr infoPage = _imp->infoPage.lock(); + if (infoPage) { + _imp->inputFormats.clear(); + for (int i = 0; i < inputCount; ++i) { + std::string inputLabel = getInputLabel(i); + boost::shared_ptr inputInfo = Natron::createKnob(_imp->liveInstance.get(), inputLabel + ' ' + tr("Info").toStdString(), 1, false); + inputInfo->setName(inputLabel + "Info"); + inputInfo->setAnimationEnabled(false); + inputInfo->setIsPersistant(false); + inputInfo->setEvaluateOnChange(false); + inputInfo->setSecretByDefault(true); + inputInfo->hideDescription(); + inputInfo->setAsLabel(); + _imp->inputFormats.push_back(inputInfo); + infoPage->insertKnob(1 + i,inputInfo); + } + if (inputCount > 0) { + _imp->liveInstance->refreshKnobs(); + } + } + Q_EMIT inputsInitialized(); } @@ -3567,8 +3738,12 @@ Node::connectInput(const boost::shared_ptr & input, ///Notify the GUI Q_EMIT inputChanged(inputNumber); + bool mustCallEnd = false; + if (!useGuiInputs) { ///Call the instance changed action with a reason clip changed + beginInputEdition(); + mustCallEnd = true; onInputChanged(inputNumber); } @@ -3582,6 +3757,10 @@ Node::connectInput(const boost::shared_ptr & input, _imp->runInputChangedCallback(inputNumber, inputChangedCB); } + if (mustCallEnd) { + endInputEdition(true); + } + return true; } @@ -3654,7 +3833,10 @@ Node::replaceInput(const boost::shared_ptr& input,int inputNumber) ///Notify the GUI Q_EMIT inputChanged(inputNumber); + bool mustCallEnd = false; if (!useGuiInputs) { + beginInputEdition(); + mustCallEnd = true; ///Call the instance changed action with a reason clip changed onInputChanged(inputNumber); } @@ -3669,7 +3851,9 @@ Node::replaceInput(const boost::shared_ptr& input,int inputNumber) _imp->runInputChangedCallback(inputNumber, inputChangedCB); } - + if (mustCallEnd) { + endInputEdition(true); + } return true; } @@ -3741,9 +3925,13 @@ Node::switchInput0And1() } Q_EMIT inputChanged(inputAIndex); Q_EMIT inputChanged(inputBIndex); + bool mustCallEnd = false; if (!useGuiInputs) { + beginInputEdition(); + mustCallEnd = true; onInputChanged(inputAIndex); onInputChanged(inputBIndex); + } computeHash(); @@ -3755,6 +3943,10 @@ Node::switchInput0And1() _imp->ifGroupForceHashChangeOfInputs(); + + if (mustCallEnd) { + endInputEdition(true); + } } // switchInput0And1 @@ -3812,7 +4004,10 @@ Node::disconnectInput(int inputNumber) NodePtr inputShared; bool useGuiValues = isNodeRendering(); - _imp->liveInstance->abortAnyEvaluation(); + + if (!_imp->isBeingDestroyed) { + _imp->liveInstance->abortAnyEvaluation(); + } { QMutexLocker l(&_imp->inputsMutex); @@ -3844,7 +4039,10 @@ Node::disconnectInput(int inputNumber) } Q_EMIT inputChanged(inputNumber); + bool mustCallEnd = false; if (!useGuiValues) { + beginInputEdition(); + mustCallEnd= true; onInputChanged(inputNumber); } computeHash(); @@ -3855,6 +4053,9 @@ Node::disconnectInput(int inputNumber) if (!inputChangedCB.empty()) { _imp->runInputChangedCallback(inputNumber, inputChangedCB); } + if (mustCallEnd) { + endInputEdition(true); + } return inputNumber; } @@ -3902,7 +4103,10 @@ Node::disconnectInput(Node* input) } input->disconnectOutput(useGuiValues,this); Q_EMIT inputChanged(found); + bool mustCallEnd = false; if (!useGuiValues) { + beginInputEdition(); + mustCallEnd = true; onInputChanged(found); } computeHash(); @@ -3914,6 +4118,10 @@ Node::disconnectInput(Node* input) _imp->runInputChangedCallback(found, inputChangedCB); } + if (mustCallEnd) { + endInputEdition(true); + } + return found; } @@ -4131,13 +4339,15 @@ Node::deactivate(const std::list< Node* > & outputsToDisconnect, } if (dc) { int inputNb = (*it)->getInputIndex(this); - _imp->deactivatedState.insert( make_pair(*it, inputNb) ); - - ///reconnect if inputToConnectTo is not null - if (inputToConnectTo) { - (*it)->replaceInput(inputToConnectTo, inputNb); - } else { - ignore_result((*it)->disconnectInput(this)); + if (inputNb != -1) { + _imp->deactivatedState.insert( make_pair(*it, inputNb) ); + + ///reconnect if inputToConnectTo is not null + if (inputToConnectTo) { + (*it)->replaceInput(inputToConnectTo, inputNb); + } else { + ignore_result((*it)->disconnectInput(this)); + } } } } @@ -4492,7 +4702,7 @@ Node::makePreviewImage(SequenceTime time, renderWindow, rod, requestedComps, //< preview is always rgb... - getBitDepth(), effect) ,&planes); + getBitDepth(), false, effect) ,&planes); if (retCode != Natron::EffectInstance::eRenderRoIRetCodeOk) { return false; } @@ -4628,7 +4838,11 @@ Node::getPluginID() const if (!_imp->plugin) { return std::string(); } - return _imp->plugin->getPluginID().toStdString(); + if (!_imp->pyPlugID.empty()) { + return _imp->pyPlugID; + } else { + return _imp->plugin->getPluginID().toStdString(); + } } std::string @@ -5164,7 +5378,7 @@ Node::findClosestInList(const Natron::ImageComponents& comp, } else { int diff = it->getNumComponents() - comp.getNumComponents(); int diffSoFar = closestComp->getNumComponents() - comp.getNumComponents(); - if (diff > diffSoFar) { + if (diff > 0 && diff < diffSoFar) { closestComp = it; } } @@ -5307,6 +5521,43 @@ Node::getImageBeingRendered(int time, return boost::shared_ptr(); } +void +Node::beginInputEdition() +{ + assert( QThread::currentThread() == qApp->thread() ); + ++_imp->inputModifiedRecursion; +} + +void +Node::endInputEdition(bool triggerRender) +{ + assert( QThread::currentThread() == qApp->thread() ); + if (_imp->inputModifiedRecursion > 0) { + --_imp->inputModifiedRecursion; + } + + if (!_imp->inputModifiedRecursion) { + + bool hasChanged = !_imp->inputsModified.empty(); + _imp->inputsModified.clear(); + + if (hasChanged) { + forceRefreshAllInputRelatedData(); + refreshDynamicProperties(); + } + + triggerRender = triggerRender && hasChanged; + + if (triggerRender) { + std::list viewers; + hasViewersConnected(&viewers); + for (std::list::iterator it2 = viewers.begin(); it2 != viewers.end(); ++it2) { + (*it2)->renderCurrentFrame(true); + } + } + } +} + void Node::onInputChanged(int inputNb) { @@ -5314,42 +5565,69 @@ Node::onInputChanged(int inputNb) return; } assert( QThread::currentThread() == qApp->thread() ); - _imp->duringInputChangedAction = true; - std::map::iterator found = _imp->maskSelectors.find(inputNb); - boost::shared_ptr inp = getInput(inputNb); - if ( found != _imp->maskSelectors.end() ) { - boost::shared_ptr enabled = found->second.enabled.lock(); - assert(enabled); - enabled->blockValueChanges(); - enabled->setValue(inp ? true : false, 0); - enabled->unblockValueChanges(); + + bool mustCallEndInputEdition = _imp->inputModifiedRecursion == 0; + if (mustCallEndInputEdition) { + beginInputEdition(); } - std::map::iterator foundChan = _imp->channelsSelectors.find(inputNb); - if ( foundChan != _imp->channelsSelectors.end() ) { - std::map::iterator foundOuptut = _imp->channelsSelectors.find(-1); - bool outputIsAll = false; - if (foundOuptut != _imp->channelsSelectors.end()) { - boost::shared_ptr outputChoice = foundOuptut->second.layer.lock(); - if (outputChoice) { - outputIsAll = outputChoice->getActiveEntryText_mt_safe() == "All"; - } - } - boost::shared_ptr chanChoice = foundChan->second.layer.lock(); - if (chanChoice) { - chanChoice->setSecret(!inp.get() || outputIsAll); - } + + refreshMaskEnabledNess(inputNb); + refreshLayersChoiceSecretness(inputNb); + + ViewerInstance* isViewer = dynamic_cast(_imp->liveInstance.get()); + if (isViewer) { + isViewer->refreshActiveInputs(inputNb); + } + + bool shouldDoInputChanged = (!getApp()->getProject()->isProjectClosing() && !getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) || + _imp->liveInstance->isRotoPaintNode(); + + if (shouldDoInputChanged) { + + ///When loading a group (or project) just wait until everything is setup to actually compute input + ///related data such as clip preferences + ///Exception for the Rotopaint node which needs to setup its own graph internally + + /** + * The plug-in might call getImage, set a valid thread storage on the tree. + **/ + double time = getApp()->getTimeLine()->currentFrame(); + ParallelRenderArgsSetter frameRenderArgs(getApp()->getProject().get(), + time, + 0 /*view*/, + true, + false, + false, + 0, + shared_from_this(), + 0, + 0, //texture index + getApp()->getTimeLine().get(), + NodePtr(), + false, + false, + false, + boost::shared_ptr()); + + + ///Don't do clip preferences while loading a project, they will be refreshed globally once the project is loaded. + _imp->liveInstance->onInputChanged(inputNb); + _imp->inputsModified.insert(inputNb); + } + + + if (mustCallEndInputEdition) { + endInputEdition(true); } - _imp->liveInstance->onInputChanged(inputNb); - _imp->liveInstance->refreshChannelSelectors_recursive(); - _imp->duringInputChangedAction = false; + } void Node::onParentMultiInstanceInputChanged(int input) { - _imp->duringInputChangedAction = true; + ++_imp->inputModifiedRecursion; _imp->liveInstance->onInputChanged(input); - _imp->duringInputChangedAction = false; + --_imp->inputModifiedRecursion; } @@ -5358,7 +5636,7 @@ Node::duringInputChangedAction() const { assert( QThread::currentThread() == qApp->thread() ); - return _imp->duringInputChangedAction; + return _imp->inputModifiedRecursion > 0; } void @@ -5567,16 +5845,37 @@ Node::setPluginDescription(const std::string& description) void Node::setPluginIDAndVersionForGui(const std::string& pluginLabel,const std::string& pluginID,unsigned int version) { + + assert(QThread::currentThread() == qApp->thread()); boost::shared_ptr nodeGui = getNodeGui(); if (!nodeGui) { return; } + + _imp->pyPlugVersion = version; + _imp->pyPlugID = pluginID; + nodeGui->setPluginIDAndVersion(pluginLabel,pluginID, version); } -void -Node::setPluginPythonModule(const std::string& pythonModule, unsigned int version) +bool +Node::hasPyPlugBeenEdited() const +{ + QMutexLocker k(&_imp->pluginPythonModuleMutex); + return _imp->pyplugChangedSinceScript || _imp->pluginPythonModule.empty(); +} + +void +Node::setPyPlugEdited(bool edited) +{ + QMutexLocker k(&_imp->pluginPythonModuleMutex); + _imp->pyplugChangedSinceScript = edited; + _imp->pyPlugID.clear(); +} + +void +Node::setPluginPythonModule(const std::string& pythonModule, unsigned int version) { QMutexLocker k(&_imp->pluginPythonModuleMutex); _imp->pluginPythonModule = pythonModule; @@ -5655,7 +5954,11 @@ Node::onEffectKnobValueChanged(KnobI* what, KnobString* strKnob = dynamic_cast(what); if (strKnob) { QString operation = strKnob->getValue().c_str(); - replaceCustomDataInlabel('(' + operation + ')'); + if (!operation.isEmpty()) { + operation.prepend("("); + operation.append(")"); + } + replaceCustomDataInlabel(operation); } } else if ( (what->getName() == kOfxImageEffectFileParamName) && _imp->liveInstance->isReader() ) { ///Refresh the preview automatically if the filename changed @@ -5722,6 +6025,18 @@ Node::onEffectKnobValueChanged(KnobI* what, } } +bool +Node::getSelectedLayer(int inputNb,std::string& layer) const +{ + std::map::iterator found = _imp->channelsSelectors.find(inputNb); + if (found == _imp->channelsSelectors.end()) { + return false; + } + boost::shared_ptr layerKnob = found->second.layer.lock(); + layer = layerKnob->getActiveEntryText_mt_safe(); + return true; +} + bool Node::Implementation::getSelectedLayer(int inputNb,const ChannelSelector& selector, ImageComponents* comp) const { @@ -5798,25 +6113,20 @@ Node::Implementation::onLayerChanged(int inputNb,const ChannelSelector& selector selector.layerName.lock()->setValue(entries[curLayer_i], 0); if (inputNb == -1) { - bool isAll = entries[curLayer_i] == "All"; - if (isAll) { - ///Disable all input selectors as it doesn't make sense to edit them whilst output is All - for (std::map::iterator it = channelsSelectors.begin(); it != channelsSelectors.end(); ++it) { - if (it->first >= 0) { - it->second.layer.lock()->setSecret(true); - } - } - } else { - for (std::map::iterator it = channelsSelectors.begin(); it != channelsSelectors.end(); ++it) { - if (it->first >= 0) { - it->second.layer.lock()->setSecret(false); - } + bool outputIsAll = entries[curLayer_i] == "All"; + + ///Disable all input selectors as it doesn't make sense to edit them whilst output is All + for (std::map::iterator it = channelsSelectors.begin(); it != channelsSelectors.end(); ++it) { + if (it->first >= 0) { + boost::shared_ptr inp = _publicInterface->getInput(it->first); + bool mustBeSecret = !inp.get() || outputIsAll; + it->second.layer.lock()->setSecret(mustBeSecret); } - } + } { - ///Clip preferences have changed + ///Clip preferences have changed RenderScale s; s.x = s.y = 1; liveInstance->checkOFXClipPreferences_public(_publicInterface->getApp()->getTimeLine()->currentFrame(), @@ -5847,6 +6157,10 @@ Node::Implementation::onLayerChanged(int inputNb,const ChannelSelector& selector enabled->setValue(true, 0); } } + + if (inputNb == -1) { + _publicInterface->s_outputLayerChanged(); + } } void @@ -5868,7 +6182,10 @@ Node::Implementation::onMaskSelectorChanged(int inputNb,const MaskSelector& sele std::vector entries = channel->getEntries_mt_safe(); int curChan_i = channel->getValue(); - assert(curChan_i >= 0 && curChan_i < (int)entries.size()); + if (curChan_i < 0 || curChan_i >= (int)entries.size()) { + _publicInterface->refreshChannelSelectors(true); + return; + } selector.channelName.lock()->setValue(entries[curChan_i], 0); { ///Clip preferences have changed @@ -6378,20 +6695,12 @@ Node::dequeueActions() _imp->outputs = _imp->guiOutputs; } + beginInputEdition(); for (std::set::iterator it = inputChanges.begin(); it!=inputChanges.end(); ++it) { onInputChanged(*it); } - - if (!inputChanges.empty()) { - std::list viewers; - hasViewersConnected(&viewers); - for (std::list::iterator it = viewers.begin(); - it != viewers.end(); - ++it) { - (*it)->renderCurrentFrame(true); - } - } - + endInputEdition(true); + { QMutexLocker k(&_imp->nodeIsDequeuingMutex); _imp->nodeIsDequeuing = false; @@ -6399,31 +6708,69 @@ Node::dequeueActions() } } -bool -Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const +static void addIdentityNodesRecursively(const Node* caller, + const Node* node, + double time, + int view, + std::list* outputs, + std::list* markedNodes) { - /* - * Here is a list of reasons when caching is enabled for a node: - * - It is references multiple times below in the graph - * - Its single output has its settings panel opened, meaning the user is actively editing the output - * - The force caching parameter in the "Node" tab is checked - * - The aggressive caching preference of Natron is checked - * - We are in a recursive action (such as an analysis) - * - The plug-in does temporal clip access - * - Preview image is enabled (and Natron is not running in background) - * - The node is a direct input of a viewer, this is to overcome linear graphs where all nodes would not be cached - * - The node is not frame varying, meaning it will always produce the same image at any time - * - The node is a roto node and it is being edited - * - The node does not support tiles - */ + if (std::find(markedNodes->begin(), markedNodes->end(), node) != markedNodes->end()) { + return; + } + + markedNodes->push_back(node); - std::list outputs; - { - QMutexLocker k(&_imp->outputsMutex); - outputs = _imp->outputs; + + if (caller != node) { + + const ParallelRenderArgs* inputFrameArgs = node->getLiveInstance()->getParallelRenderArgsTLS(); + const FrameViewRequest* request = 0; + bool isIdentity = false; + if (inputFrameArgs && inputFrameArgs->request) { + request = inputFrameArgs->request->getFrameViewRequest(time, view); + if (request) { + isIdentity = request->globalData.identityInputNb != -1; + } + } + + if (!request) { + + /* + Very unlikely that there's no request pass. But we still check + */ + RenderScale scale; + scale.x = scale.y = 1; + double inputTimeId; + int inputNbId; + U64 renderHash; + + renderHash = node->getLiveInstance()->getRenderHash(); + + RectD rod; + bool isProj; + Natron::StatusEnum stat = node->getLiveInstance()->getRegionOfDefinition_public(renderHash, time, scale, view, &rod, &isProj); + if (stat == eStatusFailed) { + isIdentity = false; + } else { + RectI pixelRod; + rod.toPixelEnclosing(scale, node->getLiveInstance()->getPreferredAspectRatio(), &pixelRod); + isIdentity = node->getLiveInstance()->isIdentity_public(true, renderHash, time, scale, pixelRod, view, &inputTimeId, &inputNbId); + } + } + + + if (!isIdentity) { + outputs->push_back(node); + return; + } } + + ///Append outputs of this node instead + std::list nodeOutputs; + node->getOutputs_mt_safe(nodeOutputs); std::list outputsToAdd; - for (std::list::iterator it = outputs.begin(); it != outputs.end(); ++it) { + for (std::list::iterator it = nodeOutputs.begin(); it != nodeOutputs.end(); ++it) { GroupOutput* isOutputNode = dynamic_cast((*it)->getLiveInstance()); //If the node is an output node, add all the outputs of the group node instead if (isOutputNode) { @@ -6439,17 +6786,45 @@ Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const } } } - outputs.insert(outputs.end(), outputsToAdd.begin(),outputsToAdd.end()); + nodeOutputs.insert(nodeOutputs.end(), outputsToAdd.begin(),outputsToAdd.end()); + for (std::list::iterator it = nodeOutputs.begin(); it!=nodeOutputs.end(); ++it) { + addIdentityNodesRecursively(caller,*it,time,view,outputs, markedNodes); + } +} + +bool +Node::shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const +{ + /* + * Here is a list of reasons when caching is enabled for a node: + * - It is references multiple times below in the graph + * - Its single output has its settings panel opened, meaning the user is actively editing the output + * - The force caching parameter in the "Node" tab is checked + * - The aggressive caching preference of Natron is checked + * - We are in a recursive action (such as an analysis) + * - The plug-in does temporal clip access + * - Preview image is enabled (and Natron is not running in background) + * - The node is a direct input of a viewer, this is to overcome linear graphs where all nodes would not be cached + * - The node is not frame varying, meaning it will always produce the same image at any time + * - The node is a roto node and it is being edited + * - The node does not support tiles + */ + + std::list outputs; + { + std::list markedNodes; + addIdentityNodesRecursively(this, this, time, view,&outputs,&markedNodes); + } + std::size_t sz = outputs.size(); if (sz > 1) { ///The node is referenced multiple times below, cache it return true; } else { - boost::shared_ptr attachedStroke = _imp->paintStroke.lock(); if (sz == 1) { - Node* output = outputs.front(); + const Node* output = outputs.front(); ViewerInstance* isViewer = dynamic_cast(output->getLiveInstance()); if (isViewer) { @@ -6457,28 +6832,364 @@ Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const isViewer->getActiveInputs(activeInputs[0], activeInputs[1]); if (output->getInput(activeInputs[0]).get() == this || output->getInput(activeInputs[1]).get() == this) { + ///The node is a direct input of the viewer. Cache it because it is likely the user will make + ///changes to the viewer that will need this image. return true; } } + + if (!isFrameVaryingOrAnimated) { + //This image never changes, cache it once. + return true; + } + if (output->isSettingsPanelOpened()) { + //Output node has panel opened, meaning the user is likely to be heavily editing + //that output node, hence requesting this node a lot. Cache it. + return true; + } + if (_imp->liveInstance->doesTemporalClipAccess()) { + //Very heavy to compute since many frames are fetched upstream. Cache it. + return true; + } + if (!_imp->liveInstance->supportsTiles()) { + //No tiles, image is going to be produced fully, cache it to prevent multiple access + //with different RoIs + return true; + } + if (_imp->liveInstance->getRecursionLevel() > 0) { + //We are in a call from getImage() and the image needs to be computed, so likely in an + //analysis pass. Cache it because the image is likely to get asked for severla times. + return true; + } + if (isForceCachingEnabled()) { + //Users wants it cached + return true; + } + NodeGroup* parentIsGroup = dynamic_cast(getGroup().get()); + if (parentIsGroup && parentIsGroup->getNode()->isForceCachingEnabled() && parentIsGroup->getOutputNodeInput(false).get() == this) { + //if the parent node is a group and it has its force caching enabled, cache the output of the Group Output's node input. + return true; + } + + if (appPTR->isAggressiveCachingEnabled()) { + ///Users wants all nodes cached + return true; + } + + if (isPreviewEnabled() && !appPTR->isBackground()) { + ///The node has a preview, meaning the image will be computed several times between previews & actual renders. Cache it. + return true; + } + + if (isRotoPaintingNode() && isSettingsPanelOpened()) { + ///The Roto node is being edited, cache its output (special case because Roto has an internal node tree) + return true; + } + + boost::shared_ptr attachedStroke = _imp->paintStroke.lock(); + if (attachedStroke && attachedStroke->getContext()->getNode()->isSettingsPanelOpened()) { + ///Internal RotoPaint tree and the Roto node has its settings panel opened, cache it. + return true; + } - return !isFrameVaryingOrAnimated || - output->isSettingsPanelOpened() || - _imp->liveInstance->doesTemporalClipAccess() || - ! _imp->liveInstance->supportsTiles() || - _imp->liveInstance->getRecursionLevel() > 0 || - isForceCachingEnabled() || - appPTR->isAggressiveCachingEnabled() || - (isPreviewEnabled() && !appPTR->isBackground()) || - (getRotoContext() && isSettingsPanelOpened()) || - (attachedStroke && attachedStroke->getContext()->getNode()->isSettingsPanelOpened()); } else { // outputs == 0, never cache, unless explicitly set or rotopaint internal node + boost::shared_ptr attachedStroke = _imp->paintStroke.lock(); return isForceCachingEnabled() || appPTR->isAggressiveCachingEnabled() || (attachedStroke && attachedStroke->getContext()->getNode()->isSettingsPanelOpened()); } } + return false; +} + +bool +Node::refreshLayersChoiceSecretness(int inputNb) +{ + std::map::iterator foundChan = _imp->channelsSelectors.find(inputNb); + boost::shared_ptr inp = getInput(inputNb); + if ( foundChan != _imp->channelsSelectors.end() ) { + std::map::iterator foundOuptut = _imp->channelsSelectors.find(-1); + bool outputIsAll = false; + if (foundOuptut != _imp->channelsSelectors.end()) { + boost::shared_ptr outputChoice = foundOuptut->second.layer.lock(); + if (outputChoice) { + outputIsAll = outputChoice->getActiveEntryText_mt_safe() == "All"; + } + } + boost::shared_ptr chanChoice = foundChan->second.layer.lock(); + if (chanChoice) { + bool isSecret = chanChoice->getIsSecret(); + bool mustBeSecret = !inp.get() || outputIsAll; + bool changed = isSecret != mustBeSecret; + if (changed) { + chanChoice->setSecret(mustBeSecret); + return true; + } + } + } + return false; +} + +bool +Node::refreshMaskEnabledNess(int inputNb) +{ + std::map::iterator found = _imp->maskSelectors.find(inputNb); + boost::shared_ptr inp = getInput(inputNb); + bool changed = false; + if ( found != _imp->maskSelectors.end() ) { + boost::shared_ptr enabled = found->second.enabled.lock(); + assert(enabled); + enabled->blockValueChanges(); + bool curValue = enabled->getValue(); + bool newValue = inp ? true : false; + changed = curValue != newValue; + if (changed) { + enabled->setValue(newValue, 0); + } + enabled->unblockValueChanges(); + } + return changed; +} + +bool +Node::refreshDraftFlagInternal(const std::vector >& inputs) +{ + bool hasDraftInput = false; + for (std::size_t i = 0; i < inputs.size(); ++i) { + if (inputs[i]) { + hasDraftInput |= inputs[i]->isDraftModeUsed(); + } + } + hasDraftInput |= _imp->liveInstance->supportsRenderQuality(); + bool changed; + { + QMutexLocker k(&_imp->pluginsPropMutex); + changed = _imp->draftModeUsed != hasDraftInput; + _imp->draftModeUsed = hasDraftInput; + } + return changed; +} + +void +Node::refreshAllInputRelatedData(bool canChangeValues) +{ + refreshAllInputRelatedData(canChangeValues,getInputs_copy()); +} + +bool +Node::refreshAllInputRelatedData(bool canChangeValues,const std::vector >& inputs) +{ + bool hasChanged = false; + hasChanged |= refreshDraftFlagInternal(inputs); + ///if all non optional clips are connected, call getClipPrefs + ///The clip preferences action is never called until all non optional clips have been attached to the plugin. + if (!hasMandatoryInputDisconnected()) { + + if (getApp()->getProject()->isLoadingProject()) { + //Nb: we clear the action cache because when creating the node many calls to getRoD and stuff might have returned + //empty rectangles, but since we force the hash to remain what was in the project file, we might then get wrong RoDs returned + _imp->liveInstance->clearActionsCache(); + } + + double time = (double)getApp()->getTimeLine()->currentFrame(); + + RenderScale scaleOne; + scaleOne.x = scaleOne.y = 1.; + ///Render scale support might not have been set already because getRegionOfDefinition could have failed until all non optional inputs were connected + if (_imp->liveInstance->supportsRenderScaleMaybe() == EffectInstance::eSupportsMaybe) { + RectD rod; + + Natron::StatusEnum stat = _imp->liveInstance->getRegionOfDefinition(getHashValue(), time, scaleOne, 0, &rod); + if (stat != eStatusFailed) { + RenderScale scale; + scale.x = 0.5; + scale.y = 0.5; + stat = _imp->liveInstance->getRegionOfDefinition(getHashValue(), time, scale, 0, &rod); + if (stat != eStatusFailed) { + _imp->liveInstance->setSupportsRenderScaleMaybe(EffectInstance::eSupportsYes); + } else { + _imp->liveInstance->setSupportsRenderScaleMaybe(EffectInstance::eSupportsNo); + } + } + + } + hasChanged |= _imp->liveInstance->checkOFXClipPreferences(time, scaleOne, kOfxChangeUserEdited, true); + } + + hasChanged |= refreshChannelSelectors(canChangeValues); + + { + QMutexLocker k(&_imp->pluginsPropMutex); + _imp->mustComputeInputRelatedData = false; + } + + return hasChanged; +} + +bool +Node::refreshInputRelatedDataInternal(std::list& markedNodes) +{ + { + QMutexLocker k(&_imp->pluginsPropMutex); + if (!_imp->mustComputeInputRelatedData) { + //We didn't change + return false; + } + } + + std::list::iterator found = std::find(markedNodes.begin(), markedNodes.end(), this); + if (found != markedNodes.end()) { + return false; + } + + ///Check if inputs must be refreshed first + + int maxInputs = getMaxInputCount(); + std::vector inputsCopy(maxInputs); + for (int i = 0; i < maxInputs; ++i) { + inputsCopy[i] = getInput(i); + if (inputsCopy[i] && inputsCopy[i]->isInputRelatedDataDirty()) { + inputsCopy[i]->refreshInputRelatedDataInternal(markedNodes); + } + } + + + markedNodes.push_back(this); + + bool hasChanged = refreshAllInputRelatedData(false, inputsCopy); + + return hasChanged; +} + +bool +Node::isInputRelatedDataDirty() const +{ + QMutexLocker k(&_imp->pluginsPropMutex); + return _imp->mustComputeInputRelatedData; +} + +void +Node::forceRefreshAllInputRelatedData() +{ + markInputRelatedDataDirtyRecursive(); + + NodeGroup* isGroup = dynamic_cast(_imp->liveInstance.get()); + if (isGroup) { + std::list inputs; + isGroup->getInputsOutputs(&inputs); + for (std::list::iterator it = inputs.begin(); it != inputs.end(); ++it) { + if ((*it)) { + (*it)->refreshInputRelatedDataRecursive(); + } + } + } else { + refreshInputRelatedDataRecursive(); + } +} + +void +Node::markAllInputRelatedDataDirty() +{ + { + QMutexLocker k(&_imp->pluginsPropMutex); + _imp->mustComputeInputRelatedData = true; + } + if (isRotoPaintingNode()) { + boost::shared_ptr roto = getRotoContext(); + assert(roto); + std::list rotoNodes; + roto->getRotoPaintTreeNodes(&rotoNodes); + for (std::list::iterator it = rotoNodes.begin(); it!=rotoNodes.end(); ++it) { + (*it)->markAllInputRelatedDataDirty(); + } + } + +} + +void +Node::markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes,bool recurse) { + std::list::iterator found = std::find(markedNodes.begin(), markedNodes.end(), this); + if (found != markedNodes.end()) { + return; + } + markAllInputRelatedDataDirty(); + markedNodes.push_back(this); + if (recurse) { + std::list outputs; + getOutputsWithGroupRedirection(outputs); + for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { + (*it)->markInputRelatedDataDirtyRecursiveInternal( markedNodes, true ); + } + } + + + +} + +void +Node::markInputRelatedDataDirtyRecursive() +{ + std::list marked; + markInputRelatedDataDirtyRecursiveInternal(marked, true); +} + +void +Node::refreshInputRelatedDataRecursiveInternal(std::list& markedNodes) +{ + refreshInputRelatedDataInternal(markedNodes); + + if (isRotoPaintingNode()) { + boost::shared_ptr roto = getRotoContext(); + assert(roto); + boost::shared_ptr bottomMerge = roto->getRotoPaintBottomMergeNode(); + if (bottomMerge) { + bottomMerge->refreshInputRelatedDataRecursiveInternal(markedNodes); + } + } + + /* + If this node is the bottom node of the rotopaint tree, forward directly to the outputs of the Rotopaint node + */ + boost::shared_ptr attachedItem = _imp->paintStroke.lock(); + if (attachedItem) { + NodePtr rotoPaintNode = attachedItem->getContext()->getNode(); + assert(rotoPaintNode); + boost::shared_ptr context = rotoPaintNode->getRotoContext(); + assert(context); + NodePtr bottomMerge = context->getRotoPaintBottomMergeNode(); + if (bottomMerge.get() == this) { + std::list outputs; + rotoPaintNode->getOutputsWithGroupRedirection(outputs); + for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { + (*it)->refreshInputRelatedDataRecursiveInternal( markedNodes ); + } + return; + } + } + + ///Now notify outputs we have changed + std::list outputs; + getOutputsWithGroupRedirection(outputs); + for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { + (*it)->refreshInputRelatedDataRecursiveInternal( markedNodes ); + } + +} + +void +Node::refreshInputRelatedDataRecursive() +{ + std::list markedNodes; + refreshInputRelatedDataRecursiveInternal(markedNodes); +} + +bool +Node::isDraftModeUsed() const +{ + QMutexLocker k(&_imp->pluginsPropMutex); + return _imp->draftModeUsed; } void @@ -6596,59 +7307,6 @@ Node::isSettingsPanelOpened() const } -void -Node::restoreClipPreferencesRecursive(std::list& markedNodes) -{ - std::list::const_iterator found = std::find(markedNodes.begin(), markedNodes.end(), this); - if (found != markedNodes.end()) { - return; - } - - /* - * Always call getClipPreferences on the inputs first since the preference of this node may - * depend on the inputs. - */ - boost::shared_ptr roto = getRotoContext(); - NodePtr rotoNode; - if (roto) { - rotoNode = roto->getNode(); - } - boost::shared_ptr rotoItem = getAttachedRotoItem(); - - for (int i = 0; i < getMaxInputCount(); ++i) { - NodePtr input = getInput(i); - if (input) { - if (rotoItem) { - if (rotoItem->getContext()->getNode() == input) { - continue; - } - } - input->restoreClipPreferencesRecursive(markedNodes); - } - } - - if (roto) { - NodeList nodes; - roto->getRotoPaintTreeNodes(&nodes); - for (NodeList::iterator it = nodes.begin(); it!=nodes.end(); ++it) { - (*it)->restoreClipPreferencesRecursive(markedNodes); - } - } - - /* - * And now call getClipPreferences on ourselves - */ - - //Nb: we clear the action cache because when creating the node many calls to getRoD and stuff might have returned - //empty rectangles, but since we force the hash to remain what was in the project file, we might then get wrong RoDs returned - _imp->liveInstance->clearActionsCache(); - - _imp->liveInstance->restoreClipPreferences(); - refreshChannelSelectors(false); - - markedNodes.push_back(this); - -} void @@ -7159,16 +7817,17 @@ Node::getChannelSelectorKnob(int inputNb) const return found->second.layer.lock(); } -void +bool Node::refreshChannelSelectors(bool setValues) { if (!isNodeCreated()) { - return; + return false; } _imp->liveInstance->setComponentsAvailableDirty(true); int time = getApp()->getTimeLine()->currentFrame(); + bool hasChanged = false; for (std::map::iterator it = _imp->channelsSelectors.begin(); it != _imp->channelsSelectors.end(); ++it) { NodePtr node; @@ -7179,9 +7838,8 @@ Node::refreshChannelSelectors(bool setValues) } boost::shared_ptr layerKnob = it->second.layer.lock(); - std::vector currentLayerEntries = layerKnob->getEntries_mt_safe(); - - std::string curLayer = it->second.layerName.lock()->getValue(); + const std::vector currentLayerEntries = layerKnob->getEntries_mt_safe(); + const std::string curLayer = it->second.layerName.lock()->getValue(); std::vector choices; @@ -7190,15 +7848,21 @@ Node::refreshChannelSelectors(bool setValues) } else { choices.push_back("None"); } - bool gotColor = false; - bool gotDisparityLeft = false; - bool gotDisparityRight = false; - bool gotMotionBw = false; - bool gotMotionFw = false; - - int colorIndex = -1; + int gotColor = -1; + Natron::ImageComponents colorComp; + /* + These are default layers that we always display in the layer selector. + If one of them is found in the clip preferences, we set the default value to it. + */ + std::map defaultLayers; + defaultLayers[kNatronDisparityLeftPlaneName] = -1; + defaultLayers[kNatronDisparityRightPlaneName] = -1; + defaultLayers[kNatronForwardMotionVectorsPlaneName] = -1; + defaultLayers[kNatronBackwardMotionVectorsPlaneName] = -1; + + if (node) { EffectInstance::ComponentsAvailableMap compsAvailable; node->getLiveInstance()->getComponentsAvailable(time, &compsAvailable); @@ -7214,7 +7878,7 @@ Node::refreshChannelSelectors(bool setValues) assert(choices.size() > 0); std::vector::iterator pos = choices.begin(); ++pos; - colorIndex = 1; + gotColor = 1; if (numComp == 1) { choices.insert(pos,kNatronAlphaComponentsName); @@ -7225,54 +7889,81 @@ Node::refreshChannelSelectors(bool setValues) } else { assert(false); } - gotColor = true; + + ///Increment all default indexes + for (std::map::iterator it = defaultLayers.begin() ;it!=defaultLayers.end(); ++it) { + if (it->second != -1) { + ++it->second; + } + } } else { choices.push_back(it2->first.getLayerName()); - if (it2->first.getLayerName() == kNatronBackwardMotionVectorsPlaneName) { - gotMotionBw = true; - } else if (it2->first.getLayerName() == kNatronForwardMotionVectorsPlaneName) { - gotMotionFw = true; - } else if (it2->first.getLayerName() == kNatronDisparityLeftPlaneName) { - gotDisparityLeft = true; - } else if (it2->first.getLayerName() == kNatronDisparityRightPlaneName) { - gotDisparityRight = true; + std::map::iterator foundDefaultLayer = defaultLayers.find(it2->first.getLayerName()); + if (foundDefaultLayer != defaultLayers.end()) { + foundDefaultLayer->second = choices.size() -1; } } } - } + } // if (node) { - if (!gotColor) { + if (gotColor == -1) { assert(choices.size() > 0); std::vector::iterator pos = choices.begin(); ++pos; - colorIndex = 1; + gotColor = 1; + ///Increment all default indexes + for (std::map::iterator it = defaultLayers.begin() ;it!=defaultLayers.end(); ++it) { + if (it->second != -1) { + ++it->second; + } + } + colorComp = ImageComponents::getRGBAComponents(); choices.insert(pos,kNatronRGBAComponentsName); } - if (!gotDisparityLeft) { - choices.push_back(kNatronDisparityLeftPlaneName); - } - if (!gotDisparityRight) { - choices.push_back(kNatronDisparityRightPlaneName); - } - if (!gotMotionFw) { - choices.push_back(kNatronForwardMotionVectorsPlaneName); - } - if (!gotMotionBw) { - choices.push_back(kNatronBackwardMotionVectorsPlaneName); + for (std::map::iterator it = defaultLayers.begin() ;it!=defaultLayers.end(); ++it) { + if (it->second == -1) { + choices.push_back(it->first); + } } + if (choices.size() != currentLayerEntries.size()) { + hasChanged = true; + } else { + for (std::size_t i = 0; i < currentLayerEntries.size(); ++i) { + if (currentLayerEntries[i] != choices[i]) { + hasChanged = true; + break; + } + } + } layerKnob->populateChoices(choices); + if (hasChanged) { + s_outputLayerChanged(); + } + if (setValues) { - assert(colorIndex != -1 && colorIndex >= 0 && colorIndex < (int)choices.size()); + if (it->second.hasAllChoice && _imp->liveInstance->isPassThroughForNonRenderedPlanes() == EffectInstance::ePassThroughRenderAllRequestedPlanes) { layerKnob->setValue(0, 0); it->second.layerName.lock()->setValue(choices[0], 0); } else { - layerKnob->setValue(colorIndex,0); - it->second.layerName.lock()->setValue(choices[colorIndex], 0); + int defaultIndex = -1; + for (std::map::iterator it = defaultLayers.begin() ;it!=defaultLayers.end(); ++it) { + if (it->second != -1) { + defaultIndex = it->second; + break; + } + } + if (defaultIndex == -1) { + defaultIndex = gotColor; + } + + assert(defaultIndex != -1 && defaultIndex >= 0 && defaultIndex < (int)choices.size()); + layerKnob->setValue(defaultIndex,0); + it->second.layerName.lock()->setValue(choices[defaultIndex], 0); } } else { if (!curLayer.empty()) { @@ -7287,8 +7978,8 @@ Node::refreshChannelSelectors(bool setValues) layerKnob->setValue(i, 0); _imp->liveInstance->endChanges(true); layerKnob->unblockValueChanges(); - if (isColor && _imp->enabledChan[0].lock()) { - assert(colorIndex != -1); + if (isColor && it->first == -1 && _imp->enabledChan[0].lock()) { + assert(gotColor != -1); //Since color plane may have changed (RGB, or RGBA or Alpha), adjust the secretness of the checkboxes const std::vector& channels = colorComp.getComponentsNames(); for (int j = 0; j < 4; ++j) { @@ -7303,8 +7994,8 @@ Node::refreshChannelSelectors(bool setValues) } } } - } - } + } // if (setValues) { + } // for (std::map::iterator it = _imp->channelsSelectors.begin(); it != _imp->channelsSelectors.end(); ++it) { for (std::map::iterator it = _imp->maskSelectors.begin(); it != _imp->maskSelectors.end(); ++it) { NodePtr node; @@ -7314,9 +8005,8 @@ Node::refreshChannelSelectors(bool setValues) node = getInput(it->first); } - std::vector currentLayerEntries = it->second.channel.lock()->getEntries_mt_safe(); - - std::string curLayer = it->second.channelName.lock()->getValue(); + const std::vector currentLayerEntries = it->second.channel.lock()->getEntries_mt_safe(); + const std::string curLayer = it->second.channelName.lock()->getValue(); std::vector choices; @@ -7375,8 +8065,20 @@ Node::refreshChannelSelectors(bool setValues) } alphaIndex = choices.size() - 1; } + + if (choices.size() != currentLayerEntries.size()) { + hasChanged = true; + } else { + for (std::size_t i = 0; i < currentLayerEntries.size(); ++i) { + if (currentLayerEntries[i] != choices[i]) { + hasChanged = true; + break; + } + } + } it->second.channel.lock()->populateChoices(choices); + if (setValues) { assert(alphaIndex != -1 && alphaIndex >= 0 && alphaIndex < (int)choices.size()); it->second.channel.lock()->setValue(alphaIndex,0); @@ -7400,6 +8102,8 @@ Node::refreshChannelSelectors(bool setValues) //Notify the effect channels have changed (the viewer needs this) _imp->liveInstance->onChannelsSelectorRefreshed(); + return hasChanged; + } // Node::refreshChannelSelectors(bool setValues) bool diff --git a/Engine/Node.h b/Engine/Node.h index f4a5bd7b8c..d65bc43343 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -60,6 +60,9 @@ CLANG_DIAG_ON(deprecated) #define kEnablePreviewKnobName "enablePreview" #define kOutputChannelsKnobName "channels" +#define kOfxMaskInvertParamName "maskInvert" +#define kOfxMixParamName "mix" + class AppInstance; class NodeSettingsPanel; class KnobI; @@ -131,6 +134,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool isPartOfProject, const QString& fixedName, const std::list >& paramValues); + + ///called by load() and OfxEffectInstance, do not call this! void loadKnobs(const NodeSerialization & serialization,bool updateKnobGui = false); @@ -422,7 +427,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON /////////////////////ROTO-PAINT related functionnalities////////////////////// ////////////////////////////////////////////////////////////////////////////// void updateLastPaintStrokeData(int newAge,const std::list >& points, - const RectD& lastPointsBbox); + const RectD& lastPointsBbox, + int strokeIndex); //Used by nodes below the rotopaint tree to optimize the RoI void setLastPaintStrokeDataNoRotopaint(const RectD& lastStrokeBbox); @@ -437,7 +443,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void getLastPaintStrokeRoD(RectD* pointsBbox) ; bool isLastPaintStrokeBitmapCleared() const; void clearLastPaintStrokeRoD(); - void getLastPaintStrokePoints(int time,std::list > >* strokes) const; + void getLastPaintStrokePoints(int time,std::list > >* strokes, int* strokeIndex) const; boost::shared_ptr getOrRenderLastStrokeImage(unsigned int mipMapLevel, const RectI& roi, double par, @@ -540,7 +546,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool isUserSelected() const; - bool shouldCacheOutput(bool isFrameVaryingOrAnimated) const; + bool shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const; /** * @brief If the session is a GUI session, then this function sets the position of the node on the nodegraph. @@ -790,6 +796,10 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON * do not respect the OpenFX specification. **/ boost::shared_ptr getImageBeingRendered(int time,unsigned int mipMapLevel,int view); + + void beginInputEdition(); + + void endInputEdition(bool triggerRender); void onInputChanged(int inputNb); @@ -916,7 +926,6 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool isForceCachingEnabled() const; - void restoreClipPreferencesRecursive(std::list& markedNodes); /** * @brief Declares to Python all parameters as attribute of the variable representing this node. @@ -1005,11 +1014,15 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void setPluginPythonModule(const std::string& pythonModule, unsigned int version); + bool hasPyPlugBeenEdited() const; + void setPyPlugEdited(bool edited); + std::string getPluginPythonModule() const; unsigned int getPluginPythonModuleVersion() const; - void refreshChannelSelectors(bool setValues); + //Returns true if changed + bool refreshChannelSelectors(bool setValues); bool getProcessChannel(int channelIndex) const; @@ -1030,9 +1043,40 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void removeAllImagesFromCacheWithMatchingIDAndDifferentKey(U64 nodeHashKey); void removeAllImagesFromCache(); + bool isDraftModeUsed() const; + bool isInputRelatedDataDirty() const; + + void forceRefreshAllInputRelatedData(); + + void markAllInputRelatedDataDirty(); + + bool getSelectedLayer(int inputNb,std::string& layer) const; + private: + void refreshInputRelatedDataRecursiveInternal(std::list& markedNodes); + + void refreshInputRelatedDataRecursive(); + + void refreshAllInputRelatedData(bool canChangeValues); + + bool refreshMaskEnabledNess(int inpubNb); + + bool refreshLayersChoiceSecretness(int inpubNb); + + void markInputRelatedDataDirtyRecursive(); + + void markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes,bool recurse); + + bool refreshAllInputRelatedData(bool hasSerializationData,const std::vector >& inputs); + + bool refreshInputRelatedDataInternal(std::list& markedNodes); + + bool refreshDraftFlagInternal(const std::vector >& inputs); + void setNameInternal(const std::string& name); + + void s_outputLayerChanged() { Q_EMIT outputLayerChanged(); } public Q_SLOTS: @@ -1075,6 +1119,8 @@ public Q_SLOTS: Q_SIGNALS: + void outputLayerChanged(); + void mustComputeHashOnMainThread(); void settingsPanelClosed(bool); diff --git a/Engine/NodeGroup.cpp b/Engine/NodeGroup.cpp index 5d137cdb7f..ee6b476253 100644 --- a/Engine/NodeGroup.cpp +++ b/Engine/NodeGroup.cpp @@ -913,16 +913,20 @@ NodeCollection::recomputeFrameRangeForAllReaders(int* firstFrame,int* lastFrame) } void -NodeCollection::forceGetClipPreferencesOnAllTrees() +NodeCollection::forceComputeInputDependentDataOnAllTrees() { NodeList nodes; getNodes_recursive(nodes); std::list trees; Project::extractTreesFromNodes(nodes, trees); + for (NodeList::iterator it = nodes.begin(); it!=nodes.end(); ++it) { + (*it)->markAllInputRelatedDataDirty(); + } + std::list markedNodes; for (std::list::iterator it = trees.begin(); it != trees.end(); ++it) { - it->output.node->restoreClipPreferencesRecursive(markedNodes); + it->output.node->forceRefreshAllInputRelatedData(); } } @@ -1052,6 +1056,9 @@ NodeCollection::invalidateParallelRenderArgs() NodeList nodes = getNodes(); for (NodeList::iterator it = nodes.begin(); it != nodes.end(); ++it) { + if (!(*it) || !(*it)->getLiveInstance()) { + continue; + } (*it)->getLiveInstance()->invalidateParallelRenderArgsTLS(); if ((*it)->isMultiInstance()) { diff --git a/Engine/NodeGroup.h b/Engine/NodeGroup.h index d565d27baa..6883872b01 100644 --- a/Engine/NodeGroup.h +++ b/Engine/NodeGroup.h @@ -267,7 +267,7 @@ class NodeCollection void getParallelRenderArgs(std::map,ParallelRenderArgs >& argsMap) const; - void forceGetClipPreferencesOnAllTrees(); + void forceComputeInputDependentDataOnAllTrees(); /** * @brief Callback called when a node of the collection is being deactivated diff --git a/Engine/NodeGroupSerialization.cpp b/Engine/NodeGroupSerialization.cpp index fc3f19a56f..aacf7f4620 100644 --- a/Engine/NodeGroupSerialization.cpp +++ b/Engine/NodeGroupSerialization.cpp @@ -75,6 +75,8 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh ///This map contains all the parents that must be reconnected and an iterator to the child serialization std::map, std::list >::const_iterator > parentsToReconnect; + std::list< boost::shared_ptr > multiInstancesToRecurse; + for (std::list< boost::shared_ptr >::const_iterator it = serializedNodes.begin(); it != serializedNodes.end(); ++it) { std::string pluginID = (*it)->getPluginID(); @@ -125,7 +127,7 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh parentsToReconnect.insert( std::make_pair(parent, it) ); } } - } + } // if ( !(*it)->getMultiInstanceParentName().empty() ) { const std::string& pythonModuleAbsolutePath = (*it)->getPythonModule(); @@ -210,15 +212,12 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh } } - } + } // if (!pythonModuleAbsolutePath.empty()) { if (!createNodes) { ///We are in the case where we loaded a PyPlug: it probably created all the nodes in the group already but didn't ///load their serialization n = group->getNodeByName((*it)->getNodeScriptName()); - if (n) { - n->loadKnobs(**it); - } } int majorVersion,minorVersion; @@ -272,11 +271,17 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh boost::shared_ptr sharedEffect = isGrp->shared_from_this(); boost::shared_ptr sharedGrp = boost::dynamic_pointer_cast(sharedEffect); NodeCollectionSerialization::restoreFromSerialization(children, sharedGrp ,!usingPythonModule, moduleUpdatesProcessed, hasProjectAWriter); + } else { + ///For multi-instances, wait for the group to be entirely created then load the sub-tracks in a separate loop. assert(n->isMultiInstance()); - NodeCollectionSerialization::restoreFromSerialization(children, group, true, moduleUpdatesProcessed, hasProjectAWriter); + multiInstancesToRecurse.push_back(*it); } } + } // for (std::list< boost::shared_ptr >::const_iterator it = serializedNodes.begin(); it != serializedNodes.end(); ++it) { + + for (std::list< boost::shared_ptr >::const_iterator it = multiInstancesToRecurse.begin(); it != multiInstancesToRecurse.end(); ++it) { + NodeCollectionSerialization::restoreFromSerialization((*it)->getNodesCollection(), group, true, moduleUpdatesProcessed, hasProjectAWriter); } @@ -330,8 +335,10 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh for (U32 j = 0; j < oldInputs.size(); ++j) { if ( !oldInputs[j].empty() && !group->connectNodes(isOfxEffect ? oldInputs.size() - 1 - j : j, oldInputs[j],thisNode.get()) ) { - qDebug() << "Failed to connect node" << (*it)->getNodeScriptName().c_str() << "to" << oldInputs[j].c_str() - << "[This is normal if loading a PyPlug]"; + if (createNodes) { + qDebug() << "Failed to connect node" << (*it)->getNodeScriptName().c_str() << "to" << oldInputs[j].c_str() + << "[This is normal if loading a PyPlug]"; + } } } } else { @@ -346,12 +353,14 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh continue; } if (!it2->second.empty() && !group->connectNodes(index, it2->second, thisNode.get())) { - qDebug() << "Failed to connect node" << (*it)->getNodeScriptName().c_str() << "to" << it2->second.c_str() - << "[This is normal if loading a PyPlug]"; + if (createNodes) { + qDebug() << "Failed to connect node" << (*it)->getNodeScriptName().c_str() << "to" << it2->second.c_str() + << "[This is normal if loading a PyPlug]"; + } } } } - } + } // for (std::list< boost::shared_ptr >::const_iterator it = serializedNodes.begin(); it != serializedNodes.end(); ++it) { ///Also reconnect parents of multiinstance nodes that were created on the fly for (std::map, std::list >::const_iterator >::const_iterator @@ -365,8 +374,10 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh for (U32 j = 0; j < oldInputs.size(); ++j) { if ( !oldInputs[j].empty() && !group->connectNodes(isOfxEffect ? oldInputs.size() - 1 - j : j, oldInputs[j],it->first.get()) ) { - qDebug() << "Failed to connect node" << it->first->getPluginLabel().c_str() << "to" << oldInputs[j].c_str() - << "[This is normal if loading a PyPlug]"; + if (createNodes) { + qDebug() << "Failed to connect node" << it->first->getPluginLabel().c_str() << "to" << oldInputs[j].c_str() + << "[This is normal if loading a PyPlug]"; + } } } @@ -382,8 +393,10 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh continue; } if (!it2->second.empty() && !group->connectNodes(index, it2->second, it->first.get())) { - qDebug() << "Failed to connect node" << it->first->getPluginLabel().c_str() << "to" << it2->second.c_str() - << "[This is normal if loading a PyPlug]"; + if (createNodes) { + qDebug() << "Failed to connect node" << it->first->getPluginLabel().c_str() << "to" << it2->second.c_str() + << "[This is normal if loading a PyPlug]"; + } } } } diff --git a/Engine/NodeGuiI.h b/Engine/NodeGuiI.h index ce16990412..2ac60a8cd0 100644 --- a/Engine/NodeGuiI.h +++ b/Engine/NodeGuiI.h @@ -131,6 +131,8 @@ public : virtual void setPluginIDAndVersion(const std::string& pluginLabel,const std::string& pluginID,unsigned int version) = 0; virtual bool isUserSelected() const = 0; + + virtual void restoreStateAfterCreation() = 0; }; #endif // NODEGUII_H diff --git a/Engine/NodeSerialization.cpp b/Engine/NodeSerialization.cpp index 9845596ba7..c6b0a73d00 100644 --- a/Engine/NodeSerialization.cpp +++ b/Engine/NodeSerialization.cpp @@ -102,9 +102,10 @@ NodeSerialization::NodeSerialization(const boost::shared_ptr & n,b _pluginID = n->getPluginID(); - _pythonModule = n->getPluginPythonModule(); - - _pythonModuleVersion = n->getPluginPythonModuleVersion(); + if(!n->hasPyPlugBeenEdited()) { + _pythonModule = n->getPluginPythonModule(); + _pythonModuleVersion = n->getPluginPythonModuleVersion(); + } _pluginMajorVersion = n->getMajorVersion(); diff --git a/Engine/NodeSerialization.h b/Engine/NodeSerialization.h index 585f98a8e2..548415cc6a 100644 --- a/Engine/NodeSerialization.h +++ b/Engine/NodeSerialization.h @@ -58,7 +58,8 @@ GCC_DIAG_ON(sign-compare) #define NODE_SERIALIZATION_INTRODUCES_USER_COMPONENTS 10 #define NODE_SERIALIZATION_INTRODUCES_PYTHON_MODULE_VERSION 11 #define NODE_SERIALIZATION_INTRODUCES_CACHE_ID 12 -#define NODE_SERIALIZATION_CURRENT_VERSION NODE_SERIALIZATION_INTRODUCES_CACHE_ID +#define NODE_SERIALIZATION_SERIALIZE_PYTHON_MODULE_ALWAYS 13 +#define NODE_SERIALIZATION_CURRENT_VERSION NODE_SERIALIZATION_SERIALIZE_PYTHON_MODULE_ALWAYS namespace Natron { class Node; @@ -246,10 +247,9 @@ class NodeSerialization ar & boost::serialization::make_nvp("Plugin_label",_nodeLabel); ar & boost::serialization::make_nvp("Plugin_script_name",_nodeScriptName); ar & boost::serialization::make_nvp("Plugin_id",_pluginID); - if (_pluginID == PLUGINID_NATRON_GROUP) { - ar & boost::serialization::make_nvp("PythonModule",_pythonModule); - ar & boost::serialization::make_nvp("PythonModuleVersion",_pythonModuleVersion); - } + ar & boost::serialization::make_nvp("PythonModule",_pythonModule); + ar & boost::serialization::make_nvp("PythonModuleVersion",_pythonModuleVersion); + ar & boost::serialization::make_nvp("Plugin_major_version",_pluginMajorVersion); ar & boost::serialization::make_nvp("Plugin_minor_version",_pluginMinorVersion); ar & boost::serialization::make_nvp("KnobsCount", _nbKnobs); @@ -305,7 +305,8 @@ class NodeSerialization ar & boost::serialization::make_nvp("Plugin_id",_pluginID); if (version >= NODE_SERIALIZATION_INTRODUCES_PYTHON_MODULE) { - if (_pluginID == PLUGINID_NATRON_GROUP) { + + if (version >= NODE_SERIALIZATION_SERIALIZE_PYTHON_MODULE_ALWAYS || _pluginID == PLUGINID_NATRON_GROUP) { ar & boost::serialization::make_nvp("PythonModule",_pythonModule); if (version >= NODE_SERIALIZATION_INTRODUCES_PYTHON_MODULE_VERSION) { ar & boost::serialization::make_nvp("PythonModuleVersion",_pythonModuleVersion); diff --git a/Engine/NodeWrapper.cpp b/Engine/NodeWrapper.cpp index 7406071c2c..66230ac5ce 100644 --- a/Engine/NodeWrapper.cpp +++ b/Engine/NodeWrapper.cpp @@ -32,6 +32,25 @@ #include "Engine/NodeGroup.h" #include "Engine/RotoWrapper.h" +UserParamHolder::UserParamHolder() +: _holder(0) +{ + +} + +UserParamHolder::UserParamHolder(KnobHolder* holder) +: _holder(holder) +{ + +} + +void +UserParamHolder::setHolder(KnobHolder* holder) +{ + assert(!_holder); + _holder = holder; +} + Effect::Effect(const boost::shared_ptr& node) : Group() , UserParamHolder(node ? node->getLiveInstance() : 0) @@ -144,7 +163,7 @@ Effect::getInputLabel(int inputNumber) try { return _node->getInputLabel(inputNumber); } catch (const std::exception& e) { - std::cout << e.what() << std::endl; + _node->getApp()->appendToScriptEditor(e.what()); } return std::string(); } @@ -298,12 +317,14 @@ void Effect::beginChanges() { _node->getLiveInstance()->beginChanges(); + _node->beginInputEdition(); } void Effect::endChanges() { _node->getLiveInstance()->endChanges(); + _node->endInputEdition(true); } IntParam* diff --git a/Engine/NodeWrapper.h b/Engine/NodeWrapper.h index e88e0ffd9c..07158964c7 100644 --- a/Engine/NodeWrapper.h +++ b/Engine/NodeWrapper.h @@ -69,25 +69,13 @@ class UserParamHolder KnobHolder* _holder; public: - UserParamHolder() - : _holder(0) - { - - } - - UserParamHolder(KnobHolder* holder) - : _holder(holder) - { - - } + UserParamHolder(); + + UserParamHolder(KnobHolder* holder); virtual ~UserParamHolder() {} - void setHolder(KnobHolder* holder) - { - assert(!_holder); - _holder = holder; - } + void setHolder(KnobHolder* holder); /////////////Functions to create custom parameters////////////////////////// /////////////////////////////////////////////////////////////////////////// diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index 4a41c59fed..c38d8d44a7 100644 --- a/Engine/OfxClipInstance.cpp +++ b/Engine/OfxClipInstance.cpp @@ -132,9 +132,9 @@ OfxClipInstance::getUnmappedComponents() const static const std::string alphaStr(kOfxImageComponentAlpha); EffectInstance* inputNode = getAssociatedNode(); - if (!isOutput() && inputNode) { + /*if (!isOutput() && inputNode) { inputNode = inputNode->getNearestNonIdentity(_nodeInstance->getApp()->getTimeLine()->currentFrame()); - } + }*/ if (inputNode) { ///Get the input node's output preferred bit depth and componentns std::list comps; @@ -758,6 +758,11 @@ OfxClipInstance::getStereoscopicImage(OfxTime time, OFX::Host::ImageEffect::Image* OfxClipInstance::getImagePlane(OfxTime time, int view, const std::string& plane,const OfxRectD *optionalBounds) { + if (time != time) { + // time is NaN + + return NULL; + } return getImagePlaneInternal(time, view, optionalBounds, &plane); } @@ -779,7 +784,12 @@ OfxClipInstance::getImagePlaneInternal(OfxTime time, int view, const OfxRectD *o //If TLS does not work then nothing will work. assert( hasLocalData ); #endif - + if (time != time) { + // time is NaN + + return NULL; + } + if (isOutput()) { return getOutputImageInternal(ofxPlane); } else { @@ -820,6 +830,8 @@ OfxClipInstance::getInputImageInternal(OfxTime time, std::list comps = ofxComponentsToNatronComponents(getComponents()); assert(comps.size() == 1); comp = comps.front(); + } else { + comp = _nodeInstance->getNode()->findClosestSupportedComponents(inputnb, comp); } } @@ -830,8 +842,13 @@ OfxClipInstance::getInputImageInternal(OfxTime time, if (comp.getNumComponents() == 0) { return 0; } - - + if (time != time) { + // time is NaN + + return 0; + } + + boost::shared_ptr transform; bool usingReroute = false; int rerouteInputNb = -1; @@ -970,7 +987,7 @@ OfxClipInstance::getInputImageInternal(OfxTime time, std::list requestedComps; requestedComps.push_back(comp); EffectInstance::RenderRoIArgs args((SequenceTime)time,renderScale,mipMapLevel, - view,false,pixelRoI,RectD(),requestedComps,bitDepth,_nodeInstance,inputImages); + view,false,pixelRoI,RectD(),requestedComps,bitDepth,true,_nodeInstance,inputImages); ImageList planes; EffectInstance::RenderRoIRetCode retCode = inputNode->renderRoI(args,&planes); assert(planes.size() == 1 || planes.empty()); @@ -1002,7 +1019,7 @@ OfxClipInstance::getInputImageInternal(OfxTime time, nComps = natronComps.front().getNumComponents(); } - /* // this will dump the image as seen from the plug-in + /*// this will dump the image as seen from the plug-in QString filename; QTextStream ts(&filename); QDateTime now = QDateTime::currentDateTime(); @@ -1066,7 +1083,7 @@ OfxClipInstance::getOutputImageInternal(const std::string* ofxPlane) Otherwise, hack the clipGetImage and return the plane requested by the user via the interface instead of the colour plane. */ bool multiPlanar = _nodeInstance->isMultiPlanar(); - const std::string& layerName = multiPlanar ? natronPlane.getLayerName() : planeBeingRendered.getLayerName(); + const std::string& layerName = /*multiPlanar ?*/ natronPlane.getLayerName();// : planeBeingRendered.getLayerName(); for (std::map::iterator it = outputPlanes.begin(); it != outputPlanes.end(); ++it) { if (it->first.getLayerName() == layerName) { @@ -1239,7 +1256,6 @@ OfxImage::OfxImage(boost::shared_ptr internalImage, assert(internalImage); - unsigned int mipMapLevel = internalImage->getMipMapLevel(); RenderScale scale; @@ -1329,6 +1345,9 @@ OfxImage::OfxImage(boost::shared_ptr internalImage, } +OfxImage::~OfxImage() +{ +} int OfxClipInstance::getInputNb() const diff --git a/Engine/OfxClipInstance.h b/Engine/OfxClipInstance.h index 61bb12d2a4..d0e997ad05 100644 --- a/Engine/OfxClipInstance.h +++ b/Engine/OfxClipInstance.h @@ -360,9 +360,7 @@ class OfxImage int nComps, OfxClipInstance &clip); - virtual ~OfxImage() - { - } + virtual ~OfxImage(); boost::shared_ptr getInternalImage() const { diff --git a/Engine/OfxEffectInstance.cpp b/Engine/OfxEffectInstance.cpp index 5e0d42318a..5e36fc9fb4 100644 --- a/Engine/OfxEffectInstance.cpp +++ b/Engine/OfxEffectInstance.cpp @@ -218,7 +218,8 @@ OfxEffectInstance::createOfxImageEffectInstance(OFX::Host::ImageEffect::ImageEff const NodeSerialization* serialization, const std::list >& paramValues, bool allowFileDialogs, - bool disableRenderScaleSupport) + bool disableRenderScaleSupport, + bool *hasUsedFileDialog) { /*Replicate of the code in OFX::Host::ImageEffect::ImageEffectPlugin::createInstance. We need to pass more parameters to the constructor . That means we cannot @@ -233,6 +234,9 @@ OfxEffectInstance::createOfxImageEffectInstance(OFX::Host::ImageEffect::ImageEff assert( QThread::currentThread() == qApp->thread() ); assert(plugin && desc && context != eContextNone); + + *hasUsedFileDialog = false; + _context = context; @@ -326,6 +330,7 @@ OfxEffectInstance::createOfxImageEffectInstance(OFX::Host::ImageEffect::ImageEff images = getApp()->saveImageFileDialog(); } if (!images.empty()) { + *hasUsedFileDialog = true; boost::shared_ptr defaultFile = createDefaultValueForParam(kOfxImageEffectFileParamName, images); CreateNodeArgs::DefaultValuesList list; list.push_back(defaultFile); @@ -391,19 +396,9 @@ OfxEffectInstance::createOfxImageEffectInstance(OFX::Host::ImageEffect::ImageEff // If we don't, the following assert will crash at the beginning of EffectInstance::renderRoIInternal(): // assert(isSupportedBitDepth(outputDepth) && isSupportedComponent(-1, outputComponents)); // If a component/bitdepth is not supported (this is probably a plugin bug), use the closest one, but don't crash Natron. - checkOFXClipPreferences_public(getApp()->getTimeLine()->currentFrame(), scaleOne, kOfxChangeUserEdited,true, false); - - - // check that the plugin supports kOfxImageComponentRGBA for all the clips - /*const std::vector & clips = effectInstance()->getDescriptor().getClipsByOrder(); - for (U32 i = 0; i < clips.size(); ++i) { - if ( (clips[i]->getProps().findStringPropValueIndex(kOfxImageEffectPropSupportedComponents, kOfxImageComponentRGBA) == -1) - && !clips[i]->isOptional() && !clips[i]->isMask() ) { - appPTR->writeToOfxLog_mt_safe( QString( plugin->getDescriptor().getLabel().c_str() ) - + "RGBA components not supported by OFX plugin in context " + QString( context.c_str() ) ); - throw std::runtime_error(std::string("RGBA components not supported by OFX plugin in context ") + context); - } - }*/ + //checkOFXClipPreferences_public(getApp()->getTimeLine()->currentFrame(), scaleOne, kOfxChangeUserEdited,true, false); + + } catch (const std::exception & e) { qDebug() << "Error: Caught exception while creating OfxImageEffectInstance" << ": " << e.what(); throw; @@ -414,25 +409,7 @@ OfxEffectInstance::createOfxImageEffectInstance(OFX::Host::ImageEffect::ImageEff _initialized = true; - ///Now that the instance is created, make sure instanceChangedActino is called for all extra default values - ///that we set - for (std::list >::const_iterator it = paramValues.begin(); it != paramValues.end(); ++it) { - boost::shared_ptr knob = getKnobByName((*it)->getName()); - if (knob) { - for (int i = 0; i < knob->getDimension(); ++i) { - knob->evaluateValueChange(i, Natron::eValueChangedReasonUserEdited); - } - } else { - qDebug() << "WARNING: No such parameter " << (*it)->getName().c_str(); - } - } - - if (!images.empty()) { - boost::shared_ptr fileNameKnob = getKnobByName(kOfxImageEffectFileParamName); - if (fileNameKnob) { - fileNameKnob->evaluateValueChange(0,Natron::eValueChangedReasonUserEdited); - } - } + endChanges(); } // createOfxImageEffectInstance @@ -944,9 +921,7 @@ void OfxEffectInstance::onInputChanged(int inputNo) { - if (getApp()->getProject()->isLoadingProject() || getApp()->isCreatingPythonGroup()) { - return; - } + assert(_context != eContextNone); OfxClipInstance* clip = getClipCorrespondingToInput(inputNo); assert(clip); @@ -955,56 +930,8 @@ OfxEffectInstance::onInputChanged(int inputNo) s.x = s.y = 1.; - /** - * The plug-in might call getImage, set a valid thread storage on the tree. - **/ - ParallelRenderArgsSetter frameRenderArgs(getApp()->getProject().get(), - time, - 0 /*view*/, - true, - false, - false, - 0, - getNode(), - 0, - 0, //texture index - getApp()->getTimeLine().get(), - NodePtr(), - false, - false, - false, - boost::shared_ptr()); - EffectPointerThreadProperty_RAII propHolder_raii(this); - - ///Don't do clip preferences while loading a project, they will be refreshed globally once the project is loaded. - - ///if all non optional clips are connected, call getClipPrefs - ///The clip preferences action is never called until all non optional clips have been attached to the plugin. - if (_effect->areAllNonOptionalClipsConnected()) { - - ///Render scale support might not have been set already because getRegionOfDefinition could have failed until all non optional inputs were connected - if (supportsRenderScaleMaybe() == eSupportsMaybe) { - OfxRectD rod; - OfxPointD scaleOne; - scaleOne.x = scaleOne.y = 1.; - OfxStatus rodstat = _effect->getRegionOfDefinitionAction(time, scaleOne, 0, rod); - if ( (rodstat == kOfxStatOK) || (rodstat == kOfxStatReplyDefault) ) { - OfxPointD scale; - scale.x = 0.5; - scale.y = 0.5; - rodstat = _effect->getRegionOfDefinitionAction(time, scale, 0, rod); - if ( (rodstat == kOfxStatOK) || (rodstat == kOfxStatReplyDefault) ) { - setSupportsRenderScaleMaybe(eSupportsYes); - } else { - setSupportsRenderScaleMaybe(eSupportsNo); - } - } - - } - checkOFXClipPreferences_public(time,s,kOfxChangeUserEdited,true, true); - } - + { RECURSIVE_ACTION(); SET_CAN_SET_VALUE(true); @@ -1020,7 +947,6 @@ OfxEffectInstance::onInputChanged(int inputNo) _effect->endInstanceChangedAction(kOfxChangeUserEdited); } - getNode()->refreshDynamicProperties(); } /** @brief map a std::string to a context */ @@ -1286,7 +1212,7 @@ clipPrefsProxy(OfxEffectInstance* self, -void +bool OfxEffectInstance::checkOFXClipPreferences(double time, const RenderScale & scale, const std::string & reason, @@ -1294,7 +1220,7 @@ OfxEffectInstance::checkOFXClipPreferences(double time, { if (!_created) { - return; + return false; } assert(_context != eContextNone); assert( QThread::currentThread() == qApp->thread() ); @@ -1312,15 +1238,15 @@ OfxEffectInstance::checkOFXClipPreferences(double time, QWriteLocker preferencesLocker(_preferencesLock); if (forceGetClipPrefAction) { if (!_effect->getClipPreferences_safe(clipsPrefs,effectPrefs)) { - return; + return false; } } else { if (_effect->areClipPrefsDirty()) { if (!_effect->getClipPreferences_safe(clipsPrefs, effectPrefs)) { - return; + return false; } } else { - return; + return false; } } } @@ -1333,6 +1259,7 @@ OfxEffectInstance::checkOFXClipPreferences(double time, clipPrefsProxy(this,time,clipsPrefs,effectPrefs,modifiedClips); + bool changed = false; //////////////////////////////////////////////////////////////// //////////////////////////////// @@ -1341,12 +1268,21 @@ OfxEffectInstance::checkOFXClipPreferences(double time, { QWriteLocker l(_preferencesLock); for (std::map::const_iterator it = clipsPrefs.begin(); it != clipsPrefs.end(); ++it) { - it->first->setComponents(it->second.components); - it->first->setPixelDepth(it->second.bitdepth); - it->first->setAspectRatio(it->second.par); + if (it->first->getComponents() != it->second.components) { + it->first->setComponents(it->second.components); + changed = true; + } + if (it->second.bitdepth != it->first->getPixelDepth()) { + it->first->setPixelDepth(it->second.bitdepth); + changed = true; + } + if (it->second.par != it->first->getAspectRatio()) { + it->first->setAspectRatio(it->second.par); + changed = true; + } } - effectInstance()->updatePreferences_safe(effectPrefs.frameRate, effectPrefs.fielding, effectPrefs.premult, + changed |= effectInstance()->updatePreferences_safe(effectPrefs.frameRate, effectPrefs.fielding, effectPrefs.premult, effectPrefs.continuous, effectPrefs.frameVarying); } @@ -1368,45 +1304,9 @@ OfxEffectInstance::checkOFXClipPreferences(double time, } } - + return changed; } // checkOFXClipPreferences -void -OfxEffectInstance::restoreClipPreferences() -{ - assert(_context != eContextNone); - - double time = getApp()->getTimeLine()->currentFrame(); - RenderScale s; - s.x = s.y = 1.; - - ///Render scale support might not have been set already because getRegionOfDefinition could have failed until all non optional inputs were connected - if (supportsRenderScaleMaybe() == eSupportsMaybe) { - OfxRectD rod; - OfxPointD scaleOne; - scaleOne.x = scaleOne.y = 1.; - OfxStatus rodstat = _effect->getRegionOfDefinitionAction(time, scaleOne, 0, rod); - if ( (rodstat == kOfxStatOK) || (rodstat == kOfxStatReplyDefault) ) { - OfxPointD scale; - scale.x = 0.5; - scale.y = 0.5; - rodstat = _effect->getRegionOfDefinitionAction(time, scale, 0, rod); - if ( (rodstat == kOfxStatOK) || (rodstat == kOfxStatReplyDefault) ) { - setSupportsRenderScaleMaybe(eSupportsYes); - } else { - setSupportsRenderScaleMaybe(eSupportsNo); - } - } - - } - - - ///if all non optional clips are connected, call getClipPrefs - ///The clip preferences action is never called until all non optional clips have been attached to the plugin. - if ( _effect->areAllNonOptionalClipsConnected() ) { - checkOFXClipPreferences_public(time,s,kOfxChangeUserEdited,true, false); - } -} std::vector OfxEffectInstance::supportedFileFormats() const @@ -1744,7 +1644,7 @@ OfxEffectInstance::getFramesNeeded(double time, int view) } if ( (stat != kOfxStatOK) && (stat != kOfxStatReplyDefault) ) { - throw std::runtime_error("getFramesNeeded action failed"); + throw std::runtime_error("getFrameViewsNeeded action failed"); } else if (stat == kOfxStatOK) { for (OFX::Host::ImageEffect::ViewsRangeMap::iterator it = inputRanges.begin(); it != inputRanges.end(); ++it) { OfxClipInstance* clip = dynamic_cast(it->first); @@ -1769,7 +1669,7 @@ OfxEffectInstance::getFramesNeeded(double time, int view) } if ( (stat != kOfxStatOK) && (stat != kOfxStatReplyDefault) ) { throw std::runtime_error("getFramesNeeded action failed"); - } else if (stat == kOfxStatOK) { + } else { for (OFX::Host::ImageEffect::RangeMap::iterator it = inputRanges.begin(); it != inputRanges.end(); ++it) { OfxClipInstance* clip = dynamic_cast(it->first); assert(clip); @@ -1784,9 +1684,11 @@ OfxEffectInstance::getFramesNeeded(double time, int view) } } } - if (stat == kOfxStatReplyDefault) { - return Natron::EffectInstance::getFramesNeeded(time,view); - } + + //Default is already handled by HostSupport +// if (stat == kOfxStatReplyDefault) { +// return Natron::EffectInstance::getFramesNeeded(time,view); +// } return ret; } @@ -2533,11 +2435,11 @@ OfxEffectInstance::natronValueChangedReasonToOfxValueChangedReason(Natron::Value switch (reason) { case Natron::eValueChangedReasonUserEdited: case Natron::eValueChangedReasonNatronGuiEdited: + case Natron::eValueChangedReasonSlaveRefresh: + case Natron::eValueChangedReasonRestoreDefault: return kOfxChangeUserEdited; case Natron::eValueChangedReasonPluginEdited: case Natron::eValueChangedReasonNatronInternalEdited: - case Natron::eValueChangedReasonSlaveRefresh: - case Natron::eValueChangedReasonRestoreDefault: return kOfxChangePluginEdited; case Natron::eValueChangedReasonTimeChanged: return kOfxChangeTime; @@ -2714,6 +2616,12 @@ OfxEffectInstance::getMinorVersion() const return effectInstance()->getPlugin()->getVersionMinor(); } +bool +OfxEffectInstance::supportsRenderQuality() const +{ + return effectInstance()->supportsRenderQuality(); +} + bool OfxEffectInstance::supportsTiles() const { diff --git a/Engine/OfxEffectInstance.h b/Engine/OfxEffectInstance.h index 34a35d61bf..e4f3c36530 100644 --- a/Engine/OfxEffectInstance.h +++ b/Engine/OfxEffectInstance.h @@ -85,7 +85,8 @@ class AbstractOfxEffectInstance const NodeSerialization* serialization, const std::list >& paramValues, bool allowFileDialogs, - bool disableRenderScaleSupport) = 0; + bool disableRenderScaleSupport, + bool *hasUsedFileDialog) = 0; static QStringList makePluginGrouping(const std::string & pluginIdentifier, int versionMajor, int versionMinor, const std::string & pluginLabel, @@ -114,7 +115,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON const NodeSerialization* serialization, const std::list >& paramValues, bool allowFileDialogs, - bool disableRenderScaleSupport) OVERRIDE FINAL; + bool disableRenderScaleSupport, + bool *hasUsedFileDialog) OVERRIDE FINAL; Natron::OfxImageEffectInstance* effectInstance() WARN_UNUSED_RETURN { @@ -231,6 +233,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON * full RoD images to the effect whenever it fetches one. **/ virtual bool supportsTiles() const OVERRIDE FINAL WARN_UNUSED_RETURN; + virtual bool supportsRenderQuality() const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual Natron::PluginOpenGLRenderSupport supportsOpenGLRender() const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual bool doesTemporalClipAccess() const OVERRIDE FINAL WARN_UNUSED_RETURN; @@ -245,7 +248,6 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual bool supportsMultipleClipsPAR() const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual bool isHostChannelSelectorSupported(bool* defaultR,bool* defaultG, bool* defaultB, bool* defaultA) const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual void onInputChanged(int inputNo) OVERRIDE FINAL; - virtual void restoreClipPreferences() OVERRIDE FINAL; virtual std::vector supportedFileFormats() const OVERRIDE FINAL; virtual Natron::StatusEnum beginSequenceRender(double first, double last, @@ -270,7 +272,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual void getPreferredDepthAndComponents(int inputNb, std::list* comp, Natron::ImageBitDepthEnum* depth) const OVERRIDE FINAL; virtual Natron::SequentialPreferenceEnum getSequentialPreference() const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual Natron::ImagePremultiplicationEnum getOutputPremultiplication() const OVERRIDE FINAL WARN_UNUSED_RETURN; - virtual void checkOFXClipPreferences(double time, + virtual bool checkOFXClipPreferences(double time, const RenderScale & scale, const std::string & reason, bool forceGetClipPrefAction) OVERRIDE FINAL; diff --git a/Engine/OfxHost.cpp b/Engine/OfxHost.cpp index 9cccbe134e..88a4168c33 100644 --- a/Engine/OfxHost.cpp +++ b/Engine/OfxHost.cpp @@ -512,7 +512,8 @@ Natron::OfxHost::createOfxEffect(boost::shared_ptr node, const NodeSerialization* serialization, const std::list >& paramValues, bool allowFileDialogs, - bool disableRenderScaleSupport) + bool disableRenderScaleSupport, + bool *hasUsedFileDialog) { assert(node); const Natron::Plugin* natronPlugin = node->getPlugin(); @@ -528,7 +529,7 @@ Natron::OfxHost::createOfxEffect(boost::shared_ptr node, node->setLiveInstance(hostSideEffect); } - hostSideEffect->createOfxImageEffectInstance(plugin, desc, ctx,serialization,paramValues,allowFileDialogs,disableRenderScaleSupport); + hostSideEffect->createOfxImageEffectInstance(plugin, desc, ctx,serialization,paramValues,allowFileDialogs,disableRenderScaleSupport,hasUsedFileDialog); return hostSideEffect; } @@ -664,8 +665,6 @@ Natron::OfxHost::loadOFXPlugins(std::map::const_iterator foundReader = contexts.find(kOfxImageEffectContextReader); std::set::const_iterator foundWriter = contexts.find(kOfxImageEffectContextWriter); - bool userCreatable = !p->getDescriptor().isDeprecated(); - bool isInternalOnly = openfxId == PLUGINID_OFX_ROTO; Natron::Plugin* natronPlugin = appPTR->registerPlugin( groups, openfxId.c_str(), @@ -676,7 +675,8 @@ Natron::OfxHost::loadOFXPlugins(std::mapgetDescriptor().getRenderThreadSafety() == kOfxImageEffectRenderUnsafe, - p->getVersionMajor(), p->getVersionMinor(),userCreatable ); + p->getVersionMajor(), p->getVersionMinor(),p->getDescriptor().isDeprecated() ); + bool isInternalOnly = openfxId == PLUGINID_OFX_ROTO; if (isInternalOnly) { natronPlugin->setForInternalUseOnly(true); } diff --git a/Engine/OfxHost.h b/Engine/OfxHost.h index ae31615067..74046538f6 100644 --- a/Engine/OfxHost.h +++ b/Engine/OfxHost.h @@ -142,7 +142,8 @@ class OfxHost const NodeSerialization* serialization, const std::list >& paramValues, bool allowFileDialogs, - bool disableRenderScaleSupport); + bool disableRenderScaleSupport, + bool *hasUsedFileDialog); void addPathToLoadOFXPlugins(const std::string path); diff --git a/Engine/OfxImageEffectInstance.cpp b/Engine/OfxImageEffectInstance.cpp index 6a50d2a449..0f5b8acdc3 100644 --- a/Engine/OfxImageEffectInstance.cpp +++ b/Engine/OfxImageEffectInstance.cpp @@ -577,17 +577,19 @@ OfxImageEffectInstance::newParam(const std::string ¶mName, knob->setAsInstanceSpecific(); } + OfxParamToKnob* ptk = dynamic_cast(instance); + assert(ptk); + ptk->connectDynamicProperties(); + OfxPluginEntryPoint* interact = (OfxPluginEntryPoint*)descriptor.getProperties().getPointerProperty(kOfxParamPropInteractV1); if (interact) { - OfxParamToKnob* ptk = dynamic_cast(instance); - assert(ptk); OFX::Host::Interact::Descriptor & interactDesc = ptk->getInteractDesc(); interactDesc.getProperties().addProperties(interactDescProps); interactDesc.setEntryPoint(interact); interactDesc.describe(8, false); } - + return instance; } // newParam @@ -1061,15 +1063,32 @@ OfxImageEffectInstance::getClipPreferences_safe(std::map & from, to->beginChanges(); int dims = to->getDimension(); for (int i = 0; i < dims; ++i) { - to->evaluateValueChange(i, Natron::eValueChangedReasonPluginEdited); + to->evaluateValueChange(i, from->getCurrentTime(), Natron::eValueChangedReasonPluginEdited); } to->endChanges(); } @@ -237,6 +237,134 @@ copyFrom(const boost::shared_ptr & from, } } +PropertyModified_RAII::PropertyModified_RAII(OfxParamToKnob* h) +: _h(h) +{ + h->setDynamicPropertyModified(true); +} + +PropertyModified_RAII::~PropertyModified_RAII() +{ + _h->setDynamicPropertyModified(false); +} + +void +OfxParamToKnob::connectDynamicProperties() +{ + boost::shared_ptr knob = getKnob(); + if (!knob) { + return; + } + KnobSignalSlotHandler* handler = knob->getSignalSlotHandler().get(); + if (!handler) { + return; + } + QObject::connect(handler, SIGNAL(descriptionChanged()), this, SLOT(onDescriptionChanged())); + QObject::connect(handler, SIGNAL(evaluateOnChangeChanged(bool)), this, SLOT(onEvaluateOnChangeChanged(bool))); + QObject::connect(handler, SIGNAL(secretChanged()), this, SLOT(onSecretChanged())); + QObject::connect(handler, SIGNAL(enabledChanged()), this, SLOT(onEnabledChanged())); + QObject::connect(handler, SIGNAL(displayMinMaxChanged(double,double,int)), this, SLOT(onDisplayMinMaxChanged(double,double,int))); + QObject::connect(handler, SIGNAL(minMaxChanged(double,double,int)), this, SLOT(onMinMaxChanged(double,double,int))); +} + +void +OfxParamToKnob::onEvaluateOnChangeChanged(bool evaluate) +{ + if (isDynamicPropertyBeingModified()) { + return; + } + OFX::Host::Param::Instance* param = getOfxParam(); + assert(param); + param->getProperties().setIntProperty(kOfxParamPropEvaluateOnChange, (int)evaluate); +} + +void +OfxParamToKnob::onSecretChanged() +{ + if (isDynamicPropertyBeingModified()) { + return; + } + + OFX::Host::Param::Instance* param = getOfxParam(); + assert(param); + boost::shared_ptr knob = getKnob(); + if (!knob) { + return; + } + param->getProperties().setIntProperty(kOfxParamPropSecret, (int)knob->getIsSecret()); + +} + +void +OfxParamToKnob::onEnabledChanged() +{ + if (isDynamicPropertyBeingModified()) { + return; + } + + OFX::Host::Param::Instance* param = getOfxParam(); + assert(param); + boost::shared_ptr knob = getKnob(); + if (!knob) { + return; + } + param->getProperties().setIntProperty(kOfxParamPropEnabled, (int)knob->isEnabled(0)); +} + +void +OfxParamToKnob::onDescriptionChanged() +{ + if (isDynamicPropertyBeingModified()) { + return; + } + + OFX::Host::Param::Instance* param = getOfxParam(); + assert(param); + boost::shared_ptr knob = getKnob(); + if (!knob) { + return; + } + param->getProperties().setStringProperty(kOfxPropLabel, knob->getDescription()); +} + +void +OfxParamToKnob::onDisplayMinMaxChanged(double min,double max, int index) +{ + if (isDynamicPropertyBeingModified()) { + return; + } + + OFX::Host::Param::Instance* param = getOfxParam(); + assert(param); + if (hasDoubleMinMaxProps()) { + param->getProperties().setDoubleProperty(kOfxParamPropDisplayMin, min, index); + param->getProperties().setDoubleProperty(kOfxParamPropDisplayMax, max, index); + } else { + param->getProperties().setIntProperty(kOfxParamPropDisplayMin, (int)min, index); + param->getProperties().setIntProperty(kOfxParamPropDisplayMax, (int)max, index); + } +} + +void +OfxParamToKnob::onMinMaxChanged(double min,double max, int index) +{ + if (isDynamicPropertyBeingModified()) { + return; + } + + OFX::Host::Param::Instance* param = getOfxParam(); + assert(param); + if (hasDoubleMinMaxProps()) { + param->getProperties().setDoubleProperty(kOfxParamPropMin, min, index); + param->getProperties().setDoubleProperty(kOfxParamPropMax, max, index); + } else { + param->getProperties().setIntProperty(kOfxParamPropMin, (int)min, index); + param->getProperties().setIntProperty(kOfxParamPropMax, (int)max, index); + } +} + + + ////////////////////////// OfxPushButtonInstance ///////////////////////////////////////////////// OfxPushButtonInstance::OfxPushButtonInstance(OfxEffectInstance* node, @@ -253,6 +381,7 @@ OfxPushButtonInstance::OfxPushButtonInstance(OfxEffectInstance* node, void OfxPushButtonInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -260,18 +389,21 @@ OfxPushButtonInstance::setEnabled() void OfxPushButtonInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxPushButtonInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } void OfxPushButtonInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -346,6 +478,7 @@ OfxIntegerInstance::set(OfxTime time, void OfxIntegerInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -353,18 +486,21 @@ OfxIntegerInstance::setEnabled() void OfxIntegerInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxIntegerInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } void OfxIntegerInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -429,6 +565,7 @@ OfxIntegerInstance::onKnobAnimationLevelChanged(int,int lvl) void OfxIntegerInstance::setDisplayRange() { + SET_DYNAMIC_PROPERTY_EDITED(); int displayMin = getProperties().getIntProperty(kOfxParamPropDisplayMin); int displayMax = getProperties().getIntProperty(kOfxParamPropDisplayMax); @@ -439,6 +576,7 @@ OfxIntegerInstance::setDisplayRange() void OfxIntegerInstance::setRange() { + SET_DYNAMIC_PROPERTY_EDITED(); int mini = getProperties().getIntProperty(kOfxParamPropMin); int maxi = getProperties().getIntProperty(kOfxParamPropMax); @@ -558,6 +696,7 @@ OfxDoubleInstance::integrate(OfxTime time1, void OfxDoubleInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -565,24 +704,28 @@ OfxDoubleInstance::setEnabled() void OfxDoubleInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxDoubleInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } void OfxDoubleInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } void OfxDoubleInstance::setDisplayRange() { + SET_DYNAMIC_PROPERTY_EDITED(); double displayMin = getProperties().getDoubleProperty(kOfxParamPropDisplayMin); double displayMax = getProperties().getDoubleProperty(kOfxParamPropDisplayMax); @@ -593,6 +736,7 @@ OfxDoubleInstance::setDisplayRange() void OfxDoubleInstance::setRange() { + SET_DYNAMIC_PROPERTY_EDITED(); double mini = getProperties().getDoubleProperty(kOfxParamPropMin); double maxi = getProperties().getDoubleProperty(kOfxParamPropMax); @@ -720,6 +864,7 @@ OfxBooleanInstance::set(OfxTime time, void OfxBooleanInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -727,18 +872,21 @@ OfxBooleanInstance::setEnabled() void OfxBooleanInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxBooleanInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } void OfxBooleanInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -873,6 +1021,7 @@ OfxChoiceInstance::set(OfxTime time, void OfxChoiceInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -880,18 +1029,21 @@ OfxChoiceInstance::setEnabled() void OfxChoiceInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxChoiceInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } void OfxChoiceInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -1113,6 +1265,7 @@ OfxRGBAInstance::integrate(OfxTime time1, void OfxRGBAInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -1120,12 +1273,14 @@ OfxRGBAInstance::setEnabled() void OfxRGBAInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxRGBAInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } @@ -1133,6 +1288,7 @@ OfxRGBAInstance::setLabel() void OfxRGBAInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -1325,6 +1481,7 @@ OfxRGBInstance::integrate(OfxTime time1, void OfxRGBInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -1332,18 +1489,21 @@ OfxRGBInstance::setEnabled() void OfxRGBInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxRGBInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } void OfxRGBInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -1558,6 +1718,7 @@ OfxDouble2DInstance::integrate(OfxTime time1, void OfxDouble2DInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -1565,12 +1726,14 @@ OfxDouble2DInstance::setEnabled() void OfxDouble2DInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxDouble2DInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } @@ -1578,12 +1741,14 @@ OfxDouble2DInstance::setLabel() void OfxDouble2DInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } void OfxDouble2DInstance::setDisplayRange() { + SET_DYNAMIC_PROPERTY_EDITED(); std::vector displayMins(2); std::vector displayMaxs(2); @@ -1597,6 +1762,7 @@ OfxDouble2DInstance::setDisplayRange() void OfxDouble2DInstance::setRange() { + SET_DYNAMIC_PROPERTY_EDITED(); std::vector displayMins(2); std::vector displayMaxs(2); @@ -1764,6 +1930,7 @@ OfxInteger2DInstance::set(OfxTime time, void OfxInteger2DInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -1771,12 +1938,14 @@ OfxInteger2DInstance::setEnabled() void OfxInteger2DInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxInteger2DInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } @@ -1784,6 +1953,7 @@ OfxInteger2DInstance::setLabel() void OfxInteger2DInstance::setDisplayRange() { + SET_DYNAMIC_PROPERTY_EDITED(); std::vector displayMins(2); std::vector displayMaxs(2); @@ -1798,6 +1968,8 @@ OfxInteger2DInstance::setDisplayRange() void OfxInteger2DInstance::setRange() { + SET_DYNAMIC_PROPERTY_EDITED(); + std::vector displayMins(2); std::vector displayMaxs(2); @@ -2002,6 +2174,7 @@ OfxDouble3DInstance::integrate(OfxTime time1, void OfxDouble3DInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -2009,12 +2182,14 @@ OfxDouble3DInstance::setEnabled() void OfxDouble3DInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxDouble3DInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } @@ -2022,6 +2197,7 @@ OfxDouble3DInstance::setLabel() void OfxDouble3DInstance::setDisplayRange() { + SET_DYNAMIC_PROPERTY_EDITED(); std::vector displayMins(3); std::vector displayMaxs(3); @@ -2038,6 +2214,7 @@ OfxDouble3DInstance::setDisplayRange() void OfxDouble3DInstance::setRange() { + SET_DYNAMIC_PROPERTY_EDITED(); std::vector displayMins(3); std::vector displayMaxs(3); @@ -2054,6 +2231,7 @@ OfxDouble3DInstance::setRange() void OfxDouble3DInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -2222,6 +2400,7 @@ OfxInteger3DInstance::set(OfxTime time, void OfxInteger3DInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -2229,12 +2408,14 @@ OfxInteger3DInstance::setEnabled() void OfxInteger3DInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxInteger3DInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } @@ -2242,6 +2423,7 @@ OfxInteger3DInstance::setLabel() void OfxInteger3DInstance::setDisplayRange() { + SET_DYNAMIC_PROPERTY_EDITED(); std::vector displayMins(3); std::vector displayMaxs(3); @@ -2258,6 +2440,7 @@ OfxInteger3DInstance::setDisplayRange() void OfxInteger3DInstance::setRange() { + SET_DYNAMIC_PROPERTY_EDITED(); std::vector displayMins(3); std::vector displayMaxs(3); @@ -2274,6 +2457,7 @@ OfxInteger3DInstance::setRange() void OfxInteger3DInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -2368,6 +2552,7 @@ boost::shared_ptr OfxGroupInstance::getKnob() const void OfxGroupInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _groupKnob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -2375,12 +2560,14 @@ OfxGroupInstance::setEnabled() void OfxGroupInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _groupKnob.lock()->setSecret( getSecret() ); } void OfxGroupInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _groupKnob.lock()->setDescription(getParamLabel(this)); } @@ -2399,6 +2586,7 @@ OfxPageInstance::OfxPageInstance(OfxEffectInstance* node, void OfxPageInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _pageKnob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -2406,12 +2594,14 @@ OfxPageInstance::setEnabled() void OfxPageInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _pageKnob.lock()->setAllDimensionsEnabled( getSecret() ); } void OfxPageInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _pageKnob.lock()->setDescription(getParamLabel(this)); } @@ -2658,6 +2848,7 @@ OfxStringInstance::getKnob() const void OfxStringInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); if (_fileKnob.lock()) { _fileKnob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -2675,6 +2866,7 @@ OfxStringInstance::setEnabled() void OfxStringInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); if (_fileKnob.lock()) { _fileKnob.lock()->setDescription(getParamLabel(this)); } @@ -2693,6 +2885,7 @@ OfxStringInstance::setLabel() void OfxStringInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); if (_fileKnob.lock()) { _fileKnob.lock()->setSecret( getSecret() ); } @@ -2710,6 +2903,7 @@ OfxStringInstance::setSecret() void OfxStringInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); if (_fileKnob.lock()) { _fileKnob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -2944,6 +3138,7 @@ boost::shared_ptr OfxCustomInstance::getKnob() const void OfxCustomInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -2951,18 +3146,21 @@ OfxCustomInstance::setEnabled() void OfxCustomInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxCustomInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription(getParamLabel(this)); } void OfxCustomInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -3081,6 +3279,7 @@ boost::shared_ptr OfxParametricInstance::getKnob() const void OfxParametricInstance::setEnabled() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setAllDimensionsEnabled( getEnabled() ); } @@ -3088,12 +3287,14 @@ OfxParametricInstance::setEnabled() void OfxParametricInstance::setSecret() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setSecret( getSecret() ); } void OfxParametricInstance::setEvaluateOnChange() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setEvaluateOnChange( getEvaluateOnChange() ); } @@ -3101,6 +3302,7 @@ OfxParametricInstance::setEvaluateOnChange() void OfxParametricInstance::setLabel() { + SET_DYNAMIC_PROPERTY_EDITED(); _knob.lock()->setDescription( getParamLabel(this) ); for (int i = 0; i < _knob.lock()->getDimension(); ++i) { const std::string & curveName = getProperties().getStringProperty(kOfxParamPropDimensionLabel,i); @@ -3111,6 +3313,7 @@ OfxParametricInstance::setLabel() void OfxParametricInstance::setDisplayRange() { + SET_DYNAMIC_PROPERTY_EDITED(); double range_min = getProperties().getDoubleProperty(kOfxParamPropParametricRange,0); double range_max = getProperties().getDoubleProperty(kOfxParamPropParametricRange,1); diff --git a/Engine/OfxParamInstance.h b/Engine/OfxParamInstance.h index 88fb16cf0e..26b0980083 100644 --- a/Engine/OfxParamInstance.h +++ b/Engine/OfxParamInstance.h @@ -35,6 +35,7 @@ #endif CLANG_DIAG_OFF(deprecated) #include +#include CLANG_DIAG_ON(deprecated) #include "Engine/ThreadStorage.h" @@ -81,23 +82,87 @@ class OfxOverlayInteract; } -class OfxParamToKnob +class OfxParamToKnob; +class PropertyModified_RAII { - OFX::Host::Interact::Descriptor interactDesc; + OfxParamToKnob* _h; +public: + + PropertyModified_RAII(OfxParamToKnob* h); + + ~PropertyModified_RAII(); +}; + +#define SET_DYNAMIC_PROPERTY_EDITED() PropertyModified_RAII dynamic_prop_edited_raii(this) + +class OfxParamToKnob : public QObject +{ + + Q_OBJECT + friend class PropertyModified_RAII; + + OFX::Host::Interact::Descriptor interactDesc; + mutable QMutex dynamicPropModifiedMutex; + bool _dynamicPropModified; + + public: + OfxParamToKnob() + : dynamicPropModifiedMutex() + , _dynamicPropModified(false) { } virtual boost::shared_ptr getKnob() const = 0; + virtual OFX::Host::Param::Instance* getOfxParam() = 0; + OFX::Host::Interact::Descriptor & getInteractDesc() { return interactDesc; } + + + + void connectDynamicProperties(); + +public Q_SLOTS: + + /* + These are called when the properties are changed on the Natron side + */ + void onEvaluateOnChangeChanged(bool evaluate); + void onSecretChanged(); + void onEnabledChanged(); + void onDescriptionChanged(); + void onDisplayMinMaxChanged(double min,double max, int index); + void onMinMaxChanged(double min,double max, int index); + + +protected: + + void setDynamicPropertyModified(bool dynamicPropModified) + { + QMutexLocker k(&dynamicPropModifiedMutex); + _dynamicPropModified = dynamicPropModified; + } + + bool isDynamicPropertyBeingModified() const + { + QMutexLocker k(&dynamicPropModifiedMutex); + return _dynamicPropModified; + } + + virtual bool hasDoubleMinMaxProps() const { return true; } + }; + + + + class OfxPushButtonInstance : public OFX::Host::Param::PushbuttonInstance, public OfxParamToKnob { @@ -116,6 +181,7 @@ class OfxPushButtonInstance /// callback which should set evaluate on change virtual void setEvaluateOnChange() OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } private: boost::weak_ptr _knob; @@ -123,7 +189,7 @@ class OfxPushButtonInstance class OfxIntegerInstance - : public QObject, public OFX::Host::Param::IntegerInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::IntegerInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -159,17 +225,21 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } public Q_SLOTS: void onKnobAnimationLevelChanged(int dim,int lvl); private: + + virtual bool hasDoubleMinMaxProps() const OVERRIDE FINAL { return false; } + boost::weak_ptr _knob; }; class OfxDoubleInstance - : public QObject, public OFX::Host::Param::DoubleInstance, public OfxParamToKnob +: public OfxParamToKnob, public OFX::Host::Param::DoubleInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -207,6 +277,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } bool isAnimated() const; @@ -215,12 +286,13 @@ public Q_SLOTS: void onKnobAnimationLevelChanged(int,int lvl); private: + boost::weak_ptr _knob; OfxEffectInstance* _node; }; class OfxBooleanInstance - : public QObject, public OFX::Host::Param::BooleanInstance, public OfxParamToKnob +: public OfxParamToKnob, public OFX::Host::Param::BooleanInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -254,17 +326,21 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } public Q_SLOTS: void onKnobAnimationLevelChanged(int,int lvl); private: + + virtual bool hasDoubleMinMaxProps() const OVERRIDE FINAL { return false; } + boost::weak_ptr _knob; }; class OfxChoiceInstance - : public QObject, public OFX::Host::Param::ChoiceInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::ChoiceInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -300,18 +376,22 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } public Q_SLOTS: void onKnobAnimationLevelChanged(int,int lvl); private: + + virtual bool hasDoubleMinMaxProps() const OVERRIDE FINAL { return false; } + std::vector _entries; boost::weak_ptr _knob; }; class OfxRGBAInstance - : public QObject, public OFX::Host::Param::RGBAInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::RGBAInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -346,6 +426,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } bool isAnimated(int dimension) const; bool isAnimated() const; @@ -360,7 +441,7 @@ public Q_SLOTS: class OfxRGBInstance - : public QObject, public OFX::Host::Param::RGBInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::RGBInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -395,6 +476,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } bool isAnimated(int dimension) const; bool isAnimated() const; @@ -408,7 +490,7 @@ public Q_SLOTS: }; class OfxDouble2DInstance - : public QObject, public OFX::Host::Param::Double2DInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::Double2DInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -445,6 +527,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } bool isAnimated(int dimension) const; bool isAnimated() const; @@ -460,7 +543,7 @@ public Q_SLOTS: class OfxInteger2DInstance - : public QObject, public OFX::Host::Param::Integer2DInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::Integer2DInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -496,18 +579,22 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } public Q_SLOTS: void onKnobAnimationLevelChanged(int,int lvl); private: + + virtual bool hasDoubleMinMaxProps() const OVERRIDE FINAL { return false; } + OfxEffectInstance* _node; boost::weak_ptr _knob; }; class OfxDouble3DInstance - : public QObject, public OFX::Host::Param::Double3DInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::Double3DInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -545,6 +632,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } bool isAnimated(int dimension) const; bool isAnimated() const; @@ -559,7 +647,7 @@ public Q_SLOTS: }; class OfxInteger3DInstance - : public QObject, public OFX::Host::Param::Integer3DInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::Integer3DInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -595,12 +683,16 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } public Q_SLOTS: void onKnobAnimationLevelChanged(int,int lvl); private: + + virtual bool hasDoubleMinMaxProps() const OVERRIDE FINAL { return false; } + OfxEffectInstance* _node; boost::weak_ptr _knob; }; @@ -616,6 +708,7 @@ class OfxGroupInstance void addKnob(boost::shared_ptr k); virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } virtual ~OfxGroupInstance() { @@ -650,14 +743,14 @@ class OfxPageInstance // callback which should set secret state as appropriate virtual void setSecret() OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; - + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } private: boost::weak_ptr _pageKnob; }; class OfxStringInstance - : public QObject, public OFX::Host::Param::StringInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::StringInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -702,7 +795,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllKeys() OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; - + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } + virtual ~OfxStringInstance() { } @@ -733,7 +827,7 @@ public Q_SLOTS: class OfxCustomInstance - : public QObject, public OFX::Host::Param::CustomInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::Param::CustomInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -770,7 +864,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON /// callback which should set evaluate on change virtual void setEvaluateOnChange() OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; - + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } + virtual ~OfxCustomInstance() { } @@ -803,7 +898,7 @@ public Q_SLOTS: class OfxParametricInstance - : public QObject, public OFX::Host::ParametricParam::ParametricInstance, public OfxParamToKnob + : public OfxParamToKnob, public OFX::Host::ParametricParam::ParametricInstance { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -857,7 +952,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual OfxStatus deleteAllControlPoints(int curveIndex) OVERRIDE FINAL; virtual OfxStatus copyFrom(const OFX::Host::Param::Instance &instance, OfxTime offset, const OfxRangeD* range) OVERRIDE FINAL; virtual boost::shared_ptr getKnob() const OVERRIDE FINAL; - + virtual OFX::Host::Param::Instance* getOfxParam() OVERRIDE FINAL { return this; } + public Q_SLOTS: void onCustomBackgroundDrawingRequested(); diff --git a/Engine/OpenGLViewerI.h b/Engine/OpenGLViewerI.h index 9af63da153..56bab69185 100644 --- a/Engine/OpenGLViewerI.h +++ b/Engine/OpenGLViewerI.h @@ -197,6 +197,11 @@ class OpenGLViewerI **/ virtual void clearLastRenderedImage() = 0; + /** + *@brief To be called if redraw needs to be called now without waiting the end of the event loop + **/ + virtual void redrawNow() = 0; + }; diff --git a/Engine/OutputSchedulerThread.cpp b/Engine/OutputSchedulerThread.cpp index 99f4996dfe..f1adbca9be 100644 --- a/Engine/OutputSchedulerThread.cpp +++ b/Engine/OutputSchedulerThread.cpp @@ -146,6 +146,11 @@ struct ProducedFrame bool processRequest; }; +static bool isBufferFull(int nbBufferedElement, int hardwardIdealThreadCount) +{ + return nbBufferedElement >= hardwardIdealThreadCount * 3; +} + struct OutputSchedulerThreadPrivate { @@ -614,9 +619,8 @@ OutputSchedulerThread::pushFramesToRenderInternal(int startingFrame,int nThreads firstFrame = _imp->livingRunArgs.firstFrame; lastFrame = _imp->livingRunArgs.lastFrame; } - + PlaybackModeEnum pMode = _imp->engine->getPlaybackMode(); - if (firstFrame == lastFrame) { _imp->framesToRender.push_back(startingFrame); _imp->lastFramePushedIndex = startingFrame; @@ -721,7 +725,7 @@ OutputSchedulerThread::pickFrameToRender(RenderThreadTask* thread,bool* enableRe bool bufferFull; { QMutexLocker k(&_imp->bufMutex); - bufferFull = (int)_imp->buf.size() >= nbThreadsHardware * 3; + bufferFull = isBufferFull((int)_imp->buf.size(),nbThreadsHardware); } QMutexLocker l(&_imp->framesToRenderMutex); @@ -732,11 +736,11 @@ OutputSchedulerThread::pickFrameToRender(RenderThreadTask* thread,bool* enableRe _imp->framesToRenderNotEmptyCond.wait(&_imp->framesToRenderMutex); - { QMutexLocker k(&_imp->bufMutex); - bufferFull = (int)_imp->buf.size() >= nbThreadsHardware * 3; + bufferFull = isBufferFull((int)_imp->buf.size(),nbThreadsHardware); } + } @@ -747,7 +751,6 @@ OutputSchedulerThread::pickFrameToRender(RenderThreadTask* thread,bool* enableRe int ret = _imp->framesToRender.front(); _imp->framesToRender.pop_front(); - ///Flag the thread as active { QMutexLocker l(&_imp->renderThreadsMutex); @@ -843,7 +846,6 @@ OutputSchedulerThread::startRender() Natron::SchedulingPolicyEnum policy = getSchedulingPolicy(); - if (policy == Natron::eSchedulingPolicyFFA) { @@ -869,7 +871,6 @@ OutputSchedulerThread::startRender() return; } } - ///Push as many frames as there are threads pushFramesToRender(startingFrame,nThreads); } @@ -921,6 +922,7 @@ OutputSchedulerThread::stopRender() QMutexLocker abortBeingProcessedLocker(&_imp->abortBeingProcessedMutex); _imp->abortBeingProcessed = true; + bool wasAborted; { QMutexLocker l(&_imp->abortedRequestedMutex); @@ -929,6 +931,26 @@ OutputSchedulerThread::stopRender() ///reset back the abort flag _imp->abortRequested = 0; + + ///Clear any frames that were processed ahead + { + QMutexLocker l2(&_imp->bufMutex); + _imp->clearBuffer(); + } + + ///Notify everyone that the render is finished + _imp->engine->s_renderFinished(wasAborted ? 1 : 0); + + onRenderStopped(wasAborted); + + ///Flag that we're no longer doing work + { + QMutexLocker l(&_imp->workingMutex); + _imp->working = false; + } + + + { QMutexLocker k(&_imp->abortFlagMutex); _imp->abortFlag = false; @@ -937,27 +959,11 @@ OutputSchedulerThread::stopRender() _imp->abortedRequestedCondition.wakeAll(); } - ///Flag that we're no longer doing work - { - QMutexLocker l(&_imp->workingMutex); - _imp->working = false; - } - - ///Clear any frames that were processed ahead - { - QMutexLocker l2(&_imp->bufMutex); - _imp->clearBuffer(); - } - - ///Notify everyone that the render is finished - _imp->engine->s_renderFinished(wasAborted ? 1 : 0); - - onRenderStopped(wasAborted); + } - { QMutexLocker l(&_imp->startRequestsMutex); while (_imp->startRequests <= 0) { @@ -997,6 +1003,10 @@ OutputSchedulerThread::run() bufferEmpty = _imp->buf.empty(); } + int expectedTimeToRender; + bool isAbortRequested; + bool blocking; + while (!bufferEmpty) { if ( _imp->checkForExit() ) { @@ -1014,7 +1024,7 @@ OutputSchedulerThread::run() } } - int expectedTimeToRender = timelineGetTime(); + expectedTimeToRender = timelineGetTime(); BufferedFrames framesToRender; { @@ -1088,6 +1098,14 @@ OutputSchedulerThread::run() } + { + QMutexLocker abortRequestedLock (&_imp->abortedRequestedMutex); + isAbortRequested = _imp->abortRequested > 0; + blocking = _imp->isAbortRequestBlocking; + } + if (isAbortRequested) { + break; + } if (_imp->mode == eProcessFrameBySchedulerThread) { processFrame(framesToRender); @@ -1156,18 +1174,30 @@ OutputSchedulerThread::run() } // while(!bufferEmpty) - bool isAbortRequested; - bool blocking; + ///Refresh the abort requested flag because the abort might have got called in between { QMutexLocker abortRequestedLock (&_imp->abortedRequestedMutex); isAbortRequested = _imp->abortRequested > 0; blocking = _imp->isAbortRequestBlocking; } + + if (!renderFinished && !isAbortRequested) { - QMutexLocker bufLocker (&_imp->bufMutex); - ///Wait here for more frames to be rendered, we will be woken up once appendToBuffer(...) is called + QMutexLocker bufLocker (&_imp->bufMutex); + ///Wait here for more frames to be rendered, we will be woken up once appendToBuffer(...) is called + if (_imp->buf.empty()) { _imp->bufCondition.wait(&_imp->bufMutex); + } + /*else { + + if (isBufferFull((int)_imp->buf.size(),appPTR->getHardwareIdealThreadCount())) { + qDebug() << "PLAYBACK STALL detected: Internal buffer is full but frame" << expectedTimeToRender + << "is still expected to be rendered. Stopping render."; + assert(false); + break; + } + }*/ } else { if (blocking) { //Move the timeline to the last rendered frame to keep it in sync with what is displayed @@ -1175,7 +1205,7 @@ OutputSchedulerThread::run() } break; } - } + } // for (;;) stopRender(); @@ -1516,6 +1546,10 @@ OutputSchedulerThread::abortRendering(bool autoRestart,bool blocking) _imp->framesToRender.clear(); } + { + QMutexLocker k(&_imp->bufMutex); + _imp->buf.clear(); + } if (isMainThread) { @@ -1529,7 +1563,7 @@ OutputSchedulerThread::abortRendering(bool autoRestart,bool blocking) _imp->runningCallbackCond.wakeAll(); } - } + } // QMutexLocker l2(&_imp->processMutex); ///If the scheduler is asleep waiting for the buffer to be filling up, we post a fake request ///that will not be processed anyway because the first thing it does is checking for abort { @@ -2125,6 +2159,7 @@ class DefaultRenderFrameRunnable : public RenderThreadTask rod, // < any precomputed rod ? in canonical coordinates components, imageDepth, + false, _imp->output),&planes); if (retCode != EffectInstance::eRenderRoIRetCodeOk) { _imp->scheduler->notifyRenderFailure("Error caught while rendering"); @@ -2225,6 +2260,7 @@ DefaultScheduler::processFrame(const BufferedFrames& frames) rod, components, imageDepth, + false, _effect, inputImages); try { @@ -2434,8 +2470,10 @@ ViewerDisplayScheduler::processFrame(const BufferedFrames& frames) assert(params); _viewer->updateViewer(params); } + _viewer->redrawViewerNow(); + } else { + _viewer->redrawViewer(); } - _viewer->redrawViewer(); } diff --git a/Engine/ParallelRenderArgs.cpp b/Engine/ParallelRenderArgs.cpp index 00113297b4..d96c96eb09 100644 --- a/Engine/ParallelRenderArgs.cpp +++ b/Engine/ParallelRenderArgs.cpp @@ -249,6 +249,7 @@ Natron::EffectInstance::RenderRoIRetCode EffectInstance::treeRecurseFunctor(bool RectD(), // < did we precompute any RoD to speed-up the call ? componentsToRender, //< requested comps inputPrefDepth, + false, effect); @@ -302,6 +303,13 @@ Natron::StatusEnum Natron::EffectInstance::getInputsRoIsFunctor(bool useTransfor EffectInstance* effect = node->getLiveInstance(); assert(effect); + if (effect->supportsRenderScaleMaybe() == Natron::EffectInstance::eSupportsMaybe) { + /* + If this flag was not set already that means it probably failed all calls to getRegionOfDefinition. + We safely fail here + */ + return eStatusFailed; + } assert(effect->supportsRenderScaleMaybe() == Natron::EffectInstance::eSupportsNo || effect->supportsRenderScaleMaybe() == Natron::EffectInstance::eSupportsYes); bool supportsRs = effect->supportsRenderScale(); diff --git a/Engine/ParameterWrapper.cpp b/Engine/ParameterWrapper.cpp index 412cfe45ce..b7f7858539 100644 --- a/Engine/ParameterWrapper.cpp +++ b/Engine/ParameterWrapper.cpp @@ -394,14 +394,14 @@ Int3DParam::get() const } int -IntParam::get(int frame) const +IntParam::get(double frame) const { boost::shared_ptr knob = _intKnob.lock(); return knob->getValueAtTime(frame,0); } Int2DTuple -Int2DParam::get(int frame) const +Int2DParam::get(double frame) const { Int2DTuple ret; boost::shared_ptr knob = _intKnob.lock(); @@ -412,7 +412,7 @@ Int2DParam::get(int frame) const Int3DTuple -Int3DParam::get(int frame) const +Int3DParam::get(double frame) const { Int3DTuple ret; boost::shared_ptr knob = _intKnob.lock(); @@ -490,7 +490,7 @@ IntParam::setValue(int value,int dimension) } int -IntParam::getValueAtTime(int time,int dimension) const +IntParam::getValueAtTime(double time,int dimension) const { return _intKnob.lock()->getValueAtTime(time,dimension); } @@ -623,13 +623,13 @@ Double3DParam::get() const } double -DoubleParam::get(int frame) const +DoubleParam::get(double frame) const { return _doubleKnob.lock()->getValueAtTime(frame, 0); } Double2DTuple -Double2DParam::get(int frame) const +Double2DParam::get(double frame) const { Double2DTuple ret; boost::shared_ptr knob = _doubleKnob.lock(); @@ -639,7 +639,7 @@ Double2DParam::get(int frame) const } Double3DTuple -Double3DParam::get(int frame) const +Double3DParam::get(double frame) const { boost::shared_ptr knob = _doubleKnob.lock(); Double3DTuple ret; @@ -727,7 +727,7 @@ DoubleParam::setValue(double value,int dimension) } double -DoubleParam::getValueAtTime(int time,int dimension) const +DoubleParam::getValueAtTime(double time,int dimension) const { return _doubleKnob.lock()->getValueAtTime(time,dimension); } @@ -848,7 +848,7 @@ ColorParam::get() const ColorTuple -ColorParam::get(int frame) const +ColorParam::get(double frame) const { ColorTuple ret; boost::shared_ptr knob = _colorKnob.lock(); @@ -914,7 +914,7 @@ ColorParam::setValue(double value,int dimension) } double -ColorParam::getValueAtTime(int time,int dimension) const +ColorParam::getValueAtTime(double time,int dimension) const { return _colorKnob.lock()->getValueAtTime(time,dimension); } @@ -1025,7 +1025,7 @@ ChoiceParam::get() const } int -ChoiceParam::get(int frame) const +ChoiceParam::get(double frame) const { return _choiceKnob.lock()->getValueAtTime(frame,0); } @@ -1063,7 +1063,7 @@ ChoiceParam::setValue(int value) } int -ChoiceParam::getValueAtTime(int time) const +ChoiceParam::getValueAtTime(double time) const { return _choiceKnob.lock()->getValueAtTime(time, 0); } @@ -1183,7 +1183,7 @@ BooleanParam::get() const } bool -BooleanParam::get(int frame) const +BooleanParam::get(double frame) const { return _boolKnob.lock()->getValueAtTime(frame,0); } @@ -1214,7 +1214,7 @@ BooleanParam::setValue(bool value) } bool -BooleanParam::getValueAtTime(int time) const +BooleanParam::getValueAtTime(double time) const { return _boolKnob.lock()->getValueAtTime(time,0); } @@ -1274,7 +1274,7 @@ StringParamBase::get() const } std::string -StringParamBase::get(int frame) const +StringParamBase::get(double frame) const { return _stringKnob.lock()->getValueAtTime(frame,0); } @@ -1305,7 +1305,7 @@ StringParamBase::setValue(const std::string& value) } std::string -StringParamBase::getValueAtTime(int time) const +StringParamBase::getValueAtTime(double time) const { return _stringKnob.lock()->getValueAtTime(time,0); } @@ -1494,6 +1494,12 @@ ButtonParam::setIconFilePath(const std::string& icon) _buttonKnob.lock()->setIconFilePath(icon); } +void +ButtonParam::trigger() +{ + _buttonKnob.lock()->trigger(); +} + ///////////////////GroupParam GroupParam::GroupParam(const boost::shared_ptr& knob) diff --git a/Engine/ParameterWrapper.h b/Engine/ParameterWrapper.h index 6067403615..243528458d 100644 --- a/Engine/ParameterWrapper.h +++ b/Engine/ParameterWrapper.h @@ -289,7 +289,7 @@ class IntParam : public AnimatedParam * @brief Convenience function that calls getValue() for all dimensions and store them in a tuple-like struct. **/ int get() const; - int get(int frame) const; + int get(double frame) const; /** * @brief Convenience functions for multi-dimensional setting of values @@ -314,7 +314,7 @@ class IntParam : public AnimatedParam * 2 keyframes surrounding the given time. If time is exactly one keyframe then the value of the keyframe is returned. * If this parameter is not animated for the given dimension, then this function returns the same as getValue(int) **/ - int getValueAtTime(int time,int dimension = 0) const; + int getValueAtTime(double time,int dimension = 0) const; /** * @brief Set a new keyframe on the parameter at the given time. If a keyframe already exists, it will modify it. @@ -398,7 +398,7 @@ class Int2DParam : public IntParam virtual ~Int2DParam() {} Int2DTuple get() const; - Int2DTuple get(int frame) const; + Int2DTuple get(double frame) const; void set(int x, int y); void set(int x, int y, int frame); }; @@ -412,7 +412,7 @@ class Int3DParam : public Int2DParam virtual ~Int3DParam() {} Int3DTuple get() const; - Int3DTuple get(int frame) const; + Int3DTuple get(double frame) const; void set(int x, int y, int z); void set(int x, int y, int z, int frame); }; @@ -434,7 +434,7 @@ class DoubleParam : public AnimatedParam * @brief Convenience function that calls getValue() for all dimensions and store them in a tuple-like struct. **/ double get() const; - double get(int frame) const; + double get(double frame) const; /** * @brief Convenience functions for multi-dimensional setting of values @@ -459,7 +459,7 @@ class DoubleParam : public AnimatedParam * 2 keyframes surrounding the given time. If time is exactly one keyframe then the value of the keyframe is returned. * If this parameter is not animated for the given dimension, then this function returns the same as getValue(int) **/ - double getValueAtTime(int time,int dimension = 0) const; + double getValueAtTime(double time,int dimension = 0) const; /** * @brief Set a new keyframe on the parameter at the given time. If a keyframe already exists, it will modify it. @@ -543,7 +543,7 @@ class Double2DParam : public DoubleParam virtual ~Double2DParam() {} Double2DTuple get() const; - Double2DTuple get(int frame) const; + Double2DTuple get(double frame) const; void set(double x, double y); void set(double x, double y, int frame); @@ -559,7 +559,7 @@ class Double3DParam : public Double2DParam virtual ~Double3DParam() {} Double3DTuple get() const; - Double3DTuple get(int frame) const; + Double3DTuple get(double frame) const; void set(double x, double y, double z); void set(double x, double y, double z, int frame); }; @@ -580,7 +580,7 @@ class ColorParam : public AnimatedParam * @brief Convenience function that calls getValue() for all dimensions and store them in a tuple-like struct. **/ ColorTuple get() const; - ColorTuple get(int frame) const; + ColorTuple get(double frame) const; /** * @brief Convenience functions for multi-dimensional setting of values @@ -608,7 +608,7 @@ class ColorParam : public AnimatedParam * 2 keyframes surrounding the given time. If time is exactly one keyframe then the value of the keyframe is returned. * If this parameter is not animated for the given dimension, then this function returns the same as getValue(int) **/ - double getValueAtTime(int time,int dimension = 0) const; + double getValueAtTime(double time,int dimension = 0) const; /** * @brief Set a new keyframe on the parameter at the given time. If a keyframe already exists, it will modify it. @@ -698,7 +698,7 @@ class ChoiceParam : public AnimatedParam * @brief Convenience function that calls getValue() for all dimensions and store them in a tuple-like struct. **/ int get() const; - int get(int frame) const; + int get(double frame) const; /** * @brief Convenience functions for multi-dimensional setting of values @@ -729,7 +729,7 @@ class ChoiceParam : public AnimatedParam * 2 keyframes surrounding the given time. If time is exactly one keyframe then the value of the keyframe is returned. * If this parameter is not animated for the given dimension, then this function returns the same as getValue(int) **/ - int getValueAtTime(int time) const; + int getValueAtTime(double time) const; /** * @brief Set a new keyframe on the parameter at the given time. If a keyframe already exists, it will modify it. @@ -805,7 +805,7 @@ class BooleanParam : public AnimatedParam * @brief Convenience function that calls getValue() for all dimensions and store them in a tuple-like struct. **/ bool get() const; - bool get(int frame) const; + bool get(double frame) const; /** * @brief Convenience functions for multi-dimensional setting of values @@ -830,7 +830,7 @@ class BooleanParam : public AnimatedParam * 2 keyframes surrounding the given time. If time is exactly one keyframe then the value of the keyframe is returned. * If this parameter is not animated for the given dimension, then this function returns the same as getValue(int) **/ - bool getValueAtTime(int time) const; + bool getValueAtTime(double time) const; /** * @brief Set a new keyframe on the parameter at the given time. If a keyframe already exists, it will modify it. @@ -878,7 +878,7 @@ class StringParamBase : public AnimatedParam * @brief Convenience function that calls getValue() for all dimensions and store them in a tuple-like struct. **/ std::string get() const; - std::string get(int frame) const; + std::string get(double frame) const; /** * @brief Convenience functions for multi-dimensional setting of values @@ -903,7 +903,7 @@ class StringParamBase : public AnimatedParam * 2 keyframes surrounding the given time. If time is exactly one keyframe then the value of the keyframe is returned. * If this parameter is not animated for the given dimension, then this function returns the same as getValue(int) **/ - std::string getValueAtTime(int time) const; + std::string getValueAtTime(double time) const; /** * @brief Set a new keyframe on the parameter at the given time. If a keyframe already exists, it will modify it. @@ -1040,6 +1040,8 @@ class ButtonParam : public Param * This can only be called right away after the parameter has been created. **/ void setIconFilePath(const std::string& icon); + + void trigger(); }; class GroupParam : public Param diff --git a/Engine/Plugin.cpp b/Engine/Plugin.cpp index 7689933c8c..aa19f2a1a3 100644 --- a/Engine/Plugin.cpp +++ b/Engine/Plugin.cpp @@ -26,7 +26,9 @@ #include +#include "Engine/AppManager.h" #include "Engine/LibraryBinary.h" +#include "Engine/Settings.h" using namespace Natron; @@ -216,6 +218,16 @@ Plugin::getOfxDesc(ContextEnum* ctx) const return _ofxDescriptor; } +bool +Plugin::getIsUserCreatable() const +{ + if (!_activatedSet) { + _activated = !appPTR->getCurrentSettings()->isPluginDeactivated(this); + _activatedSet = true; + } + return !_isInternalOnly && _activated; +} + void Plugin::setOfxDesc(OFX::Host::ImageEffect::Descriptor* desc,ContextEnum ctx) { @@ -245,3 +257,4 @@ PluginGroupNode::tryRemoveChild(PluginGroupNode* plugin) } } } + diff --git a/Engine/Plugin.h b/Engine/Plugin.h index 473282d1f9..7adec33777 100644 --- a/Engine/Plugin.h +++ b/Engine/Plugin.h @@ -181,12 +181,15 @@ class Plugin OFX::Host::ImageEffect::Descriptor* _ofxDescriptor; ContextEnum _ofxContext; - //Can be activated/deactivated by the user - bool _isUserCreatable; + //Deprecated are by default Disabled in the Preferences. + bool _isDeprecated; //Is not visible by the user, just for internal use bool _isInternalOnly; + mutable bool _activatedSet; + mutable bool _activated; + public: Plugin() @@ -207,8 +210,10 @@ class Plugin , _ofxPlugin(0) , _ofxDescriptor(0) , _ofxContext(eContextNone) - , _isUserCreatable(true) + , _isDeprecated(false) , _isInternalOnly(false) + , _activatedSet(false) + , _activated(true) { } @@ -223,7 +228,7 @@ class Plugin int minorVersion, bool isReader, bool isWriter, - bool isUserCreatable) + bool isDeprecated) : _binary(binary) , _id(id) , _label(label) @@ -240,8 +245,10 @@ class Plugin , _ofxPlugin(0) , _ofxDescriptor(0) , _ofxContext(eContextNone) - , _isUserCreatable(isUserCreatable) + , _isDeprecated(isDeprecated) , _isInternalOnly(false) + , _activatedSet(false) + , _activated(true) { } @@ -251,9 +258,9 @@ class Plugin void setForInternalUseOnly(bool b) { _isInternalOnly = b; } - bool getIsUserCreatable() const { return _isUserCreatable && !_isInternalOnly ; } + bool getIsDeprecated() const { return _isDeprecated; } - void setIsUserCreatable(bool f) { _isUserCreatable = f; } + bool getIsUserCreatable() const; void setPluginID(const QString & id); diff --git a/Engine/Project.cpp b/Engine/Project.cpp index b1700653d9..6049e32af8 100644 --- a/Engine/Project.cpp +++ b/Engine/Project.cpp @@ -56,6 +56,7 @@ #ifdef __NATRON_WIN32__ #include // for wideStringToString #endif +#include // OFX::XML::escape #include "Engine/AppInstance.h" #include "Engine/AppManager.h" @@ -777,7 +778,6 @@ Project::initializeKnobs() _imp->mainView->disableSlider(); _imp->mainView->setDefaultValue(0); _imp->mainView->setMinimum(0); - _imp->mainView->setMaximum(0); _imp->mainView->setAnimationEnabled(false); page->addKnob(_imp->mainView); @@ -1234,7 +1234,7 @@ Project::onKnobValueChanged(KnobI* knob, } ///Format change, hence probably the PAR so run getClipPreferences again - forceGetClipPreferencesOnAllTrees(); + forceComputeInputDependentDataOnAllTrees(); } Q_EMIT formatChanged(frmt); } @@ -1243,7 +1243,7 @@ Project::onKnobValueChanged(KnobI* knob, } else if ( knob == _imp->previewMode.get() ) { Q_EMIT autoPreviewChanged( _imp->previewMode->getValue() ); } else if ( knob == _imp->frameRate.get() ) { - forceGetClipPreferencesOnAllTrees(); + forceComputeInputDependentDataOnAllTrees(); } else if (knob == _imp->frameRange.get()) { int first = _imp->frameRange->getValue(0); int last = _imp->frameRange->getValue(1); @@ -1456,23 +1456,24 @@ Project::reset(bool aboutToQuit) } } } - clearNodes(true); - - { - QMutexLocker l(&_imp->projectLock); - _imp->autoSetProjectFormat = appPTR->getCurrentSettings()->isAutoProjectFormatEnabled(); - _imp->hasProjectBeenSavedByUser = false; - _imp->projectCreationTime = QDateTime::currentDateTime(); - _imp->setProjectFilename(NATRON_PROJECT_UNTITLED); - _imp->setProjectPath(""); - _imp->autoSaveTimer->stop(); - _imp->additionalFormats.clear(); - } - _imp->timeline->removeAllKeyframesIndicators(); - - Q_EMIT projectNameChanged(NATRON_PROJECT_UNTITLED); + clearNodes(!aboutToQuit); if (!aboutToQuit) { + + { + QMutexLocker l(&_imp->projectLock); + _imp->autoSetProjectFormat = appPTR->getCurrentSettings()->isAutoProjectFormatEnabled(); + _imp->hasProjectBeenSavedByUser = false; + _imp->projectCreationTime = QDateTime::currentDateTime(); + _imp->setProjectFilename(NATRON_PROJECT_UNTITLED); + _imp->setProjectPath(""); + _imp->autoSaveTimer->stop(); + _imp->additionalFormats.clear(); + } + _imp->timeline->removeAllKeyframesIndicators(); + + Q_EMIT projectNameChanged(NATRON_PROJECT_UNTITLED); + const std::vector > & knobs = getKnobs(); beginChanges(); @@ -1615,9 +1616,6 @@ Project::escapeXML(const std::string &istr) i += 5; break; default: { - // Escape even the whitespace characters '\n' '\r' '\t', although they are valid - // XML, because they would be converted to space when re-read. - // See http://www.w3.org/TR/xml/#AVNormalize unsigned char c = (unsigned char)(str[i]); // Escape even the whitespace characters '\n' '\r' '\t', although they are valid // XML, because they would be converted to space when re-read. @@ -1639,6 +1637,7 @@ Project::escapeXML(const std::string &istr) } break; } } + assert(str == OFX::XML::escape(istr)); // check that this escaped string is consistent with the one in HostSupport return str; } diff --git a/Engine/ProjectPrivate.cpp b/Engine/ProjectPrivate.cpp index 05691cf0d8..46a6a9cf3e 100644 --- a/Engine/ProjectPrivate.cpp +++ b/Engine/ProjectPrivate.cpp @@ -198,7 +198,7 @@ ProjectPrivate::restoreFromSerialization(const ProjectSerialization & obj, - _publicInterface->forceGetClipPreferencesOnAllTrees(); + _publicInterface->forceComputeInputDependentDataOnAllTrees(); QDateTime time = QDateTime::currentDateTime(); autoSetProjectFormat = false; diff --git a/Engine/RotoContext.cpp b/Engine/RotoContext.cpp index 1824558d0c..f2811483b2 100644 --- a/Engine/RotoContext.cpp +++ b/Engine/RotoContext.cpp @@ -123,6 +123,21 @@ RotoContext::getStrokeBeingPainted() const return _imp->strokeBeingPainted; } +boost::shared_ptr +RotoContext::getRotoPaintBottomMergeNode() const +{ + std::list > items = getCurvesByRenderOrder(); + if (items.empty()) { + return boost::shared_ptr(); + } + + const boost::shared_ptr& firstStrokeItem = items.back(); + assert(firstStrokeItem); + boost::shared_ptr bottomMerge = firstStrokeItem->getMergeNode(); + assert(bottomMerge); + return bottomMerge; +} + void RotoContext::getRotoPaintTreeNodes(std::list >* nodes) const { @@ -2353,7 +2368,7 @@ RotoContext::renderMaskFromStroke(const boost::shared_ptr& str hash.append(stroke->getMergeNode()->getLiveInstance()->getRenderHash()); hash.computeHash(); - Natron::ImageKey key = Natron::Image::makeKey(stroke.get(),hash.value(), true ,time, view, false); + Natron::ImageKey key = Natron::Image::makeKey(stroke.get(),hash.value(), true ,time, view, false, false); { QMutexLocker k(&_imp->cacheAccessMutex); diff --git a/Engine/RotoContext.h b/Engine/RotoContext.h index cd5d1b1497..0367e51973 100644 --- a/Engine/RotoContext.h +++ b/Engine/RotoContext.h @@ -376,6 +376,8 @@ class RotoContext void getRotoPaintTreeNodes(std::list >* nodes) const; + boost::shared_ptr getRotoPaintBottomMergeNode() const; + void setStrokeBeingPainted(const boost::shared_ptr& stroke); boost::shared_ptr getStrokeBeingPainted() const; diff --git a/Engine/RotoDrawableItem.cpp b/Engine/RotoDrawableItem.cpp index 5687b875e6..0ad9c9bf3d 100644 --- a/Engine/RotoDrawableItem.cpp +++ b/Engine/RotoDrawableItem.cpp @@ -902,7 +902,7 @@ RotoDrawableItem::resetCloneTransformCenter() if (!center) { return; } - resetCenter->evaluateValueChange(0, Natron::eValueChangedReasonUserEdited); + resetCenter->evaluateValueChange(0, resetCenter->getCurrentTime(), Natron::eValueChangedReasonUserEdited); double x = center->getValue(0); double y = center->getValue(1); _imp->cloneCenter->setValues(x, y, Natron::eValueChangedReasonNatronGuiEdited); diff --git a/Engine/RotoPaint.cpp b/Engine/RotoPaint.cpp index 644961a090..4a6799243c 100644 --- a/Engine/RotoPaint.cpp +++ b/Engine/RotoPaint.cpp @@ -222,16 +222,6 @@ RotoPaint::getRegionOfDefinition(U64 hash,double time, const RenderScale & scale FramesNeededMap RotoPaint::getFramesNeeded(double time, int view) { - boost::shared_ptr roto = getNode()->getRotoContext(); - std::list > items = roto->getCurvesByRenderOrder(); - if (items.empty()) { - return FramesNeededMap(); - } - - const boost::shared_ptr& firstStrokeItem = items.back(); - assert(firstStrokeItem); - boost::shared_ptr bottomMerge = firstStrokeItem->getMergeNode(); - assert(bottomMerge); FramesNeededMap ret; std::map > views; @@ -251,17 +241,10 @@ RotoPaint::getRegionsOfInterest(double /*time*/, RoIMap* ret) { boost::shared_ptr roto = getNode()->getRotoContext(); - std::list > items = roto->getCurvesByRenderOrder(); - if (items.empty()) { - return; + boost::shared_ptr bottomMerge = roto->getRotoPaintBottomMergeNode(); + if (bottomMerge) { + ret->insert(std::make_pair(bottomMerge->getLiveInstance(), renderWindow)); } - - const boost::shared_ptr& firstStrokeItem = items.back(); - assert(firstStrokeItem); - boost::shared_ptr bottomMerge = firstStrokeItem->getMergeNode(); - assert(bottomMerge); - - ret->insert(std::make_pair(bottomMerge->getLiveInstance(), renderWindow)); } bool @@ -363,6 +346,7 @@ RotoPaint::render(const RenderActionArgs& args) RectD(), neededComps, bgDepth, + false, this); ImageList rotoPaintImages; RenderRoIRetCode code = bottomMerge->getLiveInstance()->renderRoI(rotoPaintArgs, &rotoPaintImages); diff --git a/Engine/RotoSmear.cpp b/Engine/RotoSmear.cpp index b33e6eb0f0..ae157352dd 100644 --- a/Engine/RotoSmear.cpp +++ b/Engine/RotoSmear.cpp @@ -240,8 +240,8 @@ RotoSmear::render(const RenderActionArgs& args) unsigned int mipmapLevel = Image::getLevelFromScale(args.originalScale.x); std::list > > strokes; - //stroke->evaluateStroke(0, &points); - node->getLastPaintStrokePoints(args.time, &strokes); + int strokeIndex; + node->getLastPaintStrokePoints(args.time, &strokes, &strokeIndex); bool isFirstStrokeTick = false; @@ -295,6 +295,8 @@ RotoSmear::render(const RenderActionArgs& args) //renderPoint is the final point we rendered, recorded for the next call to render when we are bulding up the smear std::pair prev,cur,renderPoint; + bool bgInitialized = false; + for (std::list > >::const_iterator itStroke = strokes.begin(); itStroke!=strokes.end(); ++itStroke) { int firstPoint = (int)std::floor((itStroke->size() * writeOnStart)); int endPoint = (int)std::ceil((itStroke->size() * writeOnEnd)); @@ -312,6 +314,14 @@ RotoSmear::render(const RenderActionArgs& args) bool didPaint = false; double distToNext = 0.; + ImagePtr bgImg; + + if (strokeIndex == 0) { + ///For the first multi-stroke, init background + bgImg = getImage(0, args.time, args.mappedScale, args.view, 0, foundBg->second.front(), Natron::eImageBitDepthFloat, par, false, &bgImgRoI); + } + + for (std::list > >::const_iterator plane = args.outputPlanes.begin(); plane != args.outputPlanes.end(); ++plane) { @@ -319,19 +329,20 @@ RotoSmear::render(const RenderActionArgs& args) int nComps = plane->first.getNumComponents(); - ImagePtr bgImg = getImage(0, args.time, args.mappedScale, args.view, 0, foundBg->second.front(), plane->second->getBitDepth(), par, false, &bgImgRoI); - if (!bgImg) { + if (!bgImg && !bgInitialized && strokeIndex == 0) { plane->second->fillZero(args.roi); + bgInitialized = true; continue; } //First copy the source image if this is the first stroke tick - if (isFirstStrokeTick || !duringPainting) { + if ((isFirstStrokeTick || !duringPainting) && !bgInitialized && strokeIndex == 0) { //Make sure all areas are black and transparant plane->second->fillZero(args.roi); plane->second->pasteFrom(*bgImg,args.roi, false); + bgInitialized = true; } if (brushSpacing == 0 || (writeOnEnd - writeOnStart) <= 0. || visiblePortion.empty() || itStroke->size() <= 1) { @@ -365,6 +376,7 @@ RotoSmear::render(const RenderActionArgs& args) cur = _imp->lastCur; } + isFirstStrokeTick = false; while (it!=visiblePortion.end()) { diff --git a/Engine/RotoStrokeItem.cpp b/Engine/RotoStrokeItem.cpp index 4eafb673c5..3c18305a81 100644 --- a/Engine/RotoStrokeItem.cpp +++ b/Engine/RotoStrokeItem.cpp @@ -266,10 +266,11 @@ evaluateStrokeInternal(const KeyFrameSet& xCurve, } // for (; xNext != xCurve.end() ;++xNext, ++yNext, ++pNext) { if (bbox) { - bbox->x1 -= halfBrushSize * pressure; - bbox->x2 += halfBrushSize * pressure; - bbox->y1 -= halfBrushSize * pressure; - bbox->y2 += halfBrushSize * pressure; + double padding = std::max(0.5,halfBrushSize) * pressure; + bbox->x1 -= padding; + bbox->x2 += padding; + bbox->y1 -= padding; + bbox->y2 += padding; } } @@ -608,7 +609,8 @@ RotoStrokeItem::getMostRecentStrokeChangesSinceAge(int time,int lastAge, std::list >* points, RectD* pointsBbox, RectD* wholeStrokeBbox, - int* newAge) + int* newAge, + int* strokeIndex) { Transform::Matrix3x3 transform; @@ -625,6 +627,7 @@ RotoStrokeItem::getMostRecentStrokeChangesSinceAge(int time,int lastAge, QMutexLocker k(&itemMutex); assert(!_imp->strokes.empty()); RotoStrokeItemPrivate::StrokeCurves& stroke = _imp->strokes.back(); + *strokeIndex = (int)_imp->strokes.size() - 1; assert(stroke.xCurve->getKeyFramesCount() == stroke.yCurve->getKeyFramesCount() && stroke.xCurve->getKeyFramesCount() == stroke.pressureCurve->getKeyFramesCount()); diff --git a/Engine/RotoStrokeItem.h b/Engine/RotoStrokeItem.h index 0cd05fd7d7..d614a50345 100644 --- a/Engine/RotoStrokeItem.h +++ b/Engine/RotoStrokeItem.h @@ -152,7 +152,8 @@ class RotoStrokeItem : public RotoDrawableItem bool getMostRecentStrokeChangesSinceAge(int time,int lastAge, std::list >* points, RectD* pointsBbox, RectD* wholeStrokeBbox, - int* newAge); + int* newAge, + int* strokeIndex); RectD getWholeStrokeRoDWhilePainting() const; diff --git a/Engine/Settings.cpp b/Engine/Settings.cpp index bea81cdf21..78986b3b60 100644 --- a/Engine/Settings.cpp +++ b/Engine/Settings.cpp @@ -641,6 +641,12 @@ Settings::initializeKnobsViewers() _checkerboardColor2->setHintToolTip("The second color used by the checkerboard."); _viewersTab->addKnob(_checkerboardColor2); + _autoWipe = Natron::createKnob(this, "Automatically enable wipe"); + _autoWipe->setName("autoWipeForViewer"); + _autoWipe->setHintToolTip("When checked, the wipe tool of the viewer will be automatically enabled " + "when the mouse is hovering the viewer and changing an input of a viewer." ); + _autoWipe->setAnimationEnabled(false); + _viewersTab->addKnob(_autoWipe); _autoProxyWhenScrubbingTimeline = Natron::createKnob(this, "Automatically enable proxy when scrubbing the timeline"); @@ -1169,7 +1175,7 @@ Settings::setDefaultValues() _hostName->setDefaultValue(NATRON_ORGANIZATION_DOMAIN_TOPLEVEL "." NATRON_ORGANIZATION_DOMAIN_SUB "." NATRON_APPLICATION_NAME); _natronSettingsExist->setDefaultValue(false); _systemFontChoice->setDefaultValue(0); - _fontSize->setDefaultValue(NATRON_FONT_SIZE_10); + _fontSize->setDefaultValue(NATRON_FONT_SIZE_DEFAULT); _checkForUpdates->setDefaultValue(false); _notifyOnFileChange->setDefaultValue(true); _autoSaveDelay->setDefaultValue(5, 0); @@ -1206,6 +1212,7 @@ Settings::setDefaultValues() _checkerboardColor2->setDefaultValue(0.,1); _checkerboardColor2->setDefaultValue(0.,2); _checkerboardColor2->setDefaultValue(0.,3); + _autoWipe->setDefaultValue(true); _autoProxyWhenScrubbingTimeline->setDefaultValue(true); _autoProxyLevel->setDefaultValue(1); _enableProgressReport->setDefaultValue(false); @@ -2101,36 +2108,15 @@ static int filterDefaultRenderScaleSupportPlugin(const QString& ofxPluginID) return 0; } -struct PerPluginKnobs -{ - boost::shared_ptr enabled; - boost::shared_ptr renderScaleSupport; - - PerPluginKnobs(const boost::shared_ptr& enabled, - const boost::shared_ptr& renderScaleSupport) - : enabled(enabled) - , renderScaleSupport(renderScaleSupport) - { - - } - - PerPluginKnobs() - : enabled() , renderScaleSupport() - { - - } -}; void -Settings::populatePluginsTab(std::vector& pluginsToIgnore) +Settings::populatePluginsTab() { const PluginsMap& plugins = appPTR->getPluginsList(); std::vector > knobsToRestore; - std::map pluginsMap; - std::map< std::string,std::string > groupNames; ///First pass to exctract all groups for (PluginsMap::const_iterator it = plugins.begin(); it != plugins.end(); ++it) { @@ -2169,87 +2155,114 @@ Settings::populatePluginsTab(std::vector& pluginsToIgnore) } assert(it->second.size() > 0); - Natron::Plugin* plugin = *it->second.rbegin(); - assert(plugin); - - if (plugin->getIsForInternalUseOnly()) { - continue; - } - - boost::shared_ptr group; - const QStringList& grouping = plugin->getGrouping(); - if (grouping.size() > 0) { + for (PluginMajorsOrdered::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + Natron::Plugin* plugin = *it2; + assert(plugin); - std::string mainGroup = Natron::makeNameScriptFriendly(grouping[0].toStdString()); + if (plugin->getIsForInternalUseOnly()) { + continue; + } - ///Find the corresponding group - for (std::list< boost::shared_ptr >::const_iterator it2 = groups.begin(); it2 != groups.end(); ++it2) { - if ((*it2)->getName() == mainGroup) { - group = *it2; - break; + boost::shared_ptr group; + const QStringList& grouping = plugin->getGrouping(); + if (grouping.size() > 0) { + + std::string mainGroup = Natron::makeNameScriptFriendly(grouping[0].toStdString()); + + ///Find the corresponding group + for (std::list< boost::shared_ptr >::const_iterator it3 = groups.begin(); it3 != groups.end(); ++it3) { + if ((*it3)->getName() == mainGroup) { + group = *it3; + break; + } } } + + ///Create checkbox to activate/deactivate the plug-in + std::string pluginName = plugin->getPluginID().toStdString(); + + boost::shared_ptr pluginLabel = Natron::createKnob(this, pluginName); + pluginLabel->setAsLabel(); + pluginLabel->setName(it->first); + pluginLabel->setAnimationEnabled(false); + pluginLabel->setDefaultValue(pluginName); + pluginLabel->setAddNewLine(false); + pluginLabel->hideDescription(); + pluginLabel->setIsPersistant(false); + if (group) { + group->addKnob(pluginLabel); + } + + + boost::shared_ptr pluginActivation = Natron::createKnob(this, "Enabled"); + pluginActivation->setDefaultValue(filterDefaultActivatedPlugin(plugin->getPluginID()) && !plugin->getIsDeprecated()); + pluginActivation->setName(it->first + ".enabled"); + pluginActivation->setAnimationEnabled(false); + pluginActivation->setAddNewLine(false); + pluginActivation->setHintToolTip("When checked, " + pluginName + " will be activated and you can create a node using this plug-in in " NATRON_APPLICATION_NAME ". When unchecked, you'll be unable to create a node for this plug-in. Changing this parameter requires a restart of the application."); + if (group) { + group->addKnob(pluginActivation); + } + + knobsToRestore.push_back(pluginActivation); + + boost::shared_ptr zoomSupport = Natron::createKnob(this, "Zoom support"); + zoomSupport->populateChoices(zoomSupportEntries); + zoomSupport->setName(it->first + ".zoomSupport"); + zoomSupport->setDefaultValue(filterDefaultRenderScaleSupportPlugin(plugin->getPluginID())); + zoomSupport->setHintToolTip("Controls whether the plug-in should have its default zoom support or it should be deactivated. " + "This parameter is useful because some plug-ins flag that they can support different level of zoom " + "scale for rendering but in reality they don't. This enables you to explicitly turn-off that flag for a particular " + "plug-in, hence making it work at different zoom levels." + "Changes to this parameter will not be applied to existing instances of the plug-in (nodes) unless you " + "restart the application."); + zoomSupport->setAnimationEnabled(false); + if (group) { + group->addKnob(zoomSupport); + } + + knobsToRestore.push_back(zoomSupport); + + + _pluginsMap.insert(std::make_pair(plugin, PerPluginKnobs(pluginActivation,zoomSupport))); + } - ///Create checkbox to activate/deactivate the plug-in - std::string pluginName = plugin->getPluginID().toStdString(); - - boost::shared_ptr pluginLabel = Natron::createKnob(this, pluginName); - pluginLabel->setAsLabel(); - pluginLabel->setName(it->first); - pluginLabel->setAnimationEnabled(false); - pluginLabel->setDefaultValue(pluginName); - pluginLabel->setAddNewLine(false); - pluginLabel->hideDescription(); - pluginLabel->setIsPersistant(false); - if (group) { - group->addKnob(pluginLabel); - } - - - boost::shared_ptr pluginActivation = Natron::createKnob(this, "Enabled"); - pluginActivation->setDefaultValue(filterDefaultActivatedPlugin(plugin->getPluginID()) && plugin->getIsUserCreatable()); - pluginActivation->setName(it->first + ".enabled"); - pluginActivation->setAnimationEnabled(false); - pluginActivation->setAddNewLine(false); - pluginActivation->setHintToolTip("When checked, " + pluginName + " will be activated and you can create a node using this plug-in in " NATRON_APPLICATION_NAME ". When unchecked, you'll be unable to create a node for this plug-in. Changing this parameter requires a restart of the application."); - if (group) { - group->addKnob(pluginActivation); - } - - knobsToRestore.push_back(pluginActivation); - - boost::shared_ptr zoomSupport = Natron::createKnob(this, "Zoom support"); - zoomSupport->populateChoices(zoomSupportEntries); - zoomSupport->setName(it->first + ".zoomSupport"); - zoomSupport->setDefaultValue(filterDefaultRenderScaleSupportPlugin(plugin->getPluginID())); - zoomSupport->setHintToolTip("Controls whether the plug-in should have its default zoom support or it should be activated. " - "This parameter is useful because some plug-ins flag that they can support different level of zoom " - "scale for rendering but in reality they don't. This enables you to explicitly turn-off that flag for a particular " - "plug-in, hence making it work at different zoom levels." - "Changes to this parameter will not be applied to existing instances of the plug-in (nodes) unless you " - "restart the application."); - zoomSupport->setAnimationEnabled(false); - if (group) { - group->addKnob(zoomSupport); - } - - knobsToRestore.push_back(zoomSupport); + } + + restoreKnobsFromSettings(knobsToRestore); + +} - - pluginsMap.insert(std::make_pair(plugin, PerPluginKnobs(pluginActivation,zoomSupport))); +bool +Settings::isPluginDeactivated(const Natron::Plugin* p) const +{ + if (p->getIsForInternalUseOnly()) { + return false; + } + std::map::const_iterator found = _pluginsMap.find(p); + if (found == _pluginsMap.end()) { + qDebug() << "Settings::isPluginDeactivated: Plugin not found"; + return false; + } + return !found->second.enabled->getValue(); +} +int +Settings::getRenderScaleSupportPreference(const Natron::Plugin* p) const +{ + if (p->getIsForInternalUseOnly()) { + return 0; } - - for (std::map::iterator it = pluginsMap.begin(); it != pluginsMap.end(); ++it) { - if (!it->second.enabled->getValue()) { - pluginsToIgnore.push_back(it->first); - } else { - _perPluginRenderScaleSupport.insert(std::make_pair(it->first->getPluginID().toStdString(), it->second.renderScaleSupport)); - } + std::map::const_iterator found = _pluginsMap.find(p); + if (found == _pluginsMap.end()) { + qDebug() << "Settings::getRenderScaleSupportPreference: Plugin not found"; + return -1; } + return found->second.renderScaleSupport->getValue(); } + void Settings::populateSystemFonts(const QSettings& settings,const std::vector& fonts) { @@ -2746,15 +2759,6 @@ Settings::didSettingsExistOnStartup() const } -int -Settings::getRenderScaleSupportPreference(const std::string& pluginID) const -{ - std::map >::const_iterator found = _perPluginRenderScaleSupport.find(pluginID); - if (found != _perPluginRenderScaleSupport.end()) { - return found->second->getValue(); - } - return -1; -} bool Settings::notifyOnFileChange() const @@ -3113,3 +3117,9 @@ Settings::getUserStyleSheetFilePath() const { return _qssFile->getValue(); } + +bool +Settings::isAutoWipeEnabled() const +{ + return _autoWipe->getValue(); +} diff --git a/Engine/Settings.h b/Engine/Settings.h index d3cde68777..101c5b7844 100644 --- a/Engine/Settings.h +++ b/Engine/Settings.h @@ -120,7 +120,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void populateWriterPluginsAndFormats(const std::map > > & rows); - void populatePluginsTab(std::vector& pluginsToIgnore); + void populatePluginsTab(); void populateSystemFonts(const QSettings& settings,const std::vector& fonts); @@ -209,6 +209,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool isAutoFixRelativeFilePathEnabled() const; + bool isAutoWipeEnabled() const; + int getCheckerboardTileSize() const; void getCheckerboardColor1(double* r,double* g,double* b,double* a) const; void getCheckerboardColor2(double* r,double* g,double* b,double* a) const; @@ -236,7 +238,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON * for the given plug-in. * If the plug-in ID is not valid, -1 is returned. **/ - int getRenderScaleSupportPreference(const std::string& pluginID) const; + int getRenderScaleSupportPreference(const Natron::Plugin* p) const; bool notifyOnFileChange() const; @@ -307,6 +309,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON std::string getUserStyleSheetFilePath() const; + bool isPluginDeactivated(const Natron::Plugin* p) const; + Q_SIGNALS: void settingChanged(KnobI* knob); @@ -393,6 +397,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON boost::shared_ptr _checkerboardTileSize; boost::shared_ptr _checkerboardColor1; boost::shared_ptr _checkerboardColor2; + boost::shared_ptr _autoWipe; boost::shared_ptr _autoProxyWhenScrubbingTimeline; boost::shared_ptr _autoProxyLevel; boost::shared_ptr _enableProgressReport; @@ -479,7 +484,27 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON boost::shared_ptr _dopeSheetEditorScaleColor; boost::shared_ptr _dopeSheetEditorGridColor; - std::map > _perPluginRenderScaleSupport; + struct PerPluginKnobs + { + boost::shared_ptr enabled; + boost::shared_ptr renderScaleSupport; + + PerPluginKnobs(const boost::shared_ptr& enabled, + const boost::shared_ptr& renderScaleSupport) + : enabled(enabled) + , renderScaleSupport(renderScaleSupport) + { + + } + + PerPluginKnobs() + : enabled() , renderScaleSupport() + { + + } + }; + + std::map _pluginsMap; bool _restoringSettings; bool _ocioRestored; bool _settingsExisted; diff --git a/Engine/Timer.cpp b/Engine/Timer.cpp index e8d3bc9726..6df5e4046d 100644 --- a/Engine/Timer.cpp +++ b/Engine/Timer.cpp @@ -156,6 +156,9 @@ Timer::waitUntilNextFrameIsDue () double timeSinceLastFrame = now.tv_sec - _lastFrameTime.tv_sec + (now.tv_usec - _lastFrameTime.tv_usec) * 1e-6f; + if (timeSinceLastFrame < 0) { + timeSinceLastFrame = 0; + } double timeToSleep = spf - timeSinceLastFrame - _timingError; #ifdef _WIN32 diff --git a/Engine/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index 7e0b5718cd..7fb9cbc1b6 100644 --- a/Engine/ViewerInstance.cpp +++ b/Engine/ViewerInstance.cpp @@ -526,11 +526,12 @@ ViewerInstance::getViewerArgsAndRenderViewer(SequenceTime time, int lastAge,newAge; NodePtr mergeNode = activeStroke->getMergeNode(); lastAge = mergeNode->getStrokeImageAge(); - if (activeStroke->getMostRecentStrokeChangesSinceAge(time, lastAge, &lastStrokePoints, &lastStrokeBbox, &wholeStrokeRod ,&newAge)) { + int strokeIndex; + if (activeStroke->getMostRecentStrokeChangesSinceAge(time, lastAge, &lastStrokePoints, &lastStrokeBbox, &wholeStrokeRod ,&newAge,&strokeIndex)) { for (NodeList::iterator it = rotoPaintNodes.begin(); it!=rotoPaintNodes.end(); ++it) { if ((*it)->getAttachedRotoItem() == activeStroke) { - (*it)->updateLastPaintStrokeData(newAge, lastStrokePoints, lastStrokeBbox); + (*it)->updateLastPaintStrokeData(newAge, lastStrokePoints, lastStrokeBbox,strokeIndex); } } updateLastStrokeDataRecursively(thisNode.get(), rotoPaintNode, lastStrokeBbox, false); @@ -604,7 +605,7 @@ ViewerInstance::renderViewer(int view, } - if ( (ret[0] == eStatusFailed) && (ret[1] == eStatusFailed) ) { + if ( (ret[0] == eStatusFailed) || (ret[1] == eStatusFailed) ) { return eStatusFailed; } @@ -797,8 +798,17 @@ ViewerInstance::getRenderViewerArgsAndCheckCache(SequenceTime time, //The original mipMapLevel without draft applied unsigned originalMipMapLevel = mipMapLevel; - if (zoomFactor < 1. && outArgs->draftModeEnabled && appPTR->getCurrentSettings()->isAutoProxyEnabled()) { + if (outArgs->draftModeEnabled && appPTR->getCurrentSettings()->isAutoProxyEnabled()) { unsigned int autoProxyLevel = appPTR->getCurrentSettings()->getAutoProxyMipMapLevel(); + if (zoomFactor > 1) { + //Decrease draft mode at each inverse mipmaplevel level taken + unsigned int invLevel = Image::getLevelFromScale(1. / zoomFactor); + if (invLevel < autoProxyLevel) { + autoProxyLevel -= invLevel; + } else { + autoProxyLevel = 0; + } + } mipMapLevel = std::max(mipMapLevel, (int)autoProxyLevel); } @@ -1124,12 +1134,7 @@ ViewerInstance::renderViewer_internal(int view, ///Check that we were not aborted already if ( !isSequentialRender && (inArgs.activeInputToRender->getHash() != inArgs.activeInputHash || inArgs.params->time != getTimeline()->currentFrame()) ) { -// if (!isSequentialRender) { -// _imp->checkAndUpdateDisplayAge(inArgs.params->textureIndex,inArgs.params->renderAge); -// } - if (!isSequentialRender) { - _imp->removeOngoingRender(inArgs.params->textureIndex, inArgs.params->renderAge); - } + _imp->removeOngoingRender(inArgs.params->textureIndex, inArgs.params->renderAge); return eStatusReplyDefault; } @@ -1404,7 +1409,7 @@ ViewerInstance::renderViewer_internal(int view, splitRoi[rectIndex], inArgs.params->rod, requestedComponents, - imageDepth, this),&planes); + imageDepth, false, this),&planes); assert(planes.size() == 0 || planes.size() == 1); if (!planes.empty() && retCode == EffectInstance::eRenderRoIRetCodeOk) { image = planes.front(); @@ -2491,17 +2496,23 @@ ViewerInstance::onGammaChanged(double value) { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); + bool changed = false; { QMutexLocker l(&_imp->viewerParamsMutex); - _imp->viewerParamsGamma = value; - _imp->fillGammaLut(1. / value); + if (_imp->viewerParamsGamma != value) { + _imp->viewerParamsGamma = value; + _imp->fillGammaLut(1. / value); + changed = true; + } } assert(_imp->uiContext); - if ( ( (_imp->uiContext->getBitDepth() == Natron::eImageBitDepthByte) || !_imp->uiContext->supportsGLSL() ) - && !getApp()->getProject()->isLoadingProject() ) { - renderCurrentFrame(true); - } else { - _imp->uiContext->redraw(); + if (changed) { + if ( ( (_imp->uiContext->getBitDepth() == Natron::eImageBitDepthByte) || !_imp->uiContext->supportsGLSL() ) + && !getApp()->getProject()->isLoadingProject() ) { + renderCurrentFrame(true); + } else { + _imp->uiContext->redraw(); + } } } @@ -2517,17 +2528,23 @@ ViewerInstance::onGainChanged(double exp) { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); - + + bool changed = false; { QMutexLocker l(&_imp->viewerParamsMutex); - _imp->viewerParamsGain = exp; + if (_imp->viewerParamsGain != exp) { + _imp->viewerParamsGain = exp; + changed = true; + } } - assert(_imp->uiContext); - if ( ( (_imp->uiContext->getBitDepth() == Natron::eImageBitDepthByte) || !_imp->uiContext->supportsGLSL() ) - && !getApp()->getProject()->isLoadingProject() ) { - renderCurrentFrame(true); - } else { - _imp->uiContext->redraw(); + if (changed) { + assert(_imp->uiContext); + if ( ( (_imp->uiContext->getBitDepth() == Natron::eImageBitDepthByte) || !_imp->uiContext->supportsGLSL() ) + && !getApp()->getProject()->isLoadingProject() ) { + renderCurrentFrame(true); + } else { + _imp->uiContext->redraw(); + } } } @@ -2554,12 +2571,15 @@ ViewerInstance::onAutoContrastChanged(bool autoContrast, { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); - + bool changed = false; { QMutexLocker l(&_imp->viewerParamsMutex); - _imp->viewerParamsAutoContrast = autoContrast; + if (_imp->viewerParamsAutoContrast != autoContrast) { + _imp->viewerParamsAutoContrast = autoContrast; + changed = true; + } } - if ( refresh && !getApp()->getProject()->isLoadingProject() ) { + if ( changed && refresh && !getApp()->getProject()->isLoadingProject() ) { renderCurrentFrame(true); } } @@ -2581,7 +2601,9 @@ ViewerInstance::onColorSpaceChanged(Natron::ViewerColorSpaceEnum colorspace) { QMutexLocker l(&_imp->viewerParamsMutex); - + if (_imp->viewerParamsLut == colorspace) { + return; + } _imp->viewerParamsLut = colorspace; } assert(_imp->uiContext); @@ -2598,17 +2620,27 @@ ViewerInstance::setDisplayChannels(DisplayChannelsEnum channels, bool bothInputs { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); - + + bool changed = false; { QMutexLocker l(&_imp->viewerParamsMutex); if (!bothInputs) { - _imp->viewerParamsChannels[0] = channels; + if (_imp->viewerParamsChannels[0] != channels) { + _imp->viewerParamsChannels[0] = channels; + changed = true; + } } else { - _imp->viewerParamsChannels[0] = channels; - _imp->viewerParamsChannels[1] = channels; + if (_imp->viewerParamsChannels[0] != channels) { + _imp->viewerParamsChannels[0] = channels; + changed = true; + } + if (_imp->viewerParamsChannels[1] != channels) { + _imp->viewerParamsChannels[1] = channels; + changed = true; + } } } - if ( !getApp()->getProject()->isLoadingProject() ) { + if ( changed && !getApp()->getProject()->isLoadingProject() ) { renderCurrentFrame(true); } } @@ -2618,11 +2650,15 @@ ViewerInstance::setActiveLayer(const Natron::ImageComponents& layer, bool doRend { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); + bool changed = false; { QMutexLocker l(&_imp->viewerParamsMutex); - _imp->viewerParamsLayer = layer; + if (_imp->viewerParamsLayer != layer) { + _imp->viewerParamsLayer = layer; + changed = true; + } } - if ( doRender && !getApp()->getProject()->isLoadingProject() ) { + if ( doRender && changed && !getApp()->getProject()->isLoadingProject() ) { renderCurrentFrame(true); } } @@ -2632,12 +2668,19 @@ ViewerInstance::setAlphaChannel(const Natron::ImageComponents& layer, const std: { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); + bool changed = false; { QMutexLocker l(&_imp->viewerParamsMutex); - _imp->viewerParamsAlphaLayer = layer; - _imp->viewerParamsAlphaChannelName = channelName; + if (_imp->viewerParamsAlphaLayer != layer) { + _imp->viewerParamsAlphaLayer = layer; + changed = true; + } + if (_imp->viewerParamsAlphaChannelName != channelName) { + _imp->viewerParamsAlphaChannelName = channelName; + changed = true; + } } - if ( doRender && !getApp()->getProject()->isLoadingProject() ) { + if ( changed && doRender && !getApp()->getProject()->isLoadingProject() ) { renderCurrentFrame(true); } } @@ -2675,6 +2718,14 @@ ViewerInstance::redrawViewer() _imp->uiContext->redraw(); } +void +ViewerInstance::redrawViewerNow() +{ + // always running in the main thread + assert( qApp && qApp->thread() == QThread::currentThread() ); + assert(_imp->uiContext); + _imp->uiContext->redrawNow(); +} int ViewerInstance::getLutType() const @@ -2748,17 +2799,17 @@ ViewerInstance::isInputChangeRequestedFromViewer() const } void -ViewerInstance::onInputChanged(int inputNb) +ViewerInstance::refreshActiveInputs(int inputNbChanged) { assert( QThread::currentThread() == qApp->thread() ); - NodePtr inputNode = getNode()->getRealInput(inputNb); + NodePtr inputNode = getNode()->getRealInput(inputNbChanged); { QMutexLocker l(&_imp->activeInputsMutex); if (!inputNode) { ///check if the input was one of the active ones if so set to -1 - if (_imp->activeInputs[0] == inputNb) { + if (_imp->activeInputs[0] == inputNbChanged) { _imp->activeInputs[0] = -1; - } else if (_imp->activeInputs[1] == inputNb) { + } else if (_imp->activeInputs[1] == inputNbChanged) { _imp->activeInputs[1] = -1; } } else { @@ -2768,31 +2819,32 @@ ViewerInstance::onInputChanged(int inputNb) _imp->uiContext->setCompositingOperator(Natron::eViewerCompositingOperatorWipe); op = Natron::eViewerCompositingOperatorWipe; } - _imp->activeInputs[1] = inputNb; + _imp->activeInputs[1] = inputNbChanged; } else { - _imp->activeInputs[0] = inputNb; + _imp->activeInputs[0] = inputNbChanged; } } } Q_EMIT activeInputsChanged(); Q_EMIT refreshOptionalState(); - Q_EMIT clipPreferencesChanged(); } void -ViewerInstance::restoreClipPreferences() +ViewerInstance::onInputChanged(int /*inputNb*/) { + Q_EMIT clipPreferencesChanged(); } -void +bool ViewerInstance::checkOFXClipPreferences(double /*time*/, const RenderScale & /*scale*/, const std::string & /*reason*/, bool /*forceGetClipPrefAction*/) { Q_EMIT clipPreferencesChanged(); + return false; } void diff --git a/Engine/ViewerInstance.h b/Engine/ViewerInstance.h index 1dd06405aa..5a022ff049 100644 --- a/Engine/ViewerInstance.h +++ b/Engine/ViewerInstance.h @@ -213,8 +213,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual void onInputChanged(int inputNb) OVERRIDE FINAL; - virtual void restoreClipPreferences() OVERRIDE FINAL; - + void refreshActiveInputs(int inputNbChanged); + void setInputA(int inputNb); void setInputB(int inputNb); @@ -233,7 +233,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON static const Natron::Color::Lut* lutFromColorspace(Natron::ViewerColorSpaceEnum cs) WARN_UNUSED_RETURN; - virtual void checkOFXClipPreferences(double time, + virtual bool checkOFXClipPreferences(double time, const RenderScale & scale, const std::string & reason, bool forceGetClipPrefAction) OVERRIDE FINAL; @@ -269,6 +269,8 @@ public Q_SLOTS: * @brief Redraws the OpenGL viewer. Can only be called on the main-thread. **/ void redrawViewer(); + + void redrawViewerNow(); void executeDisconnectTextureRequestOnMainThread(int index); diff --git a/Global/Enums.h b/Global/Enums.h index 6cd384c32a..3e4b534d5c 100644 --- a/Global/Enums.h +++ b/Global/Enums.h @@ -37,6 +37,8 @@ enum TimelineStateEnum eTimelineStateIdle, eTimelineStateDraggingCursor, eTimelineStateDraggingBoundary, + eTimelineStatePanning, + eTimelineStateSelectingZoomRange, }; enum TimelineChangeReasonEnum @@ -154,6 +156,8 @@ enum PixmapEnum NATRON_PIXMAP_VIEWER_ROI_DISABLED, NATRON_PIXMAP_VIEWER_RENDER_SCALE, NATRON_PIXMAP_VIEWER_RENDER_SCALE_CHECKED, + NATRON_PIXMAP_VIEWER_AUTOCONTRAST_ENABLED, + NATRON_PIXMAP_VIEWER_AUTOCONTRAST_DISABLED, NATRON_PIXMAP_OPEN_FILE, NATRON_PIXMAP_COLORWHEEL, diff --git a/Global/GitVersion.h b/Global/GitVersion.h index a5508ae0cf..7f303624fc 100644 --- a/Global/GitVersion.h +++ b/Global/GitVersion.h @@ -1,5 +1,5 @@ #ifndef NATRON_GITVERSION_H #define NATRON_GITVERSION_H #define GIT_BRANCH "workshop" -#define GIT_COMMIT "70485d32ca646d0a701542102052a24891563b8a" +#define GIT_COMMIT "4809203b95036b42a45dce3f2796e5c18bc2b206" #endif diff --git a/Gui/ActionShortcuts.h b/Gui/ActionShortcuts.h index 623b742aa3..7949cbe158 100644 --- a/Gui/ActionShortcuts.h +++ b/Gui/ActionShortcuts.h @@ -52,6 +52,7 @@ CLANG_DIAG_ON(uninitialized) #define kShortcutGroupTracking "Tracking" #define kShortcutGroupPlayer "Player" #define kShortcutGroupNodes "Nodes" +#define kShortcutGroupScriptEditor "ScriptEditor" /////////GLOBAL SHORTCUTS #define kShortcutIDActionNewProject "newProject" @@ -300,6 +301,12 @@ CLANG_DIAG_ON(uninitialized) #define kShortcutIDSwitchInputAAndB "switchAB" #define kShortcutDescSwitchInputAAndB "Switch Input A and B" +#define kShortcutIDShowLeftView "leftView" +#define kShortcutDescShowLeftView "Left" + +#define kShortcutIDShowRightView "rightView" +#define kShortcutDescShowRightView "Right" + ///////////PLAYER SHORTCUTS #define kShortcutIDActionPlayerPrevious "prev" @@ -573,6 +580,25 @@ CLANG_DIAG_ON(uninitialized) #define kShortcutIDActionDopeSheetEditorPasteKeyframes "pastekeyframes" #define kShortcutDescActionDopeSheetEditorPasteKeyframes "Paste keyframes" +// Script editor shortcuts +#define kShortcutIDActionScriptEditorPrevScript "prevScript" +#define kShortcutDescActionScriptEditorPrevScript "Previous Script" + +#define kShortcutIDActionScriptEditorNextScript "nextScript" +#define kShortcutDescActionScriptEditorNextScript "Next Script" + +#define kShortcutIDActionScriptEditorClearHistory "clearHistory" +#define kShortcutDescActionScriptEditorClearHistory "Clear History" + +#define kShortcutIDActionScriptExecScript "execScript" +#define kShortcutDescActionScriptExecScript "Execute Script" + +#define kShortcutIDActionScriptClearOutput "clearOutput" +#define kShortcutDescActionScriptClearOutput "Clear Output Window" + +#define kShortcutIDActionScriptShowOutput "showHideOutput" +#define kShortcutDescActionScriptShowOutput "Show/Hide Output Window" + class QWidget; inline diff --git a/Gui/BackDropGui.h b/Gui/BackDropGui.h index 6863c5465d..eb82311ad7 100644 --- a/Gui/BackDropGui.h +++ b/Gui/BackDropGui.h @@ -17,8 +17,8 @@ * ***** END LICENSE BLOCK ***** */ -#ifndef NODEBACKDROP_H -#define NODEBACKDROP_H +#ifndef Gui_BackDropGui_h +#define Gui_BackDropGui_h // ***** BEGIN PYTHON BLOCK ***** // from : @@ -89,4 +89,4 @@ public Q_SLOTS: boost::scoped_ptr _imp; }; -#endif // NODEBACKDROP_H +#endif // Gui_BackDropGui_h diff --git a/Gui/CurveEditor.cpp b/Gui/CurveEditor.cpp index 386dfef872..b3eed471ee 100644 --- a/Gui/CurveEditor.cpp +++ b/Gui/CurveEditor.cpp @@ -51,6 +51,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Engine/EffectInstance.h" #include "Engine/TimeLine.h" +#include "Gui/ActionShortcuts.h" #include "Gui/CurveGui.h" #include "Gui/NodeGui.h" #include "Gui/KnobGui.h" @@ -63,6 +64,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/KnobUndoCommand.h" #include "Gui/Label.h" #include "Gui/NodeSettingsPanel.h" +#include "Gui/TabWidget.h" using std::make_pair; using std::cout; @@ -103,8 +105,6 @@ class CurveEditorTreeWidget : public QTreeWidget struct CurveEditorPrivate { - Gui* gui; - std::list nodes; std::list rotos; QVBoxLayout* mainLayout; @@ -130,9 +130,8 @@ struct CurveEditorPrivate boost::weak_ptr selectedKnobCurve; - CurveEditorPrivate(Gui* gui) - : gui(gui) - , nodes() + CurveEditorPrivate() + : nodes() , rotos() , mainLayout(0) , splitter(0) @@ -163,8 +162,8 @@ CurveEditor::CurveEditor(Gui* gui, QWidget *parent) : QWidget(parent) , CurveSelection() -, ScriptObject() -, _imp(new CurveEditorPrivate(gui)) +, PanelWidget(this,gui) +, _imp(new CurveEditorPrivate()) { setObjectName("CurveEditor"); _imp->undoAction = _imp->undoStack->createUndoAction( this,tr("&Undo") ); @@ -897,8 +896,8 @@ CurveEditor::onItemDoubleClicked(QTreeWidgetItem* item,int) if ( !node->wasBeginEditCalled() ) { node->beginEditKnobs(); } - _imp->gui->putSettingsPanelFirst( node->getSettingPanel() ); - _imp->gui->getApp()->redrawAllViewers(); + getGui()->putSettingsPanelFirst( node->getSettingPanel() ); + getGui()->getApp()->redrawAllViewers(); } } @@ -1535,11 +1534,42 @@ RotoCurveEditorContext::findElement(KnobGui* knob,int dimension) const void CurveEditor::keyPressEvent(QKeyEvent* e) { - if (e->key() == Qt::Key_F && modCASIsControl(e)) { + Qt::KeyboardModifiers modifiers = e->modifiers(); + Qt::Key key = (Qt::Key)e->key(); + + bool accept = true; + if (isKeybind(kShortcutGroupViewer, kShortcutIDActionFitViewer, modifiers, key)) { _imp->filterEdit->setFocus(); } else { + accept = false; QWidget::keyPressEvent(e); } + if (accept) { + takeClickFocus(); + e->accept(); + } else { + handleUnCaughtKeyPressEvent(); + } +} + +void +CurveEditor::enterEvent(QEvent* e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} + +void +CurveEditor::leaveEvent(QEvent* e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); +} + +void +CurveEditor::onInputEventCalled() +{ + takeClickFocus(); } boost::shared_ptr @@ -1578,7 +1608,7 @@ CurveEditor::setSelectedCurve(const boost::shared_ptr& curve) std::string expr = knob->getExpression(knobCurve->getDimension()); if (!expr.empty()) { _imp->knobLineEdit->setText(expr.c_str()); - double v = knob->getValueAtWithExpression(_imp->gui->getApp()->getTimeLine()->currentFrame(), knobCurve->getDimension()); + double v = knob->getValueAtWithExpression(getGui()->getApp()->getTimeLine()->currentFrame(), knobCurve->getDimension()); _imp->resultLabel->setText("= " + QString::number(v)); } else { _imp->knobLineEdit->clear(); @@ -1608,7 +1638,7 @@ CurveEditor::refreshCurrentExpression() boost::shared_ptr knob = curve->getInternalKnob(); std::string expr = knob->getExpression(curve->getDimension()); - double v = knob->getValueAtWithExpression(_imp->gui->getApp()->getTimeLine()->currentFrame(), curve->getDimension()); + double v = knob->getValueAtWithExpression(getGui()->getApp()->getTimeLine()->currentFrame(), curve->getDimension()); _imp->knobLineEdit->setText(expr.c_str()); _imp->resultLabel->setText("= " + QString::number(v)); } diff --git a/Gui/CurveEditor.h b/Gui/CurveEditor.h index 7590d31b5d..e83d13d352 100644 --- a/Gui/CurveEditor.h +++ b/Gui/CurveEditor.h @@ -39,8 +39,7 @@ CLANG_DIAG_ON(uninitialized) #include "Global/GlobalDefines.h" #include "Global/Macros.h" -#include "Engine/ScriptObject.h" - +#include "Gui/PanelWidget.h" #include "Gui/CurveSelection.h" #include "Gui/CurveEditorUndoRedo.h" @@ -347,7 +346,7 @@ struct CurveEditorPrivate; class CurveEditor : public QWidget , public CurveSelection - , public ScriptObject + , public PanelWidget { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -395,6 +394,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void setSelectedCurveExpression(const QString& expression); + void onInputEventCalled(); + public Q_SLOTS: void onFilterTextChanged(const QString& filter); @@ -406,6 +407,9 @@ public Q_SLOTS: void onExprLineEditFinished(); private: + + virtual void enterEvent(QEvent* e) OVERRIDE FINAL; + virtual void leaveEvent(QEvent* e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; diff --git a/Gui/CurveEditorUndoRedo.cpp b/Gui/CurveEditorUndoRedo.cpp index 8abb805f9a..cb8787202f 100644 --- a/Gui/CurveEditorUndoRedo.cpp +++ b/Gui/CurveEditorUndoRedo.cpp @@ -393,7 +393,7 @@ moveKey(KeyPtr &k, int newIndex; k->key = curve->setKeyFrameValueAndTime(newX,newY, keyframeIndex, &newIndex); - isParametric->evaluateValueChange(isKnobCurve->getDimension(), Natron::eValueChangedReasonUserEdited); + isParametric->evaluateValueChange(isKnobCurve->getDimension(), isParametric->getCurrentTime() ,Natron::eValueChangedReasonUserEdited); } else { knob->moveValueAtTime(Natron::eCurveChangeReasonCurveEditor, k->key.getTime(), isKnobCurve->getDimension(), dt, dv,&k->key); } @@ -560,9 +560,9 @@ SetKeysInterpolationCommand::setNewInterpolation(bool undo) int keyframeIndex = it->key->curve->getKeyFrameIndex( it->key->key.getTime() ); if (keyframeIndex != -1) { - it->key->curve->setKeyFrameInterpolation(interp, keyframeIndex); + it->key->key = it->key->curve->setKeyFrameInterpolation(interp, keyframeIndex); } - + isParametric->evaluateValueChange(isKnobCurve->getDimension(), it->key->key.getTime(), Natron::eValueChangedReasonUserEdited); } else { knob->setInterpolationAtTime(Natron::eCurveChangeReasonCurveEditor, isKnobCurve->getDimension(), it->key->key.getTime(), interp, &it->key->key); } @@ -752,7 +752,7 @@ MoveTangentCommand::setNewDerivatives(bool undo) int keyframeIndexInCurve = _key->curve->getInternalCurve()->keyFrameIndex( _key->key.getTime() ); _key->key = _key->curve->getInternalCurve()->setKeyFrameInterpolation(interp, keyframeIndexInCurve); _key->key = _key->curve->getInternalCurve()->setKeyFrameDerivatives(left, right,keyframeIndexInCurve); - attachedKnob->evaluateValueChange(isKnobCurve->getDimension(), Natron::eValueChangedReasonUserEdited); + attachedKnob->evaluateValueChange(isKnobCurve->getDimension(), _key->key.getTime(), Natron::eValueChangedReasonUserEdited); } _widget->refreshDisplayedTangents(); @@ -1028,7 +1028,7 @@ TransformKeysCommand::transform(const KeyPtr& k) int newIndex; k->key = curve->setKeyFrameValueAndTime(p.x,p.y, keyframeIndex, &newIndex); - isParametric->evaluateValueChange(isKnobCurve->getDimension(), Natron::eValueChangedReasonUserEdited); + isParametric->evaluateValueChange(isKnobCurve->getDimension(), isParametric->getCurrentTime(), Natron::eValueChangedReasonUserEdited); } else { knob->transformValueAtTime(Natron::eCurveChangeReasonCurveEditor, k->key.getTime(), isKnobCurve->getDimension(), *_matrix,&k->key); } diff --git a/Gui/CurveGui.cpp b/Gui/CurveGui.cpp index d870751940..f2484b72a5 100644 --- a/Gui/CurveGui.cpp +++ b/Gui/CurveGui.cpp @@ -258,31 +258,34 @@ CurveGui::drawCurve(int curveIndex, } if (!keyframes.empty()) { - std::pair isX1AKey; - while ( x1 < (w - 1) ) { - double x,y; - if (!isX1AKey.second) { - x = _curveWidget->toZoomCoordinates(x1,0).x(); - y = evaluate(false,x); - } else { - x = isX1AKey.first.getTime(); - y = isX1AKey.first.getValue(); + try { + std::pair isX1AKey; + while ( x1 < (w - 1) ) { + double x,y; + if (!isX1AKey.second) { + x = _curveWidget->toZoomCoordinates(x1,0).x(); + y = evaluate(false,x); + } else { + x = isX1AKey.first.getTime(); + y = isX1AKey.first.getValue(); + } + vertices.push_back( (float)x ); + vertices.push_back( (float)y ); + isX1AKey = nextPointForSegment(x1,&x2,keyframes); + x1 = x2; } - vertices.push_back( (float)x ); - vertices.push_back( (float)y ); - isX1AKey = nextPointForSegment(x1,&x2,keyframes); - x1 = x2; - } - //also add the last point - { - double x = _curveWidget->toZoomCoordinates(x1,0).x(); - double y = evaluate(false,x); - vertices.push_back( (float)x ); - vertices.push_back( (float)y ); + //also add the last point + { + double x = _curveWidget->toZoomCoordinates(x1,0).x(); + double y = evaluate(false,x); + vertices.push_back( (float)x ); + vertices.push_back( (float)y ); + } + } catch (...) { + } - } - + QPointF btmLeft = _curveWidget->toZoomCoordinates(0,_curveWidget->height() - 1); QPointF topRight = _curveWidget->toZoomCoordinates(_curveWidget->width() - 1, 0); @@ -593,11 +596,10 @@ KnobCurveGui::getKeyFrameIndex(double time) const return getInternalCurve()->keyFrameIndex(time); } -void +KeyFrame KnobCurveGui::setKeyFrameInterpolation(Natron::KeyframeTypeEnum interp,int index) { - assert(_internalCurve); - _internalCurve->setKeyFrameInterpolation(interp, index); + return getInternalCurve()->setKeyFrameInterpolation(interp, index); } BezierCPCurveGui::BezierCPCurveGui(const CurveWidget *curveWidget, @@ -687,9 +689,10 @@ BezierCPCurveGui::getKeyFrameIndex(double time) const return _bezier->getKeyFrameIndex(time); } -void +KeyFrame BezierCPCurveGui::setKeyFrameInterpolation(Natron::KeyframeTypeEnum interp,int index) { _bezier->setKeyFrameInterpolation(interp, index); + return KeyFrame(); } diff --git a/Gui/CurveGui.h b/Gui/CurveGui.h index 84b42f7fae..c1f489307e 100644 --- a/Gui/CurveGui.h +++ b/Gui/CurveGui.h @@ -132,7 +132,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual bool isYComponentMovable() const; virtual KeyFrameSet getKeyFrames() const; virtual int getKeyFrameIndex(double time) const = 0; - virtual void setKeyFrameInterpolation(Natron::KeyframeTypeEnum interp,int index) = 0; + virtual KeyFrame setKeyFrameInterpolation(Natron::KeyframeTypeEnum interp,int index) = 0; Q_SIGNALS: @@ -206,7 +206,7 @@ class KnobCurveGui : public CurveGui } virtual int getKeyFrameIndex(double time) const OVERRIDE FINAL WARN_UNUSED_RETURN; - virtual void setKeyFrameInterpolation(Natron::KeyframeTypeEnum interp,int index) OVERRIDE FINAL; + virtual KeyFrame setKeyFrameInterpolation(Natron::KeyframeTypeEnum interp,int index) OVERRIDE FINAL; private: @@ -244,7 +244,7 @@ class BezierCPCurveGui : public CurveGui virtual KeyFrameSet getKeyFrames() const OVERRIDE FINAL WARN_UNUSED_RETURN; virtual int getKeyFrameIndex(double time) const OVERRIDE FINAL WARN_UNUSED_RETURN; - virtual void setKeyFrameInterpolation(Natron::KeyframeTypeEnum interp,int index) OVERRIDE FINAL; + virtual KeyFrame setKeyFrameInterpolation(Natron::KeyframeTypeEnum interp,int index) OVERRIDE FINAL; private: diff --git a/Gui/CurveWidget.cpp b/Gui/CurveWidget.cpp index 40ff0e18d6..afce3ead43 100644 --- a/Gui/CurveWidget.cpp +++ b/Gui/CurveWidget.cpp @@ -50,8 +50,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/GuiApplicationManager.h" #include "Gui/GuiDefines.h" #include "Gui/GuiMacros.h" -#include "Gui/Histogram.h" -#include "Gui/NodeGraph.h" + #include "Gui/PythonPanels.h" // PyModelDialog #include "Gui/TabWidget.h" #include "Gui/ViewerGL.h" @@ -157,7 +156,7 @@ CurveWidget::addCurveAndSetColor(const boost::shared_ptr& curve) // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); - //updateGL(); //force initializeGL to be called if it wasn't before. + //update(); //force initializeGL to be called if it wasn't before. _imp->_curves.push_back(curve); curve->setColor(_imp->_nextCurveAddedColor); _imp->_nextCurveAddedColor.setHsv( _imp->_nextCurveAddedColor.hsvHue() + 60, @@ -644,7 +643,19 @@ CurveWidget::mousePressEvent(QMouseEvent* e) { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); - + CurveEditor* ce = 0; + if ( parentWidget() ) { + QWidget* parent = parentWidget()->parentWidget(); + if (parent) { + if (parent->objectName() == "CurveEditor") { + ce = dynamic_cast(parent); + } + } + } + if (ce) { + ce->onInputEventCalled(); + } + setFocus(); //// // right button: popup menu @@ -655,7 +666,7 @@ CurveWidget::mousePressEvent(QMouseEvent* e) // no need to set _imp->_lastMousePos // no need to set _imp->_dragStartPoint - // no need to updateGL() + // no need to update() e->accept(); return; } @@ -713,7 +724,7 @@ CurveWidget::mousePressEvent(QMouseEvent* e) _imp->_dragStartPoint = e->pos(); // no need to set _imp->_dragStartPoint - // no need to updateGL() + // no need to update() return; } else if (((e->buttons() & Qt::MiddleButton) && (buttonMetaAlt(e) == Qt::AltModifier || (e->buttons() & Qt::LeftButton))) || @@ -757,7 +768,7 @@ CurveWidget::mousePressEvent(QMouseEvent* e) _imp->_keyDragLastMovement.ry() = 0.; _imp->_dragStartPoint = e->pos(); _imp->_lastMousePos = e->pos(); - //no need to updateGL() + //no need to update() return; } } @@ -825,7 +836,7 @@ CurveWidget::mousePressEvent(QMouseEvent* e) _imp->_state = eEventStateDraggingTimeline; _imp->_lastMousePos = e->pos(); // no need to set _imp->_dragStartPoint - // no need to updateGL() + // no need to update() return; } @@ -925,7 +936,7 @@ CurveWidget::mouseReleaseEvent(QMouseEvent*) _imp->_gui->setDraftRenderEnabled(false); bool autoProxyEnabled = appPTR->getCurrentSettings()->isAutoProxyEnabled(); if (autoProxyEnabled) { - _imp->_gui->renderAllViewers(); + _imp->_gui->renderAllViewers(true); } } } @@ -1283,18 +1294,6 @@ CurveWidget::sizeHint() const return _imp->sizeH; } -static TabWidget* findParentTabRecursive(QWidget* w) -{ - QWidget* parent = w->parentWidget(); - if (!parent) { - return 0; - } - TabWidget* tab = dynamic_cast(parent); - if (tab) { - return tab; - } - return findParentTabRecursive(parent); -} void CurveWidget::keyPressEvent(QKeyEvent* e) @@ -1302,17 +1301,11 @@ CurveWidget::keyPressEvent(QKeyEvent* e) // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); + bool accept = true; Qt::KeyboardModifiers modifiers = e->modifiers(); Qt::Key key = (Qt::Key)e->key(); - if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionShowPaneFullScreen, modifiers, key) ) { - TabWidget* parentTab = findParentTabRecursive(this); - if (parentTab) { - QKeyEvent* ev = new QKeyEvent(QEvent::KeyPress, key, modifiers); - QCoreApplication::postEvent(parentTab,ev); - } - - } else if ( isKeybind(kShortcutGroupCurveEditor, kShortcutIDActionCurveEditorRemoveKeys, modifiers, key) ) { + if ( isKeybind(kShortcutGroupCurveEditor, kShortcutIDActionCurveEditorRemoveKeys, modifiers, key) ) { deleteSelectedKeyFrames(); } else if ( isKeybind(kShortcutGroupCurveEditor, kShortcutIDActionCurveEditorConstant, modifiers, key) ) { constantInterpForSelectedKeyFrames(); @@ -1343,35 +1336,41 @@ CurveWidget::keyPressEvent(QKeyEvent* e) QWheelEvent e(mapFromGlobal(QCursor::pos()), -120, Qt::NoButton, Qt::NoModifier); // one wheel click = +-120 delta wheelEvent(&e); } else { + accept = false; QGLWidget::keyPressEvent(e); } + + CurveEditor* ce = 0; + if ( parentWidget() ) { + QWidget* parent = parentWidget()->parentWidget(); + if (parent) { + if (parent->objectName() == "CurveEditor") { + ce = dynamic_cast(parent); + } + } + } + + if (accept) { + if (ce) { + ce->onInputEventCalled(); + } + + e->accept(); + } else { + if (ce) { + ce->handleUnCaughtKeyPressEvent(); + } + } } // keyPressEvent + void CurveWidget::enterEvent(QEvent* e) { - // always running in the main thread - assert( qApp && qApp->thread() == QThread::currentThread() ); - QWidget* currentFocus = qApp->focusWidget(); - - bool canSetFocus = !currentFocus || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - currentFocus->objectName() == "Properties" || - currentFocus->objectName() == "tree" || - currentFocus->objectName() == "SettingsPanel" || - currentFocus->objectName() == "qt_tabwidget_tabbar"; - - if (canSetFocus) { - setFocus(); - } - + setFocus(); QGLWidget::enterEvent(e); + } - //struct RefreshTangent_functor{ // CurveWidgetPrivate* _imp; // @@ -1549,7 +1548,7 @@ CurveWidget::pasteKeyFramesFromClipBoardToSelectedCurve() return; } - //this function will call updateGL() for us + //this function will call update() for us pushUndoCommand( new AddKeysCommand(this,curve, _imp->_keyFramesClipBoard) ); } @@ -1704,28 +1703,6 @@ CurveWidget::onTimeLineFrameChanged(SequenceTime, update(); } -bool -CurveWidget::isTabVisible() const -{ - if ( parentWidget() ) { - QWidget* parent = parentWidget()->parentWidget(); - if (parent) { - if (parent->objectName() == "CurveEditor") { - TabWidget* tab = dynamic_cast( parentWidget()->parentWidget()->parentWidget() ); - if (tab) { - if ( tab->isFloatingWindowChild() ) { - return true; - } - - return tab->currentWidget() == parent; - } - } - } - } - - return false; -} - void CurveWidget::onTimeLineBoundariesChanged(int,int) { diff --git a/Gui/CurveWidget.h b/Gui/CurveWidget.h index 8068d3a098..e17da07f02 100644 --- a/Gui/CurveWidget.h +++ b/Gui/CurveWidget.h @@ -209,9 +209,9 @@ public Q_SLOTS: virtual void mouseDoubleClickEvent(QMouseEvent* e) OVERRIDE FINAL; virtual void mouseReleaseEvent(QMouseEvent* e) OVERRIDE FINAL; virtual void mouseMoveEvent(QMouseEvent* e) OVERRIDE FINAL; + virtual void enterEvent(QEvent* e) OVERRIDE FINAL; virtual void wheelEvent(QWheelEvent* e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; - virtual void enterEvent(QEvent* e) OVERRIDE FINAL; virtual void focusInEvent(QFocusEvent* e) OVERRIDE FINAL; void renderText(double x,double y,const QString & text,const QColor & color,const QFont & font) const; @@ -234,7 +234,6 @@ public Q_SLOTS: private: - bool isTabVisible() const; boost::scoped_ptr _imp; }; diff --git a/Gui/CurveWidgetPrivate.cpp b/Gui/CurveWidgetPrivate.cpp index 7f82fb9ec2..7b9a8a67ff 100644 --- a/Gui/CurveWidgetPrivate.cpp +++ b/Gui/CurveWidgetPrivate.cpp @@ -389,7 +389,7 @@ CurveWidgetPrivate::drawTimelineMarkers() glVertex2f( _timeline->currentFrame(),btmRight.y() ); glVertex2f( _timeline->currentFrame(),topLeft.y() ); glEnd(); - glCheckError(); + glCheckErrorIgnoreOSXBug(); glEnable(GL_POLYGON_SMOOTH); glHint(GL_POLYGON_SMOOTH_HINT,GL_DONT_CARE); @@ -401,7 +401,7 @@ CurveWidgetPrivate::drawTimelineMarkers() glVertex2f( _timelineBtmPoly.at(1).x(),_timelineBtmPoly.at(1).y() ); glVertex2f( _timelineBtmPoly.at(2).x(),_timelineBtmPoly.at(2).y() ); glEnd(); - glCheckError(); + glCheckErrorIgnoreOSXBug(); glBegin(GL_POLYGON); glVertex2f( _timelineTopPoly.at(0).x(),_timelineTopPoly.at(0).y() ); @@ -409,7 +409,7 @@ CurveWidgetPrivate::drawTimelineMarkers() glVertex2f( _timelineTopPoly.at(2).x(),_timelineTopPoly.at(2).y() ); glEnd(); } // GLProtectAttrib a(GL_HINT_BIT | GL_ENABLE_BIT | GL_LINE_BIT | GL_POLYGON_BIT); - glCheckError(); + glCheckErrorIgnoreOSXBug(); } void diff --git a/Gui/CustomParamInteract.cpp b/Gui/CustomParamInteract.cpp index a4c91fc85f..500db75e51 100644 --- a/Gui/CustomParamInteract.cpp +++ b/Gui/CustomParamInteract.cpp @@ -155,7 +155,7 @@ CustomParamInteract::swapOpenGLBuffers() void CustomParamInteract::redraw() { - updateGL(); + update(); } void @@ -240,7 +240,7 @@ CustomParamInteract::mousePressEvent(QMouseEvent* e) viewportPos.y = e->y(); OfxStatus stat = _imp->entryPoint->penDownAction(time, scale, pos, viewportPos, /*pressure=*/1.); if (stat == kOfxStatOK) { - updateGL(); + update(); } } @@ -259,7 +259,7 @@ CustomParamInteract::mouseMoveEvent(QMouseEvent* e) viewportPos.y = e->y(); OfxStatus stat = _imp->entryPoint->penMotionAction(time, scale, pos, viewportPos, /*pressure=*/1.); if (stat == kOfxStatOK) { - updateGL(); + update(); } } @@ -278,7 +278,7 @@ CustomParamInteract::mouseReleaseEvent(QMouseEvent* e) viewportPos.y = e->y(); OfxStatus stat = _imp->entryPoint->penUpAction(time, scale, pos, viewportPos, /*pressure=*/1.); if (stat == kOfxStatOK) { - updateGL(); + update(); } } @@ -291,7 +291,7 @@ CustomParamInteract::focusInEvent(QFocusEvent* /*e*/) int time = _imp->knob->getKnob()->getHolder()->getApp()->getTimeLine()->currentFrame(); OfxStatus stat = _imp->entryPoint->gainFocusAction(time, scale); if (stat == kOfxStatOK) { - updateGL(); + update(); } } @@ -304,7 +304,7 @@ CustomParamInteract::focusOutEvent(QFocusEvent* /*e*/) int time = _imp->knob->getKnob()->getHolder()->getApp()->getTimeLine()->currentFrame(); OfxStatus stat = _imp->entryPoint->loseFocusAction(time, scale); if (stat == kOfxStatOK) { - updateGL(); + update(); } } @@ -323,7 +323,7 @@ CustomParamInteract::keyPressEvent(QKeyEvent* e) stat = _imp->entryPoint->keyDownAction( time, scale, (int)QtEnumConvert::fromQtKey( (Qt::Key)e->key() ), keyStr.data() ); } if (stat == kOfxStatOK) { - updateGL(); + update(); } } @@ -337,7 +337,7 @@ CustomParamInteract::keyReleaseEvent(QKeyEvent* e) QByteArray keyStr; OfxStatus stat = _imp->entryPoint->keyUpAction( time, scale, (int)QtEnumConvert::fromQtKey( (Qt::Key)e->key() ), keyStr.data() ); if (stat == kOfxStatOK) { - updateGL(); + update(); } } diff --git a/Gui/DefaultOverlays.cpp b/Gui/DefaultOverlays.cpp index 7937598c2d..3572fbc2d3 100644 --- a/Gui/DefaultOverlays.cpp +++ b/Gui/DefaultOverlays.cpp @@ -27,6 +27,8 @@ #include #include +#include + #include "Gui/NodeGui.h" #include "Gui/TextRenderer.h" #include "Gui/GuiApplicationManager.h" @@ -57,7 +59,7 @@ enum PositionInteractState struct PositionInteract { - boost::shared_ptr param; + boost::weak_ptr param; QPointF dragPos; PositionInteractState state; @@ -136,7 +138,7 @@ DefaultOverlay::addPositionParam(const boost::shared_ptr& position) assert(QThread::currentThread() == qApp->thread()); for (PositionInteracts::iterator it = _imp->positions.begin(); it != _imp->positions.end(); ++it) { - if (it->param == position) { + if (it->param.lock() == position) { return false; } } @@ -172,6 +174,15 @@ DefaultOverlay::draw(double time,const RenderScale& /*renderScale*/) QFontMetrics fm(font); for (PositionInteracts::iterator it = _imp->positions.begin(); it != _imp->positions.end(); ++it) { + boost::shared_ptr knob = it->param.lock(); + if (!knob) { + continue; + } + if (knob->getIsSecretRecursive()) { + continue; + } + + float pR = 1.f; float pG = 1.f; float pB = 1.f; @@ -188,8 +199,9 @@ DefaultOverlay::draw(double time,const RenderScale& /*renderScale*/) if (it->state == ePositionInteractStatePicked) { pos = _imp->lastPenPos; } else { - pos.rx() = it->param->getValueAtTime(time, 0); - pos.ry() = it->param->getValueAtTime(time, 1); + pos.rx() = knob->getValueAtTime(time, 0); + pos.ry() = knob->getValueAtTime(time, 1); + } //glPushAttrib(GL_ALL_ATTRIB_BITS); // caller is responsible for protecting attribs glPointSize( (GLfloat)it->pointSize() ); @@ -210,8 +222,9 @@ DefaultOverlay::draw(double time,const RenderScale& /*renderScale*/) glEnd(); QColor c; c.setRgbF(pR * l, pG * l, pB * l); + _imp->textRenderer.renderText(pos.x(), pos.y() - (fm.height() + it->pointSize()) * pscale.y, - pscale.x, pscale.y, QString(it->param->getDescription().c_str()), c, font); + pscale.x, pscale.y, QString(knob->getDescription().c_str()), c, font); } } @@ -232,8 +245,11 @@ DefaultOverlay::penMotion(double time, if (it->state == ePositionInteractStatePicked) { pos = _imp->lastPenPos; } else { - pos.rx() = it->param->getValueAtTime(time, 0); - pos.ry() = it->param->getValueAtTime(time, 1); + boost::shared_ptr param = it->param.lock(); + if (param) { + pos.rx() = param->getValueAtTime(time, 0); + pos.ry() = param->getValueAtTime(time, 1); + } } bool didSomething = false; @@ -268,8 +284,10 @@ DefaultOverlay::penMotion(double time, if (it->state != ePositionInteractStateInactive && _imp->interactiveDrag && valuesChanged) { double x = fround(_imp->lastPenPos.x(), pscale.x); double y = fround(_imp->lastPenPos.y(), pscale.y); - it->param->setValue(x , 0); - it->param->setValue(y , 1); + boost::shared_ptr param = it->param.lock(); + if (param) { + param->setValues(x, y, Natron::eValueChangedReasonNatronGuiEdited); + } } if (didSomething || valuesChanged) { return true; @@ -296,8 +314,10 @@ DefaultOverlay::penUp(double time, if (!_imp->interactiveDrag) { double x = fround(_imp->lastPenPos.x(), pscale.x); double y = fround(_imp->lastPenPos.y(), pscale.y); - it->param->setValue(x , 0); - it->param->setValue(y , 1); + boost::shared_ptr param = it->param.lock(); + if (param) { + param->setValues(x, y, Natron::eValueChangedReasonNatronGuiEdited); + } } it->state = ePositionInteractStateInactive; @@ -393,7 +413,7 @@ DefaultOverlay::hasDefaultOverlayForParam(const KnobI* param) { assert(QThread::currentThread() == qApp->thread()); for (PositionInteracts::iterator it = _imp->positions.begin(); it != _imp->positions.end(); ++it) { - if (it->param.get() == param) { + if (it->param.lock().get() == param) { return true; } } @@ -404,7 +424,7 @@ void DefaultOverlay::removeDefaultOverlay(KnobI* knob) { for (PositionInteracts::iterator it = _imp->positions.begin(); it != _imp->positions.end(); ++it) { - if (it->param.get() == knob) { + if (it->param.lock().get() == knob) { _imp->positions.erase(it); return; } diff --git a/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index 73d494d375..d2c223f69a 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -74,8 +74,6 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/ViewerGL.h" #include "Gui/ViewerTab.h" - -#define NATRON_VERTICAL_BAR_WIDTH 4 using std::make_pair; using namespace Natron; @@ -416,7 +414,8 @@ DockablePanel::DockablePanel(Gui* gui , _imp->_horizContainer = new QWidget(this); _imp->_horizLayout = new QHBoxLayout(_imp->_horizContainer); - _imp->_horizLayout->setContentsMargins(NATRON_VERTICAL_BAR_WIDTH, 3, 3, 0); + _imp->_horizLayout->setContentsMargins(0, 3, 3, 0); + _imp->_horizLayout->setSpacing(2); if (iseffect) { _imp->_verticalColorBar = new VerticalColorBar(_imp->_horizContainer); _imp->_verticalColorBar->setColor(currentColor); @@ -432,11 +431,12 @@ DockablePanel::DockablePanel(Gui* gui , tabWidget->getTabBar()->setObjectName("DockablePanelTabWidget"); _imp->_tabWidget->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Preferred); } + QObject::connect(_imp->_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(onPageIndexChanged(int))); _imp->_horizLayout->addWidget(_imp->_tabWidget); _imp->_mainLayout->addWidget(_imp->_horizContainer); if (createDefaultPage) { - _imp->addPage(NULL,defaultPageName); + _imp->getOrCreatePage(NULL); } } @@ -460,6 +460,24 @@ DockablePanel::~DockablePanel() } } +void +DockablePanel::onPageIndexChanged(int index) +{ + assert(_imp->_tabWidget); + QString name = _imp->_tabWidget->tabText(index); + PageMap::iterator found = _imp->_pages.find(name); + if (found == _imp->_pages.end()) { + return; + } + + _imp->refreshPagesSecretness(); + + Natron::EffectInstance* isEffect = dynamic_cast(_imp->_holder); + if (isEffect && isEffect->getNode()->hasOverlay()) { + isEffect->getApp()->redrawAllViewers(); + } +} + void DockablePanel::turnOffPages() { @@ -469,7 +487,7 @@ DockablePanel::turnOffPages() setFrameShape(QFrame::NoFrame); boost::shared_ptr userPage = _imp->_holder->getOrCreateUserPageKnob(); - _imp->addPage(userPage.get(), userPage->getDescription().c_str()); + _imp->getOrCreatePage(userPage.get()); } @@ -532,6 +550,7 @@ DockablePanel::setUserPageActiveIndex() for (int i = 0; i < _imp->_tabWidget->count(); ++i) { if (_imp->_tabWidget->tabText(i) == NATRON_USER_MANAGED_KNOBS_PAGE_LABEL) { _imp->_tabWidget->setCurrentIndex(i); + _imp->refreshPagesSecretness(); break; } } @@ -646,24 +665,23 @@ DockablePanel::initializeKnobsInternal() if (roto) { -// boost::shared_ptr page = _imp->ensureDefaultPageKnobCreated(); -// assert(page); -// PageMap::iterator foundPage = _imp->_pages.find(page->getDescription().c_str()); -// assert(foundPage != _imp->_pages.end()); -// -// QGridLayout* layout = 0; -// if (_imp->_useScrollAreasForTabs) { -// layout = dynamic_cast( dynamic_cast(foundPage->second.tab)->widget()->layout() ); -// } else { -// layout = dynamic_cast( foundPage->second.tab->layout() ); -// } -// assert(layout); -// layout->addWidget(roto, layout->rowCount(), 0 , 1, 2); _imp->_mainLayout->addWidget(roto); } initializeExtraGui(_imp->_mainLayout); + NodeSettingsPanel* isNodePanel = dynamic_cast(this); + if (isNodePanel) { + boost::shared_ptr collec = isNodePanel->getNode()->getNode()->getGroup(); + NodeGroup* isGroup = dynamic_cast(collec.get()); + if (isGroup) { + if (!isGroup->getNode()->hasPyPlugBeenEdited()) { + setEnabled(false); + } + } + } + _imp->refreshPagesSecretness(); + } void @@ -813,6 +831,10 @@ DockablePanel::setClosedInternal(bool c) NodeSettingsPanel* nodePanel = dynamic_cast(this); if (nodePanel) { + + nodePanel->getNode()->getNode()->getLiveInstance()->refreshAfterTimeChange(getGui()->getApp()->getTimeLine()->currentFrame()); + + boost::shared_ptr nodeGui = nodePanel->getNode(); boost::shared_ptr internalNode = nodeGui->getNode(); boost::shared_ptr panel = getMultiInstancePanel(); @@ -990,6 +1012,7 @@ DockablePanel::deleteKnobGui(const boost::shared_ptr& knob) int index = _imp->_tabWidget->indexOf(found->second.tab); if (index != -1) { _imp->_tabWidget->removeTab(index); + _imp->refreshPagesSecretness(); } } found->second.tab->deleteLater(); @@ -1438,9 +1461,9 @@ DockablePanel::onCenterButtonClicked() } void -DockablePanel::onSubGraphEditionChanged(bool editable) +DockablePanel::onSubGraphEditionChanged(bool /*editable*/) { - _imp->_enterInGroupButton->setVisible(editable); + // _imp->_enterInGroupButton->setVisible(editable); } void diff --git a/Gui/DockablePanel.h b/Gui/DockablePanel.h index 7520e33475..87a30223e3 100644 --- a/Gui/DockablePanel.h +++ b/Gui/DockablePanel.h @@ -177,6 +177,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void setPluginIDAndVersion(const std::string& pluginLabel,const std::string& pluginID,unsigned int version); public Q_SLOTS: + + void onPageIndexChanged(int index); /*Internal slot, not meant to be called externally.*/ void closePanel(); diff --git a/Gui/DockablePanelPrivate.cpp b/Gui/DockablePanelPrivate.cpp index c806f0f010..3ca77a2c86 100644 --- a/Gui/DockablePanelPrivate.cpp +++ b/Gui/DockablePanelPrivate.cpp @@ -36,7 +36,6 @@ #include #include -#include "Engine/Knob.h" // KnobI #include "Engine/KnobTypes.h" #include "Engine/Node.h" // NATRON_PARAMETER_PAGE_NAME_INFO #include "Gui/ClickableLabel.h" @@ -242,7 +241,7 @@ DockablePanelPrivate::ensureDefaultPageKnobCreated() for (U32 i = 0; i < knobs.size(); ++i) { boost::shared_ptr p = boost::dynamic_pointer_cast(knobs[i]); if ( p && (p->getDescription() != NATRON_PARAMETER_PAGE_NAME_INFO) && (p->getDescription() != NATRON_PARAMETER_PAGE_NAME_EXTRA) ) { - addPage(p.get(), p->getDescription().c_str() ); + getOrCreatePage(p.get()); return p; } } @@ -256,7 +255,7 @@ DockablePanelPrivate::ensureDefaultPageKnobCreated() pk = boost::dynamic_pointer_cast(knob); } assert(pk); - addPage(pk.get(), _defaultPageName); + getOrCreatePage(pk.get()); return pk; } @@ -269,7 +268,7 @@ DockablePanelPrivate::getDefaultPage(const boost::shared_ptr &knob) for (U32 i = 0; i < knobs.size(); ++i) { KnobPage* p = dynamic_cast( knobs[i].get() ); if ( p && (p->getDescription() != NATRON_PARAMETER_PAGE_NAME_INFO) && (p->getDescription() != NATRON_PARAMETER_PAGE_NAME_EXTRA) ) { - page = addPage(p, p->getDescription().c_str() ); + page = getOrCreatePage(p); p->addKnob(knob); break; } @@ -288,7 +287,7 @@ DockablePanelPrivate::getDefaultPage(const boost::shared_ptr &knob) ///Last resort: The plug-in didn't specify ANY page, just put it into the default page if ( page == _pages.end() ) { - page = addPage(NULL, _defaultPageName); + page = getOrCreatePage(NULL); } } return page; @@ -323,8 +322,7 @@ DockablePanelPrivate::findKnobGuiOrCreate(const boost::shared_ptr & knob, if (isPage->getChildren().empty()) { return 0; } - QString pageName(isPage->getDescription().c_str()); - addPage(isPage.get(), pageName); + getOrCreatePage(isPage.get()); } else { @@ -356,7 +354,7 @@ DockablePanelPrivate::findKnobGuiOrCreate(const boost::shared_ptr & knob, if (!parentKnob) { page = getDefaultPage(knob); } else { - page = addPage(parentIsPage, parentIsPage->getDescription().c_str() ); + page = getOrCreatePage(parentIsPage); } bool existed = true; if (!page->second.groupAsTab) { @@ -394,7 +392,7 @@ DockablePanelPrivate::findKnobGuiOrCreate(const boost::shared_ptr & knob, assert(parentParentIsGroup || parentParentIsPage); TabGroup* parentTabGroup = 0; if (parentParentIsPage) { - PageMap::iterator page = addPage(parentParentIsPage.get(),parentParentIsPage->getDescription().c_str()); + PageMap::iterator page = getOrCreatePage(parentParentIsPage.get()); assert(page != _pages.end()); parentTabGroup = page->second.groupAsTab; } else { @@ -411,7 +409,7 @@ DockablePanelPrivate::findKnobGuiOrCreate(const boost::shared_ptr & knob, } else { KnobPage* topLevelPage = knob->getTopLevelPage(); - PageMap::iterator page = addPage(topLevelPage,topLevelPage->getDescription().c_str()); + PageMap::iterator page = getOrCreatePage(topLevelPage); assert(page != _pages.end()); ///retrieve the form layout QGridLayout* layout; @@ -443,7 +441,7 @@ DockablePanelPrivate::findKnobGuiOrCreate(const boost::shared_ptr & knob, KnobPage* isTopLevelParentAPage = dynamic_cast(parentKnobTmp); assert(isTopLevelParentAPage); - PageMap::iterator page = addPage(isTopLevelParentAPage, isTopLevelParentAPage->getDescription().c_str()); + PageMap::iterator page = getOrCreatePage(isTopLevelParentAPage); assert( page != _pages.end() ); ///retrieve the form layout @@ -535,7 +533,7 @@ DockablePanelPrivate::findKnobGuiOrCreate(const boost::shared_ptr & knob, layout = groupAsTab->addTab(closestParentGroupTab, closestParentGroupTab->getDescription().c_str()); } else if (parentParentIsPage) { - PageMap::iterator page = addPage(parentParentIsPage, parentParentIsPage->getDescription().c_str()); + PageMap::iterator page = getOrCreatePage(parentParentIsPage); assert(page != _pages.end()); assert(page->second.groupAsTab); layout = page->second.groupAsTab->addTab(closestParentGroupTab, closestParentGroupTab->getDescription().c_str()); @@ -647,12 +645,19 @@ DockablePanelPrivate::findKnobGuiOrCreate(const boost::shared_ptr & knob, } // findKnobGuiOrCreate PageMap::iterator -DockablePanelPrivate::addPage(KnobPage* page,const QString & name) +DockablePanelPrivate::getOrCreatePage(KnobPage* page) { if (!_pagesEnabled && _pages.size() > 0) { return _pages.begin(); } + QString name; + if (!page) { + name = _defaultPageName; + } else { + name = page->getDescription().c_str(); + } + PageMap::iterator found = _pages.find(name); if ( found != _pages.end() ) { @@ -714,5 +719,28 @@ DockablePanelPrivate::addPage(KnobPage* page,const QString & name) return _pages.insert( make_pair(name,p) ).first; } - +void +DockablePanelPrivate::refreshPagesSecretness() +{ + assert(_tabWidget); + std::string stdName = _tabWidget->tabText(_tabWidget->currentIndex()).toStdString(); + const std::vector >& knobs = _holder->getKnobs(); + for (std::vector >::const_iterator it = knobs.begin(); it!=knobs.end(); ++it) { + KnobPage* isPage = dynamic_cast(it->get()); + if (!isPage) { + continue; + } + if (isPage->getDescription() == stdName) { + if (isPage->getIsSecret()) { + isPage->setSecret(false); + isPage->evaluateValueChange(0, isPage->getCurrentTime(), Natron::eValueChangedReasonUserEdited); + } + } else { + if (!isPage->getIsSecret()) { + isPage->setSecret(true); + isPage->evaluateValueChange(0, isPage->getCurrentTime(), Natron::eValueChangedReasonUserEdited); + } + } + } +} diff --git a/Gui/DockablePanelPrivate.h b/Gui/DockablePanelPrivate.h index e15798043c..f1b278d797 100644 --- a/Gui/DockablePanelPrivate.h +++ b/Gui/DockablePanelPrivate.h @@ -172,7 +172,7 @@ struct DockablePanelPrivate const boost::shared_ptr& stack); /*inserts a new page to the dockable panel.*/ - PageMap::iterator addPage(KnobPage* page,const QString & name); + PageMap::iterator getOrCreatePage(KnobPage* page); boost::shared_ptr ensureDefaultPageKnobCreated() ; @@ -190,6 +190,8 @@ struct DockablePanelPrivate const std::vector< boost::shared_ptr< KnobI > > & knobsOnSameLine = std::vector< boost::shared_ptr< KnobI > >() ); PageMap::iterator getDefaultPage(const boost::shared_ptr &knob); + + void refreshPagesSecretness(); }; diff --git a/Gui/DopeSheet.cpp b/Gui/DopeSheet.cpp index 4977265210..5e9da5a9b8 100644 --- a/Gui/DopeSheet.cpp +++ b/Gui/DopeSheet.cpp @@ -551,7 +551,11 @@ DopeSheetSelectionModel *DopeSheet::getSelectionModel() const return _imp->selectionModel; } - +DopeSheetEditor* +DopeSheet::getEditor() const +{ + return _imp->editor; +} void DopeSheet::deleteSelectedKeyframes() { @@ -1379,7 +1383,7 @@ DSNodePrivate::~DSNodePrivate() void DSNodePrivate::initGroupNode() { - boost::shared_ptr node = nodeGui.lock(); + /* boost::shared_ptr node = nodeGui.lock(); if (!node) { return; } @@ -1396,10 +1400,10 @@ void DSNodePrivate::initGroupNode() NodePtr subNode = (*it); boost::shared_ptr subNodeGui = boost::dynamic_pointer_cast(subNode->getNodeGui()); - if (!subNodeGui->getSettingPanel() || !subNodeGui->isSettingsPanelVisible()) { + if (!subNodeGui || !subNodeGui->getSettingPanel() || !subNodeGui->isSettingsPanelVisible()) { continue; } - } + }*/ } DSNode::DSNode(DopeSheet *model, diff --git a/Gui/DopeSheet.h b/Gui/DopeSheet.h index 1fb4be311b..2fd1b8036d 100644 --- a/Gui/DopeSheet.h +++ b/Gui/DopeSheet.h @@ -371,7 +371,8 @@ class DopeSheet: public QObject DopeSheetSelectionModel *getSelectionModel() const; - + DopeSheetEditor* getEditor() const; + // User interaction void deleteSelectedKeyframes(); diff --git a/Gui/DopeSheetEditor.cpp b/Gui/DopeSheetEditor.cpp index 14575055d4..044b241ce0 100644 --- a/Gui/DopeSheetEditor.cpp +++ b/Gui/DopeSheetEditor.cpp @@ -47,11 +47,10 @@ class DopeSheetEditorPrivate { public: - DopeSheetEditorPrivate(DopeSheetEditor *qq, Gui *gui); + DopeSheetEditorPrivate(DopeSheetEditor *qq); /* attributes */ DopeSheetEditor *q_ptr; - Gui *gui; QVBoxLayout *mainLayout; @@ -63,9 +62,8 @@ class DopeSheetEditorPrivate }; -DopeSheetEditorPrivate::DopeSheetEditorPrivate(DopeSheetEditor *qq, Gui *gui) : +DopeSheetEditorPrivate::DopeSheetEditorPrivate(DopeSheetEditor *qq) : q_ptr(qq), - gui(gui), mainLayout(0), model(0), splitter(0), @@ -80,8 +78,8 @@ DopeSheetEditorPrivate::DopeSheetEditorPrivate(DopeSheetEditor *qq, Gui *gui) : */ DopeSheetEditor::DopeSheetEditor(Gui *gui, boost::shared_ptr timeline, QWidget *parent) : QWidget(parent), - ScriptObject(), - _imp(new DopeSheetEditorPrivate(this, gui)) + PanelWidget(this,gui), + _imp(new DopeSheetEditorPrivate(this)) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -170,7 +168,7 @@ DopeSheetEditor::refreshSelectionBboxAndRedrawView() int DopeSheetEditor::getTimelineCurrentTime() const { - return _imp->gui->getApp()->getTimeLine()->currentFrame(); + return getGui()->getApp()->getTimeLine()->currentFrame(); } DopeSheetView* @@ -203,8 +201,59 @@ DopeSheetEditor::keyPressEvent(QKeyEvent* e) Qt::Key key = (Qt::Key)e->key(); Qt::KeyboardModifiers modifiers = e->modifiers(); + bool accept = true; if (isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphRenameNode, modifiers, key)) { _imp->model->renameSelectedNode(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorDeleteKeys, modifiers, key)) { + _imp->dopeSheetView->deleteSelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorFrameSelection, modifiers, key)) { + _imp->dopeSheetView->centerOnSelection(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorSelectAllKeyframes, modifiers, key)) { + _imp->dopeSheetView->onSelectedAllTriggered(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorConstant, modifiers, key)) { + _imp->dopeSheetView->constantInterpSelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorLinear, modifiers, key)) { + _imp->dopeSheetView->linearInterpSelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorSmooth, modifiers, key)) { + _imp->dopeSheetView->smoothInterpSelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorCatmullrom, modifiers, key)) { + _imp->dopeSheetView->catmullRomInterpSelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorCubic, modifiers, key)) { + _imp->dopeSheetView->cubicInterpSelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorHorizontal, modifiers, key)) { + _imp->dopeSheetView->horizontalInterpSelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorBreak, modifiers, key)) { + _imp->dopeSheetView->breakInterpSelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorCopySelectedKeyframes, modifiers, key)) { + _imp->dopeSheetView->copySelectedKeyframes(); + } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorPasteKeyframes, modifiers, key)) { + _imp->dopeSheetView->pasteKeyframes(); + } else { + accept = false; + QWidget::keyPressEvent(e); + } + if (accept) { + takeClickFocus(); + e->accept(); + } else { + handleUnCaughtKeyPressEvent(); } } +void DopeSheetEditor::enterEvent(QEvent *e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} + +void DopeSheetEditor::leaveEvent(QEvent *e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); +} + +void +DopeSheetEditor::onInputEventCalled() +{ + takeClickFocus(); +} \ No newline at end of file diff --git a/Gui/DopeSheetEditor.h b/Gui/DopeSheetEditor.h index 0aecdf7ac4..0468b057ff 100644 --- a/Gui/DopeSheetEditor.h +++ b/Gui/DopeSheetEditor.h @@ -18,7 +18,7 @@ CLANG_DIAG_OFF(uninitialized) CLANG_DIAG_ON(deprecated) CLANG_DIAG_ON(uninitialized) -#include "Engine/ScriptObject.h" +#include "Gui/PanelWidget.h" class DopeSheetEditorPrivate; class Gui; @@ -59,7 +59,7 @@ class TimeLine; * * These two views query the model to display and modify data. */ -class DopeSheetEditor : public QWidget, public ScriptObject +class DopeSheetEditor : public QWidget, public PanelWidget { public: @@ -95,9 +95,13 @@ class DopeSheetEditor : public QWidget, public ScriptObject void setTreeWidgetWidth(int width); int getTreeWidgetWidth() const; + + void onInputEventCalled(); private: + virtual void enterEvent(QEvent *e) OVERRIDE FINAL; + virtual void leaveEvent(QEvent *e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; boost::scoped_ptr _imp; diff --git a/Gui/DopeSheetEditorUndoRedo.cpp b/Gui/DopeSheetEditorUndoRedo.cpp index 5069d3aabf..b2447f9400 100644 --- a/Gui/DopeSheetEditorUndoRedo.cpp +++ b/Gui/DopeSheetEditorUndoRedo.cpp @@ -841,10 +841,23 @@ DSPasteKeysCommand::DSPasteKeysCommand(const std::vector &keys, DopeSheetEditor *model, QUndoCommand *parent) : QUndoCommand(parent), - _keys(keys), + _refTime(0), + _refKeyindex(-1), + _keys(), _model(model) { + _refTime = _model->getTimelineCurrentTime(); setText(QObject::tr("Paste keyframes")); + for (std::size_t i = 0; i < keys.size(); ++i) { + _keys.push_back(keys[i]); + if (_refKeyindex == -1) { + _refKeyindex = i; + } else { + if (keys[i].key.getTime() < _keys[_refKeyindex].key.getTime()) { + _refKeyindex = i; + } + } + } } void DSPasteKeysCommand::undo() @@ -859,10 +872,10 @@ void DSPasteKeysCommand::redo() void DSPasteKeysCommand::addOrRemoveKeyframe(bool add) { - for (std::vector::const_iterator it = _keys.begin(); it != _keys.end(); ++it) { - const DopeSheetKey& key = (*it); - boost::shared_ptr knobContext = key.context.lock(); + for (std::size_t i = 0; i < _keys.size(); ++i) { + + boost::shared_ptr knobContext = _keys[i].context.lock(); if (!knobContext) { continue; } @@ -871,9 +884,9 @@ void DSPasteKeysCommand::addOrRemoveKeyframe(bool add) boost::shared_ptr knob = knobContext->getInternalKnob(); knob->beginChanges(); - SequenceTime currentTime = _model->getTimelineCurrentTime(); + double keyTime = _keys[i].key.getTime(); - double keyTime = key.key.getTime(); + double setTime = keyTime - _keys[_refKeyindex].key.getTime() + _refTime; if (add) { Knob* isDouble = dynamic_cast*>(knob.get()); @@ -883,22 +896,33 @@ void DSPasteKeysCommand::addOrRemoveKeyframe(bool add) for (int i = 0; i < knob->getDimension(); ++i) { if (dim == -1 || i == dim) { + KeyFrame k = _keys[i].key; + k.setTime(setTime); + if (isDouble) { - isDouble->setValueAtTime(currentTime, isDouble->getValueAtTime(keyTime,i), i); + k.setValue(isDouble->getValueAtTime(keyTime)); } else if (isBool) { - isBool->setValueAtTime(currentTime, isBool->getValueAtTime(keyTime,i), i); + k.setValue(isDouble->getValueAtTime(keyTime)); } else if (isInt) { - isInt->setValueAtTime(currentTime, isInt->getValueAtTime(keyTime,i), i); + k.setValue(isDouble->getValueAtTime(keyTime)); } else if (isString) { - isString->setValueAtTime(currentTime, isString->getValueAtTime(keyTime,i), i); + std::string v = isString->getValueAtTime(keyTime); + double keyFrameValue = 0.; + AnimatingKnobStringHelper* isStringAnimatedKnob = dynamic_cast(this); + assert(isStringAnimatedKnob); + if (isStringAnimatedKnob) { + isStringAnimatedKnob->stringToKeyFrameValue(keyTime,v,&keyFrameValue); + } + k.setValue(keyFrameValue); } + knob->setKeyFrame(k, i, Natron::eValueChangedReasonNatronGuiEdited); } } } else { for (int i = 0; i < knob->getDimension(); ++i) { if (dim == -1 || i == dim) { - knob->deleteValueAtTime(Natron::eCurveChangeReasonDopeSheet,currentTime, i); + knob->deleteValueAtTime(Natron::eCurveChangeReasonDopeSheet,setTime, i); } } } diff --git a/Gui/DopeSheetEditorUndoRedo.h b/Gui/DopeSheetEditorUndoRedo.h index f0e73f722e..bbc9b88b7c 100644 --- a/Gui/DopeSheetEditorUndoRedo.h +++ b/Gui/DopeSheetEditorUndoRedo.h @@ -360,6 +360,8 @@ class DSSetSelectedKeysInterpolationCommand : public QUndoCommand */ class DSPasteKeysCommand : public QUndoCommand { + + public: DSPasteKeysCommand(const std::vector &keys, DopeSheetEditor *model, @@ -378,6 +380,9 @@ class DSPasteKeysCommand : public QUndoCommand void addOrRemoveKeyframe(bool add); private: + + int _refTime; + int _refKeyindex; std::vector _keys; DopeSheetEditor *_model; }; diff --git a/Gui/DopeSheetView.cpp b/Gui/DopeSheetView.cpp index 65738a1bc5..11105b3791 100644 --- a/Gui/DopeSheetView.cpp +++ b/Gui/DopeSheetView.cpp @@ -50,6 +50,7 @@ #include "Gui/CurveWidget.h" #include "Gui/DockablePanel.h" #include "Gui/DopeSheet.h" +#include "Gui/DopeSheetEditor.h" #include "Gui/DopeSheetEditorUndoRedo.h" #include "Gui/DopeSheetHierarchyView.h" #include "Gui/Gui.h" @@ -261,6 +262,8 @@ class DopeSheetViewPrivate // std::map nodeRanges; + std::list nodeRangesBeingComputed; // to avoid recursion in groups + int rangeComputationRecursion; // for rendering QFont *font; @@ -308,6 +311,8 @@ DopeSheetViewPrivate::DopeSheetViewPrivate(DopeSheetView *qq) : gui(0), timeline(), nodeRanges(), + nodeRangesBeingComputed(), + rangeComputationRecursion(0), font(new QFont(appFont,appFontSize)), textRenderer(), kfTexturesIDs(), @@ -1808,6 +1813,13 @@ void DopeSheetViewPrivate::computeNodeRange(DSNode *dsNode) void DopeSheetViewPrivate::computeReaderRange(DSNode *reader) { + if (std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), reader) != nodeRangesBeingComputed.end()) { + return; + } + + nodeRangesBeingComputed.push_back(reader); + ++rangeComputationRecursion; + NodePtr node = reader->getInternalNode(); Knob *startingTimeKnob = dynamic_cast *>(node->getKnobByName(kReaderParamNameStartingTime).get()); @@ -1833,10 +1845,23 @@ void DopeSheetViewPrivate::computeReaderRange(DSNode *reader) if (boost::shared_ptr isConnectedToTimeNode = model->getNearestTimeNodeFromOutputs(reader)) { computeNodeRange(isConnectedToTimeNode.get()); } + + --rangeComputationRecursion; + std::list::iterator found = std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), reader); + assert(found != nodeRangesBeingComputed.end()); + nodeRangesBeingComputed.erase(found); } void DopeSheetViewPrivate::computeRetimeRange(DSNode *retimer) { + + if (std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), retimer) != nodeRangesBeingComputed.end()) { + return; + } + + nodeRangesBeingComputed.push_back(retimer); + ++rangeComputationRecursion; + NodePtr node = retimer->getInternalNode(); NodePtr input = node->getInput(0); if (input) { @@ -1870,10 +1895,23 @@ void DopeSheetViewPrivate::computeRetimeRange(DSNode *retimer) else { nodeRanges[retimer] = FrameRange(); } + + --rangeComputationRecursion; + std::list::iterator found = std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), retimer); + assert(found != nodeRangesBeingComputed.end()); + nodeRangesBeingComputed.erase(found); } void DopeSheetViewPrivate::computeTimeOffsetRange(DSNode *timeOffset) { + + if (std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), timeOffset) != nodeRangesBeingComputed.end()) { + return; + } + + nodeRangesBeingComputed.push_back(timeOffset); + ++rangeComputationRecursion; + FrameRange range(0, 0); // Retrieve nearest reader useful values @@ -1891,10 +1929,23 @@ void DopeSheetViewPrivate::computeTimeOffsetRange(DSNode *timeOffset) } nodeRanges[timeOffset] = range; + + --rangeComputationRecursion; + std::list::iterator found = std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), timeOffset); + assert(found != nodeRangesBeingComputed.end()); + nodeRangesBeingComputed.erase(found); } void DopeSheetViewPrivate::computeFRRange(DSNode *frameRange) { + + if (std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), frameRange) != nodeRangesBeingComputed.end()) { + return; + } + + nodeRangesBeingComputed.push_back(frameRange); + ++rangeComputationRecursion; + NodePtr node = frameRange->getInternalNode(); Knob *frameRangeKnob = dynamic_cast *>(node->getKnobByName("frameRange").get()); @@ -1905,24 +1956,39 @@ void DopeSheetViewPrivate::computeFRRange(DSNode *frameRange) range.second = frameRangeKnob->getValue(1); nodeRanges[frameRange] = range; + + --rangeComputationRecursion; + std::list::iterator found = std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), frameRange); + assert(found != nodeRangesBeingComputed.end()); + nodeRangesBeingComputed.erase(found); } void DopeSheetViewPrivate::computeGroupRange(DSNode *group) { + + if (std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), group) != nodeRangesBeingComputed.end()) { + return; + } + NodePtr node = group->getInternalNode(); assert(node); if (!node) { throw std::logic_error("DopeSheetViewPrivate::computeGroupRange: node is NULL"); } - + FrameRange range; std::set times; - + NodeGroup* nodegroup = dynamic_cast(node->getLiveInstance()); assert(nodegroup); if (!nodegroup) { throw std::logic_error("DopeSheetViewPrivate::computeGroupRange: node is not a group"); } + + nodeRangesBeingComputed.push_back(group); + ++rangeComputationRecursion; + + NodeList nodes = nodegroup->getNodes(); for (NodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { @@ -1984,6 +2050,12 @@ void DopeSheetViewPrivate::computeGroupRange(DSNode *group) } nodeRanges[group] = range; + + --rangeComputationRecursion; + std::list::iterator found = std::find(nodeRangesBeingComputed.begin(), nodeRangesBeingComputed.end(), group); + assert(found != nodeRangesBeingComputed.end()); + nodeRangesBeingComputed.erase(found); + } void DopeSheetViewPrivate::onMouseLeftButtonDrag(QMouseEvent *e) @@ -2974,6 +3046,8 @@ void DopeSheetView::paintGL() void DopeSheetView::mousePressEvent(QMouseEvent *e) { running_in_main_thread(); + + _imp->model->getEditor()->onInputEventCalled(); bool didSomething = false; @@ -3254,7 +3328,7 @@ void DopeSheetView::mouseReleaseEvent(QMouseEvent *e) _imp->gui->setDraftRenderEnabled(false); bool autoProxyEnabled = appPTR->getCurrentSettings()->isAutoProxyEnabled(); if (autoProxyEnabled) { - _imp->gui->renderAllViewers(); + _imp->gui->renderAllViewers(true); } } } @@ -3334,7 +3408,6 @@ DopeSheetView::wheelEvent(QWheelEvent *e) QPointF zoomCenter = _imp->zoomContext.toZoomCoordinates(e->x(), e->y()); _imp->zoomOrPannedSinceLastFit = true; - qDebug() << scaleFactor; par = _imp->zoomContext.aspectRatio() * scaleFactor; @@ -3364,14 +3437,7 @@ DopeSheetView::wheelEvent(QWheelEvent *e) } } -void DopeSheetView::enterEvent(QEvent *e) -{ - running_in_main_thread(); - setFocus(); - - QGLWidget::enterEvent(e); -} void DopeSheetView::focusInEvent(QFocusEvent *e) { @@ -3380,57 +3446,3 @@ void DopeSheetView::focusInEvent(QFocusEvent *e) _imp->model->setUndoStackActive(); } -void DopeSheetView::keyPressEvent(QKeyEvent *e) -{ - running_in_main_thread(); - - Qt::KeyboardModifiers modifiers = e->modifiers(); - Qt::Key key = Qt::Key(e->key()); - - if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionShowPaneFullScreen, modifiers, key) ) { - TabWidget* tab = dynamic_cast(parentWidget()->parentWidget()->parentWidget()); - assert(tab); - if (tab) { - QKeyEvent* ev = new QKeyEvent(QEvent::KeyPress, key, modifiers); - QCoreApplication::postEvent(tab,ev); - } - - } else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorDeleteKeys, modifiers, key)) { - deleteSelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorFrameSelection, modifiers, key)) { - centerOnSelection(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorSelectAllKeyframes, modifiers, key)) { - onSelectedAllTriggered(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorConstant, modifiers, key)) { - constantInterpSelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorLinear, modifiers, key)) { - linearInterpSelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorSmooth, modifiers, key)) { - smoothInterpSelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorCatmullrom, modifiers, key)) { - catmullRomInterpSelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorCubic, modifiers, key)) { - cubicInterpSelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorHorizontal, modifiers, key)) { - horizontalInterpSelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionCurveEditorBreak, modifiers, key)) { - breakInterpSelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorCopySelectedKeyframes, modifiers, key)) { - copySelectedKeyframes(); - } - else if (isKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorPasteKeyframes, modifiers, key)) { - pasteKeyframes(); - } else { - QGLWidget::keyPressEvent(e); - } -} diff --git a/Gui/DopeSheetView.h b/Gui/DopeSheetView.h index 28166fb117..187bd4f950 100644 --- a/Gui/DopeSheetView.h +++ b/Gui/DopeSheetView.h @@ -155,12 +155,9 @@ public Q_SLOTS: void wheelEvent(QWheelEvent *e) OVERRIDE FINAL; - void enterEvent(QEvent *e) OVERRIDE FINAL; void focusInEvent(QFocusEvent *e) OVERRIDE FINAL; - void keyPressEvent(QKeyEvent *e) OVERRIDE FINAL; - -private Q_SLOTS: +public Q_SLOTS: /** * @brief Computes the timeline positions and refresh the view. * diff --git a/Gui/Edge.cpp b/Gui/Edge.cpp index 86cdcc0f0e..7fa005424d 100644 --- a/Gui/Edge.cpp +++ b/Gui/Edge.cpp @@ -198,7 +198,13 @@ Edge::setSourceAndDestination(const boost::shared_ptr & src, } if (!_label) { - _label = new QGraphicsTextItem(QString( dst->getNode()->getInputLabel(_inputNb).c_str() ),this); + QString label; + try { + label = QString( dst->getNode()->getInputLabel(_inputNb).c_str() ); + } catch (...) { + + } + _label = new QGraphicsTextItem(label,this); _label->setDefaultTextColor( QColor(200,200,200) ); } else { _label->setPlainText( QString( dst->getNode()->getInputLabel(_inputNb).c_str() ) ); diff --git a/Gui/EditExpressionDialog.cpp b/Gui/EditExpressionDialog.cpp index 1c1a72cf8d..b8db7d2ded 100644 --- a/Gui/EditExpressionDialog.cpp +++ b/Gui/EditExpressionDialog.cpp @@ -171,6 +171,6 @@ EditExpressionDialog::getDeclaredVariables(std::list variables.push_back(std::make_pair("thisNode", tr("the current node"))); variables.push_back(std::make_pair("thisGroup", tr("When thisNode belongs to a group, it references the parent group node, otherwise it will reference the current application instance"))); variables.push_back(std::make_pair("thisParam", tr("the current param being edited"))); - variables.push_back(std::make_pair("dimension", tr("Defined only if the parameter is multi-dimensional, it references the dimension of the parameter being edited (0-based index"))); - variables.push_back(std::make_pair("frame", tr("the current time on the timeline"))); + variables.push_back(std::make_pair("dimension", tr("Defined only if the parameter is multi-dimensional, it references the dimension of the parameter being edited (0-based index)"))); + variables.push_back(std::make_pair("frame", tr("the current time on the timeline or the time passed to the get function"))); } diff --git a/Gui/Gui.cpp b/Gui/Gui.cpp index c4186922b9..352da184b8 100644 --- a/Gui/Gui.cpp +++ b/Gui/Gui.cpp @@ -87,6 +87,9 @@ Gui::Gui(GuiAppInstance* app, QObject::connect( this, SIGNAL( doDialogWithStopAskingCheckbox(int, QString, QString, bool, Natron::StandardButtons, int) ), this, SLOT( onDoDialogWithStopAskingCheckbox(int, QString, QString, bool, Natron::StandardButtons, int) ) ); QObject::connect( app, SIGNAL( pluginsPopulated() ), this, SLOT( addToolButttonsToToolBar() ) ); + + QObject::connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*, QWidget*))); + } Gui::~Gui() @@ -175,7 +178,7 @@ Gui::abortProject(bool quitApp) assert(_imp->_appInstance); - _imp->_appInstance->getProject()->closeProject(true); + // _imp->_appInstance->getProject()->closeProject(true); _imp->notifyGuiClosing(); _imp->_appInstance->quit(); } else { @@ -235,10 +238,7 @@ Gui::closeEvent(QCloseEvent* e) boost::shared_ptr Gui::createNodeGUI( boost::shared_ptr node, bool requestedByLoad, - double xPosHint, - double yPosHint, - bool pushUndoRedoCommand, - bool autoConnect) + bool pushUndoRedoCommand) { assert(_imp->_nodeGraphArea); @@ -252,8 +252,7 @@ Gui::createNodeGUI( boost::shared_ptr node, } else { graph = _imp->_nodeGraphArea; } - boost::shared_ptr nodeGui = graph->createNodeGUI(node, requestedByLoad, - xPosHint, yPosHint, pushUndoRedoCommand, autoConnect); + boost::shared_ptr nodeGui = graph->createNodeGUI(node, requestedByLoad,pushUndoRedoCommand); QObject::connect( node.get(), SIGNAL( labelChanged(QString) ), this, SLOT( onNodeNameChanged(QString) ) ); assert(nodeGui); diff --git a/Gui/Gui.h b/Gui/Gui.h index c8ce7aeb79..e5c8f0f825 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -69,6 +69,7 @@ class QMutex; #include "Gui/RegisteredTabs.h" class GuiLayoutSerialization; class GuiAppInstance; +class PanelWidget; class AppInstance; class NodeGui; class TabWidget; @@ -125,9 +126,11 @@ GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT GCC_DIAG_SUGGEST_OVERRIDE_ON -public: public: + + friend class PanelWidget; + explicit Gui(GuiAppInstance* app, QWidget* parent = 0); @@ -140,10 +143,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON boost::shared_ptr createNodeGUI(boost::shared_ptr node, bool requestedByLoad, - double xPosHint, - double yPosHint, - bool pushUndoRedoCommand, - bool autoConnect); + bool pushUndoRedoCommand); void addNodeGuiToCurveEditor(const boost::shared_ptr &node); @@ -299,8 +299,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void registerPane(TabWidget* pane); void unregisterPane(TabWidget* pane); - void registerTab(QWidget* tab,ScriptObject* obj); - void unregisterTab(QWidget* tab); + void registerTab(PanelWidget* tab,ScriptObject* obj); + void unregisterTab(PanelWidget* tab); void registerFloatingWindow(FloatingWidget* window); void unregisterFloatingWindow(FloatingWidget* window); @@ -324,10 +324,10 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON /*Returns a valid tab if a tab with a matching name has been found. Otherwise returns NULL.*/ - QWidget* findExistingTab(const std::string & name) const; - void findExistingTab(const std::string & name, QWidget** w,ScriptObject** o) const; + PanelWidget* findExistingTab(const std::string & name) const; + void findExistingTab(const std::string & name, PanelWidget** w,ScriptObject** o) const; - void appendTabToDefaultViewerPane(QWidget* tab,ScriptObject* obj); + void appendTabToDefaultViewerPane(PanelWidget* tab,ScriptObject* obj); /** * @brief Get the central of the application, it is either 1 TabWidget or a Splitter. @@ -356,9 +356,9 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON /*force a refresh on all previews no matter what*/ void forceRefreshAllPreviews(); - void startDragPanel(QWidget* panel); + void startDragPanel(PanelWidget* panel); - QWidget* stopDragPanel(QSize* initialSize); + PanelWidget* stopDragPanel(QSize* initialSize); bool isDraggingPanel() const; @@ -531,7 +531,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void redrawAllViewers(); - void renderAllViewers(); + void renderAllViewers(bool canAbort); void toggleAutoHideGraphInputs(); @@ -590,6 +590,17 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON **/ bool abortProject(bool quitApp); + /* + * @brief To be called by "main widgets" such as NodeGraph, Viewer etc... to determine if focus stealing is possible to have + * mouse position dependent shortcuts without the user actually clicking. + */ + static bool isFocusStealingPossible(); + + PanelWidget* getCurrentPanelFocus() const; + + void setLastKeyPressVisitedClickFocus(bool visited); + + Q_SIGNALS: @@ -601,6 +612,11 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void viewersChanged(); public Q_SLOTS: + + ///Called whenever the time changes on the timeline + void onTimeChanged(SequenceTime time,int reason); + + void onTimelineTimeAboutToChange(); void reloadStylesheet(); @@ -711,10 +727,14 @@ public Q_SLOTS: void onRenderProgressDialogFinished(); -private: + void onFocusChanged(QWidget* old, QWidget*); +private: + + void setCurrentPanelFocus(PanelWidget* widget); + AppInstance* openProjectInternal(const std::string & absoluteFileName) WARN_UNUSED_RETURN; void setupUi(); diff --git a/Gui/Gui.pro b/Gui/Gui.pro index ee16d5ef7d..edc9666a29 100644 --- a/Gui/Gui.pro +++ b/Gui/Gui.pro @@ -160,10 +160,12 @@ SOURCES += \ NodeGui.cpp \ NodeGuiSerialization.cpp \ NodeSettingsPanel.cpp \ + PanelWidget.cpp \ PickKnobDialog.cpp \ PreferencesPanel.cpp \ ProjectGui.cpp \ ProjectGuiSerialization.cpp \ + PropertiesBinWrapper.cpp \ PythonPanels.cpp \ QtDecoder.cpp \ QtEncoder.cpp \ @@ -291,6 +293,7 @@ HEADERS += \ NodeGui.h \ NodeGuiSerialization.h \ NodeSettingsPanel.h \ + PanelWidget.h \ PickKnobDialog.h \ PreferencesPanel.h \ ProjectGui.h \ diff --git a/Gui/Gui05.cpp b/Gui/Gui05.cpp index 3e47b94fc5..9d91a08f5d 100644 --- a/Gui/Gui05.cpp +++ b/Gui/Gui05.cpp @@ -39,6 +39,7 @@ #include "Engine/Node.h" #include "Engine/NodeGroup.h" #include "Engine/Project.h" +#include "Engine/TimeLine.h" #include "Gui/AboutWindow.h" #include "Gui/AutoHideToolBar.h" @@ -133,8 +134,13 @@ Gui::setupUi() //the same action also clears the ofx plugins caches, they are not the same cache but are used to the same end - QObject::connect( _imp->_appInstance->getProject().get(), SIGNAL( projectNameChanged(QString) ), this, SLOT( onProjectNameChanged(QString) ) ); - + + boost::shared_ptr project = _imp->_appInstance->getProject(); + QObject::connect( project.get(), SIGNAL( projectNameChanged(QString) ), this, SLOT( onProjectNameChanged(QString) ) ); + + boost::shared_ptr timeline = project->getTimeLine(); + QObject::connect( timeline.get(),SIGNAL( frameChanged(SequenceTime,int) ), this,SLOT( onTimeChanged(SequenceTime,int) ) ); + QObject::connect( timeline.get(),SIGNAL( frameAboutToChange()), this, SLOT(onTimelineTimeAboutToChange())); /*Searches recursively for all child objects of the given object, and connects matching signals from them to slots of object that follow the following form: @@ -165,12 +171,12 @@ Gui::onPropertiesScrolled() (*it)->redrawGLWidgets(); } } - _imp->_curveEditor->getCurveWidget()->updateGL(); + _imp->_curveEditor->getCurveWidget()->update(); { QMutexLocker k (&_imp->_histogramsMutex); for (std::list::iterator it = _imp->_histograms.begin(); it != _imp->_histograms.end(); ++it) { - (*it)->updateGL(); + (*it)->update(); } } #endif diff --git a/Gui/Gui20.cpp b/Gui/Gui20.cpp index eeeea39293..a39344d9d8 100644 --- a/Gui/Gui20.cpp +++ b/Gui/Gui20.cpp @@ -149,13 +149,18 @@ Gui::loadStyleSheet() { boost::shared_ptr settings = appPTR->getCurrentSettings(); - QString selStr, sunkStr, baseStr, raisedStr, txtStr, intStr, kfStr, eStr, altStr; + QString selStr, sunkStr, baseStr, raisedStr, txtStr, intStr, kfStr, eStr, altStr, lightSelStr; //settings-> { double r, g, b; settings->getSelectionColor(&r, &g, &b); + double lr,lg,lb; + lr = 1; + lg = 0.75; + lb = 0.47; selStr = QString("rgb(%1,%2,%3)").arg(Color::floatToInt<256>(r)).arg(Color::floatToInt<256>(g)).arg(Color::floatToInt<256>(b)); + lightSelStr = QString("rgb(%1,%2,%3)").arg(Color::floatToInt<256>(lr)).arg(Color::floatToInt<256>(lg)).arg(Color::floatToInt<256>(lb)); } { double r, g, b; @@ -220,7 +225,8 @@ Gui::loadStyleSheet() .arg(kfStr) // %7: keyframe value color .arg("rgb(0,0,0)") // %8: disabled editable text .arg(eStr) // %9: expression background color - .arg(altStr) ); // %10 = altered text color + .arg(altStr) // %10 = altered text color + .arg(lightSelStr)); // %11 = mouse over selection color } else { Natron::errorDialog(tr("Stylesheet").toStdString(), tr("Failure to load stylesheet file ").toStdString() + qss.fileName().toStdString()); } @@ -242,7 +248,7 @@ Gui::maximize(TabWidget* what) bool hasProperties = false; for (int i = 0; i < (*it)->count(); ++i) { - QString tabName = (*it)->tabAt(i)->objectName(); + QString tabName = (*it)->tabAt(i)->getWidget()->objectName(); if (tabName == kPropertiesBinName) { hasProperties = true; break; @@ -251,7 +257,7 @@ Gui::maximize(TabWidget* what) bool hasNodeGraphOrCurveEditor = false; for (int i = 0; i < what->count(); ++i) { - QWidget* tab = what->tabAt(i); + QWidget* tab = what->tabAt(i)->getWidget(); assert(tab); NodeGraph* isGraph = dynamic_cast(tab); CurveEditor* isEditor = dynamic_cast(tab); @@ -369,7 +375,7 @@ Gui::addViewerTab(ViewerTab* tab, } void -Gui::registerTab(QWidget* tab, +Gui::registerTab(PanelWidget* tab, ScriptObject* obj) { std::string name = obj->getScriptName(); @@ -381,8 +387,11 @@ Gui::registerTab(QWidget* tab, } void -Gui::unregisterTab(QWidget* tab) +Gui::unregisterTab(PanelWidget* tab) { + if (getCurrentPanelFocus() == tab) { + tab->removeClickFocus(); + } for (RegisteredTabs::iterator it = _imp->_registeredTabs.begin(); it != _imp->_registeredTabs.end(); ++it) { if (it->second.first == tab) { _imp->_registeredTabs.erase(it); @@ -509,7 +518,7 @@ Gui::removeViewerTab(ViewerTab* tab, if ( it != _imp->_viewerTabs.end() ) { _imp->_viewerTabs.erase(it); } - tab->notifyAppClosing(); + tab->notifyGuiClosingPublic(); tab->deleteLater(); } } @@ -685,7 +694,7 @@ Gui::getPythonPanels() const return _imp->_userPanels; } -QWidget* +PanelWidget* Gui::findExistingTab(const std::string & name) const { RegisteredTabs::const_iterator it = _imp->_registeredTabs.find(name); @@ -699,7 +708,7 @@ Gui::findExistingTab(const std::string & name) const void Gui::findExistingTab(const std::string & name, - QWidget** w, + PanelWidget** w, ScriptObject** o) const { RegisteredTabs::const_iterator it = _imp->_registeredTabs.find(name); diff --git a/Gui/Gui40.cpp b/Gui/Gui40.cpp index 0381ecf58a..bc7fa85838 100644 --- a/Gui/Gui40.cpp +++ b/Gui/Gui40.cpp @@ -65,6 +65,7 @@ #include "Gui/TabWidget.h" #include "Gui/ViewerGL.h" #include "Gui/ViewerTab.h" +#include "Gui/NodeSettingsPanel.h" using namespace Natron; @@ -83,20 +84,20 @@ Gui::forceRefreshAllPreviews() } void -Gui::startDragPanel(QWidget* panel) +Gui::startDragPanel(PanelWidget* panel) { assert(!_imp->_currentlyDraggedPanel); _imp->_currentlyDraggedPanel = panel; if (panel) { - _imp->_currentlyDraggedPanelInitialSize = panel->size(); + _imp->_currentlyDraggedPanelInitialSize = panel->getWidget()->size(); } } -QWidget* +PanelWidget* Gui::stopDragPanel(QSize* initialSize) { assert(_imp->_currentlyDraggedPanel); - QWidget* ret = _imp->_currentlyDraggedPanel; + PanelWidget* ret = _imp->_currentlyDraggedPanel; _imp->_currentlyDraggedPanel = 0; *initialSize = _imp->_currentlyDraggedPanelInitialSize; @@ -512,7 +513,7 @@ Gui::getPropertiesLayout() const } void -Gui::appendTabToDefaultViewerPane(QWidget* tab, +Gui::appendTabToDefaultViewerPane(PanelWidget* tab, ScriptObject* obj) { TabWidget* viewerAnchor = getAnchor(); @@ -842,3 +843,53 @@ Gui::onEnableRenderStatsActionTriggered() _imp->statsDialog->show(); } } + + +void +Gui::onTimelineTimeAboutToChange() +{ + assert(QThread::currentThread() == qApp->thread()); + _imp->wasLaskUserSeekDuringPlayback = false; + const std::list& viewers = getViewersList(); + for (std::list::const_iterator it = viewers.begin(); it != viewers.end(); ++it) { + RenderEngine* engine = (*it)->getInternalNode()->getRenderEngine(); + _imp->wasLaskUserSeekDuringPlayback |= engine->abortRendering(true,true); + } +} + +void +Gui::onTimeChanged(SequenceTime time, + int reason) +{ + assert(QThread::currentThread() == qApp->thread()); + + boost::shared_ptr project = getApp()->getProject(); + + ///Refresh all visible knobs at the current time + if (!getApp()->isGuiFrozen()) { + for (std::list::const_iterator it = _imp->openedPanels.begin(); it!=_imp->openedPanels.end(); ++it) { + NodeSettingsPanel* nodePanel = dynamic_cast(*it); + if (nodePanel) { + nodePanel->getNode()->getNode()->getLiveInstance()->refreshAfterTimeChange(time); + } + } + } + + + ViewerInstance* leadViewer = getApp()->getLastViewerUsingTimeline(); + + bool isUserEdited = reason == eTimelineChangeReasonUserSeek || + reason == eTimelineChangeReasonDopeSheetEditorSeek || + reason == eTimelineChangeReasonCurveEditorSeek; + + + + const std::list& viewers = getViewersList(); + ///Syncrhronize viewers + for (std::list::const_iterator it = viewers.begin(); it!=viewers.end();++it) { + if ((*it)->getInternalNode() != leadViewer || isUserEdited) { + (*it)->getInternalNode()->renderCurrentFrame(reason != eTimelineChangeReasonPlaybackSeek); + } + } +} + diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 83c06f74c7..625df26fff 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -43,8 +43,16 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF #include GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include "Engine/GroupOutput.h" #include "Engine/Node.h" @@ -56,6 +64,8 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/ActionShortcuts.h" #include "Gui/CurveEditor.h" +#include "Gui/CurveWidget.h" +#include "Gui/ComboBox.h" #include "Gui/DockablePanel.h" #include "Gui/DopeSheetEditor.h" #include "Gui/GuiAppInstance.h" @@ -73,7 +83,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/TabWidget.h" #include "Gui/ViewerGL.h" #include "Gui/ViewerTab.h" - +#include "Gui/Histogram.h" using namespace Natron; @@ -252,7 +262,7 @@ Gui::progressUpdate(KnobHolder* effect, } found->second->setValue(t * 100); } - //QCoreApplication::processEvents(); + QCoreApplication::processEvents(); return true; } @@ -432,21 +442,45 @@ Gui::resizeEvent(QResizeEvent* e) setMtSafeWindowSize( width(), height() ); } +static RightClickableWidget* isParentSettingsPanelRecursive(QWidget* w) +{ + if (!w) { + return 0; + } + RightClickableWidget* panel = qobject_cast(w); + if (panel) { + return panel; + } else { + return isParentSettingsPanelRecursive(w->parentWidget()); + } +} + void -Gui::keyPressEvent(QKeyEvent* e) +Gui::setLastKeyPressVisitedClickFocus(bool visited) { - QWidget* w = qApp->widgetAt( QCursor::pos() ); + _imp->keyPressEventHasVisitedFocusWidget = visited; +} - if ( w && ( w->objectName() == QString("SettingsPanel") ) && (e->key() == Qt::Key_Escape) ) { - RightClickableWidget* panel = dynamic_cast(w); - assert(panel); - panel->getPanel()->closePanel(); +void +Gui::keyPressEvent(QKeyEvent* e) +{ + if (_imp->currentPanelFocusEventRecursion > 0) { + return; } + + QWidget* w = qApp->widgetAt( QCursor::pos() ); + Qt::Key key = (Qt::Key)e->key(); Qt::KeyboardModifiers modifiers = e->modifiers(); - if ( isKeybind(kShortcutGroupPlayer, kShortcutIDActionPlayerPrevious, modifiers, key) ) { + if (key == Qt::Key_Escape) { + + RightClickableWidget* panel = isParentSettingsPanelRecursive(w); + if (panel) { + panel->getPanel()->closePanel(); + } + } else if ( isKeybind(kShortcutGroupPlayer, kShortcutIDActionPlayerPrevious, modifiers, key) ) { if ( getNodeGraph()->getLastSelectedViewer() ) { getNodeGraph()->getLastSelectedViewer()->previousFrame(); } @@ -511,7 +545,18 @@ Gui::keyPressEvent(QKeyEvent* e) } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionConnectViewerToInput10, modifiers, key) ) { connectInput(9); } else { - QMainWindow::keyPressEvent(e); + if (_imp->currentPanelFocus && !_imp->keyPressEventHasVisitedFocusWidget) { + + ++_imp->currentPanelFocusEventRecursion; + //If a panel as the click focus, try to send the event to it + QWidget* curFocusWidget = _imp->currentPanelFocus->getWidget(); + assert(curFocusWidget); + QKeyEvent* ev = new QKeyEvent(QEvent::KeyPress, key, modifiers); + qApp->notify(curFocusWidget,ev); + --_imp->currentPanelFocusEventRecursion; + } else { + QMainWindow::keyPressEvent(e); + } } } @@ -583,42 +628,32 @@ Gui::getNodesEntitledForOverlays(std::list > & n for (int i = 0; i < layoutItemsCount; ++i) { QLayoutItem* item = _imp->_layoutPropertiesBin->itemAt(i); NodeSettingsPanel* panel = dynamic_cast( item->widget() ); - if (panel) { - boost::shared_ptr node = panel->getNode(); - boost::shared_ptr internalNode = node->getNode(); - if (node && internalNode) { - boost::shared_ptr multiInstance = node->getMultiInstancePanel(); - if (multiInstance) { -// const std::list< std::pair,bool > >& instances = multiInstance->getInstances(); -// for (std::list< std::pair,bool > >::const_iterator it = instances.begin(); it != instances.end(); ++it) { -// NodePtr instance = it->first.lock(); -// if (node->isSettingsPanelVisible() && -// !node->isSettingsPanelMinimized() && -// instance->isActivated() && -// instance->hasOverlay() && -// it->second && -// !instance->isNodeDisabled()) { -// nodes.push_back(instance); -// } -// } - if ( internalNode->hasOverlay() && - !internalNode->isNodeDisabled() && - node->isSettingsPanelVisible() && - !node->isSettingsPanelMinimized() ) { - nodes.push_back( node->getNode() ); - } - } else { - if ( ( internalNode->hasOverlay() || internalNode->getRotoContext() ) && - !internalNode->isNodeDisabled() && - !internalNode->getParentMultiInstance() && - internalNode->isActivated() && - node->isSettingsPanelVisible() && - !node->isSettingsPanelMinimized() ) { - nodes.push_back( node->getNode() ); - } + if (!panel) { + continue; + } + boost::shared_ptr node = panel->getNode(); + boost::shared_ptr internalNode = node->getNode(); + if (node && internalNode) { + boost::shared_ptr multiInstance = node->getMultiInstancePanel(); + if (multiInstance) { + if ( internalNode->hasOverlay() && + !internalNode->isNodeDisabled() && + node->isSettingsPanelVisible() && + !node->isSettingsPanelMinimized() ) { + nodes.push_back( node->getNode() ); + } + } else { + if ( ( internalNode->hasOverlay() || internalNode->getRotoContext() ) && + !internalNode->isNodeDisabled() && + !internalNode->getParentMultiInstance() && + internalNode->isActivated() && + node->isSettingsPanelVisible() && + !node->isSettingsPanelMinimized() ) { + nodes.push_back( node->getNode() ); } } } + } } @@ -635,12 +670,12 @@ Gui::redrawAllViewers() } void -Gui::renderAllViewers() +Gui::renderAllViewers(bool canAbort) { assert(QThread::currentThread() == qApp->thread()); for (std::list::const_iterator it = _imp->_viewerTabs.begin(); it != _imp->_viewerTabs.end(); ++it) { if ( (*it)->isVisible() ) { - (*it)->getInternalNode()->renderCurrentFrame(true); + (*it)->getInternalNode()->renderCurrentFrame(canAbort); } } } @@ -680,7 +715,12 @@ Gui::onPrevTabTriggered() if (t) { t->moveToPreviousTab(); + PanelWidget* pw = t->currentWidget(); + if (pw) { + pw->takeClickFocus(); + } } + } @@ -691,6 +731,10 @@ Gui::onNextTabTriggered() if (t) { t->moveToNextTab(); + PanelWidget* pw = t->currentWidget(); + if (pw) { + pw->takeClickFocus(); + } } } @@ -701,6 +745,10 @@ Gui::onCloseTabTriggered() if (t) { t->closeCurrentWidget(); + PanelWidget* pw = t->currentWidget(); + if (pw) { + pw->takeClickFocus(); + } } } @@ -842,4 +890,60 @@ Gui::ddeOpenFile(const QString& filePath) openProject(filePath.toStdString()); #pragma message WARN("CONTROL FLOW ERROR: should check the return value of openProject, raise an error...") } -#endif \ No newline at end of file +#endif + + +bool +Gui::isFocusStealingPossible() +{ + assert( qApp && qApp->thread() == QThread::currentThread() ); + QWidget* currentFocus = qApp->focusWidget(); + + bool focusStealingNotPossible = + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus); + + return !focusStealingNotPossible; + +} + +void +Gui::setCurrentPanelFocus(PanelWidget* widget) +{ + assert(QThread::currentThread() == qApp->thread()); + _imp->currentPanelFocus = widget; +} + +PanelWidget* +Gui::getCurrentPanelFocus() const +{ + assert(QThread::currentThread() == qApp->thread()); + return _imp->currentPanelFocus; +} + +static PanelWidget* isPaneChild(QWidget* w, int recursionLevel) +{ + if (!w) { + return 0; + } + PanelWidget* pw = dynamic_cast(w); + if (pw && recursionLevel > 0) { + /* + Do not return it if recursion is 0, otherwise the focus stealing of the mouse over will actually take click focus + */ + return pw; + } + return isPaneChild(w->parentWidget(), recursionLevel + 1); +} + +void +Gui::onFocusChanged(QWidget* /*old*/, QWidget* newFocus) +{ + PanelWidget* pw = isPaneChild(newFocus,0); + if (pw) { + pw->takeClickFocus(); + } +} \ No newline at end of file diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index 41f59917da..05731c957d 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -45,6 +45,7 @@ #include "Gui/GuiApplicationManager.h" #include "Gui/Gui.h" +#include "Gui/BackDropGui.h" #include "Gui/NodeGraph.h" #include "Gui/NodeGui.h" #include "Gui/MultiInstancePanel.h" @@ -161,29 +162,19 @@ GuiAppInstance::deletePreviewProvider() void GuiAppInstance::aboutToQuit() { - deletePreviewProvider(); + + AppInstance::aboutToQuit(); + _imp->_isClosing = true; _imp->_gui->close(); - _imp->_gui->setParent(NULL); + _imp->_gui->deleteLater(); + _imp->_gui = 0; } GuiAppInstance::~GuiAppInstance() { - ///process events before closing gui - QCoreApplication::processEvents(); - - ///clear nodes prematurely so that any thread running is stopped - getProject()->clearNodes(false); - QCoreApplication::processEvents(); -//#ifndef __NATRON_WIN32__ - _imp->_gui->getNodeGraph()->discardGuiPointer(); - _imp->_gui->deleteLater(); - _imp->_gui = 0; - _imp.reset(); -//#endif - QCoreApplication::processEvents(); } bool @@ -421,7 +412,7 @@ GuiAppInstance::createNodeGui(const boost::shared_ptr &node, std::list > selectedNodes = graph->getSelectedNodes(); - boost::shared_ptr nodegui = _imp->_gui->createNodeGUI(node,loadRequest,xPosHint,yPosHint,pushUndoRedoCommand,autoConnect); + boost::shared_ptr nodegui = _imp->_gui->createNodeGUI(node,loadRequest,pushUndoRedoCommand); assert(nodegui); if ( parentMultiInstance && nodegui) { @@ -464,6 +455,32 @@ GuiAppInstance::createNodeGui(const boost::shared_ptr &node, triggerAutoSave(); } + + + ///only move main instances + if (node->getParentMultiInstanceName().empty()) { + if (selectedNodes.empty()) { + autoConnect = false; + } + if ( (xPosHint != INT_MIN) && (yPosHint != INT_MIN) && !autoConnect ) { + QPointF pos = nodegui->mapToParent( nodegui->mapFromScene( QPointF(xPosHint,yPosHint) ) ); + nodegui->refreshPosition( pos.x(),pos.y(), true ); + } else { + BackDropGui* isBd = dynamic_cast(nodegui.get()); + if (!isBd && !isGroup) { + boost::shared_ptr selectedNode; + if (selectedNodes.size() == 1) { + selectedNode = selectedNodes.front(); + BackDropGui* isBackdropGui = dynamic_cast(selectedNode.get()); + if (isBackdropGui) { + selectedNode.reset(); + } + } + nodegui->getDagGui()->moveNodesForIdealPosition(nodegui,selectedNode,autoConnect); + } + } + } + } // createNodeGui std::string @@ -1012,9 +1029,9 @@ GuiAppInstance::closeLoadPRojectSplashScreen() } void -GuiAppInstance::renderAllViewers() +GuiAppInstance::renderAllViewers(bool canAbort) { - _imp->_gui->renderAllViewers(); + _imp->_gui->renderAllViewers(canAbort); } void @@ -1047,32 +1064,40 @@ GuiAppInstance::clearOverlayRedrawRequests() } void -GuiAppInstance::onGroupCreationFinished(const boost::shared_ptr& node) -{ - NodeGraph* graph = 0; - boost::shared_ptr collection = node->getGroup(); - assert(collection); - NodeGroup* isGrp = dynamic_cast(collection.get()); - if (isGrp) { - NodeGraphI* graph_i = isGrp->getNodeGraph(); - assert(graph_i); - graph = dynamic_cast(graph_i); - } else { - graph = _imp->_gui->getNodeGraph(); +GuiAppInstance::onGroupCreationFinished(const boost::shared_ptr& node,bool requestedByLoad) +{ + if (!requestedByLoad) { + NodeGraph* graph = 0; + boost::shared_ptr collection = node->getGroup(); + assert(collection); + NodeGroup* isGrp = dynamic_cast(collection.get()); + if (isGrp) { + NodeGraphI* graph_i = isGrp->getNodeGraph(); + assert(graph_i); + graph = dynamic_cast(graph_i); + } else { + graph = _imp->_gui->getNodeGraph(); + } + assert(graph); + std::list > selectedNodes = graph->getSelectedNodes(); + boost::shared_ptr selectedNode; + if (!selectedNodes.empty()) { + selectedNode = selectedNodes.front(); + if (dynamic_cast(selectedNode.get())) { + selectedNode.reset(); + } + } + boost::shared_ptr node_gui_i = node->getNodeGui(); + assert(node_gui_i); + boost::shared_ptr nodeGui = boost::dynamic_pointer_cast(node_gui_i); + graph->moveNodesForIdealPosition(nodeGui, selectedNode, true); } - assert(graph); - - boost::shared_ptr node_gui_i = node->getNodeGui(); - assert(node_gui_i); - boost::shared_ptr nodeGui = boost::dynamic_pointer_cast(node_gui_i); - graph->moveNodesForIdealPosition(nodeGui, true); - std::list viewers; node->hasViewersConnected(&viewers); for (std::list::iterator it2 = viewers.begin(); it2 != viewers.end(); ++it2) { (*it2)->renderCurrentFrame(false); } - AppInstance::onGroupCreationFinished(node); + AppInstance::onGroupCreationFinished(node,requestedByLoad); } bool diff --git a/Gui/GuiAppInstance.h b/Gui/GuiAppInstance.h index 5bb7417aef..d45000f5f4 100644 --- a/Gui/GuiAppInstance.h +++ b/Gui/GuiAppInstance.h @@ -174,7 +174,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual void closeLoadPRojectSplashScreen() OVERRIDE FINAL; - virtual void renderAllViewers() OVERRIDE FINAL; + virtual void renderAllViewers(bool canAbort) OVERRIDE FINAL; virtual void queueRedrawForAllViewers() OVERRIDE FINAL; @@ -220,7 +220,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON private: - virtual void onGroupCreationFinished(const boost::shared_ptr& node) OVERRIDE FINAL; + virtual void onGroupCreationFinished(const boost::shared_ptr& node,bool requestedByLoad) OVERRIDE FINAL; virtual void createNodeGui(const boost::shared_ptr &node, const boost::shared_ptr& parentMultiInstance, diff --git a/Gui/GuiAppWrapper.cpp b/Gui/GuiAppWrapper.cpp index 49605e72dc..c47f6e341b 100644 --- a/Gui/GuiAppWrapper.cpp +++ b/Gui/GuiAppWrapper.cpp @@ -91,7 +91,7 @@ GuiApp::getTabWidget(const std::string& name) const bool GuiApp::moveTab(const std::string& scriptName,PyTabWidget* pane) { - QWidget* w; + PanelWidget* w; ScriptObject* o; _app->getGui()->findExistingTab(scriptName, &w, &o); if (!w || !o) { @@ -435,11 +435,11 @@ GuiApp::getViewer(const std::string& scriptName) const PyPanel* GuiApp::getUserPanel(const std::string& scriptName) const { - QWidget* w = _app->getGui()->findExistingTab(scriptName); + PanelWidget* w = _app->getGui()->findExistingTab(scriptName); if (!w) { return 0; } - return dynamic_cast(w); + return dynamic_cast(w->getWidget()); } PyViewer::PyViewer(const boost::shared_ptr& node) diff --git a/Gui/GuiApplicationManager.cpp b/Gui/GuiApplicationManager.cpp index 61407afcf9..76c53ca6b4 100644 --- a/Gui/GuiApplicationManager.cpp +++ b/Gui/GuiApplicationManager.cpp @@ -216,6 +216,12 @@ GuiApplicationManager::getIcon(Natron::PixmapEnum e, case NATRON_PIXMAP_VIEWER_RENDER_SCALE_CHECKED: path = NATRON_IMAGES_PATH "renderScale_checked.png"; break; + case NATRON_PIXMAP_VIEWER_AUTOCONTRAST_ENABLED: + path = NATRON_IMAGES_PATH "AutoContrastON.png"; + break; + case NATRON_PIXMAP_VIEWER_AUTOCONTRAST_DISABLED: + path = NATRON_IMAGES_PATH "AutoContrast.png"; + break; case NATRON_PIXMAP_OPEN_FILE: path = NATRON_IMAGES_PATH "open-file.png"; break; diff --git a/Gui/GuiApplicationManager10.cpp b/Gui/GuiApplicationManager10.cpp index ba83d8f78f..472fec0f0b 100644 --- a/Gui/GuiApplicationManager10.cpp +++ b/Gui/GuiApplicationManager10.cpp @@ -134,7 +134,7 @@ GuiApplicationManager::loadBuiltinNodePlugins(std::mapgetPluginID().c_str(), reader->getPluginLabel().c_str(), "", "", false, false, readerPlugin, false, reader->getMajorVersion(), reader->getMinorVersion(), true); + registerPlugin(grouping, reader->getPluginID().c_str(), reader->getPluginLabel().c_str(), "", "", false, false, readerPlugin, false, reader->getMajorVersion(), reader->getMinorVersion(), false); std::vector extensions = reader->supportedFileFormats(); for (U32 k = 0; k < extensions.size(); ++k) { @@ -159,7 +159,7 @@ GuiApplicationManager::loadBuiltinNodePlugins(std::mapgetPluginID().c_str(), writer->getPluginLabel().c_str(),"", "", false, false, writerPlugin, false, writer->getMajorVersion(), writer->getMinorVersion(), true); + registerPlugin(grouping, writer->getPluginID().c_str(), writer->getPluginLabel().c_str(),"", "", false, false, writerPlugin, false, writer->getMajorVersion(), writer->getMinorVersion(), false); @@ -190,7 +190,7 @@ GuiApplicationManager::loadBuiltinNodePlugins(std::mapgetPluginID().c_str(), viewer->getPluginLabel().c_str(),NATRON_IMAGES_PATH "viewer_icon.png", "", false, false, viewerPlugin, false, viewer->getMajorVersion(), viewer->getMinorVersion(), true); + registerPlugin(grouping, viewer->getPluginID().c_str(), viewer->getPluginLabel().c_str(),NATRON_IMAGES_PATH "viewer_icon.png", "", false, false, viewerPlugin, false, viewer->getMajorVersion(), viewer->getMinorVersion(), false); } @@ -786,6 +786,11 @@ GuiApplicationManager::populateShortcuts() registerKeybind(kShortcutGroupViewer, kShortcutIDNextLayer, kShortcutDescNextLayer, Qt::NoModifier, Qt::Key_PageDown); registerKeybind(kShortcutGroupViewer, kShortcutIDPrevLayer, kShortcutDescPrevLayer, Qt::NoModifier, Qt::Key_PageUp); registerKeybind(kShortcutGroupViewer, kShortcutIDSwitchInputAAndB, kShortcutDescSwitchInputAAndB, Qt::NoModifier, Qt::Key_Return); + registerKeybindWithMask(kShortcutGroupViewer, kShortcutIDShowLeftView, kShortcutDescShowLeftView, Qt::ControlModifier, Qt::Key_1, + Qt::ShiftModifier); + registerKeybindWithMask(kShortcutGroupViewer, kShortcutIDShowRightView, kShortcutDescShowRightView, Qt::ControlModifier, Qt::Key_2, + Qt::ShiftModifier); + registerMouseShortcut(kShortcutGroupViewer, kShortcutIDMousePickColor, kShortcutDescMousePickColor, Qt::ControlModifier, Qt::LeftButton); registerMouseShortcut(kShortcutGroupViewer, kShortcutIDMouseRectanglePick, kShortcutDescMouseRectanglePick, Qt::ShiftModifier | Qt::ControlModifier, Qt::LeftButton); @@ -907,6 +912,16 @@ GuiApplicationManager::populateShortcuts() registerKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorCopySelectedKeyframes, kShortcutDescActionDopeSheetEditorCopySelectedKeyframes, Qt::ControlModifier, Qt::Key_C); registerKeybind(kShortcutGroupDopeSheetEditor, kShortcutIDActionDopeSheetEditorPasteKeyframes, kShortcutDescActionDopeSheetEditorPasteKeyframes, Qt::ControlModifier, Qt::Key_V); + + //Script editor + registerKeybindWithMask(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorPrevScript, kShortcutDescActionScriptEditorPrevScript, Qt::ControlModifier, Qt::Key_BracketLeft, Qt::ShiftModifier | Qt::AltModifier); + registerKeybindWithMask(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorNextScript, kShortcutDescActionScriptEditorNextScript, Qt::ControlModifier, Qt::Key_BracketRight, Qt::ShiftModifier | Qt::AltModifier); + registerKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorClearHistory, kShortcutDescActionScriptEditorClearHistory, Qt::NoModifier, (Qt::Key)0); + registerKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptExecScript, kShortcutDescActionScriptExecScript, Qt::ControlModifier, Qt::Key_Return); + registerKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptClearOutput, kShortcutDescActionScriptClearOutput, Qt::ControlModifier, Qt::Key_Backspace); + registerKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptShowOutput, kShortcutDescActionScriptShowOutput, Qt::NoModifier, (Qt::Key)0); + + } // populateShortcuts diff --git a/Gui/GuiDefines.h b/Gui/GuiDefines.h index 709c3d5d51..1b8f296b56 100644 --- a/Gui/GuiDefines.h +++ b/Gui/GuiDefines.h @@ -49,6 +49,8 @@ #define NATRON_FONT_SIZE_11 11 #define NATRON_FONT_SIZE_12 12 #define NATRON_FONT_SIZE_13 13 +#define NATRON_FONT_SIZE_DEFAULT NATRON_FONT_SIZE_11 // the sliders font becomes undreadable below 11 on non-HiDPI mac displays + #define NATRON_MAX_RECENT_FILES 5 #endif // Gui_GuiDefines_h diff --git a/Gui/GuiPrivate.cpp b/Gui/GuiPrivate.cpp index 4988fb4ef1..f8bf394f2f 100644 --- a/Gui/GuiPrivate.cpp +++ b/Gui/GuiPrivate.cpp @@ -248,6 +248,10 @@ GuiPrivate::GuiPrivate(GuiAppInstance* app, , _lastEnteredTabWidget(0) , pythonCommands() , statsDialog(0) +, currentPanelFocus(0) +, currentPanelFocusEventRecursion(0) +, keyPressEventHasVisitedFocusWidget(false) +, wasLaskUserSeekDuringPlayback(false) { } @@ -268,10 +272,16 @@ GuiPrivate::notifyGuiClosing() { ///This is to workaround an issue that when destroying a widget it calls the focusOut() handler hence can ///cause bad pointer dereference to the Gui object since we're destroying it. + std::list tabs; { - QMutexLocker k(&_viewerTabsMutex); - for (std::list::iterator it = _viewerTabs.begin(); it != _viewerTabs.end(); ++it) { - (*it)->notifyAppClosing(); + QMutexLocker k(&_panesMutex); + tabs = _panes; + } + for (std::list::iterator it = tabs.begin(); it != tabs.end(); ++it) { + (*it)->discardGuiPointer(); + for (int i = 0; i < (*it)->count(); ++i) { + (*it)->tabAt(i)->notifyGuiClosingPublic(); + } } @@ -284,17 +294,7 @@ GuiPrivate::notifyGuiClosing() } } _lastFocusedGraph = 0; - _nodeGraphArea->discardGuiPointer(); - for (std::list::iterator it = _groups.begin(); it != _groups.end(); ++it) { - (*it)->discardGuiPointer(); - } - { - QMutexLocker k(&_panesMutex); - for (std::list::iterator it = _panes.begin(); it != _panes.end(); ++it) { - (*it)->discardGuiPointer(); - } - } } void @@ -309,6 +309,9 @@ GuiPrivate::createPropertiesBinGui() mainPropertiesLayout->setSpacing(0); _propertiesScrollArea = new QScrollArea(_propertiesBin); + + ///Remove wheel autofocus + _propertiesScrollArea->setFocusPolicy(Qt::StrongFocus); QObject::connect( _propertiesScrollArea->verticalScrollBar(), SIGNAL( valueChanged(int) ), _gui, SLOT( onPropertiesScrolled() ) ); _propertiesScrollArea->setObjectName("Properties"); assert(_nodeGraphArea); diff --git a/Gui/GuiPrivate.h b/Gui/GuiPrivate.h index 9eecd93338..eaa706daad 100644 --- a/Gui/GuiPrivate.h +++ b/Gui/GuiPrivate.h @@ -242,7 +242,7 @@ struct GuiPrivate ProjectGui* _projectGui; ///ptr to the currently dragged tab for d&d purpose. - QWidget* _currentlyDraggedPanel; + PanelWidget* _currentlyDraggedPanel; QSize _currentlyDraggedPanelInitialSize; ///The "About" window. @@ -266,6 +266,14 @@ struct GuiPrivate RenderStatsDialog* statsDialog; + PanelWidget* currentPanelFocus; + + //To prevent recursion when we forward an uncaught event to the click focus widget + int currentPanelFocusEventRecursion; + bool keyPressEventHasVisitedFocusWidget; + + bool wasLaskUserSeekDuringPlayback; + GuiPrivate(GuiAppInstance* app, Gui* gui); diff --git a/Gui/GuiResources.qrc b/Gui/GuiResources.qrc index 445a719240..bc23ec57fd 100644 --- a/Gui/GuiResources.qrc +++ b/Gui/GuiResources.qrc @@ -299,5 +299,7 @@ Resources/Images/interp_curve_z.png Resources/Images/interp_curve_z_selected.png Resources/Images/rotoToolPencil.png + Resources/Images/AutoContrast.png + Resources/Images/AutoContrastON.png diff --git a/Gui/Histogram.cpp b/Gui/Histogram.cpp index 88b448d95f..86e9b7dd34 100644 --- a/Gui/Histogram.cpp +++ b/Gui/Histogram.cpp @@ -46,6 +46,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Engine/Node.h" #include "Engine/ViewerInstance.h" +#include "Gui/ActionShortcuts.h" #include "Gui/ClickableLabel.h" #include "Gui/ComboBox.h" #include "Gui/CurveWidget.h" @@ -79,94 +80,91 @@ enum EventStateEnum struct HistogramPrivate { - HistogramPrivate(Gui* parent, - Histogram* widget) - : gui(parent) - , mainLayout(NULL) - , rightClickMenu(NULL) - , histogramSelectionMenu(NULL) - , histogramSelectionGroup(NULL) - , viewerCurrentInputMenu(NULL) - , viewerCurrentInputGroup(NULL) - , modeActions(0) - , modeMenu(NULL) - , fullImage(NULL) - , filterActions(0) - , filterMenu(NULL) - , widget(widget) - , mode(Histogram::eDisplayModeRGB) - , oldClick() - , zoomCtx() - , supportsGLSL(true) - , hasOpenGLVAOSupport(true) - , state(eEventStateNone) - , hasBeenModifiedSinceResize(false) - , _baseAxisColor(118,215,90,255) - , _scaleColor(67,123,52,255) - , _font(appFont,appFontSize) - , textRenderer() - , drawCoordinates(false) - , xCoordinateStr() - , rValueStr() - , gValueStr() - , bValueStr() - , filterSize(0) + HistogramPrivate(Histogram* widget) + : mainLayout(NULL) + , rightClickMenu(NULL) + , histogramSelectionMenu(NULL) + , histogramSelectionGroup(NULL) + , viewerCurrentInputMenu(NULL) + , viewerCurrentInputGroup(NULL) + , modeActions(0) + , modeMenu(NULL) + , fullImage(NULL) + , filterActions(0) + , filterMenu(NULL) + , widget(widget) + , mode(Histogram::eDisplayModeRGB) + , oldClick() + , zoomCtx() + , supportsGLSL(true) + , hasOpenGLVAOSupport(true) + , state(eEventStateNone) + , hasBeenModifiedSinceResize(false) + , _baseAxisColor(118,215,90,255) + , _scaleColor(67,123,52,255) + , _font(appFont,appFontSize) + , textRenderer() + , drawCoordinates(false) + , xCoordinateStr() + , rValueStr() + , gValueStr() + , bValueStr() + , filterSize(0) #ifdef NATRON_HISTOGRAM_USING_OPENGL - , histogramComputingShader() - , histogramMaximumShader() - , histogramRenderingShader() + , histogramComputingShader() + , histogramMaximumShader() + , histogramRenderingShader() #else - , histogramThread() - , histogram1() - , histogram2() - , histogram3() - , pixelsCount(0) - , vmin(0) - , vmax(0) - , binsCount(0) - , mipMapLevel(0) - , hasImage(false) + , histogramThread() + , histogram1() + , histogram2() + , histogram3() + , pixelsCount(0) + , vmin(0) + , vmax(0) + , binsCount(0) + , mipMapLevel(0) + , hasImage(false) #endif - , sizeH() + , sizeH() { } - + boost::shared_ptr getHistogramImage(RectI* imagePortion) const; - - + + void showMenu(const QPoint & globalPos); - + void drawScale(); - + void drawPicker(); void drawWarnings(); void drawMissingImage(); - + void updatePicker(double x); - + #ifdef NATRON_HISTOGRAM_USING_OPENGL - + void resizeComputingVBO(int w,int h); - - + + ///For all these functions, mode refers to either R,G,B,A or Y void computeHistogram(Histogram::DisplayModeEnum mode); void renderHistogram(Histogram::DisplayModeEnum mode); void activateHistogramComputingShader(Histogram::DisplayModeEnum mode); void activateHistogramRenderingShader(Histogram::DisplayModeEnum mode); - + #else void drawHistogramCPU(); #endif - + ////////////////////////////////// // data members - - Gui* gui; //< ptr to the gui + QVBoxLayout* mainLayout; - + ///////// OPTIONS Natron::Menu* rightClickMenu; QMenu* histogramSelectionMenu; @@ -272,8 +270,9 @@ struct HistogramPrivate Histogram::Histogram(Gui* gui, const QGLWidget* shareWidget) - : QGLWidget(gui,shareWidget) - , _imp( new HistogramPrivate(gui,this) ) +: QGLWidget(gui,shareWidget) +, PanelWidget(this,gui) +, _imp( new HistogramPrivate(this) ) { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); @@ -402,7 +401,7 @@ Histogram::Histogram(Gui* gui, } QObject::connect( _imp->filterActions, SIGNAL( triggered(QAction*) ), this, SLOT( onFilterChanged(QAction*) ) ); - QObject::connect( _imp->gui, SIGNAL( viewersChanged() ), this, SLOT( populateViewersChoices() ) ); + QObject::connect( getGui(), SIGNAL( viewersChanged() ), this, SLOT( populateViewersChoices() ) ); populateViewersChoices(); } @@ -457,11 +456,11 @@ boost::shared_ptr HistogramPrivate::getHistogramImage(RectI* imag return boost::shared_ptr(); } else if (index == 1) { //current viewer - viewer = gui->getNodeGraph()->getLastSelectedViewer(); + viewer = widget->getGui()->getNodeGraph()->getLastSelectedViewer(); } else { boost::shared_ptr ret; - const std::list & viewerTabs = gui->getViewersList(); + const std::list & viewerTabs = widget->getGui()->getViewersList(); for (std::list::const_iterator it = viewerTabs.begin(); it != viewerTabs.end(); ++it) { if ( (*it)->getInternalNode()->getScriptName_mt_safe() == viewerName ) { viewer = *it; @@ -564,7 +563,7 @@ Histogram::populateViewersChoices() _imp->histogramSelectionMenu->addAction(currentAction); - const std::list & viewerTabs = _imp->gui->getViewersList(); + const std::list & viewerTabs = getGui()->getViewersList(); int c = 2; for (std::list::const_iterator it = viewerTabs.begin(); it != viewerTabs.end(); ++it) { if ( (*it)->getInternalNode()->getNode()->isActivated() ) { @@ -614,7 +613,7 @@ Histogram::onViewerImageChanged(ViewerGL* viewer, if (viewer && hasImageBackend) { QString viewerName = viewer->getInternalNode()->getScriptName_mt_safe().c_str(); - ViewerTab* lastSelectedViewer = _imp->gui->getNodeGraph()->getLastSelectedViewer(); + ViewerTab* lastSelectedViewer = getGui()->getNodeGraph()->getLastSelectedViewer(); QString currentViewerName; if (lastSelectedViewer) { currentViewerName = lastSelectedViewer->getInternalNode()->getScriptName_mt_safe().c_str(); @@ -1229,6 +1228,8 @@ Histogram::mousePressEvent(QMouseEvent* e) // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); + takeClickFocus(); + //// // middle button: scroll view if ( buttonDownIsMiddle(e) ) { @@ -1424,36 +1425,31 @@ Histogram::keyPressEvent(QKeyEvent* e) { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); - - if (e->key() == Qt::Key_Space) { - QKeyEvent* ev = new QKeyEvent(QEvent::KeyPress, Qt::Key_Space, Qt::NoModifier); - QCoreApplication::postEvent(parentWidget(),ev); - } else if (e->key() == Qt::Key_F) { + + Qt::KeyboardModifiers modifiers = e->modifiers(); + Qt::Key key = (Qt::Key)e->key(); + + bool accept = true; + + if (isKeybind(kShortcutGroupViewer, kShortcutIDActionFitViewer, modifiers, key)) { _imp->hasBeenModifiedSinceResize = false; _imp->zoomCtx.fill(0., 1., 0., 10.); computeHistogramAndRefresh(); } else { + accept = false; QGLWidget::keyPressEvent(e); } + if (accept) { + takeClickFocus(); + e->accept(); + } else { + handleUnCaughtKeyPressEvent(); + } } void Histogram::enterEvent(QEvent* e) { - QWidget* currentFocus = qApp->focusWidget(); - - bool canSetFocus = !currentFocus || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - currentFocus->objectName() == "Properties" || - currentFocus->objectName() == "SettingsPanel" || - currentFocus->objectName() == "qt_tabwidget_tabbar"; - - if (canSetFocus) { - setFocus(); - } + enterEventBase(); QGLWidget::enterEvent(e); } @@ -1462,7 +1458,7 @@ Histogram::leaveEvent(QEvent* e) { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); - + leaveEventBase(); _imp->drawCoordinates = false; QGLWidget::leaveEvent(e); } diff --git a/Gui/Histogram.h b/Gui/Histogram.h index 7cb63e2b21..debcbf00d4 100644 --- a/Gui/Histogram.h +++ b/Gui/Histogram.h @@ -37,8 +37,7 @@ CLANG_DIAG_ON(uninitialized) #include #include #endif - -#include "Engine/ScriptObject.h" +#include "Gui/PanelWidget.h" class QString; class QColor; @@ -52,7 +51,7 @@ class ViewerGL; struct HistogramPrivate; class Histogram : public QGLWidget - , public ScriptObject + , public PanelWidget { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT diff --git a/Gui/KnobGui.h b/Gui/KnobGui.h index 8d01dc78c6..9aa2f30836 100644 --- a/Gui/KnobGui.h +++ b/Gui/KnobGui.h @@ -209,6 +209,32 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON return (int)ret; } + + /*This function is used by KnobUndoCommand. Calling this in a onInternalValueChanged/valueChanged + signal/slot sequence can cause an infinite loop.*/ + template + void setValueAtTime(int dimension, + const T & v, + int time, + KeyFrame* newKey, + bool refreshGui, + Natron::ValueChangedReasonEnum reason) + { + + Knob* knob = dynamic_cast*>( getKnob().get() ); + assert(knob); + bool addedKey = false; + if (knob) { + addedKey = knob->setValueAtTime(time,v,dimension,reason,newKey); + } + if ((knob) && reason == Natron::eValueChangedReasonUserEdited) { + assert(newKey); + setKeyframeMarkerOnTimeline( newKey->getTime() ); + } + if (refreshGui) { + updateGUI(dimension); + } + } virtual void swapOpenGLBuffers() OVERRIDE FINAL; virtual void redraw() OVERRIDE FINAL; diff --git a/Gui/KnobGuiButton.cpp b/Gui/KnobGuiButton.cpp index 9f72591af5..567dc5cb1c 100644 --- a/Gui/KnobGuiButton.cpp +++ b/Gui/KnobGuiButton.cpp @@ -140,7 +140,9 @@ void KnobGuiButton::removeSpecificGui() void KnobGuiButton::emitValueChanged() { - _knob.lock()->evaluateValueChange(0, Natron::eValueChangedReasonUserEdited); + boost::shared_ptr k = _knob.lock(); + assert(k); + k->trigger(); } void diff --git a/Gui/KnobGuiColor.cpp b/Gui/KnobGuiColor.cpp index e551b77ea8..ce4975e6f3 100644 --- a/Gui/KnobGuiColor.cpp +++ b/Gui/KnobGuiColor.cpp @@ -421,7 +421,7 @@ KnobGuiColor::onSliderEditingFinished(bool hasMovedOnce) double v = _slider->getPosition(); onSliderValueChanged(v); } else if (autoProxyEnabled && hasMovedOnce) { - getGui()->renderAllViewers(); + getGui()->renderAllViewers(true); } } @@ -864,7 +864,7 @@ KnobGuiColor::showColorDialog() knob->endChanges(); } - knob->evaluateValueChange(0, eValueChangedReasonNatronGuiEdited); + knob->evaluateValueChange(0, knob->getCurrentTime(), eValueChangedReasonNatronGuiEdited); } // showColorDialog void diff --git a/Gui/KnobGuiDouble.cpp b/Gui/KnobGuiDouble.cpp index fafe3e2078..ea67d1355b 100644 --- a/Gui/KnobGuiDouble.cpp +++ b/Gui/KnobGuiDouble.cpp @@ -546,7 +546,7 @@ KnobGuiDouble::onSliderEditingFinished(bool hasMovedOnce) double v = _slider->getPosition(); sliderEditingEnd(v); } else if (autoProxyEnabled && hasMovedOnce) { - getGui()->renderAllViewers(); + getGui()->renderAllViewers(true); } } diff --git a/Gui/KnobGuiFile.cpp b/Gui/KnobGuiFile.cpp index 3a63c621e4..3a60a65e8b 100644 --- a/Gui/KnobGuiFile.cpp +++ b/Gui/KnobGuiFile.cpp @@ -162,7 +162,7 @@ KnobGuiFile::onReloadClicked() effect->purgeCaches(); effect->clearPersistentMessage(false); } - knob->evaluateValueChange(0, Natron::eValueChangedReasonNatronInternalEdited); + knob->evaluateValueChange(0, knob->getCurrentTime(), Natron::eValueChangedReasonNatronInternalEdited); } } @@ -303,7 +303,7 @@ KnobGuiFile::watchedFileChanged() } } else { - knob->evaluateValueChange(0, Natron::eValueChangedReasonNatronInternalEdited); + knob->evaluateValueChange(0, knob->getCurrentTime() , Natron::eValueChangedReasonNatronInternalEdited); } } diff --git a/Gui/KnobGuiGroup.cpp b/Gui/KnobGuiGroup.cpp index 3f74aed64a..cce1670775 100644 --- a/Gui/KnobGuiGroup.cpp +++ b/Gui/KnobGuiGroup.cpp @@ -176,20 +176,27 @@ KnobGuiGroup::createWidget(QHBoxLayout* layout) } void -KnobGuiGroup::setChecked(bool b) +KnobGuiGroup::setCheckedInternal(bool checked, bool userRequested) { - if (b == _checked) { + if (checked == _checked) { return; } - _checked = b; - + _checked = checked; + + if (userRequested) { + boost::shared_ptr knob = _knob.lock(); + if (knob) { + knob->evaluateValueChange(0, knob->getCurrentTime(), Natron::eValueChangedReasonUserEdited); + } + } + ///get the current index of the group knob in the layout, and reinsert ///the children back with an offset relative to the group. int realIndexInLayout = getActualIndexInLayout(); int startChildIndex = realIndexInLayout + 1; for (std::list::iterator it = _children.begin(); it != _children.end(); ++it) { - if (!b) { + if (!checked) { (*it)->hide(); } else if ( !(*it)->getKnob()->getIsSecret() ) { (*it)->show(startChildIndex); @@ -200,6 +207,12 @@ KnobGuiGroup::setChecked(bool b) } } +void +KnobGuiGroup::setChecked(bool b) +{ + setCheckedInternal(b, true); +} + bool KnobGuiGroup::eventFilter(QObject */*target*/, QEvent* /*event*/) @@ -217,7 +230,7 @@ KnobGuiGroup::updateGUI(int /*dimension*/) { bool b = _knob.lock()->getValue(0); - setChecked(b); + setCheckedInternal(b,false); if (_button) { _button->setChecked(b); } diff --git a/Gui/KnobGuiGroup.h b/Gui/KnobGuiGroup.h index e7e7b22085..265c0e16e6 100644 --- a/Gui/KnobGuiGroup.h +++ b/Gui/KnobGuiGroup.h @@ -130,6 +130,9 @@ public Q_SLOTS: void setChecked(bool b); private: + + void setCheckedInternal(bool checked, bool userRequested); + virtual void createWidget(QHBoxLayout* layout) OVERRIDE FINAL; virtual void _hide() OVERRIDE FINAL; virtual void _show() OVERRIDE FINAL; diff --git a/Gui/KnobGuiInt.cpp b/Gui/KnobGuiInt.cpp index 80d136bbed..8e24cabc7b 100644 --- a/Gui/KnobGuiInt.cpp +++ b/Gui/KnobGuiInt.cpp @@ -461,7 +461,7 @@ KnobGuiInt::onSliderEditingFinished(bool hasMovedOnce) double v = _slider->getPosition(); sliderEditingEnd(v); } else if (autoProxyEnabled && hasMovedOnce) { - getGui()->renderAllViewers(); + getGui()->renderAllViewers(true); } } diff --git a/Gui/KnobUndoCommand.cpp b/Gui/KnobUndoCommand.cpp index e862681c74..67c1ca6ea8 100644 --- a/Gui/KnobUndoCommand.cpp +++ b/Gui/KnobUndoCommand.cpp @@ -27,6 +27,8 @@ #include "Engine/KnobTypes.h" #include "Engine/KnobFile.h" #include "Engine/Node.h" +#include "Engine/TimeLine.h" +#include "Engine/AppInstance.h" #include "Gui/GuiApplicationManager.h" PasteUndoCommand::PasteUndoCommand(KnobGui* knob, @@ -329,8 +331,9 @@ MultipleKnobEditsUndoCommand::undo() if (holder) { holder->beginChanges(); + int time = holder->getCurrentTime(); for (std::set ::iterator it = knobsUnique.begin(); it != knobsUnique.end(); ++it) { - (*it)->evaluateValueChange(0, _reason); + (*it)->evaluateValueChange(0,time, _reason); } holder->endChanges(); Natron::EffectInstance* effect = dynamic_cast(holder); @@ -352,6 +355,7 @@ MultipleKnobEditsUndoCommand::redo() if (firstRedoCalled) { ///just clone std::set knobsUnique; + int time = holder->getCurrentTime(); for (ParamsMap::iterator it = knobs.begin(); it != knobs.end(); ++it) { boost::shared_ptr originalKnob = it->first->getKnob(); boost::shared_ptr copyWithOldValues = createCopyForKnob(originalKnob); @@ -366,7 +370,7 @@ MultipleKnobEditsUndoCommand::redo() for (std::set ::iterator it = knobsUnique.begin(); it != knobsUnique.end(); ++it) { - (*it)->evaluateValueChange(0, _reason); + (*it)->evaluateValueChange(0, time, _reason); } } } else { @@ -378,20 +382,35 @@ MultipleKnobEditsUndoCommand::redo() Knob* isBool = dynamic_cast*>( knob.get() ); Knob* isDouble = dynamic_cast*>( knob.get() ); Knob* isString = dynamic_cast*>( knob.get() ); - if (isInt) { - it->first->setValue(it->second.dimension, it->second.newValue.toInt(), &k,true,_reason); - } else if (isBool) { - it->first->setValue(it->second.dimension, it->second.newValue.toBool(), &k,true,_reason); - } else if (isDouble) { - it->first->setValue(it->second.dimension, it->second.newValue.toDouble(), &k,true,_reason); - } else if (isString) { - it->first->setValue(it->second.dimension, it->second.newValue.toString().toStdString(), - &k,true,_reason); - } else { - assert(false); - } + + if (it->second.setKeyFrame) { - it->first->setKeyframe(it->second.time, it->second.dimension); + bool refreshGui = it->second.time == knob->getHolder()->getApp()->getTimeLine()->currentFrame(); + if (isInt) { + it->first->setValueAtTime(it->second.dimension, it->second.newValue.toInt(), it->second.time, &k,refreshGui,_reason); + } else if (isBool) { + it->first->setValueAtTime(it->second.dimension, it->second.newValue.toBool(), it->second.time, &k,refreshGui,_reason); + } else if (isDouble) { + it->first->setValueAtTime(it->second.dimension,it->second.newValue.toDouble(), it->second.time, &k,refreshGui,_reason); + } else if (isString) { + it->first->setValueAtTime(it->second.dimension, it->second.newValue.toString().toStdString(), it->second.time, + &k,refreshGui,_reason); + } else { + assert(false); + } + } else { + if (isInt) { + it->first->setValue(it->second.dimension, it->second.newValue.toInt(), &k,true,_reason); + } else if (isBool) { + it->first->setValue(it->second.dimension, it->second.newValue.toBool(), &k,true,_reason); + } else if (isDouble) { + it->first->setValue(it->second.dimension, it->second.newValue.toDouble(), &k,true,_reason); + } else if (isString) { + it->first->setValue(it->second.dimension, it->second.newValue.toString().toStdString(), + &k,true,_reason); + } else { + assert(false); + } } } } @@ -512,6 +531,7 @@ RestoreDefaultsCommand::redo() KnobHolder* holder = first->getHolder(); if (holder && holder->getApp()) { timeline = holder->getApp()->getTimeLine(); + holder->beginChanges(); } for (std::list >::iterator it = _knobs.begin(); it != _knobs.end(); ++it) { @@ -531,13 +551,39 @@ RestoreDefaultsCommand::redo() if ((*it)->getHolder()) { (*it)->getHolder()->beginChanges(); } + (*it)->blockValueChanges(); + for (int d = 0; d < (*it)->getDimension(); ++d) { (*it)->resetToDefaultValue(d); } + + (*it)->unblockValueChanges(); + if ((*it)->getHolder()) { (*it)->getHolder()->endChanges(true); } + } + + /* + Block value changes and call instanceChange on all knobs afterwards to put back the plug-in + in a correct state + */ + int time = 0; + if (timeline) { + time = timeline->currentFrame(); + } + for (std::list >::iterator it = _knobs.begin(); it != _knobs.end(); ++it) { + if ((*it)->getHolder()) { + (*it)->getHolder()->onKnobValueChanged_public(it->get(), Natron::eValueChangedReasonRestoreDefault, time, true); + } + } + + if (holder && holder->getApp()) { + holder->endChanges(); + } + + if (timeline) { timeline->removeMultipleKeyframeIndicator(times,true); } @@ -583,7 +629,7 @@ SetExpressionCommand::undo() } } - _knob->evaluateValueChange(_dimension == -1 ? 0 : _dimension, Natron::eValueChangedReasonNatronGuiEdited); + _knob->evaluateValueChange(_dimension == -1 ? 0 : _dimension, _knob->getCurrentTime(), Natron::eValueChangedReasonNatronGuiEdited); setText( QObject::tr("Set expression") ); } @@ -606,6 +652,6 @@ SetExpressionCommand::redo() Natron::errorDialog(QObject::tr("Expression").toStdString(), QObject::tr("The expression is invalid").toStdString()); } } - _knob->evaluateValueChange(_dimension == -1 ? 0 : _dimension, Natron::eValueChangedReasonNatronGuiEdited); + _knob->evaluateValueChange(_dimension == -1 ? 0 : _dimension, _knob->getCurrentTime(), Natron::eValueChangedReasonNatronGuiEdited); setText( QObject::tr("Set expression") ); } \ No newline at end of file diff --git a/Gui/MultiInstancePanel.cpp b/Gui/MultiInstancePanel.cpp index 835909bea5..27eb99150c 100644 --- a/Gui/MultiInstancePanel.cpp +++ b/Gui/MultiInstancePanel.cpp @@ -517,7 +517,7 @@ MultiInstancePanel::createMultiInstanceGui(QVBoxLayout* layout) _imp->view->setColumnCount( dimensionNames.size() ); _imp->view->setHorizontalHeaderLabels(dimensionNames); - _imp->view->setAttribute(Qt::WA_MacShowFocusRect,0); + _imp->view->setAttribute(Qt::WA_MacShowFocusRect,1); _imp->view->setUniformRowHeights(true); #if QT_VERSION < 0x050000 diff --git a/Gui/NodeGraph.cpp b/Gui/NodeGraph.cpp index ad14971c49..0ba8279300 100644 --- a/Gui/NodeGraph.cpp +++ b/Gui/NodeGraph.cpp @@ -74,8 +74,8 @@ NodeGraph::NodeGraph(Gui* gui, QWidget *parent) : QGraphicsView(scene,parent) , NodeGraphI() - , ScriptObject() - , _imp( new NodeGraphPrivate(gui,this, group) ) + , PanelWidget(this,gui) + , _imp( new NodeGraphPrivate(this, group) ) { group->setNodeGraphPointer(this); @@ -139,7 +139,7 @@ NodeGraph::NodeGraph(Gui* gui, _imp->_undoStack = new QUndoStack(this); _imp->_undoStack->setUndoLimit( appPTR->getCurrentSettings()->getMaximumUndoRedoNodeGraph() ); - _imp->_gui->registerNewUndoStack(_imp->_undoStack); + getGui()->registerNewUndoStack(_imp->_undoStack); _imp->_hintInputEdge = new Edge(0,0,boost::shared_ptr(),_imp->_nodeRoot); _imp->_hintInputEdge->setDefaultColor( QColor(0,255,0,100) ); @@ -176,11 +176,7 @@ NodeGraph::NodeGraph(Gui* gui, setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _imp->_menu = new Natron::Menu(this); - //_imp->_menu->setFont( QFont(appFont,appFontSize) ); - - boost::shared_ptr timeline = _imp->_gui->getApp()->getTimeLine(); - QObject::connect( timeline.get(),SIGNAL( frameChanged(SequenceTime,int) ), this,SLOT( onTimeChanged(SequenceTime,int) ) ); - QObject::connect( timeline.get(),SIGNAL( frameAboutToChange() ), this,SLOT( onTimelineTimeAboutToChange() ) ); + } NodeGraph::~NodeGraph() @@ -196,7 +192,7 @@ NodeGraph::~NodeGraph() (*it)->discardGraphPointer(); } - if (_imp->_gui) { + if (getGui()) { QGraphicsScene* scene = _imp->_hintInputEdge->scene(); if (scene) { scene->removeItem(_imp->_hintInputEdge); @@ -236,16 +232,10 @@ NodeGraph::getRootItem() const return _imp->_root; } -Gui* -NodeGraph::getGui() const -{ - return _imp->_gui; -} void -NodeGraph::discardGuiPointer() +NodeGraph::notifyGuiClosing() { - _imp->_gui = 0; boost::shared_ptr group = getGroup(); if (group) { group->discardNodeGraphPointer(); @@ -293,6 +283,18 @@ NodeGraph::paintEvent(QPaintEvent* e) if (app && app->getProject()->isLoadingProjectInternal()) { return; } + + boost::shared_ptr collection = getGroup(); + NodeGroup* isGroup = dynamic_cast(collection.get()); + bool isGroupEditable = true; + bool groupEdited = true; + if (isGroup) { + isGroupEditable = isGroup->isSubGraphEditable(); + groupEdited = isGroup->getNode()->hasPyPlugBeenEdited(); + } + + bool drawLockedMode = !isGroupEditable || !groupEdited; + if (_imp->_refreshOverlays) { ///The visible portion of the scene, in scene coordinates QRectF visibleScene = visibleSceneRect(); @@ -312,6 +314,31 @@ NodeGraph::paintEvent(QPaintEvent* e) _imp->_refreshOverlays = false; } QGraphicsView::paintEvent(e); + + if (drawLockedMode) { + ///Show a semi-opaque forground indicating the PyPlug has not been edited + QPainter p(this); + p.setBrush(QColor(120,120,120)); + p.setOpacity(0.7); + p.drawRect(rect()); + + if (isGroupEditable) { + ///Draw the unlock icon + QPoint pixPos = _imp->getPyPlugUnlockPos(); + int pixW = _imp->unlockIcon.width(); + int pixH = _imp->unlockIcon.height(); + QRect pixRect(pixPos.x(),pixPos.y(),pixW,pixH); + pixRect.adjust(-2, -2, 2, 2); + QRect selRect(pixPos.x(),pixPos.y(),pixW,pixH); + pixRect.adjust(-3, -3, 3, 3); + p.setBrush(QColor(243,137,0)); + p.setOpacity(1.); + p.drawRoundedRect(selRect,5,5); + p.setBrush(QColor(50,50,50)); + p.drawRoundedRect(pixRect,5,5); + p.drawPixmap(pixPos.x(), pixPos.y(), pixW, pixH, _imp->unlockIcon, 0, 0, pixW, pixH); + } + } } QRectF @@ -329,10 +356,7 @@ NodeGraph::visibleWidgetRect() const boost::shared_ptr NodeGraph::createNodeGUI(const boost::shared_ptr & node, bool requestedByLoad, - double xPosHint, - double yPosHint, - bool pushUndoRedoCommand, - bool autoConnect) + bool pushUndoRedoCommand) { boost::shared_ptr node_ui; Dot* isDot = dynamic_cast( node->getLiveInstance() ); @@ -382,20 +406,7 @@ NodeGraph::createNodeGUI(const boost::shared_ptr & node, QMutexLocker l(&_imp->_nodesMutex); _imp->_nodes.push_back(node_ui); } - ///only move main instances - if ( node->getParentMultiInstanceName().empty() ) { - if (_imp->_selection.empty()) { - autoConnect = false; - } - if ( (xPosHint != INT_MIN) && (yPosHint != INT_MIN) && !autoConnect ) { - QPointF pos = node_ui->mapToParent( node_ui->mapFromScene( QPointF(xPosHint,yPosHint) ) ); - node_ui->refreshPosition( pos.x(),pos.y(), true ); - } else { - if (!isBd && !isGrp) { - moveNodesForIdealPosition(node_ui,autoConnect); - } - } - } + if (!requestedByLoad && (!getGui()->getApp()->isCreatingPythonGroup() || dynamic_cast(node->getLiveInstance()))) { node_ui->ensurePanelCreated(); @@ -405,7 +416,7 @@ NodeGraph::createNodeGUI(const boost::shared_ptr & node, boost::shared_ptr nodeStack = node_ui->getUndoStack(); if (nodeStack) { - _imp->_gui->registerNewUndoStack(nodeStack.get()); + getGui()->registerNewUndoStack(nodeStack.get()); } if (pushUndoRedoCommand) { diff --git a/Gui/NodeGraph.h b/Gui/NodeGraph.h index 20e9e35834..16b3718f81 100644 --- a/Gui/NodeGraph.h +++ b/Gui/NodeGraph.h @@ -42,8 +42,8 @@ CLANG_DIAG_ON(uninitialized) #include "Engine/NodeGraphI.h" -#include "Engine/ScriptObject.h" #include "Global/GlobalDefines.h" +#include "Gui/PanelWidget.h" class QVBoxLayout; class QScrollArea; @@ -65,7 +65,7 @@ namespace Natron { class Node; } -class NodeGraph : public QGraphicsView, public NodeGraphI, public ScriptObject, public boost::noncopyable +class NodeGraph : public QGraphicsView, public NodeGraphI, public PanelWidget, public boost::noncopyable { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -83,8 +83,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON boost::shared_ptr getGroup() const; const std::list< boost::shared_ptr > & getSelectedNodes() const; - boost::shared_ptr createNodeGUI(const boost::shared_ptr & node,bool requestedByLoad, - double xPosHint,double yPosHint,bool pushUndoRedoCommand,bool autoConnect); + boost::shared_ptr createNodeGUI(const boost::shared_ptr & node,bool requestedByLoad,bool pushUndoRedoCommand); void selectNode(const boost::shared_ptr & n,bool addToSelection); @@ -117,9 +116,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void restoreFromTrash(NodeGui* node); QGraphicsItem* getRootItem() const; - Gui* getGui() const; - void discardGuiPointer(); + virtual void notifyGuiClosing() OVERRIDE FINAL; void discardScenePointer(); @@ -167,7 +165,9 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON * It will move the inputs / outputs slightly to fit this node into the nodegraph * so they do not overlap. **/ - void moveNodesForIdealPosition(boost::shared_ptr n,bool autoConnect); + void moveNodesForIdealPosition(const boost::shared_ptr &n, + const boost::shared_ptr& selected, + bool autoConnect); void copyNodes(const std::list >& nodes,NodeClipBoard& clipboard); @@ -226,12 +226,7 @@ public Q_SLOTS: void toggleConnectionHints(); void toggleAutoHideInputs(bool setSettings = true); - - void onTimelineTimeAboutToChange(); - - ///Called whenever the time changes on the timeline - void onTimeChanged(SequenceTime time,int reason); - + void onGuiFrozenChanged(bool frozen); void onNodeCreationDialogFinished(); diff --git a/Gui/NodeGraph05.cpp b/Gui/NodeGraph05.cpp index 13338fc6ea..a5aeb49f0b 100644 --- a/Gui/NodeGraph05.cpp +++ b/Gui/NodeGraph05.cpp @@ -47,7 +47,9 @@ using namespace Natron; void -NodeGraph::moveNodesForIdealPosition(boost::shared_ptr node,bool autoConnect) +NodeGraph::moveNodesForIdealPosition(const boost::shared_ptr &node, + const boost::shared_ptr &selected, + bool autoConnect) { BackDropGui* isBd = dynamic_cast(node.get()); if (isBd) { @@ -61,15 +63,6 @@ NodeGraph::moveNodesForIdealPosition(boost::shared_ptr node,bool autoCo /// 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; - boost::shared_ptr selected; - - if (_imp->_selection.size() == 1) { - selected = _imp->_selection.front(); - BackDropGui* isBd = dynamic_cast(selected.get()); - if (isBd) { - selected.reset(); - } - } if (!selected || !autoConnect) { behavior = 0; @@ -239,7 +232,7 @@ NodeGraph::moveNodesForIdealPosition(boost::shared_ptr node,bool autoCo QString(), CreateNodeArgs::DefaultValuesList(), createdNodeInternal->getGroup()); - boost::shared_ptr dotNode = _imp->_gui->getApp()->createNode(args); + boost::shared_ptr dotNode = getGui()->getApp()->createNode(args); assert(dotNode); boost::shared_ptr dotNodeGui_i = dotNode->getNodeGui(); assert(dotNodeGui_i); @@ -354,7 +347,7 @@ NodeGraph::moveNodesForIdealPosition(boost::shared_ptr node,bool autoCo QString(), CreateNodeArgs::DefaultValuesList(), createdNodeInternal->getGroup()); - boost::shared_ptr dotNode = _imp->_gui->getApp()->createNode(args); + boost::shared_ptr dotNode = getGui()->getApp()->createNode(args); assert(dotNode); boost::shared_ptr dotNodeGui_i = dotNode->getNodeGui(); assert(dotNodeGui_i); diff --git a/Gui/NodeGraph10.cpp b/Gui/NodeGraph10.cpp index 561a20db3f..f37762a205 100644 --- a/Gui/NodeGraph10.cpp +++ b/Gui/NodeGraph10.cpp @@ -46,6 +46,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/GuiAppInstance.h" #include "Gui/GuiMacros.h" #include "Gui/NodeGui.h" +#include "Gui/NodeSettingsPanel.h" #include "Global/QtCompat.h" @@ -56,6 +57,9 @@ void NodeGraph::mousePressEvent(QMouseEvent* e) { assert(e); + + takeClickFocus(); + _imp->_hasMovedOnce = false; _imp->_deltaSinceMousePress = QPointF(0,0); if ( buttonDownIsMiddle(e) ) { @@ -63,7 +67,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) return; } - + bool didSomething = false; _imp->_lastMousePos = e->pos(); @@ -78,6 +82,45 @@ NodeGraph::mousePressEvent(QMouseEvent* e) return; } + + boost::shared_ptr collection = getGroup(); + NodeGroup* isGroup = dynamic_cast(collection.get()); + bool isGroupEditable = true; + bool groupEdited = true; + if (isGroup) { + isGroupEditable = isGroup->isSubGraphEditable(); + groupEdited = isGroup->getNode()->hasPyPlugBeenEdited(); + } + + if (!groupEdited) { + + if (isGroupEditable) { + ///check if user clicked unlock + int iw = _imp->unlockIcon.width(); + int ih = _imp->unlockIcon.height(); + int w = width(); + int offset = 20; + if (e->x() >= (w - iw - 10 - offset) && e->x() <= (w - 10 + offset) && + e->y() >= (10 - offset) && e->y() <= (10 + ih + offset)) { + assert(isGroup); + isGroup->getNode()->setPyPlugEdited(true); + NodeList nodes = isGroup->getNodes(); + for (NodeList::iterator it = nodes.begin(); it!=nodes.end(); ++it) { + boost::shared_ptr nodeUi = boost::dynamic_pointer_cast((*it)->getNodeGui()); + if (nodeUi) { + NodeSettingsPanel* panel = nodeUi->getSettingPanel(); + if (panel) { + panel->setEnabled(true); + } + } + } + } + getGui()->getApp()->triggerAutoSave(); + update(); + } + } + + NodeGuiPtr selected; Edge* selectedEdge = 0; Edge* selectedBendPoint = 0; @@ -95,7 +138,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) if (isBd) { if (isBd->isNearbyNameFrame(evpt)) { matches.insert(std::make_pair((*it)->zValue(),*it)); - } else if (isBd->isNearbyResizeHandle(evpt)) { + } else if (isBd->isNearbyResizeHandle(evpt) && groupEdited) { selected = *it; _imp->_backdropResized = *it; _imp->_evtState = eEventStateResizingBackdrop; @@ -130,6 +173,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) } } + if (selected) { didSomething = true; if ( buttonDownIsLeft(e) ) { @@ -149,7 +193,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) _imp->_selection.erase(it); } } - if (_imp->_evtState != eEventStateResizingBackdrop) { + if (groupEdited && _imp->_evtState != eEventStateResizingBackdrop) { _imp->_evtState = eEventStateDraggingNode; } ///build the _nodesWithinBDAtPenDown map @@ -168,7 +212,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) selectNode(selected,true); ///< don't wipe the selection } } - } else if (selectedBendPoint) { + } else if (selectedBendPoint && groupEdited) { _imp->setNodesBendPointsVisible(false); ///Now connect the node to the edge input @@ -195,7 +239,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) QString(), CreateNodeArgs::DefaultValuesList(), _imp->group.lock()); - boost::shared_ptr dotNode = _imp->_gui->getApp()->createNode(args); + boost::shared_ptr dotNode = getGui()->getApp()->createNode(args); assert(dotNode); boost::shared_ptr dotNodeGui_i = dotNode->getNodeGui(); boost::shared_ptr dotNodeGui = boost::dynamic_pointer_cast(dotNodeGui_i); @@ -207,8 +251,8 @@ NodeGraph::mousePressEvent(QMouseEvent* e) - assert(_imp->_gui); - GuiAppInstance* guiApp = _imp->_gui->getApp(); + assert(getGui()); + GuiAppInstance* guiApp = getGui()->getApp(); assert(guiApp); boost::shared_ptr project = guiApp->getProject(); assert(project); @@ -239,7 +283,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) _imp->_evtState = eEventStateDraggingNode; _imp->_lastNodeDragStartPoint = dotNodeGui->getPos_mt_safe(); didSomething = true; - } else if (selectedEdge) { + } else if (selectedEdge && groupEdited) { _imp->_arrowSelected = selectedEdge; didSomething = true; _imp->_evtState = eEventStateDraggingArrow; @@ -263,7 +307,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) _imp->_backdropResized.reset(); } - if ( buttonDownIsRight(e) ) { + if ( buttonDownIsRight(e) && groupEdited) { showMenu( mapToGlobal( e->pos() ) ); didSomething = true; } diff --git a/Gui/NodeGraph15.cpp b/Gui/NodeGraph15.cpp index ddd47840e6..33c2227227 100644 --- a/Gui/NodeGraph15.cpp +++ b/Gui/NodeGraph15.cpp @@ -232,32 +232,9 @@ NodeGraph::mouseReleaseEvent(QMouseEvent* e) _imp->_highLightedEdge->getSource() ) ); } } else { - boost::shared_ptr src = _imp->_highLightedEdge->getSource(); - pushUndoCommand( new ConnectCommand(this,_imp->_highLightedEdge,_imp->_highLightedEdge->getSource(), - selectedNode) ); - - ///find out if the node is already connected to what the edge is connected - bool alreadyConnected = false; - const std::vector > & inpNodes = selectedNode->getNode()->getGuiInputs(); - if (src) { - for (U32 i = 0; i < inpNodes.size(); ++i) { - if ( inpNodes[i] == src->getNode() ) { - alreadyConnected = true; - break; - } - } - } - - if (src && !alreadyConnected) { - ///push a second command... this is a bit dirty but I don't have time to add a whole new command just for this - int prefInput = selectedNode->getNode()->getPreferredInputForConnection(); - if (prefInput != -1) { - Edge* inputEdge = selectedNode->getInputArrow(prefInput); - assert(inputEdge); - pushUndoCommand( new ConnectCommand(this,inputEdge,inputEdge->getSource(),src) ); - } - } - } + + pushUndoCommand(new InsertNodeCommand(this, _imp->_highLightedEdge, selectedNode)); + } // if ( _imp->_highLightedEdge->isOutputEdge() ) _imp->_highLightedEdge = 0; _imp->_hintInputEdge->hide(); diff --git a/Gui/NodeGraph20.cpp b/Gui/NodeGraph20.cpp index 892457b7df..adf93cbb18 100644 --- a/Gui/NodeGraph20.cpp +++ b/Gui/NodeGraph20.cpp @@ -31,12 +31,14 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF CLANG_DIAG_OFF(deprecated) CLANG_DIAG_OFF(uninitialized) #include +#include CLANG_DIAG_ON(deprecated) CLANG_DIAG_ON(uninitialized) GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Engine/EffectInstance.h" #include "Engine/Node.h" +#include "Engine/NodeGroup.h" #include "Engine/Settings.h" #include "Gui/BackDropGui.h" @@ -44,6 +46,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/GuiApplicationManager.h" #include "Gui/GuiMacros.h" #include "Gui/NodeGui.h" +#include "Gui/Utils.h" #include "Global/QtCompat.h" @@ -64,8 +67,30 @@ NodeGraph::mouseMoveEvent(QMouseEvent* e) bool mustUpdate = true; + boost::shared_ptr collection = getGroup(); + NodeGroup* isGroup = dynamic_cast(collection.get()); + bool isGroupEditable = true; + bool groupEdited = true; + if (isGroup) { + isGroupEditable = isGroup->isSubGraphEditable(); + groupEdited = isGroup->getNode()->hasPyPlugBeenEdited(); + } + if (!groupEdited && isGroupEditable) { + ///check if user is nearby unlock + int iw = _imp->unlockIcon.width(); + int ih = _imp->unlockIcon.height(); + int w = width(); + if (e->x() >= (w - iw - 10 - 15) && e->x() <= (w - 10 + 15) && + e->y() >= (10 - 15) && e->y() <= (10 + ih + 15)) { + assert(isGroup); + QPoint pos = mapToGlobal(e->pos()); + QToolTip::showText(pos, Natron::convertFromPlainText(QObject::tr("Clicking the unlock button will convert the PyPlug to a regular group saved in the project and dettach it from the script.\nAny modification will not be written to the Python script. Subsequent loading of the project will no longer load this group from the python script."),Qt::WhiteSpaceNormal)); + } + + } + QRectF sceneR = visibleSceneRect(); - if (_imp->_evtState != eEventStateSelectionRect && _imp->_evtState != eEventStateDraggingArrow) { + if (groupEdited && _imp->_evtState != eEventStateSelectionRect && _imp->_evtState != eEventStateDraggingArrow) { ///set cursor boost::shared_ptr selected; Edge* selectedEdge = 0; @@ -228,7 +253,7 @@ NodeGraph::mouseMoveEvent(QMouseEvent* e) boost::shared_ptr nodeToShowMergeRect; boost::shared_ptr selectedNodeInternalNode = selectedNode->getNode(); - bool selectedNodeIsReader = selectedNodeInternalNode->getLiveInstance()->isReader(); + bool selectedNodeIsReader = selectedNodeInternalNode->getLiveInstance()->isReader() || selectedNodeInternalNode->getMaxInputCount() == 0; Edge* edge = 0; { QMutexLocker l(&_imp->_nodesMutex); @@ -312,7 +337,8 @@ NodeGraph::mouseMoveEvent(QMouseEvent* e) if ( edge && !edge->isOutputEdge() ) { - if ((*it)->getNode()->getLiveInstance()->isReader()) { + if ((*it)->getNode()->getLiveInstance()->isReader() || + (*it)->getNode()->getMaxInputCount() == 0) { edge = 0; continue; } @@ -324,7 +350,7 @@ NodeGraph::mouseMoveEvent(QMouseEvent* e) Natron::Node::CanConnectInputReturnValue ret = edge->getDest()->getNode()->canConnectInput(selectedNodeInternalNode, edge->getInputNumber()); if (ret == Natron::Node::eCanConnectInput_inputAlreadyConnected && - !selectedNodeInternalNode->getLiveInstance()->isReader()) { + !selectedNodeIsReader) { ret = Natron::Node::eCanConnectInput_ok; } diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index d259f33d50..9cb0f6cec1 100644 --- a/Gui/NodeGraph25.cpp +++ b/Gui/NodeGraph25.cpp @@ -86,7 +86,7 @@ NodeGraph::mouseDoubleClickEvent(QMouseEvent* e) } else { node->setVisibleSettingsPanel(true); if (node->getSettingPanel()) { - _imp->_gui->putSettingsPanelFirst( node->getSettingPanel() ); + getGui()->putSettingsPanelFirst( node->getSettingPanel() ); } else { ViewerInstance* isViewer = dynamic_cast(node->getNode()->getLiveInstance()); if (isViewer) { @@ -131,7 +131,7 @@ NodeGraph::mouseDoubleClickEvent(QMouseEvent* e) if (modCASIsShift(e)) { NodeGroup* isGrp = dynamic_cast(node->getNode()->getLiveInstance()); - if (isGrp && isGrp->isSubGraphEditable()) { + if (isGrp) { NodeGraphI* graph_i = isGrp->getNodeGraph(); assert(graph_i); NodeGraph* graph = dynamic_cast(graph_i); @@ -140,7 +140,7 @@ NodeGraph::mouseDoubleClickEvent(QMouseEvent* e) if (isParentTab) { isParentTab->setCurrentWidget(graph); } else { - NodeGraph* lastSelectedGraph = _imp->_gui->getLastSelectedGraph(); + NodeGraph* lastSelectedGraph = getGui()->getLastSelectedGraph(); ///We're in the double click event, it should've entered the focus in event beforehand! assert(lastSelectedGraph == this); @@ -162,7 +162,7 @@ NodeGraph::mouseDoubleClickEvent(QMouseEvent* e) bool NodeGraph::event(QEvent* e) { - if (!_imp->_gui) { + if (!getGui()) { return false; } if (e->type() == QEvent::KeyPress) { @@ -177,7 +177,7 @@ NodeGraph::event(QEvent* e) QObject::connect( nodeCreation,SIGNAL( rejected() ),this,SLOT( onNodeCreationDialogFinished() ) ); nodeCreation->show(); - + takeClickFocus(); ke->accept(); return true; @@ -232,20 +232,28 @@ NodeGraph::onNodeCreationDialogFinished() void NodeGraph::keyPressEvent(QKeyEvent* e) { + boost::shared_ptr collection = getGroup(); + NodeGroup* isGroup = dynamic_cast(collection.get()); + bool isGroupEditable = true; + bool groupEdited = true; + if (isGroup) { + isGroupEditable = isGroup->isSubGraphEditable(); + groupEdited = isGroup->getNode()->hasPyPlugBeenEdited(); + } + + if (!groupEdited) { + return; + } + Qt::KeyboardModifiers modifiers = e->modifiers(); Qt::Key key = (Qt::Key)e->key(); - if (key == Qt::Key_Escape) { - return QGraphicsView::keyPressEvent(e); - } + bool accept = true; - if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionShowPaneFullScreen, modifiers, key) ) { - QKeyEvent* ev = new QKeyEvent(QEvent::KeyPress, key, modifiers); - QCoreApplication::postEvent(parentWidget(),ev); - } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphCreateReader, modifiers, key) ) { - _imp->_gui->createReader(); + if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphCreateReader, modifiers, key) ) { + getGui()->createReader(); } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphCreateWriter, modifiers, key) ) { - _imp->_gui->createWriter(); + getGui()->createWriter(); } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphRemoveNodes, modifiers, key) ) { deleteSelection(); } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphForcePreview, modifiers, key) ) { @@ -277,8 +285,9 @@ NodeGraph::keyPressEvent(QKeyEvent* e) createGroupFromSelection(); } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphExpandGroup, modifiers, key) ) { expandSelectedGroups(); - } else if (key == Qt::Key_Control) { + } else if (key == Qt::Key_Control && e->modifiers() == Qt::ControlModifier) { _imp->setNodesBendPointsVisible(true); + accept = false; } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphSelectUp, modifiers, key) || isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphNavigateUpstream, modifiers, key) ) { ///We try to find if the last selected node has an input, if so move selection (or add to selection) @@ -426,9 +435,16 @@ NodeGraph::keyPressEvent(QKeyEvent* e) if (!intercepted) { + accept = false; QGraphicsView::keyPressEvent(e); } } + if (accept) { + takeClickFocus(); + e->accept(); + } else { + handleUnCaughtKeyPressEvent(); + } } // keyPressEvent void diff --git a/Gui/NodeGraph30.cpp b/Gui/NodeGraph30.cpp index bf033943f7..d45768ea1c 100644 --- a/Gui/NodeGraph30.cpp +++ b/Gui/NodeGraph30.cpp @@ -33,6 +33,8 @@ CLANG_DIAG_OFF(uninitialized) #include #include #include +#include +#include GCC_DIAG_UNUSED_PRIVATE_FIELD_ON CLANG_DIAG_ON(deprecated) CLANG_DIAG_ON(uninitialized) @@ -61,8 +63,32 @@ using namespace Natron; void NodeGraph::connectCurrentViewerToSelection(int inputNB) { - if ( !getLastSelectedViewer() ) { - _imp->_gui->getApp()->createNode( CreateNodeArgs(PLUGINID_NATRON_VIEWER, + ViewerTab* lastUsedViewer = getLastSelectedViewer(); + + if (lastUsedViewer) { + boost::shared_ptr 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& tabs = getGui()->getViewersList(); + lastUsedViewer = 0; + for (std::list::const_iterator it = tabs.begin(); it!=tabs.end(); ++it) { + + boost::shared_ptr otherCollection = (*it)->getInternalNode()->getNode()->getGroup(); + if (otherCollection && otherCollection->getNodeGraph() == this) { + lastUsedViewer = *it; + break; + } + } + } + } + + + boost::shared_ptr v; + if ( lastUsedViewer ) { + v = boost::dynamic_pointer_cast( lastUsedViewer-> + getInternalNode()->getNode() ); + } else { + NodePtr viewerNode = getGui()->getApp()->createNode( CreateNodeArgs(PLUGINID_NATRON_VIEWER, "", -1,-1, true, @@ -73,12 +99,16 @@ NodeGraph::connectCurrentViewerToSelection(int inputNB) QString(), CreateNodeArgs::DefaultValuesList(), getGroup()) ); + if (!viewerNode) { + return; + } + v = boost::dynamic_pointer_cast(viewerNode); } - ///get a pointer to the last user selected viewer - boost::shared_ptr v = boost::dynamic_pointer_cast( getLastSelectedViewer()-> - getInternalNode()->getNode() ); - + 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; @@ -120,24 +150,8 @@ NodeGraph::connectCurrentViewerToSelection(int inputNB) void NodeGraph::enterEvent(QEvent* e) { + enterEventBase(); QGraphicsView::enterEvent(e); - - QWidget* currentFocus = qApp->focusWidget(); - - bool canSetFocus = !currentFocus || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - currentFocus->objectName() == "Properties" || - currentFocus->objectName() == "SettingsPanel" || - currentFocus->objectName() == "qt_tabwidget_tabbar"; - - if (canSetFocus) { - setFocus(); - } - _imp->_nodeCreationShortcutEnabled = true; } @@ -145,6 +159,7 @@ NodeGraph::enterEvent(QEvent* e) void NodeGraph::leaveEvent(QEvent* e) { + leaveEventBase(); QGraphicsView::leaveEvent(e); _imp->_nodeCreationShortcutEnabled = false; @@ -401,7 +416,7 @@ NodeGraph::selectNode(const boost::shared_ptr & n, ViewerInstance* isViewer = dynamic_cast( n->getNode()->getLiveInstance() ); if (isViewer) { OpenGLViewerI* viewer = isViewer->getUiContext(); - const std::list & viewerTabs = _imp->_gui->getViewersList(); + const std::list & viewerTabs = getGui()->getViewersList(); for (std::list::const_iterator it = viewerTabs.begin(); it != viewerTabs.end(); ++it) { if ( (*it)->getViewer() == viewer ) { setLastSelectedViewer( (*it) ); diff --git a/Gui/NodeGraph35.cpp b/Gui/NodeGraph35.cpp index 80a60115df..ea8533c34f 100644 --- a/Gui/NodeGraph35.cpp +++ b/Gui/NodeGraph35.cpp @@ -288,13 +288,13 @@ NodeGraph::toggleKnobLinksVisible() void NodeGraph::toggleAutoPreview() { - _imp->_gui->getApp()->getProject()->toggleAutoPreview(); + getGui()->getApp()->getProject()->toggleAutoPreview(); } void NodeGraph::forceRefreshAllPreviews() { - _imp->_gui->forceRefreshAllPreviews(); + getGui()->forceRefreshAllPreviews(); } void @@ -409,9 +409,9 @@ NodeGraph::showMenu(const QPoint & pos) QAction* autoPreview = new ActionWithShortcut(kShortcutGroupNodegraph,kShortcutIDActionGraphToggleAutoPreview, kShortcutDescActionGraphToggleAutoPreview,_imp->_menu); autoPreview->setCheckable(true); - autoPreview->setChecked( _imp->_gui->getApp()->getProject()->isAutoPreviewEnabled() ); + autoPreview->setChecked( getGui()->getApp()->getProject()->isAutoPreviewEnabled() ); QObject::connect( autoPreview,SIGNAL( triggered() ),this,SLOT( toggleAutoPreview() ) ); - QObject::connect( _imp->_gui->getApp()->getProject().get(),SIGNAL( autoPreviewChanged(bool) ),autoPreview,SLOT( setChecked(bool) ) ); + QObject::connect( getGui()->getApp()->getProject().get(),SIGNAL( autoPreviewChanged(bool) ),autoPreview,SLOT( setChecked(bool) ) ); _imp->_menu->addAction(autoPreview); QAction* autoTurbo = new ActionWithShortcut(kShortcutGroupNodegraph,kShortcutIDActionGraphToggleAutoTurbo, @@ -434,7 +434,7 @@ NodeGraph::showMenu(const QPoint & pos) _imp->_menu->addSeparator(); - std::list orederedToolButtons = _imp->_gui->getToolButtonsOrdered(); + std::list orederedToolButtons = getGui()->getToolButtonsOrdered(); for (std::list::iterator it = orederedToolButtons.begin(); it != orederedToolButtons.end(); ++it) { (*it)->getMenu()->setIcon( (*it)->getMenuIcon() ); _imp->_menu->addAction( (*it)->getMenu()->menuAction() ); diff --git a/Gui/NodeGraph45.cpp b/Gui/NodeGraph45.cpp index 0e2526ab84..64f9ec857d 100644 --- a/Gui/NodeGraph45.cpp +++ b/Gui/NodeGraph45.cpp @@ -136,59 +136,6 @@ NodeGraph::refreshNodesKnobsAtTime(SequenceTime time) } } -void -NodeGraph::onTimelineTimeAboutToChange() -{ - assert(QThread::currentThread() == qApp->thread()); - _imp->wasLaskUserSeekDuringPlayback = false; - const std::list& viewers = _imp->_gui->getViewersList(); - for (std::list::const_iterator it = viewers.begin(); it != viewers.end(); ++it) { - RenderEngine* engine = (*it)->getInternalNode()->getRenderEngine(); - _imp->wasLaskUserSeekDuringPlayback |= engine->abortRendering(true,true); - } -} - -void -NodeGraph::onTimeChanged(SequenceTime time, - int reason) -{ - assert(QThread::currentThread() == qApp->thread()); - std::vector viewers; - - if (!_imp->_gui) { - return; - } - boost::shared_ptr project = _imp->_gui->getApp()->getProject(); - - ///Refresh all knobs at the current time - for (std::list >::iterator it = _imp->_nodes.begin(); it != _imp->_nodes.end(); ++it) { - ViewerInstance* isViewer = dynamic_cast( (*it)->getNode()->getLiveInstance() ); - if (isViewer) { - viewers.push_back(isViewer); - } - (*it)->refreshKnobsAfterTimeChange(time); - } - - ViewerInstance* leadViewer = getGui()->getApp()->getLastViewerUsingTimeline(); - - bool isUserEdited = reason == eTimelineChangeReasonUserSeek || - reason == eTimelineChangeReasonDopeSheetEditorSeek || - reason == eTimelineChangeReasonCurveEditorSeek; - - bool startPlayback = isUserEdited && _imp->wasLaskUserSeekDuringPlayback; - - ///Syncrhronize viewers - for (U32 i = 0; i < viewers.size(); ++i) { - if (!startPlayback) { - if (viewers[i] != leadViewer || isUserEdited) { - viewers[i]->renderCurrentFrame(reason != eTimelineChangeReasonPlaybackSeek); - } - } else { - viewers[i]->renderFromCurrentFrameUsingCurrentDirection(getGui()->getApp()->isRenderStatsActionChecked()); - } - } -} - void NodeGraph::onGuiFrozenChanged(bool frozen) { @@ -223,8 +170,8 @@ void NodeGraph::focusInEvent(QFocusEvent* e) { QGraphicsView::focusInEvent(e); - if (_imp->_gui) { - _imp->_gui->setLastSelectedGraph(this); + if (getGui()) { + getGui()->setLastSelectedGraph(this); } } diff --git a/Gui/NodeGraphPrivate.cpp b/Gui/NodeGraphPrivate.cpp index 0101454e57..888e0165a3 100644 --- a/Gui/NodeGraphPrivate.cpp +++ b/Gui/NodeGraphPrivate.cpp @@ -43,11 +43,9 @@ using namespace Natron; -NodeGraphPrivate::NodeGraphPrivate(Gui* gui, - NodeGraph* p, +NodeGraphPrivate::NodeGraphPrivate(NodeGraph* p, const boost::shared_ptr& group) : _publicInterface(p) -, _gui(gui) , group(group) , _lastMousePos() , _lastNodeDragStartPoint() @@ -89,8 +87,14 @@ NodeGraphPrivate::NodeGraphPrivate(Gui* gui, , _deltaSinceMousePress(0,0) , _hasMovedOnce(false) , lastSelectedViewer(0) -, wasLaskUserSeekDuringPlayback(false) { + appPTR->getIcon(Natron::NATRON_PIXMAP_LOCKED, &unlockIcon); +} + +QPoint +NodeGraphPrivate::getPyPlugUnlockPos() const +{ + return QPoint(_publicInterface->width() - unlockIcon.width() - 10, 10); } void diff --git a/Gui/NodeGraphPrivate.h b/Gui/NodeGraphPrivate.h index 6da5b7c2c4..3359e81856 100644 --- a/Gui/NodeGraphPrivate.h +++ b/Gui/NodeGraphPrivate.h @@ -176,7 +176,6 @@ class SelectionRectangle struct NodeGraphPrivate { NodeGraph* _publicInterface; - Gui* _gui; boost::weak_ptr group; @@ -228,12 +227,14 @@ struct NodeGraphPrivate bool _hasMovedOnce; ViewerTab* lastSelectedViewer; - bool wasLaskUserSeekDuringPlayback; - NodeGraphPrivate(Gui* gui, - NodeGraph* p, + QPixmap unlockIcon; + + NodeGraphPrivate(NodeGraph* p, const boost::shared_ptr& group); + QPoint getPyPlugUnlockPos() const; + void resetAllClipboards(); QRectF calcNodesBoundingRect(); diff --git a/Gui/NodeGraphPrivate10.cpp b/Gui/NodeGraphPrivate10.cpp index f557b30447..1259ba242a 100644 --- a/Gui/NodeGraphPrivate10.cpp +++ b/Gui/NodeGraphPrivate10.cpp @@ -113,7 +113,7 @@ NodeGraphPrivate::pasteNode(const NodeSerialization & internalSerialization, const std::string& parentName, bool clone) { - boost::shared_ptr n = _gui->getApp()->loadNode( LoadNodeArgs(internalSerialization.getPluginID().c_str(), + boost::shared_ptr n = _publicInterface->getGui()->getApp()->loadNode( LoadNodeArgs(internalSerialization.getPluginID().c_str(), parentName, internalSerialization.getPluginMajorVersion(), internalSerialization.getPluginMinorVersion(), @@ -153,7 +153,7 @@ NodeGraphPrivate::pasteNode(const NodeSerialization & internalSerialization, const std::string & masterNodeName = internalSerialization.getMasterNodeName(); if ( !masterNodeName.empty() ) { - boost::shared_ptr masterNode = _gui->getApp()->getProject()->getNodeByName(masterNodeName); + boost::shared_ptr masterNode = _publicInterface->getGui()->getApp()->getProject()->getNodeByName(masterNodeName); ///the node could not exist any longer if the user deleted it in the meantime if ( masterNode && masterNode->isActivated() ) { @@ -161,7 +161,7 @@ NodeGraphPrivate::pasteNode(const NodeSerialization & internalSerialization, } } std::list > allNodes; - _gui->getApp()->getProject()->getActiveNodes(&allNodes); + _publicInterface->getGui()->getApp()->getProject()->getActiveNodes(&allNodes); n->restoreKnobsLinks(internalSerialization,allNodes); //We don't want the clone to have the same hash as the original @@ -170,7 +170,7 @@ NodeGraphPrivate::pasteNode(const NodeSerialization & internalSerialization, gui->copyFrom(guiSerialization); QPointF newPos = gui->getPos_mt_safe() + offset; gui->setPosition( newPos.x(), newPos.y() ); - gui->forceComputePreview( _gui->getApp()->getProject()->currentFrame() ); + gui->forceComputePreview( _publicInterface->getGui()->getApp()->getProject()->currentFrame() ); if (clone) { DotGui* isDot = dynamic_cast( gui.get() ); diff --git a/Gui/NodeGraphUndoRedo.cpp b/Gui/NodeGraphUndoRedo.cpp index 882f03f3b6..f75ba96fbf 100644 --- a/Gui/NodeGraphUndoRedo.cpp +++ b/Gui/NodeGraphUndoRedo.cpp @@ -410,96 +410,159 @@ ConnectCommand::ConnectCommand(NodeGraph* graph, void ConnectCommand::undo() { - doConnect(_newSrc.lock() ? _newSrc.lock()->getNode() : boost::shared_ptr(), - _oldSrc.lock() ? _oldSrc.lock()->getNode() : boost::shared_ptr()); + NodeGuiPtr newSrc = _newSrc.lock(); + NodeGuiPtr oldSrc = _oldSrc.lock(); + NodeGuiPtr dst = _dst.lock(); + doConnect(newSrc, + oldSrc, + dst, + _inputNb); + + if (newSrc) { + setText( QObject::tr("Connect %1 to %2") + .arg(dst->getNode()->getLabel().c_str() ).arg( newSrc->getNode()->getLabel().c_str() ) ); + } else { + setText( QObject::tr("Disconnect %1") + .arg(dst->getNode()->getLabel().c_str() ) ); + } + + + ViewerInstance* isDstAViewer = dynamic_cast(dst->getNode()->getLiveInstance() ); + if (!isDstAViewer) { + _graph->getGui()->getApp()->triggerAutoSave(); + } + _graph->update(); } // undo void ConnectCommand::redo() { - doConnect(_oldSrc.lock() ? _oldSrc.lock()->getNode() : boost::shared_ptr(), - _newSrc.lock() ? _newSrc.lock()->getNode() : boost::shared_ptr()); -} // redo - - - -void -ConnectCommand::doConnect(const boost::shared_ptr &oldSrc, - const boost::shared_ptr & newSrc) -{ + + NodeGuiPtr newSrc = _newSrc.lock(); + NodeGuiPtr oldSrc = _oldSrc.lock(); NodeGuiPtr dst = _dst.lock(); - boost::shared_ptr internalDst = dst->getNode(); - InspectorNode* inspector = dynamic_cast(internalDst.get()); + doConnect(oldSrc, + newSrc, + dst, + _inputNb); if (newSrc) { setText( QObject::tr("Connect %1 to %2") - .arg(internalDst->getLabel().c_str() ).arg( newSrc->getLabel().c_str() ) ); + .arg(dst->getNode()->getLabel().c_str() ).arg( newSrc->getNode()->getLabel().c_str() ) ); } else { setText( QObject::tr("Disconnect %1") - .arg(internalDst->getLabel().c_str() ) ); + .arg(dst->getNode()->getLabel().c_str() ) ); } + + + ViewerInstance* isDstAViewer = dynamic_cast(dst->getNode()->getLiveInstance() ); + if (!isDstAViewer) { + _graph->getGui()->getApp()->triggerAutoSave(); + } + _graph->update(); +} // redo + + + +void +ConnectCommand::doConnect(const NodeGuiPtr &oldSrc, + const NodeGuiPtr &newSrc, + const NodeGuiPtr& dst, + int inputNb) +{ + NodePtr internalDst = dst->getNode(); + NodePtr internalNewSrc = newSrc ? newSrc->getNode() : NodePtr(); + NodePtr internalOldSrc = oldSrc ? oldSrc->getNode() : NodePtr(); + + InspectorNode* inspector = dynamic_cast(internalDst.get()); + + if (inspector) { ///if the node is an inspector disconnect any current connection between the inspector and the _newSrc for (int i = 0; i < inspector->getMaxInputCount(); ++i) { - if (i != _inputNb && inspector->getInput(i) == newSrc) { + if (i != inputNb && inspector->getInput(i) == internalNewSrc) { inspector->disconnectInput(i); } } } - if (oldSrc && newSrc) { - internalDst->replaceInput(newSrc, _inputNb); + if (internalOldSrc && internalNewSrc) { + internalDst->replaceInput(internalNewSrc, inputNb); } else { - if (oldSrc && newSrc) { - Natron::Node::CanConnectInputReturnValue ret = internalDst->canConnectInput(newSrc, _inputNb); + if (internalOldSrc && internalNewSrc) { + Natron::Node::CanConnectInputReturnValue ret = internalDst->canConnectInput(internalNewSrc, inputNb); bool connectionOk = ret == Natron::Node::eCanConnectInput_ok || ret == Natron::Node::eCanConnectInput_differentFPS || ret == Natron::Node::eCanConnectInput_differentPars; if (connectionOk) { - internalDst->replaceInput(newSrc, _inputNb); + internalDst->replaceInput(internalNewSrc, inputNb); } else { - internalDst->disconnectInput(internalDst->getInputIndex(oldSrc.get())); + internalDst->disconnectInput(internalDst->getInputIndex(internalOldSrc.get())); } - } else if (oldSrc && !newSrc) { - internalDst->disconnectInput(internalDst->getInputIndex(oldSrc.get())); - } else if (!oldSrc && newSrc) { - Natron::Node::CanConnectInputReturnValue ret = internalDst->canConnectInput(newSrc, _inputNb); + } else if (internalOldSrc && !internalNewSrc) { + internalDst->disconnectInput(internalDst->getInputIndex(internalOldSrc.get())); + } else if (!internalOldSrc && internalNewSrc) { + Natron::Node::CanConnectInputReturnValue ret = internalDst->canConnectInput(internalNewSrc, inputNb); bool connectionOk = ret == Natron::Node::eCanConnectInput_ok || ret == Natron::Node::eCanConnectInput_differentFPS || ret == Natron::Node::eCanConnectInput_differentPars; if (connectionOk) { - internalDst->connectInput(newSrc,_inputNb); + internalDst->connectInput(internalNewSrc,inputNb); } else { - internalDst->disconnectInput(internalDst->getInputIndex(oldSrc.get())); + internalDst->disconnectInput(internalDst->getInputIndex(internalOldSrc.get())); } } } - - assert(_dst.lock()); dst->refreshEdges(); dst->checkOptionalEdgesVisibility(); - boost::shared_ptr newSrcGui = _newSrc.lock(); - boost::shared_ptr oldSrcGui = _oldSrc.lock(); - if (newSrcGui) { - newSrcGui->checkOptionalEdgesVisibility(); + + if (newSrc) { + newSrc->checkOptionalEdgesVisibility(); } - if (oldSrcGui) { - oldSrcGui->checkOptionalEdgesVisibility(); + if (oldSrc) { + oldSrc->checkOptionalEdgesVisibility(); } - ///if the node has no inputs, all the viewers attached to that node should be black. - std::list viewers; - internalDst->hasViewersConnected(&viewers); - for (std::list::iterator it = viewers.begin(); it != viewers.end(); ++it) { - (*it)->renderCurrentFrame(true); + +} + +InsertNodeCommand::InsertNodeCommand(NodeGraph* graph, + Edge* edge, + const boost::shared_ptr & newSrc, + QUndoCommand *parent) +: ConnectCommand(graph,edge,edge->getSource(),newSrc,parent) +, _inputEdge(0) +{ + assert(newSrc); + setText(QObject::tr("Insert node")); +} + + +void +InsertNodeCommand::undo() +{ + NodeGuiPtr oldSrc = _oldSrc.lock(); + NodeGuiPtr newSrc = _newSrc.lock(); + NodeGuiPtr dst = _dst.lock(); + assert(newSrc); + + NodePtr oldSrcInternal = oldSrc ? oldSrc->getNode() : NodePtr(); + NodePtr newSrcInternal = newSrc->getNode(); + NodePtr dstInternal = dst->getNode(); + assert(newSrcInternal && dstInternal); + + doConnect(newSrc,oldSrc,dst,_inputNb); + + if (_inputEdge) { + doConnect(_inputEdge->getSource(), NodeGuiPtr(), _inputEdge->getDest(), _inputEdge->getInputNumber()); } - ViewerInstance* isDstAViewer = dynamic_cast(internalDst->getLiveInstance() ); + ViewerInstance* isDstAViewer = dynamic_cast(dst->getNode()->getLiveInstance() ); if (!isDstAViewer) { _graph->getGui()->getApp()->triggerAutoSave(); } @@ -507,6 +570,66 @@ ConnectCommand::doConnect(const boost::shared_ptr &oldSrc, } +void +InsertNodeCommand::redo() +{ + NodeGuiPtr oldSrc = _oldSrc.lock(); + NodeGuiPtr newSrc = _newSrc.lock(); + NodeGuiPtr dst = _dst.lock(); + assert(newSrc); + + NodePtr oldSrcInternal = oldSrc ? oldSrc->getNode() : NodePtr(); + NodePtr newSrcInternal = newSrc->getNode(); + NodePtr dstInternal = dst->getNode(); + assert(newSrcInternal && dstInternal); + + newSrcInternal->beginInputEdition(); + dstInternal->beginInputEdition(); + + doConnect(oldSrc,newSrc,dst,_inputNb); + + + ///find out if the node is already connected to what the edge is connected + bool alreadyConnected = false; + const std::vector > & inpNodes = newSrcInternal->getGuiInputs(); + if (oldSrcInternal) { + for (U32 i = 0; i < inpNodes.size(); ++i) { + if (inpNodes[i] == oldSrcInternal) { + alreadyConnected = true; + break; + } + } + } + + _inputEdge = 0; + if (oldSrcInternal && !alreadyConnected) { + ///push a second command... this is a bit dirty but I don't have time to add a whole new command just for this + int prefInput = newSrcInternal->getPreferredInputForConnection(); + if (prefInput != -1) { + _inputEdge = newSrc->getInputArrow(prefInput); + assert(_inputEdge); + doConnect(_inputEdge->getSource(), oldSrc, _inputEdge->getDest(), _inputEdge->getInputNumber()); + } + } + + ViewerInstance* isDstAViewer = dynamic_cast(dst->getNode()->getLiveInstance() ); + if (!isDstAViewer) { + _graph->getGui()->getApp()->triggerAutoSave(); + } + + newSrcInternal->endInputEdition(false); + dstInternal->endInputEdition(false); + + std::list viewers; + dstInternal->hasViewersConnected(&viewers); + for (std::list::iterator it2 = viewers.begin(); it2 != viewers.end(); ++it2) { + (*it2)->renderCurrentFrame(true); + } + + _graph->update(); + +} + ResizeBackDropCommand::ResizeBackDropCommand(const NodeGuiPtr& bd, int w, int h, diff --git a/Gui/NodeGraphUndoRedo.h b/Gui/NodeGraphUndoRedo.h index b78d2534ff..8037d61318 100644 --- a/Gui/NodeGraphUndoRedo.h +++ b/Gui/NodeGraphUndoRedo.h @@ -152,10 +152,12 @@ class ConnectCommand virtual void undo(); virtual void redo(); -private: +protected: - void doConnect(const boost::shared_ptr &oldSrc, - const boost::shared_ptr & newSrc); + static void doConnect(const NodeGuiPtr &oldSrc, + const NodeGuiPtr & newSrc, + const NodeGuiPtr& dst, + int inputNb); boost::weak_ptr _oldSrc,_newSrc; boost::weak_ptr _dst; @@ -163,6 +165,27 @@ class ConnectCommand int _inputNb; }; +/* + * @brief Inserts an existing node in a stream + */ +class InsertNodeCommand : public ConnectCommand +{ +public: + + InsertNodeCommand(NodeGraph* graph, + Edge* edge, + const boost::shared_ptr & newSrc, + QUndoCommand *parent = 0); + + virtual void undo(); + virtual void redo(); + +private: + + Edge* _inputEdge; + +}; + class ResizeBackDropCommand : public QUndoCommand diff --git a/Gui/NodeGui.cpp b/Gui/NodeGui.cpp index 693d9e5747..7052130ca9 100644 --- a/Gui/NodeGui.cpp +++ b/Gui/NodeGui.cpp @@ -288,7 +288,8 @@ NodeGui::initialize(NodeGraph* dag, QObject::connect( internalNode.get(), SIGNAL( disabledKnobToggled(bool) ),this,SLOT( onDisabledKnobToggled(bool) ) ); QObject::connect( internalNode.get(), SIGNAL( bitDepthWarningToggled(bool,QString) ),this,SLOT( toggleBitDepthIndicator(bool,QString) ) ); QObject::connect( internalNode.get(), SIGNAL( nodeExtraLabelChanged(QString) ),this,SLOT( onNodeExtraLabelChanged(QString) ) ); - + QObject::connect( internalNode.get(), SIGNAL( outputLayerChanged() ),this,SLOT( onOutputLayerChanged() ) ); + setCacheMode(DeviceCoordinateCache); OutputEffectInstance* isOutput = dynamic_cast(internalNode->getLiveInstance()); @@ -385,24 +386,12 @@ NodeGui::initialize(NodeGraph* dag, Natron::clamp(b, 0., 1.)); setCurrentColor(color); - if ( !internalNode->isMultiInstance() ) { - _nodeLabel = internalNode->getNodeExtraLabel().c_str(); - _nodeLabel = replaceLineBreaksWithHtmlParagraph(_nodeLabel); - } - - - ///Refresh the name in the line edit - onInternalNameChanged( internalNode->getLabel().c_str() ); - ///Make the output edge if ( !isBd && !internalNode->isOutputNode() ) { _outputEdge = new Edge( thisAsShared,parentItem() ); } - ///Refresh the disabled knob - if ( internalNode->isNodeDisabled() ) { - onDisabledKnobToggled(true); - } + restoreStateAfterCreation(); ///Link the position of the node to the position of the parent multi-instance const std::string parentMultiInstanceName = internalNode->getParentMultiInstanceName(); @@ -421,6 +410,22 @@ NodeGui::initialize(NodeGraph* dag, } // initialize +void +NodeGui::restoreStateAfterCreation() +{ + NodePtr internalNode = getNode(); + ///Refresh the disabled knob + if ( internalNode->isNodeDisabled() ) { + onDisabledKnobToggled(true); + } + if ( !internalNode->isMultiInstance() ) { + _nodeLabel = internalNode->getNodeExtraLabel().c_str(); + _nodeLabel = replaceLineBreaksWithHtmlParagraph(_nodeLabel); + } + ///Refresh the name in the line edit + onInternalNameChanged( internalNode->getLabel().c_str() ); +} + void NodeGui::ensurePanelCreated() { @@ -2656,6 +2661,23 @@ NodeGui::setNameItemHtml(const QString & name, QString textLabel; textLabel.append("

"); bool hasFontData = true; + QString extraLayerStr; + + std::string selectedLayer; + bool foundLayer = getNode()->getSelectedLayer(-1,selectedLayer); + if (foundLayer) { + if (selectedLayer != ImageComponents::getRGBAComponents().getComponentsGlobalName() && + selectedLayer != ImageComponents::getRGBComponents().getComponentsGlobalName() && + selectedLayer != ImageComponents::getAlphaComponents().getComponentsGlobalName() && + selectedLayer != "None" && + selectedLayer != "All") { + extraLayerStr.append("
"); + extraLayerStr.push_back('('); + extraLayerStr.append(selectedLayer.c_str()); + extraLayerStr.push_back(')'); + } + } + if ( !label.isEmpty() ) { QString labelCopy = label; @@ -2679,9 +2701,9 @@ NodeGui::setNameItemHtml(const QString & name, QString toFind("\">"); int endFontTag = labelCopy.indexOf(toFind,startFontTag); int i = endFontTag += toFind.size(); - labelCopy.insert(i == -1 ? 0 : i, name + "
"); + labelCopy.insert(i == -1 ? 0 : i, name + extraLayerStr + "
"); } else { - labelCopy.prepend(name + "
"); + labelCopy.prepend(name + extraLayerStr + "
"); } textLabel.append(labelCopy); @@ -2693,6 +2715,7 @@ NodeGui::setNameItemHtml(const QString & name, .arg(QApplication::font().family())); textLabel.append(fontTag); textLabel.append(name); + textLabel.append(extraLayerStr); textLabel.append(""); } textLabel.append("
"); @@ -2714,6 +2737,12 @@ NodeGui::setNameItemHtml(const QString & name, // resize( currentBbox.width(), std::max( currentBbox.height(),labelBbox.height() ) ); } // setNameItemHtml +void +NodeGui::onOutputLayerChanged() +{ + setNameItemHtml(getNode()->getLabel().c_str(),_nodeLabel); +} + void NodeGui::onNodeExtraLabelChanged(const QString & label) { diff --git a/Gui/NodeGui.h b/Gui/NodeGui.h index e3671a23d7..fd8d5e907c 100644 --- a/Gui/NodeGui.h +++ b/Gui/NodeGui.h @@ -163,6 +163,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void deleteReferences(); ~NodeGui() OVERRIDE; + + virtual void restoreStateAfterCreation() OVERRIDE FINAL; /** * @brief Fills the serializationObject with the current state of the NodeGui. @@ -427,6 +429,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON public Q_SLOTS: + void onOutputLayerChanged(); void onSettingsPanelClosed(bool closed); diff --git a/Gui/PanelWidget.cpp b/Gui/PanelWidget.cpp new file mode 100644 index 0000000000..2266514191 --- /dev/null +++ b/Gui/PanelWidget.cpp @@ -0,0 +1,148 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * This file is part of Natron , + * Copyright (C) 2015 INRIA and Alexandre Gauthier-Foichat + * + * Natron 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 2 of the License, or + * (at your option) any later version. + * + * Natron 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 Natron. If not, see + * ***** END LICENSE BLOCK ***** */ + +// ***** BEGIN PYTHON BLOCK ***** +// from : +// "Since Python may define some pre-processor definitions which affect the standard headers on some systems, you must include Python.h before any standard headers are included." +#include +// ***** END PYTHON BLOCK ***** + +#include "PanelWidget.h" +#include + +#include "Gui/TabWidget.h" +#include "Gui/Gui.h" + +PanelWidget::PanelWidget(QWidget* thisWidget, + Gui* gui) +: ScriptObject() +, _thisWidget(thisWidget) +, _gui(gui) +{ + assert(_gui && _thisWidget); +} + +PanelWidget::~PanelWidget() +{ + +} + +Gui* +PanelWidget::getGui() const +{ + return _gui; +} + +void +PanelWidget::notifyGuiClosingPublic() +{ + _gui = 0; + notifyGuiClosing(); +} + +static TabWidget* getParentPaneRecursive(QWidget* w) +{ + if (!w) { + return 0; + } + TabWidget* tw = dynamic_cast(w); + if (tw) { + return tw; + } else { + return getParentPaneRecursive(w->parentWidget()); + } +} + +TabWidget* +PanelWidget::getParentPane() const +{ + return getParentPaneRecursive(_thisWidget); +} + +void +PanelWidget::enterEventBase() +{ + + TabWidget* parentPane = getParentPane(); + if (parentPane) { + parentPane->setWidgetMouseOverFocus(this, true); + } + if (_gui && _gui->isFocusStealingPossible()) { + _thisWidget->setFocus(); + } + +} + +bool +PanelWidget::isClickFocusPanel() const +{ + if (!_gui) { + return false; + } + return _gui->getCurrentPanelFocus() == this; +} + +void +PanelWidget::takeClickFocus() +{ + TabWidget* parentPane = getParentPane(); + if (parentPane) { + parentPane->setWidgetClickFocus(this, true); + } + if (_gui) { + const RegisteredTabs& tabs = _gui->getRegisteredTabs(); + for (RegisteredTabs::const_iterator it = tabs.begin(); it!=tabs.end(); ++it) { + if (it->second.first != this) { + it->second.first->removeClickFocus(); + } + } + _gui->setCurrentPanelFocus(this); + } +} + +void +PanelWidget::removeClickFocus() +{ + TabWidget* parentPane = getParentPane(); + if (parentPane) { + parentPane->setWidgetClickFocus(this, false); + } + if (_gui) { + _gui->setCurrentPanelFocus(0); + } +} + + +void +PanelWidget::leaveEventBase() +{ + TabWidget* parentPane = getParentPane(); + if (parentPane) { + parentPane->setWidgetMouseOverFocus(this, false); + } +} + +void +PanelWidget::handleUnCaughtKeyPressEvent() +{ + if (!_gui) { + return; + } + _gui->setLastKeyPressVisitedClickFocus(_gui->getCurrentPanelFocus() == this); + +} \ No newline at end of file diff --git a/Gui/PanelWidget.h b/Gui/PanelWidget.h new file mode 100644 index 0000000000..4f6be29fd7 --- /dev/null +++ b/Gui/PanelWidget.h @@ -0,0 +1,91 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * This file is part of Natron , + * Copyright (C) 2015 INRIA and Alexandre Gauthier-Foichat + * + * Natron 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 2 of the License, or + * (at your option) any later version. + * + * Natron 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 Natron. If not, see + * ***** END LICENSE BLOCK ***** */ + +// ***** BEGIN PYTHON BLOCK ***** +// from : +// "Since Python may define some pre-processor definitions which affect the standard headers on some systems, you must include Python.h before any standard headers are included." +#include +// ***** END PYTHON BLOCK ***** + +#ifndef PANELWIDGET_H +#define PANELWIDGET_H + +#include "Global/Macros.h" + +CLANG_DIAG_OFF(deprecated) +CLANG_DIAG_OFF(uninitialized) +#include +CLANG_DIAG_ON(deprecated) +CLANG_DIAG_ON(uninitialized) + +#include "Engine/ScriptObject.h" + +class Gui; +class TabWidget; +class PanelWidget : public ScriptObject +{ + QWidget* _thisWidget; + Gui* _gui; +public: + + PanelWidget(QWidget* thisWidget,Gui* gui); + + Gui* getGui() const; + + void notifyGuiClosingPublic(); + + virtual ~PanelWidget(); + + QWidget* getWidget() const + { + return _thisWidget; + } + + TabWidget* getParentPane() const; + + void removeClickFocus(); + + void takeClickFocus(); + + bool isClickFocusPanel() const; + + + /* + * @brief To be called when a keypress event is not accepted + */ + void handleUnCaughtKeyPressEvent(); + +protected: + + virtual void notifyGuiClosing() {} + + /** + * @brief To be called in the enterEvent handler of all derived classes. + **/ + void enterEventBase(); + + /** + * @brief To be called in the leaveEvent handler of all derived classes. + **/ + void leaveEventBase(); + + + +}; + +#endif // PANELWIDGET_H diff --git a/Gui/PropertiesBinWrapper.cpp b/Gui/PropertiesBinWrapper.cpp new file mode 100644 index 0000000000..b43b0e6648 --- /dev/null +++ b/Gui/PropertiesBinWrapper.cpp @@ -0,0 +1,70 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * This file is part of Natron , + * Copyright (C) 2015 INRIA and Alexandre Gauthier-Foichat + * + * Natron 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 2 of the License, or + * (at your option) any later version. + * + * Natron 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 Natron. If not, see + * ***** END LICENSE BLOCK ***** */ + +// ***** BEGIN PYTHON BLOCK ***** +// from : +// "Since Python may define some pre-processor definitions which affect the standard headers on some systems, you must include Python.h before any standard headers are included." +#include +// ***** END PYTHON BLOCK ***** + +#include "PropertiesBinWrapper.h" + +#include + +#include "Gui/Gui.h" + +PropertiesBinWrapper::PropertiesBinWrapper(Gui* parent) +: QWidget(parent) +, PanelWidget(this,parent) +{ +} + +PropertiesBinWrapper::~PropertiesBinWrapper() +{ + +} + +void +PropertiesBinWrapper::mousePressEvent(QMouseEvent* e) +{ + takeClickFocus(); + QWidget::mousePressEvent(e); +} + + + +void +PropertiesBinWrapper::enterEvent(QEvent* e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} + +void +PropertiesBinWrapper::leaveEvent(QEvent* e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); +} + +void +PropertiesBinWrapper::keyPressEvent(QKeyEvent* e) +{ + handleUnCaughtKeyPressEvent(); + QWidget::keyPressEvent(e); +} \ No newline at end of file diff --git a/Gui/PropertiesBinWrapper.h b/Gui/PropertiesBinWrapper.h index dd22957ac2..12cf57d2be 100644 --- a/Gui/PropertiesBinWrapper.h +++ b/Gui/PropertiesBinWrapper.h @@ -25,20 +25,24 @@ #include // ***** END PYTHON BLOCK ***** -#include -#include "Global/Macros.h" +#include "Gui/PanelWidget.h" -#include "Engine/ScriptObject.h" - -class PropertiesBinWrapper : public QWidget, public ScriptObject +class PropertiesBinWrapper : public QWidget, public PanelWidget { + public: - PropertiesBinWrapper(QWidget* parent) - : QWidget(parent) - , ScriptObject() - { - } + + PropertiesBinWrapper(Gui* parent); + + virtual ~PropertiesBinWrapper(); + + +private: + virtual void mousePressEvent(QMouseEvent* e) OVERRIDE FINAL; + virtual void enterEvent(QEvent* e) OVERRIDE FINAL; + virtual void leaveEvent(QEvent* e) OVERRIDE FINAL; + virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; }; #endif // Gui_PropertiesBinWrapper_h diff --git a/Gui/PythonPanels.cpp b/Gui/PythonPanels.cpp index e864f058ff..4985e71ef4 100644 --- a/Gui/PythonPanels.cpp +++ b/Gui/PythonPanels.cpp @@ -192,7 +192,7 @@ PyModalDialog::PyModalDialog(Gui* gui) _imp->centerLayout->setContentsMargins(0, 0, 0, 0); - _imp->panel = new DockablePanel(_imp->gui, + _imp->panel = new DockablePanel(gui, _imp->holder, _imp->mainLayout, DockablePanel::eHeaderModeNoHeader, @@ -254,7 +254,6 @@ PyModalDialog::getParam(const std::string& scriptName) const struct PyPanelPrivate { - Gui* gui; DialogParamHolder* holder; @@ -269,8 +268,7 @@ struct PyPanelPrivate PyPanelPrivate() - : gui(0) - , holder(0) + : holder(0) , mainLayout(0) , panel(0) , centerContainer(0) @@ -287,21 +285,20 @@ struct PyPanelPrivate PyPanel::PyPanel(const std::string& scriptName,const std::string& label,bool useUserParameters,GuiApp* app) : QWidget(app->getGui()) , UserParamHolder() -, ScriptObject() +, PanelWidget(this,app->getGui()) , _imp(new PyPanelPrivate()) { - _imp->gui = app->getGui(); setLabel(label.c_str()); int idx = 1; std::string name = Natron::makeNameScriptFriendly(scriptName); - QWidget* existing = 0; - existing = _imp->gui->findExistingTab(name); + PanelWidget* existing = 0; + existing = getGui()->findExistingTab(name); while (existing) { std::stringstream ss; ss << name << idx; - existing = _imp->gui->findExistingTab(ss.str()); + existing = getGui()->findExistingTab(ss.str()); if (!existing) { name = ss.str(); } @@ -309,11 +306,11 @@ PyPanel::PyPanel(const std::string& scriptName,const std::string& label,bool use } setScriptName(name); - _imp->gui->registerTab(this,this); + getGui()->registerTab(this,this); if (useUserParameters) { - _imp->holder = new DialogParamHolder(name,_imp->gui->getApp()); + _imp->holder = new DialogParamHolder(name,getGui()->getApp()); setHolder(_imp->holder); _imp->holder->initializeKnobsPublic(); _imp->mainLayout = new QVBoxLayout(this); @@ -323,7 +320,7 @@ PyPanel::PyPanel(const std::string& scriptName,const std::string& label,bool use _imp->centerLayout = new QVBoxLayout(_imp->centerContainer); _imp->centerLayout->setContentsMargins(0, 0, 0, 0); - _imp->panel = new DockablePanel(_imp->gui, + _imp->panel = new DockablePanel(getGui(), _imp->holder, _imp->mainLayout, DockablePanel::eHeaderModeNoHeader, @@ -343,7 +340,7 @@ PyPanel::PyPanel(const std::string& scriptName,const std::string& label,bool use PyPanel::~PyPanel() { - _imp->gui->unregisterPyPanel(this); + getGui()->unregisterPyPanel(this); } std::string @@ -441,6 +438,33 @@ PyPanel::save_serialization_thread() const return _imp->serialization; } +void +PyPanel::mousePressEvent(QMouseEvent* e) +{ + takeClickFocus(); + QWidget::mousePressEvent(e); +} + +void +PyPanel::enterEvent(QEvent* e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} + +void +PyPanel::leaveEvent(QEvent* e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); +} + +void +PyPanel::keyPressEvent(QKeyEvent* e) +{ + QWidget::keyPressEvent(e); +} + PyTabWidget::PyTabWidget(TabWidget* pane) : _tab(pane) { @@ -467,7 +491,11 @@ PyTabWidget::insertTab(int index,PyPanel* tab) void PyTabWidget::removeTab(QWidget* tab) { - _tab->removeTab(tab, false); + PyPanel* isPanel = dynamic_cast(tab); + if (!isPanel) { + return; + } + _tab->removeTab(isPanel, false); } void @@ -497,7 +525,11 @@ PyTabWidget::count() QWidget* PyTabWidget::currentWidget() { - return _tab->currentWidget(); + PanelWidget* w = _tab->currentWidget(); + if (!w) { + return 0; + } + return w->getWidget(); } void diff --git a/Gui/PythonPanels.h b/Gui/PythonPanels.h index de381ace44..a6fbe3d4f4 100644 --- a/Gui/PythonPanels.h +++ b/Gui/PythonPanels.h @@ -34,6 +34,7 @@ CLANG_DIAG_ON(uninitialized) #include "Engine/NodeWrapper.h" #include "Engine/ScriptObject.h" #include "Engine/Knob.h" +#include "Gui/PanelWidget.h" class GuiApp; class Gui; @@ -100,9 +101,11 @@ class PyModalDialog : public QDialog, public UserParamHolder struct PyPanelPrivate; -class PyPanel : public QWidget, public UserParamHolder, public ScriptObject +class PyPanel : public QWidget, public UserParamHolder, public PanelWidget { +GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT +GCC_DIAG_SUGGEST_OVERRIDE_ON public: @@ -136,8 +139,11 @@ class PyPanel : public QWidget, public UserParamHolder, public ScriptObject void onUserDataChanged(); + virtual void mousePressEvent(QMouseEvent* e) OVERRIDE; + virtual void enterEvent(QEvent* e) OVERRIDE ; + virtual void leaveEvent(QEvent* e) OVERRIDE ; + virtual void keyPressEvent(QKeyEvent* e) OVERRIDE ; private: - boost::scoped_ptr _imp; }; diff --git a/Gui/RegisteredTabs.h b/Gui/RegisteredTabs.h index 314b6f9b08..277da1bcb2 100644 --- a/Gui/RegisteredTabs.h +++ b/Gui/RegisteredTabs.h @@ -29,9 +29,9 @@ #include #include -class QWidget; class ScriptObject; +class PanelWidget; -typedef std::map > RegisteredTabs; +typedef std::map > RegisteredTabs; #endif // Gui_RegisteredTabs_h diff --git a/Gui/Resources/Images/AutoContrast.png b/Gui/Resources/Images/AutoContrast.png new file mode 100644 index 0000000000..c8082a4ddd Binary files /dev/null and b/Gui/Resources/Images/AutoContrast.png differ diff --git a/Gui/Resources/Images/AutoContrastON.png b/Gui/Resources/Images/AutoContrastON.png new file mode 100644 index 0000000000..d3c0b522ae Binary files /dev/null and b/Gui/Resources/Images/AutoContrastON.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/3D_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/3D_grouping_3.svg new file mode 100644 index 0000000000..1186461c03 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/3D_grouping_3.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/3D_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/3D_grouping_3_512.png new file mode 100644 index 0000000000..a9cc9d6374 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/3D_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/channel_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/channel_grouping_3.svg new file mode 100644 index 0000000000..9b171f0565 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/channel_grouping_3.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/channel_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/channel_grouping_3_512.png new file mode 100644 index 0000000000..e6e68ab26b Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/channel_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/color_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/color_grouping_3.svg new file mode 100644 index 0000000000..af62b9a941 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/color_grouping_3.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/color_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/color_grouping_3_512.png new file mode 100644 index 0000000000..3358d6f890 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/color_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/deep_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/deep_grouping_3.svg new file mode 100644 index 0000000000..f037ffccab --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/deep_grouping_3.svg @@ -0,0 +1,10 @@ + + + + + D + D + D + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/deep_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/deep_grouping_3_512.png new file mode 100644 index 0000000000..513e2781c5 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/deep_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/filter_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/filter_grouping_3.svg new file mode 100644 index 0000000000..2308154482 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/filter_grouping_3.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/filter_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/filter_grouping_3_512.png new file mode 100644 index 0000000000..72727c8f20 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/filter_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/image_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/image_grouping_3.svg new file mode 100644 index 0000000000..ed1ffdbe5d --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/image_grouping_3.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/image_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/image_grouping_3_512.png new file mode 100644 index 0000000000..6754ae5ff3 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/image_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/keyer_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/keyer_grouping_3.svg new file mode 100644 index 0000000000..09b440d549 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/keyer_grouping_3.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/keyer_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/keyer_grouping_3_512.png new file mode 100644 index 0000000000..2a23873dcf Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/keyer_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/merge_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/merge_grouping_3.svg new file mode 100644 index 0000000000..18d6109840 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/merge_grouping_3.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/merge_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/merge_grouping_3_512.png new file mode 100644 index 0000000000..3c85036ff5 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/merge_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/misc_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/misc_grouping_3.svg new file mode 100644 index 0000000000..7dbd275159 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/misc_grouping_3.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/misc_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/misc_grouping_3_512.png new file mode 100644 index 0000000000..1671d05bd9 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/misc_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/multiview_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/multiview_grouping_3.svg new file mode 100644 index 0000000000..74dbc38ff2 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/multiview_grouping_3.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/multiview_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/multiview_grouping_3_512.png new file mode 100644 index 0000000000..26778b9134 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/multiview_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/other_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/other_grouping_3.svg new file mode 100644 index 0000000000..159d09cf08 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/other_grouping_3.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/other_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/other_grouping_3_512.png new file mode 100644 index 0000000000..b7a91b769b Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/other_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/paint_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/paint_grouping_3.svg new file mode 100644 index 0000000000..498980fa2e --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/paint_grouping_3.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/paint_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/paint_grouping_3_512.png new file mode 100644 index 0000000000..f204150151 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/paint_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/time_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/time_grouping_3.svg new file mode 100644 index 0000000000..26aa471ea8 --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/time_grouping_3.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/time_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/time_grouping_3_512.png new file mode 100644 index 0000000000..c58952f83d Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/time_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/toolsets_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/toolsets_grouping_3.svg new file mode 100644 index 0000000000..76d8239e1c --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/toolsets_grouping_3.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/toolsets_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/toolsets_grouping_3_512.png new file mode 100644 index 0000000000..99ede52bcb Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/toolsets_grouping_3_512.png differ diff --git a/Gui/Resources/Images/GroupingIcons/Set3/transform_grouping_3.svg b/Gui/Resources/Images/GroupingIcons/Set3/transform_grouping_3.svg new file mode 100644 index 0000000000..b959e8054a --- /dev/null +++ b/Gui/Resources/Images/GroupingIcons/Set3/transform_grouping_3.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Gui/Resources/Images/GroupingIcons/Set3/transform_grouping_3_512.png b/Gui/Resources/Images/GroupingIcons/Set3/transform_grouping_3_512.png new file mode 100644 index 0000000000..1364447079 Binary files /dev/null and b/Gui/Resources/Images/GroupingIcons/Set3/transform_grouping_3_512.png differ diff --git a/Gui/Resources/Stylesheets/mainstyle.qss b/Gui/Resources/Stylesheets/mainstyle.qss index 43fad56f72..065929d594 100644 --- a/Gui/Resources/Stylesheets/mainstyle.qss +++ b/Gui/Resources/Stylesheets/mainstyle.qss @@ -1,7 +1,7 @@ /* -*- mode: css -*- */ /*%1 = selection-color %2 = medium background %3 = soft background %4 = strong background %5 = text colour*/ /*%6 = interpolated value color %7 = keyframe value color %8 = disabled editable text (black) %9 = expression value color*/ -/*%10 = altered text colour*/ +/*%10 = altered text colour, %11 = mouse over selection color*/ /*You have to fill the arguments with stylesheet-representable colours.*/ QWidget { @@ -464,7 +464,6 @@ QTabBar { } QTabBar::tab { - color: %5; /* color of the text on all tabs */ background-color: %4; /* the background of non-selected tabs */ border: 1px solid ; border-bottom-color: %2; /* color of the thin line put at the bottom of non-selected tabs */ @@ -484,14 +483,12 @@ QTabBar::tab:hover { QTabBar::tab:selected { background: %2; /* the background of the selected tab */ border-bottom-style:none; - border-color: %1; + /*border-color: %1;*/ } -QTabBar::tab:selected:focus { - /*border-color: %1; */ /* color of the selected tabs when they have focus (i.e. only in the properties panels) - same as QTabBar::tab:selected.border-color */ -} QTabBar::tab:!selected { + color: %5; color of the text on deselected tabs margin-top: 2px; /* make non-selected tabs look smaller */ } @@ -520,7 +517,17 @@ QTabBar::close-button:hover { subcontrol-position:right; } +TabBar[mouseOverFocus="true"][clickFocus="false"] { + color: %11; +} + +TabBar[clickFocus="true"] { + color: %1; +} +TabBar[mouseOverFocus="false"][clickFocus="false"] { + color: %5; +} QTabWidget::tab-bar { alignment: left; @@ -547,19 +554,14 @@ QToolButton { SpinBox[animation="0"] { background-color: %3; - color: %5; } SpinBox[animation="1"] { background-color: %6; - color: %5; } SpinBox[animation="2"] { background-color: %7; - color: %5; -} -SpinBox[altered="false"] { - color: %5; } + SpinBox[animation="3"] { background-color: %9; color: black; diff --git a/Gui/RotoGui.cpp b/Gui/RotoGui.cpp index 4beb705907..121742bddd 100644 --- a/Gui/RotoGui.cpp +++ b/Gui/RotoGui.cpp @@ -3383,7 +3383,6 @@ RotoGui::RotoGuiPrivate::makeStroke(bool prepareForLater, const RotoPoint& p) itemName = kRotoPaintBurnBaseName; break; default: - assert(false); return; } @@ -3557,29 +3556,36 @@ RotoGui::keyDown(double /*time*/, didSomething = true; } else if ( _imp->state != eEventStateBuildingStroke && isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoSelectionTool, modifiers, key) ) { _imp->selectTool->handleSelection(); + didSomething = true; } else if ( _imp->state != eEventStateBuildingStroke && isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoEditTool, modifiers, key) ) { if (_imp->bezierEditionTool) { _imp->bezierEditionTool->handleSelection(); + didSomething = true; } } else if ( _imp->state != eEventStateBuildingStroke && isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoAddTool, modifiers, key) ) { if (_imp->pointsEditionTool) { _imp->pointsEditionTool->handleSelection(); + didSomething = true; } } else if ( _imp->state != eEventStateBuildingStroke && isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoBrushTool, modifiers, key) ) { if (_imp->paintBrushTool) { _imp->paintBrushTool->handleSelection(); + didSomething = true; } } else if ( _imp->state != eEventStateBuildingStroke && isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoCloneTool, modifiers, key) ) { if (_imp->cloneBrushTool) { _imp->cloneBrushTool->handleSelection(); + didSomething = true; } } else if ( _imp->state != eEventStateBuildingStroke && isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoEffectTool, modifiers, key) ) { if (_imp->effectBrushTool) { _imp->effectBrushTool->handleSelection(); + didSomething = true; } } else if ( _imp->state != eEventStateBuildingStroke && isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoColorTool, modifiers, key) ) { if (_imp->mergeBrushTool) { _imp->mergeBrushTool->handleSelection(); + didSomething = true; } } else if ( isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoNudgeRight, modifiers, key) ) { moveSelectedCpsWithKeyArrows(1,0); @@ -3607,6 +3613,7 @@ RotoGui::keyDown(double /*time*/, didSomething = true; } else if ( isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoLockCurve, modifiers, key) ) { lockSelectedCurves(); + didSomething = true; } return didSomething; @@ -4799,6 +4806,7 @@ void RotoGui::onPressureOpacityClicked(bool isDown) { _imp->pressureOpacityButton->setDown(isDown); + _imp->pressureOpacityButton->setChecked(isDown); onBreakMultiStrokeTriggered(); } @@ -4806,6 +4814,7 @@ void RotoGui::onPressureSizeClicked(bool isDown) { _imp->pressureSizeButton->setDown(isDown); + _imp->pressureSizeButton->setChecked(isDown); onBreakMultiStrokeTriggered(); } @@ -4813,6 +4822,7 @@ void RotoGui::onPressureHardnessClicked(bool isDown) { _imp->pressureHardnessButton->setDown(isDown); + _imp->pressureHardnessButton->setChecked(isDown); onBreakMultiStrokeTriggered(); } diff --git a/Gui/RotoPanel.cpp b/Gui/RotoPanel.cpp index 2b64252db1..cd1bccb745 100644 --- a/Gui/RotoPanel.cpp +++ b/Gui/RotoPanel.cpp @@ -409,7 +409,7 @@ RotoPanel::RotoPanel(const boost::shared_ptr& n, _imp->tree->setDragDropMode(QAbstractItemView::InternalMove); _imp->tree->setDragEnabled(true); _imp->tree->setExpandsOnDoubleClick(false); - _imp->tree->setAttribute(Qt::WA_MacShowFocusRect,0); + _imp->tree->setAttribute(Qt::WA_MacShowFocusRect,1); QString treeToolTip = Natron::convertFromPlainText(tr("This tree contains the hierarchy of shapes, strokes and layers along with some " "most commonly used attributes for each of them. " "Each attribute can be found in the parameters above in the panel.\n" diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp index fc4e4e1a9f..63a75b9cae 100644 --- a/Gui/ScaleSliderQWidget.cpp +++ b/Gui/ScaleSliderQWidget.cpp @@ -33,12 +33,14 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include #include +#include #include #include "Engine/Settings.h" #include "Engine/Image.h" #include "Engine/KnobTypes.h" +#include "Gui/GuiMacros.h" #include "Gui/ticks.h" #include "Gui/GuiApplicationManager.h" #include "Gui/GuiDefines.h" @@ -212,23 +214,66 @@ ScaleSliderQWidget::mouseReleaseEvent(QMouseEvent* e) } void -ScaleSliderQWidget::keyPressEvent(QKeyEvent* e) +ScaleSliderQWidget::focusInEvent(QFocusEvent* e) { - if (e->key() == Qt::Key_Control) { + Qt::KeyboardModifiers mod = qApp->keyboardModifiers(); + if (mod.testFlag(Qt::ControlModifier)) { _imp->ctrlDown = true; + } + if (mod.testFlag(Qt::ShiftModifier)) { + _imp->shiftDown = true; + } + zoomRange(); + QWidget::focusInEvent(e); +} + +void +ScaleSliderQWidget::focusOutEvent(QFocusEvent* e) +{ + _imp->ctrlDown = false; + _imp->shiftDown = false; + zoomRange(); + QWidget::focusOutEvent(e); +} + +void +ScaleSliderQWidget::zoomRange() +{ + if (_imp->ctrlDown) { double scale = _imp->shiftDown ? 100. : 10.; + assert(_imp->currentZoom != 0); + double effectiveZoom = scale / _imp->currentZoom; _imp->currentZoom = scale; - _imp->zoomCtx.zoomx(_imp->value, 0, scale); - update(); + _imp->zoomCtx.zoomx(_imp->value, 0, effectiveZoom); + } else { + _imp->zoomCtx.zoomx(_imp->value, 0, 1. / _imp->currentZoom); + _imp->currentZoom = 1.; + centerOn(_imp->minimum, _imp->maximum); + } + update(); +} + +void +ScaleSliderQWidget::enterEvent(QEvent* e) +{ + if (Gui::isFocusStealingPossible()) { + setFocus(); + } + QWidget::enterEvent(e); +} + +void +ScaleSliderQWidget::keyPressEvent(QKeyEvent* e) +{ + if (e->key() == Qt::Key_Control) { + _imp->ctrlDown = true; + zoomRange(); } else if (e->key() == Qt::Key_Shift) { _imp->shiftDown = true; - if (_imp->ctrlDown) { - _imp->zoomCtx.zoomx(_imp->value, 0, 10.); - _imp->currentZoom = 100.; - } - update(); + zoomRange(); + } else { + QWidget::keyPressEvent(e); } - QWidget::keyPressEvent(e); } double @@ -255,24 +300,13 @@ ScaleSliderQWidget::keyReleaseEvent(QKeyEvent* e) { if (e->key() == Qt::Key_Control) { _imp->ctrlDown = false; - _imp->zoomCtx.zoomx(_imp->value, 0, 1. / _imp->currentZoom); - _imp->currentZoom = 1.; - centerOn(_imp->minimum, _imp->maximum); - return; + zoomRange(); } else if (e->key() == Qt::Key_Shift) { _imp->shiftDown = false; - if (_imp->ctrlDown) { - _imp->zoomCtx.zoomx(_imp->value, 0, 1. / 10.); - _imp->currentZoom = 10.; - } else { - _imp->zoomCtx.zoomx(_imp->value, 0, 1. / _imp->currentZoom); - centerOn(_imp->minimum, _imp->maximum); - _imp->currentZoom = 1.; - return; - } - update(); + zoomRange(); + } else { + QWidget::keyReleaseEvent(e); } - QWidget::keyReleaseEvent(e); } @@ -364,6 +398,7 @@ ScaleSliderQWidget::paintEvent(QPaintEvent* /*e*/) QStyleOption opt; opt.init(this); QPainter p(this); + p.setOpacity(1); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); double txtR,txtG,txtB; @@ -483,8 +518,19 @@ ScaleSliderQWidget::paintEvent(QPaintEvent* /*e*/) p.setPen(_imp->sliderColor); p.fillRect(sliderBottomLeft.x(), sliderBottomLeft.y(), sliderTopRight.x() - sliderBottomLeft.x(), sliderTopRight.y() - sliderBottomLeft.y(),_imp->sliderColor); - /*draw a black rect around the slider for contrast*/ - p.setPen(Qt::black); + /*draw a black rect around the slider for contrast or orange when focused*/ + if (!hasFocus()) { + p.setPen(Qt::black); + } else { + QPen pen = p.pen(); + pen.setColor(QColor(243,137,0)); + QVector dashStyle; + qreal space = 2; + dashStyle << 1 << space; + pen.setDashPattern(dashStyle); + p.setOpacity(0.8); + p.setPen(pen); + } p.drawLine( sliderBottomLeft.x(),sliderBottomLeft.y(),sliderBottomLeft.x(),sliderTopRight.y() ); p.drawLine( sliderBottomLeft.x(),sliderTopRight.y(),sliderTopRight.x(),sliderTopRight.y() ); diff --git a/Gui/ScaleSliderQWidget.h b/Gui/ScaleSliderQWidget.h index 794120fb9b..0fba72f0f9 100644 --- a/Gui/ScaleSliderQWidget.h +++ b/Gui/ScaleSliderQWidget.h @@ -102,6 +102,8 @@ public Q_SLOTS: void seekScalePosition(double v); private: + + void zoomRange(); void seekInternal(double v); @@ -117,7 +119,10 @@ public Q_SLOTS: virtual QSize minimumSizeHint() const OVERRIDE FINAL; virtual void paintEvent(QPaintEvent* e) OVERRIDE FINAL; virtual void resizeEvent(QResizeEvent* e) OVERRIDE FINAL; - + virtual void enterEvent(QEvent* e) OVERRIDE FINAL; + virtual void focusInEvent(QFocusEvent* e) OVERRIDE FINAL; + virtual void focusOutEvent(QFocusEvent* e) OVERRIDE FINAL; + boost::scoped_ptr _imp; }; diff --git a/Gui/ScriptEditor.cpp b/Gui/ScriptEditor.cpp index 24b5c7dad7..e87d9b590c 100644 --- a/Gui/ScriptEditor.cpp +++ b/Gui/ScriptEditor.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "Gui/GuiApplicationManager.h" @@ -45,14 +46,13 @@ #include "Gui/SequenceFileDialog.h" #include "Gui/ScriptTextEdit.h" #include "Gui/Utils.h" +#include "Gui/ActionShortcuts.h" #include "Engine/Settings.h" struct ScriptEditorPrivate { - Gui* gui; - QVBoxLayout* mainLayout; QWidget* buttonsContainer; @@ -77,9 +77,11 @@ struct ScriptEditorPrivate QMutex autoSavedScriptMutex; QString autoSavedScript; - ScriptEditorPrivate(Gui* gui) - : gui(gui) - , mainLayout(0) + ///Indicate whether we should auto-scroll as results are printed or not + bool outputAtBottom; + + ScriptEditorPrivate() + : mainLayout(0) , buttonsContainer(0) , buttonsContainerLayout(0) , undoB(0) @@ -96,6 +98,7 @@ struct ScriptEditorPrivate , autoSaveTimer() , autoSavedScriptMutex() , autoSavedScript() + , outputAtBottom(true) { } @@ -103,7 +106,8 @@ struct ScriptEditorPrivate ScriptEditor::ScriptEditor(Gui* gui) : QWidget(gui) -, _imp(new ScriptEditorPrivate(gui)) +, PanelWidget(this,gui) +, _imp(new ScriptEditorPrivate()) { _imp->mainLayout = new QVBoxLayout(this); _imp->buttonsContainer = new QWidget(this); @@ -129,8 +133,8 @@ ScriptEditor::ScriptEditor(Gui* gui) _imp->undoB->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->undoB->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); _imp->undoB->setFocusPolicy(Qt::NoFocus); - _imp->undoB->setToolTip("

" + tr("Previous script.") + - "

" + tr("Keyboard shortcut:") + " " + undoSeq.toString(QKeySequence::NativeText) + "

"); + setTooltipWithShortcut(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorPrevScript,"

" + tr("Previous Script") + "

" + + "

" + tr("Keyboard shortcut") + ": %1

", _imp->undoB); _imp->undoB->setEnabled(false); QObject::connect(_imp->undoB, SIGNAL(clicked(bool)), this, SLOT(onUndoClicked())); @@ -139,16 +143,18 @@ ScriptEditor::ScriptEditor(Gui* gui) _imp->redoB->setFocusPolicy(Qt::NoFocus); _imp->redoB->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->redoB->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); - _imp->redoB->setToolTip("

" + tr("Next script.") + - "

" + tr("Keyboard shortcut:") + " " + redoSeq.toString(QKeySequence::NativeText) + "

"); + setTooltipWithShortcut(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorNextScript,"

" + tr("Next Script") + "

" + + "

" + tr("Keyboard shortcut") + ": %1

", _imp->redoB); _imp->redoB->setEnabled(false); QObject::connect(_imp->redoB, SIGNAL(clicked(bool)), this, SLOT(onRedoClicked())); _imp->clearHistoB = new Button(QIcon(clearHistoPix),"",_imp->buttonsContainer); - _imp->clearHistoB->setToolTip(Natron::convertFromPlainText(tr("Clear history."), Qt::WhiteSpaceNormal)); _imp->clearHistoB->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->clearHistoB->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); _imp->clearHistoB->setFocusPolicy(Qt::NoFocus); + setTooltipWithShortcut(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorClearHistory,"

" + tr("Clear History") + "

" + + "

" + tr("Keyboard shortcut") + ": %1

", _imp->clearHistoB); + QObject::connect(_imp->clearHistoB, SIGNAL(clicked(bool)), this, SLOT(onClearHistoryClicked())); _imp->sourceScriptB = new Button(QIcon(sourceScriptPix),"",_imp->buttonsContainer); @@ -177,8 +183,8 @@ ScriptEditor::ScriptEditor(Gui* gui) _imp->execScriptB->setFocusPolicy(Qt::NoFocus); _imp->execScriptB->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->execScriptB->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); - _imp->execScriptB->setToolTip("

" + tr("Execute the current script.") - + "

" + tr("Keyboard shortcut:") + " " + execSeq.toString(QKeySequence::NativeText) + "

"); + setTooltipWithShortcut(kShortcutGroupScriptEditor, kShortcutIDActionScriptExecScript,"

" + tr("Execute the current script") + "

" + + "

" + tr("Keyboard shortcut") + ": %1

", _imp->execScriptB); QObject::connect(_imp->execScriptB, SIGNAL(clicked(bool)), this, SLOT(onExecScriptClicked())); @@ -186,7 +192,8 @@ ScriptEditor::ScriptEditor(Gui* gui) icShowHide.addPixmap(outputVisiblePix,QIcon::Normal,QIcon::On); icShowHide.addPixmap(outputHiddenPix,QIcon::Normal,QIcon::Off); _imp->showHideOutputB = new Button(icShowHide,"",_imp->buttonsContainer); - _imp->showHideOutputB->setToolTip(Natron::convertFromPlainText(tr("Show/Hide the output area."), Qt::WhiteSpaceNormal)); + setTooltipWithShortcut(kShortcutGroupScriptEditor, kShortcutIDActionScriptShowOutput,"

" + tr("Show/Hide the output area") + "

" + + "

" + tr("Keyboard shortcut") + ": %1

", _imp->showHideOutputB); _imp->showHideOutputB->setFocusPolicy(Qt::NoFocus); _imp->showHideOutputB->setCheckable(true); _imp->showHideOutputB->setChecked(true); @@ -200,8 +207,8 @@ ScriptEditor::ScriptEditor(Gui* gui) _imp->clearOutputB->setFocusPolicy(Qt::NoFocus); _imp->clearOutputB->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->clearOutputB->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); - _imp->clearOutputB->setToolTip("

" + tr("Clear the output area") - + "

" + tr("Keyboard shortcut:") + " " + clearSeq.toString(QKeySequence::NativeText) + "

"); + setTooltipWithShortcut(kShortcutGroupScriptEditor, kShortcutIDActionScriptClearOutput,"

" + tr("Clear the output area") + "

" + + "

" + tr("Keyboard shortcut") + ": %1

", _imp->clearOutputB); QObject::connect(_imp->clearOutputB, SIGNAL(clicked(bool)), this, SLOT(onClearOutputClicked())); _imp->buttonsContainerLayout->addWidget(_imp->undoB); @@ -221,15 +228,23 @@ ScriptEditor::ScriptEditor(Gui* gui) QSplitter* splitter = new QSplitter(Qt::Vertical,this); + QFont scriptFont("Courier New", 12); + if (!scriptFont.exactMatch()) { + scriptFont = font(); + } + _imp->outputEdit = new ScriptTextEdit(this); + QObject::connect(_imp->outputEdit, SIGNAL(userScrollChanged(bool)), this, SLOT(onUserScrollChanged(bool))); _imp->outputEdit->setOutput(true); _imp->outputEdit->setFocusPolicy(Qt::ClickFocus); _imp->outputEdit->setReadOnly(true); + _imp->outputEdit->setFont(scriptFont); _imp->inputEdit = new ScriptTextEdit(this); QObject::connect(_imp->inputEdit, SIGNAL(textChanged()), this, SLOT(onInputScriptTextChanged())); QFontMetrics fm = _imp->inputEdit->fontMetrics(); _imp->inputEdit->setTabStopWidth(fm.width(' ') * 4); + _imp->inputEdit->setFont(scriptFont); _imp->outputEdit->setTabStopWidth(fm.width(' ') * 4); _imp->mainLayout->addWidget(_imp->buttonsContainer); @@ -298,13 +313,13 @@ ScriptEditor::onSourceScriptClicked() { std::vector filters; filters.push_back("py"); - SequenceFileDialog dialog(this,filters,false,SequenceFileDialog::eFileDialogModeOpen,_imp->gui->getLastLoadProjectDirectory().toStdString(), - _imp->gui,false); + SequenceFileDialog dialog(this,filters,false,SequenceFileDialog::eFileDialogModeOpen,getGui()->getLastLoadProjectDirectory().toStdString(), + getGui(),false); if (dialog.exec()) { QDir currentDir = dialog.currentDirectory(); - _imp->gui->updateLastOpenedProjectPath(currentDir.absolutePath()); + getGui()->updateLastOpenedProjectPath(currentDir.absolutePath()); QString fileName(dialog.selectedFiles().c_str()); QFile file(fileName); @@ -325,13 +340,13 @@ ScriptEditor::onLoadScriptClicked() { std::vector filters; filters.push_back("py"); - SequenceFileDialog dialog(this,filters,false,SequenceFileDialog::eFileDialogModeOpen,_imp->gui->getLastLoadProjectDirectory().toStdString(), - _imp->gui,false); + SequenceFileDialog dialog(this,filters,false,SequenceFileDialog::eFileDialogModeOpen,getGui()->getLastLoadProjectDirectory().toStdString(), + getGui(),false); if (dialog.exec()) { QDir currentDir = dialog.currentDirectory(); - _imp->gui->updateLastOpenedProjectPath(currentDir.absolutePath()); + getGui()->updateLastOpenedProjectPath(currentDir.absolutePath()); QString fileName(dialog.selectedFiles().c_str()); QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { @@ -350,13 +365,13 @@ ScriptEditor::onSaveScriptClicked() { std::vector filters; filters.push_back("py"); - SequenceFileDialog dialog(this,filters,false,SequenceFileDialog::eFileDialogModeSave,_imp->gui->getLastSaveProjectDirectory().toStdString(), - _imp->gui,false); + SequenceFileDialog dialog(this,filters,false,SequenceFileDialog::eFileDialogModeSave,getGui()->getLastSaveProjectDirectory().toStdString(), + getGui(),false); if (dialog.exec()) { QDir currentDir = dialog.currentDirectory(); - _imp->gui->updateLastSavedProjectPath(currentDir.absolutePath()); + getGui()->updateLastSavedProjectPath(currentDir.absolutePath()); QString fileName(dialog.selectedFiles().c_str()); QFile file(fileName); @@ -429,6 +444,9 @@ ScriptEditor::onExecScriptClicked() toAppend.append(output.c_str()); } _imp->outputEdit->append(toAppend); + if (_imp->outputAtBottom) { + _imp->outputEdit->verticalScrollBar()->setValue(_imp->outputEdit->verticalScrollBar()->maximum()); + } _imp->history.push(new InputScriptCommand(this,script)); _imp->inputEdit->clear(); } @@ -449,22 +467,44 @@ ScriptEditor::getInputScript() const return _imp->inputEdit->toPlainText(); } +void +ScriptEditor::mousePressEvent(QMouseEvent* e) +{ + takeClickFocus(); + QWidget::mousePressEvent(e); +} + void ScriptEditor::keyPressEvent(QKeyEvent* e) { + + bool accept = true; + Qt::Key key = (Qt::Key)e->key(); - if (key == Qt::Key_BracketLeft && modCASIsControl(e)) { + Qt::KeyboardModifiers modifiers = e->modifiers(); + + if (isKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorPrevScript, modifiers, key)) { onUndoClicked(); - } else if (key == Qt::Key_BracketRight && modifierHasControl(e)) { + } else if (isKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorNextScript, modifiers, key)) { onRedoClicked(); - } else if ((key == Qt::Key_Return || key == Qt::Key_Enter) && modifierHasControl(e)) { + } else if (isKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptExecScript, modifiers, key)) { onExecScriptClicked(); - } else if (key == Qt::Key_Backspace && modifierHasControl(e)) { + } else if (isKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptClearOutput, modifiers, key)) { onClearOutputClicked(); + } else if (isKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptEditorClearHistory, modifiers, key)) { + onClearHistoryClicked(); + } else if (isKeybind(kShortcutGroupScriptEditor, kShortcutIDActionScriptShowOutput, modifiers, key)) { + onShowHideOutputClicked(!_imp->showHideOutputB->isChecked()); } else { + accept = false; QWidget::keyPressEvent(e); } - + if (accept) { + takeClickFocus(); + e->accept(); + } else { + handleUnCaughtKeyPressEvent(); + } } void @@ -493,6 +533,9 @@ void ScriptEditor::appendToScriptEditor(const QString& str) { _imp->outputEdit->append(str + "\n"); + if (_imp->outputAtBottom) { + _imp->outputEdit->verticalScrollBar()->setValue(_imp->outputEdit->verticalScrollBar()->maximum()); + } } void @@ -504,4 +547,27 @@ ScriptEditor::printAutoDeclaredVariable(const QString& str) QString cpy = str; cpy.replace("\n", "
"); _imp->outputEdit->append("" + cpy + ""); + if (_imp->outputAtBottom) { + _imp->outputEdit->verticalScrollBar()->setValue(_imp->outputEdit->verticalScrollBar()->maximum()); + } +} + +void +ScriptEditor::enterEvent(QEvent *e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} + +void +ScriptEditor::leaveEvent(QEvent *e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); +} + +void +ScriptEditor::onUserScrollChanged(bool atBottom) +{ + _imp->outputAtBottom = atBottom; } diff --git a/Gui/ScriptEditor.h b/Gui/ScriptEditor.h index 3441eedd04..37708b8176 100644 --- a/Gui/ScriptEditor.h +++ b/Gui/ScriptEditor.h @@ -38,13 +38,13 @@ CLANG_DIAG_OFF(uninitialized) CLANG_DIAG_ON(deprecated) CLANG_DIAG_ON(uninitialized) -#include "Engine/ScriptObject.h" +#include "Gui/PanelWidget.h" class Gui; struct ScriptEditorPrivate; -class ScriptEditor : public QWidget, public ScriptObject +class ScriptEditor : public QWidget, public PanelWidget { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -67,6 +67,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void printAutoDeclaredVariable(const QString& str); public Q_SLOTS: + + void onUserScrollChanged(bool atBottom); void onShowHideOutputClicked(bool clicked); @@ -95,6 +97,9 @@ public Q_SLOTS: private: + virtual void mousePressEvent(QMouseEvent* e) OVERRIDE FINAL; + virtual void enterEvent(QEvent *e) OVERRIDE FINAL; + virtual void leaveEvent(QEvent *e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; boost::scoped_ptr _imp; diff --git a/Gui/ScriptTextEdit.cpp b/Gui/ScriptTextEdit.cpp index 816a35be4c..dbb24cb851 100644 --- a/Gui/ScriptTextEdit.cpp +++ b/Gui/ScriptTextEdit.cpp @@ -31,6 +31,7 @@ CLANG_DIAG_OFF(deprecated) CLANG_DIAG_OFF(uninitialized) #include #include +#include CLANG_DIAG_ON(deprecated) CLANG_DIAG_ON(uninitialized) @@ -140,3 +141,20 @@ ScriptTextEdit::leaveEvent(QEvent* /*e*/) } } +void +ScriptTextEdit::showEvent(QShowEvent* e) +{ + QTextEdit::showEvent(e); + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + Q_EMIT userScrollChanged(true); +} + +void +ScriptTextEdit::scrollContentsBy(int dx, int dy) +{ + QTextEdit::scrollContentsBy(dx, dy); + QScrollBar* sb = verticalScrollBar(); + int v = sb->value(); + int max = sb->maximum(); + Q_EMIT userScrollChanged(v == max); +} diff --git a/Gui/ScriptTextEdit.h b/Gui/ScriptTextEdit.h index ce2cf550a4..6901756594 100644 --- a/Gui/ScriptTextEdit.h +++ b/Gui/ScriptTextEdit.h @@ -50,8 +50,15 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void setOutput(bool o); bool getOutput() const; + +Q_SIGNALS: + + void userScrollChanged(bool atBottom); + private: + virtual void showEvent(QShowEvent* e) OVERRIDE FINAL; + virtual void dragEnterEvent(QDragEnterEvent* e) OVERRIDE FINAL; virtual void dropEvent(QDropEvent* e) OVERRIDE FINAL; @@ -62,6 +69,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual void leaveEvent(QEvent* e) OVERRIDE FINAL; + virtual void scrollContentsBy(int dx, int dy) OVERRIDE FINAL; + bool isOutput; }; diff --git a/Gui/SpinBox.cpp b/Gui/SpinBox.cpp index 366997ff0a..60b5d7f43a 100644 --- a/Gui/SpinBox.cpp +++ b/Gui/SpinBox.cpp @@ -239,14 +239,7 @@ SpinBox::interpretReturn() } } -/* - void - SpinBox::mousePressEvent(QMouseEvent* e) - { - //setCursorPosition(cursorPositionAt(e->pos())); // LineEdit::mousePressEvent(e) does this already - LineEdit::mousePressEvent(e); - } - */ + QString SpinBoxPrivate::setNum(double cur) diff --git a/Gui/SpinBox.h b/Gui/SpinBox.h index 82bd328dfa..1eef3a4606 100644 --- a/Gui/SpinBox.h +++ b/Gui/SpinBox.h @@ -101,7 +101,6 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON virtual void wheelEvent(QWheelEvent* e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; - //virtual void mousePressEvent(QMouseEvent* e) OVERRIDE FINAL; virtual void focusInEvent(QFocusEvent* e) OVERRIDE FINAL; virtual void focusOutEvent(QFocusEvent* e) OVERRIDE FINAL; virtual void paintEvent(QPaintEvent* e) OVERRIDE FINAL; diff --git a/Gui/TabWidget.cpp b/Gui/TabWidget.cpp index 080fc7c52e..43e1314040 100644 --- a/Gui/TabWidget.cpp +++ b/Gui/TabWidget.cpp @@ -36,6 +36,7 @@ CLANG_DIAG_OFF(deprecated) #include #include #include +#include #include GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF // /opt/local/include/QtGui/qmime.h:119:10: warning: private field 'type' is not used [-Wunused-private-field] @@ -47,6 +48,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include CLANG_DIAG_ON(deprecated) +#include "Engine/Node.h" #include "Engine/Project.h" #include "Engine/ScriptObject.h" #include "Engine/ViewerInstance.h" @@ -185,7 +187,7 @@ struct TabWidgetPrivate TabWidget* _publicInterface; Gui* gui; QVBoxLayout* mainLayout; - std::vector > tabs; // the actual tabs + std::vector > tabs; // the actual tabs QWidget* header; QHBoxLayout* headerLayout; bool modifyingTabBar; @@ -193,7 +195,7 @@ struct TabWidgetPrivate Button* leftCornerButton; Button* floatButton; Button* closeButton; - QWidget* currentWidget; + PanelWidget* currentWidget; bool drawDropRect; bool fullScreen; bool isAnchor; @@ -229,8 +231,8 @@ struct TabWidgetPrivate } - void declareTabToPython(QWidget* widget,const std::string& tabName); - void removeTabToPython(QWidget* widget,const std::string& tabName); + void declareTabToPython(PanelWidget* widget,const std::string& tabName); + void removeTabToPython(PanelWidget* widget,const std::string& tabName); }; TabWidget::TabWidget(Gui* gui, @@ -322,7 +324,7 @@ TabWidget::count() const return _imp->tabs.size(); } -QWidget* +PanelWidget* TabWidget::tabAt(int index) const { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -333,7 +335,7 @@ TabWidget::tabAt(int index) const } void -TabWidget::tabAt(int index, QWidget** w, ScriptObject** obj) const +TabWidget::tabAt(int index, PanelWidget** w, ScriptObject** obj) const { QMutexLocker l(&_imp->tabWidgetStateMutex); if (index < 0 || index >= (int)_imp->tabs.size()) { @@ -640,7 +642,7 @@ TabWidget::closePane() if (tabToTransferTo) { ///move this tab's splits while (count() > 0) { - QWidget* w; + PanelWidget* w; ScriptObject* o; tabAt(0,&w,&o); if (w && o) { @@ -649,7 +651,7 @@ TabWidget::closePane() } } else { while (count() > 0) { - QWidget* w = tabAt(0); + PanelWidget* w = tabAt(0); if (w) { removeTab( w, true ); } @@ -763,7 +765,7 @@ TabWidget::getTabLabel(int index) const } QString -TabWidget::getTabLabel(QWidget* tab) const +TabWidget::getTabLabel(PanelWidget* tab) const { QMutexLocker k(&_imp->tabWidgetStateMutex); for (U32 i = 0; i < _imp->tabs.size(); ++i) { @@ -775,7 +777,7 @@ TabWidget::getTabLabel(QWidget* tab) const } void -TabWidget::setTabLabel(QWidget* tab, +TabWidget::setTabLabel(PanelWidget* tab, const QString & name) { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -799,7 +801,7 @@ TabWidget::floatCurrentWidget() _imp->gui->registerPane(newPane); newPane->setObjectName_mt_safe( _imp->gui->getAvailablePaneName() ); - QWidget* w; + PanelWidget* w; ScriptObject* o; currentWidget(&w, &o); if (w && o) { @@ -815,7 +817,7 @@ TabWidget::floatCurrentWidget() void TabWidget::closeCurrentWidget() { - QWidget* w = currentWidget(); + PanelWidget* w = currentWidget(); if (!w) { return; } @@ -827,7 +829,7 @@ void TabWidget::closeTab(int index) { - QWidget *w = tabAt(index); + PanelWidget *w = tabAt(index); if (!w) { return; } @@ -945,14 +947,14 @@ TabWidget::splitVertically(bool autoSave) } bool -TabWidget::appendTab(QWidget* widget, ScriptObject* object) +TabWidget::appendTab(PanelWidget* widget, ScriptObject* object) { return appendTab(QIcon(),widget,object); } bool TabWidget::appendTab(const QIcon & icon, - QWidget* widget, + PanelWidget* widget, ScriptObject* object) { { @@ -997,7 +999,7 @@ TabWidget::appendTab(const QIcon & icon, void TabWidget::insertTab(int index, const QIcon & icon, - QWidget* widget, + PanelWidget* widget, ScriptObject* object) { @@ -1014,8 +1016,10 @@ TabWidget::insertTab(int index, _imp->tabBar->insertTab(index,icon,title); _imp->tabBar->updateGeometry(); //< necessary _imp->modifyingTabBar = false; - if (!widget->isVisible()) { - widget->setVisible(true); + + QWidget* w = widget->getWidget(); + if (!w->isVisible()) { + w->setVisible(true); } l.unlock(); @@ -1030,22 +1034,62 @@ TabWidget::insertTab(int index, void TabWidget::insertTab(int index, - QWidget* widget, + PanelWidget* widget, ScriptObject* object) { insertTab(index, QIcon(), widget,object); } -QWidget* +PanelWidget* TabWidget::removeTab(int index,bool userAction) { - QWidget* tab; + PanelWidget* tab; ScriptObject* obj; tabAt(index,&tab,&obj); if (!tab || !obj) { return 0; } + ViewerTab* isViewer = dynamic_cast(tab); + Histogram* isHisto = dynamic_cast(tab); + NodeGraph* isGraph = dynamic_cast(tab); + + int newIndex = -1; + if (isGraph) { + /* + If the tab is a group pane, try to find the parent group pane in this pane and set it active + */ + boost::shared_ptr collect = isGraph->getGroup(); + assert(collect); + NodeGroup* isGroup = dynamic_cast(collect.get()); + if (isGroup) { + collect = isGroup->getNode()->getGroup(); + if (collect) { + + NodeGraph* parentGraph = 0; + isGroup = dynamic_cast(collect.get()); + if (isGroup) { + parentGraph = dynamic_cast(isGroup->getNodeGraph()); + } else { + parentGraph = getGui()->getNodeGraph(); + } + assert(parentGraph); + for (std::size_t i = 0; i < _imp->tabs.size(); ++i) { + if (_imp->tabs[i].first == parentGraph) { + newIndex = i; + break; + } + } + } + } + } + + QWidget* w = tab->getWidget(); + + if (_imp->tabBar->hasClickFocus() && _imp->currentWidget == tab) { + tab->removeClickFocus(); + } + { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -1055,7 +1099,13 @@ TabWidget::removeTab(int index,bool userAction) _imp->modifyingTabBar = false; if (_imp->tabs.size() > 0) { l.unlock(); - makeCurrentTab((index - 1 >= 0) ? index - 1 : 0); + int i; + if (newIndex == -1) { + i = (index - 1 >= 0) ? index - 1 : 0; + } else { + i = newIndex; + } + makeCurrentTab(i); l.relock(); } else { _imp->currentWidget = 0; @@ -1070,12 +1120,10 @@ TabWidget::removeTab(int index,bool userAction) } //tab->setParent(_imp->gui); } - ViewerTab* isViewer = dynamic_cast(tab); - Histogram* isHisto = dynamic_cast(tab); - NodeGraph* isGraph = dynamic_cast(tab); + _imp->removeTabToPython(tab, obj->getScriptName()); - + if (userAction) { if (isViewer) { @@ -1088,22 +1136,22 @@ TabWidget::removeTab(int index,bool userAction) return tab; } else { ///Do not delete unique widgets such as the properties bin, node graph or curve editor - tab->setVisible(false); + w->setVisible(false); } } else { - tab->setVisible(false); + w->setVisible(false); } if (isGraph && _imp->gui->getLastSelectedGraph() == isGraph) { _imp->gui->setLastSelectedGraph(0); } - tab->setParent(_imp->gui); + w->setParent(_imp->gui); return tab; } void -TabWidget::removeTab(QWidget* widget,bool userAction) +TabWidget::removeTab(PanelWidget* widget,bool userAction) { int index = -1; @@ -1118,14 +1166,14 @@ TabWidget::removeTab(QWidget* widget,bool userAction) } if (index != -1) { - QWidget* tab = removeTab(index,userAction); + PanelWidget* tab = removeTab(index,userAction); assert(tab == widget); Q_UNUSED(tab); } } void -TabWidget::setCurrentWidget(QWidget* w) +TabWidget::setCurrentWidget(PanelWidget* w) { int index = -1; { @@ -1148,45 +1196,64 @@ TabWidget::makeCurrentTab(int index) if (_imp->modifyingTabBar) { return; } - QWidget* tab = tabAt(index); + PanelWidget* tab = tabAt(index); if (!tab) { return; } + QWidget* tabW = tab->getWidget(); + + PanelWidget* curWidget; + { + QMutexLocker l(&_imp->tabWidgetStateMutex); + curWidget = _imp->currentWidget; + } + bool hadFocus = false; + if (curWidget && _imp->tabBar->hasClickFocus()) { + hadFocus = true; + curWidget->removeClickFocus(); + } { QMutexLocker l(&_imp->tabWidgetStateMutex); /*Remove previous widget if any*/ if (_imp->currentWidget) { - QObject::disconnect(_imp->currentWidget, SIGNAL(destroyed()), this, SLOT(onCurrentTabDeleted())); - _imp->currentWidget->setVisible(false); - _imp->mainLayout->removeWidget(_imp->currentWidget); + QWidget* w = _imp->currentWidget->getWidget(); + QObject::disconnect(w, SIGNAL(destroyed()), this, SLOT(onCurrentTabDeleted())); + w->setVisible(false); + _imp->mainLayout->removeWidget(w); } - - _imp->mainLayout->addWidget(tab); - QObject::connect(tab, SIGNAL(destroyed()), this, SLOT(onCurrentTabDeleted())); _imp->currentWidget = tab; + curWidget = _imp->currentWidget; } - tab->setVisible(true); + + _imp->mainLayout->addWidget(tabW); + QObject::connect(tabW, SIGNAL(destroyed()), this, SLOT(onCurrentTabDeleted())); + + tabW->setVisible(true); //tab->setParent(this); _imp->modifyingTabBar = true; _imp->tabBar->setCurrentIndex(index); _imp->modifyingTabBar = false; + + if (hadFocus) { + curWidget->takeClickFocus(); + } } - -void + + void TabWidget::onCurrentTabDeleted() { QObject* s = sender(); QMutexLocker l(&_imp->tabWidgetStateMutex); - if (s != _imp->currentWidget) { + if (!_imp->currentWidget || (s != _imp->currentWidget->getWidget())) { return; } _imp->currentWidget = 0; for (U32 i = 0; i < _imp->tabs.size(); ++i) { - if (_imp->tabs[i].first == s) { + if (_imp->tabs[i].first->getWidget() == s) { _imp->tabs.erase(_imp->tabs.begin() + i); _imp->modifyingTabBar = true; @@ -1248,7 +1315,7 @@ TabWidget::dropEvent(QDropEvent* e) { e->accept(); QString name( e->mimeData()->data("Tab") ); - QWidget* w; + PanelWidget* w; ScriptObject* obj; _imp->gui->findExistingTab(name.toStdString(), &w, &obj); if (w && obj) { @@ -1271,16 +1338,36 @@ TabWidget::dropEvent(QDropEvent* e) TabBar::TabBar(TabWidget* tabWidget, QWidget* parent) - : QTabBar(parent) - , _dragPos() - , _dragPix(0) - , _tabWidget(tabWidget) - , _processingLeaveEvent(false) + : QTabBar(parent) + , _dragPos() + , _dragPix(0) + , _tabWidget(tabWidget) + , _processingLeaveEvent(false) + , mouseOverFocus(false) + , clickFocus(false) { setTabsClosable(true); setMouseTracking(true); QObject::connect( this, SIGNAL( tabCloseRequested(int) ), tabWidget, SLOT( closeTab(int) ) ); } + +void +TabBar::setMouseOverFocus(bool focus) +{ + mouseOverFocus = focus; + style()->unpolish(this); + style()->polish(this); + update(); +} + +void +TabBar::setClickFocus(bool focus) +{ + clickFocus = focus; + style()->unpolish(this); + style()->polish(this); + update(); +} void TabBar::mousePressEvent(QMouseEvent* e) @@ -1411,7 +1498,7 @@ TabBar::makePixmapForDrag(int index) ///insert just the tab we want to screen shot addTab(tabs[index].second, tabs[index].first); - QPixmap currentTabPixmap = Gui::screenShot( _tabWidget->tabAt(index) ); + QPixmap currentTabPixmap = Gui::screenShot( _tabWidget->tabAt(index)->getWidget() ); #if QT_VERSION < 0x050000 QPixmap tabBarPixmap = QPixmap::grabWidget(this); #else @@ -1491,7 +1578,7 @@ TabWidget::stopDragTab(const QPoint & globalPos) } QSize draggedPanelSize; - QWidget* draggedPanel = _imp->gui->stopDragPanel(&draggedPanelSize); + PanelWidget* draggedPanel = _imp->gui->stopDragPanel(&draggedPanelSize); if (!draggedPanel) { return false; } @@ -1589,17 +1676,17 @@ TabWidget::stopDragTab(const QPoint & globalPos) void TabWidget::startDragTab(int index) { - QWidget* selectedTab = tabAt(index); + PanelWidget* selectedTab = tabAt(index); if (!selectedTab) { return; } - - selectedTab->setParent(this); + QWidget* w = selectedTab->getWidget(); + w->setParent(this); _imp->gui->startDragPanel(selectedTab); removeTab(selectedTab, false); - selectedTab->hide(); + w->hide(); } void @@ -1691,46 +1778,60 @@ TabWidget::setAsAnchor(bool anchor) update(); } } + +void +TabWidget::togglePaneFullScreen() +{ + bool oldFullScreen; + { + QMutexLocker l(&_imp->tabWidgetStateMutex); + oldFullScreen = _imp->fullScreen; + _imp->fullScreen = !_imp->fullScreen; + } + + if (oldFullScreen) { + _imp->gui->minimize(); + } else { + _imp->gui->maximize(this); + } +} void TabWidget::keyPressEvent (QKeyEvent* e) { - if ( (e->key() == Qt::Key_Space) && modCASIsNone(e) ) { - bool fullScreen; - { - QMutexLocker l(&_imp->tabWidgetStateMutex); - fullScreen = _imp->fullScreen; - } - { - QMutexLocker l(&_imp->tabWidgetStateMutex); - _imp->fullScreen = !_imp->fullScreen; - } - if (fullScreen) { - _imp->gui->minimize(); - } else { - _imp->gui->maximize(this); - } - e->accept(); - } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionNextTab, e->modifiers(), e->key())) { + + Qt::KeyboardModifiers modifiers = e->modifiers(); + Qt::Key key = (Qt::Key)e->key(); + + bool accepted = true; + + if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionShowPaneFullScreen, modifiers, key) ) { + togglePaneFullScreen(); + } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionNextTab, modifiers, key)) { moveToNextTab(); - } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionPrevTab, e->modifiers(), e->key())) { + } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionPrevTab, modifiers, key)) { moveToPreviousTab(); - }else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionCloseTab, e->modifiers(), e->key())) { + } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionCloseTab, modifiers, key)) { closeCurrentWidget(); - } else if (isFloatingWindowChild() && isKeybind(kShortcutGroupGlobal, kShortcutIDActionFullscreen, e->modifiers(), e->key())) { + } else if (isFloatingWindowChild() && isKeybind(kShortcutGroupGlobal, kShortcutIDActionFullscreen, modifiers, key)) { _imp->gui->toggleFullScreen(); - e->accept(); } else { + accepted = false; QFrame::keyPressEvent(e); } + if (accepted) { + if (_imp->currentWidget) { + _imp->currentWidget->takeClickFocus(); + } + } } bool -TabWidget::moveTab(QWidget* what, +TabWidget::moveTab(PanelWidget* what, ScriptObject* obj, TabWidget *where) { - TabWidget* from = dynamic_cast( what->parentWidget() ); + TabWidget* from = dynamic_cast( what->getWidget()->parentWidget() ); if (from == where) { /*We check that even if it is the same TabWidget, it really exists.*/ @@ -1747,12 +1848,20 @@ TabWidget::moveTab(QWidget* what, //it wasn't found somehow } + bool hadClickFocus = false; if (from) { + hadClickFocus = from->getGui()->getCurrentPanelFocus() == what; + + ///This line will remove click focus from->removeTab(what, false); } assert(where); + where->appendTab(what,obj); - //what->setParent(where); + if (hadClickFocus) { + where->setWidgetClickFocus(what, true); + } + if ( !where->getGui()->isAboutToClose() && !where->getGui()->getApp()->getProject()->isLoadingProject() ) { where->getGui()->getApp()->triggerAutoSave(); } @@ -1798,7 +1907,7 @@ TabWidget::getTabScriptNames() const return ret; } -QWidget* +PanelWidget* TabWidget::currentWidget() const { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -1807,7 +1916,7 @@ TabWidget::currentWidget() const } void -TabWidget::currentWidget(QWidget** w,ScriptObject** obj) const +TabWidget::currentWidget(PanelWidget** w,ScriptObject** obj) const { QMutexLocker l(&_imp->tabWidgetStateMutex); *w = _imp->currentWidget; @@ -1819,6 +1928,26 @@ TabWidget::currentWidget(QWidget** w,ScriptObject** obj) const } *obj = 0; } + +void +TabWidget::setWidgetMouseOverFocus(PanelWidget* w, bool hoverFocus) +{ + assert(QThread::currentThread() == qApp->thread()); + if (w == _imp->currentWidget) { + _imp->tabBar->setMouseOverFocus(hoverFocus); + } + +} + +void +TabWidget::setWidgetClickFocus(PanelWidget* w, bool clickFocus) +{ + assert(QThread::currentThread() == qApp->thread()); + if (w == _imp->currentWidget) { + _imp->tabBar->setClickFocus(clickFocus); + } + +} int TabWidget::activeIndex() const @@ -1944,7 +2073,7 @@ TabWidget::onTabBarMouseLeft() } void -TabWidget::onTabScriptNameChanged(QWidget* tab,const std::string& oldName,const std::string& newName) +TabWidget::onTabScriptNameChanged(PanelWidget* tab,const std::string& oldName,const std::string& newName) { ViewerTab* isViewer = dynamic_cast(tab); if (!isViewer) { @@ -1970,7 +2099,7 @@ TabWidget::onTabScriptNameChanged(QWidget* tab,const std::string& oldName,const } void -TabWidgetPrivate::declareTabToPython(QWidget* widget,const std::string& tabName) +TabWidgetPrivate::declareTabToPython(PanelWidget* widget,const std::string& tabName) { ViewerTab* isViewer = dynamic_cast(widget); PyPanel* isPanel = dynamic_cast(widget); @@ -2001,7 +2130,7 @@ TabWidgetPrivate::declareTabToPython(QWidget* widget,const std::string& tabName) } void -TabWidgetPrivate::removeTabToPython(QWidget* widget,const std::string& tabName) +TabWidgetPrivate::removeTabToPython(PanelWidget* widget,const std::string& tabName) { ViewerTab* isViewer = dynamic_cast(widget); PyPanel* isPanel = dynamic_cast(widget); diff --git a/Gui/TabWidget.h b/Gui/TabWidget.h index c9e9332f20..40a9f2f2f0 100644 --- a/Gui/TabWidget.h +++ b/Gui/TabWidget.h @@ -61,7 +61,7 @@ class QPaintEvent; class Gui; class ScriptObject; class Splitter; - +class PanelWidget; class DragPixmap : public QWidget @@ -91,7 +91,10 @@ class TabBar GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT GCC_DIAG_SUGGEST_OVERRIDE_ON - + + Q_PROPERTY(bool mouseOverFocus READ hasMouseOverFocus WRITE setMouseOverFocus) + Q_PROPERTY(bool clickFocus READ hasClickFocus WRITE setClickFocus) + public: explicit TabBar(TabWidget* tabWidget, @@ -101,6 +104,18 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON { } + bool hasMouseOverFocus() const + { + return mouseOverFocus; + } + + bool hasClickFocus() const + { + return clickFocus; + } + + void setMouseOverFocus(bool focus); + void setClickFocus(bool focus); Q_SIGNALS: @@ -117,6 +132,9 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON DragPixmap* _dragPix; TabWidget* _tabWidget; // ptr to the tabWidget bool _processingLeaveEvent; // to avoid recursions in leaveEvent + + bool mouseOverFocus; + bool clickFocus; }; class TabWidgetHeader : public QWidget @@ -179,51 +197,54 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON /*Appends a new tab to the tab widget. The name of the tab will be the QWidget's object's name. Returns false if the object's name is empty but the TabWidget needs a decoration*/ - bool appendTab(QWidget* widget, ScriptObject* object); + bool appendTab(PanelWidget* widget, ScriptObject* object); /*Appends a new tab to the tab widget. The name of the tab will be the QWidget's object's name. Returns false if the title is empty but the TabWidget needs a decoration*/ - bool appendTab(const QIcon & icon,QWidget* widget, ScriptObject* object); + bool appendTab(const QIcon & icon,PanelWidget* widget, ScriptObject* object); /*Inserts before the element at index.*/ - void insertTab(int index,const QIcon & icon,QWidget* widget, ScriptObject* object); + void insertTab(int index,const QIcon & icon,PanelWidget* widget, ScriptObject* object); - void insertTab(int index,QWidget* widget, ScriptObject* object); + void insertTab(int index,PanelWidget* widget, ScriptObject* object); /*Removes from the TabWidget, but does not delete the widget. Returns NULL if the index is not in a good range.*/ - QWidget* removeTab(int index,bool userAction); + PanelWidget* removeTab(int index,bool userAction); /*Get the header name of the tab at index "index".*/ QString getTabLabel(int index) const; /*Convenience function*/ - QString getTabLabel(QWidget* tab) const; + QString getTabLabel(PanelWidget* tab) const; - void setTabLabel(QWidget* tab,const QString & name); + void setTabLabel(PanelWidget* tab,const QString & name); /*Removes from the TabWidget, but does not delete the widget.*/ - void removeTab(QWidget* widget,bool userAction); + void removeTab(PanelWidget* widget,bool userAction); int count() const; - QWidget* tabAt(int index) const; + PanelWidget* tabAt(int index) const; - void tabAt(int index, QWidget** w, ScriptObject** obj) const; + void tabAt(int index, PanelWidget** w, ScriptObject** obj) const; QStringList getTabScriptNames() const; - QWidget* currentWidget() const; + PanelWidget* currentWidget() const; + + void currentWidget(PanelWidget** w,ScriptObject** obj) const; - void currentWidget(QWidget** w,ScriptObject** obj) const; + void setWidgetMouseOverFocus(PanelWidget* w, bool hoverFocus); + void setWidgetClickFocus(PanelWidget* w, bool clickFocus); /** * @brief Set w as the current widget of the tab **/ - void setCurrentWidget(QWidget* w); + void setCurrentWidget(PanelWidget* w); void dettachTabs(); - static bool moveTab(QWidget* what, ScriptObject* obj,TabWidget* where); + static bool moveTab(PanelWidget* what, ScriptObject* obj,TabWidget* where); /** * @brief Starts dragging the selected panel. The following actions are performed: @@ -253,6 +274,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON ///MT-Safe bool isFullScreen() const; + + void togglePaneFullScreen(); ///MT-Safe bool isAnchor() const; @@ -266,7 +289,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void discardGuiPointer(); - void onTabScriptNameChanged(QWidget* tab,const std::string& oldName,const std::string& newName); + void onTabScriptNameChanged(PanelWidget* tab,const std::string& oldName,const std::string& newName); public Q_SLOTS: /*Makes current the tab at index "index". Passing an diff --git a/Gui/TimeLineGui.cpp b/Gui/TimeLineGui.cpp index 5b1c42a615..689e37f44c 100644 --- a/Gui/TimeLineGui.cpp +++ b/Gui/TimeLineGui.cpp @@ -120,6 +120,8 @@ struct TimelineGuiPrivate bool alphaCursor; // should cursor be drawn semi-transparant QPoint lastMouseEventWidgetCoord; Natron::TimelineStateEnum state; //state machine for mouse events + int mousePressX; // widget X coordinate of last click + int mouseMoveX; // widget X coordinate of last mousemove position TimeLineZoomContext tlZoomCtx; Natron::TextRenderer textRenderer; QFont font; @@ -144,6 +146,8 @@ struct TimelineGuiPrivate , alphaCursor(false) , lastMouseEventWidgetCoord() , state(eTimelineStateIdle) + , mousePressX(0) + , mouseMoveX(0) , tlZoomCtx() , textRenderer() , font(appFont,appFontSize) @@ -159,7 +163,7 @@ struct TimelineGuiPrivate void updateEditorFrameRanges() { - double zoomRight = parent->toTimeLineCoordinates(parent->width() - 1, 0).x(); + double zoomRight = parent->toTimeLine(parent->width() - 1); gui->getCurveEditor()->getCurveWidget()->centerOn(tlZoomCtx.left - 5, zoomRight - 5); gui->getDopeSheetEditor()->centerOn(tlZoomCtx.left - 5, zoomRight - 5); @@ -167,7 +171,7 @@ struct TimelineGuiPrivate void updateOpenedViewersFrameRanges() { - double zoomRight = parent->toTimeLineCoordinates(parent->width() - 1, 0).x(); + double zoomRight = parent->toTimeLine(parent->width() - 1); const std::list &viewers = gui->getViewersList(); @@ -203,17 +207,12 @@ TimeLineGui::setTimeline(const boost::shared_ptr& timeline) QObject::disconnect( _imp->timeline.get(), SIGNAL( frameChanged(SequenceTime,int) ), this, SLOT( onFrameChanged(SequenceTime,int) ) ); //connect the gui to the internal timeline - QObject::disconnect( this, SIGNAL( frameChanged(SequenceTime) ), _imp->timeline.get(), SLOT( onFrameChanged(SequenceTime) ) ); QObject::disconnect( _imp->timeline.get(), SIGNAL( keyframeIndicatorsChanged() ), this, SLOT( onKeyframesIndicatorsChanged() ) ); } //connect the internal timeline to the gui QObject::connect( timeline.get(), SIGNAL( frameChanged(SequenceTime,int) ), this, SLOT( onFrameChanged(SequenceTime,int) ) ); - - //connect the gui to the internal timeline - QObject::connect( this, SIGNAL( frameChanged(SequenceTime) ), timeline.get(), SLOT( onFrameChanged(SequenceTime) ) ); - QObject::connect( timeline.get(), SIGNAL( keyframeIndicatorsChanged() ), this, SLOT( onKeyframesIndicatorsChanged() ) ); _imp->timeline = timeline; @@ -337,8 +336,8 @@ TimeLineGui::paintGL() QPointF firstFrameWidgetPos = toWidgetCoordinates(firstFrame,0); QPointF lastFrameWidgetPos = toWidgetCoordinates(lastFrame,0); - glScissor( firstFrameWidgetPos.x(),0, - lastFrameWidgetPos.x() - firstFrameWidgetPos.x(),height() ); + glScissor( firstFrameWidgetPos.x(), 0, + lastFrameWidgetPos.x() - firstFrameWidgetPos.x(), height() ); double bgR,bgG,bgB; settings->getBaseColor(&bgR, &bgG, &bgB); @@ -352,6 +351,20 @@ TimeLineGui::paintGL() glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (_imp->state == eTimelineStateSelectingZoomRange) { + // draw timeline selected range + // https://github.com/MrKepzie/Natron/issues/917 + // draw the select range, from _imp->mousePressX to _imp->mouseMoveX + glColor4f(1, 1, 1, 0.3); + glBegin(GL_POLYGON); + glVertex2f(toTimeLine(_imp->mousePressX), btmLeft.y()); + glVertex2f(toTimeLine(_imp->mousePressX), topRight.y()); + glVertex2f(toTimeLine(_imp->mouseMoveX), topRight.y()); + glVertex2f(toTimeLine(_imp->mouseMoveX), btmLeft.y()); + glEnd(); + } + + QFontMetrics fontM(_imp->font); double lineYPosWidget = height() - 1 - fontM.height() - TICK_HEIGHT / 2.; @@ -538,7 +551,7 @@ TimeLineGui::paintGL() QString currentFrameStr( QString::number( _imp->timeline->currentFrame() ) ); double cursorTextXposWidget = cursorBtmWidgetCoord.x() - fontM.width(currentFrameStr) / 2.; - double cursorTextPos = toTimeLineCoordinates(cursorTextXposWidget,0).x(); + double cursorTextPos = toTimeLine(cursorTextXposWidget); renderText(cursorTextPos,cursorTopLeft.y(), currentFrameStr, actualCursorColor, _imp->font); glBegin(GL_POLYGON); glVertex2f( cursorBtm.x(),cursorBtm.y() ); @@ -555,7 +568,7 @@ TimeLineGui::paintGL() if ( leftBound != _imp->timeline->currentFrame() ) { QString leftBoundStr( QString::number(leftBound) ); double leftBoundTextXposWidget = toWidgetCoordinates( ( leftBoundBtm.x() + leftBoundBtmRight.x() ) / 2.,0 ).x() - fontM.width(leftBoundStr) / 2.; - double leftBoundTextPos = toTimeLineCoordinates(leftBoundTextXposWidget,0).x(); + double leftBoundTextPos = toTimeLine(leftBoundTextXposWidget); renderText(leftBoundTextPos,leftBoundTop.y(), leftBoundStr, boundsColor, _imp->font); } @@ -570,7 +583,7 @@ TimeLineGui::paintGL() if ( rightBound != cur ) { QString rightBoundStr( QString::number( rightBound ) ); double rightBoundTextXposWidget = toWidgetCoordinates( ( rightBoundBtm.x() + rightBoundBtmLeft.x() ) / 2.,0 ).x() - fontM.width(rightBoundStr) / 2.; - double rightBoundTextPos = toTimeLineCoordinates(rightBoundTextXposWidget,0).x(); + double rightBoundTextPos = toTimeLine(rightBoundTextXposWidget); renderText(rightBoundTextPos,rightBoundTop.y(), rightBoundStr, boundsColor, _imp->font); } @@ -652,8 +665,12 @@ TimeLineGui::renderText(double x, void TimeLineGui::onFrameChanged(SequenceTime, - int) + int reason) { + Natron::TimelineChangeReasonEnum r = (Natron::TimelineChangeReasonEnum)reason; + if (r == eTimelineChangeReasonUserSeek) { + return; + } update(); } @@ -662,7 +679,7 @@ TimeLineGui::seek(SequenceTime time) { if ( time != _imp->timeline->currentFrame() ) { _imp->gui->getApp()->setLastViewerUsingTimeline(_imp->viewer->getNode()); - Q_EMIT frameChanged(time); + _imp->timeline->onFrameChanged(time); update(); } } @@ -670,27 +687,26 @@ TimeLineGui::seek(SequenceTime time) void TimeLineGui::mousePressEvent(QMouseEvent* e) { - int leftBound,rightBound; - { - QMutexLocker k(&_imp->boundariesMutex); - leftBound = _imp->leftBoundary; - rightBound = _imp->rightBoundary; - } + _imp->mousePressX = e->x(); + _imp->mouseMoveX = _imp->mousePressX; if (buttonDownIsMiddle(e)) { - centerOn(leftBound, rightBound); - - if (_imp->gui->isTripleSyncEnabled()) { - _imp->updateEditorFrameRanges(); - _imp->updateOpenedViewersFrameRanges(); - } + _imp->state = eTimelineStatePanning; + } else if (buttonDownIsRight(e)) { + _imp->state = eTimelineStateSelectingZoomRange; } else { _imp->lastMouseEventWidgetCoord = e->pos(); - double t = toTimeLineCoordinates(e->x(),0).x(); + const double t = toTimeLine(_imp->mousePressX); SequenceTime tseq = std::floor(t + 0.5); if (modCASIsControl(e)) { + int leftBound,rightBound; + { + QMutexLocker k(&_imp->boundariesMutex); + leftBound = _imp->leftBoundary; + rightBound = _imp->rightBoundary; + } _imp->state = eTimelineStateDraggingBoundary; - int firstPos = toWidgetCoordinates(leftBound - 1,0).x(); - int lastPos = toWidgetCoordinates(rightBound + 1,0).x(); + int firstPos = toWidget(leftBound - 1); + int lastPos = toWidget(rightBound + 1); int distFromFirst = std::abs(e->x() - firstPos); int distFromLast = std::abs(e->x() - lastPos); if (distFromFirst > distFromLast) { @@ -708,29 +724,40 @@ TimeLineGui::mousePressEvent(QMouseEvent* e) void TimeLineGui::mouseMoveEvent(QMouseEvent* e) { - int leftBound,rightBound; - { - QMutexLocker k(&_imp->boundariesMutex); - leftBound = _imp->leftBoundary; - rightBound = _imp->rightBoundary; - } - + int mouseMoveXprev = _imp->mouseMoveX; _imp->lastMouseEventWidgetCoord = e->pos(); - double t = toTimeLineCoordinates(e->x(),0).x(); + _imp->mouseMoveX = e->x(); + const double t = toTimeLine(_imp->mouseMoveX); SequenceTime tseq = std::floor(t + 0.5); bool distortViewPort = false; bool onEditingFinishedOnly = appPTR->getCurrentSettings()->getRenderOnEditingFinishedOnly(); - if (_imp->state == eTimelineStateDraggingCursor && !onEditingFinishedOnly) { + if (_imp->state == eTimelineStatePanning) { + _imp->tlZoomCtx.left += toTimeLine(mouseMoveXprev) - toTimeLine(_imp->mouseMoveX); + update(); + if (_imp->gui->isTripleSyncEnabled()) { + _imp->updateEditorFrameRanges(); + _imp->updateOpenedViewersFrameRanges(); + } + } else if (_imp->state == eTimelineStateSelectingZoomRange) { + // https://github.com/MrKepzie/Natron/issues/917 + update(); + } else if (_imp->state == eTimelineStateDraggingCursor && !onEditingFinishedOnly) { if ( tseq != _imp->timeline->currentFrame() ) { _imp->gui->setDraftRenderEnabled(true); _imp->gui->getApp()->setLastViewerUsingTimeline(_imp->viewer->getNode()); - Q_EMIT frameChanged(tseq); + _imp->timeline->onFrameChanged(tseq); } distortViewPort = true; _imp->alphaCursor = false; } else if (_imp->state == eTimelineStateDraggingBoundary) { - int firstPos = toWidgetCoordinates(leftBound - 1,0).x(); - int lastPos = toWidgetCoordinates(rightBound + 1,0).x(); + int leftBound,rightBound; + { + QMutexLocker k(&_imp->boundariesMutex); + leftBound = _imp->leftBoundary; + rightBound = _imp->rightBoundary; + } + int firstPos = toWidget(leftBound - 1); + int lastPos = toWidget(rightBound + 1); int distFromFirst = std::abs(e->x() - firstPos); int distFromLast = std::abs(e->x() - lastPos); if (distFromFirst > distFromLast) { // moving last frame anchor @@ -750,8 +777,8 @@ TimeLineGui::mouseMoveEvent(QMouseEvent* e) } if (distortViewPort) { - double leftMost = toTimeLineCoordinates(0,0).x(); - double rightMost = toTimeLineCoordinates(width() - 1,0).x(); + double leftMost = toTimeLine(0); + double rightMost = toTimeLine(width() - 1); if (tseq < leftMost) { centerOn(tseq, rightMost); } else if (tseq > rightMost) { @@ -762,6 +789,7 @@ TimeLineGui::mouseMoveEvent(QMouseEvent* e) } else { update(); } + } void @@ -783,8 +811,36 @@ TimeLineGui::leaveEvent(QEvent* e) void TimeLineGui::mouseReleaseEvent(QMouseEvent* e) { - if (_imp->state == eTimelineStateDraggingCursor) { - + if (_imp->state == eTimelineStateSelectingZoomRange) { + // - if the last selected frame is the same as the first selected frame, zoom on the PROJECT range + // (NOT the playback range as in the following, and NOT adding margins as centerOn() does) + // - if they are different, zoom on that range + double t = toTimeLine(e->x()); + + int leftBound = std::floor(t + 0.5); + int rightBound = std::floor(toTimeLine(_imp->mousePressX) + 0.5); + if (leftBound > rightBound) { + std::swap(leftBound, rightBound); + } else if (leftBound == rightBound) { + if (!_imp->viewerTab->isFileDialogViewer()) { + double firstFrame,lastFrame; + _imp->gui->getApp()->getFrameRange(&firstFrame, &lastFrame); + leftBound = std::floor(firstFrame + 0.5); + rightBound = std::floor(lastFrame + 0.5); + + } else { + _imp->viewerTab->getTimelineBounds(&leftBound, &rightBound); + } + } + + centerOn(leftBound, rightBound, 0); + + if (_imp->gui->isTripleSyncEnabled()) { + _imp->updateEditorFrameRanges(); + _imp->updateOpenedViewersFrameRanges(); + } + } else if (_imp->state == eTimelineStateDraggingCursor) { + bool wasScrubbing = false; if (_imp->gui->isDraftRenderEnabled()) { _imp->gui->setDraftRenderEnabled(false); @@ -798,16 +854,16 @@ TimeLineGui::mouseReleaseEvent(QMouseEvent* e) if (onEditingFinishedOnly) { - double t = toTimeLineCoordinates(e->x(),0).x(); + double t = toTimeLine(e->x()); SequenceTime tseq = std::floor(t + 0.5); if ( (tseq != _imp->timeline->currentFrame()) ) { _imp->gui->getApp()->setLastViewerUsingTimeline(_imp->viewer->getNode()); - Q_EMIT frameChanged(tseq); + _imp->timeline->onFrameChanged(tseq); } } else if (autoProxyEnabled && wasScrubbing) { - _imp->gui->getApp()->renderAllViewers(); + _imp->gui->getApp()->renderAllViewers(true); } } @@ -902,12 +958,13 @@ TimeLineGui::getVisibleRange(SequenceTime* left, SequenceTime* right) const void TimeLineGui::centerOn(SequenceTime left, - SequenceTime right) + SequenceTime right, + int margin) { - double curveWidth = right - left + 10; + double curveWidth = right - left + 2 * margin; double w = width(); - _imp->tlZoomCtx.left = left - 5; + _imp->tlZoomCtx.left = left - margin; _imp->tlZoomCtx.zoomFactor = w / curveWidth; update(); @@ -942,32 +999,46 @@ TimeLineGui::currentFrame() const return _imp->timeline->currentFrame(); } +double +TimeLineGui::toTimeLine(double x) const +{ + double w = (double)width(); + double left = _imp->tlZoomCtx.left; + double right = left + w / _imp->tlZoomCtx.zoomFactor; + + return ( ( (right - left) * x ) / w ) + left; +} + +double +TimeLineGui::toWidget(double t) const +{ + double w = (double)width(); + double left = _imp->tlZoomCtx.left; + double right = left + w / _imp->tlZoomCtx.zoomFactor; + + return ( (t - left) / (right - left) ) * w; +} + QPointF TimeLineGui::toTimeLineCoordinates(double x, double y) const { - double w = (double)width(); double h = (double)height(); double bottom = _imp->tlZoomCtx.bottom; - double left = _imp->tlZoomCtx.left; double top = bottom + h / _imp->tlZoomCtx.zoomFactor; - double right = left + w / _imp->tlZoomCtx.zoomFactor; - return QPointF( ( ( (right - left) * x ) / w ) + left,( ( (bottom - top) * y ) / h ) + top ); + return QPointF( toTimeLine(x), ( ( (bottom - top) * y ) / h ) + top ); } QPointF TimeLineGui::toWidgetCoordinates(double x, double y) const { - double w = (double)width(); double h = (double)height(); double bottom = _imp->tlZoomCtx.bottom; - double left = _imp->tlZoomCtx.left; double top = bottom + h / _imp->tlZoomCtx.zoomFactor; - double right = left + w / _imp->tlZoomCtx.zoomFactor; - return QPoint( ( (x - left) / (right - left) ) * w,( (y - top) / (bottom - top) ) * h ); + return QPoint( toWidget(x), ( (y - top) / (bottom - top) ) * h ); } void diff --git a/Gui/TimeLineGui.h b/Gui/TimeLineGui.h index 6688426fb7..b339d93b2a 100644 --- a/Gui/TimeLineGui.h +++ b/Gui/TimeLineGui.h @@ -95,11 +95,13 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON *@brief See toZoomCoordinates in ViewerGL.h **/ QPointF toTimeLineCoordinates(double x, double y) const; + double toTimeLine(double x) const; /** *@brief See toWidgetCoordinates in ViewerGL.h **/ QPointF toWidgetCoordinates(double x, double y) const; + double toWidget(double t) const; /** * @brief Activates the SLOT onViewerCacheFrameAdded() and the SIGNALS removedLRUCachedFrame() and clearedViewerCache() @@ -126,7 +128,7 @@ public Q_SLOTS: void recenterOnBounds(); - void centerOn(SequenceTime left,SequenceTime right); + void centerOn(SequenceTime left, SequenceTime right, int margin = 5); void onFrameChanged(SequenceTime,int); @@ -159,7 +161,6 @@ public Q_SLOTS: Q_SIGNALS: - void frameChanged(SequenceTime); void boundariesChanged(SequenceTime,SequenceTime); private: diff --git a/Gui/VerticalColorBar.cpp b/Gui/VerticalColorBar.cpp index 9bd27d3374..04f35a2024 100644 --- a/Gui/VerticalColorBar.cpp +++ b/Gui/VerticalColorBar.cpp @@ -27,7 +27,7 @@ #include #include -#define NATRON_VERTICAL_BAR_WIDTH 4 +#define NATRON_VERTICAL_BAR_WIDTH 2 VerticalColorBar::VerticalColorBar(QWidget* parent) : QWidget(parent) @@ -47,7 +47,7 @@ QSize VerticalColorBar::sizeHint() const { return QWidget::sizeHint(); - //return QSize(5,1000); + //return QSize(NATRON_VERTICAL_BAR_WIDTH,1000); } void @@ -55,11 +55,11 @@ VerticalColorBar::paintEvent(QPaintEvent* /*e*/) { QPainter p(this); QPen pen; - pen.setCapStyle(Qt::RoundCap); + pen.setCapStyle(Qt::FlatCap); pen.setWidth(NATRON_VERTICAL_BAR_WIDTH); pen.setColor(_color); p.setPen(pen); - p.drawLine( 0, NATRON_VERTICAL_BAR_WIDTH, 0, height() - NATRON_VERTICAL_BAR_WIDTH); + p.drawLine( NATRON_VERTICAL_BAR_WIDTH/2, NATRON_VERTICAL_BAR_WIDTH, NATRON_VERTICAL_BAR_WIDTH/2, height() - NATRON_VERTICAL_BAR_WIDTH); } diff --git a/Gui/ViewerGL.cpp b/Gui/ViewerGL.cpp index ae254f2d4f..f30ab40bf4 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -40,6 +40,8 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF #include GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include +#include +#include #include "Engine/Lut.h" #include "Engine/Node.h" @@ -211,7 +213,7 @@ ViewerGL::resizeGL(int w, if (!_imp->persistentMessages.empty()) { updatePersistentMessageToWidth(w - 20); } else { - updateGL(); + update(); } } } @@ -1578,6 +1580,8 @@ ViewerGL::mousePressEvent(QMouseEvent* e) if ( !_imp->viewerTab->getGui() ) { return; } + + _imp->viewerTab->onMousePressCalledInViewer(); _imp->hasMovedSincePress = false; _imp->pressureOnRelease = 1.; @@ -2818,7 +2822,7 @@ ViewerGL::clearViewer() assert( qApp && qApp->thread() == QThread::currentThread() ); _imp->activeTextures[0] = 0; _imp->activeTextures[1] = 0; - updateGL(); + update(); } /*overload of QT enter/leave/resize events*/ @@ -2855,30 +2859,6 @@ ViewerGL::focusOutEvent(QFocusEvent* e) QGLWidget::focusOutEvent(e); } -void -ViewerGL::enterEvent(QEvent* e) -{ - // always running in the main thread - assert( qApp && qApp->thread() == QThread::currentThread() ); - - QWidget* currentFocus = qApp->focusWidget(); - - bool canSetFocus = !currentFocus || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - currentFocus->objectName() == "Properties" || - currentFocus->objectName() == "SettingsPanel" || - currentFocus->objectName() == "qt_tabwidget_tabbar" || - currentFocus->objectName() == "PanelTabBar"; - - if (canSetFocus) { - setFocus(); - } - QWidget::enterEvent(e); -} void ViewerGL::leaveEvent(QEvent* e) @@ -2898,68 +2878,6 @@ ViewerGL::resizeEvent(QResizeEvent* e) QGLWidget::resizeEvent(e); } -void -ViewerGL::keyPressEvent(QKeyEvent* e) -{ - // always running in the main thread - assert( qApp && qApp->thread() == QThread::currentThread() ); - - Qt::KeyboardModifiers modifiers = e->modifiers(); - Qt::Key key = (Qt::Key)e->key(); - double scale = 1. / (1 << getCurrentRenderScale()); - - if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideOverlays, modifiers, key) ) { - toggleOverlays(); - } else if (isKeybind(kShortcutGroupViewer, kShortcutIDToggleWipe, modifiers, key)) { - toggleWipe(); - } else if (isKeybind(kShortcutGroupViewer, kShortcutIDCenterWipe, modifiers, key)) { - centerWipe(); - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideAll, modifiers, key) ) { - _imp->viewerTab->hideAllToolbars(); - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionShowAll, modifiers, key) ) { - _imp->viewerTab->showAllToolbars(); - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHidePlayer, modifiers, key) ) { - _imp->viewerTab->togglePlayerVisibility(); - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideTimeline, modifiers, key) ) { - _imp->viewerTab->toggleTimelineVisibility(); - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideInfobar, modifiers, key) ) { - _imp->viewerTab->toggleInfobarVisbility(); - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideLeft, modifiers, key) ) { - _imp->viewerTab->toggleLeftToolbarVisiblity(); - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideRight, modifiers, key) ) { - _imp->viewerTab->toggleRightToolbarVisibility(); - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideTop, modifiers, key) ) { - _imp->viewerTab->toggleTopToolbarVisibility(); - } else if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionZoomIn, Qt::NoModifier, key) ) { // zoom in/out doesn't care about modifiers - QWheelEvent e(mapFromGlobal(QCursor::pos()), 120, Qt::NoButton, Qt::NoModifier); // one wheel click = +-120 delta - wheelEvent(&e); - } else if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionZoomOut, Qt::NoModifier, key) ) { // zoom in/out doesn't care about modifiers - QWheelEvent e(mapFromGlobal(QCursor::pos()), -120, Qt::NoButton, Qt::NoModifier); // one wheel click = +-120 delta - wheelEvent(&e); - } else if ( e->isAutoRepeat() && _imp->viewerTab->notifyOverlaysKeyRepeat(scale, scale, e) ) { - update(); - } else if ( _imp->viewerTab->notifyOverlaysKeyDown(scale, scale, e) ) { - update(); - } else { - QGLWidget::keyPressEvent(e); - } -} - -void -ViewerGL::keyReleaseEvent(QKeyEvent* e) -{ - // always running in the main thread - assert( qApp && qApp->thread() == QThread::currentThread() ); - if (!_imp->viewerTab->getGui()) { - return QGLWidget::keyPressEvent(e); - } - double scale = 1. / (1 << getCurrentRenderScale()); - if ( _imp->viewerTab->notifyOverlaysKeyUp(scale, scale, e) ) { - update(); - } else { - QGLWidget::keyReleaseEvent(e); - } -} Natron::ImageBitDepthEnum ViewerGL::getBitDepth() const @@ -3362,6 +3280,14 @@ ViewerGL::swapOpenGLBuffers() **/ void ViewerGL::redraw() +{ + // always running in the main thread + assert( qApp && qApp->thread() == QThread::currentThread() ); + update(); +} + +void +ViewerGL::redrawNow() { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); diff --git a/Gui/ViewerGL.h b/Gui/ViewerGL.h index af3b4bba5e..6fa32b6353 100644 --- a/Gui/ViewerGL.h +++ b/Gui/ViewerGL.h @@ -296,9 +296,14 @@ public Q_SLOTS: virtual void swapOpenGLBuffers() OVERRIDE FINAL; /** - * @brief Repaint + * @brief update() **/ virtual void redraw() OVERRIDE FINAL; + + /** + * @brief updateGL(); + **/ + virtual void redrawNow() OVERRIDE FINAL; /** * @brief Returns the width and height of the viewport in window coordinates. @@ -418,10 +423,7 @@ public Q_SLOTS: virtual void wheelEvent(QWheelEvent* e) OVERRIDE FINAL; virtual void focusInEvent(QFocusEvent* e) OVERRIDE FINAL; virtual void focusOutEvent(QFocusEvent* e) OVERRIDE FINAL; - virtual void enterEvent(QEvent* e) OVERRIDE FINAL; virtual void leaveEvent(QEvent* e) OVERRIDE FINAL; - virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; - virtual void keyReleaseEvent(QKeyEvent* e) OVERRIDE FINAL; virtual void tabletEvent(QTabletEvent* e) OVERRIDE FINAL; /** diff --git a/Gui/ViewerTab.cpp b/Gui/ViewerTab.cpp index 7eb1d44b71..ab422e41e2 100644 --- a/Gui/ViewerTab.cpp +++ b/Gui/ViewerTab.cpp @@ -98,9 +98,9 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, Gui* gui, ViewerInstance* node, QWidget* parent) - : QWidget(parent) - , ScriptObject() - , _imp( new ViewerTabPrivate(gui,node) ) +: QWidget(parent) +, PanelWidget(this,gui) +, _imp( new ViewerTabPrivate(this,node) ) { installEventFilter(this); @@ -314,14 +314,14 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, addSpacer(_imp->firstRowLayout); _imp->firstInputLabel = new Natron::Label("A:",_imp->firstSettingsRow); + _imp->firstInputLabel->setToolTip(Natron::convertFromPlainText(tr("Viewer input A."), Qt::WhiteSpaceNormal)); _imp->firstRowLayout->addWidget(_imp->firstInputLabel); _imp->firstInputImage = new ComboBox(_imp->firstSettingsRow); - + _imp->firstInputImage->setToolTip(_imp->firstInputLabel->toolTip()); _imp->firstInputImage->setFixedWidth(fm.width("ColorCorrect1") + 3 * DROP_DOWN_ICON_SIZE); _imp->firstInputImage->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); _imp->firstInputImage->addItem(" - "); - QObject::connect( _imp->firstInputImage,SIGNAL( currentIndexChanged(QString) ),this,SLOT( onFirstInputNameChanged(QString) ) ); _imp->firstRowLayout->addWidget(_imp->firstInputImage); @@ -329,6 +329,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, appPTR->getIcon(NATRON_PIXMAP_MERGE_GROUPING, NATRON_MEDIUM_BUTTON_ICON_SIZE, &pixMerge); _imp->compositingOperatorLabel = new Natron::Label("",_imp->firstSettingsRow); _imp->compositingOperatorLabel->setPixmap(pixMerge); + _imp->compositingOperatorLabel->setToolTip(Natron::convertFromPlainText(tr("Operation applied between viewer inputs A and B."), Qt::WhiteSpaceNormal)); _imp->firstRowLayout->addWidget(_imp->compositingOperatorLabel); @@ -336,6 +337,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, QObject::connect( _imp->compositingOperator,SIGNAL( currentIndexChanged(int) ),this,SLOT( onCompositingOperatorIndexChanged(int) ) ); _imp->compositingOperator->setFixedWidth(fm.width("Minus") + 3 * DROP_DOWN_ICON_SIZE); _imp->compositingOperator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + _imp->compositingOperator->setToolTip(_imp->compositingOperatorLabel->toolTip()); _imp->compositingOperator->addItem(tr(" - "), QIcon(), QKeySequence(), tr("Only the A input is used.")); _imp->compositingOperator->addItem(tr("Over"), QIcon(), QKeySequence(), tr("A + B(1 - Aalpha)")); _imp->compositingOperator->addItem(tr("Under"), QIcon(), QKeySequence(), tr("A(1 - Balpha) + B")); @@ -347,13 +349,15 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->firstRowLayout->addWidget(_imp->compositingOperator); _imp->secondInputLabel = new Natron::Label("B:",_imp->firstSettingsRow); + _imp->secondInputLabel->setToolTip(Natron::convertFromPlainText(tr("Viewer input B."), Qt::WhiteSpaceNormal)); _imp->firstRowLayout->addWidget(_imp->secondInputLabel); _imp->secondInputImage = new ComboBox(_imp->firstSettingsRow); - QObject::connect( _imp->secondInputImage,SIGNAL( currentIndexChanged(QString) ),this,SLOT( onSecondInputNameChanged(QString) ) ); + _imp->secondInputImage->setToolTip(_imp->secondInputLabel->toolTip()); _imp->secondInputImage->setFixedWidth(fm.width("ColorCorrect1") + 3 * DROP_DOWN_ICON_SIZE); _imp->secondInputImage->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); _imp->secondInputImage->addItem(" - "); + QObject::connect( _imp->secondInputImage,SIGNAL( currentIndexChanged(QString) ),this,SLOT( onSecondInputNameChanged(QString) ) ); _imp->firstRowLayout->addWidget(_imp->secondInputImage); _imp->firstRowLayout->addStretch(); @@ -391,7 +395,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->secondRowLayout->addWidget(_imp->gainBox); - _imp->gainSlider = new ScaleSliderQWidget(-6, 6, 0.0,ScaleSliderQWidget::eDataTypeDouble,_imp->gui,Natron::eScaleTypeLinear,_imp->secondSettingsRow); + _imp->gainSlider = new ScaleSliderQWidget(-6, 6, 0.0,ScaleSliderQWidget::eDataTypeDouble,getGui(),Natron::eScaleTypeLinear,_imp->secondSettingsRow); QObject::connect(_imp->gainSlider, SIGNAL(editingFinished(bool)), this, SLOT(onGainSliderEditingFinished(bool))); _imp->gainSlider->setToolTip(gainTt); _imp->secondRowLayout->addWidget(_imp->gainSlider); @@ -399,12 +403,19 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, QString autoContrastToolTip( "

" + tr("Auto-contrast:") + "

" + tr( "Automatically adjusts the gain and the offset applied " "to the colors of the visible image portion on the viewer.") + "

"); - _imp->autoConstrastLabel = new ClickableLabel(tr("Auto-contrast:"),_imp->secondSettingsRow); - _imp->autoConstrastLabel->setToolTip(autoContrastToolTip); - _imp->secondRowLayout->addWidget(_imp->autoConstrastLabel); - _imp->autoContrast = new QCheckBox(_imp->secondSettingsRow); - _imp->autoContrast->setAttribute(Qt::WA_LayoutUsesWidgetRect); // Don't use the layout rect calculated from QMacStyle. + QPixmap acOn,acOff; + appPTR->getIcon(NATRON_PIXMAP_VIEWER_AUTOCONTRAST_DISABLED, NATRON_MEDIUM_BUTTON_ICON_SIZE, &acOff); + appPTR->getIcon(NATRON_PIXMAP_VIEWER_AUTOCONTRAST_ENABLED, NATRON_MEDIUM_BUTTON_ICON_SIZE, &acOn); + QIcon acIc; + acIc.addPixmap(acOn,QIcon::Normal,QIcon::On); + acIc.addPixmap(acOff,QIcon::Normal,QIcon::Off); + _imp->autoContrast = new Button(acIc,"",_imp->secondSettingsRow); + _imp->autoContrast->setCheckable(true); + _imp->autoContrast->setChecked(false); + _imp->autoContrast->setDown(false); + _imp->autoContrast->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); + _imp->autoContrast->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->autoContrast->setToolTip(autoContrastToolTip); _imp->secondRowLayout->addWidget(_imp->autoContrast); @@ -422,17 +433,17 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->toggleGammaButton->setFocusPolicy(Qt::NoFocus); _imp->toggleGammaButton->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->toggleGammaButton->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); - _imp->toggleGammaButton->setToolTip(Natron::convertFromPlainText(tr("Switch between gamma at 1.0 and the previous setting"), Qt::WhiteSpaceNormal)); + _imp->toggleGammaButton->setToolTip(Natron::convertFromPlainText(tr("Viewer gamma correction: switch between gamma=1.0 and user setting."), Qt::WhiteSpaceNormal)); _imp->secondRowLayout->addWidget(_imp->toggleGammaButton); _imp->gammaBox = new SpinBox(_imp->secondSettingsRow, SpinBox::eSpinBoxTypeDouble); - QString gammaTt = Natron::convertFromPlainText(tr("Gamma correction. It is applied after gain and before colorspace correction"), Qt::WhiteSpaceNormal); + QString gammaTt = Natron::convertFromPlainText(tr("Viewer gamma correction level (applied after gain and before colorspace correction)."), Qt::WhiteSpaceNormal); _imp->gammaBox->setToolTip(gammaTt); QObject::connect(_imp->gammaBox,SIGNAL(valueChanged(double)), this, SLOT(onGammaSpinBoxValueChanged(double))); _imp->gammaBox->setValue(1.0); _imp->secondRowLayout->addWidget(_imp->gammaBox); - _imp->gammaSlider = new ScaleSliderQWidget(0,4,1.0,ScaleSliderQWidget::eDataTypeDouble,_imp->gui,Natron::eScaleTypeLinear,_imp->secondSettingsRow); + _imp->gammaSlider = new ScaleSliderQWidget(0,4,1.0,ScaleSliderQWidget::eDataTypeDouble,getGui(),Natron::eScaleTypeLinear,_imp->secondSettingsRow); QObject::connect(_imp->gammaSlider, SIGNAL(editingFinished(bool)), this, SLOT(onGammaSliderEditingFinished(bool))); _imp->gammaSlider->setToolTip(gammaTt); QObject::connect(_imp->gammaSlider,SIGNAL(positionChanged(double)), this, SLOT(onGammaSliderValueChanged(double))); @@ -463,7 +474,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->checkerboardButton->setChecked(false); _imp->checkerboardButton->setDown(false); _imp->checkerboardButton->setToolTip(Natron::convertFromPlainText(tr("If checked, the viewer draws a checkerboard under the image instead of black " - "(within the project window only)."), Qt::WhiteSpaceNormal)); + "(only within the project window)."), Qt::WhiteSpaceNormal)); _imp->checkerboardButton->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->checkerboardButton->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); QObject::connect(_imp->checkerboardButton,SIGNAL(clicked(bool)),this,SLOT(onCheckerboardButtonClicked())); @@ -474,7 +485,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, "Tells the viewer what view should be displayed.") ); _imp->secondRowLayout->addWidget(_imp->viewsComboBox); _imp->viewsComboBox->hide(); - int viewsCount = _imp->app->getProject()->getProjectViewsCount(); //getProjectViewsCount + int viewsCount = getGui()->getApp()->getProject()->getProjectViewsCount(); //getProjectViewsCount updateViewsMenu(viewsCount); _imp->secondRowLayout->addStretch(); @@ -676,18 +687,19 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, //QFont font(appFont,appFontSize); - _imp->canEditFrameRangeLabel = new ClickableLabel(tr("Frame range"),_imp->playerButtonsContainer); + _imp->canEditFrameRangeLabel = new ClickableLabel(tr("Playback range:"),_imp->playerButtonsContainer); //_imp->canEditFrameRangeLabel->setFont(font); _imp->playerLayout->addWidget(_imp->canEditFrameRangeLabel); _imp->frameRangeEdit = new LineEdit(_imp->playerButtonsContainer); QObject::connect( _imp->frameRangeEdit,SIGNAL( editingFinished() ),this,SLOT( onFrameRangeEditingFinished() ) ); - _imp->frameRangeEdit->setToolTip( Natron::convertFromPlainText(tr("Define here the timeline bounds in which the cursor will playback. Alternatively" - " you can drag the red markers on the timeline. The frame range of the project " - "is the part coloured in grey on the timeline."), + _imp->frameRangeEdit->setToolTip( Natron::convertFromPlainText(tr("Timeline bounds for video playback. It may be edited by dragging" + " the red markers on the timeline using Ctrl+click+drag. This is " + "different from the project frame range, which " + "is displayed on the timeline with a lighter background."), Qt::WhiteSpaceNormal) ); - boost::shared_ptr timeline = _imp->app->getTimeLine(); + boost::shared_ptr timeline = getGui()->getApp()->getTimeLine(); _imp->frameRangeEdit->setMaximumWidth(70); _imp->playerLayout->addWidget(_imp->frameRangeEdit); @@ -703,7 +715,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, tripleSyncIc.addPixmap(tripleSyncUnlockPix, QIcon::Normal, QIcon::Off); tripleSyncIc.addPixmap(tripleSyncLockedPix, QIcon::Normal, QIcon::On); _imp->tripleSyncButton = new Button(tripleSyncIc,"",_imp->playerButtonsContainer); - _imp->tripleSyncButton->setToolTip(Natron::convertFromPlainText(tr("When activated, timeline's frame-range will be synchronized with the Dope Sheet and the Curve Editor as well."),Qt::WhiteSpaceNormal)); + _imp->tripleSyncButton->setToolTip(Natron::convertFromPlainText(tr("When activated, the timeline frame-range is synchronized with the Dope Sheet and the Curve Editor."),Qt::WhiteSpaceNormal)); _imp->tripleSyncButton->setCheckable(true); _imp->tripleSyncButton->setChecked(false); _imp->tripleSyncButton->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE,NATRON_MEDIUM_BUTTON_SIZE); @@ -718,10 +730,10 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->canEditFpsBox = new QCheckBox(_imp->playerButtonsContainer); - QString canEditFpsBoxTT = Natron::convertFromPlainText(tr("When unchecked, the frame rate will be automatically set by " - " the informations of the input stream of the Viewer. " - "When checked, you're free to set the frame rate of the Viewer.") - , Qt::WhiteSpaceNormal); + QString canEditFpsBoxTT = Natron::convertFromPlainText(tr("When unchecked, the playback frame rate is automatically set from " + " the Viewer A input. " + "When checked, the user setting is used.") + , Qt::WhiteSpaceNormal); _imp->canEditFpsBox->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->canEditFpsBox->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); @@ -729,7 +741,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->canEditFpsBox->setChecked(!_imp->fpsLocked); QObject::connect( _imp->canEditFpsBox,SIGNAL( clicked(bool) ),this,SLOT( onCanSetFPSClicked(bool) ) ); - _imp->canEditFpsLabel = new ClickableLabel(tr("fps"),_imp->playerButtonsContainer); + _imp->canEditFpsLabel = new ClickableLabel(tr("fps:"),_imp->playerButtonsContainer); QObject::connect(_imp->canEditFpsLabel, SIGNAL(clicked(bool)),this,SLOT(onCanSetFPSLabelClicked(bool))); _imp->canEditFpsLabel->setToolTip(canEditFpsBoxTT); //_imp->canEditFpsLabel->setFont(font); @@ -738,12 +750,13 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->playerLayout->addWidget(_imp->canEditFpsLabel); _imp->fpsBox = new SpinBox(_imp->playerButtonsContainer,SpinBox::eSpinBoxTypeDouble); - _imp->fpsBox->setReadOnly(_imp->fpsLocked); + _imp->fpsBox->setEnabled(!_imp->fpsLocked); _imp->fpsBox->decimals(1); - _imp->fpsBox->setValue(24.0); + _imp->userFps = 24.; + _imp->fpsBox->setValue(_imp->userFps); _imp->fpsBox->setIncrement(0.1); _imp->fpsBox->setToolTip( "

" + tr("fps:") + "

" + tr( - "Enter here the desired playback rate.") ); + "Viewer playback framerate, in frames per second.") ); _imp->playerLayout->addWidget(_imp->fpsBox); @@ -760,8 +773,8 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->turboButton->setFixedSize(NATRON_MEDIUM_BUTTON_SIZE, NATRON_MEDIUM_BUTTON_SIZE); _imp->turboButton->setIconSize(QSize(NATRON_MEDIUM_BUTTON_ICON_SIZE, NATRON_MEDIUM_BUTTON_ICON_SIZE)); _imp->turboButton->setToolTip("

" + tr("Turbo mode:") + "

" + - tr("When checked, everything besides the viewer will not be refreshed in the user interface " - "for maximum efficiency during playback.") + "

"); + tr("When checked, only the viewer is redrawn during playback, " + "for maximum efficiency.") + "

"); _imp->turboButton->setFocusPolicy(Qt::NoFocus); QObject::connect( _imp->turboButton, SIGNAL (clicked(bool)), getGui(), SLOT(onFreezeUIButtonClicked(bool) ) ); _imp->playerLayout->addWidget(_imp->turboButton); @@ -862,14 +875,14 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, tr("Clips the portion of the image displayed " "on the viewer to the project format. " "When off, everything in the union of all nodes " - "region of definition will be displayed.") +"

" + + "region of definition is displayed.") +"

" + "

" + tr("Keyboard shortcut") + ": %1

", _imp->clipToProjectFormatButton); QStringList roiActions; roiActions << kShortcutIDActionROIEnabled; roiActions << kShortcutIDActionNewROI; setTooltipWithShortcut2(kShortcutGroupViewer, roiActions,"

" + - tr("When active, enables the region of interest that will limit" + tr("When active, enables the region of interest that limits" " the portion of the viewer that is kept updated.") +"

" + "

" + tr("Keyboard shortcut") + ": %1

" + "

" + tr("Press ") + " %2 " + tr("to activate and drag a new region.") + "

", _imp->enableViewerRoI); @@ -878,7 +891,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, /*=================================================*/ /*frame seeker*/ - _imp->timeLineGui = new TimeLineGui(node,timeline,_imp->gui,this); + _imp->timeLineGui = new TimeLineGui(node,timeline,getGui(),this); QObject::connect(_imp->timeLineGui, SIGNAL( boundariesChanged(SequenceTime,SequenceTime)), this, SLOT(onTimelineBoundariesChanged(SequenceTime, SequenceTime))); QObject::connect(gui->getApp()->getProject().get(), SIGNAL(frameRangeChanged(int,int)), _imp->timeLineGui, SLOT(onProjectFrameRangeChanged(int,int))); @@ -938,8 +951,6 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, QObject::connect( _imp->viewsComboBox,SIGNAL( currentIndexChanged(int) ),this,SLOT( showView(int) ) ); QObject::connect( _imp->enableViewerRoI, SIGNAL( clicked(bool) ), this, SLOT( onEnableViewerRoIButtonToggle(bool) ) ); QObject::connect( _imp->autoContrast,SIGNAL( clicked(bool) ),this,SLOT( onAutoContrastChanged(bool) ) ); - QObject::connect( _imp->autoConstrastLabel,SIGNAL( clicked(bool) ),this,SLOT( onAutoContrastChanged(bool) ) ); - QObject::connect( _imp->autoConstrastLabel,SIGNAL( clicked(bool) ),_imp->autoContrast,SLOT( setChecked(bool) ) ); QObject::connect( _imp->renderScaleCombo,SIGNAL( currentIndexChanged(int) ),this,SLOT( onRenderScaleComboIndexChanged(int) ) ); QObject::connect( _imp->activateRenderScale,SIGNAL( toggled(bool) ),this,SLOT( onRenderScaleButtonClicked(bool) ) ); @@ -972,7 +983,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, //Refresh the viewport lock state - const std::list& viewers = _imp->gui->getViewersList(); + const std::list& viewers = getGui()->getViewersList(); if (!viewers.empty()) { ViewerTab* other = viewers.front(); if (other->isViewersSynchroEnabled()) { diff --git a/Gui/ViewerTab.h b/Gui/ViewerTab.h index 7ee9562302..cbeb2fbce1 100644 --- a/Gui/ViewerTab.h +++ b/Gui/ViewerTab.h @@ -26,11 +26,7 @@ // ***** END PYTHON BLOCK ***** #include "Global/Macros.h" -CLANG_DIAG_OFF(deprecated) -CLANG_DIAG_OFF(uninitialized) -#include -CLANG_DIAG_ON(deprecated) -CLANG_DIAG_ON(uninitialized) + #if !defined(Q_MOC_RUN) && !defined(SBK_RUN) #include #include @@ -39,9 +35,10 @@ CLANG_DIAG_ON(uninitialized) #include "Global/GlobalDefines.h" #include "Global/KeySymbols.h" // Key -#include "Engine/ScriptObject.h" #include "Engine/RenderStats.h" +#include "Gui/PanelWidget.h" + namespace Natron { class Node; @@ -62,9 +59,7 @@ class TrackerGui; class QInputEvent; struct RotoGuiSharedData; struct ViewerTabPrivate; -class ViewerTab - : public QWidget - , public ScriptObject +class ViewerTab : public QWidget, public PanelWidget { GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT @@ -85,7 +80,6 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON ViewerInstance* getInternalNode() const; void discardInternalNodePointer(); - Gui* getGui() const; ViewerGL* getViewer() const; void setCurrentView(int view); @@ -94,7 +88,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void seek(SequenceTime time); - void notifyAppClosing(); + virtual void notifyGuiClosing() OVERRIDE FINAL; /** *@brief Tells all the nodes in the grpah to draw their overlays @@ -287,6 +281,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool isPickerEnabled() const; void setPickerEnabled(bool enabled); + + void onMousePressCalledInViewer(); public Q_SLOTS: @@ -437,6 +433,12 @@ public Q_SLOTS: private: + void refreshFPSBoxFromClipPreferences(); + + void onSpinboxFpsChangedInternal(double fps); + + void onPickerButtonClickedInternal(ViewerTab* caller,bool); + void onCompositingOperatorChangedInternal(Natron::ViewerCompositingOperatorEnum oldOp,Natron::ViewerCompositingOperatorEnum newOp); @@ -446,6 +448,9 @@ public Q_SLOTS: virtual bool eventFilter(QObject *target, QEvent* e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; + virtual void keyReleaseEvent(QKeyEvent* e) OVERRIDE FINAL; + virtual void enterEvent(QEvent* e) OVERRIDE FINAL; + virtual void leaveEvent(QEvent* e) OVERRIDE FINAL; virtual QSize minimumSizeHint() const OVERRIDE FINAL; virtual QSize sizeHint() const OVERRIDE FINAL; boost::scoped_ptr _imp; diff --git a/Gui/ViewerTab10.cpp b/Gui/ViewerTab10.cpp index 72074d6535..e7ef7062ca 100644 --- a/Gui/ViewerTab10.cpp +++ b/Gui/ViewerTab10.cpp @@ -56,6 +56,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/SpinBox.h" #include "Gui/TimeLineGui.h" #include "Gui/TrackerGui.h" +#include "Gui/TabWidget.h" #include "Gui/ViewerGL.h" @@ -106,23 +107,32 @@ ViewerTab::updateViewsMenu(int count) _imp->viewsComboBox->clear(); if (count == 1) { _imp->viewsComboBox->hide(); - _imp->viewsComboBox->addItem( tr("Main") ); - } else if (count == 2) { + _imp->viewsComboBox->addItem(tr("Main")); + } else if (count >= 2) { _imp->viewsComboBox->show(); - _imp->viewsComboBox->addItem( tr("Left"),QIcon(),QKeySequence(Qt::CTRL + Qt::Key_1) ); - _imp->viewsComboBox->addItem( tr("Right"),QIcon(),QKeySequence(Qt::CTRL + Qt::Key_2) ); + ActionWithShortcut* leftAct = new ActionWithShortcut(kShortcutGroupViewer, + kShortcutIDShowLeftView, + kShortcutDescShowLeftView, + _imp->viewsComboBox); + _imp->viewsComboBox->addAction(leftAct); + ActionWithShortcut* rightAct = new ActionWithShortcut(kShortcutGroupViewer, + kShortcutIDShowRightView, + kShortcutDescShowRightView, + _imp->viewsComboBox); + _imp->viewsComboBox->addAction(rightAct); + for (int i = 2; i < count; ++i) { + _imp->viewsComboBox->addItem( QString( tr("View ") ) + QString::number(i + 1)); + } } else { _imp->viewsComboBox->show(); - for (int i = 0; i < count; ++i) { - _imp->viewsComboBox->addItem( QString( tr("View ") ) + QString::number(i + 1),QIcon(),Gui::keySequenceForView(i) ); - } + } if ( ( currentIndex < _imp->viewsComboBox->count() ) && (currentIndex != -1) ) { _imp->viewsComboBox->setCurrentIndex(currentIndex); } else { _imp->viewsComboBox->setCurrentIndex(0); } - _imp->gui->updateViewsActions(count); + getGui()->updateViewsActions(count); } void @@ -237,7 +247,7 @@ ViewerTab::startPause(bool b) { abortRendering(); if (b) { - _imp->gui->getApp()->setLastViewerUsingTimeline(_imp->viewerNode->getNode()); + getGui()->getApp()->setLastViewerUsingTimeline(_imp->viewerNode->getNode()); _imp->viewerNode->getRenderEngine()->renderFromCurrentFrame(getGui()->getApp()->isRenderStatsActionChecked(), OutputSchedulerThread::eRenderDirectionForward); } } @@ -253,12 +263,12 @@ ViewerTab::abortRendering() _imp->play_Backward_Button->setDown(false); _imp->play_Backward_Button->setChecked(false); } - if (_imp->gui && _imp->gui->isGUIFrozen() && appPTR->getCurrentSettings()->isAutoTurboEnabled()) { - _imp->gui->onFreezeUIButtonClicked(false); + if (getGui() && getGui()->isGUIFrozen() && appPTR->getCurrentSettings()->isAutoTurboEnabled()) { + getGui()->onFreezeUIButtonClicked(false); } - if (_imp->gui) { + if (getGui()) { ///Abort all viewers because they are all synchronised. - const std::list & activeNodes = _imp->gui->getViewersList(); + const std::list & activeNodes = getGui()->getViewersList(); for (std::list::const_iterator it = activeNodes.begin(); it != activeNodes.end(); ++it) { ViewerInstance* viewer = (*it)->getInternalNode(); @@ -272,7 +282,7 @@ ViewerTab::abortRendering() void ViewerTab::onEngineStarted(bool forward) { - if (!_imp->gui) { + if (!getGui()) { return; } @@ -286,15 +296,15 @@ ViewerTab::onEngineStarted(bool forward) _imp->play_Backward_Button->setChecked(!forward); } - if (_imp->gui && !_imp->gui->isGUIFrozen() && appPTR->getCurrentSettings()->isAutoTurboEnabled()) { - _imp->gui->onFreezeUIButtonClicked(true); + if (getGui() && !getGui()->isGUIFrozen() && appPTR->getCurrentSettings()->isAutoTurboEnabled()) { + getGui()->onFreezeUIButtonClicked(true); } } void ViewerTab::onEngineStopped() { - if (!_imp->gui) { + if (!getGui()) { return; } @@ -307,8 +317,8 @@ ViewerTab::onEngineStopped() _imp->play_Backward_Button->setDown(false); _imp->play_Backward_Button->setChecked(false); } - if (_imp->gui && _imp->gui->isGUIFrozen() && appPTR->getCurrentSettings()->isAutoTurboEnabled()) { - _imp->gui->onFreezeUIButtonClicked(false); + if (getGui() && getGui()->isGUIFrozen() && appPTR->getCurrentSettings()->isAutoTurboEnabled()) { + getGui()->onFreezeUIButtonClicked(false); } } @@ -317,7 +327,7 @@ ViewerTab::startBackward(bool b) { abortRendering(); if (b) { - _imp->gui->getApp()->setLastViewerUsingTimeline(_imp->viewerNode->getNode()); + getGui()->getApp()->setLastViewerUsingTimeline(_imp->viewerNode->getNode()); _imp->viewerNode->getRenderEngine()->renderFromCurrentFrame(getGui()->getApp()->isRenderStatsActionChecked(), OutputSchedulerThread::eRenderDirectionBackward); } @@ -379,12 +389,12 @@ void ViewerTab::onTimeLineTimeChanged(SequenceTime time, int /*reason*/) { - if (!_imp->gui) { + if (!getGui()) { return; } _imp->currentFrameBox->setValue(time); - if (_imp->timeLineGui->getTimeline() != _imp->gui->getApp()->getTimeLine()) { + if (_imp->timeLineGui->getTimeline() != getGui()->getApp()->getTimeLine()) { _imp->viewerNode->renderCurrentFrame(true); } } @@ -402,7 +412,7 @@ ViewerTab::centerViewer() if ( _imp->viewer->displayingImage() ) { _imp->viewerNode->renderCurrentFrame(false); } else { - _imp->viewer->updateGL(); + _imp->viewer->update(); } } @@ -427,7 +437,7 @@ ViewerTab::refresh() ViewerTab::~ViewerTab() { - if (_imp->gui) { + if (getGui()) { NodeGraph* graph = 0; if (_imp->viewerNode) { boost::shared_ptr collection = _imp->viewerNode->getNode()->getGroup(); @@ -440,15 +450,15 @@ ViewerTab::~ViewerTab() assert(graph); } } else { - graph = _imp->gui->getNodeGraph(); + graph = getGui()->getNodeGraph(); } } _imp->viewerNode->invalidateUiContext(); } else { - graph = _imp->gui->getNodeGraph(); + graph = getGui()->getNodeGraph(); } assert(graph); - if ( _imp->app && !_imp->app->isClosing() && graph && (graph->getLastSelectedViewer() == this) ) { + if ( getGui()->getApp() && !getGui()->getApp()->isClosing() && graph && (graph->getLastSelectedViewer() == this) ) { graph->setLastSelectedViewer(0); } } @@ -478,18 +488,31 @@ ViewerTab::previousLayer() _imp->layerChoice->setCurrentIndex(currentIndex); } +void +ViewerTab::enterEvent(QEvent* e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} + +void +ViewerTab::leaveEvent(QEvent* e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); +} + void ViewerTab::keyPressEvent(QKeyEvent* e) { + + bool accept = true; + Qt::KeyboardModifiers modifiers = e->modifiers(); Qt::Key key = (Qt::Key)e->key(); + double scale = 1. / (1 << _imp->viewer->getCurrentRenderScale()); - if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionShowPaneFullScreen, modifiers, key) ) { //< this shortcut is global - if ( parentWidget() ) { - QKeyEvent* ev = new QKeyEvent(QEvent::KeyPress,key, modifiers); - QCoreApplication::postEvent(parentWidget(),ev); - } - } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionLuminance, modifiers, key) ) { + if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionLuminance, modifiers, key) ) { int currentIndex = _imp->viewerChannels->activeIndex(); if (currentIndex == 0) { _imp->viewerChannels->setCurrentIndex_no_emit(1); @@ -603,10 +626,10 @@ ViewerTab::keyPressEvent(QKeyEvent* e) lastFrame(); } else if ( isKeybind(kShortcutGroupPlayer, kShortcutIDActionPlayerPrevKF, modifiers, key) ) { //prev key - _imp->app->getTimeLine()->goToPreviousKeyframe(); + getGui()->getApp()->getTimeLine()->goToPreviousKeyframe(); } else if ( isKeybind(kShortcutGroupPlayer, kShortcutIDActionPlayerNextKF, modifiers, key) ) { //next key - _imp->app->getTimeLine()->goToNextKeyframe(); + getGui()->getApp()->getTimeLine()->goToNextKeyframe(); } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionFitViewer, modifiers, key) ) { centerViewer(); } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionClipEnabled, modifiers, key) ) { @@ -634,8 +657,6 @@ ViewerTab::keyPressEvent(QKeyEvent* e) } else if (isKeybind(kShortcutGroupViewer, kShortcutIDActionZoomLevel100, modifiers, key) ) { _imp->viewer->zoomSlot(100); _imp->zoomCombobox->setCurrentIndex_no_emit(4); - } else if (isKeybind(kShortcutGroupViewer, kShortcutIDSwitchInputAAndB, modifiers, key) ) { - switchInputAAndB(); } else if (isKeybind(kShortcutGroupViewer, kShortcutIDActionZoomIn, modifiers, key) ) { zoomIn(); } else if (isKeybind(kShortcutGroupViewer, kShortcutIDActionZoomOut, modifiers, key) ) { @@ -664,11 +685,75 @@ ViewerTab::keyPressEvent(QKeyEvent* e) connectToInput(8); } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionConnectViewerToInput10, modifiers, key) ) { connectToInput(9); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideOverlays, modifiers, key) ) { + _imp->viewer->toggleOverlays(); + } else if (isKeybind(kShortcutGroupViewer, kShortcutIDToggleWipe, modifiers, key)) { + _imp->viewer->toggleWipe(); + } else if (isKeybind(kShortcutGroupViewer, kShortcutIDCenterWipe, modifiers, key)) { + _imp->viewer->centerWipe(); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideAll, modifiers, key) ) { + hideAllToolbars(); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionShowAll, modifiers, key) ) { + showAllToolbars(); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHidePlayer, modifiers, key) ) { + togglePlayerVisibility(); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideTimeline, modifiers, key) ) { + toggleTimelineVisibility(); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideInfobar, modifiers, key) ) { + toggleInfobarVisbility(); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideLeft, modifiers, key) ) { + toggleLeftToolbarVisiblity(); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideRight, modifiers, key) ) { + toggleRightToolbarVisibility(); + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideTop, modifiers, key) ) { + toggleTopToolbarVisibility(); + } else if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionZoomIn, Qt::NoModifier, key) ) { // zoom in/out doesn't care about modifiers + QWheelEvent e(mapFromGlobal(QCursor::pos()), 120, Qt::NoButton, Qt::NoModifier); // one wheel click = +-120 delta + wheelEvent(&e); + } else if ( isKeybind(kShortcutGroupGlobal, kShortcutIDActionZoomOut, Qt::NoModifier, key) ) { // zoom in/out doesn't care about modifiers + QWheelEvent e(mapFromGlobal(QCursor::pos()), -120, Qt::NoButton, Qt::NoModifier); // one wheel click = +-120 delta + wheelEvent(&e); + } else if ( e->isAutoRepeat() && notifyOverlaysKeyRepeat(scale, scale, e) ) { + update(); + } else if ( notifyOverlaysKeyDown(scale, scale, e) ) { + update(); + } else if (isKeybind(kShortcutGroupViewer, kShortcutIDSwitchInputAAndB, modifiers, key) ) { + ///Put it after notifyOverlaysKeyDown() because Roto may intercept Enter + switchInputAAndB(); + } else if (isKeybind(kShortcutGroupViewer, kShortcutIDShowLeftView, modifiers, key) ) { + showView(0); + } else if (isKeybind(kShortcutGroupViewer, kShortcutIDShowRightView, modifiers, key) ) { + showView(1); } else { + accept = false; QWidget::keyPressEvent(e); } + if (accept) { + takeClickFocus(); + e->accept(); + } else { + handleUnCaughtKeyPressEvent(); + } } // keyPressEvent + + +void +ViewerTab::keyReleaseEvent(QKeyEvent* e) +{ + // always running in the main thread + assert( qApp && qApp->thread() == QThread::currentThread() ); + if (!getGui()) { + return QWidget::keyPressEvent(e); + } + double scale = 1. / (1 << _imp->viewer->getCurrentRenderScale()); + if ( notifyOverlaysKeyUp(scale, scale, e) ) { + _imp->viewer->redraw(); + } else { + QWidget::keyReleaseEvent(e); + } +} + void ViewerTab::setDisplayChannels(int i, bool setBothInputs) { @@ -711,11 +796,11 @@ ViewerTab::eventFilter(QObject *target, QEvent* e) { if (e->type() == QEvent::MouseButtonPress) { - if (_imp->gui && _imp->app) { + if (getGui() && getGui()->getApp()) { boost::shared_ptr gui_i = _imp->viewerNode->getNode()->getNodeGui(); assert(gui_i); boost::shared_ptr gui = boost::dynamic_pointer_cast(gui_i); - _imp->gui->selectNode(gui); + getGui()->selectNode(gui); } } diff --git a/Gui/ViewerTab20.cpp b/Gui/ViewerTab20.cpp index 981a4b9f4e..a4a3d97d09 100644 --- a/Gui/ViewerTab20.cpp +++ b/Gui/ViewerTab20.cpp @@ -73,13 +73,13 @@ ViewerTab::drawOverlays(double time, double scaleY) const { - if ( !_imp->app || + if (!getGui() || + !getGui()->getApp() || !_imp->viewer || - _imp->app->isClosing() || + getGui()->getApp()->isClosing() || isFileDialogViewer() || - !_imp->gui || - (_imp->gui->isGUIFrozen() && !_imp->app->getIsUserPainting()) || - _imp->app->isShowingDialog()) { + (getGui()->isGUIFrozen() && !getGui()->getApp()->getIsUserPainting()) || + getGui()->getApp()->isShowingDialog()) { return; } @@ -166,7 +166,7 @@ ViewerTab::notifyOverlaysPenDown_internal(const boost::shared_ptr& QPointF transformViewportPos; QPointF transformPos; - double time = _imp->app->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); #ifdef NATRON_TRANSFORM_AFFECTS_OVERLAYS @@ -264,11 +264,14 @@ ViewerTab::notifyOverlaysPenDown(double scaleX, double timestamp, QMouseEvent* e) { - - if ( !_imp->app || _imp->app->isClosing() ) { + + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } - + + _imp->hasPenDown = true; + _imp->hasCaughtPenMotionWhileDragging = false; + std::list > nodes; getGui()->getNodesEntitledForOverlays(nodes); @@ -309,7 +312,7 @@ ViewerTab::notifyOverlaysPenDoubleClick(double scaleX, const QPointF & pos, QMouseEvent* e) { - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } @@ -320,7 +323,7 @@ ViewerTab::notifyOverlaysPenDoubleClick(double scaleX, for (std::list >::reverse_iterator it = nodes.rbegin(); it != nodes.rend(); ++it) { QPointF transformViewportPos; QPointF transformPos; - double time = _imp->app->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); #ifdef NATRON_TRANSFORM_AFFECTS_OVERLAYS int view = getCurrentView(); @@ -395,7 +398,7 @@ ViewerTab::notifyOverlaysPenMotion_internal(const boost::shared_ptrapp->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); #ifdef NATRON_TRANSFORM_AFFECTS_OVERLAYS int view = getCurrentView(); @@ -463,11 +466,21 @@ ViewerTab::notifyOverlaysPenMotion_internal(const boost::shared_ptrhasPenDown && !getGui()->isDraftRenderEnabled()) { + getGui()->setDraftRenderEnabled(true); + } + Natron::EffectInstance* effect = node->getLiveInstance(); assert(effect); effect->setCurrentViewportForOverlays_public(_imp->viewer); bool didSmthing = effect->onOverlayPenMotion_public(time, scaleX, scaleY, transformViewportPos, transformPos, pressure); if (didSmthing) { + + if (_imp->hasPenDown) { + _imp->hasCaughtPenMotionWhileDragging = true; + } + //http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html // if the instance returns kOfxStatOK, the host should not pass the pen motion @@ -490,7 +503,7 @@ ViewerTab::notifyOverlaysPenMotion(double scaleX, { bool didSomething = false; - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } @@ -540,10 +553,21 @@ ViewerTab::notifyOverlaysPenUp(double scaleX, { bool didSomething = false; - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } + ///Reset draft + bool mustTriggerRender = false; + if (getGui()->isDraftRenderEnabled()) { + getGui()->setDraftRenderEnabled(false); + mustTriggerRender = _imp->hasCaughtPenMotionWhileDragging; + } + + _imp->hasPenDown = false; + _imp->hasCaughtPenMotionWhileDragging = false; + + _imp->lastOverlayNode.reset(); std::list > nodes; @@ -553,7 +577,7 @@ ViewerTab::notifyOverlaysPenUp(double scaleX, QPointF transformViewportPos; QPointF transformPos; - double time = _imp->app->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); #ifdef NATRON_TRANSFORM_AFFECTS_OVERLAYS int view = getCurrentView(); @@ -627,13 +651,20 @@ ViewerTab::notifyOverlaysPenUp(double scaleX, } + - if (!didSomething && getGui()->getApp()->getOverlayRedrawRequestsCount() > 0) { + if (!mustTriggerRender && !didSomething && getGui()->getApp()->getOverlayRedrawRequestsCount() > 0) { getGui()->getApp()->redrawAllViewers(); } getGui()->getApp()->clearOverlayRedrawRequests(); - + if (mustTriggerRender) { + //We had draft enabled but penRelease didn't trigger any render, trigger one to refresh the viewer + getGui()->getApp()->renderAllViewers(true); + } + + + return didSomething; } @@ -643,7 +674,7 @@ ViewerTab::notifyOverlaysKeyDown_internal(const boost::shared_ptr& Natron::KeyboardModifiers km) { - double time = _imp->app->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); #ifdef NATRON_TRANSFORM_AFFECTS_OVERLAYS double transformedTime; bool ok = _imp->getTimeTransform(time, 0, node, getInternalNode(), &transformedTime); @@ -702,11 +733,29 @@ ViewerTab::notifyOverlaysKeyDown(double scaleX, { bool didSomething = false; - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } + Natron::Key natronKey = QtEnumConvert::fromQtKey( (Qt::Key)e->key() ); + + /* + Modifiers key down/up should be passed to all active interacts always so that they can properly figure out + whether they are up or down + */ + bool isModifier = e->key() == Qt::Key_Control || e->key() == Qt::Key_Shift || e->key() == Qt::Key_Alt || + e->key() == Qt::Key_Meta; + + /* + * We should not pass to the plug-in the event if the panel doesn't have the focus because this event was forwarded from the Gui + * but if this panel does not accept the keyDown, the following keyUp will never be issued (since this panel did not take focus) + * which can lead to plug-in ending up in bad state with modifiers tracking (e.g: Transform). + */ + if (isModifier && !isClickFocusPanel()) { + return false; + } + Natron::KeyboardModifiers natronMod = QtEnumConvert::fromQtModifiers( e->modifiers() ); std::list > nodes; @@ -717,6 +766,10 @@ ViewerTab::notifyOverlaysKeyDown(double scaleX, for (std::list >::iterator it = nodes.begin(); it != nodes.end(); ++it) { if (*it == lastOverlay) { if (notifyOverlaysKeyDown_internal(*it, scaleX, scaleY, e, natronKey, natronMod)) { + if (isModifier) { + nodes.erase(it); + break; + } return true; } else { nodes.erase(it); @@ -727,10 +780,14 @@ ViewerTab::notifyOverlaysKeyDown(double scaleX, } + for (std::list >::reverse_iterator it = nodes.rbegin(); it != nodes.rend(); ++it) { if (notifyOverlaysKeyDown_internal(*it, scaleX, scaleY, e, natronKey, natronMod)) { + if (isModifier) { + continue; + } return true; } } @@ -750,14 +807,13 @@ ViewerTab::notifyOverlaysKeyUp(double scaleX, { bool didSomething = false; - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } _imp->lastOverlayNode.reset(); - - - double time = _imp->app->getTimeLine()->currentFrame(); + + double time = getGui()->getApp()->getTimeLine()->currentFrame(); std::list > nodes; getGui()->getNodesEntitledForOverlays(nodes); @@ -813,7 +869,7 @@ ViewerTab::notifyOverlaysKeyRepeat_internal(const boost::shared_ptrapp->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); #ifdef NATRON_TRANSFORM_AFFECTS_OVERLAYS double transformedTime; bool ok = _imp->getTimeTransform(time, 0, node, getInternalNode(), &transformedTime); @@ -865,7 +921,7 @@ ViewerTab::notifyOverlaysKeyRepeat(double scaleX, double scaleY, QKeyEvent* e) { - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } @@ -910,11 +966,11 @@ bool ViewerTab::notifyOverlaysFocusGained(double scaleX, double scaleY) { - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } - double time = _imp->app->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); bool ret = false; @@ -952,11 +1008,11 @@ bool ViewerTab::notifyOverlaysFocusLost(double scaleX, double scaleY) { - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } - double time = _imp->app->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); bool ret = false; std::list > nodes; diff --git a/Gui/ViewerTab30.cpp b/Gui/ViewerTab30.cpp index d6b02d819a..f363d15412 100644 --- a/Gui/ViewerTab30.cpp +++ b/Gui/ViewerTab30.cpp @@ -288,16 +288,12 @@ ViewerTab::discardInternalNodePointer() _imp->viewerNode = 0; } -Gui* -ViewerTab::getGui() const -{ - return _imp->gui; -} - void ViewerTab::onAutoContrastChanged(bool b) { + _imp->autoContrast->setDown(b); + _imp->autoContrast->setChecked(b); _imp->gainSlider->setEnabled(!b); _imp->gainBox->setEnabled(!b); _imp->toggleGainButton->setEnabled(!b); @@ -431,7 +427,7 @@ ViewerTab::removeTrackerInterface(NodeGui* n, std::map::iterator it = _imp->trackerNodes.find(n); if ( it != _imp->trackerNodes.end() ) { - if (!_imp->gui) { + if (!getGui()) { if (permanently) { delete it->second; } @@ -482,7 +478,7 @@ void ViewerTab::createRotoInterface(NodeGui* n) { RotoGui* roto = new RotoGui( n,this,getRotoGuiSharedData(n) ); - QObject::connect( roto,SIGNAL( selectedToolChanged(int) ),_imp->gui,SLOT( onRotoSelectedToolChanged(int) ) ); + QObject::connect( roto,SIGNAL( selectedToolChanged(int) ),getGui(),SLOT( onRotoSelectedToolChanged(int) ) ); std::pair::iterator,bool> ret = _imp->rotoNodes.insert( std::make_pair(n,roto) ); assert(ret.second); @@ -685,7 +681,7 @@ ViewerTab::getRotoGuiSharedData(NodeGui* node) const void ViewerTab::onRotoEvaluatedForThisViewer() { - _imp->gui->onViewerRotoEvaluated(this); + getGui()->onViewerRotoEvaluated(this); } void @@ -721,12 +717,9 @@ ViewerTab::onTrackerNodeGuiSettingsPanelClosed(bool closed) } void -ViewerTab::notifyAppClosing() +ViewerTab::notifyGuiClosing() { - _imp->gui = 0; _imp->timeLineGui->discardGuiPointer(); - _imp->app = 0; - for (std::map::iterator it = _imp->rotoNodes.begin() ; it!=_imp->rotoNodes.end(); ++it) { it->second->notifyGuiClosing(); } @@ -753,7 +746,7 @@ ViewerTab::onCompositingOperatorChangedInternal(Natron::ViewerCompositingOperato _imp->infoWidget[1]->show(); } - _imp->viewer->updateGL(); + _imp->viewer->update(); } void @@ -1002,9 +995,14 @@ ViewerTab::connectToInput(int inputNb) { InspectorNode* node = dynamic_cast(getInternalNode()->getNode().get()); assert(node); - getInternalNode()->setActivateInputChangeRequestedFromViewer(true); + bool isAutoWipeEnabled = appPTR->getCurrentSettings()->isAutoWipeEnabled(); + if (isAutoWipeEnabled) { + getInternalNode()->setActivateInputChangeRequestedFromViewer(true); + } node->setActiveInputAndRefresh(inputNb, true); - getInternalNode()->setActivateInputChangeRequestedFromViewer(false); + if (isAutoWipeEnabled) { + getInternalNode()->setActivateInputChangeRequestedFromViewer(false); + } } void @@ -1014,26 +1012,31 @@ ViewerTab::onAvailableComponentsChanged() } +void +ViewerTab::refreshFPSBoxFromClipPreferences() +{ + int activeInputs[2]; + + _imp->viewerNode->getActiveInputs(activeInputs[0], activeInputs[1]); + EffectInstance* input0 = activeInputs[0] != - 1 ? _imp->viewerNode->getInput(activeInputs[0]) : 0; + if (input0) { + _imp->fpsBox->setValue(input0->getPreferredFrameRate()); + } else { + EffectInstance* input1 = activeInputs[1] != - 1 ? _imp->viewerNode->getInput(activeInputs[1]) : 0; + if (input1) { + _imp->fpsBox->setValue(input1->getPreferredFrameRate()); + } else { + _imp->fpsBox->setValue(getGui()->getApp()->getProjectFrameRate()); + } + } + onSpinboxFpsChangedInternal(_imp->fpsBox->value()); +} + void ViewerTab::onClipPreferencesChanged() { //Try to set auto-fps if it is enabled if (_imp->fpsLocked) { - - int activeInputs[2]; - - _imp->viewerNode->getActiveInputs(activeInputs[0], activeInputs[1]); - EffectInstance* input0 = activeInputs[0] != - 1 ? _imp->viewerNode->getInput(activeInputs[0]) : 0; - if (input0) { - _imp->fpsBox->setValue(input0->getPreferredFrameRate()); - } else { - EffectInstance* input1 = activeInputs[1] != - 1 ? _imp->viewerNode->getInput(activeInputs[1]) : 0; - if (input1) { - _imp->fpsBox->setValue(input1->getPreferredFrameRate()); - } else { - _imp->fpsBox->setValue(getGui()->getApp()->getProjectFrameRate()); - } - } - onSpinboxFpsChanged(_imp->fpsBox->value()); + refreshFPSBoxFromClipPreferences(); } } diff --git a/Gui/ViewerTab40.cpp b/Gui/ViewerTab40.cpp index 4886a591ab..48fc7c83fa 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -220,11 +220,17 @@ ViewerTab::onCanSetFPSLabelClicked(bool toggled) void ViewerTab::onCanSetFPSClicked(bool toggled) { - _imp->fpsBox->setReadOnly(!toggled); + _imp->fpsBox->setEnabled(toggled); { QMutexLocker l(&_imp->fpsLockedMutex); _imp->fpsLocked = !toggled; } + + if (toggled) { + onSpinboxFpsChangedInternal(_imp->userFps); + } else { + refreshFPSBoxFromClipPreferences(); + } } @@ -557,8 +563,8 @@ void ViewerTab::onVideoEngineStopped() { ///Refresh knobs - if (_imp->gui && _imp->gui->isGUIFrozen()) { - NodeGraph* graph = _imp->gui->getNodeGraph(); + if (getGui() && getGui()->isGUIFrozen()) { + NodeGraph* graph = getGui()->getNodeGraph(); if (graph && _imp->timeLineGui) { boost::shared_ptr timeline = _imp->timeLineGui->getTimeline(); graph->refreshNodesKnobsAtTime(timeline->currentFrame()); @@ -582,22 +588,35 @@ ViewerTab::setPickerEnabled(bool enabled) } void -ViewerTab::onPickerButtonClicked(bool clicked) +ViewerTab::onMousePressCalledInViewer() { - const std::list &viewers = getGui()->getViewersList(); - for (std::list::const_iterator it = viewers.begin(); it!=viewers.end(); ++it) { - if ((*it) != this) { - (*it)->onPickerButtonClicked(clicked); + takeClickFocus(); +} + +void +ViewerTab::onPickerButtonClickedInternal(ViewerTab* caller,bool clicked) +{ + if (this == caller) { + const std::list &viewers = getGui()->getViewersList(); + for (std::list::const_iterator it = viewers.begin(); it!=viewers.end(); ++it) { + if ((*it) != caller) { + (*it)->onPickerButtonClickedInternal(caller,clicked); + } } } - _imp->pickerButton->setDown(clicked); - + setInfobarVisible(clicked); getGui()->clearColorPickers(); } +void +ViewerTab::onPickerButtonClicked(bool clicked) +{ + onPickerButtonClickedInternal(this, clicked); +} + void ViewerTab::onCheckerboardButtonClicked() { @@ -627,11 +646,24 @@ void ViewerTab::setCheckerboardEnabled(bool enabled) } void -ViewerTab::onSpinboxFpsChanged(double fps) +ViewerTab::onSpinboxFpsChangedInternal(double fps) { + _imp->fpsBox->setValue(fps); _imp->viewerNode->getRenderEngine()->setDesiredFPS(fps); - QMutexLocker k(&_imp->fpsMutex); - _imp->fps = fps; + { + QMutexLocker k(&_imp->fpsMutex); + _imp->fps = fps; + } +} + +void +ViewerTab::onSpinboxFpsChanged(double fps) +{ + { + QMutexLocker k(&_imp->fpsMutex); + _imp->userFps = fps; + } + onSpinboxFpsChangedInternal(fps); } double @@ -647,6 +679,7 @@ ViewerTab::setDesiredFps(double fps) { QMutexLocker l(&_imp->fpsMutex); _imp->fps = fps; + _imp->userFps = fps; } _imp->fpsBox->setValue(fps); _imp->viewerNode->getRenderEngine()->setDesiredFPS(fps); @@ -693,8 +726,8 @@ ViewerTab::setTurboButtonDown(bool down) void ViewerTab::redrawGLWidgets() { - _imp->viewer->updateGL(); - _imp->timeLineGui->updateGL(); + _imp->viewer->update(); + _imp->timeLineGui->update(); } void @@ -1071,7 +1104,7 @@ ViewerTab::onGammaSliderEditingFinished(bool hasMovedOnce) { bool autoProxyEnabled = appPTR->getCurrentSettings()->isAutoProxyEnabled(); if (autoProxyEnabled && hasMovedOnce) { - getGui()->renderAllViewers(); + getGui()->renderAllViewers(true); } } @@ -1080,7 +1113,7 @@ ViewerTab::onGainSliderEditingFinished(bool hasMovedOnce) { bool autoProxyEnabled = appPTR->getCurrentSettings()->isAutoProxyEnabled(); if (autoProxyEnabled && hasMovedOnce) { - getGui()->renderAllViewers(); + getGui()->renderAllViewers(true); } } @@ -1093,11 +1126,11 @@ ViewerTab::isViewersSynchroEnabled() const void ViewerTab::synchronizeOtherViewersProjection() { - assert(_imp->gui); - _imp->gui->setMasterSyncViewer(this); + assert(getGui()); + getGui()->setMasterSyncViewer(this); double left,bottom,factor,par; _imp->viewer->getProjection(&left, &bottom, &factor, &par); - const std::list& viewers = _imp->gui->getViewersList(); + const std::list& viewers = getGui()->getViewersList(); for (std::list::const_iterator it = viewers.begin(); it != viewers.end(); ++it) { if ((*it) != this) { (*it)->getViewer()->setProjection(left, bottom, factor, par); @@ -1112,7 +1145,7 @@ void ViewerTab::onSyncViewersButtonPressed(bool clicked) { - const std::list& viewers = _imp->gui->getViewersList(); + const std::list& viewers = getGui()->getViewersList(); for (std::list::const_iterator it = viewers.begin(); it != viewers.end(); ++it) { (*it)->_imp->syncViewerButton->setDown(clicked); (*it)->_imp->syncViewerButton->setChecked(clicked); @@ -1179,14 +1212,14 @@ ViewerTab::toggleTripleSync(bool toggled) _imp->tripleSyncButton->setDown(toggled); getGui()->setTripleSyncEnabled(toggled); if (toggled) { - DopeSheetEditor* deditor = _imp->gui->getDopeSheetEditor(); - CurveEditor* cEditor = _imp->gui->getCurveEditor(); + DopeSheetEditor* deditor = getGui()->getDopeSheetEditor(); + CurveEditor* cEditor = getGui()->getCurveEditor(); //Sync curve editor and dopesheet tree width cEditor->setTreeWidgetWidth(deditor->getTreeWidgetWidth()); SequenceTime left,right; _imp->timeLineGui->getVisibleRange(&left, &right); - _imp->gui->centerOpenedViewersOn(left, right); + getGui()->centerOpenedViewersOn(left, right); deditor->centerOn(left, right); cEditor->getCurveWidget()->centerOn(left, right); } diff --git a/Gui/ViewerTabPrivate.cpp b/Gui/ViewerTabPrivate.cpp index 8b51d3cc17..6200ce16d9 100644 --- a/Gui/ViewerTabPrivate.cpp +++ b/Gui/ViewerTabPrivate.cpp @@ -39,15 +39,15 @@ #include "Gui/ClickableLabel.h" #include "Gui/Gui.h" #include "Gui/GuiAppInstance.h" +#include "Gui/ViewerTab.h" using namespace Natron; -ViewerTabPrivate::ViewerTabPrivate(Gui* gui, - ViewerInstance* node) -: viewer(NULL) -, app( gui->getApp() ) +ViewerTabPrivate::ViewerTabPrivate(ViewerTab* publicInterface,ViewerInstance* node) +: publicInterface(publicInterface) +, viewer(NULL) , viewerContainer(NULL) , viewerLayout(NULL) , viewerSubContainer(NULL) @@ -82,7 +82,6 @@ ViewerTabPrivate::ViewerTabPrivate(Gui* gui, , gainBox(NULL) , gainSlider(NULL) , lastFstopValue(0.) -, autoConstrastLabel(NULL) , autoContrast(NULL) , gammaBox(NULL) , lastGammaValue(1.) @@ -121,6 +120,7 @@ ViewerTabPrivate::ViewerTabPrivate(Gui* gui, , fpsLockedMutex() , fpsLocked(true) , fpsBox(NULL) +, userFps(24) , turboButton(NULL) , timeLineGui(NULL) , rotoNodes() @@ -130,7 +130,6 @@ ViewerTabPrivate::ViewerTabPrivate(Gui* gui, , inputNamesMap() , compOperatorMutex() , compOperator(eViewerCompositingOperatorNone) -, gui(gui) , viewerNode(node) , visibleToolbarsMutex() , infobarVisible(true) @@ -145,6 +144,8 @@ ViewerTabPrivate::ViewerTabPrivate(Gui* gui, , fpsMutex() , fps(24.) , lastOverlayNode() +, hasPenDown(false) +, hasCaughtPenMotionWhileDragging(false) { infoWidget[0] = infoWidget[1] = NULL; currentRoto.first = NULL; @@ -332,7 +333,7 @@ ViewerTabPrivate::getComponentsAvailabel(std::set* comps) const activeInput[i] = viewerNode->getInput(activeInputIdx[i]); if (activeInput[i]) { EffectInstance::ComponentsAvailableMap compsAvailable; - activeInput[i]->getComponentsAvailable(gui->getApp()->getTimeLine()->currentFrame(), &compsAvailable); + activeInput[i]->getComponentsAvailable(publicInterface->getGui()->getApp()->getTimeLine()->currentFrame(), &compsAvailable); for (EffectInstance::ComponentsAvailableMap::iterator it = compsAvailable.begin(); it != compsAvailable.end(); ++it) { if (it->second.lock()) { comps->insert(it->first); diff --git a/Gui/ViewerTabPrivate.h b/Gui/ViewerTabPrivate.h index 4d6c856017..7181af1c54 100644 --- a/Gui/ViewerTabPrivate.h +++ b/Gui/ViewerTabPrivate.h @@ -49,6 +49,7 @@ namespace Natron { class ImageComponents; } class ViewerGL; +class ViewerTab; class GuiAppInstance; class ComboBox; class Button; @@ -83,9 +84,10 @@ struct ViewerTabPrivate typedef std::map InputNamesMap; + ViewerTab* publicInterface; + /*OpenGL viewer*/ ViewerGL* viewer; - GuiAppInstance* app; QWidget* viewerContainer; QHBoxLayout* viewerLayout; QWidget* viewerSubContainer; @@ -125,8 +127,7 @@ struct ViewerTabPrivate SpinBox* gainBox; ScaleSliderQWidget* gainSlider; double lastFstopValue; - ClickableLabel* autoConstrastLabel; - QCheckBox* autoContrast; + Button* autoContrast; SpinBox* gammaBox; double lastGammaValue; Button* toggleGammaButton; @@ -172,6 +173,7 @@ struct ViewerTabPrivate mutable QMutex fpsLockedMutex; bool fpsLocked; SpinBox* fpsBox; + double userFps; Button* turboButton; /*frame seeker*/ @@ -183,7 +185,6 @@ struct ViewerTabPrivate InputNamesMap inputNamesMap; mutable QMutex compOperatorMutex; ViewerCompositingOperatorEnum compOperator; - Gui* gui; ViewerInstance* viewerNode; // < pointer to the internal node mutable QMutex visibleToolbarsMutex; //< protects the 4 bool below @@ -204,9 +205,10 @@ struct ViewerTabPrivate //The last node that took the penDown/motion/keyDown/keyRelease etc... boost::weak_ptr lastOverlayNode; + bool hasPenDown; + bool hasCaughtPenMotionWhileDragging; - ViewerTabPrivate(Gui* gui, - ViewerInstance* node); + ViewerTabPrivate(ViewerTab* publicInterface,ViewerInstance* node); #ifdef NATRON_TRANSFORM_AFFECTS_OVERLAYS // return the tronsform to apply to the overlay as a 3x3 homography in canonical coordinates diff --git a/HostSupport/HostSupport.pro b/HostSupport/HostSupport.pro index af52d68b00..7470e352ec 100644 --- a/HostSupport/HostSupport.pro +++ b/HostSupport/HostSupport.pro @@ -19,12 +19,13 @@ TARGET = HostSupport TEMPLATE = lib CONFIG += staticlib -CONFIG += expat CONFIG -= qt include(../global.pri) include(../config.pri) +!noexpat: CONFIG += expat + contains(CONFIG,trace_ofx_actions) { DEFINES += OFX_DEBUG_ACTIONS } @@ -67,6 +68,26 @@ win32 { } } +noexpat { + SOURCES += \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/xmlparse.c \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/xmltok.c \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/xmltok_impl.c \ + + HEADERS += \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/expat.h \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/expat_external.h \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/ascii.h \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/xmltok.h \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/xmltok_impl.h \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/asciitab.h \ + ../libs/OpenFX/HostSupport/expat-2.1.0/expat_config.h \ + + DEFINES += HAVE_EXPAT_CONFIG_H + + INCLUDEPATH += $$PWD/../libs/OpenFX/HostSupport/expat-2.1.0/lib +} + SOURCES += \ ../libs/OpenFX/HostSupport/src/ofxhBinary.cpp \ ../libs/OpenFX/HostSupport/src/ofxhClip.cpp \ diff --git a/INSTALL_LINUX.md b/INSTALL_LINUX.md index e35a820def..5a3540245d 100644 --- a/INSTALL_LINUX.md +++ b/INSTALL_LINUX.md @@ -151,19 +151,14 @@ You must copy them to a directory named `../share/OpenColorIO-Configs` relative pacman -S qt4 cairo glew python expat boost pixman ffmpeg opencolorio openimageio wget git cmake gcc make libxslt pkg-config wget https://raw.githubusercontent.com/olear/natron-linux/master/include/misc/build-natron-on-archlinux.sh -## CentOS6 - -### Add devtools-2 -``` -wget http://people.centos.org/tru/devtools-2/devtools-2.repo -O /etc/yum.repos.d/devtools-2.repo -``` +## CentOS6+ (64-bit) ### Install dependencies ``` -yum -y install libxslt-devel pango-devel librsvg2-devel libxml2-devel devtoolset-2-toolchain gcc-c++ kernel-devel libX*devel fontconfig-devel freetype-devel zlib-devel *GL*devel *xcb*devel xorg*devel libdrm-devel mesa*devel *glut*devel dbus-devel bzip2-devel glib2-devel bison flex expat-devel libtool-ltdl-devel git +yum -y install gcc-c++ wget libX*devel *GL*devel *xcb*devel xorg*devel libdrm-devel mesa*devel *glut*devel dbus-devel bison flex expat-devel libtool-ltdl-devel git make glibc-devel glibc-devel.i686 ``` -### Download SDK +### Download SDK (third-party software) ``` wget http://downloads.natron.fr/Third_Party_Binaries/Natron-CY2015-Linux-x86_64-SDK.tar.xz tar xvf Natron-CY2015-Linux-x86_64-SDK.tar.xz -C /opt/ @@ -180,18 +175,15 @@ for i in $(echo "Natron openfx-io openfx-misc openfx-arena");do cd $i ; git subm ### Build Natron and plugins ``` -scl enable devtoolset-2 bash export INSTALL_PATH=/opt/Natron-CY2015 export PKG_CONFIG_PATH=$INSTALL_PATH/lib/pkgconfig:$INSTALL_PATH/ffmpeg-gpl/lib/pkgconfig -export LD_LIBRARY_PATH=$INSTALL_PATH/lib:$INSTALL_PATH/ffmpeg-gpl/lib -export PATH=/usr/local/bin:$INSTALL_PATH/bin:$INSTALL_PATH/ffmpeg-gpl/bin:$PATH +export LD_LIBRARY_PATH=$INSTALL_PATH/gcc/lib64:$INSTALL_PATH/lib:$INSTALL_PATH/ffmpeg-gpl/lib +export PATH=$INSTALL_PATH/gcc/bin:$INSTALL_PATH/bin:$INSTALL_PATH/ffmpeg-gpl/bin:$PATH export QTDIR=$INSTALL_PATH export BOOST_ROOT=$INSTALL_PATH export PYTHON_HOME=$INSTALL_PATH export PYTHON_PATH=$INSTALL_PATH/lib/python2.7 export PYTHON_INCLUDE=$INSTALL_PATH/include/python2.7 -export OPENJPEG_HOME=$INSTALL_PATH -export THIRD_PARTY_TOOLS_HOME=$INSTALL_PATH cd Natron wget https://raw.githubusercontent.com/MrKepzie/Natron/workshop/tools/linux/include/natron/config.pri diff --git a/Renderer/Renderer.pro b/Renderer/Renderer.pro index 16d38f2c6b..bf145e70ad 100644 --- a/Renderer/Renderer.pro +++ b/Renderer/Renderer.pro @@ -24,7 +24,8 @@ TARGET = NatronRenderer CONFIG += console CONFIG -= app_bundle CONFIG += moc -CONFIG += boost qt expat cairo python shiboken pyside +CONFIG += boost qt cairo python shiboken pyside +!noexpat: CONFIG += expat TEMPLATE = app diff --git a/Tests/Image_Test.cpp b/Tests/Image_Test.cpp index 780286536d..93a7483097 100644 --- a/Tests/Image_Test.cpp +++ b/Tests/Image_Test.cpp @@ -113,7 +113,7 @@ TEST(ImageKeyTest,Equality) { SequenceTime time1 = 0; int view1 = 0; double pa1 = 1.; - Natron::ImageKey key1(0,randomHashKey1, false, time1,view1,pa1, false); + Natron::ImageKey key1(0,randomHashKey1, false, time1,view1,pa1, false, false); U64 keyHash1 = key1.getHash(); @@ -122,7 +122,7 @@ TEST(ImageKeyTest,Equality) { SequenceTime time2 = time1; int view2 = view1; double pa2 = pa1; - Natron::ImageKey key2(0,randomHashKey2, false, time2,view2,pa2, false); + Natron::ImageKey key2(0,randomHashKey2, false, time2,view2,pa2, false, false); U64 keyHash2 = key2.getHash(); ASSERT_TRUE(keyHash1 == keyHash2); } @@ -134,7 +134,7 @@ TEST(ImageKeyTest,Difference) { SequenceTime time1 = 0; int view1 = 0; double pa1 = 1.; - Natron::ImageKey key1(0,randomHashKey1,false, time1,view1,pa1, false); + Natron::ImageKey key1(0,randomHashKey1,false, time1,view1,pa1, false, false); U64 keyHash1 = key1.getHash(); @@ -144,7 +144,7 @@ TEST(ImageKeyTest,Difference) { SequenceTime time2 = time1; int view2 = view1; double pa2 = pa1; - Natron::ImageKey key2(0,randomHashKey2,false, time2,view2,pa2, false); + Natron::ImageKey key2(0,randomHashKey2,false, time2,view2,pa2, false, false); U64 keyHash2 = key2.getHash(); ASSERT_TRUE(keyHash1 != keyHash2); } diff --git a/Tests/Tests.pro b/Tests/Tests.pro index 823bcf3dbe..5129675ada 100644 --- a/Tests/Tests.pro +++ b/Tests/Tests.pro @@ -20,10 +20,12 @@ TEMPLATE = app CONFIG += console CONFIG -= app_bundle CONFIG += moc rcc -CONFIG += boost glew opengl qt expat cairo python shiboken pyside +CONFIG += boost glew opengl qt cairo python shiboken pyside QT += gui core opengl network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets concurrent +!noexpat: CONFIG += expat + INCLUDEPATH += google-test/include INCLUDEPATH += google-test INCLUDEPATH += google-mock/include diff --git a/global.pri b/global.pri index 1b13972368..f476f6fa21 100644 --- a/global.pri +++ b/global.pri @@ -53,7 +53,8 @@ CONFIG(debug, debug|release){ CONFIG(noassertions) { - DEFINES *= NDEBUG QT_NO_DEBUG +#See http://doc.qt.io/qt-4.8/debug.html + DEFINES *= NDEBUG QT_NO_DEBUG QT_NO_DEBUG_OUTPUT QT_NO_WARNING_OUTPUT } CONFIG(snapshot) { diff --git a/libs/OpenFX b/libs/OpenFX index de8216d05b..0424cf839c 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit de8216d05b0d4f230734d1b7a2b7b7144a4f9f15 +Subproject commit 0424cf839cda460d8403c0ddd7c6861a07fc05c7 diff --git a/tools/MacOSX/build-installer.sh b/tools/MacOSX/build-installer.sh index c9e45623a1..37e027e019 100755 --- a/tools/MacOSX/build-installer.sh +++ b/tools/MacOSX/build-installer.sh @@ -89,4 +89,5 @@ end tell EOT # convert to compressed image, delete temp image -hdiutil convert "${DMG_TMP}" -format UDZO -imagekey zlib-level=9 -o "${DMG_FINAL}" || exit 1 \ No newline at end of file +# UDBZ (bzip2) is supported on OS X >= 10.4 +hdiutil convert "${DMG_TMP}" -format UDBZ -o "${DMG_FINAL}" || exit 1 diff --git a/tools/Windows/common.sh b/tools/Windows/common.sh index 54cbb5703b..c87d61aeec 100644 --- a/tools/Windows/common.sh +++ b/tools/Windows/common.sh @@ -19,10 +19,10 @@ fi #THE FOLLOWING CAN BE MODIFIED TO CONFIGURE RELEASE BUILDS #---------------------------------------------------------- NATRON_GIT_TAG=tags/2.0.0-RC1 -IOPLUG_GIT_TAG=tags/2.0.0-RC1 -MISCPLUG_GIT_TAG=tags/2.0.0-RC1 -ARENAPLUG_GIT_TAG=tags/2.0.0-RC1 -CVPLUG_GIT_TAG=tags/2.0.0-RC1 +IOPLUG_GIT_TAG=tags/Natron-2.0.0-RC1 +MISCPLUG_GIT_TAG=tags/Natron-2.0.0-RC1 +ARENAPLUG_GIT_TAG=tags/Natron-2.0.0-RC1 +CVPLUG_GIT_TAG=tags/Natron-2.0.0-RC1 #---------------------------------------------------------- diff --git a/tools/Windows/include/patches/SeExpr/mingw-compat.patch b/tools/Windows/include/patches/SeExpr/mingw-compat.patch new file mode 100644 index 0000000000..d6ba963b53 --- /dev/null +++ b/tools/Windows/include/patches/SeExpr/mingw-compat.patch @@ -0,0 +1,17 @@ +--- SePlatform.h.orig 2015-10-04 16:59:06.340547700 +0200 ++++ SePlatform.h 2015-10-04 17:00:59.794036900 +0200 +@@ -78,12 +78,13 @@ + + // missing functions on Windows + #ifdef WINDOWS ++#ifndef __MINGW32__ + # define snprintf sprintf_s + # define strtok_r strtok_s + typedef __int64 FilePos; + # define fseeko _fseeki64 + # define ftello _ftelli64 +- ++#endif // !defined(__MINGW32__) + inline double log2(double x) { + return log(x) * 1.4426950408889634; + } diff --git a/tools/Windows/include/scripts/build-sdk.sh b/tools/Windows/include/scripts/build-sdk.sh index 647de8ecd0..57896e57a4 100644 --- a/tools/Windows/include/scripts/build-sdk.sh +++ b/tools/Windows/include/scripts/build-sdk.sh @@ -200,10 +200,13 @@ if [ ! -f $INSTALL_PATH/lib/libSeExpr.a ]; then fi tar xvf $SRC_PATH/$SEE_TAR || exit 1 cd SeExpr-* || exit 1 + cd src/SeExpr || exit 1 + patch -u -i $CWD/include/patches/SeExpr/mingw-compat.patch || exit 1 + cd ../.. mkdir build || exit 1 cd build || exit 1 env CPPFLAGS="-I${INSTALL_PATH}/include" LDFLAGS="-L${INSTALL_PATH}/lib" cmake .. -G"MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=$INSTALL_PATH || exit 1 - make || exit 1 + make -j${MKJOBS} || exit 1 make install || exit 1 mkdir -p $INSTALL_PATH/docs/seexpr || exit 1 cp ../README ../src/doc/license.txt $INSTALL_PATH/docs/seexpr/ || exit 1 diff --git a/tools/linux/README.md b/tools/linux/README.md index 4614c54652..c4733f6d97 100644 --- a/tools/linux/README.md +++ b/tools/linux/README.md @@ -20,12 +20,6 @@ yum install libGLU apt-get install libxcb-shm0 ``` -**Kubuntu:** - -``` -apt-get install librsvg2-2 -``` - Technical information ===================== @@ -34,22 +28,6 @@ Minimum requirements for running Natron on Linux: - Linux 2.6.18 - Glibc 2.12 - LibGCC 4.4 -- Freetype -- Zlib -- Glib -- LibSM -- LibICE -- LibXrender -- Fontconfig -- LibXext -- LibX11 -- Libxcb -- Libexpat -- LibXau -- Bzip2 -- LibGL -- Pango -- librsvg Most Linux installations since 2010 meet these requirements. Natron is compatible with the VFX Reference Platform CY2015. @@ -78,7 +56,13 @@ For this to work you need to create a file named **repo.sh** next to *snapshot.s REPO_DEST=user@host:/path REPO_URL=http://some.url + +Launching snapshots +=================== + #Build GPL snapshots using 8 threads + MKJOBS=8 NATRON_LICENSE=GPL sh snapshot.sh + Release build: =============== diff --git a/tools/linux/common.sh b/tools/linux/common.sh index d65c35e572..7635f43a78 100644 --- a/tools/linux/common.sh +++ b/tools/linux/common.sh @@ -49,8 +49,8 @@ PACKAGES=$NATRON_PKG,$CORELIBS_PKG,$PROFILES_PKG,$IOPLUG_PKG,$MISCPLUG_PKG,$AREN GIT_OCIO_CONFIG_TAR=https://github.com/MrKepzie/OpenColorIO-Configs/archive/Natron-v2.0.tar.gz COLOR_PROFILES_VERSION=2.0.0 -# bump number on bugfixes -CORELIBS_VERSION=2.0.0 +# bump timestamp on SDK changes, important! +CORELIBS_VERSION=20151006 # SDK # @@ -136,8 +136,8 @@ OCIO_TAR=OpenColorIO-1.0.9.tar.gz OIIO_TAR=oiio-Release-1.5.18.tar.gz PYSIDE_TAR=pyside-qt4.8+1.2.2.tar.bz2 SHIBOK_TAR=shiboken-1.2.2.tar.bz2 -#LIBXML_TAR=libxml2-2.9.2.tar.gz (#25) -#LIBXSL_TAR=libxslt-1.1.28.tar.gz (#25) +LIBXML_TAR=libxml2-2.9.2.tar.gz +LIBXSLT_TAR=libxslt-1.1.28.tar.gz SEE_TAR=SeExpr-rel-1.0.1.tar.gz LIBRAW_TAR=LibRaw-0.16.0.tar.gz PIX_TAR=pixman-0.32.6.tar.gz @@ -158,6 +158,19 @@ DIRAC_TAR=schroedinger-1.0.11.tar.gz ORC_TAR=orc-0.4.23.tar.xz X264_TAR=x264-snapshot-20150725-2245.tar.bz2 #GPL-only XVID_TAR=xvidcore-1.3.4.tar.gz #GPL-only +ICU_TAR=icu4c-55_1-src.tgz +ZLIB_TAR=zlib-1.2.8.tar.gz +EXPAT_TAR=expat-2.1.0.tar.gz +FCONFIG_TAR=fontconfig-2.10.2.tar.gz +FTYPE_TAR=freetype-2.4.11.tar.gz +FFI_TAR=libffi-3.2.1.tar.gz +GLIB_TAR=glib-2.42.2.tar.xz +BUZZ_TAR=harfbuzz-0.9.40.tar.bz2 +PANGO_TAR=pango-1.37.0.tar.xz +BZIP_TAR=bzip2-1.0.6.tar.gz +CROCO_TAR=libcroco-0.6.8.tar.xz +SVG_TAR=librsvg-2.40.10.tar.xz +GDK_TAR=gdk-pixbuf-2.32.1.tar.xz TC_GCC=4.8.5 TC_MPC=1.0.1 @@ -216,3 +229,10 @@ if [ -z "$MKJOBS" ]; then MKJOBS=$DEFAULT_MKJOBS fi +# License +# +# +if [ "$NATRON_LICENSE" != "GPL" ] && [ "$NATRON_LICENSE" != "COMMERCIAL" ]; then + echo "Please select a License with NATRON_LICENSE=(GPL,COMMERCIAL)" + exit 1 +fi diff --git a/tools/linux/include/natron/Natron.spec b/tools/linux/include/natron/Natron.spec new file mode 100644 index 0000000000..f76ae139b8 --- /dev/null +++ b/tools/linux/include/natron/Natron.spec @@ -0,0 +1,41 @@ +%define _binary_payload w7.xzdio + +Summary: open-source compositing software +Name: Natron + +Version: REPLACE_VERSION +Release: 1 +License: GPLv2 + +Group: System Environment/Base +URL: http://natron.fr +Vendor: INRIA, http://inria.fr +AutoReqProv: no + +%description +Node-graph based, open-source compositing software. Similar in functionalities to Adobe After Effects and The Foundry Nuke. + +%build +echo OK + +%install +mkdir -p $RPM_BUILD_ROOT/opt/Natron2 $RPM_BUILD_ROOT/usr/share/applications $RPM_BUILD_ROOT/usr/share/pixmaps $RPM_BUILD_ROOT/usr/bin + +cp -a /root/Natron/tools/linux/tmp/Natron-installer/packages/fr.inria.*/data/* $RPM_BUILD_ROOT/opt/Natron2/ +cat /root/Natron/tools/linux/include/natron/Natron2.desktop > $RPM_BUILD_ROOT/usr/share/applications/Natron2.desktop +cp /root/Natron/tools/linux/include/config/icon.png $RPM_BUILD_ROOT/usr/share/pixmaps/Natron.png + +cd $RPM_BUILD_ROOT/usr/bin ; ln -sf ../../opt/Natron2/Natron . +cd $RPM_BUILD_ROOT/usr/bin; ln -sf ../../opt/Natron2/NatronRenderer . + +%post -p /opt/Natron2/bin/postinstall.sh + +%files +%defattr(-,root,root) +/opt/Natron2 +/usr/bin/Natron +/usr/bin/NatronRenderer +/usr/share/applications/Natron2.desktop +/usr/share/pixmaps/Natron.png + +%changelog diff --git a/tools/linux/include/natron/Natron2.desktop b/tools/linux/include/natron/Natron2.desktop new file mode 100644 index 0000000000..2744b272f4 --- /dev/null +++ b/tools/linux/include/natron/Natron2.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Terminal=false +Name=Natron2 +MimeType=application/vnd.natron.project;application/vnd.natron.nodepresets;application/vnd.natron.layout +Exec=Natron %U +GenericName=Compositing software +Comment=Node-graph based compositing software +Icon=Natron +Categories=Graphics;2DGraphics;RasterGraphics; + diff --git a/tools/linux/include/scripts/Natron.sh b/tools/linux/include/scripts/Natron.sh index 58c7e0eec7..766a0f5da3 100644 --- a/tools/linux/include/scripts/Natron.sh +++ b/tools/linux/include/scripts/Natron.sh @@ -1,5 +1,9 @@ #!/bin/bash +# +# Universal launch script for Natron +# +# Get real current dir SOURCE="${BASH_SOURCE[0]}" while [ -h "$SOURCE" ]; do SOURCEDIR=`dirname "$SOURCE"` @@ -10,21 +14,71 @@ done SOURCEDIR=`dirname "$SOURCE"` DIR=`cd -P "$SOURCEDIR" && pwd` +# Force numeric export LC_NUMERIC=C +# Set fontconfig path +# Not needed (done in app), but added to avoid warn before splashscreen +if [ -d "$DIR/Resources/etc/fonts" ]; then + export FONTCONFIG_PATH="$DIR/Resources/etc/fonts" +fi + +# Check gcc compat +COMPAT_ARCH=`uname -m` +COMPAT_VERSION=3.4.15 +if [ "$COMPAT_ARCH" = "x86_64" ]; then + COMPAT_SUFFIX=64 +fi +if [ -f /usr/lib$COMPAT_SUFFIX/libstdc++.so.6 ]; then + STDC_LIB=/usr/lib$COMPAT_SUFFIX/libstdc++.so.6 +elif [ -f /usr/lib/libstdc++.so.6 ]; then + STDC_LIB=/usr/lib/libstdc++.so.6 +elif [ -f /usr/lib/$COMPAT_ARCH-linux-gnu/libstdc++.so.6 ]; then + STDC_LIB=/usr/lib/$COMPAT_ARCH-linux-gnu/libstdc++.so.6 +elif [ -f /usr/lib/i386-linux-gnu/libstdc++.so.6 ]; then + STDC_LIB=/usr/lib/i386-linux-gnu/libstdc++.so.6 +fi +if [ "$STDC_LIB" != "" ]; then + COMPAT_GCC=`$DIR/bin/strings $STDC_LIB | grep GLIBCXX_${COMPAT_VERSION}` +fi +if [ "$COMPAT_GCC" = "GLIBCXX_${COMPAT_VERSION}" ]; then + if [ -f "$DIR/lib/libstdc++.so.6" ]; then + rm -f $DIR/lib/libstdc++.so.6 || echo "Failed to remove symlink, please run as root to fix." + fi + if [ -f "$DIR/lib/libgcc_s.so.1" ]; then + rm -f $DIR/lib/libgcc_s.so.1 || echo "Failed to remove symlink, please run as root to fix." + fi + if [ -f "$DIR/lib/libgomp.so.1" ]; then + rm -f $DIR/lib/libgomp.so.1 || echo "Failed to remove symlink, please run as root to fix." + fi +else + if [ ! -f "$DIR/lib/libstdc++.so.6" ]; then + cd $DIR/lib ; ln -sf compat/libstdc++.so.6 . || echo "Failed to create symlink, please run as root to fix." + fi + if [ ! -f "$DIR/lib/libgcc_s.so.1" ]; then + cd $DIR/lib ; ln -sf compat/libgcc_s.so.1 . || echo "Failed to create symlink, please run as root to fix." + fi + if [ ! -f "$DIR/lib/libgomp.so.1" ]; then + cd $DIR/lib ; ln -sf compat/libgomp.so.1 . || echo "Failed to create symlink, please run as root to fix." + fi +fi + +# Check for updates if [ "$1" = "-update" -a -x "$DIR/NatronSetup" ]; then "$DIR/NatronSetup" --updater exit 0 fi +# Portable mode, save settings in current dir if [ "$1" = "-portable" ]; then #XDG_CACHE_HOME=/tmp - XDG_DATA_HOME=$DIR - XDG_CONFIG_HOME=$DIR + XDG_DATA_HOME="$DIR" + XDG_CONFIG_HOME="$DIR" export XDG_DATA_HOME XDG_CONFIG_HOME fi -if [ "$1" = "-debug" ]; then +# start app, with optional debug +if [ "$1" = "-debug" -a -x "$DIR/bin/Natron.debug" ]; then SEGFAULT_SIGNALS="all" export SEGFAULT_SIGNALS catchsegv "$DIR/bin/Natron.debug" -style fusion "$@" diff --git a/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index c875a8293a..6ecdfc1166 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -73,15 +73,22 @@ mkdir -p $INSTALLER/config $INSTALLER/packages || exit 1 cat $INC_PATH/config/config.xml | sed "s/_VERSION_/${NATRON_VERSION_NUMBER}/;s#_OS_BRANCH_BIT_#${REPO_OS}#g;s#_URL_#${REPO_URL}#g" > $INSTALLER/config/config.xml || exit 1 cp $INC_PATH/config/*.png $INSTALLER/config/ || exit 1 +if [ "$NATRON_LICENSE" = "GPL" ]; then + FFLIC=gpl +else + FFLIC=lgpl +fi + # OFX IO OFX_IO_VERSION=$TAG OFX_IO_PATH=$INSTALLER/packages/$IOPLUG_PKG -mkdir -p $OFX_IO_PATH/data $OFX_IO_PATH/meta $OFX_IO_PATH/data/Plugins || exit 1 +mkdir -p $OFX_IO_PATH/data $OFX_IO_PATH/meta $OFX_IO_PATH/data/Plugins $OFX_IO_PATH/data/bin || exit 1 cat $XML/openfx-io.xml | sed "s/_VERSION_/${OFX_IO_VERSION}/;s/_DATE_/${DATE}/" > $OFX_IO_PATH/meta/package.xml || exit 1 cat $QS/openfx-io.qs > $OFX_IO_PATH/meta/installscript.qs || exit 1 -cat $INSTALL_PATH/docs/openfx-io/VERSION > $OFX_IO_PATH/meta/ofx-io-license.txt || exit 1 -echo "" >> $OFX_IO_PATH/meta/ofx-io-license.txt || exit 1 -cat $INSTALL_PATH/docs/openfx-io/LICENSE >> $OFX_IO_PATH/meta/ofx-io-license.txt || exit 1 +cp $INSTALL_PATH/ffmpeg-$FFLIC/bin/{ffmpeg,ffprobe} $OFX_IO_PATH/data/bin/ || exit 1 +cat $CWD/include/scripts/ffmpeg.sh > $OFX_IO_PATH/data/ffmpeg || exit 1 +cat $CWD/include/scripts/ffmpeg.sh | sed 's/ffmpeg/ffprobe/g' > $OFX_IO_PATH/data/ffprobe || exit 1 +chmod +x $OFX_IO_PATH/data/{ffmpeg,ffprobe} || exit 1 cp -a $INSTALL_PATH/Plugins/IO.ofx.bundle $OFX_IO_PATH/data/Plugins/ || exit 1 strip -s $OFX_IO_PATH/data/Plugins/*/*/*/* IO_LIBS=$OFX_IO_PATH/data/Plugins/IO.ofx.bundle/Libraries @@ -92,156 +99,26 @@ for x in $OFX_DEPENDS; do cp -v $x $IO_LIBS/ || exit 1 done -if [ "$NATRON_LICENSE" != "GPL" ] && [ "$NATRON_LICENSE" != "COMMERCIAL" ]; then - echo "Please select a License with NATRON_LICENSE=(GPL,COMMERCIAL)" - exit 1 -fi - -if [ "$NATRON_LICENSE" = "GPL" ]; then - FFLIC=gpl -else - FFLIC=lgpl -fi -cp -v $INSTALL_PATH/ffmpeg-$FFLIC/lib/{libavformat.so.56,libavcodec.so.56,libswscale.so.3,libavutil.so.54,libswresample.so.1} $IO_LIBS/ || exit 1 +cp -v $INSTALL_PATH/ffmpeg-$FFLIC/lib/{libavfilter.so.5,libavdevice.so.56,libpostproc.so.53,libavresample.so.2,libavformat.so.56,libavcodec.so.56,libswscale.so.3,libavutil.so.54,libswresample.so.1} $IO_LIBS/ || exit 1 OFX_LIB_DEP=$(ldd $IO_LIBS/*|grep opt | awk '{print $3}') for y in $OFX_LIB_DEP; do cp -v $y $IO_LIBS/ || exit 1 done -rm -f $IO_LIBS/{libgcc*,libstdc*} - -IO_LIC=$OFX_IO_PATH/meta/ofx-io-license.txt -echo "" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -echo "BOOST:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/boost/LICENSE_1_0.txt >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "FFMPEG:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -if [ "$NATRON_LICENSE" = "GPL" ]; then - cat $INSTALL_PATH/docs/ffmpeg/COPYING.GPLv3 >> $IO_LIC || exit 1 -else - cat $INSTALL_PATH/docs/ffmpeg/COPYING.LGPLv2.1 >>$IO_LIC || exit 1 -fi - -echo "" >>$IO_LIC || exit 1 -echo "JPEG:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/jpeg/README >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "OPENCOLORIO:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/ocio/LICENSE >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "OPENIMAGEIO:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/oiio/LICENSE >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "OPENEXR:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/openexr/LICENSE >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "OPENJPEG:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/openjpeg/LICENSE >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "PNG:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/png/LICENSE >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "TIFF:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/tiff/COPYRIGHT >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "SEEXPR:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/seexpr/license.txt >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "LIBRAW:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/libraw/COPYRIGHT >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "JASPER:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/jasper/COPYRIGHT >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "LCMS:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/lcms/COPYING >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "DIRAC:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/dirac/COPYING.MPL >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "LAME:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/lame/COPYING >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "MODPLUG:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/libmodplug/COPYING >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "OGG:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/libogg/COPYING >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "THEORA:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/libtheora/COPYING >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "VORBIS:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/libvorbis/COPYING >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "VPX:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/libvpx/LICENSE >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "OPUS:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/opus/COPYING >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "ORC:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/orc/COPYING >>$IO_LIC || exit 1 - -echo "" >>$IO_LIC || exit 1 -echo "SPEEX:" >>$IO_LIC || exit 1 -echo "" >>$IO_LIC || exit 1 -cat $INSTALL_PATH/docs/speex/COPYING >>$IO_LIC || exit 1 - -if [ "$NATRON_LICENSE" = "GPL" ]; then - echo "" >>$IO_LIC || exit 1 - echo "X264:" >>$IO_LIC || exit 1 - echo "" >>$IO_LIC || exit 1 - cat $INSTALL_PATH/docs/x264/COPYING >>$IO_LIC || exit 1 - - echo "" >>$IO_LIC || exit 1 - echo "XVID:" >>$IO_LIC || exit 1 - echo "" >>$IO_LIC || exit 1 - cat $INSTALL_PATH/docs/xvidcore/LICENSE >>$IO_LIC || exit 1 -fi +rm -f $IO_LIBS/{libgcc*,libstdc*,libbz2*,libfont*,libfree*,libpng*,libjpeg*,libtiff*,libz.*} +(cd $IO_LIBS ; + ln -sf ../../../lib/libbz2.so.1.0 . + ln -sf ../../../lib/libbz2.so.1 . + ln -sf ../../../lib/libfontconfig.so.1 . + ln -sf ../../../lib/libfreetype.so.6 . + ln -sf ../../../lib/libpng12.so.0 . + ln -sf ../../../lib/libjpeg.so.9 . + ln -sf ../../../lib/libtiff.so.5 . + ln -sf ../../../lib/libz.so.1 . + ln -sf ../../../lib/libgcc_s.so.1 . + ln -sf ../../../lib/libstdc++.so.6 . +) +strip -s $IO_LIBS/* # OFX MISC OFX_MISC_VERSION=$TAG @@ -249,9 +126,6 @@ OFX_MISC_PATH=$INSTALLER/packages/$MISCPLUG_PKG mkdir -p $OFX_MISC_PATH/data $OFX_MISC_PATH/meta $OFX_MISC_PATH/data/Plugins || exit 1 cat $XML/openfx-misc.xml | sed "s/_VERSION_/${OFX_MISC_VERSION}/;s/_DATE_/${DATE}/" > $OFX_MISC_PATH/meta/package.xml || exit 1 cat $QS/openfx-misc.qs > $OFX_MISC_PATH/meta/installscript.qs || exit 1 -cat $INSTALL_PATH/docs/openfx-misc/VERSION > $OFX_MISC_PATH/meta/ofx-misc-license.txt || exit 1 -echo "" >> $OFX_MISC_PATH/meta/ofx-misc-license.txt || exit 1 -cat $INSTALL_PATH/docs/openfx-misc/LICENSE >> $OFX_MISC_PATH/meta/ofx-misc-license.txt || exit 1 cp -a $INSTALL_PATH/Plugins/{CImg,Misc}.ofx.bundle $OFX_MISC_PATH/data/Plugins/ || exit 1 strip -s $OFX_MISC_PATH/data/Plugins/*/*/*/* CIMG_LIBS=$OFX_MISC_PATH/data/Plugins/CImg.ofx.bundle/Libraries @@ -261,8 +135,19 @@ OFX_CIMG_DEPENDS=$(ldd $OFX_MISC_PATH/data/Plugins/*/*/*/*|grep opt | awk '{prin for x in $OFX_CIMG_DEPENDS; do cp -v $x $CIMG_LIBS/ || exit 1 done -rm -f $CIMG_LIBS/{libgcc*,libstdc*} +rm -f $CIMG_LIBS/{libgcc*,libstdc*,libgomp*} +(cd $CIMG_LIBS ; + ln -sf ../../../lib/libgcc_s.so.1 . + ln -sf ../../../lib/libstdc++.so.6 . + ln -sf ../../../lib/libgomp.so.1 . +) strip -s $CIMG_LIBS/* +MISC_LIBS=$OFX_MISC_PATH/data/Plugins/Misc.ofx.bundle/Libraries +mkdir -p $MISC_LIBS || exit 1 +(cd $MISC_LIBS ; + ln -sf ../../../lib/libgcc_s.so.1 . + ln -sf ../../../lib/libstdc++.so.6 . +) # NATRON NATRON_PATH=$INSTALLER/packages/$NATRON_PKG @@ -278,10 +163,6 @@ if [ -f "$NATRON_PATH/data/bin/NatronCrashReporter" ]; then strip -s $NATRON_PATH/data/bin/NatronCrashReporter $NATRON_PATH/data/bin/NatronRendererCrashReporter fi -# most (all) distros has ffmpeg in the repository -#cp $INSTALL_PATH/ffmpeg-${FFLIC}/bin/ffmpeg $NATRON_PATH/data/bin/ || exit 1 -#cp $INSTALL_PATH/ffmpeg-${FFLIC}/bin/ffprobe $NATRON_PATH/data/bin/ || exit 1 - wget $NATRON_API_DOC || exit 1 mv natron.pdf $NATRON_PATH/data/docs/Natron_Python_API_Reference.pdf || exit 1 rm $NATRON_PATH/data/docs/TuttleOFX-README.txt || exit 1 @@ -323,28 +204,29 @@ if [ -f $INC_PATH/misc/compat${BIT}.tgz ]; then tar xvf $INC_PATH/misc/compat${BIT}.tgz -C $CLIBS_PATH/data/lib/ || exit 1 fi +cp $INSTALL_PATH/lib/{libicudata.so.55,libicui18n.so.55,libicuuc.so.55} $CLIBS_PATH/data/lib/ || exit 1 +(cd $CLIBS_PATH/data/lib; + rm -f libbz2.so.1.0 + ln -sf libbz2.so.1 libbz2.so.1.0 +) + +mv $IO_LIBS/{libOpenColor*,libgomp*} $CLIBS_PATH/data/lib/ || exit 1 +(cd $IO_LIBS ; + ln -sf ../../../lib/libOpenColorIO.so.1 . + ln -sf ../../../lib/libgomp.so.1 . +) + +mkdir -p $CLIBS_PATH/data/share/etc/fonts/conf.d || exit 1 +cp $INSTALL_PATH/etc/fonts/fonts.conf $CLIBS_PATH/data/share/etc/fonts/ || exit 1 +cp $INSTALL_PATH/share/fontconfig/conf.avail/* $CLIBS_PATH/data/share/etc/fonts/conf.d/ || exit 1 +sed -i "s#${SDK_PATH}/Natron-${SDK_VERSION}/#/#;/conf.d/d" $CLIBS_PATH/data/share/etc/fonts/fonts.conf || exit 1 +(cd $CLIBS_PATH/data ; ln -sf share Resources ) + # TODO: At this point send unstripped binaries (and debug binaries?) to Socorro server for breakpad strip -s $CLIBS_PATH/data/lib/* strip -s $CLIBS_PATH/data/bin/*/* -CORE_DOC=$CLIBS_PATH -cat $INSTALL_PATH/docs/boost/LICENSE_1_0.txt >> $CORE_DOC/meta/3rdparty-license.txt -cat $INSTALL_PATH/docs/cairo/COPYING-MPL-1.1 >> $CORE_DOC/meta/3rdparty-license.txt -cat $INSTALL_PATH/docs/glew/LICENSE.txt >> $CORE_DOC/meta/3rdparty-license.txt -cat $INSTALL_PATH/docs/jpeg/README >> $CORE_DOC/meta/3rdparty-license.txt -cat $INSTALL_PATH/docs/png/LICENSE >> $CORE_DOC/meta/3rdparty-license.txt -cat $INSTALL_PATH/docs/qt/*LGPL* >> $CORE_DOC/meta/3rdparty-license.txt -cat $INSTALL_PATH/docs/tiff/COPYRIGHT >> $CORE_DOC/meta/3rdparty-license.txt - -if [ "$PYV" = "3" ]; then - cat $INSTALL_PATH/docs/python3/LICENSE >> $CORE_DOC/meta/3rdparty-license.txt || exit 1 -else - cat $INSTALL_PATH/docs/python2/LICENSE >> $CORE_DOC/meta/3rdparty-license.txt || exit 1 -fi -cat $INSTALL_PATH/docs/pyside/* >> $CORE_DOC/meta/3rdparty-license.txt -cat $INSTALL_PATH/docs/shibroken/* >> $CORE_DOC/meta/3rdparty-license.txt - #Copy Python distrib mkdir -p $CLIBS_PATH/data/Plugins || exit 1 if [ "$PYV" = "3" ]; then @@ -358,7 +240,6 @@ else (cd $CLIBS_PATH/data/lib/python2.7/site-packages; ln -sf ../../../Plugins/PySide . ) rm -rf $CLIBS_PATH/data/lib/python2.7/{test,config} || exit 1 fi -#rm -f $CLIBS_PATH/data/Plugins/PySide/{QtDeclarative,QtHelp,QtScript,QtScriptTools,QtSql,QtTest,QtUiTools,QtXmlPatterns}.so || exit 1 PY_DEPENDS=$(ldd $CLIBS_PATH/data/Plugins/PySide/*|grep opt | awk '{print $3}') for y in $PY_DEPENDS; do cp -v $y $CLIBS_PATH/data/lib/ || exit 1 @@ -366,21 +247,22 @@ done (cd $CLIBS_PATH ; find . -type d -name __pycache__ -exec rm -rf {} \;) strip -s $CLIBS_PATH/data/Plugins/PySide/* $CLIBS_PATH/data/lib/python*/* $CLIBS_PATH/data/lib/python*/*/* +# let Natron.sh handle gcc libs +mkdir $CLIBS_PATH/data/lib/compat || exit 1 +mv $CLIBS_PATH/data/lib/{libgomp*,libgcc*,libstdc*} $CLIBS_PATH/data/lib/compat/ || exit 1 +if [ ! -f "$SRC_PATH/strings$BIT.tgz" ]; then + wget $THIRD_PARTY_SRC_URL/strings$BIT.tgz -O $SRC_PATH/strings$BIT.tgz || exit 1 +fi +tar xvf $SRC_PATH/strings$BIT.tgz -C $CLIBS_PATH/data/bin/ || exit 1 + # OFX ARENA OFX_ARENA_VERSION=$TAG OFX_ARENA_PATH=$INSTALLER/packages/$ARENAPLUG_PKG mkdir -p $OFX_ARENA_PATH/meta $OFX_ARENA_PATH/data/Plugins || exit 1 cat $XML/openfx-arena.xml | sed "s/_VERSION_/${OFX_ARENA_VERSION}/;s/_DATE_/${DATE}/" > $OFX_ARENA_PATH/meta/package.xml || exit 1 cat $QS/openfx-arena.qs > $OFX_ARENA_PATH/meta/installscript.qs || exit 1 -cat $INSTALL_PATH/docs/openfx-arena/VERSION > $OFX_ARENA_PATH/meta/ofx-extra-license.txt || exit 1 -echo "" >> $OFX_ARENA_PATH/meta/ofx-extra-license.txt || exit 1 -cat $INSTALL_PATH/docs/openfx-arena/LICENSE >> $OFX_ARENA_PATH/meta/ofx-extra-license.txt || exit 1 cp -av $INSTALL_PATH/Plugins/Arena.ofx.bundle $OFX_ARENA_PATH/data/Plugins/ || exit 1 strip -s $OFX_ARENA_PATH/data/Plugins/*/*/*/* -echo "ImageMagick License:" >> $OFX_ARENA_PATH/meta/ofx-extra-license.txt || exit 1 -cat $INSTALL_PATH/docs/imagemagick/LICENSE >> $OFX_ARENA_PATH/meta/ofx-extra-license.txt || exit 1 -echo "LCMS License:" >>$OFX_ARENA_PATH/meta/ofx-extra-license.txt || exit 1 -cat $INSTALL_PATH/docs/lcms/COPYING >>$OFX_ARENA_PATH/meta/ofx-extra-license.txt || exit 1 ARENA_LIBS=$OFX_ARENA_PATH/data/Plugins/Arena.ofx.bundle/Libraries mkdir -p $ARENA_LIBS || exit 1 @@ -388,8 +270,23 @@ OFX_ARENA_DEPENDS=$(ldd $OFX_ARENA_PATH/data/Plugins/*/*/*/*|grep opt | awk '{pr for x in $OFX_ARENA_DEPENDS; do cp -v $x $ARENA_LIBS/ || exit 1 done +rm -f $ARENA_LIBS/{libgomp*,libOpenColorIO*,libbz2*,libfont*,libz.so*,libglib-2*,libgthread*,libpng*,libfree*,libexpat*,libgcc*,libstdc*} +(cd $ARENA_LIBS ; + ln -sf ../../../lib/libbz2.so.1.0 . + ln -sf ../../../lib/libbz2.so.1 . + ln -sf ../../../lib/libexpat.so.1 . + ln -sf ../../../lib/libfontconfig.so.1 . + ln -sf ../../../lib/libfreetype.so.6 . + ln -sf ../../../lib/libglib-2.0.so.0 . + ln -sf ../../../lib/libgthread-2.0.so.0 . + ln -sf ../../../lib/libpng12.so.0 . + ln -sf ../../../lib/libz.so.1 . + ln -sf ../../../lib/libgcc_s.so.1 . + ln -sf ../../../lib/libstdc++.so.6 . + ln -sf ../../../lib/libOpenColorIO.so.1 . + ln -sf ../../../lib/libgomp.so.1 . +) strip -s $ARENA_LIBS/* -rm -f $ARENA_LIBS/{libcairo*,libgcc*,libstdc*} # OFX CV #OFX_CV_VERSION=$TAG @@ -420,37 +317,60 @@ rm -f $ARENA_LIBS/{libcairo*,libgcc*,libstdc*} chown root:root -R $INSTALLER/* (cd $INSTALLER; find . -type d -name .git -exec rm -rf {} \;) -# Build repo and package -if [ "$NO_INSTALLER" != "1" ]; then - if [ "$1" = "workshop" ]; then - ONLINE_TAG=snapshot - else - ONLINE_TAG=release - fi - - ONLINE_INSTALL=Natron-${PKGOS}-online-install-$ONLINE_TAG - BUNDLED_INSTALL=Natron-$NATRON_VERSION-${PKGOS} +# Build repo and packages - REPO_DIR=$REPO_DIR_PREFIX$ONLINE_TAG - rm -rf $REPO_DIR/packages $REPO_DIR/installers - - mkdir -p $REPO_DIR/packages || exit 1 +if [ "$1" = "workshop" ]; then + ONLINE_TAG=snapshot +else + ONLINE_TAG=release +fi +ONLINE_INSTALL=Natron-${PKGOS}-online-install-$ONLINE_TAG +BUNDLED_INSTALL=Natron-$NATRON_VERSION-${PKGOS} +REPO_DIR=$REPO_DIR_PREFIX$ONLINE_TAG +rm -rf $REPO_DIR/packages $REPO_DIR/installers +mkdir -p $REPO_DIR/{packages,installers} || exit 1 + +if [ "$TAR_BUILD" = "1" ]; then + TAR_INSTALL=$TMP_PATH/$BUNDLED_INSTALL + mkdir -p $TAR_INSTALL || exit 1 + cd $TAR_INSTALL || exit 1 + cp -a $INSTALLER/packages/fr.*/data/* . || exit 1 + cd .. || exit 1 + tar cvvJf $BUNDLED_INSTALL.tar.xz $BUNDLED_INSTALL || exit 1 + rm -rf $BUNDLED_INSTALL + ln -sf $BUNDLED_INSTALL.tar.xz Natron-latest-$PKGOS-$ONLINE_TAG.tar.xz || exit 1 + mv $BUNDLED_INSTALL.tar.xz Natron-latest-$PKGOS-$ONLINE_TAG.tar.xz $REPO_DIR/installers/ || exit 1 +fi +if [ "$NO_INSTALLER" != "1" ]; then $INSTALL_PATH/bin/repogen -v --update-new-components -p $INSTALLER/packages -c $INSTALLER/config/config.xml $REPO_DIR/packages || exit 1 - - mkdir -p $REPO_DIR/installers || exit 1 - + cd $REPO_DIR/installers || exit 1 if [ "$OFFLINE" != "0" ]; then $INSTALL_PATH/bin/binarycreator -v -f -p $INSTALLER/packages -c $INSTALLER/config/config.xml -i $PACKAGES $REPO_DIR/installers/$BUNDLED_INSTALL || exit 1 - cd $REPO_DIR/installers || exit 1 tar cvvzf $BUNDLED_INSTALL.tgz $BUNDLED_INSTALL || exit 1 ln -sf $BUNDLED_INSTALL.tgz Natron-latest-$PKGOS-$ONLINE_TAG.tgz || exit 1 fi - $INSTALL_PATH/bin/binarycreator -v -n -p $INSTALLER/packages -c $INSTALLER/config/config.xml $ONLINE_INSTALL || exit 1 tar cvvzf $ONLINE_INSTALL.tgz $ONLINE_INSTALL || exit 1 fi +if [ "$RPM_BUILD" = "1" ]; then + if [ ! -f "/usr/bin/rpmbuild" ]; then + yum install -y rpmdevtools + fi + rm -rf ~/rpmbuild/* + echo "#!/bin/bash" > $INSTALLER/packages/fr.inria.natron/data/bin/postinstall.sh || exit 1 + echo "echo \"Checking GCC compatibility for Natron ...\"" >>$INSTALLER/packages/fr.inria.natron/data/bin/postinstall.sh || exit 1 + echo "DIR=/opt/Natron2" >> $INSTALLER/packages/fr.inria.natron/data/bin/postinstall.sh || exit 1 + cat $INSTALLER/packages/fr.inria.natron/data/Natron | sed '26,65!d' >> $INSTALLER/packages/fr.inria.natron/data/bin/postinstall.sh || exit 1 + chmod +x $INSTALLER/packages/fr.inria.natron/data/bin/postinstall.sh || exit 1 + sed -i '26,65d' $INSTALLER/packages/fr.inria.natron/data/Natron || exit 1 + sed -i '26,65d' $INSTALLER/packages/fr.inria.natron/data/NatronRenderer || exit 1 + cat $INC_PATH/natron/Natron.spec | sed "s/REPLACE_VERSION/`echo $NATRON_VERSION|sed 's/-/./g'`/" > $TMP_PATH/Natron.spec || exit 1 + rpmbuild -bb $TMP_PATH/Natron.spec || exit 1 + mv ~/rpmbuild/RPMS/*/Natron*.rpm $REPO_DIR/installers/ || exit 1 +fi + rm $REPO_DIR/installers/$ONLINE_INSTALL $REPO_DIR/installers/$BUNDLED_INSTALL echo "All Done!!!" diff --git a/tools/linux/include/scripts/build-plugins.sh b/tools/linux/include/scripts/build-plugins.sh index fb8a4228fa..0e45007b9f 100644 --- a/tools/linux/include/scripts/build-plugins.sh +++ b/tools/linux/include/scripts/build-plugins.sh @@ -61,11 +61,6 @@ else export LD_LIBRARY_PATH=$INSTALL_PATH/gcc/lib:$LD_LIBRARY_PATH fi -if [ "$NATRON_LICENSE" != "GPL" ] && [ "$NATRON_LICENSE" != "COMMERCIAL" ]; then - echo "Please select a License with NATRON_LICENSE=(GPL,COMMERCIAL)" - exit 1 -fi - if [ "$NATRON_LICENSE" = "GPL" ]; then export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$INSTALL_PATH/ffmpeg-gpl/lib/pkgconfig export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$INSTALL_PATH/ffmpeg-gpl/lib diff --git a/tools/linux/include/scripts/build-sdk.sh b/tools/linux/include/scripts/build-sdk.sh index f5d7e7ace2..3e51ee9d63 100644 --- a/tools/linux/include/scripts/build-sdk.sh +++ b/tools/linux/include/scripts/build-sdk.sh @@ -148,6 +148,37 @@ else fi export LD_LIBRARY_PATH +# Install zlib +if [ ! -f "$INSTALL_PATH/lib/libz.so.1" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$ZLIB_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$ZLIB_TAR" -O "$SRC_PATH/$ZLIB_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$ZLIB_TAR" || exit 1 + cd zlib* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install bzip +if [ ! -f "$INSTALL_PATH/lib/libbz2.so" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$BZIP_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$BZIP_TAR" -O "$SRC_PATH/$BZIP_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$BZIP_TAR" || exit 1 + cd bzip* || exit 1 + sed -e 's/^CFLAGS=\(.*\)$/CFLAGS=\1 \$(BIGFILES)/' -i ./Makefile-libbz2_so || exit 1 + make -f Makefile-libbz2_so || exit 1 + install -m755 libbz2.so.1.0.6 $INSTALL_PATH/lib || exit 1 + install -m644 bzlib.h $INSTALL_PATH/include/ || exit 1 + cd $INSTALL_PATH/lib || exit 1 + ln -s libbz2.so.1.0.6 libbz2.so || exit 1 + ln -s libbz2.so.1.0.6 libbz2.so.1 || exit 1 + ln -s libbz2.so.1.0.6 libbz2.so.1.0 || exit 1 +fi + # Install yasm if [ ! -f "$INSTALL_PATH/bin/yasm" ]; then cd "$TMP_PATH" || exit 1 @@ -174,6 +205,19 @@ if [ ! -f "$INSTALL_PATH/bin/cmake" ]; then make install || exit 1 fi +# Install icu +if [ ! -f "$INSTALL_PATH/bin/icu-config" ]; then + cd $TMP_PATH || exit 1 + if [ ! -f "$SRC_PATH/$ICU_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$ICU_TAR" -O "$SRC_PATH/$ICU_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$ICU_TAR" || exit 1 + cd icu/source || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + # Install Python2 if [ ! -f "$INSTALL_PATH/lib/pkgconfig/python2.pc" ]; then cd "$TMP_PATH" || exit 1 @@ -206,8 +250,6 @@ fi # Setup env PKG_CONFIG_PATH="$INSTALL_PATH/lib/pkgconfig" -#LD_LIBRARY_PATH="$INSTALL_PATH/lib" -#PATH="$INSTALL_PATH/bin:$PATH" QTDIR="$INSTALL_PATH" BOOST_ROOT="$INSTALL_PATH" OPENJPEG_HOME="$INSTALL_PATH" @@ -217,6 +259,112 @@ PYTHON_PATH="$INSTALL_PATH/lib/python2.7" PYTHON_INCLUDE="$INSTALL_PATH/include/python2.7" export PKG_CONFIG_PATH LD_LIBRARY_PATH PATH QTDIR BOOST_ROOT OPENJPEG_HOME THIRD_PARTY_TOOLS_HOME PYTHON_HOME PYTHON_PATH PYTHON_INCLUDE +# Install expat +if [ ! -f "$INSTALL_PATH/lib/libexpat.so" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$EXPAT_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$EXPAT_TAR" -O "$SRC_PATH/$EXPAT_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$EXPAT_TAR" || exit 1 + cd expat* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install png +if [ ! -f $INSTALL_PATH/lib/pkgconfig/libpng.pc ]; then + cd $TMP_PATH || exit 1 + if [ ! -f $SRC_PATH/$PNG_TAR ]; then + wget $THIRD_PARTY_SRC_URL/$PNG_TAR -O $SRC_PATH/$PNG_TAR || exit 1 + fi + tar xvf $SRC_PATH/$PNG_TAR || exit 1 + cd libpng* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" CPPFLAGS="-I${INSTALL_PATH}/include" LDFLAGS="-L${INSTALL_PATH}/lib" ./configure --prefix=$INSTALL_PATH --libdir=$INSTALL_PATH/lib --enable-shared --enable-static || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 + mkdir -p $INSTALL_PATH/docs/png || exit 1 + cp LIC* COP* README AUTH* CONT* $INSTALL_PATH/docs/png/ +fi + +# Install freetype +if [ ! -f "$INSTALL_PATH/lib/libfreetype.so" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$FTYPE_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$FTYPE_TAR" -O "$SRC_PATH/$FTYPE_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$FTYPE_TAR" || exit 1 + cd freetype* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install fontconfig +if [ ! -f "$INSTALL_PATH/lib/libfontconfig.so" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$FCONFIG_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$FCONFIG_TAR" -O "$SRC_PATH/$FCONFIG_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$FCONFIG_TAR" || exit 1 + cd fontconfig* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install ffi +if [ ! -f "$INSTALL_PATH/lib/pkgconfig/libffi.pc" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$FFI_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$FFI_TAR" -O "$SRC_PATH/$FFI_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$FFI_TAR" || exit 1 + cd libffi* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install glib +if [ ! -f "$INSTALL_PATH/lib/pkgconfig/glib-2.0.pc" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$GLIB_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$GLIB_TAR" -O "$SRC_PATH/$GLIB_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$GLIB_TAR" || exit 1 + cd glib-2* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install xml +if [ ! -f "$INSTALL_PATH/lib/pkgconfig/libxml-2.0.pc" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$LIBXML_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$LIBXML_TAR" -O "$SRC_PATH/$LIBXML_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$LIBXML_TAR" || exit 1 + cd libxml* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared --without-python || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install xslt +if [ ! -f "$INSTALL_PATH/lib/libxslt.so.1" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$LIBXSLT_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$LIBXSLT_TAR" -O "$SRC_PATH/$LIBXSLT_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$LIBXSLT_TAR" || exit 1 + cd libxslt* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared --without-python || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + # Install boost if [ ! -f "$INSTALL_PATH/lib/libboost_atomic.so" ]; then cd "$TMP_PATH" || exit 1 @@ -247,21 +395,6 @@ if [ ! -f $INSTALL_PATH/lib/libjpeg.a ]; then cp LIC* COP* READ* AUTH* CONT* $INSTALL_PATH/docs/jpeg/ fi -# Install png -if [ ! -f $INSTALL_PATH/lib/pkgconfig/libpng.pc ]; then - cd $TMP_PATH || exit 1 - if [ ! -f $SRC_PATH/$PNG_TAR ]; then - wget $THIRD_PARTY_SRC_URL/$PNG_TAR -O $SRC_PATH/$PNG_TAR || exit 1 - fi - tar xvf $SRC_PATH/$PNG_TAR || exit 1 - cd libpng* || exit 1 - env CFLAGS="$BF" CXXFLAGS="$BF" CPPFLAGS="-I${INSTALL_PATH}/include" LDFLAGS="-L${INSTALL_PATH}/lib" ./configure --prefix=$INSTALL_PATH --libdir=$INSTALL_PATH/lib --enable-shared --enable-static || exit 1 - make -j${MKJOBS} || exit 1 - make install || exit 1 - mkdir -p $INSTALL_PATH/docs/png || exit 1 - cp LIC* COP* README AUTH* CONT* $INSTALL_PATH/docs/png/ -fi - # Install tiff if [ ! -f $INSTALL_PATH/lib/pkgconfig/libtiff-4.pc ]; then cd $TMP_PATH || exit 1 @@ -365,6 +498,112 @@ if [ ! -f $INSTALL_PATH/lib/pkgconfig/OpenEXR.pc ]; then cp LIC* COP* README AUTH* CONT* $INSTALL_PATH/docs/openexr/ fi +# Install pixman +if [ ! -f $INSTALL_PATH/lib/pkgconfig/pixman-1.pc ]; then + if [ ! -f $SRC_PATH/$PIX_TAR ]; then + wget $THIRD_PARTY_SRC_URL/$PIX_TAR -O $SRC_PATH/$PIX_TAR || exit 1 + fi + tar xvf $SRC_PATH/$PIX_TAR || exit 1 + cd pixman-* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" CPPFLAGS="-I${INSTALL_PATH}/include" LDFLAGS="-L${INSTALL_PATH}/lib" ./configure --prefix=$INSTALL_PATH --libdir=$INSTALL_PATH/lib --disable-shared --enable-static || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 + mkdir -p $INSTALL_PATH/docs/pixman || exit 1 + cp COPYING* README AUTHORS $INSTALL_PATH/docs/pixman/ || exit 1 +fi + +# Install cairo +if [ ! -f $INSTALL_PATH/lib/pkgconfig/cairo.pc ]; then + cd $TMP_PATH || exit 1 + if [ ! -f $SRC_PATH/$CAIRO_TAR ]; then + wget $THIRD_PARTY_SRC_URL/$CAIRO_TAR -O $SRC_PATH/$CAIRO_TAR || exit 1 + fi + tar xvf $SRC_PATH/$CAIRO_TAR || exit 1 + cd cairo-* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" CPPFLAGS="-I${INSTALL_PATH}/include -I${INSTALL_PATH}/include/pixman-1" LDFLAGS="-L${INSTALL_PATH}/lib -lpixman-1" ./configure --prefix=$INSTALL_PATH --libdir=$INSTALL_PATH/lib --enable-shared --enable-static || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 + mkdir -p $INSTALL_PATH/docs/cairo || exit 1 + cp COPYING* README AUTHORS $INSTALL_PATH/docs/cairo/ || exit 1 +fi + +# Install harbuzz +if [ ! -f $INSTALL_PATH/lib/pkgconfig/harfbuzz.pc ]; then + cd $TMP_PATH || exit 1 + if [ ! -f $SRC_PATH/$BUZZ_TAR ]; then + wget $THIRD_PARTY_SRC_URL/$BUZZ_TAR -O $SRC_PATH/$BUZZ_TAR || exit 1 + fi + tar xvf $SRC_PATH/$BUZZ_TAR || exit 1 + cd harfbuzz-* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" CPPFLAGS="-I${INSTALL_PATH}/include" LDFLAGS="-L${INSTALL_PATH}/lib" ./configure --prefix=$INSTALL_PATH --libdir=$INSTALL_PATH/lib --disable-docs --enable-static --enable-shared --with-freetype --with-cairo --with-gobject --with-glib --with-icu || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 + mkdir -p $INSTALL_PATH/docs/cairo || exit 1 + cp COPYING* README AUTHORS $INSTALL_PATH/docs/cairo/ || exit 1 + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$FTYPE_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$FTYPE_TAR" -O "$SRC_PATH/$FTYPE_TAR" || exit 1 + fi + rm -rf freetype* + tar xvf "$SRC_PATH/$FTYPE_TAR" || exit 1 + cd freetype* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install pango +if [ ! -f "$INSTALL_PATH/lib/pkgconfig/pango.pc" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$PANGO_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$PANGO_TAR" -O "$SRC_PATH/$PANGO_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$PANGO_TAR" || exit 1 + cd pango-* || exit 1 + env FONTCONFIG_CFLAGS="-I$INSTALL_PATH/include" FONTCONFIG_LIBS="-L$INSTALL_PATH/lib -lfontconfig" CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared --with-included-modules=basic-fc || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install croco +if [ ! -f "$INSTALL_PATH/lib/libcroco-0.6.so.3" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$CROCO_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$CROCO_TAR" -O "$SRC_PATH/$CROCO_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$CROCO_TAR" || exit 1 + cd libcroco-* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install gdk +if [ ! -f "$INSTALL_PATH/lib/pkgconfig/gdk-pixbuf-2.0.pc" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$GDK_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$GDK_TAR" -O "$SRC_PATH/$GDK_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$GDK_TAR" || exit 1 + cd gdk-pix* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" CPPFLAGS="-I${INSTALL_PATH}/include" LDFLAGS="-L${INSTALL_PATH}/lib" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + +# Install svg +if [ ! -f "$INSTALL_PATH/lib/pkgconfig/librsvg-2.0.pc" ]; then + cd "$TMP_PATH" || exit 1 + if [ ! -f "$SRC_PATH/$SVG_TAR" ]; then + wget "$THIRD_PARTY_SRC_URL/$SVG_TAR" -O "$SRC_PATH/$SVG_TAR" || exit 1 + fi + tar xvf "$SRC_PATH/$SVG_TAR" || exit 1 + cd librsvg-* || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --disable-docs --enable-static --enable-shared --disable-introspection --disable-vala --disable-pixbuf-loader || exit 1 + make -j${MKJOBS} || exit 1 + make install || exit 1 +fi + # Install magick if [ "$REBUILD_MAGICK" = "1" ]; then rm -rf $INSTALL_PATH/include/ImageMagick-6/ $INSTALL_PATH/lib/libMagick* $INSTALL_PATH/share/ImageMagick-6/ $INSTALL_PATH/lib/pkgconfig/{Image,Magick}* @@ -401,35 +640,6 @@ if [ ! -f $INSTALL_PATH/lib/pkgconfig/glew.pc ]; then cp LICENSE.txt README.txt $INSTALL_PATH/docs/glew/ || exit 1 fi -# Install pixman -if [ ! -f $INSTALL_PATH/lib/pkgconfig/pixman-1.pc ]; then - if [ ! -f $SRC_PATH/$PIX_TAR ]; then - wget $THIRD_PARTY_SRC_URL/$PIX_TAR -O $SRC_PATH/$PIX_TAR || exit 1 - fi - tar xvf $SRC_PATH/$PIX_TAR || exit 1 - cd pixman-* || exit 1 - env CFLAGS="$BF" CXXFLAGS="$BF" CPPFLAGS="-I${INSTALL_PATH}/include" LDFLAGS="-L${INSTALL_PATH}/lib" ./configure --prefix=$INSTALL_PATH --libdir=$INSTALL_PATH/lib --disable-shared --enable-static || exit 1 - make -j${MKJOBS} || exit 1 - make install || exit 1 - mkdir -p $INSTALL_PATH/docs/pixman || exit 1 - cp COPYING* README AUTHORS $INSTALL_PATH/docs/pixman/ || exit 1 -fi - -# Install cairo -if [ ! -f $INSTALL_PATH/lib/pkgconfig/cairo.pc ]; then - cd $TMP_PATH || exit 1 - if [ ! -f $SRC_PATH/$CAIRO_TAR ]; then - wget $THIRD_PARTY_SRC_URL/$CAIRO_TAR -O $SRC_PATH/$CAIRO_TAR || exit 1 - fi - tar xvf $SRC_PATH/$CAIRO_TAR || exit 1 - cd cairo-* || exit 1 - env CFLAGS="$BF" CXXFLAGS="$BF" CPPFLAGS="-I${INSTALL_PATH}/include -I${INSTALL_PATH}/include/pixman-1" LDFLAGS="-L${INSTALL_PATH}/lib -lpixman-1" ./configure --prefix=$INSTALL_PATH --libdir=$INSTALL_PATH/lib --enable-shared --enable-static || exit 1 - make -j${MKJOBS} || exit 1 - make install || exit 1 - mkdir -p $INSTALL_PATH/docs/cairo || exit 1 - cp COPYING* README AUTHORS $INSTALL_PATH/docs/cairo/ || exit 1 -fi - # Install ocio if [ ! -f $INSTALL_PATH/lib/libOpenColorIO.so ]; then cd $TMP_PATH || exit 1 @@ -717,7 +927,7 @@ if [ ! -f $INSTALL_PATH/bin/qmake ]; then QT_CONF="-no-openssl -opengl desktop -opensource -nomake examples -nomake tests -release -no-gtkstyle -confirm-license -no-c++11 -I${INSTALL_PATH}/include -L${INSTALL_PATH}/lib" else QT_TAR=$QT4_TAR - QT_CONF="-no-multimedia -no-openssl -confirm-license -release -opensource -opengl desktop -nomake demos -nomake docs -nomake examples -no-gtkstyle -I${INSTALL_PATH}/include -L${INSTALL_PATH}/lib" + QT_CONF="-xrender -xrandr -xcursor -xfixes -xinerama -fontconfig -xinput -sm -no-multimedia -no-openssl -confirm-license -release -opensource -opengl desktop -nomake demos -nomake docs -nomake examples -no-gtkstyle -I${INSTALL_PATH}/include -L${INSTALL_PATH}/lib" fi if [ ! -f $SRC_PATH/$QT_TAR ]; then diff --git a/tools/linux/include/scripts/ffmpeg.sh b/tools/linux/include/scripts/ffmpeg.sh new file mode 100644 index 0000000000..08248fb0e5 --- /dev/null +++ b/tools/linux/include/scripts/ffmpeg.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ]; do + SOURCEDIR=`dirname "$SOURCE"` + DIR=`cd -P "$SOURCEDIR" && pwd` + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" +done +SOURCEDIR=`dirname "$SOURCE"` +DIR=`cd -P "$SOURCEDIR" && pwd` + +export LD_LIBRARY_PATH="$DIR/lib:$DIR/Plugins/IO.ofx.bundle/Libraries" +"$DIR/bin/ffmpeg" "$@" diff --git a/tools/linux/include/scripts/setup-centos6.sh b/tools/linux/include/scripts/setup-centos6.sh index 4713b88415..463ef7f159 100644 --- a/tools/linux/include/scripts/setup-centos6.sh +++ b/tools/linux/include/scripts/setup-centos6.sh @@ -2,7 +2,7 @@ rm -f /etc/yum.repos.d/CentOS-Base.repo || exit 1 sed -i 's#baseurl=file:///media/CentOS/#baseurl=http://vault.centos.org/6.4/os/$basearch/#;s/enabled=0/enabled=1/;s/gpgcheck=1/gpgcheck=0/;/file:/d' /etc/yum.repos.d/CentOS-Media.repo || exit 1 -yum -y install make wget glibc-devel glibc-devel.i686 vixie-cron crontabs libxslt-devel pango-devel librsvg2-devel libxml2-devel unzip rsync git screen file tree bc gcc-c++ kernel-devel libX*devel fontconfig-devel freetype-devel zlib-devel *GL*devel *xcb*devel xorg*devel libdrm-devel mesa*devel *glut*devel dbus-devel xz patch bzip2-devel glib2-devel bison flex expat-devel libtool-ltdl-devel || exit 1 +yum -y install gettext make wget glibc-devel glibc-devel.i686 vixie-cron crontabs unzip rsync git screen file tree bc gcc-c++ kernel-devel libX*devel *GL*devel *xcb*devel xorg*devel libdrm-devel mesa*devel *glut*devel dbus-devel xz patch bison flex libtool-ltdl-devel || exit 1 chkconfig crond on service crond start diff --git a/tools/linux/include/xml/corelibs.xml b/tools/linux/include/xml/corelibs.xml index 07e6fc56b1..bff5f85f07 100644 --- a/tools/linux/include/xml/corelibs.xml +++ b/tools/linux/include/xml/corelibs.xml @@ -1,14 +1,10 @@ Natron Libraries - Libraries needed by Natron. + Libraries needed by Natron and plugins. _VERSION_ _DATE_ fr.inria.natron.libs - - - - script diff --git a/tools/linux/include/xml/openfx-arena.xml b/tools/linux/include/xml/openfx-arena.xml index c9b4f43952..557a517280 100644 --- a/tools/linux/include/xml/openfx-arena.xml +++ b/tools/linux/include/xml/openfx-arena.xml @@ -6,10 +6,6 @@ _DATE_ fr.inria.openfx.extra fr.inria.natron - - - - script diff --git a/tools/linux/include/xml/openfx-io.xml b/tools/linux/include/xml/openfx-io.xml index a5fbbde3a4..699b9af2ae 100644 --- a/tools/linux/include/xml/openfx-io.xml +++ b/tools/linux/include/xml/openfx-io.xml @@ -6,9 +6,6 @@ _DATE_ fr.inria.openfx.io fr.inria.natron - - - 1234 script diff --git a/tools/linux/include/xml/openfx-misc.xml b/tools/linux/include/xml/openfx-misc.xml index cc4e464ef1..046c7093cb 100644 --- a/tools/linux/include/xml/openfx-misc.xml +++ b/tools/linux/include/xml/openfx-misc.xml @@ -6,9 +6,6 @@ _DATE_ fr.inria.openfx.misc fr.inria.natron - - - 1234 script diff --git a/tools/linux/snapshot.sh b/tools/linux/snapshot.sh index 755c9f2893..07ff63af4c 100644 --- a/tools/linux/snapshot.sh +++ b/tools/linux/snapshot.sh @@ -24,6 +24,8 @@ #Usage snapshot.sh noThreads #Options: #NO_LOOP=1: Only build once and then exits. +#NATRON_LICENSE=(GPL,COMMERCIAL) +#Usage example: MKJOBS=8 NATRON_LICENSE=GPL sh snapshot.sh #Easier to debug set -x @@ -181,7 +183,7 @@ BUILD_CV=0 cd $CWD || exit 1 if [ "$FAIL" != "1" ]; then if [ "$BUILD_NATRON" = "1" -o "$BUILD_IO" = "1" -o "$BUILD_MISC" = "1" -o "$BUILD_ARENA" = "1" -o "$BUILD_CV" = "1" ]; then - NATRON_LICENSE=GPL OFFLINE_INSTALLER=1 SYNC=1 NOCLEAN=1 SNAPSHOT=1 sh build.sh workshop $JOBS + NATRON_LICENSE=$NATRON_LICENSE OFFLINE_INSTALLER=1 SYNC=1 NOCLEAN=1 SNAPSHOT=1 sh build.sh workshop $JOBS fi fi